@phnx-labs/agents-cli 1.22.42 → 1.22.44

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 (256) hide show
  1. package/CHANGELOG.md +149 -0
  2. package/README.md +15 -13
  3. package/dist/bootstrap.js +33 -13
  4. package/dist/cli/command-registry.d.ts +0 -8
  5. package/dist/cli/command-registry.js +1 -25
  6. package/dist/commands/alias.d.ts +7 -4
  7. package/dist/commands/alias.js +33 -8
  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/auth.js +14 -6
  14. package/dist/commands/beta.d.ts +7 -1
  15. package/dist/commands/beta.js +16 -9
  16. package/dist/commands/commands.js +1 -1
  17. package/dist/commands/computer.js +20 -6
  18. package/dist/commands/daemon.js +1 -1
  19. package/dist/commands/doctor.d.ts +4 -2
  20. package/dist/commands/doctor.js +21 -9
  21. package/dist/commands/events.d.ts +2 -2
  22. package/dist/commands/events.js +6 -5
  23. package/dist/commands/exec.d.ts +1 -1
  24. package/dist/commands/exec.js +11 -6
  25. package/dist/commands/factory.d.ts +1 -1
  26. package/dist/commands/feed.js +1 -30
  27. package/dist/commands/fleet-capture.d.ts +1 -1
  28. package/dist/commands/fleet-capture.js +4 -5
  29. package/dist/commands/harness.js +4 -52
  30. package/dist/commands/hooks.js +1 -1
  31. package/dist/commands/insights.d.ts +1 -1
  32. package/dist/commands/insights.js +3 -2
  33. package/dist/commands/menubar.js +58 -1
  34. package/dist/commands/monitors.js +1 -1
  35. package/dist/commands/org.d.ts +5 -0
  36. package/dist/commands/org.js +83 -29
  37. package/dist/commands/output.js +2 -2
  38. package/dist/commands/profiles.js +2 -2
  39. package/dist/commands/prune.js +5 -4
  40. package/dist/commands/routines.js +43 -25
  41. package/dist/commands/routines.test-fixture.d.ts +86 -0
  42. package/dist/commands/routines.test-fixture.js +345 -0
  43. package/dist/commands/sessions-picker.js +124 -122
  44. package/dist/commands/sessions.test-fixture.d.ts +24 -0
  45. package/dist/commands/sessions.test-fixture.js +224 -0
  46. package/dist/commands/setup.js +9 -1
  47. package/dist/commands/share.d.ts +9 -10
  48. package/dist/commands/share.js +68 -50
  49. package/dist/commands/skills.js +1 -1
  50. package/dist/commands/snapshot.d.ts +1 -1
  51. package/dist/commands/snapshot.js +4 -4
  52. package/dist/commands/ssh.js +15 -62
  53. package/dist/commands/status.d.ts +4 -2
  54. package/dist/commands/status.js +10 -7
  55. package/dist/commands/subagents.js +1 -1
  56. package/dist/commands/sync.d.ts +1 -0
  57. package/dist/commands/sync.js +29 -2
  58. package/dist/commands/view.js +11 -2
  59. package/dist/commands/webhook.js +1 -1
  60. package/dist/lib/accounting/rotate.d.ts +5 -3
  61. package/dist/lib/accounting/rotate.js +36 -7
  62. package/dist/lib/accounting/usage.d.ts +18 -4
  63. package/dist/lib/accounting/usage.js +138 -27
  64. package/dist/lib/agent-spec/agents.d.ts +7 -7
  65. package/dist/lib/agent-spec/agents.js +15 -15
  66. package/dist/lib/analytics/mix-commands.d.ts +6 -28
  67. package/dist/lib/analytics/mix-commands.js +14 -40
  68. package/dist/lib/audit/log.d.ts +2 -2
  69. package/dist/lib/audit/log.js +2 -2
  70. package/dist/lib/beta.js +1 -1
  71. package/dist/lib/browser/domain-skills.d.ts +26 -6
  72. package/dist/lib/browser/domain-skills.js +81 -43
  73. package/dist/lib/cloud/rush.d.ts +0 -15
  74. package/dist/lib/cloud/rush.js +0 -35
  75. package/dist/lib/crabbox/runtimes.d.ts +6 -2
  76. package/dist/lib/crabbox/runtimes.js +21 -5
  77. package/dist/lib/daemon/daemon.js +97 -58
  78. package/dist/lib/daemon/daemon.test-fixture.d.ts +24 -0
  79. package/dist/lib/daemon/daemon.test-fixture.js +71 -0
  80. package/dist/lib/daemon/runner.d.ts +18 -1
  81. package/dist/lib/daemon/runner.js +105 -11
  82. package/dist/lib/daemon-ticks.d.ts +38 -1
  83. package/dist/lib/daemon-ticks.js +52 -5
  84. package/dist/lib/device-config.js +1 -2
  85. package/dist/lib/devices/discovery-policy.d.ts +3 -3
  86. package/dist/lib/devices/discovery-policy.js +3 -3
  87. package/dist/lib/devices/doctor-findings.js +1 -1
  88. package/dist/lib/devices/fleet.d.ts +4 -8
  89. package/dist/lib/devices/fleet.js +3 -15
  90. package/dist/lib/devices/health-report.js +4 -4
  91. package/dist/lib/devices/pool.d.ts +0 -6
  92. package/dist/lib/devices/pool.js +0 -6
  93. package/dist/lib/devices/registry.d.ts +0 -15
  94. package/dist/lib/devices/registry.js +0 -9
  95. package/dist/lib/devices/stats-cache.d.ts +12 -0
  96. package/dist/lib/devices/stats-cache.js +24 -5
  97. package/dist/lib/drift-sync.d.ts +1 -1
  98. package/dist/lib/drift-sync.js +2 -2
  99. package/dist/lib/drift.d.ts +1 -1
  100. package/dist/lib/exec.js +13 -3
  101. package/dist/lib/fleet/capture.d.ts +1 -1
  102. package/dist/lib/fleet/manifest.js +1 -1
  103. package/dist/lib/fleet/remote-login.d.ts +2 -2
  104. package/dist/lib/fleet/remote-login.js +3 -4
  105. package/dist/lib/format.d.ts +9 -0
  106. package/dist/lib/format.js +16 -0
  107. package/dist/lib/github/pr-mergeable.d.ts +49 -0
  108. package/dist/lib/github/pr-mergeable.js +157 -0
  109. package/dist/lib/github/pr-verdict.d.ts +67 -0
  110. package/dist/lib/github/pr-verdict.js +72 -0
  111. package/dist/lib/harness/adapter.d.ts +6 -5
  112. package/dist/lib/hosts/dispatch.d.ts +2 -0
  113. package/dist/lib/hosts/dispatch.js +8 -1
  114. package/dist/lib/hosts/passthrough.d.ts +10 -0
  115. package/dist/lib/hosts/passthrough.js +28 -7
  116. package/dist/lib/hosts/providers/devices.js +1 -13
  117. package/dist/lib/hosts/ready.d.ts +12 -3
  118. package/dist/lib/hosts/ready.js +27 -12
  119. package/dist/lib/hosts/registry.d.ts +4 -5
  120. package/dist/lib/hosts/registry.js +3 -7
  121. package/dist/lib/hosts/session-index.js +1 -1
  122. package/dist/lib/installations/shims.js +1 -1
  123. package/dist/lib/installations/versions.d.ts +40 -0
  124. package/dist/lib/installations/versions.js +78 -4
  125. package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +1 -5
  126. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  127. package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +2 -15
  128. package/dist/lib/menubar/install-menubar.d.ts +109 -0
  129. package/dist/lib/menubar/install-menubar.js +218 -12
  130. package/dist/lib/monitors/config.d.ts +2 -2
  131. package/dist/lib/monitors/config.js +3 -2
  132. package/dist/lib/origin-machine.d.ts +18 -0
  133. package/dist/lib/origin-machine.js +34 -0
  134. package/dist/lib/perf/db.js +1 -1
  135. package/dist/lib/perf/spool.js +1 -1
  136. package/dist/lib/prix-account.d.ts +2 -1
  137. package/dist/lib/prix-account.js +2 -1
  138. package/dist/lib/profiles.d.ts +7 -0
  139. package/dist/lib/profiles.js +26 -1
  140. package/dist/lib/remote-agents-json.js +1 -7
  141. package/dist/lib/routines-placement.d.ts +1 -1
  142. package/dist/lib/routines-placement.js +1 -1
  143. package/dist/lib/sandbox.d.ts +24 -0
  144. package/dist/lib/sandbox.js +101 -0
  145. package/dist/lib/scheduling/routines.d.ts +18 -9
  146. package/dist/lib/scheduling/routines.js +16 -8
  147. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  148. package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -2
  149. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  150. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +1 -13
  151. package/dist/lib/secrets/agent.js +9 -1
  152. package/dist/lib/secrets/index.js +1 -1
  153. package/dist/lib/secrets/list-filter.js +1 -1
  154. package/dist/lib/service-manifest.d.ts +20 -0
  155. package/dist/lib/service-manifest.js +34 -0
  156. package/dist/lib/session/active.d.ts +4 -16
  157. package/dist/lib/session/active.js +2 -6
  158. package/dist/lib/session/cloud.js +1 -1
  159. package/dist/lib/session/db.d.ts +13 -98
  160. package/dist/lib/session/db.js +5 -12
  161. package/dist/lib/session/discover.d.ts +5 -81
  162. package/dist/lib/session/discover.js +5 -5
  163. package/dist/lib/session/fork.js +1 -1
  164. package/dist/lib/session/live-metadata.js +1 -1
  165. package/dist/lib/session/origin-machine.d.ts +1 -18
  166. package/dist/lib/session/origin-machine.js +4 -34
  167. package/dist/lib/session/relative-time.d.ts +1 -44
  168. package/dist/lib/session/relative-time.js +4 -120
  169. package/dist/lib/session/remote/remote-bundle.d.ts +12 -0
  170. package/dist/lib/session/remote/remote-bundle.js +61 -0
  171. package/dist/lib/session/remote/remote-list.d.ts +148 -0
  172. package/dist/lib/session/remote/remote-list.js +607 -0
  173. package/dist/lib/session/remote/remote.d.ts +132 -0
  174. package/dist/lib/session/remote/remote.js +314 -0
  175. package/dist/lib/session/remote/watch.d.ts +101 -0
  176. package/dist/lib/session/remote/watch.js +241 -0
  177. package/dist/lib/session/remote-bundle.d.ts +1 -12
  178. package/dist/lib/session/remote-bundle.js +3 -61
  179. package/dist/lib/session/remote-list.d.ts +1 -148
  180. package/dist/lib/session/remote-list.js +3 -607
  181. package/dist/lib/session/remote.d.ts +1 -132
  182. package/dist/lib/session/remote.js +3 -314
  183. package/dist/lib/session/short-id.d.ts +1 -17
  184. package/dist/lib/session/short-id.js +4 -20
  185. package/dist/lib/session/watch.d.ts +1 -101
  186. package/dist/lib/session/watch.js +3 -242
  187. package/dist/lib/session/width.d.ts +1 -29
  188. package/dist/lib/session/width.js +4 -101
  189. package/dist/lib/share/config.d.ts +12 -2
  190. package/dist/lib/share/config.js +47 -11
  191. package/dist/lib/share/delete.js +1 -1
  192. package/dist/lib/share/publish.js +1 -1
  193. package/dist/lib/share/worker-template.js +2 -2
  194. package/dist/lib/smart-launch.d.ts +4 -4
  195. package/dist/lib/smart-launch.js +8 -18
  196. package/dist/lib/snapshot.d.ts +2 -2
  197. package/dist/lib/snapshot.js +1 -1
  198. package/dist/lib/staleness/index.d.ts +3 -1
  199. package/dist/lib/staleness/index.js +21 -1
  200. package/dist/lib/staleness/types.d.ts +11 -0
  201. package/dist/lib/staleness/writers/commands.js +13 -4
  202. package/dist/lib/staleness/writers/hooks.js +3 -1
  203. package/dist/lib/staleness/writers/mcp.d.ts +0 -7
  204. package/dist/lib/staleness/writers/mcp.js +16 -1
  205. package/dist/lib/staleness/writers/rules.js +1 -1
  206. package/dist/lib/staleness/writers/skills.js +3 -1
  207. package/dist/lib/staleness/writers/subagents.d.ts +0 -9
  208. package/dist/lib/staleness/writers/subagents.js +18 -1
  209. package/dist/lib/staleness/writers/types.d.ts +10 -0
  210. package/dist/lib/startup/command-registry.d.ts +10 -1
  211. package/dist/lib/startup/command-registry.js +26 -7
  212. package/dist/lib/startup/root-command.d.ts +18 -1
  213. package/dist/lib/startup/root-command.js +18 -1
  214. package/dist/lib/state.js +7 -0
  215. package/dist/lib/teams/placement-probe.js +28 -13
  216. package/dist/lib/teams/scheduler.d.ts +3 -1
  217. package/dist/lib/teams/scheduler.js +1 -1
  218. package/dist/lib/text/relative-time.d.ts +44 -0
  219. package/dist/lib/text/relative-time.js +120 -0
  220. package/dist/lib/text/short-id.d.ts +17 -0
  221. package/dist/lib/text/short-id.js +20 -0
  222. package/dist/lib/text/width.d.ts +29 -0
  223. package/dist/lib/text/width.js +101 -0
  224. package/dist/lib/triggers/handlers.d.ts +7 -0
  225. package/dist/lib/triggers/handlers.js +8 -0
  226. package/dist/lib/triggers/webhook.d.ts +1 -0
  227. package/dist/lib/triggers/webhook.js +2 -1
  228. package/dist/lib/types.d.ts +1 -1
  229. package/dist/lib/view-types.d.ts +7 -0
  230. package/package.json +2 -3
  231. package/dist/bin/agents +0 -0
  232. package/dist/commands/serve.d.ts +0 -10
  233. package/dist/commands/serve.js +0 -68
  234. package/dist/commands/tickets.d.ts +0 -2
  235. package/dist/commands/tickets.js +0 -43
  236. package/dist/commands/trends.d.ts +0 -10
  237. package/dist/commands/trends.js +0 -12
  238. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  239. package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
  240. package/dist/lib/observe-aliases.d.ts +0 -30
  241. package/dist/lib/observe-aliases.js +0 -38
  242. package/dist/lib/secrets/Agents CLI.app/Contents/Resources/AppIcon.icns +0 -0
  243. package/dist/lib/serve/control.d.ts +0 -95
  244. package/dist/lib/serve/control.js +0 -260
  245. package/dist/lib/serve/data.d.ts +0 -81
  246. package/dist/lib/serve/data.js +0 -91
  247. package/dist/lib/serve/page.d.ts +0 -7
  248. package/dist/lib/serve/page.js +0 -140
  249. package/dist/lib/serve/server.d.ts +0 -80
  250. package/dist/lib/serve/server.js +0 -145
  251. package/dist/lib/serve/stream.d.ts +0 -43
  252. package/dist/lib/serve/stream.js +0 -116
  253. package/dist/lib/serve/token.d.ts +0 -35
  254. package/dist/lib/serve/token.js +0 -85
  255. package/dist/lib/tickets/list.d.ts +0 -53
  256. package/dist/lib/tickets/list.js +0 -153
