@phnx-labs/agents-cli 1.20.77 → 1.20.82

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 (212) hide show
  1. package/CHANGELOG.md +589 -0
  2. package/README.md +27 -25
  3. package/dist/bin/agents +0 -0
  4. package/dist/commands/activity.d.ts +9 -0
  5. package/dist/commands/activity.js +153 -24
  6. package/dist/commands/apply.js +11 -4
  7. package/dist/commands/cli.js +1 -1
  8. package/dist/commands/commands.d.ts +1 -1
  9. package/dist/commands/commands.js +7 -6
  10. package/dist/commands/defaults.js +3 -11
  11. package/dist/commands/doctor.d.ts +74 -0
  12. package/dist/commands/doctor.js +718 -40
  13. package/dist/commands/events.js +1 -1
  14. package/dist/commands/exec.js +53 -8
  15. package/dist/commands/feed.d.ts +4 -0
  16. package/dist/commands/feed.js +87 -11
  17. package/dist/commands/harness.js +2 -2
  18. package/dist/commands/hooks.js +1 -1
  19. package/dist/commands/import.js +8 -4
  20. package/dist/commands/logs.js +6 -0
  21. package/dist/commands/models.js +1 -1
  22. package/dist/commands/packages.js +6 -6
  23. package/dist/commands/permissions.js +1 -1
  24. package/dist/commands/profiles.js +1 -1
  25. package/dist/commands/pull.d.ts +1 -1
  26. package/dist/commands/pull.js +4 -4
  27. package/dist/commands/repo.d.ts +78 -0
  28. package/dist/commands/repo.js +195 -11
  29. package/dist/commands/routines.js +16 -4
  30. package/dist/commands/rules.js +13 -12
  31. package/dist/commands/secrets.d.ts +3 -1
  32. package/dist/commands/secrets.js +108 -34
  33. package/dist/commands/sessions-browser.d.ts +45 -3
  34. package/dist/commands/sessions-browser.js +118 -12
  35. package/dist/commands/sessions-export.d.ts +3 -0
  36. package/dist/commands/sessions-export.js +13 -10
  37. package/dist/commands/sessions-inject.d.ts +7 -4
  38. package/dist/commands/sessions-inject.js +18 -18
  39. package/dist/commands/sessions-picker.d.ts +15 -1
  40. package/dist/commands/sessions-picker.js +90 -7
  41. package/dist/commands/sessions-resume.d.ts +16 -0
  42. package/dist/commands/sessions-resume.js +14 -13
  43. package/dist/commands/sessions.d.ts +126 -1
  44. package/dist/commands/sessions.js +365 -42
  45. package/dist/commands/set.d.ts +15 -0
  46. package/dist/commands/set.js +79 -0
  47. package/dist/commands/setup-fleet.d.ts +21 -0
  48. package/dist/commands/setup-fleet.js +201 -0
  49. package/dist/commands/setup-secrets.d.ts +20 -0
  50. package/dist/commands/setup-secrets.js +223 -0
  51. package/dist/commands/setup.js +17 -3
  52. package/dist/commands/skills.js +1 -1
  53. package/dist/commands/ssh.js +9 -1
  54. package/dist/commands/sync.js +7 -2
  55. package/dist/commands/teams-picker.js +0 -1
  56. package/dist/commands/teams.d.ts +33 -1
  57. package/dist/commands/teams.js +208 -44
  58. package/dist/commands/versions.js +10 -5
  59. package/dist/commands/view.d.ts +1 -0
  60. package/dist/commands/view.js +11 -0
  61. package/dist/index.js +8 -13
  62. package/dist/lib/acp/harnesses.js +0 -7
  63. package/dist/lib/activity.d.ts +156 -0
  64. package/dist/lib/activity.js +282 -0
  65. package/dist/lib/agents.d.ts +9 -23
  66. package/dist/lib/agents.js +72 -100
  67. package/dist/lib/auto-pull.d.ts +16 -8
  68. package/dist/lib/auto-pull.js +23 -28
  69. package/dist/lib/browser/service.js +3 -0
  70. package/dist/lib/browser/types.d.ts +7 -0
  71. package/dist/lib/capabilities.js +6 -2
  72. package/dist/lib/crabbox/lease.js +2 -2
  73. package/dist/lib/crabbox/setup-copy.d.ts +4 -4
  74. package/dist/lib/crabbox/setup-copy.js +4 -4
  75. package/dist/lib/daemon.d.ts +26 -40
  76. package/dist/lib/daemon.js +63 -123
  77. package/dist/lib/devices/fleet-divergence.d.ts +101 -0
  78. package/dist/lib/devices/fleet-divergence.js +188 -0
  79. package/dist/lib/devices/fleet-inventory.d.ts +19 -0
  80. package/dist/lib/devices/fleet-inventory.js +57 -0
  81. package/dist/lib/devices/fleet.d.ts +1 -1
  82. package/dist/lib/devices/fleet.js +1 -1
  83. package/dist/lib/devices/health-report.d.ts +10 -2
  84. package/dist/lib/devices/health-report.js +32 -1
  85. package/dist/lib/doctor-diff.d.ts +20 -0
  86. package/dist/lib/doctor-diff.js +6 -1
  87. package/dist/lib/drift.d.ts +31 -11
  88. package/dist/lib/drift.js +58 -7
  89. package/dist/lib/events.d.ts +8 -1
  90. package/dist/lib/events.js +10 -1
  91. package/dist/lib/exec.js +35 -8
  92. package/dist/lib/feed-post.d.ts +28 -1
  93. package/dist/lib/feed-post.js +110 -2
  94. package/dist/lib/fleet/apply.js +8 -1
  95. package/dist/lib/fleet/auth-sync.d.ts +18 -1
  96. package/dist/lib/fleet/auth-sync.js +25 -11
  97. package/dist/lib/fleet/remote-login.js +5 -0
  98. package/dist/lib/git.d.ts +27 -14
  99. package/dist/lib/git.js +138 -43
  100. package/dist/lib/hooks.d.ts +42 -0
  101. package/dist/lib/hooks.js +137 -93
  102. package/dist/lib/hosts/dispatch.d.ts +28 -1
  103. package/dist/lib/hosts/dispatch.js +33 -5
  104. package/dist/lib/hosts/option.js +2 -2
  105. package/dist/lib/hosts/passthrough.d.ts +22 -1
  106. package/dist/lib/hosts/passthrough.js +238 -13
  107. package/dist/lib/hosts/remote-cmd.js +2 -0
  108. package/dist/lib/hosts/run-target.d.ts +2 -0
  109. package/dist/lib/hosts/run-target.js +1 -0
  110. package/dist/lib/hq/floor.d.ts +1 -1
  111. package/dist/lib/hq/floor.js +6 -1
  112. package/dist/lib/mcp.js +3 -71
  113. package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +2 -0
  114. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  115. package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
  116. package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +15 -2
  117. package/dist/lib/menubar/notify-desktop.d.ts +15 -0
  118. package/dist/lib/menubar/notify-desktop.js +41 -5
  119. package/dist/lib/merged-resources.d.ts +11 -0
  120. package/dist/{commands/resources.js → lib/merged-resources.js} +11 -19
  121. package/dist/lib/permissions.d.ts +0 -32
  122. package/dist/lib/permissions.js +4 -157
  123. package/dist/lib/picker.js +1 -1
  124. package/dist/lib/platform/process.d.ts +16 -5
  125. package/dist/lib/platform/process.js +54 -0
  126. package/dist/lib/plugins.d.ts +0 -8
  127. package/dist/lib/plugins.js +4 -110
  128. package/dist/lib/project-resources.js +4 -1
  129. package/dist/lib/refresh.d.ts +5 -3
  130. package/dist/lib/refresh.js +7 -5
  131. package/dist/lib/resources/commands.js +4 -1
  132. package/dist/lib/resources/mcp.js +0 -4
  133. package/dist/lib/resources/permissions.d.ts +1 -1
  134. package/dist/lib/resources/permissions.js +1 -5
  135. package/dist/lib/resources/rules.js +4 -1
  136. package/dist/lib/resources/skills.js +4 -1
  137. package/dist/lib/resources/subagents.js +4 -0
  138. package/dist/lib/resources/types.d.ts +1 -1
  139. package/dist/lib/routines.d.ts +20 -0
  140. package/dist/lib/routines.js +6 -0
  141. package/dist/lib/run-defaults.d.ts +1 -0
  142. package/dist/lib/run-defaults.js +9 -0
  143. package/dist/lib/runner.d.ts +0 -12
  144. package/dist/lib/runner.js +71 -29
  145. package/dist/lib/sandbox.js +0 -16
  146. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  147. package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +2 -0
  148. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  149. package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
  150. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +13 -1
  151. package/dist/lib/secrets/agent.d.ts +18 -0
  152. package/dist/lib/secrets/agent.js +131 -17
  153. package/dist/lib/secrets/audit.d.ts +46 -0
  154. package/dist/lib/secrets/audit.js +56 -0
  155. package/dist/lib/secrets/bundles.d.ts +12 -9
  156. package/dist/lib/secrets/bundles.js +47 -31
  157. package/dist/lib/secrets/index.d.ts +2 -1
  158. package/dist/lib/secrets/index.js +8 -2
  159. package/dist/lib/secrets/remote.d.ts +75 -2
  160. package/dist/lib/secrets/remote.js +139 -5
  161. package/dist/lib/secrets/scope.d.ts +26 -0
  162. package/dist/lib/secrets/scope.js +29 -0
  163. package/dist/lib/secrets/session-store.d.ts +10 -0
  164. package/dist/lib/secrets/session-store.js +64 -11
  165. package/dist/lib/secrets/sync.js +3 -3
  166. package/dist/lib/session/active.d.ts +117 -26
  167. package/dist/lib/session/active.js +199 -53
  168. package/dist/lib/session/actor-sidecar.d.ts +23 -0
  169. package/dist/lib/session/actor-sidecar.js +101 -0
  170. package/dist/lib/session/db.d.ts +41 -1
  171. package/dist/lib/session/db.js +173 -8
  172. package/dist/lib/session/digest.js +126 -21
  173. package/dist/lib/session/discover.d.ts +19 -2
  174. package/dist/lib/session/discover.js +32 -11
  175. package/dist/lib/session/hook-sessions.d.ts +9 -0
  176. package/dist/lib/session/hook-sessions.js +57 -8
  177. package/dist/lib/session/origin-machine.d.ts +18 -0
  178. package/dist/lib/session/origin-machine.js +34 -0
  179. package/dist/lib/session/pid-registry.d.ts +13 -0
  180. package/dist/lib/session/render.d.ts +2 -0
  181. package/dist/lib/session/render.js +1 -1
  182. package/dist/lib/session/state.d.ts +1 -0
  183. package/dist/lib/session/state.js +1 -1
  184. package/dist/lib/session/sync/config.js +2 -2
  185. package/dist/lib/session/types.d.ts +16 -0
  186. package/dist/lib/smart-launch.d.ts +86 -0
  187. package/dist/lib/smart-launch.js +172 -0
  188. package/dist/lib/staleness/detectors/commands.js +2 -2
  189. package/dist/lib/staleness/detectors/permissions.js +1 -45
  190. package/dist/lib/staleness/writers/commands.js +4 -5
  191. package/dist/lib/staleness/writers/hooks.js +1 -1
  192. package/dist/lib/startup/command-registry.d.ts +1 -2
  193. package/dist/lib/startup/command-registry.js +2 -4
  194. package/dist/lib/subagents-registry.js +5 -12
  195. package/dist/lib/subagents.d.ts +0 -10
  196. package/dist/lib/subagents.js +0 -12
  197. package/dist/lib/teams/agents.d.ts +0 -2
  198. package/dist/lib/teams/agents.js +10 -27
  199. package/dist/lib/teams/index.d.ts +1 -1
  200. package/dist/lib/teams/index.js +1 -1
  201. package/dist/lib/types.d.ts +17 -9
  202. package/dist/lib/usage.d.ts +28 -5
  203. package/dist/lib/usage.js +271 -8
  204. package/dist/lib/versions.js +7 -1
  205. package/dist/lib/watchdog/watchdog.d.ts +1 -1
  206. package/dist/lib/watchdog/watchdog.js +25 -13
  207. package/package.json +1 -2
  208. package/dist/commands/check.d.ts +0 -15
  209. package/dist/commands/check.js +0 -180
  210. package/dist/commands/resources.d.ts +0 -5
  211. package/dist/lib/secrets/account-token.d.ts +0 -20
  212. package/dist/lib/secrets/account-token.js +0 -64
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * Core agent configuration and detection module.
3
3
  *
