@phnx-labs/agents-cli 1.22.43 → 1.22.45

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 (164) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +14 -12
  3. package/dist/bootstrap.js +3 -6
  4. package/dist/cli/command-registry.d.ts +0 -7
  5. package/dist/cli/command-registry.js +1 -21
  6. package/dist/commands/accounts.d.ts +1 -9
  7. package/dist/commands/accounts.js +12 -90
  8. package/dist/commands/apply.d.ts +7 -10
  9. package/dist/commands/apply.js +15 -35
  10. package/dist/commands/artifacts.js +4 -5
  11. package/dist/commands/audit.d.ts +6 -6
  12. package/dist/commands/audit.js +12 -12
  13. package/dist/commands/beta.d.ts +7 -1
  14. package/dist/commands/beta.js +16 -9
  15. package/dist/commands/commands.js +1 -1
  16. package/dist/commands/doctor.js +1 -4
  17. package/dist/commands/events.d.ts +2 -2
  18. package/dist/commands/events.js +6 -5
  19. package/dist/commands/exec.d.ts +1 -1
  20. package/dist/commands/exec.js +8 -4
  21. package/dist/commands/factory.d.ts +1 -1
  22. package/dist/commands/fleet-capture.d.ts +1 -1
  23. package/dist/commands/fleet-capture.js +4 -5
  24. package/dist/commands/harness.js +1 -49
  25. package/dist/commands/hooks.js +1 -1
  26. package/dist/commands/insights.d.ts +1 -1
  27. package/dist/commands/insights.js +85 -156
  28. package/dist/commands/menubar.js +58 -1
  29. package/dist/commands/output.js +2 -2
  30. package/dist/commands/prune.js +5 -4
  31. package/dist/commands/routines.test-fixture.d.ts +86 -0
  32. package/dist/commands/routines.test-fixture.js +345 -0
  33. package/dist/commands/sessions.test-fixture.d.ts +24 -0
  34. package/dist/commands/sessions.test-fixture.js +224 -0
  35. package/dist/commands/setup.js +5 -1
  36. package/dist/commands/share.d.ts +9 -10
  37. package/dist/commands/share.js +68 -50
  38. package/dist/commands/skills.js +1 -1
  39. package/dist/commands/ssh.js +15 -62
  40. package/dist/commands/subagents.js +1 -1
  41. package/dist/commands/view.js +5 -2
  42. package/dist/lib/analytics/mix-commands.d.ts +6 -28
  43. package/dist/lib/analytics/mix-commands.js +14 -40
  44. package/dist/lib/audit/log.d.ts +2 -2
  45. package/dist/lib/audit/log.js +2 -2
  46. package/dist/lib/beta.js +1 -1
  47. package/dist/lib/browser/domain-skills.d.ts +26 -6
  48. package/dist/lib/browser/domain-skills.js +81 -43
  49. package/dist/lib/daemon/daemon.js +11 -0
  50. package/dist/lib/daemon/daemon.test-fixture.d.ts +24 -0
  51. package/dist/lib/daemon/daemon.test-fixture.js +71 -0
  52. package/dist/lib/daemon-ticks.d.ts +38 -1
  53. package/dist/lib/daemon-ticks.js +52 -5
  54. package/dist/lib/device-config.js +1 -2
  55. package/dist/lib/devices/discovery-policy.d.ts +3 -3
  56. package/dist/lib/devices/discovery-policy.js +3 -3
  57. package/dist/lib/devices/fleet.d.ts +4 -8
  58. package/dist/lib/devices/fleet.js +3 -15
  59. package/dist/lib/devices/health-report.js +3 -3
  60. package/dist/lib/devices/pool.d.ts +0 -6
  61. package/dist/lib/devices/pool.js +0 -6
  62. package/dist/lib/devices/registry.d.ts +0 -15
  63. package/dist/lib/devices/registry.js +0 -9
  64. package/dist/lib/devices/stats-cache.d.ts +12 -0
  65. package/dist/lib/devices/stats-cache.js +24 -5
  66. package/dist/lib/fleet/capture.d.ts +1 -1
  67. package/dist/lib/fleet/manifest.js +1 -1
  68. package/dist/lib/fleet/remote-login.d.ts +2 -2
  69. package/dist/lib/fleet/remote-login.js +3 -4
  70. package/dist/lib/hosts/dispatch.d.ts +2 -0
  71. package/dist/lib/hosts/dispatch.js +8 -1
  72. package/dist/lib/hosts/passthrough.js +0 -1
  73. package/dist/lib/hosts/providers/devices.js +1 -13
  74. package/dist/lib/hosts/ready.d.ts +12 -3
  75. package/dist/lib/hosts/ready.js +27 -12
  76. package/dist/lib/hosts/registry.d.ts +4 -5
  77. package/dist/lib/hosts/registry.js +3 -7
  78. package/dist/lib/installations/versions.js +21 -2
  79. package/dist/lib/menubar/install-menubar.d.ts +109 -0
  80. package/dist/lib/menubar/install-menubar.js +199 -4
  81. package/dist/lib/remote-agents-json.js +1 -7
  82. package/dist/lib/routines-placement.d.ts +1 -1
  83. package/dist/lib/routines-placement.js +1 -1
  84. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  85. package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -2
  86. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  87. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +1 -13
  88. package/dist/lib/session/active.d.ts +4 -16
  89. package/dist/lib/session/active.js +2 -6
  90. package/dist/lib/session/db.d.ts +13 -98
  91. package/dist/lib/session/db.js +4 -11
  92. package/dist/lib/session/discover.d.ts +5 -81
  93. package/dist/lib/session/discover.js +5 -5
  94. package/dist/lib/session/remote/remote-bundle.d.ts +12 -0
  95. package/dist/lib/session/remote/remote-bundle.js +61 -0
  96. package/dist/lib/session/remote/remote-list.d.ts +148 -0
  97. package/dist/lib/session/remote/remote-list.js +607 -0
  98. package/dist/lib/session/remote/remote.d.ts +132 -0
  99. package/dist/lib/session/remote/remote.js +314 -0
  100. package/dist/lib/session/remote/watch.d.ts +101 -0
  101. package/dist/lib/session/remote/watch.js +241 -0
  102. package/dist/lib/session/remote-bundle.d.ts +1 -12
  103. package/dist/lib/session/remote-bundle.js +3 -61
  104. package/dist/lib/session/remote-list.d.ts +1 -148
  105. package/dist/lib/session/remote-list.js +3 -607
  106. package/dist/lib/session/remote.d.ts +1 -132
  107. package/dist/lib/session/remote.js +3 -314
  108. package/dist/lib/session/watch.d.ts +1 -101
  109. package/dist/lib/session/watch.js +3 -242
  110. package/dist/lib/share/config.d.ts +12 -2
  111. package/dist/lib/share/config.js +47 -11
  112. package/dist/lib/share/delete.js +1 -1
  113. package/dist/lib/share/publish.js +1 -1
  114. package/dist/lib/share/worker-template.js +2 -2
  115. package/dist/lib/smart-launch.d.ts +4 -4
  116. package/dist/lib/smart-launch.js +8 -18
  117. package/dist/lib/staleness/index.d.ts +3 -1
  118. package/dist/lib/staleness/index.js +21 -1
  119. package/dist/lib/staleness/types.d.ts +11 -0
  120. package/dist/lib/staleness/writers/commands.js +13 -4
  121. package/dist/lib/staleness/writers/hooks.js +3 -1
  122. package/dist/lib/staleness/writers/mcp.d.ts +0 -7
  123. package/dist/lib/staleness/writers/mcp.js +16 -1
  124. package/dist/lib/staleness/writers/rules.js +1 -1
  125. package/dist/lib/staleness/writers/skills.js +3 -1
  126. package/dist/lib/staleness/writers/subagents.d.ts +0 -9
  127. package/dist/lib/staleness/writers/subagents.js +18 -1
  128. package/dist/lib/staleness/writers/types.d.ts +10 -0
  129. package/dist/lib/startup/command-registry.d.ts +8 -1
  130. package/dist/lib/startup/command-registry.js +21 -6
  131. package/dist/lib/startup/root-command.d.ts +18 -1
  132. package/dist/lib/startup/root-command.js +18 -1
  133. package/dist/lib/state.js +7 -0
  134. package/dist/lib/teams/placement-probe.js +27 -12
  135. package/dist/lib/teams/scheduler.d.ts +3 -1
  136. package/dist/lib/types.d.ts +1 -1
  137. package/dist/lib/view-types.d.ts +3 -0
  138. package/package.json +2 -3
  139. package/dist/bin/agents +0 -0
  140. package/dist/commands/auth.d.ts +0 -9
  141. package/dist/commands/auth.js +0 -108
  142. package/dist/commands/org.d.ts +0 -6
  143. package/dist/commands/org.js +0 -175
  144. package/dist/commands/serve.d.ts +0 -10
  145. package/dist/commands/serve.js +0 -68
  146. package/dist/commands/trends.d.ts +0 -10
  147. package/dist/commands/trends.js +0 -12
  148. package/dist/lib/entitlement.d.ts +0 -31
  149. package/dist/lib/entitlement.js +0 -137
  150. package/dist/lib/prix-account.d.ts +0 -158
  151. package/dist/lib/prix-account.js +0 -214
  152. package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
  153. package/dist/lib/serve/control.d.ts +0 -95
  154. package/dist/lib/serve/control.js +0 -260
  155. package/dist/lib/serve/data.d.ts +0 -81
  156. package/dist/lib/serve/data.js +0 -91
  157. package/dist/lib/serve/page.d.ts +0 -7
  158. package/dist/lib/serve/page.js +0 -140
  159. package/dist/lib/serve/server.d.ts +0 -80
  160. package/dist/lib/serve/server.js +0 -145
  161. package/dist/lib/serve/stream.d.ts +0 -43
  162. package/dist/lib/serve/stream.js +0 -116
  163. package/dist/lib/serve/token.d.ts +0 -35
  164. package/dist/lib/serve/token.js +0 -85