@@ -312,7 +312,7 @@ export async function loadSessionMetasForFeedEnrichment(load) {
312
312
  export function registerFeedCommand(program) {
313
313
  const feed = program
314
314
  .command('feed')
315
- .description('Operator inbox + agent status posts (alias: inbox = needs-you). Agent progress = --filter updates')
315
+ .description('Operator inbox + agent status posts. Default is needs-you; agent progress = --filter updates')
316
316
  .option('--json', 'Output as JSON (each block stamped with its outcome + ask class)')
317
317
  .option('--filter <view>', 'What to show: needs (default) · updates · all', 'needs')
318
318
  .option('--flat', 'List one block per agent instead of grouping by outcome')
@@ -642,35 +642,6 @@ export function registerFeedCommand(program) {
642
642
  renderOutcomeGroup(g, self);
643
643
  await renderTrailingActivity();
644
644
  });
645
- // Observe-umbrella aliases (Phase 3): intentional second names, not deprecations.
646
- // Re-parse into `feed` so flags/help stay single-sourced. See lib/observe-aliases.ts.
647
- registerFeedObserveAliases(program);
648
- }
649
- /**
650
- * `inbox` → feed. Loaded with the feed module so the lazy COMMAND_LOADERS entry
651
- * for `inbox` also gets the real `feed` command registered for re-parse.
652
- */
653
- function registerFeedObserveAliases(program) {
654
- const reparse = async (alias) => {
655
- const { expandObserveAlias } = await import('../lib/observe-aliases.js');
656
- const rest = process.argv.slice(3);
657
- const expanded = expandObserveAlias(alias, rest);
658
- if (!expanded) {
659
- console.error(chalk.red(`Unknown observe alias: ${alias}`));
660
- process.exit(1);
661
- }
662
- if (process.stderr.isTTY)
663
- process.stderr.write(chalk.gray(`${expanded.note}\n`));
664
- await program.parseAsync(['node', 'agents', ...expanded.argv]);
665
- };
666
- program
667
- .command('inbox')
668
- .description('Needs-you inbox (alias of `agents feed`). Open blocks waiting on you.')
669
- .allowUnknownOption()
670
- .allowExcessArguments()
671
- .action(async () => {
672
- await reparse('inbox');
673
- });
674
645
  }
