@phnx-labs/agents-cli 1.20.88 → 1.20.90

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.
Files changed (102) hide show
  1. package/CHANGELOG.md +503 -0
  2. package/README.md +15 -1
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/commands.js +7 -7
  5. package/dist/commands/exec.js +7 -1
  6. package/dist/commands/factory.js +26 -2
  7. package/dist/commands/funnel.js +16 -1
  8. package/dist/commands/harness.d.ts +27 -0
  9. package/dist/commands/harness.js +120 -13
  10. package/dist/commands/menubar.js +117 -34
  11. package/dist/commands/profiles.d.ts +3 -0
  12. package/dist/commands/profiles.js +1 -1
  13. package/dist/commands/routines.d.ts +19 -0
  14. package/dist/commands/routines.js +51 -7
  15. package/dist/commands/secrets-rotate-passphrase.d.ts +17 -0
  16. package/dist/commands/secrets-rotate-passphrase.js +96 -0
  17. package/dist/commands/secrets.d.ts +10 -1
  18. package/dist/commands/secrets.js +20 -6
  19. package/dist/commands/sessions-browser.d.ts +4 -0
  20. package/dist/commands/sessions-browser.js +51 -9
  21. package/dist/commands/sessions-favorite.d.ts +20 -0
  22. package/dist/commands/sessions-favorite.js +120 -0
  23. package/dist/commands/sessions.d.ts +110 -21
  24. package/dist/commands/sessions.js +395 -74
  25. package/dist/commands/setup-secrets.d.ts +7 -0
  26. package/dist/commands/setup-secrets.js +12 -9
  27. package/dist/commands/versions.js +12 -4
  28. package/dist/commands/view.d.ts +14 -1
  29. package/dist/commands/view.js +103 -128
  30. package/dist/commands/webhook.js +7 -2
  31. package/dist/lib/agents.d.ts +4 -2
  32. package/dist/lib/agents.js +21 -6
  33. package/dist/lib/commands.js +9 -1
  34. package/dist/lib/daemon.d.ts +29 -0
  35. package/dist/lib/daemon.js +58 -4
  36. package/dist/lib/events.d.ts +1 -1
  37. package/dist/lib/factory/snapshot.d.ts +78 -0
  38. package/dist/lib/factory/snapshot.js +209 -0
  39. package/dist/lib/fs-atomic.d.ts +14 -1
  40. package/dist/lib/fs-atomic.js +35 -3
  41. package/dist/lib/funnel.d.ts +1 -0
  42. package/dist/lib/funnel.js +8 -0
  43. package/dist/lib/hosts/dispatch.js +19 -1
  44. package/dist/lib/hq/floor.js +12 -0
  45. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  46. package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
  47. package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +2 -2
  48. package/dist/lib/menubar/install-menubar.d.ts +53 -2
  49. package/dist/lib/menubar/install-menubar.js +183 -28
  50. package/dist/lib/picker.d.ts +27 -2
  51. package/dist/lib/picker.js +71 -7
  52. package/dist/lib/platform/process.d.ts +2 -0
  53. package/dist/lib/platform/process.js +5 -3
  54. package/dist/lib/profiles.d.ts +48 -0
  55. package/dist/lib/profiles.js +67 -0
  56. package/dist/lib/resources.d.ts +8 -0
  57. package/dist/lib/resources.js +34 -1
  58. package/dist/lib/rotate.d.ts +24 -2
  59. package/dist/lib/rotate.js +63 -6
  60. package/dist/lib/routines-placement.d.ts +2 -1
  61. package/dist/lib/routines-placement.js +8 -4
  62. package/dist/lib/routines.d.ts +57 -1
  63. package/dist/lib/routines.js +74 -1
  64. package/dist/lib/runner.d.ts +2 -0
  65. package/dist/lib/runner.js +21 -8
  66. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  67. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  68. package/dist/lib/secrets/bundles.js +9 -34
  69. package/dist/lib/secrets/filestore.d.ts +152 -34
  70. package/dist/lib/secrets/filestore.js +676 -123
  71. package/dist/lib/session/active.d.ts +109 -3
  72. package/dist/lib/session/active.js +269 -13
  73. package/dist/lib/session/db.d.ts +14 -0
  74. package/dist/lib/session/db.js +35 -0
  75. package/dist/lib/session/favorites.d.ts +39 -0
  76. package/dist/lib/session/favorites.js +101 -0
  77. package/dist/lib/session/host-link.d.ts +68 -0
  78. package/dist/lib/session/host-link.js +64 -0
  79. package/dist/lib/session/presence.d.ts +85 -0
  80. package/dist/lib/session/presence.js +150 -0
  81. package/dist/lib/session/remote-active.d.ts +4 -1
  82. package/dist/lib/session/remote-active.js +8 -2
  83. package/dist/lib/session/remote-list.d.ts +10 -0
  84. package/dist/lib/session/remote-list.js +47 -9
  85. package/dist/lib/session/viewing-in.d.ts +31 -0
  86. package/dist/lib/session/viewing-in.js +47 -0
  87. package/dist/lib/state.d.ts +17 -0
  88. package/dist/lib/state.js +30 -2
  89. package/dist/lib/tmux/binary.d.ts +7 -0
  90. package/dist/lib/tmux/binary.js +11 -1
  91. package/dist/lib/triggers/handlers.d.ts +95 -0
  92. package/dist/lib/triggers/handlers.js +384 -0
  93. package/dist/lib/triggers/webhook.d.ts +10 -2
  94. package/dist/lib/triggers/webhook.js +65 -11
  95. package/dist/lib/types.d.ts +4 -3
  96. package/dist/lib/usage-backoff.d.ts +29 -0
  97. package/dist/lib/usage-backoff.js +165 -0
  98. package/dist/lib/usage.d.ts +112 -5
  99. package/dist/lib/usage.js +464 -46
  100. package/dist/lib/watchdog/runner.d.ts +13 -0
  101. package/dist/lib/watchdog/runner.js +16 -1
  102. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,508 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.20.90