package/CHANGELOG.md CHANGED
@@ -1,5 +1,90 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.22.45
4
+
5
+ - **BREAKING: the Prix-coupled account layer is removed — `agents auth`, `agents org`, and the plan-tier gates (RUSH-2581).** 1.22.42 shipped `agents auth login/whoami/logout` and `agents org` against the Rush product's backend (`api.prix.dev`), with `entitlement.ts` reading the Rush billing tier to cap accounts at 3-per-harness on free and to gate the `agents insights` friction sections. agents-cli's identity must not ride a separate product's login and database, so all of it is removed: `auth` and `org` are retired top-level names (they fail loudly, no auto-correct), account registration is uncapped again, `agents accounts` output drops the `dormant` field/suffix, and `agents insights` renders the full report with no `plan`/`notice` JSON fields. The Rush *feature* integrations are untouched (`cloud/rush.ts` dispatch, the opt-in secrets sync driver, the owner-notify transport). The replacement — agents-cli's own account backend with Phoenix branding, built for later Phoenix-ID consolidation — is tracked in RUSH-2581. Source: `apps/cli/src/lib/{prix-account,entitlement}.ts` (deleted), `apps/cli/src/commands/{auth,org}.ts` (deleted), `apps/cli/src/commands/{accounts,insights}.ts`, `apps/cli/src/{bootstrap.ts,cli/command-registry.ts,lib/startup/command-registry.ts}`.
6
+
7
+ - **The menubar prepack gate fails closed off-Mac when the bundle has no stapled notarization ticket (RUSH-3026).** 1.22.44 was packed on a Linux box where the gate's codesign/xcrun checks silently no-op'd, so an un-stapled dev bundle shipped and Gatekeeper rejected it on every Mac ("not notarized/valid; skipping launch") — the menu bar died until a rollback to 1.22.43. `stapler staple` writes the ticket as a plain file (`Contents/CodeResources`), so its absence is provable anywhere: the gate now hard-fails the pack when it is missing and no xcrun is available, turning that failure into a pack error instead of a shipped regression. Source: `apps/cli/scripts/verify-menubar-helper.sh`.
8
+
9
+ - **A Linux box can now produce the pretested release tarball — the attestation producer seeds the already-signed helper apps (RUSH-3026).** `release-attestation-produce.sh` runs in a fresh worktree whose `bin/` is empty (the signed `.app` helpers are untracked), so on any non-Mac box `npm pack` died at the prepack gates and attestation production — and therefore every release — stayed chained to a Mac. Off a macOS signing box the producer now seeds `bin/Agents CLI.app` and `bin/MenubarHelper.app` copy-if-absent from the caller checkout; the prepack gates still verify them (keychain sha pin, menubar presence), so a wrong or tampered seed fails the pack exactly as before. A Darwin producer's freshly signed apps are never overwritten. Source: `apps/cli/scripts/release-attestation-produce.sh`.
10
+
11
+ ## 1.22.44
12
+
13
+ - **Browser domain-skill discovery is layer-aware (RUSH-2497).** `agents browser start --url <url>` auto-loads a site-specific SKILL.md, but the lookup only ever searched the user layer (`~/.agents/skills/browser/domain-skills`), so a skill shipped in the system layer (`~/.agents/.system/skills/browser/domain-skills`) — or a project's `.agents/` — was silently invisible: the browser just opened without the guide. `resolveDomainSkill` now searches project > user > system > extra repos, first layer with a match wins, mirroring `resolveResource` precedence; `$AGENTS_BROWSER_DOMAIN_SKILLS_DIR` stays a single-root override for tests. The project layer resolves from the calling process's cwd — the shared browser daemon does not yet receive the CLI caller's cwd over IPC, so there the project layer follows the daemon's own cwd until RUSH-2996 threads it through. A miss still never breaks browser start, but now logs the roots searched at debug level (`AGENTS_DEBUG`/`DEBUG`) — the total silence is what hid this. Source: `apps/cli/src/lib/browser/domain-skills.ts`.
14
+
15
+ - **`agents artifacts share list/update/delete` (and the `unshare` alias) had a silently-dropped `--json`/`--github-user` (RUSH-2687).** Commander resolves an option's long name against the WHOLE ancestor chain, not per-command — `share <file>` (the parent) already declares `--json`/`--github-user` for its own publish-time use, so the same-named flag on `list`/`update`/`delete` was swallowed at parse time even when passed alone (`--help` still showed it registered, which made it easy to miss). `agents artifacts share list --json | jq …`, the exact example in `docs/share.md`, never emitted JSON. Fixed by renaming the colliding options — `--list-json`/`--for-user` (list), `--update-json` (update), `--delete-json`/`--for-user` (delete/unshare) — matching the precedent `share revisions` already set (`--revisions-json`/`--for-user`, RUSH-2683). A CLI-wide `enablePositionalOptions()` fix was evaluated and reverted: commander copies that setting onto every command created via `.command()`, so enabling it on the root program broke `sessions backfill tools/resources --since … --json …`, which relies on a parent command's options staying visible to leaf subcommands via `optsWithGlobals()`. Source: `apps/cli/src/commands/share.ts`, `apps/cli/src/lib/startup/root-command.ts`.
16
+
17
+ - **`agents artifacts share` publishes with a partial `share:` config (RUSH-2837).** `status` and publish both used to treat a missing/empty `accountId` as "not set up", even when `baseUrl` and `WRITE_TOKEN` were present — so a fleet rewrite that blanked `accountId` (or a partial `writeMeta` that dropped `share:`) silently killed PR evidence uploads. Publish now requires only `baseUrl` + the write token; `status` reports the endpoint and names an empty account id instead of pretending nothing is configured; `writeShareConfig` will not persist `accountId: ""` over a stored id; `serializeCentral` no longer deletes `share:` just because a write omitted the key. Source: `apps/cli/src/lib/share/config.ts`, `apps/cli/src/lib/state.ts`, `apps/cli/src/commands/share.ts`.
18
+
19
+ - **Pick a device-local account after fleet placement (RUSH-2961).** `agents run <harness>@` now composes with `--device <name|auto>`: the device resolves first, then the interactive picker lists that peer's installed versions/accounts and pins the selection for this run. Automatic picker placement prefers a ready account, retains signed-out/revoked login targets, and excludes devices whose picker would contain only rate-limited or out-of-credit rows. `agents view --json` exposes each version's cached `authVerdict` so remote and local placement apply the same revoked-token gate. Source: `apps/cli/src/commands/{exec,view}.ts`, `apps/cli/src/lib/{smart-launch,hosts/dispatch,hosts/ready}.ts`.
20
+
21
+ - **BREAKING: nest `agents beta` under `agents setup beta`; retire top-level `agents apply`; drop `harness login`/`logout` (RUSH-2981).** Enabling a preview feature is setup, not its own noun — `agents setup beta list/enable/disable` keep the same `~/.agents/agents.yaml` `beta.enabled` write path. Fleet reconcile is already `agents fleet apply` / `agents devices apply`; top-level `apply` is gone (unknown command, retired from distance-1 auto-correct). Harness credentials live on `agents accounts` — `harness login`/`logout` are removed. Does not touch org/auth/trends/audit/unshare (sibling session 01a02234). Source: `apps/cli/src/commands/{beta,setup,apply,harness}.ts`, `apps/cli/src/lib/startup/command-registry.ts`.
22
+
23
+ - **Nest leftover top-level aliases; keep `agents org` as a deprecated spelling (RUSH-2989).**
24
+ `agents unshare` moves under `agents artifacts unshare` (same takedown as
25
+ `artifacts share delete`; the top-level name is retired). `agents audit` moves
26
+ under `agents events audit` (still `events --include runs`, plus `verify` for
27
+ the legacy hash-chain). Former top-level `agents trends` is `agents insights mix`
28
+ (also `agents insights trends`). `agents org` still works and prints a deprecation
29
+ line; the canonical tree is `agents auth space`. Prix login stays off
30
+ `agents accounts` (that noun is harness keys). Source:
31
+ `apps/cli/src/commands/{auth,org,artifacts,share,events,audit,insights}.ts`,
32
+ `apps/cli/src/lib/analytics/mix-commands.ts`.
33
+
34
+ - **`agents view` shows Claude usage again — the daemon auth-health probe no longer hammers `/oauth/usage` into a permanent 429 (RUSH-2998).** The daemon's fleet auth-health probe (`probeLocalFleetAuth` → `probeClaudeStatus`) hit the rate-limited `/api/oauth/usage` endpoint every ~3 minutes on **every** device. Across the fleet that drove one per-account request quota to a persistent `429`; the shared `Retry-After` backoff then parked usage fleet-wide and the usage cache froze (windows stale-zeroed → `agents view` rendered `S: 0% (now) W: 0% (now)` for weeks). The periodic tick now re-probes the endpoint at most every 20 minutes (`AUTH_PROBE_MAX_AGE_MS`) and reuses the last real verdict in between — a ~5x cut in endpoint traffic. Fleet status still publishes every tick (it does not ride that endpoint), and every device keeps a **real** auth verdict, so `agents devices ping --strict` and the run auth-preflight keep detecting revocation on every host. `agents devices ping` forces a genuinely live probe (never the throttled cached verdict). Source: `apps/cli/src/lib/daemon-ticks.ts` (`AUTH_PROBE_MAX_AGE_MS`, `isCachedFleetAuthProbeFresh`, `refreshLocalFleetAuthState` `force`), `apps/cli/src/commands/ssh.ts` (`runFleetPing`).
35
+
36
+ - **BREAKING: remove `agents serve`, its `--control` anchor, and `agents devices pair-ios` (RUSH-3001).**
37
+ The read-only local web companion (`agents serve`) duplicated what `agents sessions`,
38
+ `agents teams status`, and the AGI EXT Fleet panel already show, and its `--control`
39
+ mode existed only to anchor the unshipped iOS Fleet Cockpit prototype (`apps/ios`,
40
+ untouched since 2026-07-16, no Xcode project) — both are gone, and so is the prototype
41
+ itself. `agents devices pair-ios` (the only writer of the device registry's `control`
42
+ role) is removed with it; with no writer left, the `control`-only dial-exclusion
43
+ filters (`isControlDevice`) are removed too, across `doctor`, `apply`, `fleet status`,
44
+ `fleet-capture`, `smart-launch`, the devices host provider, session fan-out, and
45
+ remote login detection. `agents serve` now reports `unknown command` and is retired
46
+ from distance-1 auto-correct; `agents devices pair-ios` is rejected by `devices` as an
47
+ unexpected argument.
48
+ **Migration —** if you previously ran `agents devices pair-ios`, that phone or tablet is
49
+ still in your device registry with `role: control`, and the key is now ignored rather
50
+ than honoured: it becomes an ordinary fleet target. `agents fleet status`,
51
+ `agents fleet update/run`, `agents apply`, `agents doctor --check --devices`,
52
+ `agents insights output --all-hosts`, and any `--device all` passthrough will dial it and
53
+ wait out a ConnectTimeout; `agents hosts list` shows it as dispatchable; `--device auto`
54
+ can place an agent on it when no device is marked `worker`; and routine fleet placement
55
+ can schedule a recurring job onto it, since an `unknown`-platform device sorts ahead of a
56
+ real worker alphabetically. Failures are loud, not silent. Remove it with
57
+ `agents devices rm <name>`; the stale `role` key is dropped anyway on that device's next
58
+ `agents devices sync`. Session fan-out is unaffected — it still gates on a
59
+ `windows`/`linux`/`macos` platform.
60
+ Source: `apps/cli/src/commands/{serve,ssh}.ts`,
61
+ `apps/cli/src/lib/devices/{registry,fleet,pool,discovery-policy}.ts`,
62
+ `apps/cli/src/lib/{smart-launch,remote-agents-json,device-config}.ts`,
63
+ `apps/cli/src/lib/hosts/{registry,providers/devices}.ts`,
64
+ `apps/cli/src/lib/session/{watch,remote-list}.ts`, `apps/cli/src/lib/fleet/remote-login.ts`,
65
+ `apps/cli/src/cli/command-registry.ts`, `apps/cli/src/lib/startup/command-registry.ts`.
66
+
67
+ - **Menu-bar helper restarts after a bundle swap; one-time TCC migration; `agents menubar doctor` (RUSH-3019).** `installMenubarLaunchAgentOnUpgrade()` swapped the installed bundle on an upgrade but never restarted the running helper — verified live: a helper pid started 21:44:16 kept running while the bundle under it was replaced at 21:53:46, so it kept requesting Accessibility under the OLD code identity and the grant never stuck. The heal now kickstarts the launchd job (`launchctl kickstart -k`) after a real content swap (a version bump or the ad-hoc -> Developer ID transition — a plist-only interpreter repoint, RUSH-3005's churn, does not trigger this) and falls back to ending the specific pid(s) still running the old binary when `kickstart -k` can't reach the GUI launchd domain from a non-Aqua shell context, so launchd's `KeepAlive` relaunches from the swapped binary. Separately, a machine that transitioned ad-hoc -> Developer ID (6fa36f73a) now gets a one-time `tccutil reset Accessibility com.phnx-labs.agents-menubar` to clear the dead grant recorded against the old identity, stamped so it never re-runs. The "Paste needs Accessibility" notification now opens System Settings' Accessibility pane on click and names the actual running bundle path. New read-only `agents menubar doctor` reports install path, installed vs CLI version, signing identity stability, and whether a live helper pid predates the on-disk bundle (the stale-process bug this fixes). Source: `apps/cli/src/lib/menubar/install-menubar.ts`, `apps/cli/src/commands/menubar.ts`, `apps/cli/menubar/Sources/MenubarHelper/Clip.swift`.
68
+
69
+ - **Daemon autostart no longer fires under a redirected (sandbox/test) HOME (RUSH-3021).** #2860 gated service-manager registration on `isolatedHomeSuffix()` but left `ensureDaemonStarted()`'s detached spawn ungated, so a test-spawned CLI could fork a daemon into the test's temp HOME; the child outlived the test and raced its recursive teardown `rm` (the `ENOTEMPTY` flake that cost most full-suite attestation rolls on shared boxes). The launch path now refuses under a redirected HOME via the same `serviceManagerRegistrationAllowed()` signal and `AGENTS_SERVICE_MANAGER_ALLOW_REDIRECTED_HOME` test seam; reporting an already-running daemon is untouched, and `agents daemon start` remains the operator override. Source: `apps/cli/src/lib/daemon/daemon.ts` (`ensureDaemonStarted`), regression test in `apps/cli/src/lib/daemon/daemon.registry.test.ts`.
70
+
71
+ - **The npm tarball no longer bundles the version-stamped signed CLI binary — releases publish from any OS (RUSH-3026).** `dist/bin/agents` (the Developer-ID-signed arm64 Mach-O, #315) embedded the release version, so every release forced a fresh Mac build + sign even when nothing native changed — chaining publishing to a single provisioned Mac (`mac-mini`), whose downtime stranded merged fixes unshippable. The binary is dropped from `package.json` `files` and the `prepack` gates; `postinstall`'s existing run-probe falls back to the JS entrypoint when the binary is absent, so macOS installs keep working (it returns as a per-release GitHub asset in a follow-up). `release.sh`'s home-base phase — already promote-only (download attested tarball, verify, install-smoke, `npm publish`) — drops its vestigial macOS gate, and a new `assert_promote_home_base` preflight (npm token + gh auth, via `scripts/promote-home-base-probe.sh`) runs BEFORE the release's first mutation, fixing the tagged-but-unpublished failure shape: the old signing preflight was defined but never invoked. The two `.app` helpers (keychain, menubar) remain bundled and manifest-reused — unchanged helpers are never rebuilt. Source: `apps/cli/package.json`, `apps/cli/scripts/release.sh`, `apps/cli/scripts/promote-home-base-probe.sh`.
72
+
73
+ - **`agents sync` restores a resource deleted from a version home — no `--force` needed (#2398, #2397).** The staleness fast-guard decided sync-ness from source fingerprints alone, so a managed artifact deleted from the version home (a subagent, command, skill, hook, MCP config, or rules file) read as "Already in sync" and only `--force` restored it. The sync manifest now records the artifact paths the last full sync wrote (`writtenTargets`, reported by each resource writer), and `isStale` treats a missing path as stale — one `existsSync` per path, no content reads, so the fast-guard budget holds. A manifest written by an older agents-cli lacks the field and reads as stale once; that full sync establishes the baseline. Relatedly, four commands (`agents commands/skills/hooks/subagents remove`) claimed a removed resource "will re-sync on next agent launch" — the shim's launch hook (`agents sync --launch`) is project-scoped only and deliberately skips version-home reconciliation, so the messages now name the real recovery: `agents sync <agent>@<version> --yes`. `agents doctor --fix` was already immune (it heals from the live-home diff, not the manifest). Source: `apps/cli/src/lib/staleness/{index,types}.ts`, `apps/cli/src/lib/staleness/writers/{types,commands,skills,hooks,mcp,subagents,rules}.ts`, `apps/cli/src/lib/installations/versions.ts`, `apps/cli/src/commands/{commands,skills,hooks,subagents,prune}.ts`.
74
+
75
+ - **`agents devices list` no longer serves fossilized load/mem numbers (#2666).** The
76
+ fleet stats cache (`.fleet-stats.json`) had no age bound and — since RUSH-2061
77
+ removed the daemon's N² fleet warm — no background writer, so the load/mem columns
78
+ froze at the last manual `--refresh` and re-rendered as current fleet state
79
+ indefinitely (observed: a 9-day-old row reporting an idle Mac at 1058% load). Cached
80
+ rows are now bounded by `STATS_STALE_MS` (3 minutes, the same window as the
81
+ agent-count mirror): a row past the bound is treated like a missing one — re-probed
82
+ live this call and rewritten — so the default `devices list` / `devices status` read
83
+ is itself the cache's writer and a stale value can never be presented as current.
84
+ This cache feeds the fleet scheduler, `--device auto` affinity, and the
85
+ session-start fleet banner. Source: `apps/cli/src/lib/devices/stats-cache.ts`,
86
+ `apps/cli/src/commands/ssh.ts`.
87
+
3
88
  ## 1.22.43
4
89
 
5
90
  - **Rush Cloud no longer has a helper that reads the interactive Claude login (RUSH-2359 / incident #1767).** Dispatch already sends an email-only account manifest (SING-1b) and never uploaded a token; the leftover `readClaudeCredentialsBlob` still read Keychain / `.credentials.json` — the exact shape that captured a `claude setup-token` TTY banner as an Authorization header. That helper is deleted. `--lease` SING-1b detection now reads the wrapped rotating blob itself and ignores anything that is not `{ claudeAiOauth.accessToken }`. Source: `apps/cli/src/lib/cloud/rush.ts`, `apps/cli/src/lib/crabbox/runtimes.ts`.
package/README.md CHANGED
@@ -188,6 +188,8 @@ agents run claude "summarize recent commits" --strategy balanced
188
188
  # Or choose one account/version interactively for only this run.
189
189
  agents run claude@
190
190
  agents run codex@ "review this branch"
191
+ agents run claude@ --device auto # pick the device, then choose there
192
+ agents run claude@ --device yosemite-s0 # choose from one device's accounts
191
193
  ```
192
194
 
193
195
  `--strategy balanced` spreads work across available versions of the same agent -- useful when you have multiple accounts and want to avoid burning through one. When a Claude run reports a session limit, agents-cli records the stated reset time, shows `session-limited` in `agents view`, and excludes that account until the reset. When every account is rate-limited, the run exits nonzero naming each excluded account and the earliest window reset (use `--strategy pinned` to force the default) -- it never launches into an exhausted account.
@@ -205,7 +207,7 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the host layer
205
207
 
206
208
  A trailing `@` opens an account picker before either an interactive or prompt-based run. Each installed version shows its account identity, exact version, login state, plan, and every available session, weekly, or monthly limit. Logged-out, rate-limited, and out-of-credit accounts remain visible with the reason they cannot be selected; signed-in accounts whose provider does not expose quota data stay selectable and say `limits unavailable`. The choice pins only that run and does not change your default version.
207
209
 
208
- Account selection is available for Claude, Codex, Gemini, Cursor, Antigravity, Grok, Kimi, Droid, and OpenCode. It requires a terminal and cannot be combined with `--resume`, `--strategy`/`--balanced`, `--lease`, or `--device`; profiles and workflows must use their concrete host agent instead.
210
+ Account selection is available for Claude, Codex, Gemini, Cursor, Antigravity, Grok, Kimi, Droid, and OpenCode. It requires a terminal and cannot be combined with `--resume`, `--strategy`/`--balanced`, `--lease`, or a warm-box lease. Device routing is resolved first, then the picker is populated from the selected device. Profiles and workflows must use their concrete host agent instead.
209
211
 
210
212
  ### Chain agents
211
213
 
@@ -454,7 +456,7 @@ A stalled session whose tail shows a hard account limit ("You've hit your weekly
454
456
  ## Sync the fleet
455
457
 
456
458
  <p align="center">
457
- <img src="assets/fleet-sync.svg" alt="agents apply: reconcile every device to one profile from agents.yaml — install missing agents and sync config. Native logins stay on the machine that minted them." width="100%" />
459
+ <img src="assets/fleet-sync.svg" alt="agents fleet apply: reconcile every device to one profile from agents.yaml — install missing agents and sync config. Native logins stay on the machine that minted them." width="100%" />
458
460
  </p>
459
461
 
460
462
 
@@ -471,14 +473,14 @@ fleet:
471
473
  ```
472
474
 
473
475
  ```bash
474
- agents apply --plan # device x dimension matrix; changes nothing
475
- agents apply # reconcile the fleet (confirms first; -y to skip)
476
- agents apply --device yosemite-s0 # scope to one device
477
- agents apply --only agents,config # limit dimensions (agents, config, login)
478
- agents apply --no-login # skip login propagation
476
+ agents fleet apply --plan # device x dimension matrix; changes nothing
477
+ agents fleet apply # reconcile the fleet (confirms first; -y to skip)
478
+ agents fleet apply --device yosemite-s0 # scope to one device
479
+ agents fleet apply --only agents,config # limit dimensions (agents, config, login)
480
+ agents fleet apply --no-login # skip login propagation
479
481
  ```
480
482
 
481
- `agents apply` (`ag apply`) probes every target over the existing SSH transport, then reconciles it to the profile: installs missing agents, upgrades `agi-cli`, syncs the named config scopes, and **propagates logins** so a host signed in once seeds the fleet -- turning "6 hosts x 8 harnesses = 48 OAuth flows" into one. Portable credential files (claude, codex, grok, kimi, opencode, droid, antigravity) stream to each target over encrypted SSH stdin, never shell-interpolated, and land at `0600`. **Honest boundary:** macOS keychain-bound tokens (claude, antigravity on a Mac target) can't be extracted -- those surface as a one-time manual login, never faked. `--plan` / `--dry-run` shows the full matrix without touching anything.
483
+ `agents fleet apply` probes every target over the existing SSH transport, then reconciles it to the profile: installs missing agents, upgrades `agi-cli`, syncs the named config scopes, and **propagates logins** so a host signed in once seeds the fleet -- turning "6 hosts x 8 harnesses = 48 OAuth flows" into one. Portable credential files (claude, codex, grok, kimi, opencode, droid, antigravity) stream to each target over encrypted SSH stdin, never shell-interpolated, and land at `0600`. **Honest boundary:** macOS keychain-bound tokens (claude, antigravity on a Mac target) can't be extracted -- those surface as a one-time manual login, never faked. `--plan` / `--dry-run` shows the full matrix without touching anything.
482
484
 
483
485
  See [docs/fleet.md](apps/cli/docs/fleet.md) for the manifest schema and reconcile semantics.
484
486
 
@@ -670,7 +672,7 @@ age (`updated 2m ago — pass --refresh (--live) for a live probe`).
670
672
  `agents fleet status` answers "is my fleet OK?" at a glance: a one-line rollup
671
673
  (`● N online · ○ M offline`), a short **NEEDS ATTENTION** list where every item names
672
674
  the command that fixes it (offline → `check the box`, config drift or a stark CLI gap →
673
- `agents apply <box>`, version skew → `agents upgrade --fleet`), then quiet per-device
675
+ `agents fleet apply --device <box>`, version skew → `agents upgrade --fleet`), then quiet per-device
674
676
  rows grouped by OS (macOS / Linux / Windows) showing just `name · capacity · load/mem ·
675
677
  version`, with this machine flagged `▸ … ← this machine`. A healthy fleet reads in a few
676
678
  lines; orphaned versions are demoted to a one-line `agents prune` nudge in the footer.
@@ -1203,7 +1205,7 @@ agents artifacts share plan.html --json # URL object for pla
1203
1205
  agents artifacts share list --agent claude # everything published, filterable
1204
1206
  agents artifacts share revisions fleet # prior versions kept under a slug
1205
1207
  agents artifacts share status # show the endpoint
1206
- agents unshare fleet # take a published link (+ its OG cover) down
1208
+ agents artifacts unshare fleet # take a published link (+ its OG cover) down
1207
1209
  ```
1208
1210
 
1209
1211
  `agents artifacts share` closes the loop: an agent makes work (a plan, a viz, a report),
@@ -1232,7 +1234,7 @@ Republishing an existing slug keeps the prior version as a revision by default
1232
1234
  (`--no-revision` to skip); `agents artifacts share revisions <slug>` shows the retained
1233
1235
  history, newest first.
1234
1236
 
1235
- `agents artifacts share delete <targets...>` (alias `agents unshare`) takes a page down — pass a
1237
+ `agents artifacts share delete <targets...>` (alias `agents artifacts unshare`) takes a page down — pass a
1236
1238
  full URL, `<user>/<slug>`, or a bare slug (resolved against your own namespace); several
1237
1239
  targets at once are fine. It also deletes the sibling `<slug>.png` OG cover by default
1238
1240
  (`--keep-cover` opts out) and verifies the page actually 404s before reporting success —
@@ -1505,7 +1507,7 @@ For full transparency: `agi-cli` keeps a local event log at `~/.agents/.cache/lo
1505
1507
 
1506
1508
  macOS and Linux. Windows via WSL works but isn't first-class yet.
1507
1509
 
1508
- **macOS-only features:** Keychain-based secrets (`agents secrets`, `agents harness login`) require macOS. Default iCloud sync for bundles requires macOS + iCloud Keychain enabled; use `--no-icloud-sync` for device-local bundles. On Linux, use environment variables or `.env` files for API keys. Native Linux credential store support is planned.
1510
+ **macOS-only features:** Keychain-based secrets (`agents secrets`, `agents accounts add`) require macOS. Default iCloud sync for bundles requires macOS + iCloud Keychain enabled; use `--no-icloud-sync` for device-local bundles. On Linux, use environment variables or `.env` files for API keys. Native Linux credential store support is planned.
1509
1511
 
1510
1512
  Interactive tmux-backed runs require tmux 3.2 or newer.
1511
1513
 
package/dist/bootstrap.js CHANGED
@@ -211,7 +211,7 @@ Install, configure, run, and dispatch AI coding agents from one place.
211
211
  Works with Claude, Codex, Antigravity, Cursor, OpenCode, OpenClaw, and Droid.
212
212
 
213
213
  Quick start:
214
- agents setup First-time setup (interactive)
214
+ agents setup First-time setup (interactive); setup beta enables preview features
215
215
  agents view See what's installed
216
216
  agents run <agent> ["prompt"] Run an agent (interactive without prompt, headless with)
217
217
  agents sessions Browse past sessions across all agents
@@ -260,11 +260,11 @@ Observe (read the fleet — no store merge):
260
260
  feed Needs-you inbox (open blocks waiting on you); agent posts = feed --filter updates
261
261
  sessions --active Live agents (who is running right now)
262
262
  events Unified ops + activity event trail
263
- audit Tamper-evident run-dispatch log (not events)
263
+ events audit Dispatched-run outcomes (alias of events --include runs)
264
264
  sync status Sync/drift only (not the live fleet snapshot)
265
265
  devices snapshot One-process inventory + active sessions poll
266
266
 
267
- Credentials:
267
+ Credentials (harness keys):
268
268
  harness Custom (host CLI + model + auth) harnesses; replaces former profiles command
269
269
  secrets Keychain-backed env bundles; synced vault: secrets vault unlock|lock
270
270
  accounts Provider credentials + native OAuth logout
@@ -281,9 +281,6 @@ Config sync:
281
281
  repo init --path <dir> Scaffold your own editable repo from a template
282
282
  repo add <path|gh:user/repo> Merge an extra repo after the system repo
283
283
 
284
- Beta features:
285
- beta Enable preview features (factory and more)
286
-
287
284
  Automation tips:
288
285
  Pass explicit names/IDs Avoid pickers: agents sessions <id> --markdown
289
286
  Use --yes for defaults Auto-accept sync/default prompts on add/use/pull
@@ -56,19 +56,16 @@ export declare const loadPrune: ModuleLoader;
56
56
  export declare const loadTrash: ModuleLoader;
57
57
  export declare const loadRestore: ModuleLoader;
58
58
  export declare const loadDoctor: ModuleLoader;
59
- export declare const loadApply: ModuleLoader;
60
59
  export declare const loadRoute: ModuleLoader;
61
60
  export declare const loadHarness: ModuleLoader;
62
61
  export declare const loadSecrets: ModuleLoader;
63
62
  export declare const loadMenubar: ModuleLoader;
64
- export declare const loadBeta: ModuleLoader;
65
63
  export declare const loadSync: ModuleLoader;
66
64
  export declare const loadRefreshRules: ModuleLoader;
67
65
  export declare const loadFactory: ModuleLoader;
68
66
  export declare const loadUsage: ModuleLoader;
69
67
  export declare const loadInsights: ModuleLoader;
70
68
  export declare const loadPerf: ModuleLoader;
71
- export declare const loadTrends: ModuleLoader;
72
69
  export declare const loadPty: ModuleLoader;
73
70
  export declare const loadTmux: ModuleLoader;
74
71
  export declare const loadWatchdog: ModuleLoader;
@@ -88,15 +85,11 @@ export declare const loadMessage: ModuleLoader;
88
85
  export declare const loadSend: ModuleLoader;
89
86
  export declare const loadFeed: ModuleLoader;
90
87
  export declare const loadMailboxes: ModuleLoader;
91
- export declare const loadServe: ModuleLoader;
92
88
  export declare const loadArtifacts: ModuleLoader;
93
- export declare const loadAudit: ModuleLoader;
94
89
  export declare const loadWebhooks: ModuleLoader;
95
90
  export declare const loadHumans: ModuleLoader;
96
91
  export declare const loadAccounts: ModuleLoader;
97
92
  export declare const loadDaemon: ModuleLoader;
98
- export declare const loadAuth: ModuleLoader;
99
- export declare const loadOrg: ModuleLoader;
100
93
  /**
101
94
  * Commands whose modules pull in the SQLite-backed session/cloud stack. They are
102
95
  * registered AFTER `applyGlobalHelpConventions` (mirroring main's order: help
@@ -58,20 +58,16 @@ export const loadPrune = async () => (await import('../commands/prune.js')).regi
58
58
  export const loadTrash = async () => (await import('../commands/trash.js')).registerTrashCommands;
59
59
  export const loadRestore = async () => (await import('../commands/trash.js')).registerRestoreCommand;
60
60
  export const loadDoctor = async () => (await import('../commands/doctor.js')).registerDoctorCommand;
61
- export const loadApply = async () => (await import('../commands/apply.js')).registerApplyCommand;
62
61
  export const loadRoute = async () => (await import('../commands/route.js')).registerRouteCommands;
63
62
  export const loadHarness = async () => (await import('../commands/harness.js')).registerHarnessCommands;
64
63
  export const loadSecrets = async () => (await import('../commands/secrets.js')).registerSecretsCommands;
65
64
  export const loadMenubar = async () => (await import('../commands/menubar.js')).registerMenubarCommands;
66
- export const loadBeta = async () => (await import('../commands/beta.js')).registerBetaCommands;
67
65
  export const loadSync = async () => (await import('../commands/sync.js')).registerSyncCommand;
68
66
  export const loadRefreshRules = async () => (await import('../commands/refresh-rules.js')).registerRefreshRulesCommand;
69
67
  export const loadFactory = async () => (await import('../commands/factory.js')).registerFactoryCommands;
70
68
  export const loadUsage = async () => (await import('../commands/usage.js')).registerUsageCommand;
71
69
  export const loadInsights = async () => (await import('../commands/insights.js')).registerInsightsCommand;
72
70
  export const loadPerf = async () => (await import('../commands/perf.js')).registerPerfCommand;
73
- // Thin deprecated alias of `agents insights mix` — no second mix implementation.
74
- export const loadTrends = async () => (await import('../commands/trends.js')).registerTrendsCommand;
75
71
  export const loadPty = async () => (await import('../commands/pty.js')).registerPtyCommands;
76
72
  export const loadTmux = async () => (await import('../commands/tmux.js')).registerTmuxCommands;
77
73
  export const loadWatchdog = async () => (await import('../commands/watchdog.js')).registerWatchdogCommand;
@@ -91,17 +87,12 @@ export const loadMessage = async () => (await import('../commands/message.js')).
91
87
  export const loadSend = async () => (await import('../commands/send.js')).registerSendCommand;
92
88
  export const loadFeed = async () => (await import('../commands/feed.js')).registerFeedCommand;
93
89
  export const loadMailboxes = async () => (await import('../commands/mailboxes.js')).registerMailboxesCommand;
94
- export const loadServe = async () => (await import('../commands/serve.js')).registerServeCommand;
95
- // Registers the `artifacts` group (with `share` + `setup` under it) AND the
96
- // top-level `unshare` alias — see commands/artifacts.ts.
90
+ // Registers the `artifacts` group (`share` + `setup` + nested `unshare`).
97
91
  export const loadArtifacts = async () => (await import('../commands/artifacts.js')).registerArtifactsCommands;
98
- export const loadAudit = async () => (await import('../commands/audit.js')).registerAuditCommands;
99
92
  export const loadWebhooks = async () => (await import('../commands/webhook.js')).registerWebhooksCommand;
100
93
  export const loadHumans = async () => (await import('../commands/humans.js')).registerHumansCommands;
101
94
  export const loadAccounts = async () => (await import('../commands/accounts.js')).registerAccountsCommand;
102
95
  export const loadDaemon = async () => (await import('../commands/daemon.js')).registerDaemonCommand;
103
- export const loadAuth = async () => (await import('../commands/auth.js')).registerAuthCommand;
104
- export const loadOrg = async () => (await import('../commands/org.js')).registerOrgCommand;
105
96
  /**
106
97
  * Commands whose modules pull in the SQLite-backed session/cloud stack. They are
107
98
  * registered AFTER `applyGlobalHelpConventions` (mirroring main's order: help
@@ -116,7 +107,6 @@ export const LAZY_COMMAND_NAMES = new Set([
116
107
  'teams',
117
108
  'cloud',
118
109
  'message',
119
- 'serve',
120
110
  ]);
121
111
  /**
122
112
  * User-typed top-level command name -> ordered list of module loaders to run.
@@ -134,8 +124,6 @@ export const LAZY_COMMAND_NAMES = new Set([
134
124
  */
135
125
  export const COMMAND_LOADERS = {
136
126
  accounts: [loadAccounts],
137
- auth: [loadAuth],
138
- org: [loadOrg],
139
127
  view: [loadView],
140
128
  inspect: [loadInspect],
141
129
  feedback: [loadFeedback],
@@ -175,20 +163,17 @@ export const COMMAND_LOADERS = {
175
163
  trash: [loadTrash],
176
164
  restore: [loadRestore],
177
165
  doctor: [loadDoctor],
178
- apply: [loadApply],
179
166
  route: [loadRoute],
180
167
  harness: [loadHarness],
181
168
  harnesses: [loadHarness],
182
169
  secrets: [loadSecrets],
183
170
  menubar: [loadMenubar],
184
- beta: [loadBeta],
185
171
  sync: [loadSync],
186
172
  'refresh-rules': [loadRefreshRules],
187
173
  factory: [loadFactory],
188
174
  usage: [loadUsage],
189
175
  insights: [loadInsights],
190
176
  perf: [loadPerf],
191
- trends: [loadTrends],
192
177
  pty: [loadPty],
193
178
  tmux: [loadTmux],
194
179
  watchdog: [loadWatchdog],
@@ -218,12 +203,7 @@ export const COMMAND_LOADERS = {
218
203
  feed: [loadFeed],
219
204
  mailboxes: [loadMailboxes],
220
205
  mailbox: [loadMailboxes],
221
- serve: [loadServe],
222
206
  artifacts: [loadArtifacts],
223
- // `unshare` is a top-level convenience alias of `artifacts share delete` (see
224
- // commands/share.ts) — same module, registered as its own program.command().
225
- unshare: [loadArtifacts],
226
- audit: [loadAudit],
227
207
  webhooks: [loadWebhooks],
228
208
  humans: [loadHumans],
229
209
  daemon: [loadDaemon],
@@ -30,16 +30,8 @@ export declare function parseBundleKey(raw: string): {
30
30
  bundle: string;
31
31
  key: string;
32
32
  };
33
- /**
34
- * Named accounts that `set-default` / `switch` can pin for this harness —
35
- * capped to the caller's plan tier (RUSH-2424). Downgrading never deletes a
36
- * credential: an over-cap account simply falls out of this list (see
37
- * {@link dormantAccountsForHarness}) and stops being a switch/rotation target
38
- * until the plan is upgraded.
39
- */
33
+ /** Named accounts that `set-default` / `switch` can pin for this harness. */
40
34
  export declare function listSwitchableAccounts(agent: AgentId): Promise<UnifiedAccount[]>;
41
- /** Accounts registered for this harness beyond the plan's cap — kept, never deleted, excluded from switch/rotation, reactivated by an upgrade. */
42
- export declare function dormantAccountsForHarness(agent: AgentId): Promise<UnifiedAccount[]>;
43
35
  /**
44
36
  * Pin the per-harness default. Shared by `accounts set-default` and `accounts switch`.
45
37
  * Provider accounts must authenticate the harness; native accounts must belong to it.
@@ -17,7 +17,6 @@ import { discoverNativeAccounts } from '../lib/account-catalog.js';
17
17
  import { readAndResolveBundleEnv } from '../lib/secrets/bundles.js';
18
18
  import { getAccountProvider, listAccountProviders } from '../lib/account-provider-registry.js';
19
19
  import { accountBindings, addAccount, addNativeAccount, bindAccount, findAccount, findUnifiedAccount, inspectAccount, listNativeAccounts, readAccountRegistry, removeAccount, renameAccount, setAccountSecret, unbindAccount } from '../lib/account-registry.js';
20
- import { accountCapForTier, getTier } from '../lib/entitlement.js';
21
20
  function parseInstallation(raw) {
22
21
  const at = raw.lastIndexOf('@');
23
22
  if (at < 1 || at === raw.length - 1)
@@ -74,8 +73,8 @@ function secretFromBundle(raw) {
74
73
  const { bundle, key } = parseBundleKey(raw);
75
74
  return readAndResolveBundleEnv(bundle, { keys: [key], keyMode: 'storage', agentOnly: true, caller: 'accounts import' }).env[key];
76
75
  }
77
- function publicAccount(account, dormant) {
78
- return { kind: 'provider', id: account.id, name: account.name, provider: account.provider, auth: account.auth, baseUrl: account.baseUrl, policy: account.policy, secretPresent: account.secretPresent, dormant };
76
+ function publicAccount(account) {
77
+ return { kind: 'provider', id: account.id, name: account.name, provider: account.provider, auth: account.auth, baseUrl: account.baseUrl, policy: account.policy, secretPresent: account.secretPresent };
79
78
  }
80
79
  async function printAccounts(json, fleet = false) {
81
80
  if (fleet)
@@ -83,16 +82,12 @@ async function printAccounts(json, fleet = false) {
83
82
  const records = Object.values(readAccountRegistry().accounts).sort((a, b) => a.name.localeCompare(b.name));
84
83
  const discovered = await discoverNativeAccounts();
85
84
  const savedNative = listNativeAccounts(readMeta());
86
- const dormantIds = new Set();
87
- for (const agent of ALL_AGENT_IDS)
88
- for (const account of await dormantAccountsForHarness(agent))
89
- dormantIds.add(account.id);
90
85
  const native = discovered.map(row => {
91
86
  const saved = savedNative.find(account => account.agent === row.agent && account.identityKey === row.id);
92
- return { ...row, name: saved?.name, id: saved?.id ?? row.id, dormant: !!saved && dormantIds.has(saved.id) };
87
+ return { ...row, name: saved?.name, id: saved?.id ?? row.id };
93
88
  });
94
89
  if (json) {
95
- console.log(JSON.stringify([...records.map(account => publicAccount(inspectAccount(account.name), dormantIds.has(account.id))), ...native], null, 2));
90
+ console.log(JSON.stringify([...records.map(account => publicAccount(inspectAccount(account.name))), ...native], null, 2));
96
91
  return;
97
92
  }
98
93
  console.log(chalk.bold('Provider account bundles\n'));
@@ -100,14 +95,13 @@ async function printAccounts(json, fleet = false) {
100
95
  console.log(chalk.gray(" None. Add one with 'agents accounts add <name> --provider <provider> --auth <type>'."));
101
96
  for (const account of records) {
102
97
  const present = inspectAccount(account.name).secretPresent ? chalk.green('ready') : chalk.red('missing on this device');
103
- const dormantSuffix = dormantIds.has(account.id) ? chalk.gray(' — dormant (upgrade to reactivate)') : '';
104
- console.log(` ${chalk.cyan(account.name)} ${account.provider} ${account.auth} ${present}${dormantSuffix}`);
98
+ console.log(` ${chalk.cyan(account.name)} ${account.provider} ${account.auth} ${present}`);
105
99
  }
106
100
  console.log(chalk.bold('\nNative harness logins\n'));
107
101
  if (!native.length)
108
102
  console.log(chalk.gray(' No signed-in native accounts found.'));
109
103
  for (const account of native)
110
- console.log(` ${account.name ? `${chalk.cyan(account.name)} · ` : ''}${account.display} ${account.agent} ${account.versions.join(', ')}${account.dormant ? chalk.gray(' — dormant (upgrade to reactivate)') : ''}`);
104
+ console.log(` ${account.name ? `${chalk.cyan(account.name)} · ` : ''}${account.display} ${account.agent} ${account.versions.join(', ')}`);
111
105
  }
112
106
  function parseAuth(raw) {
113
107
  if (raw === 'api-key' || raw === 'setup-token' || raw === 'bearer-token')
@@ -131,7 +125,7 @@ function providerAuthenticatesHarness(provider, auth, agent) {
131
125
  throw err;
132
126
  }
133
127
  }
134
- /** Every account (native + provider) registered/usable for this harness, oldest-first by name — the full set a plan-tier cap slices into active vs dormant. */
128
+ /** Every account (native + provider) registered/usable for this harness, oldest-first by name. */
135
129
  function accountsForHarness(agent) {
136
130
  const meta = readMeta();
137
131
  const native = listNativeAccounts(meta).filter(account => account.agent === agent);
@@ -140,52 +134,9 @@ function accountsForHarness(agent) {
140
134
  .map(account => ({ ...account, kind: 'provider' }));
141
135
  return [...native, ...providers].sort((a, b) => a.name.localeCompare(b.name));
142
136
  }
143
- /**
144
- * Named accounts that `set-default` / `switch` can pin for this harness —
145
- * capped to the caller's plan tier (RUSH-2424). Downgrading never deletes a
146
- * credential: an over-cap account simply falls out of this list (see
147
- * {@link dormantAccountsForHarness}) and stops being a switch/rotation target
148
- * until the plan is upgraded.
149
- */
137
+ /** Named accounts that `set-default` / `switch` can pin for this harness. */
150
138
  export async function listSwitchableAccounts(agent) {
151
- const cap = accountCapForTier(await getTier());
152
- return accountsForHarness(agent).slice(0, cap);
153
- }
154
- /** Accounts registered for this harness beyond the plan's cap — kept, never deleted, excluded from switch/rotation, reactivated by an upgrade. */
155
- export async function dormantAccountsForHarness(agent) {
156
- const cap = accountCapForTier(await getTier());
157
- return accountsForHarness(agent).slice(cap);
158
- }
159
- function accountCapRefusalMessage(agent, tier, cap) {
160
- if (!tier.isPaid)
161
- return `free plan is capped at 3 ${agent} accounts (3/3). agents upgrade — up to 10 per harness.`;
162
- return `${tier.tierName} plan is capped at ${cap} ${agent} accounts (${cap}/${cap}).`;
163
- }
164
- /**
165
- * Refuse BEFORE any write when adding a new account would push one of
166
- * `harnesses` over the caller's plan cap. Returns the tier/cap/pre-add counts
167
- * so the caller can print the exactly-at-cap notice after a successful write.
168
- */
169
- async function assertAccountCapacityFor(harnesses) {
170
- const tier = await getTier();
171
- const cap = accountCapForTier(tier);
172
- const existing = new Map();
173
- for (const harness of harnesses)
174
- existing.set(harness, accountsForHarness(harness).length);
175
- const capped = harnesses.find(harness => (existing.get(harness) ?? 0) >= cap);
176
- if (capped)
177
- throw new Error(accountCapRefusalMessage(capped, tier, cap));
178
- return { tier, cap, existing };
179
- }
180
- /** One-line, non-blocking notice for every harness a free-tier add just brought to exactly its cap. */
181
- function printCapNoticesFor(harnesses, tier, cap, existing) {
182
- if (tier.isPaid)
183
- return;
184
- for (const harness of harnesses) {
185
- if ((existing.get(harness) ?? 0) + 1 === cap) {
186
- console.log(chalk.yellow(`${cap}/${cap} ${harness} accounts on the free plan. agents upgrade — up to 10 per harness.`));
187
- }
188
- }
139
+ return accountsForHarness(agent);
189
140
  }
190
141
  /**
191
142
  * Pin the per-harness default. Shared by `accounts set-default` and `accounts switch`.
@@ -279,16 +230,10 @@ export function registerAccountsCommand(program) {
279
230
  const provider = getAccountProvider(o.provider);
280
231
  if (!provider.authKinds.includes(auth))
281
232
  throw new Error(`Provider '${provider.provider}' does not support ${auth}. Supported: ${provider.authKinds.join(', ')}.`);
282
- // A provider account may authenticate more than one harness (e.g. openrouter);
283
- // it counts toward every harness it can authenticate, so refuse before any
284
- // write if adding it would push ANY of those harnesses over the plan cap.
285
- const harnesses = ALL_AGENT_IDS.filter(h => providerAuthenticatesHarness(provider.provider, auth, h));
286
- const { tier, cap, existing } = await assertAccountCapacityFor(harnesses);
287
233
  const secret = o.fromSecrets ? secretFromBundle(o.fromSecrets) : await password({ message: `Enter ${provider.provider} ${auth} for '${name}':` });
288
234
  const account = addAccount(name, provider.provider, auth, secret, undefined, { baseUrl: o.baseUrl });
289
235
  console.log(chalk.green(`Added ${account.provider} ${account.auth} account '${account.name}'.`));
290
236
  console.log(chalk.gray(`Secret bundle '${account.name}' is the account and uses policy never, so agent launches never request Touch ID.`));
291
- printCapNoticesFor(harnesses, tier, cap, existing);
292
237
  });
293
238
  accounts.command('set-key <name>')
294
239
  .description('Rotate an account credential without changing its identity')
@@ -306,22 +251,12 @@ export function registerAccountsCommand(program) {
306
251
  const unified = findUnifiedAccount(name, meta);
307
252
  if (!unified)
308
253
  throw new Error(`Unknown account '${name}'.`);
309
- const harnesses = unified.kind === 'provider'
310
- ? ALL_AGENT_IDS.filter(h => providerAuthenticatesHarness(unified.provider, unified.auth, h))
311
- : [unified.agent];
312
- let dormant = false;
313
- for (const harness of harnesses) {
314
- if ((await dormantAccountsForHarness(harness)).some(a => a.id === unified.id)) {
315
- dormant = true;
316
- break;
317
- }
318
- }
319
254
  const account = unified.kind === 'provider'
320
- ? { ...publicAccount(inspectAccount(unified.name), dormant), custody: 'agents secrets (policy never)', attached: accountBindings(unified.id, meta) }
321
- : { ...unified, dormant, custody: `${unified.agent} (not stored by agents-cli)`, attached: accountBindings(unified.id, meta) };
255
+ ? { ...publicAccount(inspectAccount(unified.name)), custody: 'agents secrets (policy never)', attached: accountBindings(unified.id, meta) }
256
+ : { ...unified, custody: `${unified.agent} (not stored by agents-cli)`, attached: accountBindings(unified.id, meta) };
322
257
  if (o.json || command.optsWithGlobals().json)
323
258
  return console.log(JSON.stringify(account, null, 2));
324
- console.log(chalk.bold(account.name) + (account.dormant ? chalk.gray(' — dormant (upgrade to reactivate)') : ''));
259
+ console.log(chalk.bold(account.name));
325
260
  console.log(` kind: ${account.kind}`);
326
261
  console.log(` id: ${account.id}`);
327
262
  console.log(` custody: ${account.custody}`);
@@ -340,12 +275,10 @@ export function registerAccountsCommand(program) {
340
275
  .description('Name a signed-in native installation without copying its OAuth credentials')
341
276
  .action(async (source, name) => {
342
277
  const identity = await nativeIdentityFromSource(source);
343
- const { tier, cap, existing } = await assertAccountCapacityFor([identity.agent]);
344
278
  const account = addNativeAccount(name, identity.agent, identity.identityKey, identity.identityLabel, identity.scope);
345
279
  console.log(chalk.green(`Named ${source} as ${account.name}.`));
346
280
  if (account.scope === 'device')
347
281
  console.log(chalk.gray(`${identity.agent} authentication is device-scoped; attach '${account.name}' to '${identity.agent}', not an individual version.`));
348
- printCapNoticesFor([identity.agent], tier, cap, existing);
349
282
  });
350
283
  accounts.command('attach <account> <target>')
351
284
  .description('Attach a named account to a native installation or custom harness')
@@ -383,17 +316,6 @@ export function registerAccountsCommand(program) {
383
316
  // Provider account: it must be able to authenticate the target's harness.
384
317
  getAccountProvider(account.provider).envFor(targetAgent, account.auth);
385
318
  }
386
- // Defense-in-depth cap check: by the point we get here `account` already
387
- // authenticates/belongs to `targetAgent` (validated above), so it is
388
- // already counted in accountsForHarness(targetAgent) from its `add`/`name`
389
- // time — this can only refuse if that invariant is ever violated, never a
390
- // normal re-attach of an account the harness is already at cap with.
391
- const tier = await getTier();
392
- const cap = accountCapForTier(tier);
393
- const alreadyCounted = accountsForHarness(targetAgent).some(existing => existing.id === account.id);
394
- const otherCount = accountsForHarness(targetAgent).length - (alreadyCounted ? 1 : 0);
395
- if (otherCount >= cap)
396
- throw new Error(accountCapRefusalMessage(targetAgent, tier, cap));
397
319
  bindAccount(name, target);
398
320
  console.log(chalk.green(`Attached ${account.name} to ${target}.`));
399
321
  });
@@ -1,25 +1,22 @@
1
1
  /**
2
- * `agents apply` (alias `ag apply`) — reconcile the whole fleet to a declared
3
- * profile in one command: install agents-cli + agents and sync config. Native
2
+ * `agents fleet apply` / `agents devices apply` — reconcile the whole fleet
3
+ * to a declared profile: install agents-cli + agents and sync config. Native
4
4
  * harness logins remain device-local; portable provider credentials move only
5
5
  * through explicit `agents accounts sync`.
6
6
  *
7
7
  * The manifest is the `fleet:` block of any `-f` file (default `agents.yaml`).
8
+ * Top-level `agents apply` is retired (RUSH-2981).
8
9
  */
9
10
  import { Command } from 'commander';
10
11
  /** padEnd on the visible width, ignoring chalk color codes. Exported for tests. */
11
12
  export declare function stripPad(s: string, width: number): string;
12
13
  /**
13
- * Attach the reconcile options + action to a command node. Shared by the
14
- * top-level `agents apply` and its `agents fleet apply` alias so the two can
15
- * never drift — identical flags, identical engine.
14
+ * Attach the reconcile options + action to a command node.
16
15
  */
17
16
  export declare function configureApplyCommand(cmd: Command): Command;
18
- export declare function registerApplyCommand(program: Command): void;
19
17
  /**
20
- * Surface `agents apply` under the fleet command tree as `agents fleet apply`
21
- * (and `agents devices apply`). Same reconcile engine as the top-level command
22
- * a discoverability alias for users who reach for `fleet` as the verb. Kept in
23
- * lockstep via {@link configureApplyCommand}.
18
+ * Canonical surface: `agents fleet apply` / `agents devices apply`.
19
+ * Top-level `agents apply` is retired (RUSH-2981) not a noun, already nested
20
+ * here. Same reconcile engine, kept in lockstep via {@link configureApplyCommand}.
24
21
  */
25
22
  export declare function registerFleetApplyAlias(devicesCmd: Command): void;