675
646
  /**
676
647
  * Mirror a written post to the configured sinks (`feed.broadcast` in
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * `agents fleet capture` (alias surface of `agents devices capture`) — snapshot
3
3
  * the live environment into the `fleet:` block of `agents.yaml` so a fresh
4
- * machine can reconstruct it with `agents apply`.
4
+ * machine can reconstruct it with `agents fleet apply`.
5
5
  *
6
6
  * Local-read + local-write, zero SSH. Records device NAMES only (never IPs or
7
7
  * usernames — those are re-resolved live from Tailscale at apply-time), plus the
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * `agents fleet capture` (alias surface of `agents devices capture`) — snapshot
3
3
  * the live environment into the `fleet:` block of `agents.yaml` so a fresh
4
- * machine can reconstruct it with `agents apply`.
4
+ * machine can reconstruct it with `agents fleet apply`.
5
5
  *
6
6
  * Local-read + local-write, zero SSH. Records device NAMES only (never IPs or
7
7
  * usernames — those are re-resolved live from Tailscale at apply-time), plus the
@@ -12,7 +12,7 @@
12
12
  import * as fs from 'fs';
13
13
  import chalk from 'chalk';
14
14
  import * as yaml from 'yaml';
15
- import { loadDevices, isControlDevice } from '../lib/devices/registry.js';
15
+ import { loadDevices } from '../lib/devices/registry.js';
16
16
  import { readMeta, updateMeta, getDevicePinsPath } from '../lib/state.js';
17
17
  import { machineId } from '../lib/machine-id.js';
18
18
  import { listBundles } from '../lib/secrets/bundles.js';
@@ -39,10 +39,9 @@ function agentsFromPins(names) {
39
39
  }
40
40
  async function runCapture(opts) {
41
41
  const meta = readMeta();
42
- // Roster: registered, non-control device names only.
42
+ // Roster: every registered device name.
43
43
  const registry = await loadDevices();
44
44
  let names = Object.values(registry)
45
- .filter((d) => !isControlDevice(d))
46
45
  .map((d) => d.name)
47
46
  .sort();
48
47
  if (opts.device) {
@@ -83,7 +82,7 @@ async function runCapture(opts) {
83
82
  console.log(chalk.green('Captured fleet profile') +
84
83
  chalk.gray(` — ${deviceCount} device(s), ${defaults.agents?.length ?? 0} agent(s), ` +
85
84
  `${inputs.secretsBundles?.length ?? 0} secret bundle(s), ${inputs.routines?.length ?? 0} routine(s).`));
86
- console.log(chalk.gray(' Wrote agents.yaml → fleet:. Push it (`agents repo push`) and run `agents apply` on any machine.'));
85
+ console.log(chalk.gray(' Wrote agents.yaml → fleet:. Push it (`agents repo push`) and run `agents fleet apply` on any machine.'));
87
86
  }
88
87
  /** Attach `capture` to the `devices`/`fleet` command tree. */
