{"id":51016325,"url":"https://github.com/dnakov/alleycat","last_synced_at":"2026-06-21T11:01:44.190Z","repository":{"id":353611276,"uuid":"1219405511","full_name":"dnakov/alleycat","owner":"dnakov","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-30T14:38:53.000Z","size":3475,"stargazers_count":39,"open_issues_count":1,"forks_count":12,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-30T16:14:35.641Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dnakov.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-23T21:00:51.000Z","updated_at":"2026-05-30T14:38:57.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/dnakov/alleycat","commit_stats":null,"previous_names":["dnakov/alleycat"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dnakov/alleycat","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dnakov%2Falleycat","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dnakov%2Falleycat/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dnakov%2Falleycat/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dnakov%2Falleycat/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dnakov","download_url":"https://codeload.github.com/dnakov/alleycat/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dnakov%2Falleycat/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34607126,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-21T02:00:05.568Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-21T11:01:41.963Z","updated_at":"2026-06-21T11:01:44.180Z","avatar_url":"https://github.com/dnakov.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Alleycat\n\n![Alleycat logo](assets/alleycat-logo.png)\n\nIroh-backed bridge that multiplexes a few local coding agents — Codex, Pi, Amp, OpenCode, Claude, Factory Droid, and Hermes — onto a single QUIC connection. Run the daemon on your machine, scan a QR with a paired client, and the client picks an agent over the same stream multiplexer.\n\n## Install\n\nEnd-user packages are published from the [`kittylitter`](https://github.com/dnakov/litter) project, which wraps this daemon under the `kittylitter` command. The currently enabled published channel is npm/bun; Homebrew, shell installer, PowerShell installer, and MSI publishing are not enabled in the release config right now. Source installs from this repo expose the daemon as `alleycat`.\n\n| Platform | Install |\n|---|---|\n| npm / bun | `npm install -g kittylitter` \u0026nbsp;or\u0026nbsp; `bunx kittylitter` |\n| From source | `cargo install --path crates/alleycat` (this repo) |\n\n## First run\n\n```bash\nkittylitter install         # autostart at login (no admin)\nkittylitter status          # node id, token, agent availability\nkittylitter pair --qr       # phone-side QR\n```\n\nUse `alleycat` instead of `kittylitter` when running a source build from this repo.\n\nThe `install` command registers a launchd user agent on macOS, a systemd `--user` unit on Linux (with `.desktop` autostart fallback), or a Startup-folder shortcut on Windows. None of them require sudo.\n\nThe daemon spawns external coding-agent CLIs on demand — install whichever ones you'll use:\n\n| Agent | Install |\n|---|---|\n| `claude` | `npm install -g @anthropic-ai/claude-code` (or `bun install -g @anthropic-ai/claude-code`). Then `claude /login` once. |\n| `opencode` | See [opencode docs](https://opencode.ai). |\n| `amp` | Install Amp from [ampcode.com/install](https://ampcode.com/install), then either run `amp login` once or set `AMP_API_KEY` in the daemon environment. |\n| `pi` | See pi-mono docs. |\n| `codex` | Install the `codex` CLI ([codex docs](https://github.com/openai/codex)). The daemon spawns `codex app-server` on demand. |\n| `droid` | Install Factory Droid, then either run `droid login` once or set `FACTORY_API_KEY` in the daemon environment. |\n| `hermes` | Install stock [Hermes Agent](https://github.com/NousResearch/hermes-agent). Alleycat prefers the Hermes gateway API on loopback and falls back to `hermes -z`; set `API_SERVER_KEY` or `HERMES_API_KEY` only in the daemon environment if your API server requires it. |\n\n## Commands\n\nThe command surface is the same for `kittylitter` packaged installs and `alleycat` source builds. The table uses `alleycat` because this is the daemon repo.\n\n| Command | What it does |\n|---|---|\n| `alleycat serve` | Run the daemon in the foreground (what `install` autostarts). |\n| `alleycat install` / `uninstall` | Per-user autostart, idempotent. |\n| `alleycat status [--json]` | Pid, node id, token fingerprint, uptime, agent availability. Falls back to a file-only readout if the daemon isn't running. |\n| `alleycat pair [--qr]` | Print the stable pair payload, optionally with an ASCII QR code. |\n| `alleycat rotate` | Mint a fresh token. Node id is preserved; the running daemon picks up the new token immediately. |\n| `alleycat reload` | Re-read `host.toml` and swap agent config without restarting. |\n| `alleycat agents list` | List configured agents and their availability. |\n| `alleycat logs [-f]` | Tail the daemon log files. |\n| `alleycat stop` | Graceful shutdown via the control socket. |\n\nThe daemon talks to the CLI over a Unix domain socket on macOS/Linux and a per-user named pipe on Windows. `status`, `pair`, and `rotate` round-trip through it when the daemon is up and fall back to file-only operations when it isn't, so first-run flows still work.\n\n## What the daemon spawns\n\n| Agent | Spawned by daemon? | How |\n|---|---|---|\n| `codex` | Yes, one shared backend | Uses Codex's Unix app-server socket by default: Alleycat lazy-spawns `codex app-server --listen unix://` only when no existing socket answers, then each iroh stream runs through `codex app-server proxy`. Older Codex CLIs fall back to `codex app-server --listen ws://\u003chost\u003e:\u003cport\u003e` or stdio. |\n| `pi` | Yes, per codex thread | `PiPool` spawns `pi --mode rpc` on demand, bounded at 16 processes with a 10-minute idle reap and LRU eviction. |\n| `amp` | Yes, per turn | Spawns `amp --execute --stream-json --stream-json-thinking --stream-json-input` for each turn, translates Amp stream JSON into codex app-server lifecycle events, stores Alleycat-owned thread records, and saves Amp's `T-*` thread id for continuation. |\n| `opencode` | Yes, one shared backend | Lazy spawn of `opencode serve --port=auto --auth-token=auto` on first connect, gated on `/global/health`. Or set `OPENCODE_BRIDGE_BACKEND_URL` to point at an existing instance. |\n| `claude` | Yes, per codex thread | `ClaudePool` spawns `claude -p --input-format stream-json --output-format stream-json --session-id \u003cthread_id\u003e --dangerously-skip-permissions` on demand. Same 16-cap, 10-minute idle reap, LRU eviction as pi. Sessions resume on next access via `--resume \u003cthread_id\u003e`. |\n| `droid` | Yes, per codex thread | Spawns `droid exec --input-format stream-jsonrpc --output-format stream-jsonrpc --cwd \u003ccwd\u003e` and translates Factory session notifications into the codex app-server wire. |\n| `hermes` | Yes, one turn per request | Uses stock Hermes gateway API (`/health`, `/v1/runs`, `/v1/runs/{id}/events`, `/v1/runs/{id}/stop`) when available, otherwise spawns `hermes -z \u003cprompt\u003e` and `--resume \u003csession\u003e` with argv-only process creation. API keys stay inside the daemon process and are never sent to paired clients. |\n\n## Pair payload\n\n`alleycat pair` prints:\n\n```json\n{\n  \"v\": 1,\n  \"node_id\": \"\u003ciroh public key\u003e\",\n  \"token\": \"\u003c32-byte hex\u003e\",\n  \"relay\": null\n}\n```\n\n`relay` is optional and only set if the operator pinned a specific iroh relay in `host.toml`; otherwise iroh's default discovery applies. There is no port or cert fingerprint — iroh handles transport, the token authenticates the first JSON frame on every stream.\n\n## Wire\n\nALPN `alleycat/1`. Each iroh bidirectional stream begins with a length-prefixed JSON request:\n\n```json\n{\"op\": \"list_agents\", \"v\": 1, \"token\": \"...\"}\n```\nor\n```json\n{\"op\": \"connect\", \"v\": 1, \"token\": \"...\", \"agent\": \"codex\"}\n```\n\nThe daemon answers with `{ok, agents?, error?}`. On `connect`, after the response the stream becomes the agent's native wire — websocket frames for `codex` (the daemon proxies straight to the shared `codex app-server` listener), JSON-RPC over JSONL for `pi`, `amp`, `opencode`, `claude`, `droid`, and `hermes`.\n\n## Configuration\n\n`host.toml` is created on first run with sensible defaults; edit and `alleycat reload` to apply.\n\n```toml\ntoken = \"...\"          # 32 bytes hex; rotate via `alleycat rotate`\n# relay = \"https://...\" # optional iroh relay override\n\n[agents.codex]\nenabled = true\nbin = \"codex\"\nhost = \"127.0.0.1\"\nport = 8390\n\n[agents.pi]\nenabled = true\nbin = \"pi\"\n\n[agents.amp]\nenabled = true\nbin = \"amp\"\napi_key_env = \"AMP_API_KEY\"\ndangerously_allow_all = true\n\n[agents.opencode]\nenabled = true\nbin = \"opencode\"\n\n[agents.claude]\nenabled = true\nbin = \"claude\"\n\n[agents.droid]\nenabled = true\nbin = \"droid\"\napi_key_env = \"FACTORY_API_KEY\"\n\n[agents.hermes]\nenabled = true\nbin = \"hermes\"\napi_base = \"http://127.0.0.1:8642\"\n```\n\nReload swaps config that's read per-request (token, agent enable flags). Codex's `bin`/`host`/`port`, pi's `bin`, Amp's `bin`/permission mode, OpenCode's `bin`/runtime port, Droid's `bin`, and Hermes's `bin`/`api_base` are pinned at first spawn; changing those requires `alleycat stop` + `serve`. Codex `host`/`port` are only used by the legacy websocket fallback. For Hermes API mode, bind the Hermes gateway to loopback and put `API_SERVER_KEY`/`HERMES_API_KEY` only in the Alleycat daemon environment; mobile clients authenticate through Alleycat pairing and never receive the Hermes key.\n\n## File layout\n\nPer-OS, via `directories::ProjectDirs`:\n\n|              | macOS                                                         | Linux                                | Windows                                   |\n|--------------|---------------------------------------------------------------|--------------------------------------|-------------------------------------------|\n| Config       | `~/Library/Application Support/dev.Alleycat.alleycat/host.toml` | `$XDG_CONFIG_HOME/alleycat/host.toml` | `%APPDATA%\\Alleycat\\alleycat\\config\\host.toml` |\n| State        | (collapses to config dir) — `host.key`, `host.lock`, `daemon.pid` | `$XDG_STATE_HOME/alleycat/`        | `%LOCALAPPDATA%\\Alleycat\\alleycat\\data\\`  |\n| Logs         | `~/Library/Logs/dev.Alleycat.alleycat/daemon.log`             | `$XDG_STATE_HOME/alleycat/logs/`     | `%LOCALAPPDATA%\\Alleycat\\alleycat\\logs\\`  |\n| Control IPC  | `$TMPDIR/alleycat-\u003cuserhash\u003e/control.sock`                    | `$XDG_RUNTIME_DIR/alleycat-\u003cuserhash\u003e/control.sock` | `\\\\.\\pipe\\alleycat-control-\u003cuserhash\u003e` |\n| Autostart    | `~/Library/LaunchAgents/dev.alleycat.alleycat.plist`          | `~/.config/systemd/user/alleycat.service` (or `~/.config/autostart/alleycat.desktop`) | `…\\Startup\\alleycat.lnk` |\n\nThe Unix control socket falls through `XDG_RUNTIME_DIR` → state dir → `TMPDIR` → `/tmp` so the path always fits in `sockaddr_un.sun_path` (104 bytes on macOS/BSD, 108 on Linux), even under deeply nested hermetic test homes.\n\n## Notes\n\n- Codex, Pi, Amp, OpenCode, Claude, Droid, and Hermes children inherit `kill_on_drop` semantics, so they exit when the daemon does.\n- `alleycat stop` shuts the iroh endpoint and the daemon process; launchd / systemd will restart it under their normal supervision.\n\n## Building from source\n\n```bash\ncargo install --locked --path crates/alleycat\n# or, for a workspace-relative build:\ncargo build --release -p alleycat\ntarget/release/alleycat install\n```\n\nThe workspace crates are:\n\n- `crates/alleycat` — `alleycat` daemon binary. Owns the iroh endpoint, the persistent identity, the agent dispatcher, and an OS-native control socket so the CLI can talk to the running daemon.\n- `crates/amp-bridge` — Amp `--stream-json` process wrapper plus codex-shaped turn/tool translation and Alleycat-owned Amp thread records.\n- `crates/bridge-conformance` — live conformance harness for comparing bridge behavior against codex app-server wire shapes.\n- `crates/bridge-core` — shared JSON-RPC framing, server scaffolding, and notification plumbing used by the bridges.\n- `crates/codex-proto` — shared codex `app-server` v2 wire shapes used by every bridge.\n- `crates/pi-bridge` — `pi-coding-agent` process pool plus a codex-shaped JSON-RPC translator (one pi process per codex thread).\n- `crates/opencode-bridge` — single shared `opencode serve` backend wrapped in the same JSON-RPC surface.\n- `crates/claude-bridge` — `claude -p --output-format stream-json` process pool wrapped in the same JSON-RPC surface (one claude process per codex thread).\n- `crates/droid-bridge` — Factory Droid `stream-jsonrpc` process wrapper plus codex-shaped turn/tool translation (one droid process per codex thread).\n- `crates/hermes-bridge` — stock-Hermes API/CLI adapter that exposes Hermes as the codex app-server JSON-RPC surface for Alleycat clients.\n- `crates/claude-remote-control` — auxiliary Claude remote-control protocol support.\n\nReleases are produced from the [`litter`](https://github.com/dnakov/litter) repo, which carries this repo as a submodule under `shared/third_party/alleycat` and runs [`dist`](https://github.com/axodotdev/cargo-dist) against the `kittylitter` wrapper to build platform release artifacts and publish the npm package. Homebrew, shell installer, PowerShell installer, and MSI publishing are disabled in litter's release config right now. To cut a release: bump `version` in the root `Cargo.toml` here, push, then bump the submodule pin in litter and tag `vX.Y.Z` there.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdnakov%2Falleycat","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdnakov%2Falleycat","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdnakov%2Falleycat/lists"}