4
- * Defines the canonical registry of all supported AI coding agents (Claude, Codex,
5
- * Gemini, Cursor, OpenCode, OpenClaw, Copilot, Amp, Kiro, Goose, Grok) with their
6
- * CLI commands, config paths, capability flags, and MCP integration points.
4
+ * Defines the canonical registry of current and legacy AI coding agents (Claude,
5
+ * Codex, Gemini, Cursor, OpenCode, OpenClaw, Copilot, Amp, Kiro, Goose, Grok)
6
+ * with their CLI commands, config paths, capability flags, and MCP integration
7
+ * points.
7
8
  *
8
9
  * Provides functions for detecting installed CLIs, resolving version-managed binaries,
9
10
  * reading account/auth info, and managing MCP server registrations across agents.
@@ -256,13 +257,15 @@ export const AGENTS = {
256
257
  nativeAgentsSkillsDir: true,
257
258
  // Google retired the Gemini CLI (announced at Google I/O 2026, May 19); the `gemini`
258
259
  // command stopped serving free/Pro/Ultra requests on June 18, 2026. Antigravity CLI
259
- // (`agy`) is the official successor. See warnAgentDeprecated() for the surfaced warning.
260
+ // (`agy`) is the official successor. Hard deprecation keeps legacy Gemini
261
+ // sessions/config parseable while blocking install/import/sync.
260
262
  deprecated: {
261
263
  by: 'Google',
262
264
  date: 'June 18, 2026',
263
265
  reason: 'The Gemini CLI was retired for free, Pro, and Ultra tiers and no longer serves requests (announced at Google I/O 2026 on May 19).',
264
266
  replacement: 'antigravity',
265
267
  url: 'https://developers.googleblog.com/an-important-update-transitioning-gemini-cli-to-antigravity-cli/',
268
+ hard: true,
266
269
  },
267
270
  // gemini hooks: shipped in v0.26.0 (Jan 2026); older binaries silently ignore the `hooks` key.
268
271
  // extensions: gemini-extension.json bundles shipped in v0.8.0; custom subagents in v0.36.0.
@@ -697,58 +700,18 @@ export const AGENTS = {
697
700
  rulesImports: false,
698
701
  },
699
702
  },