89
88
  export function registerFleetCaptureCommand(devicesCmd) {
@@ -17,8 +17,6 @@ import { listProfiles, readProfile, writeProfile, deleteProfile, profileExists,
17
17
  import { listPresets, getPreset } from '../lib/profiles-presets.js';
18
18
  import { AGENTS, ALL_AGENT_IDS, resolveAgentName } from '../lib/agents.js';
19
19
  import { findAccount } from '../lib/account-registry.js';
20
- import { keychainItemName, setKeychainToken, deleteKeychainToken } from '../lib/secrets/profiles.js';
21
- import { readStdinSync } from '../lib/format.js';
22
20
  import { runWizardSteps, createSteps, editSteps, defaultWizardIO, } from './harness-wizard.js';
23
21
  /** Short capability summary for a native harness — its supported run modes. */
24
22
  function nativeModes(id) {
@@ -73,7 +71,7 @@ export function buildFork(source, name, opts) {
73
71
  const account = findAccount(opts.account);
74
72
  if (!account)
75
73
  throw new Error(`Unknown account '${opts.account}'.`);
76
- profile.account = account.id;
74
+ profile.account = account.name;
77
75
  profile.provider = account.provider;
78
76
  }
79
77
  return profile;
@@ -97,7 +95,7 @@ export function buildFork(source, name, opts) {
97
95
  const account = findAccount(opts.account);
98
96
  if (!account)
99
97
  throw new Error(`Unknown account '${opts.account}'.`);
100
- profile.account = account.id;
98
+ profile.account = account.name;
101
99
  profile.provider = account.provider;
102
100
  }
103
101
  return profile;
@@ -162,7 +160,7 @@ export function buildEdit(name, opts) {
162
160
  const account = findAccount(opts.account);
163
161
  if (!account)
164
162
  throw new Error(`Unknown account '${opts.account}'.`);
165
- edited.account = account.id;
163
+ edited.account = account.name;
166
164
  edited.provider = account.provider;
167
165
  }
168
166
  if (opts.version === '')
@@ -567,7 +565,7 @@ Examples:
567
565
  cmd
568
566
  .command('remove <name>')
569
567
  .alias('rm')
570
- .description('Delete a custom harness (keychain token is kept use `harness logout <provider>` to remove).')
568
+ .description('Delete a custom harness (credentials stay on `agents accounts`; this only drops the named pin).')
571
569
  .action((name) => {
572
570
  const existed = deleteProfile(name);
573
571
  if (!existed) {
@@ -576,50 +574,4 @@ Examples:
576
574
  }
577
575
  console.log(chalk.green(`Harness '${name}' removed.`));
578
576
  });
579
- cmd
580
- .command('login <provider>')
581
- .description('Store or rotate the API key for a provider (e.g., openrouter). Shared across harnesses using that provider.')
582
- .option('--key-stdin', 'Read API key from stdin')
583
- .action(async (provider, opts) => {
584
- try {
585
- const item = keychainItemName(provider);
586
- let token;
587
- if (opts.keyStdin) {
588
- token = readStdinSync();
589
- if (!token)
590
- throw new Error('No key received on stdin.');
591
- }
592
- else {
593
- if (!isInteractiveTerminal()) {
594
- throw new Error('A secret is required but the shell is not interactive. Pipe the key via stdin (--key-stdin).');
595
- }
596
- const { password } = await import('@inquirer/prompts');
597
- token = await password({ message: `Enter API key for ${provider}`, mask: true });
598
- }
599
- setKeychainToken(item, token);
600
- console.log(chalk.green(`Stored in keychain: ${item}`));
601
- }
602
- catch (err) {
603
- console.error(chalk.red(err.message));
604
- process.exit(1);
605
- }
606
- });
607
- cmd
608
- .command('logout <provider>')
609
- .description('Remove a stored provider key from keychain')
610
- .action((provider) => {
611
- try {
612
- const item = keychainItemName(provider);
613
- const existed = deleteKeychainToken(item);
614
- if (!existed) {
615
- console.error(chalk.yellow(`No keychain item '${item}' to remove.`));
616
- process.exit(1);
617
- }
618
- console.log(chalk.green(`Removed keychain item: ${item}`));
619
- }
620
- catch (err) {
621
- console.error(chalk.red(err.message));
622
- process.exit(1);
623
- }
624
- });
625
577
  }
@@ -514,7 +514,7 @@ Examples:
514
514
  }
515
515
  else {
516
516
  console.log(chalk.green(`\nRemoved ${removed} hook(s) from version homes.`));
517
- console.log(chalk.gray('Central source unchanged. Hooks will re-sync on next agent launch.'));
517
+ console.log(chalk.gray("Central source unchanged. Restore any target with 'agents sync <agent>@<version> --yes'."));
518
518
  }
519
519
  });
520
520
  // `hooks prune` moved to the top-level `agents prune cleanup` command.
@@ -31,7 +31,7 @@
31
31
  * the coaching prose by piping the AGGREGATE (never raw transcripts) through a headless
32
32
  * `claude -p`.
33
33
  *
34
- * `agents trends` is a thin deprecated alias of the mix tree only (see commands/trends.ts).
34
+ * Former top-level `agents trends` is `agents insights mix` / `agents insights trends`.
35
35
  */
36
36
  import type { Command } from 'commander';
37
37
  export declare function registerInsightsCommand(program: Command): void;
