Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
279 changes: 88 additions & 191 deletions AGENTS.md

Large diffs are not rendered by default.

215 changes: 215 additions & 0 deletions apps/cli/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
# agents-cli (the CLI)

`@phnx-labs/agents-cli` — the `agents` / `ag` CLI for managing AI coding-agent
versions, config, sessions, and cloud dispatch (Claude, Codex, Gemini, Cursor,
OpenCode, OpenClaw, Grok, Droid, …).

This is the **internal architecture** map. The user-facing feature tour is
[README.md](README.md) (pin versions, run, sessions, hosts, teams, workflows,
plugins, browser, secrets, routines, pty). This file covers the design choices,
module map, build, and release mechanics the README does not.

> Phoenix Labs OSS (Apache-2.0). **NOT part of the Rush brand** — brand rules are
> repo-wide, see the root [AGENTS.md](../../AGENTS.md) §Brand identity.

## Core design choices (read this first)

Break these and downstream code drifts silently.

### 1. Three DotAgents repos, resolution is project > user > system

Resources AND `agents.yaml` resolve in that order. Same-name overrides, everything
else unions.

| Path | Role | Edited by |
|---|---|---|
| `<repo>/.agents/` | **Project repo** — project-pinned commands / skills / hooks / rules. | Project maintainers |
| `~/.agents/` | **User repo** — user resources + ALL operational state (versions, shims, sessions, `agents.yaml`, browser). | You / CLI |
| `~/.agents/.system/` | **System repo** — npm-shipped defaults ONLY. | Maintainers (`gh:phnx-labs/.agents-system`) |

Extra repos register via `agents repo add <source>` → clone into `~/.agents-<alias>/`
and participate after the user repo.

### 2. `AGENTS.md` is the canonical memory file

`CLAUDE.md`, `GEMINI.md` are symlinks. **Edit `AGENTS.md` only** — editing a symlink
target directly gets stomped on the next sync. The sync writes the right file name
per agent (`OPENCODE.md`, `.cursorrules`, etc.).

### 3. Capability table gates per-agent writes

`supports(agent, cap, version?)` in [`src/lib/capabilities.ts`](src/lib/capabilities.ts)
is the only place that decides whether an agent+version can receive a resource.
Out-of-range versions are **skipped silently** — do not add per-call agent checks
elsewhere; route through `supports()`.

### 4. No fallback logic for legacy layouts

[`src/lib/migrate.ts`](src/lib/migrate.ts) folds legacy paths ONCE at install time
(`runMigration()` writes a `.migrated` sentinel). Downstream code assumes the
post-fold layout. "Just-in-case" branches re-introduce drift bugs; the migrator is
the single source of truth for legacy handling.

### 5. Hooks live in a single layered `hooks.yaml`

System + user `hooks.yaml` merged, user wins on same name. Per-entry `matches:`
predicates (`prompt_contains`, `prompt_matches`, `tool_name`, `tool_args_match`,
`cwd_includes`, `project_has`, `git_dirty`) AND together at fire time. Per-entry
`enabled: false` disables a system-shipped hook from the user side. The `agents:`
field in `ManifestHook` is `@deprecated` — the capability table decides which
agents register a hook.

### 6. Multi-agent work → `agents teams`

DAG-style, boundary contracts, `--watch` supervisor, `--worktree` isolation, optional
`--cloud` dispatch. The old `mcp__Swarm__*` surface was folded into teams
(`migrateLegacySwarmToTeams()` in `src/lib/migrate.ts`). Don't reach for Swarm — gone.

## Supported harnesses

14 harnesses ship support today. The full id list is `AgentId`
([`src/lib/types.ts`](src/lib/types.ts)); per-harness config + capabilities live in the
`AGENTS` registry ([`src/lib/agents.ts`](src/lib/agents.ts)) and are gated through
`supports()`. **Prioritized (first-class):** Claude Code, Codex CLI, Kimi CLI,
Antigravity CLI, Grok CLI, OpenCode — features target these six first.

