Last updated: 2026-04-19 (Brian)
Status: live at https://cli.jonahtebaa.com/
The v3 Claude Code PWA (cli-pwa) — a web UI for Jonah to use Claude Code from any browser, with session management, voice dictation, subagents panel, and a live "working…" typing indicator above the input bar.
Previously served at cc.jonahtebaa.com/v3/. Migrated to cli.jonahtebaa.com/ (served at root) on 2026-04-19. The /v3/ path on cc.jonahtebaa.com has been removed.
Browser → Caddy (cli.jonahtebaa.com:443)
→ reverse_proxy host.docker.internal:8111
→ cli-pwa-backend (systemd) serves:
/api/* → REST (sessions, skills, commands, enhance, archived)
/ws/sessions/:id → WebSocket (session event stream)
/ → Static files from /opt/agent/cli-pwa/frontend/dist
/opt/agent/cli-pwa/backend/ (Node/Fastify). Spawns Claude Code CLI subprocesses via execa, pipes stream-JSON events to the client over WS. Managed by systemd unit cli-pwa-backend./opt/agent/cli-pwa/frontend/ (Vite + React). Built with npx tsc -b && npx vite build. Served as static files from backend.gate_auth + gate_check). Same cookie-based gate used by other *.jonahtebaa.com apps./opt/agent/caddy/Caddyfile, block starting cli.jonahtebaa.com {.| Path | Purpose |
|---|---|
/opt/agent/cli-pwa/backend/src/server.ts |
Fastify entrypoint, registers routes + static. |
/opt/agent/cli-pwa/backend/src/sessions.ts |
Session lifecycle, CLI subprocess manager. |
/opt/agent/cli-pwa/backend/src/routes/ws.ts |
WebSocket handler, replays buffer + forwards events. |
/opt/agent/cli-pwa/backend/src/sdk.ts |
CLIProcess — spawns claude with stream-JSON IO. |
/opt/agent/cli-pwa/frontend/src/routes/SessionView.tsx |
Live session view, status state machine, working indicator. |
/opt/agent/cli-pwa/frontend/src/components/TopBar.tsx |
Top bar: model / permission / effort / plan toggles, ctx meter. |
/opt/agent/cli-pwa/frontend/src/components/InputBar.tsx |
Input + voice dictation + interrupt. |
/opt/agent/cli-pwa/frontend/src/components/ChatView.tsx |
Transcript. |
/opt/agent/cli-pwa/frontend/src/components/SubagentDrawer.tsx |
Subagent panel (TopBar satellite badge opens it). |
/opt/agent/cli-pwa/frontend/vite.config.ts |
base: '/' (was /v3/). |
/opt/agent/cli-pwa/frontend/src/lib/api.ts |
BASE = '/api'. |
/opt/agent/cli-pwa/frontend/src/lib/ws.ts |
WS URL = /ws/sessions/:id. |
/opt/agent/cli-pwa/frontend/src/main.tsx |
Router basename: '/'. |
/opt/agent/cli-pwa/frontend/index.html |
Favicon at /favicon.svg. |
Status is driven by WebSocket event types in SessionView.tsx:
| Event type | Status |
|---|---|
assistant / stream_event |
thinking |
result |
idle |
permission_request / tool_use_request |
awaiting_permission |
session_end |
killed |
The "working…" indicator (italic muted text above the input bar, absolutely positioned at bottom: 68px) renders when connected === true AND status is thinking (shows working + animated 1→2→3 dots via pure-CSS @keyframes brian-typing-dots animating content on .brian-typing-dots::after) or awaiting_permission (shows waiting for you...).
cd /opt/agent/cli-pwa/frontend && npx tsc -b && npx vite build
# Backend auto-serves the new dist. Restart only if backend code changed:
systemctl restart cli-pwa-backend
Caddy reload:
docker exec agent-caddy caddy reload --config /etc/caddy/Caddyfile
systemctl is-active cli-pwa-backend → active.127.0.0.1:8111./data/caddy-cli.log (inside agent-caddy container).journalctl -u cli-pwa-backend --since "5 min ago".Removed from Caddyfile:
# was inside cc.jonahtebaa.com { ... }
handle_path /v3/* {
reverse_proxy host.docker.internal:8111 { flush_interval -1 }
}
Added:
cli.jonahtebaa.com {
header { ... security headers, microphone=(self) allowed ... }
import gate_auth
import gate_check
log { output file /data/caddy-cli.log ... }
reverse_proxy host.docker.internal:8111 { flush_interval -1 }
}
Frontend changes to drop /v3/ prefix: vite base → /, api.ts BASE → /api, ws.ts URL → /ws/sessions/..., main.tsx router basename → /, index.html favicon → /favicon.svg.
cli-pwa-v4.backup) was considered but rejected — would require rewriting session management on a tmux hub with bidirectional xterm.js streaming. The chat UI + events-based working indicator is the current design.Permissions-Policy: microphone=(self) — set inline in the Caddy block (not via shared snippet which sets microphone=()).