@@ -31,7 +31,7 @@
31
31
  * the coaching prose by piping the AGGREGATE (never raw transcripts) through a headless
32
32
  * `claude -p`.
33
33
  *
34
- * `agents trends` is a thin deprecated alias of the mix tree only (see commands/trends.ts).
34
+ * Former top-level `agents trends` is `agents insights mix` / `agents insights trends`.
35
35
  */
36
36
  import * as fs from 'fs';
37
37
  import chalk from 'chalk';
@@ -651,7 +651,8 @@ function configureInsightsCommand(cmd) {
651
651
  \`agents insights mix\` — cheap counters from sessions.db + usage.db
652
652
  Latency is \`agents perf\` (not mix). Quota is \`agents usage\`. Spend is
653
653
  \`agents insights cost\`; shipped output is \`agents insights output\`. Skill/slash popularity
654
- is \`agents sessions stats\`. \`agents trends\` is a deprecated alias of the mix tree.
654
+ is \`agents sessions stats\`. Former top-level \`agents trends\` is \`agents insights mix\`
655
+ (also \`agents insights trends\`).
655
656
 
656
657
  The behavioural report parses in-scope transcripts once and caches facets; later runs
657
658
  re-read only files that changed. \`--refresh\` forces a full re-read.
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import chalk from 'chalk';
10
10
  import { setHelpSections } from '../lib/help.js';
11
- import { enableMenubarService, disableMenubarService, getMenubarStatus, runMenubarSetup, } from '../lib/menubar/install-menubar.js';
11
+ import { enableMenubarService, disableMenubarService, getMenubarStatus, runMenubarSetup, buildMenubarDoctorReport, } from '../lib/menubar/install-menubar.js';
12
12
  function notMac() {
13
13
  if (process.platform !== 'darwin') {
14
14
  console.log(chalk.yellow('AGI Menu is macOS only.'));
@@ -77,6 +77,32 @@ function printSetupResult(r) {
77
77
  console.log(chalk.red('AGI Menu not fully configured.') + chalk.gray(' See the failed step above.'));
78
78
  }
79
79
  }
80
+ function printDoctorReport(r) {
81
+ console.log(chalk.bold('AGI Menu doctor\n'));
82
+ console.log(` install path ${r.installPath ? chalk.gray(r.installPath) : chalk.red('not installed')}`);
83
+ console.log(` installed version ${r.installedVersion ? chalk.gray(r.installedVersion) : chalk.gray('unknown')}`);
84
+ console.log(` CLI version ${chalk.gray(r.currentVersion)}${r.versionMatches ? '' : chalk.yellow(' (mismatch — `agents menubar setup` updates it)')}`);
85
+ const identity = r.signingIdentity === 'developer-id'
86
+ ? chalk.green('Developer ID (update-stable)')
87
+ : r.signingIdentity === 'ad-hoc'
88
+ ? chalk.red('ad-hoc (unstable — Accessibility grant breaks on every update)')
89
+ : chalk.gray('unknown (nothing installed)');
90
+ console.log(` signing identity ${identity}`);
91
+ console.log(` running ${r.running ? chalk.green('yes') : chalk.gray('no')}`);
92
+ if (r.staleRunningProcess.length > 0) {
93
+ for (const p of r.staleRunningProcess) {
94
+ const mark = p.stale
95
+ ? chalk.red('started BEFORE the on-disk bundle — running the OLD binary')
96
+ : chalk.green('matches the on-disk bundle');
97
+ console.log(` pid ${String(p.pid).padEnd(10)} ${mark}`);
98
+ }
99
+ }
100
+ if (r.accessibilityHintNeeded) {
101
+ console.log(chalk.yellow('\n Accessibility grant may not be trusted for the current binary.'));
102
+ console.log(chalk.gray(' Run `agents menubar setup` to reinstall + restart the helper, then grant access:'));
103
+ console.log(chalk.gray(' open "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility"'));
104
+ }
105
+ }
80
106
  export function registerMenubarCommands(program) {
81
107
  const menubar = program
82
108
  .command('menubar')
@@ -184,6 +210,37 @@ export function registerMenubarCommands(program) {
184
210
  }
185
211
  printStatus(s);
186
212
  });
213
+ const doctor = menubar
214
+ .command('doctor')
215
+ .description('Diagnose AGI Menu: install path, version skew, signing identity, stale process')
216
+ .option('--json', 'Emit machine-readable JSON')
217
+ .action((options) => {
218
+ const r = buildMenubarDoctorReport();
219
+ if (options.json) {
220
+ process.stdout.write(JSON.stringify(r) + '\n');
221
+ return;
222
+ }
223
+ if (r.platform !== 'darwin') {
224
+ console.log(chalk.yellow('AGI Menu is macOS only.'));
225
+ return;
226
+ }
227
+ printDoctorReport(r);
228
+ });
229
+ setHelpSections(doctor, {
230
+ examples: `
231
+ # Why did Accessibility ask again after an update?
232
+ agents menubar doctor
233
+ `,
234
+ notes: `
235
+ Read-only — never installs, restarts, or resets anything. Reports the
236
+ installed bundle's signing identity (ad-hoc signing breaks the
237
+ Accessibility grant on every update; Developer ID is stable across
238
+ updates) and whether a live helper pid started before the on-disk
239
+ bundle's last write — the sign that a running process is still the
240
+ binary an update just replaced. \`agents menubar setup\` is the fix for
241
+ anything this flags.
242
+ `,
243
+ });
187
244
  // Bare `agents menubar` -> status.