| Harness | `id` | hooks | mcp | allowlist | skills | commands | plugins | subagents | workflows |
|---|---|:--:|:--:|:--:|:--:|:--:|:--:|:--:|:--:|
| ★ Claude Code | `claude` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| ★ Codex CLI | `codex` | ≥0.116 | ✓ | — | ✓ | <0.117 | ≥0.128 | — | — |
| ★ Kimi CLI | `kimi` | ✓ | ✓ | ✓ | ✓ | — | ✓ | — | — |
| ★ Antigravity CLI | `antigravity` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | — |
| ★ Grok CLI | `grok` | ✓ | ✓ | ✓ | ✓ | — | ✓ | — | — |
| ★ OpenCode | `opencode` | — | ✓ | — | ✓ | ✓ | — | — | — |
| Gemini † | `gemini` | ≥0.26 | ✓ | — | ✓ | ✓ | — | — | — |
| Cursor | `cursor` | — | ✓ | — | ✓ | ✓ | — | — | — |
| OpenClaw | `openclaw` | ✓ | ✓ | — | ✓ | — | ✓ | ✓ | — |
| Copilot | `copilot` | — | ✓ | — | ✓ | ✓ | ✓ | — | — |
| Amp | `amp` | — | ✓ | — | ✓ | ✓ | — | — | — |
| Kiro | `kiro` | — | ✓ | — | ✓ | ✓ | — | — | — |
| Goose | `goose` | — | ✓ | — | — | — | — | — | — |
| Droid | `droid` | ✓ | ✓ | — | — | ✓ | ✓ | ✓ | — |

✓ = supported · — = not · version cell = only within that range (out-of-range =
skipped silently). [`src/lib/agents.ts`](src/lib/agents.ts) is canonical — keep this
snapshot in sync. `workflows` is Claude-only; `mcp` is universal; `allowlist` is
`claude`/`antigravity`/`grok`/`kimi`; `subagents` is `claude`/`openclaw`/`droid`.
**† Gemini is deprecated by Google** (retired June 18 2026); Antigravity is the
successor — the CLI warns on `agents add gemini` (`warnAgentDeprecated`).

## Source layout

```
src/
index.ts # CLI entry (commander.js)
commands/ # User-facing subcommands (one file per `agents <cmd>`)
lib/
state.ts # Path constants; agents.yaml read/write
resources.ts # resolveResource() / listResources() — layered resolution
capabilities.ts # supports() — the per-agent write gate
agents.ts # Per-agent capability table
versions.ts # Install, remove, syncResourcesToVersion
shims.ts # Shim generation, config symlink switching
hooks.ts # hooks.yaml parser + per-agent registrar
hooks/match.ts # `matches:` predicate evaluator
migrate.ts # One-shot idempotent migrations
session/ # `agents sessions` READER — discovery/parse/render of agent transcripts
terminal/ # Terminal launch engine — tab/split in iTerm/Ghostty/tmux, local or --host
cloud/ # Provider registry (Rush / Codex / Factory / Antigravity)
teams/ # `agents teams` orchestration
computer-rpc.ts # `agents computer` client → native/computer-mac (Unix socket)
ssh-tunnel.ts # `agents computer --host` → native/computer-win over ssh -L
menubar/ # Menu-bar helper installer (source in ../menubar)
profiles.ts # Host CLI + endpoint + model bundles
```

Note: `src/lib/session/` here is the transcript **reader**. The live-session
**writer** is a separate package, [`packages/session-tracker`](../../packages/session-tracker)
— different data, different consumer; see its AGENTS.md.

## Bundled native helpers (where the tarball's `.app`s come from)

Two native helpers ship **inside** this package's npm tarball; two more are
dev-only and live at repo-root `native/`.

| Helper | Source | Ships in tarball? | Resolver |
|---|---|---|---|
| Keychain broker | `src/lib/secrets/keychain-helper.swift` → `bin/Agents CLI.app` | **Yes** (signed + notarized) | `src/lib/secrets/` |
| Menu-bar helper | [`menubar/`](menubar) (SwiftPM) → `bin/MenubarHelper.app` | **Yes** (signed, no notarization) | `src/lib/menubar/install-menubar.ts` |
| computer-mac | [`../../native/computer-mac`](../../native/computer-mac) | No (built from source) | `src/lib/computer-rpc.ts` |
| computer-win | [`../../native/computer-win`](../../native/computer-win) | No (staged at release) | `src/lib/ssh-tunnel.ts` |

Path math: compiled resolvers run from `apps/cli/dist/lib/…`. Repo-root `native/`
is **4 hops up** (`../../../../native/…`); the co-located `menubar/` is **3 hops up**
(`../../../menubar/dist/…`) because it moved into `apps/cli` with the CLI. Recompute
depth if you move files — don't blind-replace.

## Build, test, dev

```bash
bun install && bun run build && bun test
```