700
- // ForgeCode (`forge`) from Tailcall. It reads AGENTS.md project rules,
701
- // SKILL.md directories, and MCP servers from `.mcp.json` files.
702
- forge: {
703
- id: 'forge',
704
- name: 'ForgeCode',
705
- color: 'greenBright',
706
- cliCommand: 'forge',
707
- npmPackage: '',
708
- installScript: 'curl -fsSL https://forgecode.dev/cli | sh',
709
- configDir: path.join(HOME, '.forge'),
710
- commandsDir: path.join(HOME, '.forge', 'commands'),
711
- commandsSubdir: 'commands',
712
- skillsDir: path.join(HOME, '.forge', 'skills'),
713
- hooksDir: 'hooks',
714
- instructionsFile: 'AGENTS.md',
715
- format: 'markdown',
716
- variableSyntax: '$ARGUMENTS',
717
- supportsHooks: false,
718
- // Commands: ForgeCode reads Markdown slash commands from `~/.forge/commands/<name>.md`
719
- // (also the shared `~/.agents/commands/`); the filename is the command name.
720
- // Subagents: named `.md` agent definitions with YAML frontmatter under
721
- // `~/.forge/agents/<name>.md` — same Markdown+frontmatter shape as Droid/Copilot
722
- // (no `color` field), so transformSubagentForForge aliases transformSubagentForDroid.
723
- // See https://forgecode.dev/docs/commands/ and /docs/agent-definition-guide/.
724
- capabilities: {
725
- hooks: false,
726
- mcp: true,
727
- mcpHttp: true,
728
- mcpHeaders: false,
729
- // Permissions: ~/.forge/permissions.yaml (or $FORGE_CONFIG/permissions.yaml)
730
- // carries ordered operation-family glob policies. It is active only when
731
- // `.forge.toml` sets `restricted = true`; MCP tools bypass this file.
732
- allowlist: true,
733
- skills: true,
734
- commands: true,
735
- plugins: false,
736
- subagents: true,
737
- rules: { file: 'AGENTS.md' },
738
- workflows: false,
739
- memory: false,
740
- modes: ['edit'],
741
- rulesImports: false,
742
- },
743
- },
744
703
  };
745
- /** All registered agent IDs derived from the AGENTS registry. */
704
+ /** All current and legacy agent IDs derived from the AGENTS registry. */
746
705
  export const ALL_AGENT_IDS = Object.keys(AGENTS);
