Status: Draft · Open for feedback · Not yet implemented Target milestone: P0 in the Roadmap Authors: @gurudin · open for PRs
AnyCoding's north star is to be the unified mobile front-end for any pty-based AI coding agent. To scale beyond the two hardcoded agents (Claude Code, Codex), we need a declarative way to add new agents without changing core code. This document proposes that mechanism.
- Adding a standard pty-based agent should be a manifest PR, not a code PR.
- Users can install custom / private agents locally by dropping a file into
~/.any-coding/agents/. - The CLI core stays small.
wrapper.jshandles generic spawn + pty plumbing; agent-specific knowledge lives in manifests. - Forward-compatible. The Android app should tolerate unknown agents gracefully (fallback icon, generic rendering).
- Not a plugin system that executes arbitrary code. Manifests are data only. No scripts, no dynamic module loading.
- Not a marketplace. Discovery happens via the git repo and user-local files.
- Not for relay-side agents. This is CLI-only. Agents that need a closed-source backend live elsewhere.
- Not hot-reloading. Manifest changes take effect on next CLI invocation.
Manifests are JSON files (one agent per file). JSON keeps the CLI dependency-free. YAML support is a possible v2 addition.
{
"schema_version": 1,
"name": "aider",
"description": "AI pair programmer in your terminal (paul-gauthier/aider)",
"binary": "aider",
"install_hint": {
"default": "pip install aider-chat",
"darwin": "brew install aider || pip install aider-chat"
},
"args_mode": "passthrough",
"prompt_marker": "^> $",
"icon": { "kind": "initials", "text": "AI", "color": "#B3001E" }
}| Field | Type | Required | Description |
|---|---|---|---|
schema_version |
integer | ✅ | Currently 1. CLI rejects unknown versions. |
name |
string | ✅ | Subcommand name. Invoked as any-coding <name> [args...]. Must match ^[a-z][a-z0-9_-]{1,31}$. |
description |
string | ✅ | One-line description shown in the interactive picker. |
binary |
string | ✅ | Command to execute. Resolved against $PATH. May be absolute. |
install_hint |
object | — | Map of platform → shell command. Shown when binary is not found. Keys: default, darwin, linux, win32. |
env |
object | — | Extra env vars to inject into the spawned process. |
args_mode |
enum | ✅ | How CLI args after the subcommand are forwarded. See below. |
prompt_marker |
string (regex) | — | Matched against the tail of the output buffer to detect "agent finished replying". Used by the message-counter feature. |
icon |
object | — | Rendering hint for the Android app tab strip. See "Icon rendering". |
min_cli_version |
semver string | — | Manifest refuses to load on older CLI versions. |
| Value | Behavior |
|---|---|
passthrough |
All args after the subcommand go verbatim to the binary. (Claude, Codex, Aider — the common case.) |
positional |
Args are collected and passed as positional only. Flags are rejected at CLI level. |
none |
No args forwarded. Used for agents that take no CLI args. |
Android uses this order of preference:
- Built-in:
{ "kind": "builtin", "id": "claude" | "codex" }— renders the bundled app asset. - Initials fallback (recommended for new agents):
{ "kind": "initials", "text": "AI", "color": "#B3001E" }— app draws a rounded square with the text. No asset bundling needed. - Remote URL (v2, not in initial release):
{ "kind": "url", "src": "https://..." }— app fetches and caches.
New manifests should use initials unless there's a strong reason to ship a built-in asset (which requires an Android PR).
On startup the CLI loads manifests from, in order:
- Built-in, bundled with the npm package (
any-code-cli/agents/*.json). Covers default agents (Claude, Codex, and eventually Gemini / Aider / goose / Open Interpreter). - User:
~/.any-coding/agents/*.json. - Project:
./.any-coding/agents/*.json(if present incwd).
Collision rule: higher-precedence sources win on name collision. Project overrides user overrides built-in. The CLI logs a note when an override happens (visible with any-coding list-agents --verbose).
| Command | Description |
|---|---|
any-coding list-agents |
Print available agents (name, description, binary found/missing, icon). |
any-coding list-agents --verbose |
Include source (built-in / user / project) and manifest path. |
any-coding show-agent <name> |
Pretty-print a single manifest with resolved values. |
argv[2] = <subcommand>
│
▼
load manifests (built-in + user + project)
│
├── if subcommand matches a manifest name:
│ 1. check `binary` exists on $PATH
│ · missing → print install_hint, exit 1
│ 2. build spawn() argv per args_mode
│ 3. merge env (process.env + manifest.env)
│ 4. hub.createSession({ agent: name, cwd, args }) → pty.spawn
│ 5. hub pushes agent + icon metadata on /v1/agent sessions frame
│
└── else if subcommand is a legacy alias (e.g. bare claude flags):
fall back to claude manifest (backward compat)
The sessions text frame (agent → client) currently looks like:
{ "t": "sessions", "list": [ { "id": "...", "project": "...", "cwd": "...", "owner": "desktop", "args": [...], "createdAt": 123 } ] }Proposed addition: each session object gets an agent field (string) and an icon field (object, mirror of manifest icon).
{ "id": "...", "agent": "aider", "icon": { "kind": "initials", "text": "AI", "color": "#B3001E" }, ... }The app uses agent for tab labeling and icon for rendering. Unknown agents fall back to { "kind": "initials", "text": "?" }.
Contributor workflow after this ships:
-
Install Gemini CLI locally to verify it behaves like a standard pty TUI.
-
Write
any-code-cli/agents/gemini.json:{ "schema_version": 1, "name": "gemini", "description": "Google Gemini CLI", "binary": "gemini", "install_hint": { "default": "npm install -g @google/gemini-cli" }, "args_mode": "passthrough", "prompt_marker": "^\\s*>\\s*$", "icon": { "kind": "initials", "text": "Ge", "color": "#4285F4" } } -
Manual test:
any-coding gemini --helpspawns correctly. -
Add screenshot of the app tab strip showing the new icon.
-
Open PR.
No changes to wrapper.js, no changes to the Android app, no changes to the relay.
- Manifests are data only. They cannot execute arbitrary code. The only thing they can cause is
spawn(binary, args, env)with values the user's own config supplied. - User-local manifests (
~/.any-coding/agents/) have the same trust boundary as the user's own$PATH— if an attacker can write files into your home directory, they already have a shell. - Built-in manifests ship with the npm package and go through normal code review.
install_hintis not executed; it is only printed as text for the user to run manually.
- Multi-binary fallback — should
binarybe a list, trying each in order? (Useful for agents with multiple install methods —aidervsaider-chat.) - Prompt marker complexity — a static regex may miss streaming-partial cases. Do we need a small state machine, or is "matched on last N bytes after idle timeout" enough?
- Manifest versioning policy — when
schema_versionbumps, do we break old manifests or translate? Current lean: translate where trivial, reject otherwise. - Agent-specific UI affordances — some agents benefit from quick-action buttons (e.g. "accept all edits"). Out of scope for v1; could become a P2 feature where manifests declare actions.
- Windows compatibility —
install_hint.win32and binary resolution on Windows needs real validation during implementation.
Feedback welcome on any of the above — open an issue labeled manifest-design.
- Hardcode every agent — status quo. Works for 2–3 agents, becomes a bottleneck beyond that. Every community contribution is a core PR.
- Lua / JS plugin scripts — maximally flexible, but introduces a runtime security model and breaks "manifests are data". Rejected for v1.
- Inline manifests inside
package.json— couples to Node / npm. Rejected: manifests should be usable even if we later ship a non-Node CLI (e.g. a Go rewrite). - Remote manifest registry — too much infrastructure. Git-based discovery (PR to this repo, or drop into
~/.any-coding/) is sufficient.
- 2026-04-17 — Initial draft.