@phnx-labs/agents-cli 1.20.47 → 1.20.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 1.20.48
6
+
7
+ - **Menu-bar helper: a RECENT TICKETS section shows the issues you filed via the quick-issue bar, each clickable to open in Linear.** The completion notification is transient, so the tickets the `Cmd-Shift-O` bar creates now also persist to a small local ledger (`~/.agents/.history/menubar/recent-tickets.json`, newest-first, deduped by id, capped at 10) that the menu-bar dropdown surfaces below RECENT sessions — click a row to open the ticket. The dispatch records the id + note + Linear URL on a successful create; the section renders nothing when the ledger is empty. Source: `apps/cli/menubar/Sources/MenubarHelper/{RecentTickets,StatusItemController,AgentsCLI,IssueSelfTest}.swift`.
8
+ - **Menu-bar helper: the quick-issue completion notification now deep-links to the created ticket, and the helper self-heals onto the install you actually run.** Two fixes from dogfooding the `Cmd-Shift-O` bar. (1) **Clickable notification** — the "Created RUSH-####" banner carried no click target, so there was no way to open the ticket. The ticket agent now also prints the issue's `URL:` line, the helper parses it, and clicking the notification (or its **Open** button) opens the ticket in Linear (via an `NSUserNotificationCenterDelegate`; the banner is also force-presented so it can't be silently swallowed when the accessory app is frontmost). (2) **Dual-install self-heal** — the helper bakes the node interpreter + CLI entry into its launchd plist so a GUI process can find `agents` without a login PATH, but the staleness check only re-baked on a *version* change. With two installs present (e.g. an nvm copy and a bun copy), the plist kept pointing at whichever copy first wrote it, so the menu data **and** the quick-issue dispatch ran on a stale install even after `agents upgrade`. The startup self-heal now also re-points when the plist's baked `AGENTS_ENTRY`/`AGENTS_NODE` no longer match the install currently running `agents` (a null active entry — a dev/tsx run — never churns the plist). Source: `apps/cli/menubar/Sources/MenubarHelper/{PromptPanel,AgentsCLI,IssueSelfTest}.swift`, `apps/cli/src/lib/menubar/install-menubar.ts`.
9
+ - **The npm release can now be driven from a Linux box** by offloading the Mac-only helper signing to a remote sign host. The tarball bundles two signed macOS `.app` helpers a Linux runner can't build — `bin/Agents CLI.app` (the keychain helper: `swiftc` universal → codesign with entitlements + embedded provisioning profile → `notarytool` → staple) and `bin/MenubarHelper.app` (the menu-bar status item: `swift build` → codesign, no notarization) — which is the only reason publishing was macOS-pinned. New `scripts/remote-sign-mac.sh` (invoked automatically by `release.sh` when it runs on a non-macOS host and the signed apps are absent, or on any host with `FORCE_REMOTE_SIGN=1`) rsyncs the build inputs to `${SIGN_HOST:-mac-mini}`, runs both Mac build scripts there under the appliance's headless signing creds (unlocks `rush-signing.keychain-db`, injects Apple notary creds via the `apple.com` secrets bundle), then pulls the signed `bin/*.app` back and re-verifies the keychain sha locally. The `build` script now copies the helpers into `dist/` on a **presence** gate (`[ -d 'bin/…' ]`) instead of `[ "$(uname)" = 'Darwin' ]`, so a Linux box that pulled the pre-signed bundles packages them, and `prepack`'s sha gate uses `shasum` or `sha256sum` (whichever is present) so it works on Linux too. Override the sign host with `SIGN_HOST` and its checkout with `SIGN_HOST_REPO`. Source: `apps/cli/scripts/remote-sign-mac.sh`, `apps/cli/scripts/release.sh`, `apps/cli/scripts/verify-keychain-helper.sh`, `apps/cli/package.json`.
10
+
11
+ - **The shim self-heal now repairs shims that point at a *removed* install and prunes orphaned command shims.** A dispatch shim bakes its `AGENTS_BIN` (the agents-cli entrypoint it execs) at generation time, so when that install moves or is deleted — a dev build under `~/.local/agents-cli-dev`, an old npm-global under `/opt/homebrew`, a rotated version dir — the shim keeps pointing at the dead path. Agent shims survive it via their runtime self-recovery block, but the previous self-heal only compared the *schema marker*, so a schema-current shim aimed at a removed install read as healthy and was never repaired. Two additions to the `shims` self-heal check (daemon + interactive startup): (1) **drift repair** — an agent shim whose baked `AGENTS_BIN` names a *different, now-missing* install is force-regenerated to the current install (`shimPointsAtLiveInstall`); a shim pointing at another install that still exists is left alone, so two live installs sharing the shims dir can't ping-pong. (2) **orphan prune** — legacy standalone command shims (`browser`/`secrets`/`sessions`/`teams`/`pty`) that a removed install left in the shims dir, which the current source never regenerates and which either die with `exit 127` or shadow the real package bin on PATH, are removed when their baked install is gone (`pruneOrphanedCommandShim`); user `agents alias` shims and any shim whose install still exists are spared. Verified end-to-end against a real machine carrying a deleted dev build + a removed Homebrew install: the agent shims repoint to the live install and five dead command shims are pruned. Source: `apps/cli/src/lib/shims.ts` (`shimPointsAtLiveInstall`, `pruneOrphanedCommandShim`, `listShimFileNames`), `apps/cli/src/lib/self-heal/checks/shims.ts`.
5
12
  ## 1.20.47
6
13
 
7
14
  - **Quick-issue bar (`Cmd-Shift-O`): `Cmd-V` now pastes into the note field, and double-clicking a screenshot thumbnail opens it in Preview.** Two fixes from dogfooding the new bar. (1) The panel is a borderless `.accessory` window with **no main menu**, so the standard clipboard key-equivalents (`Cmd-V`/`C`/`X`/`A`) were never dispatched to the field editor — paste silently did nothing. `PromptPanel.performKeyEquivalent` now routes them through the responder chain so the text field handles them. (2) Thumbnails are small, so there was no way to confirm which screenshot you were attaching: **single click still toggles selection, double click opens the full image in the default viewer (Preview)**. The single-click toggle is deferred by the double-click interval so a double-click previews without also flipping the selection, and the bar suppresses its own click-outside dismissal while Preview takes focus (so summoning Preview never closes the bar or drops your typed note; it re-arms when the bar regains focus). Source: `apps/cli/menubar/Sources/MenubarHelper/PromptPanel.swift`.
