Skip to content

Latest commit

 

History

History
149 lines (106 loc) · 8.14 KB

File metadata and controls

149 lines (106 loc) · 8.14 KB

Architecture

High-level map of the AnyCoding system for contributors. Read README.md first for the user-facing pitch.

Open-core layering

AnyCoding is an open-core project. Two components live here (open source); one lives in a separate private repository (closed source).

┌──────────────────────────────────────────────────────────────────┐
│                  This repository — public (Apache-2.0)           │
│                                                                  │
│   any-code-cli/            Node CLI on the user's Mac/Linux      │
│   any-code-android/        Android app                           │
└──────────────────────────────────────────────────────────────────┘
                                   │
                                   │  WebSocket (wss)
                                   ▼
┌──────────────────────────────────────────────────────────────────┐
│            Separate repository — private (closed source)         │
│                                                                  │
│   any-code-server/         FastAPI relay + user/device/billing   │
└──────────────────────────────────────────────────────────────────┘

The relay server is closed source because it handles accounts, subscriptions, and multi-tenant state. Everything that runs on end-user machines is open.

Self-hosted alternative: users who don't want to depend on the hosted relay can run the CLI with any-coding hub --public, which exposes the local hub via a cloudflared tunnel. No account needed, no closed-source server in the loop.

Components

any-code-cli/ (Node.js)

Single-file wrapper (wrapper.js) with two modes:

  • Attach mode (default): spawn a local pty running claude / codex / another agent CLI, mirror bytes into the hub, and act as a terminal viewer for the current shell.
  • Hub mode (--hub or detached): run an HTTP + WebSocket server on 127.0.0.1:7321, manage multiple pty sessions, and optionally dial out to the relay server as an "agent" client.

Key dependencies: node-pty, ws, qrcode-terminal. No TypeScript, no framework. One file for easy audit.

any-code-android/ (Kotlin)

Two-activity Android app:

  • MainActivity — WebView shell loading assets/index.html, handles login / register / pairing / device selection.
  • TerminalActivity — Native terminal using vendored Termux terminal-emulator + terminal-view libraries (com.termux.*). Bytes flow through relay/RelayClient.kt (OkHttp WebSocket) directly to the terminal session.

Minimum SDK 24 (Android 7.0). Built with AGP 8.5.2 + Gradle 8.9 (JDK 17).

Relay server (closed, not in this repo)

FastAPI + SQLAlchemy async, backed by MySQL 8 + Redis 7. Acts as:

  • Auth + account management (/v1/register, /v1/login, /v1/me)
  • Device pairing via 6-digit code + Redis TTL (/v1/pair/init, /claim, /wait)
  • Byte bridge between agent (PC) and client (phone) via WebSocket (/v1/agent, /v1/client)

The relay is stateless with respect to terminal content — it does not parse, log, or interpret pty bytes. It only relays them.

Data flow (cloud mode)

  [Android app]                         [Ghostty / iTerm]
       │                                      │
       │ Native TerminalView                  │ Local TTY viewer
       │ RelayClient (OkHttp wss)             │ ws://127.0.0.1:7321/ws/session/:id
       │                                      │
       ▼                                      ▼
  ┌────────────────┐                 ┌──────────────────────┐
  │ Relay server   │                 │ Local hub            │
  │ (closed)       │ ◀─── wss ─────▶ │ (any-coding --hub)   │
  │                │  /v1/agent      │                      │
  └────────────────┘                 │ pty.spawn('claude')  │
                                     └──────────────────────┘
                                              │
                                              ▼
                                     [claude / codex / ...]

Direct mode (hub --public)

  [Android app] ──── wss ────▶ [cloudflared quick tunnel] ──▶ [local hub] ──▶ [agent CLI]

No account, no closed server, no billing. URL changes on each restart (pair again).

Protocol summary

See README.md for the full protocol reference. In short:

  • Text frames = JSON control messages (list, open, close, input, resize, ping from client; sessions, opened, err, pong from agent).
  • Binary frames = [4-byte BE session id][raw pty bytes]. Bidirectional.
  • Close codes: 4401 auth failure, 4403 subscription expired, 4404 device offline, 4002 replaced by newer connection.

Design decisions

Why a local hub process?

So that a local terminal viewer (Ghostty, iTerm) and the phone see the same pty. Closing the terminal window doesn't kill the agent session; opening another terminal rejoins the same output stream with full replay from a 256 KB ring buffer.

Why does the PC dial out to the relay?

Any NAT / home router / coffee shop WiFi can connect without port forwarding. The PC is the agent client; the relay accepts inbound WebSockets on a public domain.

Why does the relay not parse pty bytes?

Scale + privacy + protocol agility. Adding a new agent (Codex, Gemini CLI) requires zero relay changes. The hub and the app negotiate everything.

Why vendored Termux for the Android terminal?

WebView + xterm.js was the original approach. Background requestAnimationFrame throttling, Gboard predictive input, canvas sizing races, and soft-keyboard viewport jumps made it fragile. Termux's native TerminalView is the most complete VT100/xterm implementation on Android, handles CJK widths, true color, bracketed paste, and alt screen correctly. Trade-off: no iOS reuse — an iOS port would need SwiftTerm or a new WebView/xterm.js shell.

Why 6-digit pair codes + QR?

The QR is a visualization of the same 6-digit code. Humans can type a 6-digit code as a fallback when the camera fails. The real long-lived device_token never travels inside the QR — it is returned over HTTPS from /v1/pair/wait.

Repository layout

.
├── any-code-cli/           Node CLI (Apache-2.0)
│   ├── wrapper.js          Single-file entry, hub + attach modes
│   ├── scripts/            postinstall fixes
│   └── package.json
├── any-code-android/       Android app (Apache-2.0)
│   ├── app/                Main module
│   ├── terminal-emulator/  Vendored Termux VT state machine (Apache-2.0)
│   └── terminal-view/      Vendored Termux Android View (Apache-2.0)
├── LICENSE                 Apache-2.0
├── NOTICE                  Third-party attributions
├── README.md
├── CONTRIBUTING.md
├── CODE_OF_CONDUCT.md
├── SECURITY.md
├── ARCHITECTURE.md         (this file)
└── .github/                Issue + PR templates

How changes flow

  1. Fork → branch from develop
  2. Change CLI or Android code
  3. Test against cloud relay and hub --public mode (both must work)
  4. Open PR against develop
  5. One maintainer approval → merge
  6. develop → main on release, tag, publish npm + attach APK to GitHub Release

For anything touching the wire protocol, relay endpoints, or authentication flow, please open an issue first to discuss — those changes ripple into the closed-source relay and need coordination.