3A native Android client (Kotlin + Jetpack Compose) for the [`opencode serve`](https://opencode.ai/docs/server/) HTTP API.
5Application id: `soy.iko.opencode`
7## Set up a server and connect to it
9This app is a client for the [`opencode serve`](https://opencode.ai/docs/server/) HTTP API, so you
10need an opencode server running somewhere reachable from your device.
12### 1. Start the server
14On the host that has your project and opencode installed:
17opencode serve --hostname 0.0.0.0 --port 4096
22| Flag | Default | Description |
24| `--port` | `4096` | Port to listen on |
25| `--hostname` | `127.0.0.1` | Hostname/interface to bind (`0.0.0.0` exposes it on your LAN) |
26| `--mdns` | `false` | Enable mDNS discovery |
27| `--cors` | `[]` | Extra browser origins to allow (repeatable) |
29The server publishes an OpenAPI 3.1 spec at `http://<hostname>:<port>/doc`.
31### 2. (Optional) Add authentication
33opencode has no auth by default. To protect the server with HTTP Basic auth:
36OPENCODE_SERVER_PASSWORD=your-password opencode serve --hostname 0.0.0.0 --port 4096
39The username defaults to `opencode` (override with `OPENCODE_SERVER_USERNAME`). Enter these in the
40app's add-server dialog.
42### 3. Connect from the app
44**Over Wi-Fi / LAN** — add a server with base URL `http://<host-LAN-ip>:4096`.
46**Over USB only** — forward the port to the device and point the app at localhost:
49adb reverse tcp:4096 tcp:4096
52Then use base URL `http://127.0.0.1:4096` in the app.
54> The app permits cleartext HTTP because opencode serves plain `http://`. If you terminate TLS with
55> a reverse proxy, use an `https://` base URL instead.
59- **Ktor 3 (OkHttp engine)** for REST + the long-lived `GET /event` SSE stream.
60- **kotlinx.serialization** with sealed polymorphism for opencode's discriminated unions
61 (`Part`/`type`, `MessageInfo`/`role`, `ToolState`/`status`, `BusEvent`/`type`). Unknown
62 discriminator values fall back to `Unknown*` variants (`OpencodeJson`) so a new server release
64- **Manual DI** (`AppContainer` on the `Application`) — no Hilt/KSP.
65- **Unidirectional state**: `EventStreamClient` exposes the SSE stream as a hot `SharedFlow`;
66 `SessionRepository` reduces events into per-session message state; ViewModels expose `StateFlow`;
67 Compose collects with `collectAsStateWithLifecycle`.
68- The repository subscribes to `/event` **before** the prompt POST so no early streamed part is missed.
70Source map: `data/network` (Ktor, SSE, JSON), `data/model` (wire models), `data/repo`
71(reducer + profile persistence), `di` (container/connection), `ui/<feature>` (Compose screens).
76nix develop # JDK 17, Gradle 8.14.4, Android SDK 35, aapt2 override
77./gradlew assembleDebug # -> app/build/outputs/apk/debug/app-debug.apk
78./gradlew assembleRelease # R8-minified + resource-shrunk release APK
79./gradlew testDebugUnitTest # JVM tests for the JSON/serialization layer
80./gradlew detekt # static analysis (config: config/detekt/detekt.yml)
81./gradlew lintDebug # Android Lint
82./gradlew connectedAndroidTest # device/emulator tests (needs an AVD)
88adb install -r app/build/outputs/apk/debug/app-debug.apk
89adb shell am start -n soy.iko.opencode/.MainActivity
90adb logcat --pid=$(adb shell pidof soy.iko.opencode)
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list