4
+
5
+ - **`agents sessions --active` now shows one row per agent, not one per directory.**
6
+ A live tmux agent pane whose durable identity records were missing (the common case
7
+ once meta/pid-registry entries age out) was dropped, then re-surfaced by the ps-scan
8
+ under the newest transcript in its cwd — so many distinct sessions collapsed onto one
9
+ stranger's id with an inflated `×N` badge, and `agents sessions focus <id>` could not
10
+ find them. The scanner now recovers the session id straight from the `ag-<agent>-<shortid>`
11
+ tmux pane name (resolved to the full UUID via the short-id index in one batched query),
12
+ and refuses to borrow a co-located sibling's transcript when no id is known — so every
13
+ live session surfaces as its own row and is focus-able again. Also adds a `runTmux`
14
+ timeout so a wedged tmux server can't hang the scan. Source: `apps/cli/src/lib/session/active.ts`.
15
+
16
+ - **`agents view` now shows live usage bars for Antigravity.** The `agy` account
17
+ row renders one bar per model quota bucket (`3.1P: ███░░ 42% (1d)` style),
18
+ sourced from the same Google Code Assist `:retrieveUserQuota` endpoint `agy`
19
+ itself talks to. Auth reuses the stored `agy` OAuth credential (macOS Keychain
20
+ item `gemini`/`antigravity`, Linux Secret Service, or the
21
+ `~/.gemini/antigravity-cli/antigravity-oauth-token` file fallback), refreshing
22
+ the access token in memory when expired — safe from a read path because
23
+ Google's refresh tokens are non-rotating, and never written back to the
24
+ keychain. Each per-model bucket also flows into the throttle badge, run
25
+ rotation eligibility, and `agents view --json` (whose usage windows now carry
26
+ a `label` so same-keyed per-model bars are distinguishable). Source:
27
+ `apps/cli/src/lib/usage.ts`, `apps/cli/src/lib/agents.ts`,
28
+ `apps/cli/src/commands/view.ts`.
29
+
30
+ - **A custom harness is now its own agent type in `agents view`.** A harness created
31
+ with `agents harness add` (or `agents profiles add`) used to render as an indented
32
+ `profile` row under whichever host CLI executes it. It now gets its own block beside
33
+ Claude and Codex — a bold name header, then one row carrying the pinned model, the
34
+ account/auth state, and `via <host> <version>` naming the native harness underneath.
35
+ That matches how it is already launched: `agents run <name>` treats a custom harness
36
+ exactly like a native agent id. A harness whose host CLI has no install is flagged
37
+ `(host <id> not installed)` rather than listed as runnable, and the separate
38
+ "Profile-only Agents" section is gone — those harnesses now render in the main list
39
+ like every other one. Source: `apps/cli/src/commands/view.ts`.
40
+ - **`agents view <harness>` describes a custom harness** — host, model, provider, auth,
41
+ fork lineage, YAML path — instead of failing with "unknown agent";
42
+ `agents view <harness> --json` emits its summary. Source:
43
+ `apps/cli/src/commands/harness.ts` (`renderHarnessDetail`).
44
+ - **New `agents harness fork <source> <name>`.** One verb over both starting points:
45
+ fork a native harness (`agents harness fork opencode deepseek --model
46
+ deepseek/deepseek-v4-flash-0731 --auth-provider openrouter`) or copy a custom one you
47
+ already tuned and change only what you name (`agents harness fork deepseek
48
+ deepseek-chat --model deepseek/deepseek-chat-v3`). Forking a custom harness is a full
49
+ copy — env, endpoint, auth binding, `fallback_model`, host version pin — so the two
50
+ diverge and deleting the source never affects the fork; forking a native harness
51
+ requires `--model` because there is no model to inherit. Flags: `--model`,
52
+ `--base-url`, `--auth-provider`, `--version`, `--label`, `--description`,
53
+ `--key-stdin`, `--force`. Source: `apps/cli/src/lib/profiles.ts` (`forkProfile`).
54
+ - **Profile YAML gains optional `label:` and `forkedFrom:`.** `label` sets the name
55
+ `agents view` prints for the harness (defaults to the file name); `forkedFrom` records
56
+ the parent as display-only lineage. Existing profiles keep working untouched. Source:
57
+ `apps/cli/src/lib/profiles.ts`.
58
+ - **Breaking (`--json`):** in `agents view <agent> --json`, the per-agent `profiles` key
59
+ is now `harnesses`, and each entry carries new `label`, `hostVersion`, `description`,
60
+ and `forkedFrom` fields alongside the existing ones. Source:
61
+ `apps/cli/src/commands/view.ts` (`ViewJsonAgent`).
62
+
63
+ - **Menu bar ACTIVE: project accordion + session detail submenu.** Projects are
64
+ collapsed by default as a status strip (`▶ agents-cli ●8 ◐1 zion`); click
65
+ `▶`/`▼` to fold agents open under the project (idle-row caps removed — collapse
66
+ is the wall protection). Focusing an agent opens a side submenu with linkable
67
+ detail (work title URL, cwd, Linear ticket, GitHub PR, duration, copy session
68
+ id) from the warm `sessions --active` cache. Accordion reopen rebuilds from
69
+ cache only (no teams walk / no CLI schedule). Local/remote uses the same host
70
+ normalize as CLI `machineId()` so local rows are not mislabeled remote. Source:
71
+ `apps/cli/menubar/Sources/MenubarHelper/StatusItemController.swift`,
72
+ `LocalState.swift`, `Models.swift`.
73
+
74
+ - **An offloaded editor tab no longer displays another session's id.** A Factory
75
+ tab launched with `agents run --host <device>` has no local agent process, but
76
+ the extension still resolved its "live" session id by reading the SessionStart
77
+ hook's `~/.agents/.cache/state/sessions/<pid>.json` for the local pid tree —
78
+ the pid of the ssh client. Those files are keyed by pid alone and are only
79
+ pruned when the pid is dead, so once the OS recycled a pid the tab adopted
80
+ whatever session had last held it: one remote tab showed the id and version of
81
+ an unrelated synthetic run from 20 days earlier while `/status` inside it
82
+ reported the truth. An offloaded tab now takes its identity from the device
83
+ instead of local disk, and a local tab rejects any state record whose
84
+ SessionStart timestamp predates the tab itself.
85
+ - **`AGENT_TERMINAL_ID` now rides the SSH hop.** `agents run --host` forwarded
86
+ actor provenance but not the launching tab's terminal id, so the remote pid
87
+ registry recorded no terminal — leaving `agents sessions --active --host
88
+ <device>` unable to answer "which session is this tab running?" once the agent
89
+ moved on (a `/clear`, or an exit and rerun in the same tab).
90
+ - **`agents sessions --active --json` now carries `terminalId`.** The pid registry
91
+ has always recorded it; the emitted row dropped it, so no consumer could join a
92
+ live session back to the editor tab that launched it.
93
+
94
+ - **Balanced routing no longer launches into an account it only *thinks* has
95
+ headroom.** Account usage is cached per machine under stale-while-revalidate:
96
+ a snapshot up to 24h old was served instantly, and the background refresh that
97
+ should have corrected it lands after the pick is already made. On a box whose
98
+ refresh is failing that state is permanent — measured on `yosemite-s1`, every
99
+ Claude snapshot sat 26 hours to 2.7 days old, so balanced read
100
+ `muqsit@getrush.ai` as 48% used and launched into it while the account was at
101
+ its weekly cap; the session answered "You've hit your weekly limit" on its
102
+ first turn. Routing now caps how stale a snapshot may be when it is about to
103
+ decide (5 minutes), blocking on one bounded, parallel live read past that — and
104
+ no read at all inside the existing 2-minute fresh window, which back-to-back
105
+ launches hit. Display paths (`agents view`) keep the full 24h window and stay
106
+ off the network.
107
+ - **A pick made on unconfirmed data says so.** When no account on the machine
108
+ could be refreshed, routing still launches — a broken refresh must not make a
109
+ box unusable — but the banner now reads `… (2 of 5 healthy, usage unverified —
110
+ no account could be refreshed)` instead of presenting a guess as a fact. An
111
+ account with a verified snapshot always wins over one with a stale snapshot,
112
+ even when the stale number looks emptier. This applies to `--strategy
113
+ available` as well as `balanced` — both route on the same cache, and
114
+ `available`'s headroom sort was inverted by a stale number in exactly the same
115
+ way. An explicit version preference is an instruction, not a ranking signal, so
116
+ it still wins.
117
+ - **The mid-run failover chain is unchanged.** Declining to *pick* an account on
118
+ unconfirmed data and declining to *fail over to* it after the primary already
119
+ hit a 429 are different risks — by then the alternative is not launching at
120
+ all. Every eligible account stays in the failover chain; only the initial pick
121
+ prefers verified ones.
122
+
123
+ - **`agents routines list` no longer reports another device's routine as failed.** Run
124
+ records are written into the runs dir of whichever machine fired the routine and carry
125
+ no device attribution, but the listing resolved Last Status from any local record and
126
+ rendered it even on rows for routines pinned elsewhere. A routine re-pinned to another
127
+ device therefore kept reporting the old machine's leftover records forever — on zion,
128
+ `security-sweep`, `review-open-prs` and `hetzner-lease-gc` all read `failed` from late
129
+ July while `yosemite-s0`/`s1`, the devices that actually fire them, had completed them
130
+ that morning. The macOS menu bar reads this JSON, so it painted a column of red `exit 1`
131
+ rows for routines that were green. Last Status is now scoped to the device that owns the
132
+ run: a routine this device does not fire shows `-`, and `--json` returns `null` for
133
+ `lastStatus`, `exitCode`, `failureReason`, `lastRunStartedAt` and `lastRunCompletedAt`
134
+ (`runsHere: false` already says why). A routine pinned to several devices renders one row
135
+ per device but carries a status only on its **This machine** row. Read a peer's status
136
+ with `agents routines list --device <name>`; the local history is untouched and still
137
+ readable via `agents routines runs <name>`. Source: `apps/cli/src/commands/routines.ts`
138
+ (`localLatestRun`, `groupRoutineJobsByDevice`), `apps/cli/docs/03-routines.md`.
139
+
140
+ - **`agents watchdog` now tracks per-session presence (RUSH-2007 Layer C).** Each
141
+ tick reconciles a per-session presence record — `{location, device, transport,
142
+ lastSeen, status}` at `~/.agents/.cache/state/watchdog/presence.json` — from the
143
+ tick's active scan, deriving `connected` / `disconnected` by diffing consecutive
144
+ ticks. A session that was tracked but is now absent (its SSH link dropped or the
145
+ peer went unreachable) flips to `disconnected`, and the flip is surfaced in
146
+ `agents watchdog --json` under `presence.transitions` — an interactive drop as a
147
+ `reconnect-nudge` candidate, a headless remote as `keep-alive`. Folded into the
148
+ existing tick (no revived daemon, no extra SSH fan-out); additive and does not
149
+ change the tick's nudge decisions. Source:
150
+ `apps/cli/src/lib/session/presence.ts`, `apps/cli/src/lib/watchdog/runner.ts`.
151
+
152
+ - **`agents setup secrets --policy hold` no longer fails, and `agents secrets
153
+ status` stops naming the retired `daily` policy.** The 1.20.79 `daily` → `hold`
154
+ rename swept the help, docs, and the `secrets list` POLICY column, but two
155
+ surfaces were never migrated. The worse one was functional: the onboarding
156
+ wizard carried its own copy of the policy vocabulary, so
157
+ `agents setup secrets --policy hold` — the canonical name every other secrets
158
+ command prints — exited with `Invalid --policy 'hold'. Use daily, always, or
159
+ never.`, and its interactive prompt still offered `daily` as the default
160
+ choice. It now shares `parsePolicyOpt` with `agents secrets policy`, so the two
161
+ commands can't disagree about what a policy is called; `daily`/`session` stay
162
+ accepted as aliases and the wizard's default is unchanged (the hold tier). The
163
+ second was cosmetic: `agents secrets status` printed "a daily bundle prompts
164
+ once…" and "the next read of each daily bundle…" — the one command a user runs
165
+ to answer *why did it prompt again*, naming a policy its sibling commands no
166
+ longer emit. Both lines now say `hold` and are pure values pinned by tests, so
167
+ the vocabulary can't drift again. Source:
168
+ `apps/cli/src/commands/setup-secrets.ts`, `apps/cli/src/commands/secrets.ts`.
169
+
170
+ - **Favorite sessions from the browser.** `*` stars the highlighted session in
171
+ `agents sessions` and `f` filters the list to the starred ones; outside a TTY,
172
+ `agents sessions favorite <id>` (`--remove` / `--list` / `--json`) and
173
+ `agents sessions --favorites` do the same. Stars live in
174
+ `~/.agents/.history/favorites.json` keyed by session id, so they survive a reindex
175
+ of the session cache. They are per-machine — session sync carries transcripts, not
176
+ this file. Source:
177
+ `apps/cli/src/lib/session/favorites.ts`, `apps/cli/src/commands/sessions-favorite.ts`.
178
+ - **Detect sessions that lost their host — two new statuses, `crashed` and `orphaned`.**
179
+ A session whose editor window or connection went down hard used to just VANISH from
180
+ `agents sessions --active` (its dead-pid registry entry was filtered out), and one
181
+ still running in tmux with nobody attached reported a plain `idle`. Both now say so:
182
+ `✗ crashed` when the host window stopped republishing and the agent died with it,
183
+ `◍ orphan` when the agent is alive with zero clients attached. Derived from tmux's
184
+ `#{session_attached}` and the IDE window's registry heartbeat — never from a
185
+ deliberate `agents sessions detach`, and never over a session that is still working.
186
+ Source: `apps/cli/src/lib/session/host-link.ts`, `apps/cli/src/lib/session/active.ts`.
187
+ - **`agents sessions --active --favorites` now actually filters.** The flag was wired
188
+ into the interactive browser only, so every path that skips it — `--json`,
189
+ `--waiting`, a pipe, a multi-host scope, an SSH-fanout peer — silently returned the
190
+ whole fleet. Source: `apps/cli/src/commands/sessions.ts`.
191
+ - **`agents sessions --active --waiting` no longer counts a dead session.** `activity`
192
+ is not rewritten when a session dies, so one that crashed mid-question reported "needs
193
+ your input" forever — what it needs is a relaunch. Source:
194
+ `apps/cli/src/commands/sessions.ts`.
195
+
196
+ - **Resolve historical sessions safely across the fleet (#1757).** `agents sessions --resolve <full-id|prefix|keywords> --json` uses a versioned safe peer protocol, returns only resolver metadata, reports every full-ID candidate on ambiguity, treats synced copies as one match, and exits 2 without deciding when a peer fails, returns malformed output, or runs an older CLI. Source: `apps/cli/src/commands/sessions.ts`.
197
+
198
+ - **A rate-limited usage endpoint is now backed off instead of hammered.** The
199
+ daemon warms auth-health every 3 minutes and probes *every installed version
200
+ home* in one parallel batch, so a machine with five Claude accounts sent five
201
+ concurrent requests to `api.anthropic.com/api/oauth/usage` every three minutes
202
+ — roughly 100/hour — before the usage refresh added its own. Nothing read
203
+ `Retry-After`. Measured on `yosemite-s1`: the endpoint answered
204
+ `429 rate_limit_error` with `retry-after: 2678` (about 45 minutes) for every
205
+ account while the credentials themselves read healthy, and the next tick fired
206
+ three minutes later, deep inside the penalty window, re-arming it. The box
207
+ never recovered, every usage read failed, and its cache froze — the
208
+ permanently-stale state balanced routing was already having to defend against.
209
+ - **A 429 now records its deadline and every read honours it.** Usage fetches and
210
+ health probes for that provider short-circuit until the window passes — no
211
+ request, no renewed penalty — and report
212
+ `Claude rate-limited this machine — not retrying for 45 minutes.` The state is
213
+ on disk, because the callers are separate processes: the long-lived daemon and
214
+ every one-shot `agents view` / `agents run` — one empty file per penalty under
215
+ `~/.agents/.cache/usage-backoff/`, named `<agent>.<deadline>`, so two
216
+ processes recording the same provider at once cannot displace each other and a
217
+ read takes the furthest deadline. A server delay is capped at an hour, and a
218
+ missing or unparseable `Retry-After` still backs off.
219
+
220
+ - **A usage read that fails now says so, instead of returning a silent null.**
221
+ Four branches in every networked usage fetch — Claude, Kimi, Droid and
222
+ Cursor — returned `{ snapshot: null, error: null }`: no readable credential, a
223
+ locally-expired one, a rejected request, and a request that threw (timeout,
224
+ DNS/TLS, an unparseable payload). The caller could not tell any of them apart
225
+ from a healthy read, so it fell
226
+ back to whatever the stale-while-revalidate cache held and drew those bars as
227
+ fact. Measured on `yosemite-s1`: every Claude account's stored access token had
228
+ expired (one of them eleven days earlier), so no read could succeed, and
229
+ `agents view claude --refresh` printed a full, healthy-looking table twice
230
+ while writing nothing to the cache. A usage read never refreshes a token
231
+ (RUSH-1822), so an expired credential does not heal on its own — the account
232
+ stays unreadable until that agent actually runs. A rate-limited endpoint (429)
233
+ now reads differently from a rejected credential (401), because re-authing
234
+ fixes one and not the other.
235
+ - **`agents view` marks bars the live read could not confirm.** A row whose
236
+ snapshot came from the cache after a failed live read renders the reading plus
237
+ `unverified`, rather than looking identical to a confirmed one. The number
238
+ still shows — it is the last thing we saw — but it no longer reads as current.
239
+ - **`agents view --refresh` reports what it could not refresh.** It now lists
240
+ each account it failed to reach and why, instead of rendering a table that
241
+ looks fully refreshed regardless.
242
+
243
+ ## 1.20.89
244
+
245
+ - **Webhook handler layer for one-off agent/workflow/command/routine triggers.**
246
+ Routines still fire from signed webhooks, but a new `~/.agents/webhooks/*.yml`
247
+ layer can also run one-off actions: `run.agent`, `run.workflow`, `run.command`,
248
+ or delegate to an existing `routine`. Handlers support the same source/event/
249
+ action/label/repo/branch filters as routine triggers, plus Linear
250
+ `stateTo`/`stateFrom` state-change filters. Prompts and commands can use
251
+ `{{issue.identifier}}`, `{{updatedFrom.state.name}}`, etc. The receiver emits
252
+ `webhook.received`, `webhook.authorized`, `webhook.rejected`, `webhook.matched`,
253
+ `webhook.fired`, `webhook.handler.start`, and `webhook.handler.end` events.
254
+ Source: `apps/cli/src/lib/triggers/handlers.ts`,
255
+ `apps/cli/src/lib/triggers/webhook.ts`, `apps/cli/src/lib/routines.ts`,
256
+ `apps/cli/src/commands/routines.ts`, `apps/cli/docs/03-routines.md`.
257
+
258
+ - **`agents routines add` gains `--state-to` and `--state-from` filters for Linear
259
+ triggers.** A Linear routine or handler can now fire only on a specific state
260
+ transition (for example `--state-to Plan`), instead of on every issue update.
261
+
262
+ - **Values substituted into `run.command` are shell-quoted.** A webhook context is
263
+ built from an external payload, and fields like `issue.title` or a GitHub
264
+ `pull_request` title are free text any outside contributor can set — pasted raw
265
+ into a shell command they would be a command-injection sink. Substituted values
266
+ are now single-quoted (POSIX `sh`), so a payload stays one inert argument while
267
+ the operator's own template keeps its pipes, redirects, and `&&`. On Windows,
268
+ where `exec` runs through `cmd.exe` and these quoting rules do not hold, a
269
+ `run.command` containing `{{…}}` is refused with a clear error rather than run.
270
+ `run.prompt` is unaffected — it never reaches a shell.
271
+ Source: `apps/cli/src/lib/routines.ts` (`substituteWebhookCommand`,
272
+ `assertShellSubstitutionSupported`), `apps/cli/src/lib/triggers/handlers.ts`.
273
+
274
+ - **The `Cmd-Shift-O` quick-dispatch bar now lists the repo's open Linear tickets,
275
+ and dispatches one on a click (RUSH-2098).** The panel only captured NEW work;
276
+ it now also shows what already exists. Switching the repo dropdown switches the
277
+ Linear project (the repo name is matched against `linear projects` reduced to
278
+ lowercase alphanumerics, so `agents-cli` finds "Agents CLI" with nothing to
279
+ configure; a worktree resolves to its parent repo, and a repo that matches no
280
+ project says so and lets you pick one, remembered per repo). Rows are ranked
281
+ urgent-first — Linear priority, then overdue, then in progress, then newest —
282
+ and typing filters them, so an existing ticket surfaces before Return files a
283
+ duplicate. Clicking a row (or `⌘1`–`⌘5`) dispatches that ticket to the selected
284
+ agents in the picked repo: **Run** claims it and implements it, **Plan** posts a
285
+ plan as a ticket comment. `⌘`-click opens it in Linear instead. The list renders
286
+ from a 90-second warm cache so the panel still appears instantly. Source:
287
+ `apps/cli/menubar/Sources/MenubarHelper/LinearTickets.swift`,
288
+ `apps/cli/menubar/Sources/MenubarHelper/PromptPanel.swift`,
289
+ `apps/cli/menubar/Sources/MenubarHelper/AgentsCLI.swift`.
290
+
291
+ - **Fixed: a menu-bar dispatch whose child printed more than ~64 KiB hung forever
292
+ and never notified.** The helper read a monitored child's stdout only from the
293
+ process-termination handler, so a child that filled the pipe buffer blocked on
294
+ write, never exited, and the completion callback never fired — two `linear`
295
+ processes were left wedged by a single ticket fetch. Both monitored paths (the
296
+ ticket agent and `linear create`) now drain stdout, and feed stdin, on a
297
+ background queue while the child runs. Source:
298
+ `apps/cli/menubar/Sources/MenubarHelper/AgentsCLI.swift`.
299
+
300
+ - **The daemon warns when it was launched from an ephemeral root.** A daemon
301
+ started from a temp dir (`/tmp`, `/var/folders`, `/dev/shm`) or a git worktree
302
+ resolves its own job modules by dynamic `import()` rooted at the launch entry
303
+ (`getAgentsBinPath` → `process.argv[1]`). When that directory is later removed
304
+ — a `/tmp` cleanup, a review/verify checkout teardown, `git worktree remove` —
305
+ the long-lived daemon keeps ENOENT-ing on every routine's imports
306
+ (`auto-dispatch.ts`, `routines-placement.ts`, `devices/fleet.ts`), silently
307
+ wedging until restart. `anchorDaemonCwd` already rescues the cwd, but nothing
308
+ can re-root a deleted module tree. `runDaemon` now calls
309
+ `warnEphemeralDaemonRoot` at startup, so the risk is logged the moment the
310
+ daemon comes up — including a direct `agents __daemon-run` that never passes
311
+ through the launch-time `validateDaemonBinary` check. That launch-time check is
312
+ also broadened from git-worktree-only to any ephemeral root via the shared
313
+ `describeEphemeralDaemonRoot` predicate. The fix for a wedged daemon is
314
+ unchanged: run it from the globally installed binary
315
+ (`npm i -g @phnx-labs/agents-cli`) so its entry roots at a stable version home.
316
+ Source: `apps/cli/src/lib/daemon.ts`
317
+ (`describeEphemeralDaemonRoot`, `warnEphemeralDaemonRoot`, `validateDaemonBinary`).
318
+
319
+ - **A `README.md` / `AGENTS.md` sitting in a resource directory is no longer
320
+ installed as a resource.** `listResources` skipped only dotfiles, so every `.md`
321
+ beside the actual resources was materialized as one: `commands/README.md` — which
322
+ the system repo has shipped for months — installed a bogus `/README` slash command
323
+ into every agent home, and adding per-directory `AGENTS.md` docs would have added
324
+ `/AGENTS`, `/CLAUDE`, and `/GEMINI` alongside it. `README`, `AGENTS`, `CLAUDE`, and
325
+ `GEMINI` are now filtered from both `listResources` and `resolveResource` for every
326
+ kind **except `rules`**, where `AGENTS.md` *is* the resource (the composed ruleset
327
+ that syncs as each agent's memory file). The check tests `!entry.isDirectory()`
328
+ rather than `isFile()`, because a `Dirent` for a symlink reports
329
+ `isFile() === false` and `CLAUDE.md`/`GEMINI.md` are symlinks to `AGENTS.md` by
330
+ convention — a resource *directory* named `agents/` is still a real resource.
331
+ Verified against the real installed layers: 30 commands with `README` leaking
332
+ before, 29 with none after.
333
+ - **`agents commands list` and the command picker no longer offer a name that
334
+ cannot be opened.** `listCentralCommands` and `discoverCommands`
335
+ (`src/lib/commands.ts`) run their own `readdirSync` scans rather than going
336
+ through `listResources`, so they kept offering `README` while
337
+ `agents commands view README` answered "not found" — a listed-but-unopenable
338
+ name. Both now share the one exported `isDirectoryDoc` predicate, so every
339
+ enumerator agrees. Verified: 27 names with `README` before, 26 with none after.
340
+ - **`agents commands add/remove/view` no longer suggest `README` as the example
341
+ command name.** With `README` reserved as a directory doc, the six hardcoded
342
+ examples in the help text and non-interactive hints named a command that can never
343
+ exist. They now use `plan`, which actually ships.
344
+
345
+ - **File-backed secrets bundles no longer require `AGENTS_SECRETS_PASSPHRASE` on
346
+ macOS.** The encrypted file store now silently auto-provisions a stable
347
+ machine-local key (a 0600 file under `~/.agents/.secrets-key/`, kept outside the
348
+ encrypted store) on first use on **every** platform, macOS included — no prompt,
349
+ no Touch ID, nothing to set or remember. Previously a file-backed bundle on a Mac
350
+ hard-failed unless `AGENTS_SECRETS_PASSPHRASE` was exported, which blocked
351
+ headless reads (e.g. the `auth` bundle the usage/auth reader consults) and
352
+ frequently hung. Setting `AGENTS_SECRETS_PASSPHRASE` still works and takes
353
+ precedence — use it to hold the key off disk or to share one bundle's ciphertext
354
+ across boxes under a common key. Source: `apps/cli/src/lib/secrets/filestore.ts`,
355
+ `apps/cli/src/lib/secrets/bundles.ts`.
356
+
357
+ - **Menu-bar & daemon notifications now use the current agents-cli mark, not the
358
+ legacy logo.** A desktop notification from the menu-bar helper or the routines
359
+ daemon showed the old `assets/logo.png` gradient "A" — outdated, and blank in the
360
+ notification's left-hand app-icon slot. `MenubarHelper.app`'s `AppIcon.icns` is
361
+ now generated from the current brand mark (`assets/app-icon.svg` → `app-icon.png`:
362
+ the lime-tile lowercase `a` shared with the agi-cli web favicon and the menu-bar
363
+ glyph), which drives both the notification's right-hand `contentImage` and its
364
+ left-hand app icon. The installer also registers the bundle with LaunchServices
365
+ (`lsregister -f`) at its `~/Library/Application Support` path so the OS can resolve
366
+ that app icon. Source: `apps/cli/menubar/scripts/build.sh`,
367
+ `apps/cli/src/lib/menubar/install-menubar.ts`, `assets/app-icon.svg`.
368
+
369
+ - **The menu bar is a single instance, always.** Two copies of the helper could
370
+ run at once — launchd's `KeepAlive` service plus a LaunchServices/`open` launch
371
+ of the same `.app` — putting two agents marks in the menu bar, and the second
372
+ copy could hold `Cmd-Shift-V`/`Cmd-Shift-O` (`RegisterEventHotKey` is
373
+ first-come). The helper now takes an `flock` on
374
+ `~/.agents/.cache/state/menubar.lock` at launch and holds it for its lifetime;
375
+ a helper that cannot take the lock pops the **running** helper's menu open and
376
+ exits 0, since re-launching a menu-bar app means "show me the one I already
377
+ have". An `flock` rather than a pid file: the kernel releases it when the
378
+ holder dies, so a `SIGKILL`ed helper cannot leave a stale "already running"
379
+ that blocks every later launch. Source:
380
+ `apps/cli/menubar/Sources/MenubarHelper/SingleInstance.swift`,
381
+ `apps/cli/menubar/Sources/MenubarHelper/StatusItemController.swift`.
382
+
383
+ - **`agents menubar setup` configures the menu bar end-to-end.** One idempotent
384
+ command for a machine that is wrong — never configured, helper down, or showing
385
+ a duplicate icon. It ends every running helper, installs/refreshes the bundle,
386
+ checks its code signature, writes the launchd login item (`RunAtLoad` +
387
+ `KeepAlive`), clears a previous `agents menubar disable`, and verifies exactly
388
+ one helper came back up — reporting each as its own step and exiting nonzero if
389
+ it cannot reach that state. `--check` reports without changing; `--json` emits
390
+ the step list. Source: `apps/cli/src/commands/menubar.ts`,
391
+ `apps/cli/src/lib/menubar/install-menubar.ts`.
392
+
393
+ - **`agents menubar status` now shows a duplicate.** Live helper processes were
394
+ collapsed to a boolean `running`, so two copies of the *installed* bundle — the
395
+ duplicate a user actually sees — reported as healthy. `--json` now carries an
396
+ `instances` array (copies of the installed bundle) beside the existing
397
+ `foreignInstances`, and the text readout names every extra pid and points at
398
+ `agents menubar setup`. Source:
399
+ `apps/cli/src/lib/menubar/install-menubar.ts` (`classifyMenubarProcesses`).
400
+
401
+ - **Quick-dispatch ticket list: one-row filter + sort, and a scrollable list.**
402
+ The ticket controls sit on a single row of popups next to the Linear project
403
+ (project · filter · sort) — not a chip matrix or two-column block. Quick filter
404
+ options: All open, Todo, Doing, Backlog, P1 only, P2 only, Overdue. Quick sort
405
+ options: Urgent first, Newest, Oldest, Due date, Priority (flat list, no
406
+ status grouping). Filter and sort picks are remembered across summons. Ticket
407
+ rows scroll inside a fixed viewport so more than five matches stay reachable
408
+ without growing the panel. Source:
409
+ `apps/cli/menubar/Sources/MenubarHelper/LinearTickets.swift`,
410
+ `apps/cli/menubar/Sources/MenubarHelper/PromptPanel.swift`.
411
+
412
+ - **`release.sh` now takes a release lease, and refuses to bump past an
413
+ unpublished tag.** Releases run from whichever fleet box an agent happens to be
414
+ on, so two agents could enter the pipeline at once; the collision only surfaced
415
+ at the publish gate (`merged tree != built tree -- refusing to publish`), after
416
+ one run had already merged and tagged, leaving the version merged but unshipped.
417
+ A new `scripts/release-lease.sh` holds mutual exclusion on `origin` as an orphan
418
+ commit at `refs/release-lock/held` — a second claimant's push can never be a
419
+ fast-forward, so git's rejection *is* the failed lock acquisition. The lease is
420
+ claimed before the first mutation and dropped by the existing cleanup trap on
421
+ every exit path. Because a healthy release routinely outlives any sane
422
+ expiry — the CI matrix alone has run 57 minutes and release 1.20.77 took 186
423
+ minutes — the lease is **renewed** by a background renewer for the whole run,
424
+ and the squash-merge, the tag, and the publish each **verify** ownership first,
425
+ failing closed if it can no longer be proven. A lease that stops being renewed
426
+ is reclaimable after 30 minutes, and reclaiming names the dead holder instead
427
+ of silently overwriting it. Separately,
428
+ `release.sh` now refuses to cut a new version while an older `v*` tag exists
429
+ that npm never received, and points at the re-run that finishes it — bumping
430
+ past an unpublished tag is what turned a one-version gap into npm 1.20.78 vs
431
+ main 1.20.81. Source: `apps/cli/scripts/release-lease.sh`,
432
+ `apps/cli/scripts/release.sh`.
433
+
434
+ - **`agents funnel down` disables a public Funnel port from the same wrapper used
435
+ to enable ingress.** Webhook ingress now has a complete local receiver runbook:
436
+ keep GitHub/Linear signing keys in `agents secrets`, bind the receiver to
437
+ `127.0.0.1`, expose it with `agents funnel up`, rotate one source secret at a
438
+ time, and turn the public port off with `agents funnel down` before stopping or
439
+ moving the receiver. Source: `apps/cli/src/commands/funnel.ts`,
440
+ `apps/cli/src/lib/funnel.ts`, `apps/cli/docs/03-routines.md`.
441
+
442
+ - **New `agents secrets rotate-passphrase` re-keys the encrypted file store under
443
+ a new master passphrase, atomically (RUSH-1975).** Until now there was no
444
+ supported way to rotate the file-store passphrase — `rekey` only renames macOS
445
+ keychain service names and `rotate <bundle> <key>` replaces a single secret
446
+ value, so a leaked passphrase (RUSH-1968) could only be remediated by a
447
+ hand-rolled non-atomic script or an export-to-plaintext round-trip (the exact
448
+ exposure being fixed). The new command decrypts every `<item>.enc` under the
449
+ current key, re-encrypts under a freshly generated one, and swaps both the
450
+ ciphertext and the 0600 key file by directory rename after verifying every item
451
+ round-trips. A crash at any point self-heals on the next *rotate* run to a single
452
+ readable store — content-aware recovery probes which key actually decrypts the
453
+ live store (not merely which files are present) and classifies the WHOLE store:
454
+ it completes the rotation forward or rolls back only when one key opens every
455
+ item, and if a later `secrets set` contaminated a crashed rotation into a MIXED
456
+ store (items under two keys at once, or a store dir recreated by an interstitial
457
+ write after the crash left it absent, so its backup holds items the live dir does
458
+ not) it refuses with an actionable error and preserves every recovery artifact
459
+ rather than sweeping the only copy of a key or the backed-up ciphertext — so a
460
+ crash anywhere in the swap can never orphan the store, even when a write landed in
461
+ between. The rotation and every store write run under
462
+ one cross-process lock, so a `secrets set` or a second rotation can never
463
+ interleave with a swap in the first place. No plaintext secret or passphrase is
464
+ ever written to disk, argv, or a log. Items
465
+ that don't decrypt under the current key (orphan caches, stale test artifacts)
466
+ are carried through verbatim, never re-keyed. Dry-run by default (`--commit` to
467
+ apply). A dry run never re-keys, but it *does* heal an interrupted rotation —
468
+ that is how a crashed store becomes readable again without re-keying it — and it
469
+ says so instead of claiming nothing was written. Refuses while the secrets-agent
470
+ holds live unlocks or while
471
+ `AGENTS_SECRETS_PASSPHRASE` is exported in the environment, unless `--force`.
472
+ Headless-safe and Linux-first. Source: `apps/cli/src/lib/secrets/filestore.ts`,
473
+ `apps/cli/src/commands/secrets-rotate-passphrase.ts`.
474
+
475
+ - **`agents sessions --active --json` now reports who is watching each session.**
476
+ The `viewingIn` field carries the same string the table prints — `codium tab 3`,
477
+ `ghostty tab 2`, or `detached` for a live tmux pane with **no client attached**
478
+ (its terminal was closed or crashed). It is `null` both for a session that isn't
479
+ tmux-hosted and for one whose pane the locator could not resolve — `detached` is
480
+ claimed only when the pane was actually located, so absence of evidence is never
481
+ reported as evidence of absence. Previously the JSON path returned
482
+ before the locator pass ran, so the field never appeared and a machine consumer
483
+ could not tell a session someone is looking at from an orphaned one — which is
484
+ exactly what the Factory extension's `Agents: Resume` picker ranks by. The JSON
485
+ path resolves tmux clients only — no osascript — so scriptable output keeps the
486
+ cheapness the old ordering was protecting; a Ghostty-attached client resolves as
487
+ `ghostty` without its tab number. Peers running an older CLI that still emits the
488
+ `{app, tab}` object are normalized at the fan-out boundary, so a mixed-version
489
+ fleet sweep stays correct. Source: `apps/cli/src/lib/session/viewing-in.ts`
490
+ (`viewingInLabel`, `parseViewingIn`), `apps/cli/src/commands/sessions.ts`
491
+ (`serializeActiveSessionsForJson`, `enrichTmuxLocators`),
492
+ `apps/cli/src/lib/session/remote-active.ts`.
493
+
494
+ - **Webhook handlers gain `run.env` and `host` placement.** A handler can now
495
+ inject environment variables into the process it spawns (`run.env`), and choose
496
+ where that run executes (`host`). `host` takes a device name (`yosemite-s0`), or
497
+ `fleet` to pick any eligible online worker, or `fleet/<platform>` /
498
+ `<platform>/fleet` (also a bare `linux` / `macos` / `windows`) to restrict that
499
+ pick to one platform. A fleet expression that matches no eligible device fails
500
+ loudly rather than silently falling back to the local machine, so `fleet/linux`
501
+ can never land on a macOS box. Omitting `host` runs locally, as before.
502
+ Source: `apps/cli/src/lib/triggers/handlers.ts` (`resolveHandlerHost`),
503
+ `apps/cli/src/lib/routines-placement.ts` (`pickFleetDevice` platform filter),
504
+ `apps/cli/src/lib/routines.ts` (`JobConfig.env`), `apps/cli/src/lib/runner.ts`.
505
+
3
506
  ## 1.20.88
4
507
 
5
508
  - **`agents doctor` redesigned into a prioritized, fleet-aware, per-version
package/README.md CHANGED
@@ -265,6 +265,8 @@ On a terminal, `agents sessions --active` (and a bare `agents sessions`) open th
265
265
  |---|---|---|
266
266
  | `s` | search text | `--query` / positional |
267
267
  | `r` | running only | `--active` |
268
+ | `f` | favorites only | `--favorites` |
269
+ | `*` | star / unstar the highlighted session | `agents sessions favorite <id>` |
268
270
  | `c` | team sessions | `--teams` |
269
271
  | `a` | agent (cycles) | `-a` |
270
272
  | `d` | device (cycles) | `--device` |
@@ -274,6 +276,10 @@ On a terminal, `agents sessions --active` (and a bare `agents sessions`) open th
274
276
  | `⏎` | resume / attach | `resume` / `focus` |
275
277
  | `y` | copy the equivalent command | `--print-cmd` |
276
278
 
279
+ **Star the sessions you keep coming back to.** `*` marks the highlighted row (a `★` shows in the listing), `f` narrows to the starred ones, and `agents sessions favorite <id>` / `--favorites` do the same outside a TTY. Stars live in `~/.agents/.history/favorites.json` keyed by session id, so they survive a reindex of the session cache. They're per-machine — session sync carries transcripts, not this file.
280
+
281
+ **A session that lost its host says so.** When an editor window or an SSH connection goes down hard, the agent it owned used to simply disappear from `--active`; when an agent outlived its window in tmux, it reported a plain `idle`. Both now carry their own status: `✗ crashed` (the host went down and took the agent with it) and `◍ orphan` (still alive, but no client is attached — nothing is showing it). Read from tmux's attached-client count and the editor window's registry heartbeat, so a deliberate `agents sessions detach` is never mistaken for one, and a session that is still *working* headlessly is left alone.
282
+
277
283
  Filters **stack** (they AND together), the active set shows in the header, and the highlighted row **previews below by default** (`tab` hides it) — prompt, activity, last response, plus a links line where the worked-on ticket and the PR the session opened are **clickable** (OSC 8 hyperlinks: the ticket jumps to Linear, the `PR#` to GitHub, in terminals that support them). The Linear workspace is resolved from `LINEAR_WORKSPACE` or the linear-cli config, so tickets stay plain text when it's unknown. Because every hotkey has a flag, the view you build by hand is a real command: press `y` (or run `--print-cmd`) to get the exact `ag sessions …` line — explore interactively, hand the line to an agent. Piped output, `--json`, or `--no-interactive` keep the plain listing for scripts. Peek without opening the pager with `agents sessions <id> --preview`.
278
284
 
279
285
  | before — preview hidden | after — preview open + clickable links |
@@ -1002,16 +1008,24 @@ Other useful commands: `agents doctor` checks CLI availability and resource sync
1002
1008
  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.
1003
1009
 
1004
1010
  ```bash
1005
- agents menubar enable # install + launch at login
1011
+ agents menubar setup # configure end-to-end: one instance, started at login
1006
1012
  agents menubar status # is it installed and running?
1007
1013
  ```
1008
1014
 
1015
+ There is only ever **one** agents mark: the helper takes a lock at launch, so a
1016
+ second copy surfaces the running one's menu and exits instead of adding a
1017
+ duplicate icon. `agents menubar setup` is the recovery command when a machine is
1018
+ already wrong -- it ends any duplicate, installs the bundle, wires the login
1019
+ item, and verifies exactly one helper came back up.
1020
+
1009
1021
  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.
1010
1022
 
1011
1023
  ### Quick-issue bar (⌘⇧O)
1012
1024
 
1013
1025
  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.
1014
1026
 
1027
+ The bar also lists the **open Linear tickets of the repo you picked**, urgent first. Switching the repo switches the Linear project; typing filters the list, so an existing ticket shows up before you file a duplicate; and clicking a row (or `⌘1`-`⌘5`) dispatches that ticket to the selected agents -- **Run** implements it, **Plan** posts a plan as a ticket comment.
1028
+
1015
1029
  <p align="center">
1016
1030
  <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%" />
1017
1031
  </p>
package/dist/bin/agents CHANGED
Binary file
@@ -35,7 +35,7 @@ Examples:
35
35
  agents commands add
36
36
 
37
37
  # Install specific commands by name
38
- agents commands add --names README,debug --agents codex@0.116.0
38
+ agents commands add --names plan,debug --agents codex@0.116.0
39
39
 
40
40
  When to use:
41
41
  - Project setup: 'agents commands add gh:team/commands' to sync everyone's workflow
@@ -90,7 +90,7 @@ Examples:
90
90
  agents commands add
91
91
 
92
92
  # Install specific commands to a single version
93
- agents commands add --names README,debug --agents codex@0.116.0
93
+ agents commands add --names plan,debug --agents codex@0.116.0
94
94
 
95
95
  # Pull commands from GitHub and sync to all installed agents
96
96
  agents commands add gh:user/repo --agents claude,codex,cursor
@@ -125,7 +125,7 @@ Examples:
125
125
  else {
126
126
  if (!isInteractiveTerminal()) {
127
127
  requireInteractiveSelection('Selecting commands from ~/.agents/commands/', [
128
- 'agents commands add --names README,debug --agents codex',
128
+ 'agents commands add --names plan,debug --agents codex',
129
129
  'agents commands add gh:user/repo --agents codex',
130
130
  ]);
131
131
  }
@@ -272,7 +272,7 @@ Examples:
272
272
  .addHelpText('after', `
273
273
  Examples:
274
274
  # Remove a command by name
275
- agents commands remove README
275
+ agents commands remove plan
276
276
 
277
277
  # Interactive: pick commands to remove
278
278
  agents commands remove
@@ -305,7 +305,7 @@ Examples:
305
305
  }
306
306
  if (!isInteractiveTerminal()) {
307
307
  requireInteractiveSelection('Selecting commands to remove', [
308
- 'agents commands remove README',
308
+ 'agents commands remove plan',
309
309
  ]);
310
310
  }
311
311
  try {
@@ -422,7 +422,7 @@ Examples:
422
422
  .addHelpText('after', `
423
423
  Examples:
424
424
  # View a specific command
425
- agents commands view README
425
+ agents commands view plan
426
426
 
427
427
  # Interactive picker
428
428
  agents commands view
@@ -437,7 +437,7 @@ Examples:
437
437
  }
438
438
  if (!isInteractiveTerminal()) {
439
439
  requireInteractiveSelection('Selecting a command to view', [
440
- 'agents commands view README',
440
+ 'agents commands view plan',
441
441
  ]);
442
442
  }
443
443
  try {
@@ -59,7 +59,13 @@ function formatRotationBanner(result, verb = 'balanced') {
59
59
  const { picked, healthy, excluded } = result;
60
60
  const label = picked.email ? `${picked.email} · ${picked.agent}@${picked.version}` : `${picked.agent}@${picked.version}`;
61
61
  const ratio = `${healthy.length} of ${healthy.length + excluded.length} healthy`;
62
- return `[agents] ${verb} picked ${label} (${ratio})`;
62
+ // Say it when the pick was a guess. A machine whose usage refresh is failing
63
+ // reports old percentages with total confidence, so a silent banner reads
64
+ // identical whether the router knew the account had headroom or merely hoped
65
+ // so — and the operator only finds out when the agent answers "you've hit
66
+ // your weekly limit".
67
+ const caveat = result.usageUnverified ? ', usage unverified — no account could be refreshed' : '';
68
+ return `[agents] ${verb} picked ${label} (${ratio}${caveat})`;
63
69
  }
64
70
  /**
65
71
  * Whether `cwd` is inside a git work tree.