byteclaw/README.md

141 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ByteClaw
Schlanker, selbst gehosteter KI-Assistent: **Go-Backend** (ein statisches Binary, kein CGO) + **nativer Android-Client** (Kotlin, Jetpack Compose).
## Architektur
```
Android-App (Compose) Go-Backend (cmd/byteclaw)
├─ Einstellungen (EncryptedSharedPrefs) ├─ internal/server HTTP-API + WebSocket, Bearer-Token, Origin-Check
├─ Chat ──── WebSocket /api/ws ───────▶ ├─ internal/agent Agent-Loop mit Tool-Calling, Lock pro Session
└─ Sessions ─ REST /api/sessions ──────▶ ├─ internal/llm Provider-Interface + OpenAI-kompatibler Streaming-Provider
├─ internal/tools time_now, web_fetch, file_read/_write/_list
├─ internal/store SQLite (modernc.org/sqlite, pure Go)
└─ internal/config YAML/JSON + Umgebungsvariablen
```
Ablauf einer Nachricht: Client → `/api/ws` → Agent speichert User-Nachricht → LLM (Streaming) → bei Tool-Calls werden Tools ausgeführt, Ergebnisse gespeichert und das LLM erneut gefragt (max. `agent.max_iterations`) → Tokens werden live als `delta`-Events gestreamt, am Ende `done`.
Abhängigkeiten des Backends: `gopkg.in/yaml.v3`, `modernc.org/sqlite`, `github.com/coder/websocket`.
## Sichere Defaults
- Start verweigert ohne `auth_token` (mind. 16 Zeichen); Vergleich in konstanter Zeit.
- Lauscht standardmäßig nur auf `127.0.0.1:8787`.
- Browser-Origins: nur gleicher Host oder explizit in `allowed_origins`; `*` muss bewusst gesetzt werden. Native Clients (ohne `Origin`-Header) brauchen trotzdem das Token.
- TLS-Zertifikatsprüfung ist überall an (LLM-Provider, `web_fetch`); es gibt keinen Schalter zum Abschalten.
- `web_fetch`: nur http(s), Timeout, Größenlimit, max. 5 Redirects, blockiert Loopback/Private/Link-Local/CGNAT-Adressen beim Verbindungsaufbau (schützt auch gegen DNS-Rebinding und Redirect-Tricks). Abschaltbar per `allow_private`.
- Datei-Tools sind per `os.Root` auf den Workspace beschränkt (kein `..`, keine absoluten Pfade, keine Symlink-Ausbrüche) und größenbegrenzt.
- Kein Shell-Tool.
- Docker: distroless, User `65532` (nonroot), Go-Version fixiert.
- Android: Release-Builds erlauben nur HTTPS/WSS; Klartext (http/ws) nur im Debug-Build für lokale Entwicklung. Token/URL verschlüsselt per Android Keystore.
## Quickstart Backend
```bash
cp config.example.yaml config.yaml
export BYTECLAW_AUTH_TOKEN=$(openssl rand -hex 32)
export BYTECLAW_LLM_API_KEY=sk-... # OpenAI / OpenRouter; für Ollama leer lassen
make run # = go build + ./bin/byteclaw -config config.yaml
```
Provider-Presets (`llm.provider` bzw. `BYTECLAW_LLM_PROVIDER`). Ein Preset füllt `base_url` und `model`, wenn sie leer sind; explizite Werte haben Vorrang. Der API-Key fällt auf die genannte Umgebungsvariable zurück. Alle nutzen `Authorization: Bearer <key>` und `/chat/completions`.
| provider | base_url | Default-Modell | Key-Env | Quelle |
|---------------|-----------------------------------|--------------------------|-----------------------|--------|
| `openai` | https://api.openai.com/v1 | gpt-4o-mini | OPENAI_API_KEY | platform.openai.com |
| `openrouter` | https://openrouter.ai/api/v1 | openai/gpt-4o-mini | OPENROUTER_API_KEY | openrouter.ai/docs |
| `ollama` | http://localhost:11434/v1 | llama3.1 | – | ollama docs |
| `dahl` | https://inference.dahl.global/v1 | MiniMaxAI/MiniMax-M2.7 | DAHL_API_KEY | https://docs.dahl.global/ |
| `gonkarouter` | https://api.gonkarouter.io/v1 | MiniMaxAI/MiniMax-M2.7 | GONKAROUTER_API_KEY | https://gonkarouter.io/docs |
| `custom` | (Pflicht) | (Pflicht) | – | – |
**Dahl** (dahl.global) und **GonkaRouter** sind OpenAI-kompatible Inferenz-Dienste für offene Modelle auf dem dezentralen Gonka-GPU-Netz. Weitere Modelle (Stand 25.10.2026): `zai-org/GLM-5.3-Flash`, `deepseek-ai/DeepSeek-V4-Flash-0731`. Aktuelle IDs immer über `GET <base_url>/models` prüfen. Bei Dahl braucht der Key ein zugewiesenes Token-Guthaben (sonst HTTP 402).
```yaml
llm:
provider: dahl # oder gonkarouter
model: zai-org/GLM-5.3-Flash # optional
```
`GET /api/info` liefert Provider, Modell und base_url des Servers (ohne Key); die Android-App zeigt das bei „Verbindung testen" an.
Test:
```bash
curl localhost:8787/healthz
curl -H "Authorization: Bearer $BYTECLAW_AUTH_TOKEN" -d '{"text":"Wie spät ist es in Berlin?"}' localhost:8787/api/chat
```
### API
| Methode | Pfad | Zweck |
|---------|------------------------------------|------------------------------------------|
| GET | /healthz | Health (ohne Auth) |
| GET | /api/info | Version, Provider, Modell (ohne Secrets) |
| GET | /api/sessions | Sessions (neueste zuerst) |
| POST | /api/sessions | Session anlegen `{"title":""}` |
| GET/PATCH/DELETE | /api/sessions/{id} | Lesen / umbenennen / löschen |
| GET | /api/sessions/{id}/messages | Verlauf |
| POST | /api/chat | Nicht-streamend `{"session_id"?, "text"}`|
| GET | /api/ws | WebSocket-Streaming |
Alle `/api/*` brauchen `Authorization: Bearer <token>`.
WebSocket: Client sendet `{"type":"message","session_id":"<optional>","text":"..."}`; Server sendet Events
`session`, `delta`, `tool_call`, `tool_result`, `done` oder `error` (`{"type","text","tool","args","call_id","session_id"}`). `{"type":"ping"}` → `pong`.
### Build, Tests, Cross-Builds
```bash
make # vet + test + build
make race # Tests mit Race-Detector (benötigt CGO)
make cross # dist/: linux-amd64, linux-arm64, linux-armv7, termux-arm64, termux-armv7
make docker
```
Termux: `byteclaw-termux-arm64` ist ein `GOOS=android`-Build; für armv7 wird ein statisches `linux/arm`-Binary gebaut, da `android/arm` ohne NDK/CGO nicht linkbar ist. In Termux z. B. nach `$PREFIX/bin` kopieren, `chmod +x` und starten.
### Docker
```bash
docker build -t byteclaw .
docker run -d -p 127.0.0.1:8787:8787 -v byteclaw-data:/data \
-e BYTECLAW_AUTH_TOKEN=... -e BYTECLAW_LLM_API_KEY=... byteclaw
```
Im Container lauscht der Server auf `0.0.0.0:8787`; nach außen nur hinter einem TLS-Reverse-Proxy (Caddy, Traefik) oder in einem VPN (z. B. Tailscale) freigeben.
## Android-Client
```
android/
├─ app/src/main/java/dev/byteclaw/client/
│ ├─ MainActivity.kt Navigation (Chat, Sessions, Einstellungen)
│ ├─ data/ByteClawApi.kt OkHttp REST + WebSocket-Flow
│ ├─ data/SettingsStore.kt EncryptedSharedPreferences
│ ├─ data/Models.kt kotlinx.serialization
│ └─ ui/ ChatViewModel, Compose-Screens
└─ gradle/libs.versions.toml Versionskatalog (AGP 8.7.3, Kotlin 2.0.21, compileSdk 35, minSdk 26)
```
Build (JDK 17 + Android SDK, `ANDROID_HOME` oder `local.properties` mit `sdk.dir`):
```bash
cd android
./gradlew assembleDebug # app/build/outputs/apk/debug/app-debug.apk
./gradlew testDebugUnitTest
./gradlew assembleRelease # unsigniert; Signing-Config fehlt noch
```
In der App unter Einstellungen Server-URL (`https://…`; im Debug-Build auch `http://10.0.2.2:8787` für den Emulator) und Token eintragen, „Verbindung testen", speichern.
## Nächste Schritte
- Release-Signing, App-Icon, CI (GitHub Actions) für Go + Android
- Session-Kontext zusammenfassen statt nur Fenster der letzten N Nachrichten
- Weitere Provider (Anthropic nativ), Abbrechen laufender Antworten über WebSocket
- Optional: Einrichtung per QR-Code (URL + Token)
## Vorschau
Screenshots mit Demo-Daten (Paparazzi, ohne Emulator) liegen in `preview/` (hell/dunkel). Neu erzeugen: `cd android && ./gradlew recordPaparazziDebug`.