Tests are `*.test.ts` next to source; integration in `tests/`. Every PR to `main`
runs the real suite cheaply on Linux — `test`
([`../../.github/workflows/tests.yml`](../../.github/workflows/tests.yml)) plus
`gitleaks`; those two are the required checks. The full cross-platform matrix
(ubuntu + macOS + Windows × Node 22/24, `ci.yml`) is cost-gated to `release/**`
branches and `v*` tags. CI runs from `apps/cli` via `defaults.run.working-directory`.

**Local dev build:** `scripts/install.sh --skip-tests` builds the working tree and
installs at `$HOME/.local/agents-cli-dev/`, symlinked into `$HOME/.local/bin/agents`.
The npm-installed global is never touched. Version stamps as `0.0.0-dev.<sha>[-dirty]`.

**Bin entrypoints need `chmod 755`.** [`scripts/build.sh`](scripts/build.sh) chmods
every `package.json#bin` entry after `tsc` emits. Newer npm preserves tarball file
mode and does NOT auto-chmod — 644 surfaces as `zsh: permission denied: agents`.

The `files` allowlist in [`package.json`](package.json) is a **whitelist** — only
`dist/**`, the two signed `.app`s, and the postinstall scripts + README/LICENSE ship.
Nothing from `apps/`, `native/`, or sibling `packages/` can leak into the tarball.

## Releasing

**Releases are cut locally on macOS — there is no CI publish.** Run from a clean,
in-sync `main`:

```bash
scripts/release.sh <version> # dry-run: bump, type-check, build, test, tarball preview
scripts/release.sh <version> --apply # commits chore(release), tags v<version>, npm publish, pushes
```

`release.sh` reads the npm token from the `npmjs.com` secrets bundle (`agents
secrets`) — no 2FA prompt, no token on disk. The script's git-scope reads use
`<ref>:apps/cli/package.json` (not root) since the package moved under `apps/cli`.

**Why not CI?** The tarball bundles `dist/lib/secrets/Agents CLI.app` — a native
keychain helper compiled with `swiftc`, codesigned (Developer ID), and notarized
(`xcrun notarytool`). `prepack` ([`scripts/verify-keychain-helper.sh`](scripts/verify-keychain-helper.sh))
refuses to pack unless that signed binary matches the sha pinned in
`scripts/Agents CLI.app.sha256`. CI runners are Linux and cannot produce it. Rebuild
the helper only when `src/lib/secrets/keychain-helper.swift` changes.

**Menu-bar helper** ([`menubar/`](menubar) → `bin/MenubarHelper.app`) ships the same
way — built into `bin/`, copied to `dist/lib/menubar/` by `build`, gated in `prepack`
by [`scripts/verify-menubar-helper.sh`](scripts/verify-menubar-helper.sh) (presence +
`codesign --verify`). No notarization (a status item has no Keychain ACL / TCC
grant). Keep it a **separate bundle** from the keychain app — a menu-bar crash must
never take down the secret broker. Stage a freshly-built `bin/MenubarHelper.app`
before any release or the menu bar ships code-only (the 1.20.22 bug the gate prevents).

**The `@swarmify/agents-cli` shim is frozen at 1.19.x — do NOT "catch it up."** It's a
legacy re-export not published since v1.20.0; `release.sh` publishes only `@phnx-labs`.
Bumping it would un-deprecate a retired package.

## Conventions

- Real services only — no mocking. Tests exercise the actual critical path.
- `agents repo push` / `pull` operates on `~/.agents/` only. System updates ride
`npm update -g @phnx-labs/agents-cli`.
- No sensitive data in any DotAgents repo — use `agents secrets` (Keychain-backed).

## Detailed design

[`../../docs/`](../../docs/README.md) is source-grounded reference. Start with
[`00-concepts.md`](../../docs/00-concepts.md) for the full mental model.
46 changes: 46 additions & 0 deletions native/computer-mac/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# computer-helper (macOS)

macOS backend for `agents computer` — Swift daemon exposing Accessibility (AX) +
ScreenCaptureKit + CoreGraphics event injection over line-delimited JSON-RPC on a
Unix domain socket. Windows sibling: [`native/computer-win`](../computer-win).

This file is a **map**. [README.md](README.md) is the deep reference (permissions,
peer-auth, `.app`-bundle rationale, protocol) — read it before editing.

## Layout

```
Sources/ComputerHelper/RPC.swift Canonical method list + dispatch
Sources/ComputerHelper/*.swift AX walk, screenshot, event injection, policy, peer-auth
scripts/build.sh Build (debug single-arch / release universal)
Package.swift SwiftPM manifest
```