@@ -13,11 +20,9 @@
13
20
  ## 1.20.46
14
21
 
15
22
  - **NEW: `Cmd-Shift-O` opens a Spotlight-style quick-issue bar in the menu-bar helper — type a sentence, attach recent screenshots, and an agent files the Linear ticket for you.** The menu-bar helper already turned a screenshot into a `<host>:<path>` token with `Cmd-Shift-V` (clip capture), but there was no path from "I see a bug" to "a triaged ticket exists." The new chord summons a borderless panel (a thin capture surface, not another form): you type a one-line note, optionally toggle one or more recent screenshots (from the system screencapture folder, CleanShot's export path, or the clip history) as a thumbnail strip (the newest is pre-selected when it's fresh), and hit Return. It then **dispatches a headless agent** (`agents run claude --mode auto`, isolated behind one `AgentsCLI.dispatchTicketAgent` call so a cloud pod is a later swap) that reads the screenshots, runs `agents sessions` to identify which repo/project this concerns, does a brief investigation for real context, and files the ticket via `~/.agents/skills/linear/scripts/linear create` with an honest priority + a `repo:<name>` label — no preview step, the panel closes immediately and a notification reports the created `RUSH-####`. Focus is handled for a no-Dock `.accessory` app (`NSApp.activate` → `makeKeyAndOrderFront` → `makeFirstResponder`, with a borderless `NSPanel` overriding `canBecomeKey`; click-outside dismissal is armed only after the summon settles so the activation race can't self-dismiss the panel). The `Cmd-Shift-V` clip hotkey is unchanged — the Carbon hotkey manager now demultiplexes both chords by `EventHotKeyID.id` through one installed handler. Self-test: `MENUBAR_ISSUE_TEST=1 MenubarHelper` exercises screenshot selection, ticket-id parsing, and the meta-prompt contract; `MENUBAR_PROMPT_PREVIEW=1` renders the panel without the global hotkey for QA. Source: `apps/cli/menubar/Sources/MenubarHelper/{PromptPanel,Hotkey,AgentsCLI,main,IssueSelfTest,Clip}.swift`.
16
-
17
23
  - **NEW: a unified self-heal subsystem — the shim/PATH "repair" notice no longer nags on every terminal, and the daemon now heals shim drift in the background.** agents-cli had accumulated ~37 separate repair routines scattered across the daemon, every CLI startup, and a handful of commands, each hand-rolling its own detect+fix on its own trigger. The most visible symptom: the interactive shim bootstrap (`maybeBootstrapShimIntegration`) regenerated shims, adopted shadowing launchers, and offered to add the shims dir to PATH **in the foreground on every invocation**, suppressed only by a `process.ppid`-keyed temp sentinel — so a new terminal re-ran the whole detect-and-nag, and the underlying condition was never permanently fixed. This lands a single `HealCheck` registry (`lib/self-heal/`) with one runner (`runSelfHeal`) driven by two front doors — the daemon (on its existing ~30s-after-start + ~6h `safe`-mode cycle) and the interactive startup — sharing the same checks: `shims` (regenerate stale shims/aliases), `shadowing` (adopt symlink launchers; report real-binary shadows), `path` (add the shims dir to PATH once), and `resources` (the existing `heal()` engine, wrapped unchanged). The daemon's heal cycle now runs all four in `safe` mode (low-risk fixes silently; risky ones reported), replacing the resource-only `heal()` call — and drops the desktop toast for background heals (the log is the record). The interactive startup now heals **silently** and prints at most a **persistent, once-per-condition** notice (`lib/shim-heal.ts`, keyed to a signature of the actionable state under `~/.agents/.cache/state/shim-notice.json`) for what a machine genuinely can't fix for you — a real native binary shadowing the shim — instead of re-nagging every shell. What changes is *where* the repairs run (background/silent) and *how often* you hear about them (once, not every terminal). Source: `apps/cli/src/lib/self-heal/` (new), `apps/cli/src/lib/shim-heal.ts` (new), `apps/cli/src/lib/daemon.ts`, `apps/cli/src/index.ts`, `apps/cli/src/lib/shims.ts` (`isShimCurrent` exported).
18
24
  ## 1.20.45
19
25
  - **NEW: `agents run <agent> --host <name>` without a prompt forwards your TTY over SSH and runs the agent interactively on the remote host.** Previously `--host` runs required a prompt and were always headless (`agents run <agent> "<task>" --host <name>`). Now, omitting the prompt takes the interactive path: when local stdin is a TTY, the local CLI SSHes with `-tt`, runs `agents run <agent>` on the host, and lets the remote machine's `agents` start its normal tmux wrapper. The tmux session lives on the remote box, so detaching (`Ctrl-b d`) ends the SSH connection but keeps the agent running; you can reattach from the host or resume by session id. Session ids for Claude are still minted up front so `agents sessions` can surface and resolve the remote run. `--no-follow` is rejected for interactive host runs (it is meaningless for an attached TTY), and `--mode`, `--model`, `--name`, passthrough args after `--`, and `--raw`/`--no-tmux` are forwarded to the remote invocation. Source: `apps/cli/src/commands/exec.ts`, `apps/cli/src/lib/hosts/dispatch.ts`, `apps/cli/src/lib/hosts/session-index.ts`, `apps/cli/docs/hosts.md`.
20
-
21
26
  - **`agents secrets export --host` now works against Windows targets, and a new `agents secrets unlock --host` unlocks a bundle on a remote machine.** The export push was POSIX-only (`bash -lc`, `--from /dev/stdin`, `create … || true`, `IFS= read`), so a Windows remote died with `'true' is not recognized … cannot find the path specified`. Two changes fix it: `agents secrets import` now accepts **`--from -`** (read the `.env` from stdin, replacing the POSIX-only `/dev/stdin`), and the push is **platform-aware** — `bash -lc` on POSIX, `powershell -EncodedCommand` on Windows, with the target's OS taken from the device registry. Because the npm `agents.ps1` shim does **not** forward ssh-piped stdin to the underlying node process (a raw `--from -` read hangs), the Windows keychain push bridges the piped `.env` through PowerShell into a temp file and imports `--from <file>` (deleted afterwards). File-backend export to a Windows target is refused cleanly rather than emitting broken PowerShell. Verified end-to-end: `agents secrets export linear.app --host win-mini` imported all 13 keys. Separately, **`agents secrets unlock --host <machine> <bundle>`** runs the unlock ON the remote over `ssh -tt`, so a **file-backed** bundle's passphrase prompt surfaces on your terminal — the "unlock the Mac from the road with its password" path; keychain/biometry bundles are GUI-only (a local Touch-ID/passcode sheet can't cross SSH) and can't be remote-unlocked. `unlock`'s `--host` is single-valued so it never swallows the positional bundle name. Source: `apps/cli/src/commands/secrets.ts`, `apps/cli/src/lib/hosts/remote-cmd.ts`.
22
27
  - **A session now has ONE name, not two. `--name` seeds the session label instead of a parallel column.** Shipping `agents run --name` (1.20.43) as a separate immutable `name` column created two look-alike fields — an unshown, frozen `name` and the shown, searchable `label` — that both resolved `agents sessions <ref>` and forced tie-break bookkeeping nobody could keep straight. They unify into one field. `--name` is now the universal way to *seed* the `label` at launch — the same field an agent-generated title (Claude's `/rename`) later refines and `agents sessions` displays and searches — and it works consistently across interactive, headless, `--host`, and teams teammate runs (a teammate's friendly name now seeds its session label; before, teammate sessions had no name at all). Priority is a plain fallback chain resolved at scan time, no stored winner: an agent-generated title wins, else the `--name` seed, else the listing falls back to `topic`. So a Claude run's `--name` shows until Claude titles it (your seed, then refined); a non-Claude run keeps its `--name` as the label (it has no auto-title). The seeded name is now fuzzy-searchable in FTS (the old `name` column was not). `agents hosts logs <name>` is unchanged — it resolves against the host-task sidecar, not the session column. Schema v10 folds any existing `name` into `label` (where the label was empty), mirrors it into the FTS row, then drops the `name` column; the run-name sidecars re-seed every scan (`seedLabelsFromNames`), so no rescan is needed. Reworks the 1.20.43 `--name` design (partly reverts its separate-column approach). Source: `apps/cli/src/lib/session/{db,discover,run-names,types}.ts`, `apps/cli/src/lib/hosts/session-index.ts`, `apps/cli/src/lib/teams/agents.ts`, `apps/cli/src/commands/exec.ts`, `apps/cli/docs/{05-sessions,hosts}.md`.
23
28
  - **NEW: `agents teams add`/`start` warns when a *version-pinned* teammate is on a throttled or signed-out account.** The 1.20.43 `balanced`-default fix keeps *bare* teammates off rate-limited accounts (they route through bare `agents run`, which rotates), but a **version-pinned** (`claude@2.1.112`) or **profile** teammate spawns `agents run <agent>@<version>` / `agents run <profile>`, and a pin/profile deliberately *bypasses* rotation — so it would launch straight onto a maxed account and 429 on the first request, with no mid-run failover either (that only arms when a non-pinned strategy actually rotated). `agents teams add` (at add time) and `agents teams start` (per staged teammate, deduped by `agent@version`) now pre-check a **version-pinned** teammate's account and print an advisory when it's rate-limited, out of credits, or not signed in — reusing the router's *exact* eligibility gate (`checkRunAccountReadiness` → `hasUsageAvailable`, the same session-inclusive signal the `agents view` badge uses), so the warning can never disagree with what the spawn would actually do. It **warns, never blocks** (mirroring the existing "may not be signed in" advisory); `--force` silences it. Scoped to version-pinned teammates on purpose: bare teammates are already handled by rotation, and a profile injects its own auth (a different account than the version home carries) that isn't locally checkable — so no unreliable profile warning is emitted. Source: `apps/cli/src/lib/rotate.ts` (`readinessFromCandidate`, `checkRunAccountReadiness`, `rotate.test.ts`), `apps/cli/src/commands/teams.ts`.
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
  <a href="https://github.com/phnx-labs/agents-cli"><img src="https://img.shields.io/badge/github-phnx--labs%2Fagents--cli-blue?style=flat-square" alt="github" /></a>
12
12
  </p>
13
13
 
14
- **The missing toolchain for CLI coding agents.** Run any agent on your existing subscription. Spawn parallel teams in isolated terminals. Schedule routines, drive browsers and Electron apps, and store secrets behind Touch ID — all from one CLI.
14
+ **The missing toolchain for CLI coding agents.** Run any agent on your existing subscription. Spawn parallel teams in isolated terminals or dispatch to the cloud for a PR. Watch live state across the fleet, nudge stalled runs, and message agents mid-flight. Schedule routines, drive browsers and Electron apps, store secrets behind Touch ID, and file tickets from a menu-bar bar — all from one CLI.
15
15
 
16
16
  <p align="center">
17
17
  <a href="https://github.com/anthropics/claude-code" title="Claude Code"><img src="assets/harnesses/anthropic.svg" height="32" alt="Claude Code" /></a>
@@ -51,14 +51,17 @@ Also available as `ag` -- all commands work with both `agents` and `ag`.
51
51
  - [One config, every agent](#one-config-every-agent)
52
52
  - [Run any agent](#run-any-agent)
53
53
  - [Sessions across agents](#sessions-across-agents)
54
+ - [Control the fleet](#control-the-fleet)
54
55
  - [Run open models through Claude Code](#run-open-models-through-claude-code)
55
56
  - [Teams](#teams)
57
+ - [Cloud](#cloud)
56
58
  - [Workflows](#workflows)
57
59
  - [Browser](#browser)
58
60
  - [Secrets](#secrets)
59
61
  - [Routines](#routines)
60
62
  - [PTY](#pty)
61
63
  - [Portable setup](#portable-setup)
64
+ - [Menu bar](#menu-bar)
62
65
  - [Private skills](#private-skills)
63
66
  - [Security & Privacy](#security--privacy)
64
67
  - [Compatibility](#compatibility)
@@ -188,7 +191,62 @@ agents sessions a1b2c3d4 --last 3 --include user
188
191
 
189
192
  Interactive picker when you're in a terminal. Structured output (`--json`, `--markdown`, filtered by role or turn count) when piped.
190
193
 
191
- Backed by a SQLite + FTS5 index at `~/.agents/.history/sessions/sessions.db` with incremental scanning -- warm reads in ~100ms. External tools can consume `--json` output as a programmatic observability layer; see [docs/05-sessions.md](docs/05-sessions.md) for the schema and [docs/06-observability.md](docs/06-observability.md) for the consumption patterns.
194
+ Backed by a SQLite + FTS5 index at `~/.agents/.history/sessions/sessions.db` with incremental scanning -- warm reads in ~100ms. External tools can consume `--json` output as a programmatic observability layer; see [docs/05-sessions.md](apps/cli/docs/05-sessions.md) for the schema and [docs/06-observability.md](apps/cli/docs/06-observability.md) for the consumption patterns.
195
+
196
+ ### Live state, and catching up fast
197
+
198
+ Search is the past tense. `--active` is the present -- it infers what each running session is *doing right now* from the tail of its transcript.
199
+
200
+ ```bash
201
+ agents sessions --active # every live run across the fleet, with state
202
+ agents sessions focus a1b2c3d4 # jump back into one — attach in place, or resume
203
+ ```
204
+
205
+ Each live session resolves to `working`, `waiting_input` (with why -- a question, a plan review, or a permission prompt), or `idle`, alongside badges for the PR it opened, the worktree it sits in, and the ticket it's working. `agents sessions focus [id]` attaches the live pane in place -- the tmux split locally or over SSH, or its Ghostty tab -- and falls back to a fresh tab + resume when the terminal is gone.
206
+
207
+ Landing on a session cold? `agents sessions <id>` prints a catch-up digest: an inferred title, files changed grouped by directory (created / modified / deleted), a histogram of which tools did the work, and the last test verdict -- the signals to reload a task in seconds.
208
+
209
+ ### Resume anywhere — and stay resumed
210
+
211
+ Pick up any past conversation and drop it back into a terminal:
212
+
213
+ ```bash
214
+ agents sessions resume # multi-select; packs two sessions per tab
215
+ agents sessions resume "auth middleware" # pre-filter the pool, then choose
216
+ agents sessions resume --tmux # into persistent tmux — survives editor restarts
217
+ agents sessions resume --host zion --tmux # resume on another machine over SSH
218
+ ```
219
+
220
+ `agents sessions resume` reopens sessions in whatever terminal you're in -- auto-detected across iTerm, Ghostty, tmux, and the VSCodium agent-terminal, or forced with `--iterm` / `--ghostty` / `--tmux` / `--vscodium`. Back them with **tmux** and the runs turn durable: detach, close your editor, reboot the GUI -- the session is still alive to `agents tmux attach`. The whole `agents tmux` subsystem (persistent multiplexer sessions that survive editor restarts and can be shared with other tools) sits underneath.
221
+
222
+ ---
223
+
224
+ ## Control the fleet
225
+
226
+ Running agents aren't fire-and-forget. Steer them mid-run without opening their terminals.
227
+
228
+ <p align="center">
229
+ <img src="assets/fleet-control.svg" alt="agents sessions infers live state (working, waiting, idle); watchdog injects Continue into the exact stalled split; message reaches a running agent at its next tool call" width="100%" />
230
+ </p>
231
+
232
+ ### Message a running agent
233
+
234
+ ```bash
235
+ # Delivered at the agent's next tool call — no restart, no lost context.
236
+ agents message tester "also cover the null case"
237
+ ```
238
+
239
+ `agents message <target> <text>` reaches any running agent by name or id -- a live local run, a teammate, a loop agent, or a cloud task -- and the text lands at its next tool call. Tag the sender with `--from <who>`.
240
+
241
+ ### Auto-nudge stalls
242
+
243
+ ```bash
244
+ agents watchdog # one tick, dry run — reports what it WOULD nudge and why
245
+ agents watchdog --nudge # actually inject "Continue." into the stalled split
246
+ agents watchdog --watch # daemon loop: a tick every --interval
247
+ ```
248
+
249
+ `agents watchdog` detects a stalled session, resolves the *exact* terminal split it lives in (tmux, iTerm, VSCodium, or a raw pty), and injects a nudge -- `Continue.` by default, or set `--text`. It's dry by default; `--nudge` acts on a single tick, and `agents watchdog enable` flips global auto-nudge on so `--watch` injects on its own. Steer a single run with `agents watchdog policy <id> off | keep | handsoff`.
192
250
 
193
251
  ---
194
252
 
@@ -242,7 +300,8 @@ agents hosts add gpu-box
242
300
  agents hosts check gpu-box # reachable? which agents-cli version?
243
301
 
244
302
  # Run there instead of locally
245
- agents run claude --host gpu-box "profile this build" # follows live by default
303
+ agents run claude --host gpu-box "profile this build" # headless: follows live by default
304
+ agents run claude --host gpu-box # no prompt → interactive TTY over SSH (tmux-backed)
246
305
  agents logs --host gpu-box # pick a dispatched run — concise summary by default
247
306
  agents logs <id> --full # the full raw transcript / stdout (token-heavy)
248
307
  agents logs <id> -f # re-attach to a running one and follow
@@ -255,9 +314,9 @@ agents ssh mac-mini # hardened SSH: fails fast if offline,
255
314
  # PowerShell on Windows, password-from-Keychain
256
315
  ```
257
316
 
258
- **Hosts** (`agents hosts`) are git-synced dispatch targets in `agents.yaml`; **devices** (`agents devices`) are your Tailscale machines in a local registry. Both ride SSH. See [docs/00-concepts.md](docs/00-concepts.md#devices--hosts).
317
+ **Hosts** (`agents hosts`) are git-synced dispatch targets in `agents.yaml`; **devices** (`agents devices`) are your Tailscale machines in a local registry. Both ride SSH. See [docs/00-concepts.md](apps/cli/docs/00-concepts.md#devices--hosts).
259
318
 
260
- Every `--host` command rides one multiplexed SSH engine, tuned for driving a fleet from a small laptop: the first call to a machine opens a control socket and every later call reuses it (no repeat TCP+auth handshake), connections carry keepalive so a dropped link dies in ~45 s instead of zombying, and following a remote run polls in a single round-trip per cycle. Measured against a Tailscale-relayed host: repeated calls **~6–7× faster**, dispatch readiness **~2×**, and the follow loop **~21× faster with 50% fewer local ssh spawns**. Design: [docs/09-ssh-transport.md](docs/09-ssh-transport.md) · reproduce: `node scripts/bench-ssh.mjs <host>`.
319
+ Every `--host` command rides one multiplexed SSH engine, tuned for driving a fleet from a small laptop: the first call to a machine opens a control socket and every later call reuses it (no repeat TCP+auth handshake), connections carry keepalive so a dropped link dies in ~45 s instead of zombying, and following a remote run polls in a single round-trip per cycle. Measured against a Tailscale-relayed host: repeated calls **~6–7× faster**, dispatch readiness **~2×**, and the follow loop **~21× faster with 50% fewer local ssh spawns**. Design: [docs/09-ssh-transport.md](apps/cli/docs/09-ssh-transport.md) · reproduce: `node scripts/bench-ssh.mjs <host>`.
261
320
 
262
321
  ---
263
322
 
@@ -277,7 +336,39 @@ agents teams status auth-feature # Who's working, what they changed, what the
277
336
 
278
337
  Teammates run detached -- close your terminal, they keep working. Check in with `teams status`, glance at a teammate's summary with `teams logs <name>` (add `--full` for the raw output), clean up with `teams disband`.
279
338
 
280
- Team state is observable via `agents teams list --json` / `agents teams status --json` (compact by default; add `--verbose` for the full per-teammate shape). External tools join it with `sessions --json` (teammates get `isTeamOrigin: true`) and `cloud list --json` (for `--cloud` teammates) to build a unified fleet view. See [docs/06-observability.md](docs/06-observability.md).
339
+ Team state is observable via `agents teams list --json` / `agents teams status --json` (compact by default; add `--verbose` for the full per-teammate shape). External tools join it with `sessions --json` (teammates get `isTeamOrigin: true`) and `cloud list --json` (for `--cloud` teammates) to build a unified fleet view. See [docs/06-observability.md](apps/cli/docs/06-observability.md).
340
+
341
+ ---
342
+
343
+ ## Cloud
344
+
345
+ Some work shouldn't tie up your laptop. `agents cloud run` hands a task to a managed provider that clones the repo, plans, implements, tests, and opens a PR -- while your terminal stays free.
346
+
347
+ <p align="center">
348
+ <img src="assets/cloud.svg" alt="agents cloud run dispatches one prompt to a managed provider (Rush, Codex, Factory, or Antigravity) that clones, plans, tests, and opens a pull request while you keep working" width="100%" />
349
+ </p>
350
+
351
+ ```bash
352
+ # Dispatch and detach — streams to the cloud, not your terminal.
353
+ agents cloud run "fix the flaky test in the payments suite" \
354
+ --provider rush --repo acme/api --branch main
355
+
356
+ agents cloud list # what's running, queued, or needs review
357
+ agents cloud logs <id> # re-attach and stream
358
+ agents cloud message <id> "also update the changelog" # steer it mid-run
359
+ agents cloud cancel <id>
360
+ ```
361
+
362
+ Four managed backends behind one interface (`agents cloud providers`):
363
+
364
+ | Provider | What runs | Notes |
365
+ |---|---|---|
366
+ | `rush` | Claude against a GitHub repo + branch | Opens a PR. Multi-repo via repeatable `--repo`; attach screenshots with `--image` for vision dispatch. |
367
+ | `codex` | A pre-built Codex Cloud environment | Target it with `--env`. |
368
+ | `factory` | `droid exec` on a cloud VM | Computer-use; pick the box with `--computer`. |
369
+ | `antigravity` | Gemini managed agents | Antigravity harness in a remote sandbox. |
370
+
371
+ Auto-routes each `--agent` to its native cloud, or pin the backend with `--provider`. Instead of dispatching now, register a run as an **event trigger** with `--on pull_request` (also `push`, `issue_comment`, `workflow_run`) -- it persists as a trigger-bound routine that fires on the event. `--json` on every subcommand for scripting.
281
372
 
282
373
  ---
283
374
 
@@ -582,9 +673,30 @@ Two repos with the same shape, different roles:
582
673
 
583
674
  **Resource resolution:** When syncing resources (commands, skills, rules, hooks, MCP, permissions), the order is **project > user > system**. A `.agents/` directory at project root wins, then `~/.agents/`, then `~/.agents-system/`. Same-named resources higher in the chain override lower ones; everything else unions in.
584
675
 
585
- See [docs/00-concepts.md](docs/00-concepts.md) for the full mental model: DotAgents repos, resource kinds, and how resolution works end-to-end.
676
+ See [docs/00-concepts.md](apps/cli/docs/00-concepts.md) for the full mental model: DotAgents repos, resource kinds, and how resolution works end-to-end.
677
+
678
+ Other useful commands: `agents doctor` checks CLI availability and resource sync drift, `agents usage` shows available quota/rate-limit data for installed agents, `agents budget` shows cross-vendor spend caps and current spend-to-cap (and enforces pre-flight estimates + a hard-cap kill-switch on every run — see [docs/06-observability.md](apps/cli/docs/06-observability.md#budget-guardrails-agents-budget)), `agents import` adopts an existing unmanaged install, `agents trash` lists and restores soft-deleted version directories, and `agents subagents` installs reusable subagent definitions for parent-agent workflows.
586
679
 
587
- Other useful commands: `agents doctor` checks CLI availability and resource sync drift, `agents usage` shows available quota/rate-limit data for installed agents, `agents budget` shows cross-vendor spend caps and current spend-to-cap (and enforces pre-flight estimates + a hard-cap kill-switch on every run — see [docs/06-observability.md](docs/06-observability.md#budget-guardrails-agents-budget)), `agents import` adopts an existing unmanaged install, `agents trash` lists and restores soft-deleted version directories, and `agents subagents` installs reusable subagent definitions for parent-agent workflows.
680
+ ---
681
+
682
+ ## Menu bar
683
+
684
+ On macOS, `agents-cli` puts a status item in your menu bar -- a live glance at what your agents are doing, plus a Spotlight-style bar for filing work without breaking focus.
685
+
686
+ ```bash
687
+ agents menubar enable # install + launch at login
688
+ agents menubar status # is it installed and running?
689
+ ```
690
+
691
+ The dropdown surfaces a **NEEDS YOU** queue (agents waiting on a question, a plan review, or a permission prompt), the running roster, and a routines summary -- the same live state as `agents sessions --active`, one click away.
692
+
693
+ ### Quick-issue bar (⌘⇧O)
694
+
695
+ Press `Cmd-Shift-O` anywhere for a thin capture surface: type a one-line note, `Cmd-V` to paste, and attach one or more recent screenshots (double-click a thumbnail to preview it in full). Submit, and a headless agent picks the right project from your recent sessions, investigates, and files the Linear ticket itself -- you never leave what you were doing.
696
+
697
+ <p align="center">
698
+ <img src="assets/menubar-quickissue.svg" alt="The Cmd-Shift-O quick-issue bar: a one-line note with attached screenshot thumbnails that a headless agent turns into a filed Linear ticket" width="100%" />
699
+ </p>
588
700
 
589
701
  ---
590
702
 
@@ -675,7 +787,7 @@ By default, secrets sync via iCloud Keychain to your other Macs. With `--no-iclo
675
787
 
676
788
  ## Compatibility
677
789
 
678
- Which DotAgents resources each agent CLI can load. Source of truth: [src/lib/agents.ts](src/lib/agents.ts) (`capabilities`); gates use `supports(agent, cap, version)` from [src/lib/capabilities.ts](src/lib/capabilities.ts). Full matrix also in [docs/00-concepts.md](docs/00-concepts.md).
790
+ Which DotAgents resources each agent CLI can load. Source of truth: [src/lib/agents.ts](apps/cli/src/lib/agents.ts) (`capabilities`); gates use `supports(agent, cap, version)` from [src/lib/capabilities.ts](apps/cli/src/lib/capabilities.ts). Full matrix also in [docs/00-concepts.md](apps/cli/docs/00-concepts.md).
679
791
 
680
792
  > **† Gemini CLI is deprecated.** Google retired it for free, Pro, and Ultra tiers on **June 18, 2026** (announced at Google I/O 2026); the `gemini` command no longer serves requests on those tiers. agents-cli still manages existing installs, but warns on `agents add gemini` and `agents teams add … gemini`. New setups should use **Antigravity CLI** (`antigravity`), Google's official successor — see [the transition notice](https://developers.googleblog.com/an-important-update-transitioning-gemini-cli-to-antigravity-cli/).
681
793
 
@@ -794,21 +906,32 @@ Profiles (experimental — available by default). Works with LiteLLM Proxy, Olla
794
906
 
795
907
  ### Can I add support for a new agent?
796
908
 
797
- Agents are defined in [src/lib/agents.ts](src/lib/agents.ts) -- each is a config object declaring commands dir, rules file, and capabilities. PRs welcome.
909
+ Agents are defined in [src/lib/agents.ts](apps/cli/src/lib/agents.ts) -- each is a config object declaring commands dir, rules file, and capabilities. PRs welcome.
798
910
 
799
911
  ### What's the relationship to Phoenix Labs / Rush?
800
912
 
801
913
  `agents-cli` is an open client maintained by Phoenix Labs. Rush is a separate product. No Rush account required, no upsell.
802
914
 
915
+ ## This monorepo also contains
916
+
917
+ `@phnx-labs/agents-cli` is the published package and this README is its front page — but the repo houses more. No JS workspaces: each package installs and builds independently (`bun install` inside it).
918
+
919
+ | Path | What |
920
+ |---|---|
921
+ | [`apps/cli`](apps/cli) | **The CLI** (this README) — version management, config sync, sessions, teams, cloud, browser, computer, secrets. |
922
+ | [`apps/factory`](apps/factory) | **Factory** — a VS Code extension that spawns agent terminals as tabs and adds the Factory Floor dashboard. A separate product with its own publish identity. |
923
+ | [`native/computer-mac`](native/computer-mac) · [`native/computer-win`](native/computer-win) | Native backends behind `agents computer` — Swift (macOS Accessibility + screen capture) and C#/.NET (Windows UI Automation). |
924
+ | [`packages/session-tracker`](packages/session-tracker) | The `SessionStart` hook that writes live-session state the CLI reads back. |
925
+
803
926
  ## Contributing
804
927
 
805
928
  ```bash
806
929
  git clone https://github.com/phnx-labs/agents-cli
807
- cd agents-cli
930
+ cd agents-cli/apps/cli
808
931
  bun install && bun run build && bun test
809
932
  ```
810
933
 
811
- Commands in [src/commands/](src/commands/), libraries in [src/lib/](src/lib/), tests as `*.test.ts` under vitest. [CLAUDE.md](CLAUDE.md) has the full style guide. [docs/04-landscape.md](docs/04-landscape.md) covers the competitive landscape.
934
+ Commands in [`apps/cli/src/commands/`](apps/cli/src/commands/), libraries in [`apps/cli/src/lib/`](apps/cli/src/lib/), tests as `*.test.ts` under vitest. [CLAUDE.md](CLAUDE.md) has the full style guide. [docs/04-landscape.md](apps/cli/docs/04-landscape.md) covers the competitive landscape.
812
935
 
813
936
  ## License
814
937
 
@@ -48,6 +48,22 @@ export declare function isMenubarStale(opts: {
48
48
  currentVersion: string;
49
49
  execExists: boolean;
50
50
  }): boolean;
51
+ /**
52
+ * Pure re-point decision (no I/O): the plist's baked interpreter/entry no longer
53
+ * match the install that is now running `agents`. This catches DUAL-INSTALL skew
54
+ * that a version bump can't — e.g. the plist was baked by an nvm copy but the
55
+ * user's `agents` now resolves to a bun copy (same or different version), so the
56
+ * helper keeps shelling the stale install for its menu data AND the quick-issue
57
+ * dispatch. A null active entry (a dev/tsx run where the compiled entry can't be
58
+ * resolved) never triggers a re-point, so it can't churn the plist onto a
59
+ * transient path.
60
+ */
61
+ export declare function menubarPlistNeedsRepoint(opts: {
62
+ plistEntry: string | null;
63
+ plistNode: string | null;
64
+ activeEntry: string | null;
65
+ activeNode: string | null;
66
+ }): boolean;
51
67
  /**
52
68
  * Stop + remove the menu-bar service and write the sticky opt-out so the
53
69
  * upgrade migration won't re-enable it.
@@ -273,6 +273,45 @@ function menubarSetupStale() {
273
273
  execExists: fs.existsSync(installedExecutablePath()),
274
274
  });
275
275
  }
276
+ /**
277
+ * Pure re-point decision (no I/O): the plist's baked interpreter/entry no longer
278
+ * match the install that is now running `agents`. This catches DUAL-INSTALL skew
279
+ * that a version bump can't — e.g. the plist was baked by an nvm copy but the
280
+ * user's `agents` now resolves to a bun copy (same or different version), so the
281
+ * helper keeps shelling the stale install for its menu data AND the quick-issue
282
+ * dispatch. A null active entry (a dev/tsx run where the compiled entry can't be
283
+ * resolved) never triggers a re-point, so it can't churn the plist onto a
284
+ * transient path.
285
+ */
286
+ export function menubarPlistNeedsRepoint(opts) {
287
+ if (!opts.activeEntry)
288
+ return false; // can't resolve the running install — don't churn
289
+ if (opts.plistEntry !== opts.activeEntry)
290
+ return true;
291
+ if (opts.activeNode && opts.plistNode !== opts.activeNode)
292
+ return true;
293
+ return false;
294
+ }
295
+ /** Read one EnvironmentVariables value from the installed service plist. */
296
+ function readPlistEnvValue(key) {
297
+ try {
298
+ const xml = fs.readFileSync(servicePlistPath(), 'utf-8');
299
+ const m = xml.match(new RegExp(`<key>${key}</key>\\s*<string>([^<]*)</string>`));
300
+ return m ? m[1] : null;
301
+ }
302
+ catch {
303
+ return null;
304
+ }
305
+ }
306
+ /** True when the installed plist points at a different install than the active one. */
307
+ function menubarSetupNeedsRepoint() {
308
+ return menubarPlistNeedsRepoint({
309
+ plistEntry: readPlistEnvValue('AGENTS_ENTRY'),
310
+ plistNode: readPlistEnvValue('AGENTS_NODE'),
311
+ activeEntry: resolveCliEntry(),
312
+ activeNode: process.execPath,
313
+ });
314
+ }
276
315
  /**
277
316
  * Stop + remove the menu-bar service and write the sticky opt-out so the
278
317
  * upgrade migration won't re-enable it.
@@ -326,7 +365,10 @@ export function installMenubarLaunchAgentOnUpgrade() {
326
365
  enableMenubarService({ clearOptOut: false });
327
366
  return;
328
367
  }
329
- if (menubarSetupStale()) {
368
+ // Re-enable (recopy helper + rewrite plist) when the version drifted OR the
369
+ // plist's baked interpreter/entry no longer point at the install now running
370
+ // `agents` — the dual-install skew a version bump alone can't catch.
371
+ if (menubarSetupStale() || menubarSetupNeedsRepoint()) {
330
372
  enableMenubarService({ clearOptOut: false });
331
373
  }
332
374
  }
@@ -4,7 +4,7 @@
4
4
  // background so the shim schema settles without user-facing churn.
5
5
  import { resultOf } from '../types.js';
6
6
  import { AGENTS } from '../../agents.js';
7
- import { ensureShimCurrent, ensureVersionedAliasCurrent, isShimCurrent, isVersionedAliasCurrent, removeLegacyUserShim, listAgentsWithInstalledVersions, } from '../../shims.js';
7
+ import { createShim, ensureShimCurrent, ensureVersionedAliasCurrent, isShimCurrent, isVersionedAliasCurrent, shimPointsAtLiveInstall, removeLegacyUserShim, listAgentsWithInstalledVersions, listShimFileNames, pruneOrphanedCommandShim, } from '../../shims.js';
8
8
  import { listInstalledVersions } from '../../versions.js';
9
9
  export const shimsCheck = {
10
10
  id: 'shims',
@@ -19,6 +19,14 @@ export const shimsCheck = {
19
19
  ensureShimCurrent(agent);
20
20
  fixed.push(`${cmd} shim`);
21
21
  }
22
+ else if (!shimPointsAtLiveInstall(agent)) {
23
+ // Schema is current but the baked AGENTS_BIN points at a different, removed
24
+ // install (dev build, old npm-global, rotated version dir). ensureShimCurrent
25
+ // would no-op on a schema-current shim, so force a rewrite to the current install.
26
+ if (!ctx.dryRun)
27
+ createShim(agent);
28
+ fixed.push(`${cmd} shim (repointed to current install)`);
29
+ }
22
30
  for (const version of listInstalledVersions(agent)) {
23
31
  if (!isVersionedAliasCurrent(agent, version)) {
24
32
  if (!ctx.dryRun)
@@ -30,6 +38,16 @@ export const shimsCheck = {
30
38
  if (!ctx.dryRun && removeLegacyUserShim(agent))
31
39
  fixed.push(`removed legacy ${cmd} shim`);
32
40
  }
41
+ // Prune orphaned legacy command shims (browser/secrets/sessions/… left by a
42
+ // removed install) whose baked AGENTS_BIN is dead — the current source never
43
+ // regenerates them, and they either die with exit 127 or shadow the real
44
+ // package bin on PATH. Only removes shims whose target install is gone.
45
+ if (!ctx.dryRun) {
46
+ for (const name of listShimFileNames()) {
47
+ if (pruneOrphanedCommandShim(name))
48
+ fixed.push(`pruned orphaned ${name} shim`);
49
+ }
50
+ }
33
51
  return resultOf(fixed, []);
34
52
  },
35
53
  };
@@ -284,6 +284,28 @@ export declare function shimExists(agent: AgentId): boolean;
284
284
  * older version that needs regeneration.
285
285
  */
286
286
  export declare function isShimCurrent(agent: AgentId): boolean;
287
+ /**
288
+ * True when the agent shim's baked `AGENTS_BIN` is fine to keep: it either already
289
+ * points at the install we'd generate now, OR points at some OTHER install that
290
+ * still exists on disk (leave it — regenerating could ping-pong two live installs
291
+ * sharing the shims dir). Returns FALSE only when the shim points at a DIFFERENT,
292
+ * now-removed install — the exact drift a deleted dev build (`~/.local/agents-cli-dev`),
293
+ * an old npm-global (`/opt/homebrew`), or a rotated version dir leaves behind. A shim
294
+ * can pass the schema check (`isShimCurrent`) yet still carry that stale path.
295
+ */
296
+ export declare function shimPointsAtLiveInstall(agent: AgentId): boolean;
297
+ /** Shim files in the shims dir, excluding the hooks/ subdir and @-versioned aliases. */
298
+ export declare function listShimFileNames(): string[];
299
+ /**
300
+ * Prune a stale, orphaned shim: one that is NOT a managed agent shim and NOT a user
301
+ * alias, whose baked `AGENTS_BIN` points at an install that no longer exists. These
302
+ * are legacy `exec "$AGENTS_BIN" <cmd>` command shims (browser/secrets/sessions/…)
303
+ * left behind by a removed install — the current source never generates them, and
304
+ * they either die with `exit 127` or shadow the real package bin on PATH. Only
305
+ * removed when the baked target is gone, so a working shim is never touched.
306
+ * Returns true if removed.
307
+ */
308
+ export declare function pruneOrphanedCommandShim(fileName: string): boolean;
287
309
  /**
288
310
  * Regenerate the shim if it's missing or outdated. Returns a status describing
289
311
  * what happened — callers can surface a one-line notice to the user ("Updated
package/dist/lib/shims.js CHANGED
@@ -1610,6 +1610,85 @@ export function isShimCurrent(agent) {
1610
1610
  const version = readShimSchemaVersion(agent);
1611
1611
  return version === SHIM_SCHEMA_VERSION;
1612
1612
  }
1613
+ /** Extract the baked `AGENTS_BIN='...'` value from a shim file, or null. */
1614
+ function readAgentsBinFromShim(shimPath) {
1615
+ try {
1616
+ const header = fs.readFileSync(shimPath, 'utf8').split('\n', 12).join('\n');
1617
+ const m = header.match(/^AGENTS_BIN=(?:'([^']*)'|"([^"]*)"|(\S+))/m);
1618
+ return m ? (m[1] ?? m[2] ?? m[3] ?? null) : null;
1619
+ }
1620
+ catch {
1621
+ return null;
1622
+ }
1623
+ }
1624
+ /**
1625
+ * True when the agent shim's baked `AGENTS_BIN` is fine to keep: it either already
1626
+ * points at the install we'd generate now, OR points at some OTHER install that
1627
+ * still exists on disk (leave it — regenerating could ping-pong two live installs
1628
+ * sharing the shims dir). Returns FALSE only when the shim points at a DIFFERENT,
1629
+ * now-removed install — the exact drift a deleted dev build (`~/.local/agents-cli-dev`),
1630
+ * an old npm-global (`/opt/homebrew`), or a rotated version dir leaves behind. A shim
1631
+ * can pass the schema check (`isShimCurrent`) yet still carry that stale path.
1632
+ */
1633
+ export function shimPointsAtLiveInstall(agent) {
1634
+ if (!shimExists(agent))
1635
+ return true; // missing shim is handled by ensureShimCurrent
1636
+ const baked = readAgentsBinFromShim(onDiskShimPath(agent));
1637
+ if (!baked)
1638
+ return true;
1639
+ if (baked === getAgentsBinForGeneratedShim())
1640
+ return true; // already the current install
1641
+ return fs.existsSync(baked); // a different install — keep only while it still exists
1642
+ }
1643
+ /** Shim files in the shims dir, excluding the hooks/ subdir and @-versioned aliases. */
1644
+ export function listShimFileNames() {
1645
+ try {
1646
+ return fs
1647
+ .readdirSync(getShimsDir(), { withFileTypes: true })
1648
+ .filter((e) => e.isFile() && !e.name.includes('@'))
1649
+ .map((e) => e.name);
1650
+ }
1651
+ catch {
1652
+ return [];
1653
+ }
1654
+ }
1655
+ /**
1656
+ * Prune a stale, orphaned shim: one that is NOT a managed agent shim and NOT a user
1657
+ * alias, whose baked `AGENTS_BIN` points at an install that no longer exists. These
1658
+ * are legacy `exec "$AGENTS_BIN" <cmd>` command shims (browser/secrets/sessions/…)
1659
+ * left behind by a removed install — the current source never generates them, and
1660
+ * they either die with `exit 127` or shadow the real package bin on PATH. Only
1661
+ * removed when the baked target is gone, so a working shim is never touched.
1662
+ * Returns true if removed.
1663
+ */
1664
+ export function pruneOrphanedCommandShim(fileName) {
1665
+ // Never touch a shim that corresponds to a real agent — agents-cli manages those.
1666
+ const isAgentCommand = Object.values(AGENTS).some((a) => a.cliCommand === fileName);
1667
+ if (isAgentCommand)
1668
+ return false;
1669
+ const shimPath = path.join(getShimsDir(), fileName);
1670
+ let content;
1671
+ try {
1672
+ content = fs.readFileSync(shimPath, 'utf8');
1673
+ }
1674
+ catch {
1675
+ return false;
1676
+ }
1677
+ if (content.includes('# Alias shim:'))
1678
+ return false; // a user `agents alias` — leave it
1679
+ const bin = readAgentsBinFromShim(shimPath);
1680
+ if (!bin)
1681
+ return false; // not an AGENTS_BIN-baked shim
1682
+ if (fs.existsSync(bin))
1683
+ return false; // its install is still alive — leave it
1684
+ try {
1685
+ fs.rmSync(shimPath);
1686
+ return true;
1687
+ }
1688
+ catch {
1689
+ return false;
1690
+ }
1691
+ }
1613
1692
  /**
1614
1693
  * Regenerate the shim if it's missing or outdated. Returns a status describing
1615
1694
  * what happened — callers can surface a one-line notice to the user ("Updated
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phnx-labs/agents-cli",
3
- "version": "1.20.47",
3
+ "version": "1.20.48",
4
4
  "description": "One CLI for all your AI coding agents - versions, config, cloud dispatch, sessions, and teams (now with first-class Grok Build CLI support)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -44,10 +44,10 @@
44
44
  "url": "https://github.com/phnx-labs/agents-cli/issues"
45
45
  },
46
46
  "scripts": {
47
- "build": "tsc && rm -rf 'dist/lib/secrets/AgentsKeychain.app' 'dist/lib/secrets/Agents CLI.app' 'dist/lib/menubar/MenubarHelper.app' && ([ \"$(uname)\" = \"Darwin\" ] && cp -R 'bin/Agents CLI.app' 'dist/lib/secrets/Agents CLI.app' || true) && ([ \"$(uname)\" = \"Darwin\" ] && [ -d 'bin/MenubarHelper.app' ] && mkdir -p 'dist/lib/menubar' && cp -R 'bin/MenubarHelper.app' 'dist/lib/menubar/MenubarHelper.app' || true)",
47
+ "build": "tsc && rm -rf 'dist/lib/secrets/AgentsKeychain.app' 'dist/lib/secrets/Agents CLI.app' 'dist/lib/menubar/MenubarHelper.app' && ([ -d 'bin/Agents CLI.app' ] && cp -R 'bin/Agents CLI.app' 'dist/lib/secrets/Agents CLI.app' || true) && ([ -d 'bin/MenubarHelper.app' ] && mkdir -p 'dist/lib/menubar' && cp -R 'bin/MenubarHelper.app' 'dist/lib/menubar/MenubarHelper.app' || true)",
48
48
  "build:bin": "scripts/build-bin.sh",
49
49
  "prepare": "npm run build",
50
- "prepack": "scripts/verify-keychain-helper.sh && scripts/verify-menubar-helper.sh",
50
+ "prepack": "cp ../../README.md README.md && scripts/verify-keychain-helper.sh && scripts/verify-menubar-helper.sh",
51
51
  "postinstall": "node scripts/postinstall.js",
52
52
  "dev": "tsx src/index.ts",
53
53
  "start": "node dist/index.js",