706
+ /** Agents retained only for legacy reads, not install/import/sync targets. */
707
+ export const HARD_DEPRECATED_AGENT_IDS = ALL_AGENT_IDS.filter((id) => AGENTS[id].deprecated?.hard);
708
+ /** Agents that can receive managed installs, imports, and resource sync writes. */
709
+ export const MANAGED_AGENT_IDS = ALL_AGENT_IDS.filter((id) => !AGENTS[id].deprecated?.hard);
747
710
  /**
748
711
  * A self-updating agent is a single global binary installed by an official
749
712
  * `curl … | sh` / `brew install` script that carries NO version token — the
750
713
  * installer can only ever fetch the *current* release, and the binary then keeps
751
- * itself up to date in place (droid, grok, antigravity, cursor, hermes, forge,
714
+ * itself up to date in place (droid, grok, antigravity, cursor, hermes,
752
715
  * kiro, goose). There is no semver to pin, so agents-cli must not model these as
753
716
  * having multiple installable version-homes the way it does for npm-packaged
754
717
  * agents (claude, codex, kimi, …).
@@ -768,6 +731,9 @@ export function isSelfUpdatingAgent(agent) {
768
731
  const cfg = AGENTS[agent];
769
732
  return !cfg.npmPackage && !!cfg.installScript && !cfg.installScript.includes('VERSION');
770
733
  }
734
+ export function isAgentHardDeprecated(agent) {
735
+ return AGENTS[agent].deprecated?.hard === true;
736
+ }
771
737
  // Capability-filtered agent lists used to live here as `*_CAPABLE_AGENTS`
772
738
  // constants. They were a frequent source of silent-skip bugs (e.g. grok
773
739
  // rules sync gated on `COMMANDS_CAPABLE_AGENTS`). Use `capableAgents(cap)`
@@ -1404,43 +1370,6 @@ export function isClaudeCredentialFileBlank(base, platform = process.platform) {
1404
1370
  return false;
1405
1371
  }
1406
1372
  }
1407
- /**
1408
- * Whether a Claude version home holds its OWN on-disk credential — a non-blank
1409
- * `.claude/.credentials.json` (the store Claude Code writes on login off macOS)
1410
- * or a `.claude/.oauth_token` file (the keychain-less shim fallback, shims.ts).
1411
- *
1412
- * RUSH-1979: the routines daemon injects one ambient `CLAUDE_CODE_OAUTH_TOKEN`
1413
- * (RUSH-1759) so a token-less default account still authenticates. But Claude —
1414
- * and the Linux shim's own `-z CLAUDE_CODE_OAUTH_TOKEN` guard — both prefer that
1415
- * env var over a pinned account's config dir, so when balanced rotation pins a
1416
- * specific account's `CLAUDE_CONFIG_DIR` the ambient token shadows it: the pool
1417
- * is inert and every fire 401s on the one stale token. A routine spawn drops the
1418
- * ambient token when the pinned account has its own credential (this returns
1419
- * true) so the account authenticates itself; when it does not (the RUSH-1759
1420
- * default), the injected token is kept.
1421
- *
1422
- * Off-darwin only, mirroring {@link isClaudeCredentialFileBlank}: on macOS the
1423
- * login Keychain is the canonical store and probing it raises an auth sheet, and
1424
- * there the daemon injects no token to shadow (broker-only, usually absent), so
1425
- * returning false changes nothing. Sync, no Keychain, no network.
1426
- */
1427
- export function claudeHomeHasOwnCredential(base, platform = process.platform) {
1428
- if (platform === 'darwin')
1429
- return false;
1430
- if (fs.existsSync(path.join(base, '.claude', '.oauth_token')))
1431
- return true;
1432
- try {
1433
- const raw = fs.readFileSync(path.join(base, '.claude', '.credentials.json'), 'utf-8');
1434
- const oauth = JSON.parse(raw).claudeAiOauth;
1435
- if (!oauth)
1436
- return false;
1437
- const nonEmpty = (v) => typeof v === 'string' && v.trim().length > 0;
1438
- return nonEmpty(oauth.accessToken) || nonEmpty(oauth.refreshToken);
1439
- }
1440
- catch {
1441
- return false;
1442
- }
1443
- }
1444
1373
  export async function getAccountInfo(agentId, home) {
1445
1374
  const base = home || os.homedir();
1446
1375
  const empty = {
@@ -1589,6 +1518,38 @@ export async function getAccountInfo(agentId, home) {
1589
1518
  const email = data.active || null;
1590
1519
  return { ...empty, email, signedIn: !!email, lastActive };
1591
1520
  }
1521
+ case 'cursor': {
1522
+ // Cursor CLI keeps account metadata in ~/.cursor/cli-config.json
1523
+ // (authInfo: { email, userId, authId }) and its OAuth tokens SEPARATELY
1524
+ // in ~/.config/cursor/auth.json ({ accessToken, refreshToken }). Presence
1525
+ // of an access token is the signed-in signal; email/ids come from
1526
+ // cli-config. authId is the OAuth subject (e.g. "google-oauth2|<n>") — the
1527
+ // same value the usage endpoint keys on (see getCursorUsageInfo).
1528
+ const cfgPath = resolveAccountCredentialPath(base, '.cursor', 'cli-config.json');
1529
+ if (!cfgPath)
1530
+ return { ...empty, lastActive };
1531
+ try {
1532
+ const cfg = JSON.parse(await fs.promises.readFile(cfgPath, 'utf-8'));
1533
+ const authInfo = cfg?.authInfo;
1534
+ const email = typeof authInfo?.email === 'string' ? authInfo.email : null;
1535
+ const accountId = normalizeIdentityPart(authInfo?.authId ?? authInfo?.userId);
1536
+ if (!email && !accountId)
1537
+ return { ...empty, lastActive };
1538
+ const authPath = resolveAccountCredentialPath(base, '.config', 'cursor', 'auth.json');
1539
+ let hasToken = false;
1540
+ if (authPath) {
1541
+ try {
1542
+ const tok = JSON.parse(fs.readFileSync(authPath, 'utf-8'));
1543
+ hasToken = typeof tok?.accessToken === 'string' && tok.accessToken.length > 0;
1544
+ }
1545
+ catch { /* unreadable token file */ }
1546
+ }
1547
+ const accountKey = buildIdentityKey(agentId, [['user', accountId]]);
1548
+ return { ...empty, email, accountId, accountKey, signedIn: hasToken || !!email, lastActive };
1549
+ }
1550
+ catch { }
1551
+ return { ...empty, lastActive };
1552
+ }
1592
1553
  case 'grok': {
1593
1554
  // Grok stores auth in ~/.grok/auth.json as a map keyed by
1594
1555
  // "<oidc_issuer>::<client_id>" -> { email, user_id, refresh_token,
@@ -1904,7 +1865,7 @@ function buildIdentityKey(agentId, parts) {
1904
1865
  /** Register an MCP server with an agent's CLI via `mcp add`. */
1905
1866
  export async function registerMcp(agentId, name, command, scope = 'user', transport = 'stdio', options) {
1906
1867
  const agent = AGENTS[agentId];
1907
- if (!agent.capabilities.mcp) {
1868
+ if (!supports(agentId, 'mcp').ok) {
1908
1869
  return { success: false, error: 'Agent does not support MCP' };
1909
1870
  }
1910
1871
  if (transport === 'http' && !supports(agentId, 'mcpHttp').ok) {
@@ -1913,7 +1874,7 @@ export async function registerMcp(agentId, name, command, scope = 'user', transp
1913
1874
  if (transport === 'http' && options?.headers && Object.keys(options.headers).length > 0 && !supports(agentId, 'mcpHeaders').ok) {
1914
1875
  return { success: false, error: 'skipped: HTTP MCP headers are only supported for Claude registration' };
1915
1876
  }
1916
- if (agentId === 'hermes' || agentId === 'forge') {
1877
+ if (agentId === 'hermes') {
1917
1878
  try {
1918
1879
  writeMcpToConfig(agentId, name, command, scope, transport, options?.home);
1919
1880
  return { success: true };
@@ -1964,10 +1925,10 @@ export async function registerMcp(agentId, name, command, scope = 'user', transp
1964
1925
  /** Unregister (remove) a named MCP server from an agent's CLI config. */
1965
1926
  export async function unregisterMcp(agentId, name, options) {
1966
1927
  const agent = AGENTS[agentId];
1967
- if (!agent.capabilities.mcp) {
1928
+ if (!supports(agentId, 'mcp').ok) {
1968
1929
  return { success: false, error: 'Agent does not support MCP' };
1969
1930
  }
1970
- if (agentId === 'hermes' || agentId === 'forge') {
1931
+ if (agentId === 'hermes') {
1971
1932
  try {
1972
1933
  removeMcpFromConfig(agentId, name, options?.home);
1973
1934
  return { success: true };
@@ -2319,8 +2280,6 @@ export function getUserMcpConfigPath(agentId) {
2319
2280
  return path.join(agent.configDir, 'mcp.json');
2320
2281
  case 'hermes':
2321
2282
  return path.join(agent.configDir, 'config.yaml');
2322
- case 'forge':
2323
- return path.join(agent.configDir, '.mcp.json');
2324
2283
  default:
2325
2284
  // Gemini and others use settings.json
2326
2285
  return path.join(agent.configDir, 'settings.json');
@@ -2357,8 +2316,6 @@ export function getMcpConfigPathForHome(agentId, home) {
2357
2316
  return path.join(home, '.factory', 'mcp.json');
2358
2317
  case 'hermes':
2359
2318
  return path.join(home, '.hermes', 'config.yaml');
2360
- case 'forge':
2361
- return path.join(home, '.forge', '.mcp.json');
2362
2319
  default:
2363
2320
  return path.join(home, agentConfigDirName(agentId), 'settings.json');
2364
2321
  }
@@ -2398,8 +2355,6 @@ export function getProjectMcpConfigPath(agentId, cwd = process.cwd()) {
2398
2355
  return path.join(cwd, '.factory', 'mcp.json');
2399
2356
  case 'hermes':
2400
2357
  return path.join(cwd, '.hermes', 'config.yaml');
2401
- case 'forge':
2402
- return path.join(cwd, '.mcp.json');
2403
2358
  default:
2404
2359
  return path.join(cwd, `.${agentId}`, 'settings.json');
2405
2360
  }
@@ -2543,9 +2498,6 @@ export const AGENT_NAME_ALIASES = {
2543
2498
  droid: 'droid',
2544
2499
  hermes: 'hermes',
2545
2500
  'hermes-agent': 'hermes',
2546
- forge: 'forge',
2547
- forgecode: 'forge',
2548
- 'forge-code': 'forge',
2549
2501
  };
2550
2502
  /**
2551
2503
  * Resolve a user-provided agent name (alias, shorthand, or canonical) to its AgentId.
@@ -2596,6 +2548,26 @@ export function deprecationNotice(agent) {
2596
2548
  lines.push(` ${dep.url}`);
2597
2549
  return lines;
2598
2550
  }
2551
+ export function hardDeprecationNotice(agent) {
2552
+ const dep = AGENTS[agent].deprecated;
2553
+ if (!dep?.hard)
2554
+ return null;
2555
+ const name = AGENTS[agent].name;
2556
+ const lines = [
2557
+ `${name} is no longer supported by agents-cli because ${dep.by} retired it (${dep.date}).`,
2558
+ ` ${dep.reason}`,
2559
+ ];
2560
+ if (dep.replacement) {
2561
+ const rep = AGENTS[dep.replacement];
2562
+ lines.push(` Use ${rep.name} instead: agents add ${rep.id}`);
2563
+ }
2564
+ if (dep.url)
2565
+ lines.push(` ${dep.url}`);
2566
+ return lines;
2567
+ }
2568
+ export function hardDeprecationError(agent) {
2569
+ return hardDeprecationNotice(agent)?.join('\n') ?? `${AGENTS[agent].name} is no longer supported.`;
2570
+ }
2599
2571
  /**
2600
2572
  * Print a deprecation warning (yellow) if the agent's registry entry carries a
2601
2573
  * `deprecated` marker; no-op otherwise. Call from any user entry point that
@@ -2,12 +2,13 @@
2
2
  * Background sync for tracked git repos:
3
3
  * - System repo (~/.agents/.system/) is read-only locally — fast-forward auto-pull is safe.
4
4
  * - User repo (~/.agents/) and enabled extras may have local commits, so we only
5
- * `git fetch` and write a status marker. Next CLI invocation surfaces a one-line
6
- * notice if upstream is ahead. Pulling is left to the user via `agents repo pull`.
5
+ * `git fetch` and write a status marker. `agents doctor` surfaces these markers
6
+ * as a "Repo updates" section instead of printing to stderr on every command.
7
+ * Pulling is left to the user via `agents repo pull`.
7
8
  *
8
9
  * Public API:
9
- * spawnDetachedSync() — fire-and-forget; never blocks the foreground command.
10
- * printPendingUpdateNotices() — synchronous; reads markers and prints + consumes them.
10
+ * spawnDetachedSync() — fire-and-forget; never blocks the foreground command.
11
+ * readRepoBehindMarkers() — synchronous; reads markers without consuming them.
11
12
  */
12
13
  /** Per-repo lock file path. mtime acts as a recency check. */
13
14
  export declare function lockFilePath(alias: string): string;
@@ -24,8 +25,15 @@ export interface FetchStatusMarker {
24
25
  /** Spawn the detached worker. No-op when AGENTS_NO_AUTOPULL=1 is set. */
25
26
  export declare function spawnDetachedSync(): void;
26
27
  /**
27
- * Read any pending status markers and print one-line notices for repos that
28
- * are behind upstream. Markers are deleted after printing so notices don't
29
- * repeat on every invocation. Synchronous, cheap (small JSON files).
28
+ * Read all current status markers and return those where the local repo is
29
+ * behind upstream. Markers are NOT deleted they persist until the next
30
+ * background fetch overwrites them with fresh data.
31
+ *
32
+ * Synchronous, cheap (small JSON files). Used by `agents doctor` to surface
33
+ * repo-behind warnings in one place instead of printing to stderr on every
34
+ * command.
35
+ *
36
+ * @param fetchDir - Override the fetch state dir (for tests). Defaults to the
37
+ * production getFetchCacheDir() path.
30
38
  */
31
- export declare function printPendingUpdateNotices(): void;
39
+ export declare function readRepoBehindMarkers(fetchDir?: string): FetchStatusMarker[];
@@ -2,12 +2,13 @@
2
2
  * Background sync for tracked git repos:
3
3
  * - System repo (~/.agents/.system/) is read-only locally — fast-forward auto-pull is safe.
4
4
  * - User repo (~/.agents/) and enabled extras may have local commits, so we only
5
- * `git fetch` and write a status marker. Next CLI invocation surfaces a one-line
6
- * notice if upstream is ahead. Pulling is left to the user via `agents repo pull`.
5
+ * `git fetch` and write a status marker. `agents doctor` surfaces these markers
6
+ * as a "Repo updates" section instead of printing to stderr on every command.
7
+ * Pulling is left to the user via `agents repo pull`.
7
8
  *
8
9
  * Public API:
9
- * spawnDetachedSync() — fire-and-forget; never blocks the foreground command.
10
- * printPendingUpdateNotices() — synchronous; reads markers and prints + consumes them.
10
+ * spawnDetachedSync() — fire-and-forget; never blocks the foreground command.
11
+ * readRepoBehindMarkers() — synchronous; reads markers without consuming them.
11
12
  */
12
13
  import * as fs from 'fs';
13
14
  import * as path from 'path';
@@ -57,21 +58,29 @@ export function spawnDetachedSync() {
57
58
  }
58
59
  }
59
60
  /**
60
- * Read any pending status markers and print one-line notices for repos that
61
- * are behind upstream. Markers are deleted after printing so notices don't
62
- * repeat on every invocation. Synchronous, cheap (small JSON files).
61
+ * Read all current status markers and return those where the local repo is
62
+ * behind upstream. Markers are NOT deleted they persist until the next
63
+ * background fetch overwrites them with fresh data.
64
+ *
65
+ * Synchronous, cheap (small JSON files). Used by `agents doctor` to surface
66
+ * repo-behind warnings in one place instead of printing to stderr on every
67
+ * command.
68
+ *
69
+ * @param fetchDir - Override the fetch state dir (for tests). Defaults to the
70
+ * production getFetchCacheDir() path.
63
71
  */
64
- export function printPendingUpdateNotices() {
65
- const dir = fetchStateDir();
72
+ export function readRepoBehindMarkers(fetchDir) {
73
+ const dir = fetchDir ?? fetchStateDir();
66
74
  if (!fs.existsSync(dir))
67
- return;
75
+ return [];
68
76
  let entries = [];
69
77
  try {
70
78
  entries = fs.readdirSync(dir);
71
79
  }
72
80
  catch {
73
- return;
81
+ return [];
74
82
  }
83
+ const result = [];
75
84
  for (const name of entries) {
76
85
  if (!name.endsWith('.status.json'))
77
86
  continue;
@@ -81,25 +90,11 @@ export function printPendingUpdateNotices() {
81
90
  marker = JSON.parse(fs.readFileSync(file, 'utf-8'));
82
91
  }
83
92
  catch {
84
- try {
85
- fs.unlinkSync(file);
86
- }
87
- catch { /* ignore */ }
88
93
  continue;
89
94
  }
90
- if (!marker || marker.behind <= 0) {
91
- try {
92
- fs.unlinkSync(file);
93
- }
94
- catch { /* ignore */ }
95
+ if (!marker || marker.behind <= 0)
95
96
  continue;
96
- }
97
- const repoLabel = marker.alias === 'user' ? '~/.agents/' : marker.alias;
98
- process.stderr.write(`agents-cli: ${repoLabel} is ${marker.behind} commit${marker.behind === 1 ? '' : 's'} ` +
99
- `behind ${marker.branch} — run 'agents repo pull ${marker.alias}' to update.\n`);
100
- try {
101
- fs.unlinkSync(file);
102
- }
103
- catch { /* ignore */ }
97
+ result.push(marker);
104
98
  }
99
+ return result;
105
100
  }
@@ -14,6 +14,7 @@ import { clickAtCoords, hoverAtCoords, scrollAtCoords, typeText, pressKey, focus
14
14
  import { typeEditorText } from './editor.js';
15
15
  import { detectUploadPattern, stageUploadFile, uploadToDropTarget, uploadToFileInput, uploadViaFileChooser, } from './upload.js';
16
16
  import { emit } from '../events.js';
17
+ import { resolveActor } from '../actor.js';
17
18
  import { sshExecAsync } from '../ssh-exec.js';
18
19
  function isPathInside(candidate, dir) {
19
20
  const rel = path.relative(dir, candidate);
@@ -331,6 +332,7 @@ export class BrowserService {
331
332
  currentTabId: undefined,
332
333
  createdAt: Date.now(),
333
334
  pid: conn.pid,
335
+ owner: resolveActor().id, // who launched this browser task (RUSH-2020)
334
336
  };
335
337
  // For Electron, get the existing window as the tab
336
338
  if (conn.electron) {
@@ -2012,6 +2014,7 @@ export class BrowserService {
2012
2014
  endedAt: Date.now(),
2013
2015
  domains,
2014
2016
  tabCount: Object.keys(task.tabs).length,
2017
+ ...(task.owner ? { owner: task.owner } : {}), // carry who launched it (RUSH-2020)
2015
2018
  });
2016
2019
  // Keep only last 50 entries
2017
2020
  history = history.slice(0, 50);
@@ -78,6 +78,11 @@ export interface Task {
78
78
  currentTabId?: string;
79
79
  createdAt: number;
80
80
  pid: number;
81
+ /**
82
+ * Resolved actor id (`resolveActor().id`) stamped at task start — who launched
83
+ * this browser task. Optional: tasks persisted before RUSH-2020 carry none.
84
+ */
85
+ owner?: string;
81
86
  /**
82
87
  * Per-tab snapshot of the last ref listing captured for that tab
83
88
  * (shortId -> {descriptors, opts}). Persisted to tasks.json so a later
@@ -126,6 +131,8 @@ export interface HistoricalTask {
126
131
  endedAt: number;
127
132
  domains: string[];
128
133
  tabCount: number;
134
+ /** Actor id who launched the task (RUSH-2020); absent for pre-RUSH-2020 history. */
135
+ owner?: string;
129
136
  }
130
137
  export type IPCAction = 'start' | 'record-start' | 'record-stop' | 'done' | 'stop' | 'status' | 'history' | 'navigate' | 'tab-add' | 'tab-focus' | 'tab-close' | 'tab-list' | 'evaluate' | 'screenshot' | 'pdf' | 'refs' | 'click' | 'type' | 'press' | 'hover' | 'scroll' | 'set-viewport' | 'set-device' | 'console' | 'errors' | 'requests' | 'response-body' | 'wait' | 'set-download-path' | 'wait-download' | 'upload' | 'getAppLogs' | 'version';
131
138
  export interface IPCRequest {
@@ -7,7 +7,7 @@
7
7
  * is below `since`, the install path skips the write and surfaces a clear
8
8
  * reason instead of silently corrupting an older binary's settings file.
9
9
  */
10
- import { AGENTS } from './agents.js';
10
+ import { AGENTS, MANAGED_AGENT_IDS } from './agents.js';
11
11
  /**
12
12
  * Compare semver-like versions ("0.116.0" vs "0.115.9"). Local copy to avoid
13
13
  * importing versions.ts (which imports agents.ts, which imports this file).
@@ -37,6 +37,8 @@ function getCapability(agent, cap) {
37
37
  * Useful for filtering UI lists; does not check installed version.
38
38
  */
39
39
  export function isCapable(agent, cap) {
40
+ if (AGENTS[agent]?.deprecated?.hard)
41
+ return false;
40
42
  const c = getCapability(agent, cap);
41
43
  return c !== false;
42
44
  }
@@ -47,6 +49,8 @@ export function isCapable(agent, cap) {
47
49
  * capable" filters but NOT for install-time gating.
48
50
  */
49
51
  export function supports(agent, cap, version) {
52
+ if (AGENTS[agent]?.deprecated?.hard)
53
+ return { ok: false, reason: 'unsupported' };
50
54
  const c = getCapability(agent, cap);
51
55
  if (c === false)
52
56
  return { ok: false, reason: 'unsupported' };
@@ -78,5 +82,5 @@ export function explainSkip(agent, cap, result, version) {
78
82
  }
79
83
  /** All agents whose `capabilities[cap]` is anything other than `false`. */
80
84
  export function capableAgents(cap) {
81
- return Object.keys(AGENTS).filter((id) => isCapable(id, cap));
85
+ return MANAGED_AGENT_IDS.filter((id) => isCapable(id, cap));
82
86
  }
@@ -137,7 +137,7 @@ export function buildBootstrapScript(opts) {
137
137
  // (leaseAndRun, before this script). Here — after ENSURE_AGENTS_CLI installed
138
138
  // the CLI — we materialize that config into the runtime home. Gated by
139
139
  // copySetup (cleared for --bare). Best-effort; a refresh failure never aborts.
140
- copySetup ? [step('copy-setup'), 'agents repo refresh >/dev/null 2>&1 || true'].join('\n') : '',
140
+ copySetup ? [step('copy-setup'), 'agents sync --local -y >/dev/null 2>&1 || true'].join('\n') : '',
141
141
  // Marker on its own line: the command layer shows everything before this as
142
142
  // setup progress and everything after (the agent's output) verbatim.
143
143
  `echo ${q(LEASE_AGENT_MARKER)}`,
@@ -176,7 +176,7 @@ export async function leaseAndRun(opts) {
176
176
  // Setup-copy (F1, RUSH-1920): push the git-tracked ~/.agents config onto the
177
177
  // box from the host, over crabbox's own per-lease ssh (a raw ssh fails
178
178
  // publickey). rsync only here — the box has no agents-cli yet, so the matching
179
- // `agents repo refresh` runs inside the bootstrap script, after the install
179
+ // `agents sync --local` runs inside the bootstrap script, after the install
180
180
  // step. Best-effort: a copy failure never aborts the run (the agent just runs
181
181
  // without the config).
182
182
  if (opts.copySetup !== false) {
@@ -12,7 +12,7 @@
12
12
  * 2. `rsync` that exact file set to `~/.agents` on the box over crabbox's OWN
13
13
  * ssh invocation (`crabboxSshArgv`) — crabbox provisions a per-lease identity
14
14
  * key, so a raw `ssh crabbox@ip` fails publickey; only crabbox's key works.
15
- * 3. `agents repo refresh` on the box so the copied config takes effect.
15
+ * 3. `agents sync --local` on the box so the copied config takes effect.
16
16
  *
17
17
  * NEVER copies `~/.claude` / `~/.claude.json` — they live in `$HOME`, not
18
18
  * `~/.agents`, and are rebuilt on the box by `agents add` + refresh; an explicit
@@ -34,7 +34,7 @@ export interface CopySetupOptions {
34
34
  /** Receives combined stdout/stderr of the rsync + refresh, if set. */
35
35
  onData?: (chunk: string) => void;
36
36
  /**
37
- * Run `agents repo refresh` on the box after the push (default `true`). Set
37
+ * Run `agents sync --local` on the box after the push (default `true`). Set
38
38
  * `false` when the caller runs the refresh itself in the box bootstrap — the
39
39
  * lease path does this so the refresh runs AFTER the box installs agents-cli
40
40
  * (this host-side push happens before the box boots agents-cli, so a host-side
@@ -47,7 +47,7 @@ export interface CopySetupResult {
47
47
  files: string[];
48
48
  /** rsync exit code, or null when the process failed to spawn. */
49
49
  pushExitCode: number | null;
50
- /** `agents repo refresh` exit code, or null when it was skipped/failed to spawn. */
50
+ /** `agents sync --local` exit code, or null when it was skipped/failed to spawn. */
51
51
  refreshExitCode: number | null;
52
52
  }
53
53
  /**
@@ -80,7 +80,7 @@ export declare function buildSetupRsyncArgs(opts: {
80
80
  /**
81
81
  * Replicate the git-tracked subset of the local `~/.agents` onto the crabbox box
82
82
  * and refresh it. Enumerates tracked files, rsyncs them over ssh, then runs
83
- * `agents repo refresh` on the box. Refresh is skipped when the push fails or the
83
+ * `agents sync --local` on the box. Refresh is skipped when the push fails or the
84
84
  * file set is empty (nothing to refresh). Never throws — surfaces failure through
85
85
  * the returned exit codes.
86
86
  */
@@ -12,7 +12,7 @@
12
12
  * 2. `rsync` that exact file set to `~/.agents` on the box over crabbox's OWN
13
13
  * ssh invocation (`crabboxSshArgv`) — crabbox provisions a per-lease identity
14
14
  * key, so a raw `ssh crabbox@ip` fails publickey; only crabbox's key works.
15
- * 3. `agents repo refresh` on the box so the copied config takes effect.
15
+ * 3. `agents sync --local` on the box so the copied config takes effect.
16
16
  *
17
17
  * NEVER copies `~/.claude` / `~/.claude.json` — they live in `$HOME`, not
18
18
  * `~/.agents`, and are rebuilt on the box by `agents add` + refresh; an explicit
@@ -88,7 +88,7 @@ function runStreaming(cmd, args, env, onData) {
88
88
  /**
89
89
  * Replicate the git-tracked subset of the local `~/.agents` onto the crabbox box
90
90
  * and refresh it. Enumerates tracked files, rsyncs them over ssh, then runs
91
- * `agents repo refresh` on the box. Refresh is skipped when the push fails or the
91
+ * `agents sync --local` on the box. Refresh is skipped when the push fails or the
92
92
  * file set is empty (nothing to refresh). Never throws — surfaces failure through
93
93
  * the returned exit codes.
94
94
  */
@@ -115,8 +115,8 @@ export async function copySetupToBox(opts) {
115
115
  const pushExitCode = await runStreaming('rsync', rsyncArgs, env, opts.onData);
116
116
  let refreshExitCode = null;
117
117
  if (pushExitCode === 0 && opts.refresh !== false) {
118
- // ssh <opts> crabbox@host bash -lc 'agents repo refresh'
119
- const refreshArgs = [...sshArgv.slice(1), 'bash', '-lc', 'agents repo refresh'];
118
+ // ssh <opts> crabbox@host bash -lc 'agents sync --local -y'
119
+ const refreshArgs = [...sshArgv.slice(1), 'bash', '-lc', 'agents sync --local -y'];
120
120
  refreshExitCode = await runStreaming('ssh', refreshArgs, env, opts.onData);
121
121
  }
122
122
  return { files, pushExitCode, refreshExitCode };