## Build

```bash
./scripts/build.sh # debug (single-arch)
./scripts/build.sh release # universal arm64 + x86_64
```

Outputs `dist/computer-helper-mac` (bare binary) and `dist/ComputerHelper.app`
(signed bundle, id `com.phnx-labs.computer-helper`). The `.app` form gives a stable
TCC identity that survives across launches — a bare binary inherits TCC identity
from its launcher and loses grants (see README §"Why a .app bundle").

## How the CLI reaches it

`apps/cli/src/lib/computer-rpc.ts` — `resolveHelperExec()` looks for
`native/computer-mac/dist/ComputerHelper.app/Contents/MacOS/ComputerHelper` (dev
checkout, 4 hops up from `apps/cli/dist/lib`) or the bundled npm copy. The daemon
runs under launchd on a Unix socket; the CLI connects locally (no tunnel).

## Security model (see README for full detail)

- **Default-deny allow list** — every action checks the target's bundle id against
`Computer(<bundle-id>)` rules in `~/.agents/permissions/groups/`. `agents computer
reload` pushes changes via SIGHUP.
- **Hard floor** — `com.apple.tccd`, `com.apple.SecurityAgent`,
`com.apple.systempreferences` are denied unconditionally (TCC escalation paths).
- **Peer-auth** — connecting callers are verified by exec path against
`computer-peers.json`; fail-safe-empty denies everything if the policy is missing.
53 changes: 53 additions & 0 deletions native/computer-win/AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# computer-helper-win

Windows backend for `agents computer` — C#/.NET 10 daemon, sibling of the Swift
[`native/computer-mac`](../computer-mac). Same JSON-RPC wire protocol, same result
shapes; one TS client (`apps/cli/src/lib/computer-rpc.ts`) drives both.

This file is a **map**. Read the code (and [README.md](README.md)) for current detail.

## Layout

```
Program.cs Entry point (top-level statements) — TCP listener, auth, RPC loop
Rpc.cs Method dispatch table (19 methods)
Automation.cs UI Automation tree walk + SendInput (click/type/key/scroll/drag) + window focus
Screenshot.cs Graphics.CopyFromScreen over the virtual screen
Apps.cs Process/window enumeration; launch_app via PATH + App Paths registry
ElementCache.cs @eN element handle cache
smoke/smoke.mjs Smoke test (Unicode-typing regression loops for #554/#581)
computer-helper-win.csproj net10.0-windows, WPF + WinForms enabled
```

## Build

```bash
bash ../../apps/cli/scripts/build-win.sh # dotnet publish -r win-x64 --self-contained -p:PublishSingleFile
```

Output: `dist/computer-helper-win.exe` (gitignored; staged into the npm tarball at
release). Needs the **.NET 10 SDK**; cross-publishes from macOS/Linux
(`EnableWindowsTargeting`).

## How the CLI reaches it

`apps/cli/src/lib/ssh-tunnel.ts` — `resolveWinHelperExe()` looks for
`native/computer-win/dist/computer-helper-win.exe` (dev checkout, 4 hops up from
`apps/cli/dist/lib`) or the bundled npm copy. `setupRemoteHelper()` scp's the exe
to `%LOCALAPPDATA%\agents\`, registers a Task Scheduler task
(`AgentsComputerHelper`, `-AtLogOn`, interactive), and starts it. The CLI connects
over an `ssh -L` tunnel to the daemon's loopback TCP port (`8765`).

## Key differences from computer-mac (don't assume mac semantics)

- **Transport is loopback TCP + SSH tunnel**, not a Unix socket. The tunnel is the
sole ingress; an optional `--token-file` shared secret is defense-in-depth.
- **No TCC / no permission model.** Windows UIA needs no per-process grant —
`trust_status` always returns `trusted=true`. There is no allow-list,
`computer-policy.json`, or peer-auth file. Access control = the SSH tunnel.
- **Lifecycle is Task Scheduler, not launchd** — it must run in the interactive
desktop session (not Session 0) for UIA + screen capture to work.
- **Single-file needs native-lib self-extraction** (`IncludeNativeLibrariesForSelfExtract`)
or UIA throws on the first tree walk (#519).
- **`bundle_id` = process image name** (`notepad`), not reverse-DNS.
- **`notify` is pass-through only** — no Windows Toast; Rush intercepts the return.
Loading
Loading