Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017xn8nHw1tw1R6PtiY1eEtw
3.8 KiB
ai-app
A phone interface to AI coding sessions (Claude Code and llama.cpp via pi), replacing the Claude app for daily use. Rust/Axum backend on the desktop, Kotlin/Compose Android app, WireGuard + pinned self-signed TLS + bearer token between them.
PLAN.md is the design source of truth. Read it before building or
changing anything structural. It records every decision with its date, its
rationale, and the alternatives that were rejected and why — keep that habit
when a decision changes: update the plan in place, don't let this file and
the plan drift into two versions of the truth. This file is the working notes
layer: conventions, commands, and things that have bitten.
The central design point, worth not undoing by accident: a session is a
child process speaking JSONL over stdio, translated into one common event
model. Claude Code (stream-json) and pi (RPC mode) are two translators
behind one Driver trait; the transcript, the SSE stream, the phone UI, and
SSH spawning (the same command wrapped in ssh host …) all work purely in
the common model. A new session type is a new driver — never a
session-type branch in shared code (routes, transcript, app screens).
Layout
Mirrors ../local-updater deliberately — same stack (axum 0.8 +
axum-server/rustls, tokio, clap; Kotlin 2.4.x + Compose Multiplatform,
single :androidApp module), same cert scheme, same registry pattern (every
session mutation funnels through the manager so in-memory and on-disk state
can't come apart). Read local-updater's README.md and AGENTS.md for the
conventions before diverging from them; module-by-module intent for this
repo is in PLAN.md's "Backend layout" section.
server/— Rust backend (to be created, phase 1).app/— Compose Android app (to be created, phase 1).gen-dev-cert.sh/certs/— copied from local-updater's scheme (idempotent CA, reissued leaf; regenerating the CA strands the installed app — same one-way door).
Status
Pre-implementation. Phases are in PLAN.md; phase 1 (Skeleton) proves the
whole pipe — TLS, token auth, wg0-bound listener, SSE with transcript
cursors, both app screens — against a fake EchoDriver before any AI is
involved. Every phase ends runnable and verified against the real thing.
Checking your work
Fill in real commands as they're created; until then, the inherited posture:
- Server:
cargo test+cargo clippy --all-targetsfromserver/— the build stays warning-clean from the first commit. - App: from
app/,. ./android-env.sh && ./gradlew :androidApp:compileDebugKotlin. - Tests where logic is pure (event normalization, transcript cursors, config persistence, llama-server refcounting); the app is UI over the API and is verified by running it.
- Prefer exercising the server directly over going through the UI:
curl --cacert certs/ca.pem -H "Authorization: Bearer …" https://….
Environment notes (this machine, learned in local-updater)
- Android SDK is at
~/Android/Sdk, not the root-owned/opt/android-sdkthe ambient$ANDROID_HOMEmay point at; copy local-updater'sandroid-env.shoverride pattern. - Each agent command runs in a fresh shell — exported environment does not
carry over. Chain:
cd app && . ./android-env.sh && ./gradlew …. Never pipesourceintohead/grep(subshell discards the exports). - The shared emulator AVD is named
tdep— one emulator across the Android projects on this machine, not one per repo. - Long-running servers launched from an agent must be fully detached
(
setsid nohup … & disown -h, verifyPPID 1), and everypgrep -f/pkill -fpattern needs its first character bracketed ([a]i-server) in every occurrence in the command, or the pattern matches the shell running it. Full explanation in local-updater'sAGENTS.md— it bites exactly the same way here.