Add Android client-server architecture plan

ober

8d2c3becee646b3d762316ba5723aedc8202ff9a

diff --git a/android/PLAN.md b/android/PLAN.md
new file mode 100644
index 0000000..1e87f18
--- /dev/null
+++ b/android/PLAN.md
@@ -0,0 +1,242 @@
+# Android Client-Server Architecture Plan
+
+*Replace embedded-binary APK with thin GUI client connecting to jcode serve in Termux over authenticated localhost.*
+
+## Problem
+
+The current APK embeds the jcode binary and runs it in Android's app sandbox. This sandbox blocks access to Termux's filesystem, binaries, and environment — meaning no git, no jerboa-mcp, no TypeScript compiler, no ssh-agent, no access to anything the user has installed in Termux. Every new tool requires rebundling the APK.
+
+## Solution
+
+Split into two components:
+
+- **jcode serve** (Termux): long-running server on localhost, full access to Termux environment
+- **jcode APK** (Android): thin GUI client that connects over TCP, no embedded binary
+
+## Architecture
+
+```
+┌─────────────────────────────────┐
+│         Android APK             │
+│  ┌───────────┐ ┌─────────────┐  │
+│  │ MainActivity│ │SettingsActivity│
+│  │  Chat UI    │ │ Host/Port   │  │
+│  │  Build/Plan │ │ Auth Token  │  │
+│  └──────┬──────┘ └─────────────┘  │
+│         │                         │
+│   JcodeClient.kt                  │
+│   (Socket to 127.0.0.1:PORT)      │
+│   JSONL over TCP                  │
+└─────────┬───────────────────────┘
+          │ localhost only
+┌─────────┴───────────────────────┐
+│       Termux: jcode serve       │
+│                                 │
+│  TCP listener (127.0.0.1:PORT)  │
+│  Token-based auth               │
+│  Same JSONL protocol as today   │
+│                                 │
+│  Full Termux environment:       │
+│  ├── git, ssh-agent             │
+│  ├── scheme (Chez), jerboa-mcp  │
+│  ├── node, tsc                  │
+│  ├── python, cargo, etc.        │
+│  └── all user-installed tools   │
+└─────────────────────────────────┘
+```
+
+## Security
+
+### Threat Model
+
+Other apps on the same Android device can connect to localhost ports. The jcode server must reject unauthorized connections.
+
+### Mitigations
+
+1. **Bind 127.0.0.1 only** — not reachable from the network, only from apps on this device.
+
+2. **Pre-shared bearer token** — on first run, `jcode serve` generates a cryptographically random 256-bit token (hex-encoded, 64 chars) and writes it to `~/.jcode/server-token`. The user enters this token once in the Android app's Settings screen.
+
+3. **Auth handshake** — the first message from any TCP client must be `{"type":"auth","token":"<token>"}`. The server validates it against the stored token. On mismatch: log a warning, send `{"type":"error","message":"auth failed"}`, close the socket. No further communication occurs.
+
+4. **Single-client mode** — the server accepts one authenticated client at a time. A second connection attempt while a client is active is rejected. This prevents session hijacking if another app somehow obtains the token.
+
+5. **Token rotation** — `jcode serve --rotate-token` generates a new token and prints it. The old token is immediately invalidated.
+
+### What This Does NOT Protect Against
+
+- A rooted device or debuggable app that can read `~/.jcode/server-token`
+- A malicious app that has already compromised Termux's uid
+- These are out of scope — if Termux is compromised, the attacker already has the user's SSH keys, API keys, and full filesystem access.
+
+## Server Component (Scheme)
+
+### Changes to `src/jcode/ui/serve.ss`
+
+Currently `serve-main` reads/writes stdio. Add a `--port PORT` flag:
+
+- Without `--port`: behave as today (stdio mode, for embedded APK backward compat)
+- With `--port PORT`: open a TCP listener on `127.0.0.1:PORT`
+  - On connection: read first message, validate auth token
+  - After auth: enter the same JSONL event loop as stdio mode
+  - On disconnect: return to listening state (accept next client)
+  - On SIGTERM/SIGINT: clean shutdown
+
+### Token Management
+
+- On first run with `--port`, if `~/.jcode/server-token` does not exist, generate one and print it to stderr for the user to copy.
+- Read token from `~/.jcode/server-token` (one line, hex string, no newline).
+- `--rotate-token` flag: overwrite file with new random token, print to stderr, exit.
+
+### Protocol Changes
+
+The JSONL protocol is unchanged except for the initial auth handshake:
+
+```
+Client → Server: {"type":"auth","token":"a1b2c3..."}
+Server → Client: {"type":"auth_ok"}        (on success)
+Server → Client: {"type":"error","message":"auth failed"}  (on failure, then close)
+
+... then normal JSONL protocol as today:
+Client → Server: {"type":"user","text":"...","mode":"build"}
+Server → Client: {"type":"ready","session_id":"...","session_title":"..."}
+Server → Client: {"type":"token","text":"..."}
+...
+```
+
+### Startup
+
+Typical Termux workflow:
+
+```
+$ jcode serve --port 8321
+[INFO] token: a1b2c3d4...  (copy this to the Android app)
+[INFO] listening on 127.0.0.1:8321
+[INFO] waiting for client...
+```
+
+On subsequent runs the token is reused silently (no reprint unless `--show-token`).
+
+## APK Component (Kotlin)
+
+### JcodeClient.kt (replaces JcodeProcess.kt)
+
+Replace `ProcessBuilder`-based subprocess management with TCP socket connection:
+
+- Connect to `host:port` (default `127.0.0.1:8321`, configurable in Settings)
+- Send auth message with stored token
+- Wait for `auth_ok` response
+- Enter same JSONL read/write loop as current `JcodeProcess` reader/writer threads
+- On socket disconnect: show "Disconnected — is jcode serve running in Termux?" with a Reconnect button
+- Reconnect with exponential backoff (1s, 2s, 4s, max 10s)
+
+### SettingsActivity.kt Changes
+
+Add fields:
+- **Server host** — text input, default `127.0.0.1`
+- **Server port** — number input, default `8321`
+- **Auth token** — text input (paste the 64-char hex token from Termux)
+- **Connection status** — indicator (connected/disconnected/auth failed)
+
+Remove:
+- The jcode.json / API key picker (keys are now on the server side in Termux's jcode.json)
+
+### MainActivity.kt Changes
+
+- On launch: attempt to connect to configured host:port
+- Show connection state in the toolbar (green dot = connected, red = disconnected)
+- If not connected: show a banner with instructions ("Start jcode serve in Termux")
+- Remove all JcodeProcess subprocess logic
+
+### APK Build Changes (build-apk.sh)
+
+Remove from the build:
+- jcode binary bundling (`libjcode.so`)
+- `chez_sqlite_shim.so` bundling
+- Termux shared lib staging (`assets/native/`, libncursesw, libiconv, libsqlite3, libz)
+- The binary verification step (`file` check for ARM aarch64)
+- The `JCODE_BINARY` variable and its existence check
+
+The APK becomes pure Kotlin — no native code, no `extractNativeLibs`. Expected size: well under 1MB.
+
+### Manifest Changes
+
+- Remove `android:extractNativeLibs="true"` (no native libs)
+- Keep `android:debuggable="true"` for dev builds (remove before any real distribution)
+- Keep INTERNET permission (needed for localhost socket)
+
+## Implementation Order
+
+### Phase 1: Server-side TCP listener
+1. Add `--port PORT` flag parsing to `serve-main`
+2. Implement TCP listener with `(std net tcp)` or raw Chez `tcp-connect`/`tcp-listen`
+3. Implement token generation, storage, and validation
+4. Auth handshake: validate first message, send `auth_ok` or reject
+5. After auth: reuse existing `serve-loop` with TCP streams instead of stdio
+6. Test from Termux: `echo '{"type":"auth","token":"..."}' | nc 127.0.0.1 8321`
+
+### Phase 2: APK client refactor
+1. Write `JcodeClient.kt` — Socket-based JSONL with auth handshake
+2. Update `SettingsActivity.kt` — host/port/token fields, remove key picker
+3. Update `MainActivity.kt` — connection state UI, remove subprocess logic
+4. Strip `build-apk.sh` — remove all native binary/lib bundling
+5. Build, install via `adb install`, test against Phase 1 server
+
+### Phase 3: Polish
+1. Auto-reconnect on disconnect with backoff
+2. Connection status indicator in toolbar
+3. "Server not running" banner with clear instructions
+4. `jcode serve --background` or Termux:Boot integration for auto-start
+5. Optional: mDNS/zeroconf discovery so the app finds the server without manual port config
+
+## MCP Integration
+
+With jcode serve running in Termux, jerboa-mcp (and any other MCP server) works exactly as it does on desktop:
+
+```json
+// In Termux's ~/.jcode/config.json or jcode.json:
+{
+  "mcpServers": {
+    "jerboa": {
+      "command": "scheme",
+      "args": ["--libdirs", "/path/to/jerboa-mcp/lib", "--script", "/path/to/jerboa-mcp/main.ss"]
+    }
+  }
+}
+```
+
+jcode serve spawns the MCP server as a stdio subprocess (existing `init-mcp-tools` code), registers its tools, and they appear in the LLM's tool list. The Android app doesn't need to know or care — it just renders tool call events like any other.
+
+## Git / SSH Integration
+
+With jcode serve running in Termux:
+
+- `git` is in PATH — jcode's bash tool can clone, pull, push, branch, etc.
+- If the user has `ssh-agent` running and keys loaded (`ssh-add`), git SSH operations just work — jcode serve inherits `SSH_AUTH_SOCK` from the Termux environment.
+- Working directory for agent sessions is any Termux-accessible path, not the sandboxed app dir.
+
+Typical user flow:
+1. In Termux: `eval $(ssh-agent) && ssh-add ~/.ssh/id_ed25519`
+2. In Termux: `cd ~/projects && jcode serve --port 8321`
+3. Open jcode app: "Clone https://github.com/user/repo into ~/projects/repo"
+4. jcode runs `git clone ...` via bash tool — works because git and SSH are available.
+
+## File Sizes (estimated)
+
+| Component | Before | After |
+|-----------|--------|-------|
+| APK | 16 MB | < 1 MB |
+| Termux server | (embedded) | 0 (already installed as `jcode` binary) |
+| Total on device | 16 MB | < 1 MB + existing jcode |
+
+## Open Questions
+
+1. **Multiple clients** — should the server support multiple simultaneous APK connections (e.g., tablet + phone)? Current plan says no (single-client). Could revisit.
+
+2. **Session persistence** — sessions are stored in Termux's SQLite DB. The app is stateless. Is this fine, or should the app cache recent messages for offline viewing?
+
+3. **Notifications** — should the APK show a notification when a long-running agent turn completes while the app is backgrounded? Requires a foreground service.
+
+4. **Auto-start** — should we provide a Termux:Boot script to auto-start `jcode serve` on device boot?
+
+5. **TLS** — localhost doesn't need encryption (no network exposure), but adding TLS with a self-signed cert would prevent other local apps from even seeing the traffic via packet capture. Low priority.