188
245
  menubar.action(() => {
189
246
  const s = getMenubarStatus();
@@ -440,7 +440,7 @@ export function registerMonitorsCommands(program) {
440
440
  .option('--dedupe-key <expr>', 'Regex whose first match is the "same event" signature (default: full output)')
441
441
  .option('--every', 'Fire on every observation (no dedupe) — rate-limit this')
442
442
  // ACTION
443
- .option('--run <agent>', 'Spawn an agent (claude, codex, ...) with the prompt on fire')
443
+ .option('--run <agent>', 'Spawn an agent (claude, codex, ..., or a custom harness from agents harness list) with the prompt on fire')
444
444
  .option('--prompt <prompt>', 'Prompt for --run; {event} is replaced with the fired event')
445
445
  .option('--mode <mode>', 'Execution mode for --run: plan, edit, auto, or skip')
446
446
  .option('--effort <effort>', 'Reasoning effort for --run: low | medium | high | xhigh | max | auto')
@@ -3,4 +3,9 @@ import type { Command } from 'commander';
3
3
  export declare function resolveInviteRole(raw: string | undefined): 'admin' | 'member';
4
4
  /** Validate a role argument before any network call — same rule the backend enforces server-side. */
5
5
  export declare function parseRole(raw: string): 'admin' | 'member';
6
+ /** One-line deprecation for the leftover top-level `agents org` spelling. */
7
+ export declare function printOrgDeprecation(): void;
8
+ /** Canonical home: `agents auth space …`. */
9
+ export declare function registerAuthSpaceCommand(auth: Command): void;
10
+ /** Leftover top-level spelling — same tree, deprecation on every path. */
6
11
  export declare function registerOrgCommand(program: Command): void;
@@ -1,5 +1,6 @@
1
1
  import chalk from 'chalk';
2
2
  import { setHelpSections } from '../lib/help.js';
3
+ import { runOrDie } from '../lib/format.js';
3
4
  import { createSpace, createSpaceInvite, fetchWhoAmI, listSpaceMembers, listSpaces, removeSpaceMember, resolveMemberFromList, resolvePrixToken, resolveSpaceFromList, slugify, updateSpaceMemberRole, } from '../lib/prix-account.js';
4
5
  /** Every `agents org` subcommand needs a token up front — one place to say so. */
5
6
  function requireToken() {
@@ -40,7 +41,7 @@ async function runList(o) {
40
41
  return;
41
42
  }
42
43
  if (!spaces.length) {
43
- console.log(chalk.gray("No spaces. Create one with 'agents org create <name>'."));
44
+ console.log(chalk.gray("No spaces. Create one with 'agents auth space create <name>'."));
44
45
  return;
45
46
  }
46
47
  for (const space of spaces)
@@ -132,43 +133,96 @@ async function runLeave(spaceArg, o) {
132
133
  }
133
134
  console.log(chalk.green(`Left '${space.name}'.`));
134
135
  }
135
- export function registerOrgCommand(program) {
136
- const org = program.command('org').description('Create and manage a team (a Rush "space") you can share with `agents auth login` collaborators');
137
- org.command('create <name>').description('Create a space (free tier: 1 owned space)')
136
+ /** One-line deprecation for the leftover top-level `agents org` spelling. */
137
+ export function printOrgDeprecation() {
138
+ console.error(chalk.yellow('agents org is deprecated use `agents auth space`.'));
139
+ }
140
+ /** Shared space CRUD tree — canonical under `auth space`, leftover under `org`. */
141
+ function attachSpaceCommands(parent, opts) {
142
+ const before = () => {
143
+ if (opts.deprecated)
144
+ printOrgDeprecation();
145
+ };
146
+ const jsonFlag = (o, command) => !!(o.json || command.optsWithGlobals().json);
147
+ parent.command('create <name>').description('Create a space (free tier: 1 owned space)')
138
148
  .option('--slug <slug>', 'Override the derived slug')
139
149
  .option('--description <text>', 'Optional description')
140
150
  .option('--json', 'Machine-readable output')
141
- .action((name, o, command) => runCreate(name, { ...o, json: !!(o.json || command.optsWithGlobals().json) }));
142
- org.command('list').description('List spaces you own or belong to').option('--json', 'Machine-readable output')
143
- .action((o, command) => runList({ json: !!(o.json || command.optsWithGlobals().json) }));
144
- org.command('view [space]').description('Show one space (defaults to your only space)').option('--json', 'Machine-readable output')
145
- .action((space, o, command) => runView(space, { json: !!(o.json || command.optsWithGlobals().json) }));
146
- org.command('invite <email>').description('Invite (or directly add) a member')
151
+ .action((name, o, command) => {
152
+ before();
153
+ const json = jsonFlag(o, command);
154
+ return runOrDie(() => runCreate(name, { ...o, json }), { json });
155
+ });
156
+ parent.command('list').description('List spaces you own or belong to').option('--json', 'Machine-readable output')
157
+ .action((o, command) => {
158
+ before();
159
+ const json = jsonFlag(o, command);
160
+ return runOrDie(() => runList({ json }), { json });
161
+ });
162
+ parent.command('view [space]').description('Show one space (defaults to your only space)').option('--json', 'Machine-readable output')
163
+ .action((space, o, command) => {
164
+ before();
165
+ const json = jsonFlag(o, command);
166
+ return runOrDie(() => runView(space, { json }), { json });
167
+ });
168
+ parent.command('invite <email>').description('Invite (or directly add) a member')
147
169
  .option('--role <role>', 'admin | member', 'member')
148
170
  .option('--space <id-or-slug>', 'Space to invite into (defaults to your only space)')
149
171
  .option('--json', 'Machine-readable output')
150
- .action((email, o, command) => runInvite(email, { ...o, json: !!(o.json || command.optsWithGlobals().json) }));
151
- org.command('members [space]').description('List a space\'s members').option('--json', 'Machine-readable output')
152
- .action((space, o, command) => runMembers(space, { json: !!(o.json || command.optsWithGlobals().json) }));
153
- org.command('role <email> <role>').description('Change a member\'s role (owner-only for admin)')
172
+ .action((email, o, command) => {
173
+ before();
174
+ const json = jsonFlag(o, command);
175
+ return runOrDie(() => runInvite(email, { ...o, json }), { json });
176
+ });
177
+ parent.command('members [space]').description("List a space's members").option('--json', 'Machine-readable output')
178
+ .action((space, o, command) => {
179
+ before();
180
+ const json = jsonFlag(o, command);
181
+ return runOrDie(() => runMembers(space, { json }), { json });
182
+ });
183
+ parent.command('role <email> <role>').description("Change a member's role (owner-only for admin)")
154
184
  .option('--space <id-or-slug>', 'Space to change (defaults to your only space)')
155
185
  .option('--json', 'Machine-readable output')
156
- .action((email, role, o, command) => runRole(email, role, { ...o, json: !!(o.json || command.optsWithGlobals().json) }));
157
- org.command('remove <email>').description('Remove a member from a space')
186
+ .action((email, role, o, command) => {
187
+ before();
188
+ const json = jsonFlag(o, command);
189
+ return runOrDie(() => runRole(email, role, { ...o, json }), { json });
190
+ });
191
+ parent.command('remove <email>').description('Remove a member from a space')
158
192
  .option('--space <id-or-slug>', 'Space to remove from (defaults to your only space)')
159
193
  .option('--json', 'Machine-readable output')
160
- .action((email, o, command) => runRemove(email, { ...o, json: !!(o.json || command.optsWithGlobals().json) }));
161
- org.command('leave [space]').description('Leave a space you do not own').option('--json', 'Machine-readable output')
162
- .action((space, o, command) => runLeave(space, { json: !!(o.json || command.optsWithGlobals().json) }));
163
- setHelpSections(org, {
164
- examples: `agents org create acme-team
165
- agents org list
166
- agents org view acme-team
167
- agents org invite dev@example.com --role admin
168
- agents org members
169
- agents org role dev@example.com admin
170
- agents org remove dev@example.com
171
- agents org leave acme-team`,
172
- notes: 'Maps to the Rush backend\'s /api/v1/spaces (the free-tier team primitive), not /api/v1/orgs. Free tier: 1 owned space, 3 members per space. Every command needs `agents auth login` (or a `rush login` session) first. `--space` is only needed once you belong to more than one space.',
194
+ .action((email, o, command) => {
195
+ before();
196
+ const json = jsonFlag(o, command);
197
+ return runOrDie(() => runRemove(email, { ...o, json }), { json });
198
+ });
199
+ parent.command('leave [space]').description('Leave a space you do not own').option('--json', 'Machine-readable output')
200
+ .action((space, o, command) => {
201
+ before();
202
+ const json = jsonFlag(o, command);
203
+ return runOrDie(() => runLeave(space, { json }), { json });
204
+ });
205
+ setHelpSections(parent, {
206
+ examples: `${opts.prefix} create acme-team
207
+ ${opts.prefix} list
208
+ ${opts.prefix} view acme-team
209
+ ${opts.prefix} invite dev@example.com --role admin
210
+ ${opts.prefix} members
211
+ ${opts.prefix} role dev@example.com admin
212
+ ${opts.prefix} remove dev@example.com
213
+ ${opts.prefix} leave acme-team`,
214
+ notes: opts.deprecated
215
+ ? 'Deprecated alias of `agents auth space`. Maps to the Rush backend\'s /api/v1/spaces (the free-tier team primitive), not /api/v1/orgs. Free tier: 1 owned space, 3 members per space. Every command needs `agents auth login` (or a `rush login` session) first. `--space` is only needed once you belong to more than one space.'
216
+ : 'Maps to the Rush backend\'s /api/v1/spaces (the free-tier team primitive), not /api/v1/orgs. Free tier: 1 owned space, 3 members per space. Every command needs `agents auth login` (or a `rush login` session) first. `--space` is only needed once you belong to more than one space. The leftover `agents org` spelling still works and prints a deprecation line.',
173
217
  });
174
218
  }
219
+ /** Canonical home: `agents auth space …`. */
220
+ export function registerAuthSpaceCommand(auth) {
221
+ const space = auth.command('space').description('Create and manage a Prix space (invite collaborators)');
222
+ attachSpaceCommands(space, { prefix: 'agents auth space' });
223
+ }
224
+ /** Leftover top-level spelling — same tree, deprecation on every path. */
225
+ export function registerOrgCommand(program) {
226
+ const org = program.command('org').description('Deprecated alias of `agents auth space` — create and manage a Prix space');
227
+ attachSpaceCommands(org, { prefix: 'agents org', deprecated: true });
228
+ }
@@ -10,7 +10,7 @@ import { formatUsd, PRICING_VERSION } from '../lib/pricing/index.js';
10
10
  import { formatDuration } from '../lib/session/render.js';
11
11
  import { terminalWidth, truncateToWidth, padToWidth } from '../lib/session/width.js';
12
12
  import { collectGitOutput } from '../lib/output/git-output.js';
13
- import { loadDevices, isControlDevice } from '../lib/devices/registry.js';
13
+ import { loadDevices } from '../lib/devices/registry.js';
14
14
  import { machineId } from '../lib/session/sync/config.js';
15
15
  import { stripClixml } from '../lib/hosts/remote-cmd.js';
16
16
  const execFileAsync = promisify(execFile);
@@ -189,7 +189,7 @@ async function outputAction(options) {
189
189
  const self = machineId();
190
190
  const registry = await loadDevices();
191
191
  const remotes = Object.values(registry)
192
- .filter(d => d.tailscale?.online && d.name !== self && !isControlDevice(d))
192
+ .filter(d => d.tailscale?.online && d.name !== self)
193
193
  .map(d => d.name);
194
194
  if (!options.json)
195
195
  console.error(chalk.gray(`Folding in ${remotes.length} online device${remotes.length !== 1 ? 's' : ''}…`));
@@ -191,7 +191,7 @@ export async function addProfile(name, opts, label = 'Profile') {
191
191
  authEnvVar,
192
192
  });
193
193
  if (account) {
194
- profile.account = account.id;
194
+ profile.account = account.name;
195
195
  profile.provider = account.provider;
196
196
  }
197
197
  if (opts.fromSecrets)
@@ -213,7 +213,7 @@ export async function addProfile(name, opts, label = 'Profile') {
213
213
  }
214
214
  const profile = profileFromPreset(name, preset, opts.version);
215
215
  if (account) {
216
- profile.account = account.id;
216
+ profile.account = account.name;
217
217
  profile.provider = account.provider;
218
218
  }
219
219
  if (opts.fromSecrets)
@@ -12,10 +12,11 @@
12
12
  * agents-cli.
13
13
  * - Runs: routine execution logs, keeping only the last N per job.
14
14
  *
15
- * Sync (additive: copy missing/changed files into version homes) is no longer
16
- * a user-facing verb `syncResourcesToVersion` runs at agent launch and
17
- * applies adds/updates automatically. Pruning, however, is destructive, so it
18
- * stays explicit.
15
+ * Sync (additive: copy missing/changed files into version homes) is the job
16
+ * of `agents sync` / `agents refresh` the shim's launch hook
17
+ * (`agents sync --launch`) is project-scoped only and deliberately skips
18
+ * version-home reconciliation. Pruning, however, is destructive, so it stays
19
+ * explicit.
19
20
  *
20
21
  * Default scope: each agent's currently-pinned default version for orphan
21
22
  * cleanup, plus the standard cross-agent version-dedup pass. Pass `--all`
@@ -13,6 +13,7 @@ import * as yaml from 'yaml';
13
13
  import { isDaemonRunning, signalDaemonReload, startDaemon, stopDaemon, readDaemonLog, getDaemonStatus, getDaemonLogPath, } from '../lib/daemon/daemon.js';
14
14
  import { assertSchedulerEnabled, assertDaemonEnabled, isDaemonEnabled } from '../lib/device-config.js';
15
15
  import { parseAgentVersionSpec, isAgentHardDeprecated, hardDeprecationError, ROUTINE_AGENT_IDS } from '../lib/agents.js';
16
+ import { isCustomHarnessName } from '../lib/profiles.js';
16
17
  import { RUN_STRATEGIES, normalizeRunStrategy } from '../lib/accounting/rotate.js';
17
18
  import { formatRepoLink, REPO_DISPLAY_MAX } from '../lib/routines-format.js';
18
19
  import { listJobs as listAllJobs, deleteJob, readJob, validateJob, writeJob, setJobEnabled, listRuns, routineStats, getLatestRun, getRunDir, getJobPath, isCanonicalRoutineSource, parseAtTime, hasCompletedOneShotRun, isOneShotLikeSchedule, isOneShotRoutine, jobRunsOnThisDevice, checkJobDeviceEligibility, normalizeTriggerEvent, parseHostStrategy, resolveHostStrategy, HOST_STRATEGIES, computeProjectGroupKind, projectGroupKey, projectGroupTitle, projectGroupOrder, normalizeProjects, buildRoutineListJson, fireConditionLabel, nextRunForDisplay, nextRunLabel, localLatestRun, listJobsForDisplay, } from '../lib/scheduling/routines.js';
@@ -897,7 +898,7 @@ export function registerRoutinesCommands(program) {
897
898
  .command('add [nameOrPath]')
898
899
  .description('Create a new routine from a YAML file or inline flags. Starts the scheduler automatically if it is not already running.')
899
900
  .option('-s, --schedule <cron>', 'Cron schedule in standard format (5 fields: minute hour day month weekday)')
900
- .option('-a, --agent <agent[@version]>', `Which agent runs this routine: ${ROUTINE_AGENT_IDS.join(', ')}. An @version pins that exact installed version (same syntax as agents run), persisted as separate agent + version fields.`)
901
+ .option('-a, --agent <agent[@version]>', `Which agent runs this routine: ${ROUTINE_AGENT_IDS.join(', ')}, or a custom harness name (agents harness list). An @version pins that exact installed version (same syntax as agents run), persisted as separate agent + version fields; a custom harness pins its own host version.`)
901
902
  .option('--strategy <strategy>', `Version/account selection strategy for this routine: ${RUN_STRATEGIES.join(' | ')}. Overrides run.<agent>.strategy from the firing device's config. Conflicts with an @version pin.`)
902
903
  .option('--balanced', 'Shortcut for --strategy balanced.')
903
904
  .option('--workflow <name>', 'Run an installed workflow (~/.agents/workflows/<name>) via `agents run`. Mutually exclusive with --agent.')
@@ -984,17 +985,26 @@ export function registerRoutinesCommands(program) {
984
985
  // silently missed a hard-deprecated harness pinned with @version.
985
986
  let parsedAgent;
986
987
  if (options.agent) {
987
- const parsed = parseAgentVersionSpec(options.agent);
988
- if ('error' in parsed) {
989
- console.error(chalk.red(parsed.error));
990
- process.exit(1);
988
+ // A custom harness (agents harness list) is a valid routine agent:
989
+ // the runner delegates it to `agents run <name>`. It pins its own
990
+ // host version, so no @version suffix applies (a suffixed name falls
991
+ // through to the parse error below).
992
+ if (isCustomHarnessName(options.agent)) {
993
+ parsedAgent = { agent: options.agent };
991
994
  }
992
- parsedAgent = parsed;
993
- // Hard-deprecated harnesses cannot be scheduled — refuse at create time
994
- // so no new recurring job silently fails against a retired backend.
995
- if (isAgentHardDeprecated(parsed.agent)) {
996
- console.error(chalk.red(hardDeprecationError(parsed.agent)));
997
- process.exit(1);
995
+ else {
996
+ const parsed = parseAgentVersionSpec(options.agent);
997
+ if ('error' in parsed) {
998
+ console.error(chalk.red(parsed.error));
999
+ process.exit(1);
1000
+ }
1001
+ parsedAgent = parsed;
1002
+ // Hard-deprecated harnesses cannot be scheduled — refuse at create time
1003
+ // so no new recurring job silently fails against a retired backend.
1004
+ if (isAgentHardDeprecated(parsed.agent)) {
1005
+ console.error(chalk.red(hardDeprecationError(parsed.agent)));
1006
+ process.exit(1);
1007
+ }
998
1008
  }
999
1009
  }
1000
1010
  // --strategy / --balanced: per-routine selection policy, same vocabulary
@@ -1377,22 +1387,30 @@ export function registerRoutinesCommands(program) {
1377
1387
  if (options.projectAnchor !== undefined)
1378
1388
  existing.project = options.projectAnchor;
1379
1389
  if (options.agent !== undefined) {
1380
- const parsed = parseAgentVersionSpec(options.agent);
1381
- if ('error' in parsed) {
1382
- console.error(chalk.red(parsed.error));
1383
- process.exit(1);
1390
+ if (isCustomHarnessName(options.agent)) {
1391
+ // Custom harness: delegated to `agents run <name>`; the profile
1392
+ // pins its own host version, so any prior pin is cleared.
1393
+ existing.agent = options.agent;
1394
+ delete existing.version;
1384
1395
  }
1385
- if (isAgentHardDeprecated(parsed.agent)) {
1386
- console.error(chalk.red(hardDeprecationError(parsed.agent)));
1387
- process.exit(1);
1396
+ else {
1397
+ const parsed = parseAgentVersionSpec(options.agent);
1398
+ if ('error' in parsed) {
1399
+ console.error(chalk.red(parsed.error));
1400
+ process.exit(1);
1401
+ }
1402
+ if (isAgentHardDeprecated(parsed.agent)) {
1403
+ console.error(chalk.red(hardDeprecationError(parsed.agent)));
1404
+ process.exit(1);
1405
+ }
1406
+ existing.agent = parsed.agent;
1407
+ // An @version pin replaces any previous pin; a bare agent clears it so
1408
+ // the routine returns to strategy/balanced selection.
1409
+ if (parsed.version)
1410
+ existing.version = parsed.version;
1411
+ else
1412
+ delete existing.version;
1388
1413
  }
1389
- existing.agent = parsed.agent;
1390
- // An @version pin replaces any previous pin; a bare agent clears it so
1391
- // the routine returns to strategy/balanced selection.
1392
- if (parsed.version)
1393
- existing.version = parsed.version;
1394
- else
1395
- delete existing.version;
1396
1414
  }
1397
1415
  if (options.strategy !== undefined) {
1398
1416
  const normalized = normalizeRunStrategy(options.strategy);