@phnx-labs/agents-cli 1.22.34 → 1.22.37

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 (493) hide show
  1. package/CHANGELOG.md +672 -2
  2. package/README.md +82 -35
  3. package/dist/bin/agents +0 -0
  4. package/dist/bootstrap.d.ts +15 -0
  5. package/dist/bootstrap.js +1211 -0
  6. package/dist/commands/accounts.d.ts +4 -11
  7. package/dist/commands/accounts.js +159 -27
  8. package/dist/commands/attach.js +17 -4
  9. package/dist/commands/audit.d.ts +8 -8
  10. package/dist/commands/audit.js +57 -47
  11. package/dist/commands/browser.js +15 -11
  12. package/dist/commands/cloud.js +1 -1
  13. package/dist/commands/commands.js +0 -11
  14. package/dist/commands/computer-actions.js +33 -0
  15. package/dist/commands/computer-sessions-picker.d.ts +21 -0
  16. package/dist/commands/computer-sessions-picker.js +141 -0
  17. package/dist/commands/computer.d.ts +16 -0
  18. package/dist/commands/computer.js +37 -0
  19. package/dist/commands/config.d.ts +17 -0
  20. package/dist/commands/config.js +401 -0
  21. package/dist/commands/cp.d.ts +84 -0
  22. package/dist/commands/cp.js +231 -0
  23. package/dist/commands/daemon.js +243 -14
  24. package/dist/commands/detach-core.d.ts +1 -1
  25. package/dist/commands/detach-core.js +12 -2
  26. package/dist/commands/detach.js +1 -1
  27. package/dist/commands/doctor.js +64 -5
  28. package/dist/commands/events.d.ts +24 -12
  29. package/dist/commands/events.js +144 -28
  30. package/dist/commands/exec.js +85 -41
  31. package/dist/commands/feed.d.ts +1 -1
  32. package/dist/commands/feed.js +16 -8
  33. package/dist/commands/focus.d.ts +33 -1
  34. package/dist/commands/focus.js +177 -17
  35. package/dist/commands/funnel.d.ts +1 -1
  36. package/dist/commands/go.js +9 -8
  37. package/dist/commands/harness-wizard.d.ts +1 -0
  38. package/dist/commands/harness-wizard.js +18 -20
  39. package/dist/commands/harness.d.ts +2 -0
  40. package/dist/commands/harness.js +59 -26
  41. package/dist/commands/hooks.js +0 -11
  42. package/dist/commands/inspect.d.ts +94 -1
  43. package/dist/commands/inspect.js +866 -40
  44. package/dist/commands/lease.d.ts +4 -4
  45. package/dist/commands/lease.js +6 -6
  46. package/dist/commands/login.js +0 -13
  47. package/dist/commands/logs.d.ts +2 -2
  48. package/dist/commands/logs.js +2 -2
  49. package/dist/commands/mcp.js +7 -2
  50. package/dist/commands/memory.js +7 -2
  51. package/dist/commands/message.d.ts +1 -1
  52. package/dist/commands/message.js +37 -6
  53. package/dist/commands/models.js +4 -0
  54. package/dist/commands/monitors.d.ts +0 -1
  55. package/dist/commands/monitors.js +199 -16
  56. package/dist/commands/open.d.ts +18 -0
  57. package/dist/commands/open.js +57 -0
  58. package/dist/commands/packages.js +2 -107
  59. package/dist/commands/plugins.js +6 -3
  60. package/dist/commands/profiles.d.ts +1 -0
  61. package/dist/commands/profiles.js +16 -160
  62. package/dist/commands/projects.d.ts +28 -0
  63. package/dist/commands/projects.js +125 -5
  64. package/dist/commands/reconnect.js +9 -3
  65. package/dist/commands/repo.js +42 -1
  66. package/dist/commands/resource-view.d.ts +17 -0
  67. package/dist/commands/resource-view.js +36 -14
  68. package/dist/commands/resume.js +9 -2
  69. package/dist/commands/routines.d.ts +0 -2
  70. package/dist/commands/routines.js +473 -289
  71. package/dist/commands/secrets.js +52 -9
  72. package/dist/commands/sessions-export.d.ts +47 -0
  73. package/dist/commands/sessions-export.js +116 -6
  74. package/dist/commands/sessions-import.d.ts +17 -0
  75. package/dist/commands/sessions-import.js +114 -6
  76. package/dist/commands/sessions-migrate.d.ts +27 -0
  77. package/dist/commands/sessions-migrate.js +50 -9
  78. package/dist/commands/sessions-picker.js +23 -4
  79. package/dist/commands/sessions-resume.d.ts +5 -3
  80. package/dist/commands/sessions-resume.js +30 -8
  81. package/dist/commands/sessions-watch.d.ts +2 -0
  82. package/dist/commands/sessions-watch.js +53 -0
  83. package/dist/commands/sessions.d.ts +66 -3
  84. package/dist/commands/sessions.js +298 -47
  85. package/dist/commands/set.d.ts +1 -1
  86. package/dist/commands/set.js +2 -2
  87. package/dist/commands/setup-browser.js +2 -2
  88. package/dist/commands/setup-mine.js +0 -1
  89. package/dist/commands/setup-preferences.js +1 -1
  90. package/dist/commands/setup-share.js +34 -4
  91. package/dist/commands/setup.d.ts +5 -0
  92. package/dist/commands/setup.js +26 -0
  93. package/dist/commands/share.d.ts +85 -0
  94. package/dist/commands/share.js +379 -8
  95. package/dist/commands/skills.js +0 -11
  96. package/dist/commands/snapshot.js +1 -1
  97. package/dist/commands/ssh.d.ts +18 -0
  98. package/dist/commands/ssh.js +549 -210
  99. package/dist/commands/sync.d.ts +5 -0
  100. package/dist/commands/sync.js +212 -32
  101. package/dist/commands/teams.d.ts +13 -0
  102. package/dist/commands/teams.js +187 -30
  103. package/dist/commands/tickets.d.ts +2 -0
  104. package/dist/commands/tickets.js +43 -0
  105. package/dist/commands/update.js +2 -4
  106. package/dist/commands/usage.js +5 -3
  107. package/dist/commands/versions.js +11 -5
  108. package/dist/commands/view.js +15 -7
  109. package/dist/commands/webhook.d.ts +2 -2
  110. package/dist/commands/webhook.js +7 -7
  111. package/dist/index.d.ts +18 -2
  112. package/dist/index.js +53 -1264
  113. package/dist/lib/account-catalog.d.ts +18 -0
  114. package/dist/lib/account-catalog.js +38 -0
  115. package/dist/lib/account-provider-registry.d.ts +18 -0
  116. package/dist/lib/account-provider-registry.js +59 -0
  117. package/dist/lib/account-registry.d.ts +40 -0
  118. package/dist/lib/account-registry.js +239 -0
  119. package/dist/lib/account-schema.d.ts +44 -0
  120. package/dist/lib/account-schema.js +91 -0
  121. package/dist/lib/account-state-service.d.ts +21 -0
  122. package/dist/lib/account-state-service.js +60 -0
  123. package/dist/lib/activity.d.ts +1 -1
  124. package/dist/lib/activity.js +8 -1
  125. package/dist/lib/add-dir.d.ts +80 -0
  126. package/dist/lib/add-dir.js +241 -0
  127. package/dist/lib/agent-cli-commands.d.ts +11 -0
  128. package/dist/lib/agent-cli-commands.js +30 -0
  129. package/dist/lib/agents.js +44 -36
  130. package/dist/lib/answer-router.js +1 -1
  131. package/dist/lib/audit/log.d.ts +10 -27
  132. package/dist/lib/audit/log.js +20 -33
  133. package/dist/lib/auth-health.d.ts +9 -2
  134. package/dist/lib/auth-health.js +42 -9
  135. package/dist/lib/auto-pull-worker.js +7 -3
  136. package/dist/lib/auto-pull.d.ts +44 -3
  137. package/dist/lib/auto-pull.js +88 -5
  138. package/dist/lib/binary-shadow.d.ts +28 -0
  139. package/dist/lib/binary-shadow.js +121 -0
  140. package/dist/lib/brand.js +9 -3
  141. package/dist/lib/browser/ipc.d.ts +30 -0
  142. package/dist/lib/browser/ipc.js +81 -4
  143. package/dist/lib/browser/profiles.d.ts +3 -2
  144. package/dist/lib/browser/profiles.js +65 -22
  145. package/dist/lib/browser/service.d.ts +4 -0
  146. package/dist/lib/browser/service.js +30 -3
  147. package/dist/lib/browser/sessions-list.d.ts +22 -2
  148. package/dist/lib/browser/sessions-list.js +54 -6
  149. package/dist/lib/browser/stream.d.ts +2 -0
  150. package/dist/lib/browser/stream.js +1 -0
  151. package/dist/lib/browser/types.d.ts +19 -0
  152. package/dist/lib/byok-usage.d.ts +9 -2
  153. package/dist/lib/byok-usage.js +133 -31
  154. package/dist/lib/cli-entry.d.ts +11 -0
  155. package/dist/lib/cli-entry.js +46 -0
  156. package/dist/lib/cloud/host.d.ts +1 -1
  157. package/dist/lib/cloud/host.js +2 -2
  158. package/dist/lib/cloud/registry.js +1 -1
  159. package/dist/lib/cloud/types.d.ts +1 -1
  160. package/dist/lib/codex-policy.d.ts +9 -1
  161. package/dist/lib/codex-policy.js +17 -2
  162. package/dist/lib/commands.d.ts +2 -0
  163. package/dist/lib/commands.js +2 -0
  164. package/dist/lib/computer/sessions-list.d.ts +150 -0
  165. package/dist/lib/computer/sessions-list.js +363 -0
  166. package/dist/lib/config-keys.d.ts +86 -0
  167. package/dist/lib/config-keys.js +193 -0
  168. package/dist/lib/config-machine-keys.d.ts +12 -0
  169. package/dist/lib/config-machine-keys.js +17 -0
  170. package/dist/lib/config-transfer.js +3 -4
  171. package/dist/lib/crabbox/cli.d.ts +2 -2
  172. package/dist/lib/crabbox/cli.js +3 -3
  173. package/dist/lib/daemon-health.d.ts +22 -0
  174. package/dist/lib/daemon-health.js +33 -3
  175. package/dist/lib/daemon-services.d.ts +50 -0
  176. package/dist/lib/daemon-services.js +147 -0
  177. package/dist/lib/daemon-ticks.d.ts +35 -66
  178. package/dist/lib/daemon-ticks.js +77 -159
  179. package/dist/lib/daemon.d.ts +72 -4
  180. package/dist/lib/daemon.js +792 -202
  181. package/dist/lib/deeplink/register.d.ts +49 -0
  182. package/dist/lib/deeplink/register.js +253 -0
  183. package/dist/lib/deeplink/url.d.ts +34 -0
  184. package/dist/lib/deeplink/url.js +78 -0
  185. package/dist/lib/device-config.d.ts +124 -29
  186. package/dist/lib/device-config.js +346 -141
  187. package/dist/lib/devices/config-migration.d.ts +42 -0
  188. package/dist/lib/devices/config-migration.js +203 -0
  189. package/dist/lib/devices/connect.js +14 -7
  190. package/dist/lib/devices/doctor-findings.d.ts +4 -1
  191. package/dist/lib/devices/doctor-findings.js +20 -1
  192. package/dist/lib/devices/harness-inventory.d.ts +11 -2
  193. package/dist/lib/devices/harness-inventory.js +0 -0
  194. package/dist/lib/devices/health.d.ts +1 -1
  195. package/dist/lib/devices/health.js +1 -1
  196. package/dist/lib/devices/pending.d.ts +22 -1
  197. package/dist/lib/devices/pending.js +67 -2
  198. package/dist/lib/devices/registry.d.ts +0 -25
  199. package/dist/lib/devices/registry.js +2 -83
  200. package/dist/lib/devices/resolve-profile.d.ts +20 -0
  201. package/dist/lib/devices/resolve-profile.js +44 -0
  202. package/dist/lib/devices/rollout-verify.d.ts +101 -0
  203. package/dist/lib/devices/rollout-verify.js +193 -0
  204. package/dist/lib/devices/ssh-config.js +10 -6
  205. package/dist/lib/event-families.d.ts +28 -0
  206. package/dist/lib/event-families.js +159 -0
  207. package/dist/lib/event-stream.d.ts +9 -1
  208. package/dist/lib/event-stream.js +26 -9
  209. package/dist/lib/events.d.ts +9 -1
  210. package/dist/lib/events.js +21 -6
  211. package/dist/lib/exec.d.ts +30 -0
  212. package/dist/lib/exec.js +220 -122
  213. package/dist/lib/feed-broadcast.d.ts +25 -0
  214. package/dist/lib/feed-broadcast.js +33 -0
  215. package/dist/lib/feed.d.ts +1 -1
  216. package/dist/lib/feed.js +12 -2
  217. package/dist/lib/fleet/capture.js +15 -0
  218. package/dist/lib/fleet/manifest.js +9 -0
  219. package/dist/lib/fleet/types.d.ts +9 -0
  220. package/dist/lib/fleet-status.js +10 -8
  221. package/dist/lib/git.d.ts +20 -6
  222. package/dist/lib/git.js +121 -17
  223. package/dist/lib/hosts/dispatch.d.ts +8 -3
  224. package/dist/lib/hosts/dispatch.js +29 -3
  225. package/dist/lib/hosts/logs.d.ts +2 -2
  226. package/dist/lib/hosts/logs.js +2 -2
  227. package/dist/lib/hosts/option.js +1 -1
  228. package/dist/lib/hosts/passthrough.d.ts +2 -3
  229. package/dist/lib/hosts/passthrough.js +10 -24
  230. package/dist/lib/hosts/progress.js +2 -2
  231. package/dist/lib/hosts/providers/devices.d.ts +3 -3
  232. package/dist/lib/hosts/providers/devices.js +18 -12
  233. package/dist/lib/hosts/ready.d.ts +5 -1
  234. package/dist/lib/hosts/ready.js +9 -3
  235. package/dist/lib/hosts/reconcile.d.ts +2 -2
  236. package/dist/lib/hosts/reconcile.js +2 -2
  237. package/dist/lib/hosts/registry.js +14 -9
  238. package/dist/lib/hosts/remote-cmd.js +1 -1
  239. package/dist/lib/hosts/remote-os.d.ts +9 -6
  240. package/dist/lib/hosts/remote-os.js +13 -6
  241. package/dist/lib/hosts/routing-flag.d.ts +23 -0
  242. package/dist/lib/hosts/routing-flag.js +41 -0
  243. package/dist/lib/hosts/run-target.js +1 -1
  244. package/dist/lib/hosts/session-index.js +1 -1
  245. package/dist/lib/hosts/tasks.d.ts +6 -6
  246. package/dist/lib/hosts/tasks.js +4 -4
  247. package/dist/lib/hosts/types.js +1 -2
  248. package/dist/lib/installations/resolve.js +2 -8
  249. package/dist/lib/isolation-boundary-report.js +0 -1
  250. package/dist/lib/linear-cache.d.ts +7 -0
  251. package/dist/lib/linear-cache.js +31 -4
  252. package/dist/lib/linear-project-counts.d.ts +1 -1
  253. package/dist/lib/linear-project-counts.js +2 -3
  254. package/dist/lib/linear-projects.js +1 -1
  255. package/dist/lib/loop.d.ts +2 -2
  256. package/dist/lib/loop.js +1 -1
  257. package/dist/lib/mailbox-target.d.ts +1 -1
  258. package/dist/lib/mailbox-target.js +1 -1
  259. package/dist/lib/manifest.js +7 -4
  260. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  261. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  262. package/dist/lib/menubar/snapshot.d.ts +3 -2
  263. package/dist/lib/menubar/snapshot.js +6 -2
  264. package/dist/lib/migrate-fold.d.ts +33 -0
  265. package/dist/lib/migrate-fold.js +130 -0
  266. package/dist/lib/migrate.d.ts +34 -34
  267. package/dist/lib/migrate.js +106 -128
  268. package/dist/lib/model-tier-overrides.d.ts +1 -1
  269. package/dist/lib/model-tier-overrides.js +15 -2
  270. package/dist/lib/monitors/config.d.ts +17 -3
  271. package/dist/lib/monitors/config.js +52 -18
  272. package/dist/lib/monitors/engine.d.ts +32 -1
  273. package/dist/lib/monitors/engine.js +94 -11
  274. package/dist/lib/monitors/fingerprint.d.ts +46 -0
  275. package/dist/lib/monitors/fingerprint.js +100 -0
  276. package/dist/lib/monitors/remote.d.ts +47 -0
  277. package/dist/lib/monitors/remote.js +81 -0
  278. package/dist/lib/monitors/state.d.ts +39 -0
  279. package/dist/lib/monitors/state.js +56 -0
  280. package/dist/lib/onepassword.d.ts +17 -0
  281. package/dist/lib/onepassword.js +52 -22
  282. package/dist/lib/open-url.js +1 -1
  283. package/dist/lib/picker.d.ts +14 -2
  284. package/dist/lib/picker.js +35 -13
  285. package/dist/lib/placement.d.ts +1 -1
  286. package/dist/lib/placement.js +1 -1
  287. package/dist/lib/pr-land-detach.d.ts +32 -0
  288. package/dist/lib/pr-land-detach.js +79 -0
  289. package/dist/lib/profiles-presets.js +13 -0
  290. package/dist/lib/profiles.d.ts +2 -0
  291. package/dist/lib/profiles.js +44 -1
  292. package/dist/lib/project-focus.d.ts +1 -1
  293. package/dist/lib/project-focus.js +1 -1
  294. package/dist/lib/project-key.d.ts +17 -0
  295. package/dist/lib/project-key.js +26 -0
  296. package/dist/lib/project-probe.d.ts +4 -1
  297. package/dist/lib/project-probe.js +5 -4
  298. package/dist/lib/project-resources.js +2 -18
  299. package/dist/lib/project-root.d.ts +16 -0
  300. package/dist/lib/project-root.js +20 -2
  301. package/dist/lib/projects.d.ts +20 -1
  302. package/dist/lib/projects.js +78 -1
  303. package/dist/lib/refresh-coordinator.d.ts +24 -0
  304. package/dist/lib/refresh-coordinator.js +52 -0
  305. package/dist/lib/registry.d.ts +2 -2
  306. package/dist/lib/registry.js +1 -1
  307. package/dist/lib/resource-aliases.d.ts +13 -0
  308. package/dist/lib/resource-aliases.js +26 -0
  309. package/dist/lib/resources/types.d.ts +1 -1
  310. package/dist/lib/resources.d.ts +16 -0
  311. package/dist/lib/resources.js +66 -10
  312. package/dist/lib/rotate.d.ts +3 -4
  313. package/dist/lib/rotate.js +4 -5
  314. package/dist/lib/routine-activation.d.ts +22 -2
  315. package/dist/lib/routine-activation.js +63 -16
  316. package/dist/lib/routine-context.d.ts +9 -2
  317. package/dist/lib/routine-context.js +21 -6
  318. package/dist/lib/routines-project.d.ts +58 -48
  319. package/dist/lib/routines-project.js +190 -159
  320. package/dist/lib/routines.d.ts +41 -6
  321. package/dist/lib/routines.js +92 -34
  322. package/dist/lib/run-defaults.d.ts +5 -0
  323. package/dist/lib/run-defaults.js +11 -1
  324. package/dist/lib/runner.d.ts +10 -8
  325. package/dist/lib/runner.js +122 -39
  326. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  327. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  328. package/dist/lib/secrets/agent.d.ts +10 -3
  329. package/dist/lib/secrets/agent.js +43 -8
  330. package/dist/lib/secrets/bundles.d.ts +1 -0
  331. package/dist/lib/secrets/bundles.js +92 -80
  332. package/dist/lib/secrets/index.js +32 -9
  333. package/dist/lib/secrets/install-helper.d.ts +4 -4
  334. package/dist/lib/secrets/install-helper.js +4 -4
  335. package/dist/lib/secrets/push.d.ts +40 -6
  336. package/dist/lib/secrets/push.js +58 -11
  337. package/dist/lib/secrets/read-backoff.d.ts +1 -1
  338. package/dist/lib/secrets/read-backoff.js +1 -1
  339. package/dist/lib/secrets/remote.d.ts +24 -0
  340. package/dist/lib/secrets/remote.js +54 -4
  341. package/dist/lib/self-update.d.ts +108 -0
  342. package/dist/lib/self-update.js +276 -1
  343. package/dist/lib/session/active.d.ts +89 -5
  344. package/dist/lib/session/active.js +165 -10
  345. package/dist/lib/session/actor-sidecar.d.ts +1 -1
  346. package/dist/lib/session/actor-sidecar.js +2 -1
  347. package/dist/lib/session/db.d.ts +176 -2
  348. package/dist/lib/session/db.js +484 -17
  349. package/dist/lib/session/discover.js +27 -4
  350. package/dist/lib/session/hook-sessions.js +2 -2
  351. package/dist/lib/session/host-link.d.ts +2 -2
  352. package/dist/lib/session/host-link.js +2 -2
  353. package/dist/lib/session/pid-registry.d.ts +21 -0
  354. package/dist/lib/session/pid-registry.js +57 -0
  355. package/dist/lib/session/remote-active.js +15 -1
  356. package/dist/lib/session/remote.js +2 -2
  357. package/dist/lib/session/session-cache.d.ts +14 -7
  358. package/dist/lib/session/session-cache.js +51 -9
  359. package/dist/lib/session/state.d.ts +2 -2
  360. package/dist/lib/session/state.js +1 -1
  361. package/dist/lib/session/sync/config.d.ts +10 -10
  362. package/dist/lib/session/sync/config.js +10 -10
  363. package/dist/lib/session/sync/r2.d.ts +34 -0
  364. package/dist/lib/session/sync/r2.js +123 -0
  365. package/dist/lib/session/throughput.d.ts +1 -1
  366. package/dist/lib/session/throughput.js +1 -1
  367. package/dist/lib/session/types.d.ts +23 -1
  368. package/dist/lib/session/types.js +15 -0
  369. package/dist/lib/session/viewing-in.d.ts +1 -1
  370. package/dist/lib/session/viewing-in.js +1 -1
  371. package/dist/lib/session/watch.d.ts +101 -0
  372. package/dist/lib/session/watch.js +242 -0
  373. package/dist/lib/share/config.d.ts +4 -0
  374. package/dist/lib/share/config.js +1 -0
  375. package/dist/lib/share/delete.d.ts +93 -0
  376. package/dist/lib/share/delete.js +127 -0
  377. package/dist/lib/share/provision.d.ts +38 -0
  378. package/dist/lib/share/provision.js +50 -0
  379. package/dist/lib/share/publish.d.ts +40 -1
  380. package/dist/lib/share/publish.js +116 -3
  381. package/dist/lib/share/worker-template.js +71 -6
  382. package/dist/lib/shims.d.ts +17 -3
  383. package/dist/lib/shims.js +136 -27
  384. package/dist/lib/signin-badge.js +3 -2
  385. package/dist/lib/skills.js +2 -0
  386. package/dist/lib/smart-launch.d.ts +8 -8
  387. package/dist/lib/smart-launch.js +80 -59
  388. package/dist/lib/ssh-exec.d.ts +1 -1
  389. package/dist/lib/ssh-exec.js +1 -1
  390. package/dist/lib/staleness/prune.d.ts +88 -0
  391. package/dist/lib/staleness/prune.js +69 -0
  392. package/dist/lib/staleness/registry.d.ts +1 -1
  393. package/dist/lib/staleness/writers/commands.js +34 -2
  394. package/dist/lib/staleness/writers/hooks.js +16 -0
  395. package/dist/lib/staleness/writers/kinds.d.ts +6 -0
  396. package/dist/lib/staleness/writers/skills.js +19 -0
  397. package/dist/lib/staleness/writers/subagents.d.ts +2 -3
  398. package/dist/lib/staleness/writers/subagents.js +0 -4
  399. package/dist/lib/staleness/writers/types.d.ts +32 -0
  400. package/dist/lib/startup/command-registry.d.ts +6 -12
  401. package/dist/lib/startup/command-registry.js +14 -31
  402. package/dist/lib/startup/dev-build.d.ts +10 -0
  403. package/dist/lib/startup/dev-build.js +13 -1
  404. package/dist/lib/startup/spellcheck.d.ts +18 -0
  405. package/dist/lib/startup/spellcheck.js +45 -0
  406. package/dist/lib/state.d.ts +36 -9
  407. package/dist/lib/state.js +96 -46
  408. package/dist/lib/subagents-registry.d.ts +0 -7
  409. package/dist/lib/subagents-registry.js +38 -65
  410. package/dist/lib/subagents.d.ts +0 -33
  411. package/dist/lib/subagents.js +0 -75
  412. package/dist/lib/sync-umbrella.js +1 -1
  413. package/dist/lib/teams/agents.d.ts +41 -1
  414. package/dist/lib/teams/agents.js +187 -9
  415. package/dist/lib/teams/api.d.ts +11 -1
  416. package/dist/lib/teams/api.js +11 -2
  417. package/dist/lib/teams/delivery.d.ts +41 -0
  418. package/dist/lib/teams/delivery.js +60 -0
  419. package/dist/lib/teams/registry.d.ts +14 -0
  420. package/dist/lib/teams/registry.js +51 -1
  421. package/dist/lib/teams/remoteWorktree.d.ts +19 -0
  422. package/dist/lib/teams/remoteWorktree.js +29 -0
  423. package/dist/lib/teams/scheduler.d.ts +1 -1
  424. package/dist/lib/teams/scheduler.js +1 -1
  425. package/dist/lib/teams/worktree.d.ts +24 -0
  426. package/dist/lib/teams/worktree.js +42 -0
  427. package/dist/lib/terminal/backends/vscodium-agent.d.ts +10 -1
  428. package/dist/lib/terminal/backends/vscodium-agent.js +16 -5
  429. package/dist/lib/terminal/engine.d.ts +4 -0
  430. package/dist/lib/terminal/engine.js +8 -2
  431. package/dist/lib/terminal/types.d.ts +21 -2
  432. package/dist/lib/testdata/refresh-coordinator-worker.d.ts +1 -0
  433. package/dist/lib/testdata/refresh-coordinator-worker.js +23 -0
  434. package/dist/lib/tickets/list.d.ts +53 -0
  435. package/dist/lib/tickets/list.js +153 -0
  436. package/dist/lib/tmux/orphan-reap.d.ts +320 -0
  437. package/dist/lib/tmux/orphan-reap.js +644 -0
  438. package/dist/lib/tmux/session.d.ts +68 -3
  439. package/dist/lib/tmux/session.js +95 -5
  440. package/dist/lib/triggers/handlers.d.ts +20 -0
  441. package/dist/lib/triggers/handlers.js +6 -1
  442. package/dist/lib/types.d.ts +46 -34
  443. package/dist/lib/usage-fleet.d.ts +32 -0
  444. package/dist/lib/usage-fleet.js +125 -0
  445. package/dist/lib/usage-refresh.d.ts +3 -3
  446. package/dist/lib/usage-refresh.js +19 -16
  447. package/dist/lib/usage.d.ts +51 -47
  448. package/dist/lib/usage.js +145 -153
  449. package/dist/lib/version-duplicates.js +2 -2
  450. package/dist/lib/versions.d.ts +56 -13
  451. package/dist/lib/versions.js +195 -45
  452. package/dist/lib/watchdog/log.d.ts +1 -1
  453. package/dist/lib/watchdog/log.js +3 -3
  454. package/dist/lib/watchdog/rotate.d.ts +6 -6
  455. package/dist/lib/watchdog/rotate.js +6 -6
  456. package/dist/lib/workflows.d.ts +1 -1
  457. package/dist/lib/yaml-io.d.ts +47 -0
  458. package/dist/lib/yaml-io.js +55 -0
  459. package/package.json +2 -1
  460. package/scripts/install-helper.js +2 -2
  461. package/scripts/postinstall.js +23 -20
  462. package/dist/commands/defaults.d.ts +0 -7
  463. package/dist/commands/defaults.js +0 -107
  464. package/dist/commands/export.d.ts +0 -11
  465. package/dist/commands/export.js +0 -215
  466. package/dist/commands/helper.d.ts +0 -12
  467. package/dist/commands/helper.js +0 -87
  468. package/dist/commands/hosts.d.ts +0 -11
  469. package/dist/commands/hosts.js +0 -330
  470. package/dist/commands/lock.d.ts +0 -12
  471. package/dist/commands/lock.js +0 -70
  472. package/dist/commands/pull.d.ts +0 -17
  473. package/dist/commands/pull.js +0 -39
  474. package/dist/commands/push.d.ts +0 -14
  475. package/dist/commands/push.js +0 -30
  476. package/dist/commands/wallet.d.ts +0 -20
  477. package/dist/commands/wallet.js +0 -216
  478. package/dist/commands/worktree.d.ts +0 -19
  479. package/dist/commands/worktree.js +0 -272
  480. package/dist/lib/account-labels.d.ts +0 -24
  481. package/dist/lib/account-labels.js +0 -72
  482. package/dist/lib/auto-dispatch-linear.d.ts +0 -18
  483. package/dist/lib/auto-dispatch-linear.js +0 -107
  484. package/dist/lib/auto-dispatch-provider.d.ts +0 -10
  485. package/dist/lib/auto-dispatch-provider.js +0 -30
  486. package/dist/lib/auto-dispatch.d.ts +0 -93
  487. package/dist/lib/auto-dispatch.js +0 -128
  488. package/dist/lib/export.d.ts +0 -72
  489. package/dist/lib/export.js +0 -269
  490. package/dist/lib/lock.d.ts +0 -93
  491. package/dist/lib/lock.js +0 -207
  492. package/dist/lib/wallet/index.d.ts +0 -78
  493. package/dist/lib/wallet/index.js +0 -253
@@ -0,0 +1,1211 @@
1
+ /**
2
+ * Full CLI bootstrap — loaded only after index.ts's argv fast paths miss.
3
+ *
4
+ * RUSH-2335: `src/index.ts` is a slim shell that statically imports only the
5
+ * leaf `lib/secrets/sync-commands.js` so `__secrets-*` / `__vault-age-helper` /
6
+ * `__shim` / `__daemon-run` can exit without evaluating the
7
+ * commander + self-update + command-registry graph (~140ms saved per
8
+ * synchronous broker read). Everything below that shell lands here via
9
+ * `await import('./bootstrap.js')`.
10
+ *
11
+ * This module is the previous body of `index.ts` (command registration, update
12
+ * checks, first-run setup, migrations, parse). Side-effecting top-level code
13
+ * runs on import — that is intentional.
14
+ */
15
+ import { Command } from 'commander';
16
+ import chalk from 'chalk';
17
+ import * as fs from 'fs';
18
+ import * as path from 'path';
19
+ import { fileURLToPath } from 'url';
20
+ import { detectDevBuild } from './lib/startup/dev-build.js';
21
+ import { configureRootCommand } from './lib/startup/root-command.js';
22
+ // `ora`, `@inquirer/prompts`, `./commands/utils.js`, and the agents/versions/shims
23
+ // modules are imported dynamically at their use sites: they are needed only on
24
+ // interactive / update / shim-repair paths, never for fast commands like
25
+ // `--version`, `--help`, or `view`. Keeping them off the module-eval path is
26
+ // what gets cold starts under the target.
27
+ // Get version from package.json
28
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
29
+ const packageJsonPath = path.join(__dirname, '..', 'package.json');
30
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
31
+ const VERSION = packageJson.version;
32
+ import { NPM_PACKAGE_NAME, deriveGlobalPrefix, detectPackageManager, installPackageIntoPrefix, installPackageWithBun, verifyInstalledVersion, refreshAliasShims, downloadVerifiedTarball, } from './lib/self-update.js';
33
+ import { registerUpgradeCommand } from './commands/upgrade.js';
34
+ // Detect dev/working-tree builds and default the noisy startup steps off.
35
+ // Three cases trip this:
36
+ // 1. Dev install (scripts/install.sh) — package.json version stamped 0.0.0-dev.<sha>
37
+ // 2. Running `node dist/index.js` from a working tree — repo root has .git/
38
+ // 3. Running tsx/ts-node from src/ — also has .git/ at the repo root
39
+ // For all three: skip auto-pull (no network noise + no surprise FF on the
40
+ // system repo while iterating), skip migration (a buggy in-progress migration
41
+ // must not scribble on the user's real ~/.agents/), and skip the update prompt
42
+ // (the "0.0.0-dev -> 1.x.y" message is misleading). Each individual env var
43
+ // can still be set explicitly to override (set to '0' to re-enable).
44
+ const IS_DEV_BUILD = detectDevBuild(process.argv[1] || '', VERSION);
45
+ if (IS_DEV_BUILD) {
46
+ if (process.env.AGENTS_NO_AUTOPULL === undefined)
47
+ process.env.AGENTS_NO_AUTOPULL = '1';
48
+ if (process.env.AGENTS_SKIP_MIGRATION === undefined)
49
+ process.env.AGENTS_SKIP_MIGRATION = '1';
50
+ if (process.env.AGENTS_CLI_DISABLE_AUTO_UPDATE === undefined)
51
+ process.env.AGENTS_CLI_DISABLE_AUTO_UPDATE = '1';
52
+ }
53
+ // Command registration is lazy: instead of statically importing every command
54
+ // module on each invocation (which loaded the whole ~50-module tree before the
55
+ // first byte of output), the registry maps a command name to a thunk that
56
+ // imports only what that command needs. See src/lib/startup/command-registry.ts.
57
+ // Individual load* registrars are not imported here — registerEagerForRequest
58
+ // and the lazy path pull them via COMMAND_LOADERS. The full-tree
59
+ // registerAllEagerCommands path was removed (RUSH-2329): unknown/typo commands
60
+ // spellcheck against KNOWN_TOP_LEVEL_COMMANDS and register only the corrected
61
+ // name.
62
+ import { COMMAND_LOADERS, LAZY_COMMAND_NAMES, KNOWN_TOP_LEVEL_COMMANDS, RETIRED_TOP_LEVEL_COMMANDS, } from './lib/startup/command-registry.js';
63
+ import { closestTopLevelCommand } from './lib/startup/spellcheck.js';
64
+ import { applyGlobalHelpConventions } from './lib/help.js';
65
+ import { renderWhatsNew } from './lib/whats-new.js';
66
+ import { getCliLaunch } from './lib/cli-entry.js';
67
+ import { emit, emitFriction, redactArgs } from './lib/events.js';
68
+ import { stampProvenance } from './lib/event-provenance.js';
69
+ import { die } from './lib/format.js';
70
+ // Leaf (zero imports). Gates the dynamic passthrough import so the ~187ms
71
+ // hosts graph is never loaded when no routing flag is present (RUSH-2374).
72
+ import { hasHostRoutingFlag } from './lib/hosts/routing-flag.js';
73
+ // White-label: the shim for a brand (e.g. `jack`) exports AGENTS_BRAND, so the
74
+ // CLI presents its own name/help/errors as the brand. Unbranded (AGENTS_BRAND
75
+ // unset) resolves to 'agents' and everything below is byte-identical to before.
76
+ const BRAND = resolveBrandName();
77
+ const program = configureRootCommand(new Command(), BRAND, VERSION);
78
+ // ─── Audit backbone ────────────────────────────────────────────────────────────
79
+ // One choke point logs every `agents <module> <cmd>` invocation to the structured
80
+ // event log — so team create/disband, agent run, secrets access, and everything
81
+ // else is captured generically (with SSH/remote-user attribution added in emit()),
82
+ // no per-command wiring. `agents events` reads it back. Attached to the root
83
+ // program, so it's inherited by every subcommand regardless of lazy registration.
84
+ /** Command path from the acting command up to (but excluding) the `agents` root. */
85
+ function auditCommandPath(cmd) {
86
+ const parts = [];
87
+ let c = cmd;
88
+ while (c && c.name() && c.name() !== BRAND) {
89
+ parts.unshift(c.name());
90
+ c = c.parent;
91
+ }
92
+ return parts;
93
+ }
94
+ const auditStarts = new WeakMap();
95
+ /**
96
+ * Commands that WRITE the event stream, so recording their own invocation would
97
+ * add records to the log they are writing into. `events emit` is batched — one
98
+ * flush every few seconds per open editor window — so auditing it would bury the
99
+ * real events under two `command.*` records per flush. `_internal friction`
100
+ * exists for the same reason (shell guards fire before any `agents` process
101
+ * exists, so they cannot emit in-process) and had the same self-logging bug.
102
+ */
103
+ const AUDIT_EXEMPT_COMMANDS = new Set([
104
+ 'events emit',
105
+ '_internal friction',
106
+ ]);
107
+ program.hook('preAction', (_thisCommand, actionCommand) => {
108
+ try {
109
+ const parts = auditCommandPath(actionCommand);
110
+ if (parts.length === 0)
111
+ return;
112
+ if (AUDIT_EXEMPT_COMMANDS.has(parts.join(' ')))
113
+ return;
114
+ auditStarts.set(actionCommand, Date.now());
115
+ emit('command.start', {
116
+ module: parts[0],
117
+ command: parts.join(' '),
118
+ // Commander exposes positional operands in actionCommand.args but omits
119
+ // parsed option values. Audit the real argv so sensitive flags are seen
120
+ // and redacted instead of silently bypassing the policy.
121
+ args: redactArgs(process.argv.slice(2, 22)),
122
+ cwd: process.cwd(),
123
+ });
124
+ }
125
+ catch {
126
+ // Audit logging must never break command dispatch.
127
+ }
128
+ });
129
+ program.hook('postAction', (_thisCommand, actionCommand) => {
130
+ try {
131
+ const parts = auditCommandPath(actionCommand);
132
+ if (parts.length === 0)
133
+ return;
134
+ if (AUDIT_EXEMPT_COMMANDS.has(parts.join(' ')))
135
+ return;
136
+ const started = auditStarts.get(actionCommand);
137
+ const durationMs = started !== undefined ? Date.now() - started : undefined;
138
+ const command = parts.join(' ');
139
+ emit('command.end', {
140
+ module: parts[0],
141
+ command,
142
+ ...(durationMs !== undefined ? { durationMs } : {}),
143
+ });
144
+ if (parts[0] === 'run') {
145
+ const agentName = actionCommand.args?.[0] ? String(actionCommand.args[0]).split('@')[0] : 'run';
146
+ void import('./lib/analytics/usage-db.js').then(({ recordUsage }) => {
147
+ recordUsage({
148
+ kind: 'agent',
149
+ name: agentName || 'run',
150
+ event: 'invoke',
151
+ source: 'cli',
152
+ meta: durationMs !== undefined ? { durationMs } : undefined,
153
+ });
154
+ }).catch(() => { });
155
+ }
156
+ // Disposable perf warehouse — fail-soft spool append (no SQLite on this path).
157
+ if (durationMs !== undefined && parts[0] !== 'perf') {
158
+ // sessionId/agent are resolvable here the same way emit() resolves them
159
+ // for command.start/command.end above (the shared provenance floor,
160
+ // event-provenance.ts) — without this, every command.end perf sample
161
+ // was anonymous, unlike the audit log record right next to it.
162
+ const { sessionId, agent } = stampProvenance();
163
+ void import('./lib/perf/spool.js').then(({ recordSample }) => {
164
+ recordSample({
165
+ kind: 'command.end',
166
+ label: command,
167
+ durationMs,
168
+ cwd: process.cwd(),
169
+ sessionId,
170
+ agent,
171
+ });
172
+ }).catch(() => { });
173
+ }
174
+ }
175
+ catch {
176
+ // Best-effort completion record; the start line is the durable audit fact.
177
+ }
178
+ });
179
+ /**
180
+ * Skin the static root help for a brand: rewrite the visible `agents` command
181
+ * examples to the brand name and drop lines for commands this brand disabled.
182
+ * A no-op for the unbranded `agents` CLI with nothing disabled.
183
+ */
184
+ function brandRootHelp(raw) {
185
+ let text = raw;
186
+ if (BRAND !== 'agents') {
187
+ text = text
188
+ .replace(/Usage: agents /g, `Usage: ${BRAND} `)
189
+ .replace(/^ {2}agents /gm, ` ${BRAND} `)
190
+ .replace(/Run 'agents /g, `Run '${BRAND} `);
191
+ }
192
+ const disabled = disabledCommandsForActiveBrand();
193
+ if (disabled.size > 0) {
194
+ text = text
195
+ .split('\n')
196
+ .filter((line) => {
197
+ const m = line.match(/^ {2}([a-z][\w-]*)/);
198
+ return !(m && disabled.has(m[1]));
199
+ })
200
+ .join('\n');
201
+ }
202
+ return text;
203
+ }
204
+ // Custom help for the main program only
205
+ const originalHelpInformation = program.helpInformation.bind(program);
206
+ program.helpInformation = function () {
207
+ if (this.name() === BRAND && !this.parent) {
208
+ return brandRootHelp(`Usage: agents [command] [options]
209
+
210
+ Install, configure, run, and dispatch AI coding agents from one place.
211
+ Works with Claude, Codex, Antigravity, Cursor, OpenCode, OpenClaw, and Droid.
212
+
213
+ Quick start:
214
+ agents setup First-time setup (interactive)
215
+ agents view See what's installed
216
+ agents run <agent> ["prompt"] Run an agent (interactive without prompt, headless with)
217
+ agents sessions Browse past sessions across all agents
218
+
219
+ Agent versions:
220
+ add <agent>[@version] Install an agent CLI (e.g. agents add grok or agents add codex)
221
+ import <agent> Adopt an existing global install (npm/homebrew) into agents-cli
222
+ update <agent>[@version] Move an installed agent to a new release, keeping its name (agents-cli itself is 'agents upgrade')
223
+ prune <agent>[@version] Uninstall a version
224
+ remove <agent>[@version] Alias for prune
225
+ use <agent>@<version> Set the default version
226
+ prune cleanup [target] Remove orphan resources and older duplicate version installs
227
+ trash Inspect and restore soft-deleted version directories
228
+ view [agent[@version]] List versions, inspect one in detail, or --merged for the cross-layer resource surface
229
+ inspect <target> Deep details for one agent+version, or a DotAgents repo (user|system|project|alias|path)
230
+
231
+ Agent configuration (synced across versions):
232
+ rules Instructions given to agents (CLAUDE.md, etc.)
233
+ commands Slash commands (/commit, /test, etc.)
234
+ skills Knowledge packs (SKILL.md + supporting files)
235
+ mcp MCP servers (stdio or HTTP)
236
+ permissions Allow/deny rules for tool calls
237
+ hooks Shell scripts that run on agent events (hooks.yaml in agents.yaml)
238
+ subagents Named sub-agent definitions
239
+ plugins Bundles of skills, hooks, and scripts
240
+
241
+ Packages:
242
+ search <query> Find MCP servers and skills in registries
243
+ install <pkg> Install from registry (mcp:name, skill:user/repo)
244
+
245
+ Run and dispatch:
246
+ run <agent|profile> [prompt] Run an agent. Omit prompt for interactive mode.
247
+ config Configure run defaults, project root, and device options
248
+ teams Coordinate multiple agents on shared work
249
+ routines Run agents on a cron schedule (scheduler auto-starts)
250
+ daemon Runtime status/control for the always-on daemon (secrets broker, browser IPC, scheduler)
251
+ webhook Receive signed GitHub/Linear webhooks for trigger routines
252
+ funnel Expose a webhook receiver through Tailscale Funnel
253
+ sessions Browse, search, and replay past runs (live-search in TTY; grouped by workspace)
254
+ logs [id] Show a run's log — host-dispatch task or session; -f to follow
255
+ browser Automate a browser — navigate, click, screenshot, console, network
256
+ pty Drive interactive terminal programs (REPLs, TUIs) via a persistent PTY session
257
+
258
+ Observe (read the fleet — no store merge; aliases point at the real readers):
259
+ feed / inbox Needs-you inbox (open blocks waiting on you)
260
+ timeline Agent progress stream (= feed --filter updates)
261
+ roster Live agents (= sessions --active)
262
+ events Unified ops + activity event trail
263
+ audit Tamper-evident run-dispatch log (not events)
264
+ snapshot One-process inventory + active sessions poll
265
+ status Sync/drift only (not the live fleet snapshot)
266
+
267
+ Credentials and profiles:
268
+ profile Activate resource profiles across skills, MCP, permissions, and secrets
269
+ profiles Bundles of (host CLI, endpoint, model, auth)
270
+ secrets Keychain-backed env bundles; use 'secrets exec <bundle> -- <cmd>' to inject into a subprocess
271
+
272
+ Diagnostics:
273
+ doctor [agent[@version]] Diagnose CLI availability, sync status, and resource divergence; --check for the CI drift gate
274
+ usage [agent] Show rate-limit and quota usage per agent
275
+ insights How work looks — behaviour (default) or counter mix (insights mix)
276
+ perf Latency rollups (hooks, commands, runs) from the disposable perf warehouse
277
+
278
+ Config sync:
279
+ repo pull [alias] Git pull a repo (system | user | <extra>)
280
+ sync [agent] Re-materialize installed version homes; --local to skip fetching
281
+ repo init --path <dir> Scaffold your own editable repo from a template
282
+ repo add <path|gh:user/repo> Merge an extra repo after the system repo
283
+
284
+ Beta features:
285
+ beta Enable preview features (factory and more)
286
+
287
+ Automation tips:
288
+ Pass explicit names/IDs Avoid pickers: agents sessions <id> --markdown
289
+ Use --yes for defaults Auto-accept sync/default prompts on add/use/pull
290
+ Use --names for central items e.g. agents commands add --names review-pr,debug
291
+ Use agent@version targets e.g. --agents grok@0.1.218,claude@2.1.79,codex@default
292
+ Non-TTY shells apply defaults Omitted required selections fail with a plain hint
293
+
294
+ Options:
295
+ -V, --version Show version number
296
+ -h, --help Show help
297
+ --verbose Show startup self-heal details on stderr
298
+
299
+ System config lives in ~/.agents/.system/. Run 'agents <command> --help' for details.
300
+ `);
301
+ }
302
+ return originalHelpInformation();
303
+ };
304
+ /** Compare two semver version strings. Returns 1 if a > b, -1 if a < b, 0 if equal. */
305
+ function compareVersions(a, b) {
306
+ const partsA = a.split('.').map(Number);
307
+ const partsB = b.split('.').map(Number);
308
+ for (let i = 0; i < 3; i++) {
309
+ if (partsA[i] > partsB[i])
310
+ return 1;
311
+ if (partsA[i] < partsB[i])
312
+ return -1;
313
+ }
314
+ return 0;
315
+ }
316
+ /** Fetch and display changelog entries between two versions from unpkg. */
317
+ async function showWhatsNew(fromVersion, toVersion) {
318
+ try {
319
+ const response = await fetch(`https://unpkg.com/@phnx-labs/agents-cli@${toVersion}/CHANGELOG.md`);
320
+ if (!response.ok)
321
+ return;
322
+ const relevantChanges = renderWhatsNew(await response.text(), fromVersion, toVersion);
323
+ if (relevantChanges.length > 0) {
324
+ console.log(chalk.bold("\nWhat's new:\n"));
325
+ for (const line of relevantChanges) {
326
+ console.log(line);
327
+ }
328
+ console.log(chalk.gray('\nFull notes: https://github.com/phnx-labs/agents-cli/blob/main/CHANGELOG.md'));
329
+ console.log();
330
+ }
331
+ }
332
+ catch {
333
+ // Silently ignore changelog fetch errors
334
+ }
335
+ }
336
+ const UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
337
+ import { getUpdateCheckPath, getMigratedSentinelPath, getUserAgentsDir, getRuntimeStateDir } from './lib/state.js';
338
+ import { resolveBrandName, disabledCommandsForActiveBrand } from './lib/brand.js';
339
+ import { readUpdateCache, saveUpdateCheck, dismissUpdateVersion, shouldPromptUpgrade, resolveMultiInstallInventory, remediateStaleAgentsCliInstalls, resolveRunningPackageRoot, } from './lib/self-update.js';
340
+ const UPDATE_CHECK_FILE = getUpdateCheckPath();
341
+ // Beside the existing update-check cache (RUSH-2324): short-TTL memo of the
342
+ // multi-install PATH scan so every ordinary CLI invocation does not re-walk
343
+ // PATH + known install roots (~1ms warm).
344
+ const MULTI_INSTALL_SCAN_FILE = path.join(path.dirname(UPDATE_CHECK_FILE), '.multi-install-scan');
345
+ /**
346
+ * Warn once when this machine contains a different agents-cli install than the
347
+ * copy that is currently running (or several). Divergent installs
348
+ * are how self-updates "succeed" without changing the command the user types.
349
+ * The warning re-fires only when the set of install roots or their helper-copy
350
+ * safety changes. Dev builds are included because old dev copies can still
351
+ * overwrite the shared macOS helper bundle non-atomically.
352
+ */
353
+ function maybeWarnMultiInstall() {
354
+ const sentinel = path.join(getRuntimeStateDir(), 'multi-install-warned');
355
+ let runningRoot;
356
+ try {
357
+ runningRoot = resolveRunningPackageRoot(__dirname);
358
+ }
359
+ catch {
360
+ // Without a real root for the running copy there is nothing to compare
361
+ // against, and a guess here is exactly what produced the phantom
362
+ // "/$bunfs" install. This warning is advisory — stay silent instead.
363
+ return;
364
+ }
365
+ // RUSH-2324: resolve via the short-TTL scan cache beside `.update-check`.
366
+ // Fresh cache → skip findAgentsCliInstalls (~1ms PATH walk).
367
+ const inventory = resolveMultiInstallInventory(runningRoot, VERSION, process.env.PATH || '', MULTI_INSTALL_SCAN_FILE);
368
+ if (inventory.length < 2) {
369
+ try {
370
+ fs.unlinkSync(sentinel);
371
+ }
372
+ catch { /* nothing recorded */ }
373
+ return;
374
+ }
375
+ const key = inventory
376
+ .map((info) => `${info.packageRoot}\t${info.version}\t${info.note}`)
377
+ .sort()
378
+ .join('\n');
379
+ try {
380
+ if (fs.readFileSync(sentinel, 'utf-8') === key)
381
+ return;
382
+ }
383
+ catch { /* not warned for this set yet */ }
384
+ console.error(chalk.yellow('Multiple agents-cli installs detected:'));
385
+ for (const info of inventory) {
386
+ console.error(chalk.gray(` ${info.packageRoot} ${info.version} (${info.note})`));
387
+ }
388
+ console.error(chalk.gray('Upgrades apply to the running copy. Purge npx-cache / legacy / pre-1.22.30 copies with: agents doctor --fix'));
389
+ try {
390
+ fs.mkdirSync(path.dirname(sentinel), { recursive: true });
391
+ fs.writeFileSync(sentinel, key);
392
+ }
393
+ catch { /* best-effort; worst case the warning repeats */ }
394
+ }
395
+ /** Determine whether enough time has elapsed since the last registry fetch. */
396
+ function shouldFetchLatest(cache) {
397
+ if (!cache)
398
+ return true;
399
+ return Date.now() - cache.lastCheck > UPDATE_CHECK_INTERVAL_MS;
400
+ }
401
+ /** Fetch the exact latest npm version plus its registry integrity hash. */
402
+ async function fetchNpmPackageMetadata(versionOrTag = 'latest', timeoutMs = 5000) {
403
+ const response = await fetch(`https://registry.npmjs.org/${NPM_PACKAGE_NAME}/${versionOrTag}`, {
404
+ signal: AbortSignal.timeout(timeoutMs),
405
+ });
406
+ if (!response.ok) {
407
+ if (response.status === 404) {
408
+ throw new Error(`${NPM_PACKAGE_NAME}@${versionOrTag} not found on npm`);
409
+ }
410
+ throw new Error('Could not reach npm registry');
411
+ }
412
+ const data = await response.json();
413
+ if (typeof data.version !== 'string' ||
414
+ typeof data.dist?.integrity !== 'string' ||
415
+ typeof data.dist?.tarball !== 'string') {
416
+ throw new Error('npm registry response did not include version, integrity, and tarball');
417
+ }
418
+ return { version: data.version, integrity: data.dist.integrity, tarball: data.dist.tarball };
419
+ }
420
+ function printResolvedPackage(metadata) {
421
+ console.log(chalk.gray(`Resolved: ${NPM_PACKAGE_NAME}@${metadata.version}`));
422
+ console.log(chalk.gray(`Integrity: ${metadata.integrity}`));
423
+ }
424
+ async function installResolvedPackage(metadata) {
425
+ const packageRoot = resolveRunningPackageRoot(__dirname);
426
+ // Download the published tarball and prove its bytes match the registry
427
+ // integrity BEFORE installing anything. A `name@version` spec would let the
428
+ // package manager fetch and install whatever the registry serves with no
429
+ // hash check on our side; instead we verify here and install the LOCAL, now
430
+ // trusted .tgz. A mismatch throws and nothing below runs — fail closed.
431
+ const tarball = await downloadVerifiedTarball(metadata.tarball, metadata.integrity);
432
+ try {
433
+ // Upgrade with the package manager that owns this install. A bun global
434
+ // install lives at <bunGlobalDir>/node_modules/... (no `lib` segment), so an
435
+ // `npm install --prefix` would write to <bunGlobalDir>/lib/node_modules and
436
+ // never touch the running copy — npm exits 0, the verify below fails.
437
+ if (detectPackageManager(packageRoot) === 'bun') {
438
+ await installPackageWithBun(tarball);
439
+ }
440
+ else {
441
+ await installPackageIntoPrefix(tarball, deriveGlobalPrefix(packageRoot));
442
+ }
443
+ }
444
+ finally {
445
+ // Best-effort cleanup of the verified tarball and its temp dir.
446
+ try {
447
+ fs.rmSync(path.dirname(tarball), { recursive: true, force: true });
448
+ }
449
+ catch {
450
+ /* leave it for the OS temp sweep */
451
+ }
452
+ }
453
+ verifyInstalledVersion(packageRoot, metadata.version);
454
+ refreshAliasShims(packageRoot);
455
+ // The npm install above runs with --ignore-scripts, so the postinstall that
456
+ // installs the macOS Keychain helper never fires on upgrade. Force-refresh the
457
+ // helper here so a user upgrading FROM a broken build (e.g. the entitlement-less
458
+ // 1.20.4 helper that fails SecItemAdd with -34018) gets the fixed, signed bundle
459
+ // immediately — instead of waiting for the lazy staleness check in
460
+ // getKeychainHelperPath() to repair it on their next secret operation. The new
461
+ // package is already on disk, so the dynamic import resolves the freshly-installed
462
+ // helper module + bundle. Best-effort: an upgrade must never fail because the
463
+ // helper could not be reinstalled (the lazy staleness check in
464
+ // getKeychainHelperPath() still repairs it on the next secret operation).
465
+ if (process.platform === 'darwin') {
466
+ try {
467
+ const { ensureKeychainHelperInstalled } = await import('./lib/secrets/install-helper.js');
468
+ ensureKeychainHelperInstalled({ forceReinstall: true });
469
+ }
470
+ catch {
471
+ // Non-fatal.
472
+ }
473
+ }
474
+ }
475
+ /** Present an interactive upgrade prompt (TTY) or a one-line hint (non-TTY). */
476
+ async function promptUpgrade(latestVersion) {
477
+ const { default: ora } = await import('ora');
478
+ const { confirm, select } = await import('@inquirer/prompts');
479
+ const { isInteractiveTerminal, isPromptCancelled } = await import('./commands/utils.js');
480
+ if (!isInteractiveTerminal()) {
481
+ console.error(chalk.yellow(`Update available: ${VERSION} -> ${latestVersion}. Run: agents upgrade --yes`));
482
+ return;
483
+ }
484
+ const answer = await select({
485
+ message: `Update available: ${VERSION} -> ${latestVersion}`,
486
+ choices: [
487
+ { value: 'now', name: 'Upgrade now' },
488
+ { value: 'later', name: 'Later' },
489
+ { value: 'dismiss', name: `Skip ${latestVersion}` },
490
+ ],
491
+ });
492
+ if (answer === 'dismiss') {
493
+ dismissUpdateVersion(UPDATE_CHECK_FILE, latestVersion);
494
+ return;
495
+ }
496
+ if (answer === 'now') {
497
+ const { spawnSync } = await import('child_process');
498
+ let spinner = ora('Resolving package metadata...').start();
499
+ try {
500
+ const metadata = await fetchNpmPackageMetadata();
501
+ // The prompt showed the cached latest, which can lag the registry (the
502
+ // 24h window) — sync the cache to what was actually resolved so later
503
+ // prompts and the install agree on the same version.
504
+ saveUpdateCheck(UPDATE_CHECK_FILE, metadata.version);
505
+ spinner.succeed(`Resolved ${NPM_PACKAGE_NAME}@${metadata.version}`);
506
+ printResolvedPackage(metadata);
507
+ const approved = await confirm({
508
+ message: `Install ${NPM_PACKAGE_NAME}@${metadata.version}?`,
509
+ default: false,
510
+ });
511
+ if (!approved) {
512
+ console.log(chalk.gray('Upgrade cancelled'));
513
+ return;
514
+ }
515
+ spinner = ora('Upgrading...').start();
516
+ await installResolvedPackage(metadata);
517
+ spinner.succeed(`Upgraded to ${metadata.version}`);
518
+ await showWhatsNew(VERSION, metadata.version);
519
+ console.log();
520
+ // Re-exec the verified install's entrypoint and exit. PATH lookup of
521
+ // `agents` could resolve a different copy (dev build, another prefix)
522
+ // than the one that was just upgraded. getCliLaunch resolves the JS-vs-
523
+ // standalone shape — never hand-roll `[process.execPath, entrypoint]`,
524
+ // which hands the bun virtual entry to a compiled binary as a bogus arg.
525
+ const { command, args } = getCliLaunch(process.argv.slice(2));
526
+ const result = spawnSync(command, args, {
527
+ stdio: 'inherit',
528
+ shell: false,
529
+ });
530
+ process.exit(result.status ?? 0);
531
+ }
532
+ catch (err) {
533
+ if (isPromptCancelled(err))
534
+ return;
535
+ spinner.fail(`Upgrade failed: ${err instanceof Error ? err.message : String(err)}`);
536
+ console.log(chalk.gray('Run manually: agents upgrade --yes'));
537
+ }
538
+ console.log();
539
+ }
540
+ }
541
+ /**
542
+ * Background update check — fires once per 24h cache window.
543
+ * Network: GET registry.npmjs.org/@phnx-labs/agents-cli/latest.
544
+ * Disable: set AGENTS_CLI_DISABLE_AUTO_UPDATE=1 in shell rc.
545
+ *
546
+ * Fire-and-forget; never blocks the CLI's foreground operation.
547
+ */
548
+ function refreshUpdateCacheInBackground() {
549
+ fetch('https://registry.npmjs.org/@phnx-labs/agents-cli/latest', {
550
+ signal: AbortSignal.timeout(2000),
551
+ })
552
+ .then((response) => (response.ok ? response.json() : null))
553
+ .then((data) => {
554
+ if (data && typeof data.version === 'string') {
555
+ saveUpdateCheck(UPDATE_CHECK_FILE, data.version);
556
+ }
557
+ })
558
+ .catch(() => {
559
+ /* network error, try again next invocation */
560
+ });
561
+ }
562
+ /** Check for available updates using the local cache. Triggers a background refresh if stale. */
563
+ async function checkForUpdates() {
564
+ if (process.env.AGENTS_CLI_DISABLE_AUTO_UPDATE)
565
+ return;
566
+ maybeWarnMultiInstall();
567
+ const cache = readUpdateCache(UPDATE_CHECK_FILE);
568
+ // Kick off network refresh in background if stale. Does not block.
569
+ if (shouldFetchLatest(cache)) {
570
+ refreshUpdateCacheInBackground();
571
+ }
572
+ // Prompt based on current cache (may be from a previous run's background refresh).
573
+ // Skip if the user dismissed this exact version — they'll be prompted again when
574
+ // a newer version appears.
575
+ if (shouldPromptUpgrade(cache, VERSION)) {
576
+ try {
577
+ await promptUpgrade(cache.latestVersion);
578
+ }
579
+ catch (err) {
580
+ const { isPromptCancelled } = await import('./commands/utils.js');
581
+ if (isPromptCancelled(err))
582
+ return;
583
+ /* prompt error, ignore */
584
+ }
585
+ }
586
+ }
587
+ async function maybeBootstrapShimIntegration(requestedCommand, helpOrVersionRequested, verboseStartup) {
588
+ if (!verboseStartup && (!process.stdin.isTTY || !process.stdout.isTTY)) {
589
+ return;
590
+ }
591
+ // Pure documentation paths must never trigger interactive repair — mirrors
592
+ // the helpOrVersionRequested gate around ensureInitialized below. Covers
593
+ // both bare `agents --version` (requestedCommand === undefined) and
594
+ // `agents <subcommand> --help` (requestedCommand === subcommand name).
595
+ if (helpOrVersionRequested) {
596
+ return;
597
+ }
598
+ if (requestedCommand === 'sync' || requestedCommand === 'refresh-rules') {
599
+ return;
600
+ }
601
+ // Past the documentation/non-TTY guards: heal the shim/shadow/PATH conditions
602
+ // through the unified self-heal registry — the SAME checks the daemon runs, but
603
+ // driven silently on this interactive invocation so a user who never starts the
604
+ // daemon still gets healed. Regenerating stale shims, adopting symlink launchers,
605
+ // and adding the shims dir to PATH now happen without any output. The only thing
606
+ // that ever prints is a ONE-TIME notice for what a machine can't silently fix
607
+ // (a real native binary shadowing the shim) or is worth saying once (a PATH entry
608
+ // just added). Suppression is persistent and keyed to the condition — a new
609
+ // terminal no longer re-nags (the old per-PPID sentinel did, every shell).
610
+ const { runInteractiveShimHeal } = await import('./lib/shim-heal.js');
611
+ const { summarizeSelfHeal } = await import('./lib/self-heal/registry.js');
612
+ const { noticeLines, report } = await runInteractiveShimHeal();
613
+ if (verboseStartup) {
614
+ process.stderr.write(`[agents] startup self-heal: ${summarizeSelfHeal(report)}\n`);
615
+ }
616
+ if (noticeLines) {
617
+ for (const line of noticeLines)
618
+ console.log(chalk.gray(line));
619
+ }
620
+ }
621
+ // --- Inline command registrars ----------------------------------------------
622
+ // These commands are defined here rather than in a command module because they
623
+ // close over entry-point-local state (program re-parsing, VERSION, the npm
624
+ // upgrade helpers). The lazy registrar and the all-commands fallback below both
625
+ // call them, so the behavior is identical to the old eager registration.
626
+ // memory is a first-class resource command (see commands/memory.ts via
627
+ // COMMAND_LOADERS). The old memory→rules tombstone was removed in RUSH-1330.
628
+ /** Deprecated `perms` alias — re-parses as `permissions`. */
629
+ function registerPermsAliasCommand(p) {
630
+ p.command('perms', { hidden: true })
631
+ .allowUnknownOption()
632
+ .allowExcessArguments()
633
+ .action(async () => {
634
+ console.log(chalk.yellow('Deprecated: Use "agents permissions" instead of "agents perms"\n'));
635
+ // Re-parse with 'permissions' command
636
+ const args = process.argv.slice(2);
637
+ args[0] = 'permissions';
638
+ await program.parseAsync(['node', 'agents', ...args]);
639
+ });
640
+ }
641
+ /** Deprecated `exec` alias — re-parses as `run`. */
642
+ function registerExecAliasCommand(p) {
643
+ p.command('exec', { hidden: true })
644
+ .allowUnknownOption()
645
+ .allowExcessArguments()
646
+ .action(async () => {
647
+ console.log(chalk.yellow('Deprecated: Use "agents run" instead of "agents exec"\n'));
648
+ const args = process.argv.slice(2);
649
+ args[0] = 'run';
650
+ await program.parseAsync(['node', 'agents', ...args]);
651
+ });
652
+ }
653
+ /** Deprecated `jobs` / `cron` aliases — re-parse as `routines`. */
654
+ function registerJobsCronAliasCommand(p, alias) {
655
+ p.command(alias, { hidden: true })
656
+ .allowUnknownOption()
657
+ .allowExcessArguments()
658
+ .action(async () => {
659
+ console.log(chalk.yellow(`Deprecated: Use "agents routines" instead of "agents ${alias}"\n`));
660
+ const args = process.argv.slice(2);
661
+ args[0] = 'routines';
662
+ await program.parseAsync(['node', 'agents', ...args]);
663
+ });
664
+ }
665
+ /**
666
+ * Removed `check` command (RUSH-1234) — re-parses as `doctor --check`, forwarding
667
+ * any remaining flags so `check --quiet` / `check --json` / `check --devices` keep
668
+ * working and the drift-gate exit code survives the rename. The notice goes to
669
+ * stderr so `--json` stdout stays clean for CI parsers.
670
+ */
671
+ function registerCheckTombstoneCommand(p) {
672
+ p.command('check', { hidden: true })
673
+ .allowUnknownOption()
674
+ .allowExcessArguments()
675
+ .action(async () => {
676
+ console.error(chalk.yellow('Deprecated: "agents check" is now "agents doctor --check". Running that for you.\n'));
677
+ const args = process.argv.slice(2);
678
+ args[0] = 'doctor';
679
+ args.splice(1, 0, '--check');
680
+ await program.parseAsync(['node', 'agents', ...args]);
681
+ });
682
+ }
683
+ /**
684
+ * Removed `resources` command (RUSH-1234) — re-parses as `view --merged` (the
685
+ * cross-layer, first-wins resource table now lives there; `agents inspect <target>`
686
+ * covers per-agent / per-repo detail). Forwards remaining flags like `--json`.
687
+ */
688
+ function registerResourcesTombstoneCommand(p) {
689
+ p.command('resources', { hidden: true })
690
+ .allowUnknownOption()
691
+ .allowExcessArguments()
692
+ .action(async () => {
693
+ console.error(chalk.yellow('Deprecated: "agents resources" is now "agents view --merged" (use "agents inspect <target>" for per-agent/per-repo detail). Running that for you.\n'));
694
+ const args = process.argv.slice(2);
695
+ args[0] = 'view';
696
+ args.splice(1, 0, '--merged');
697
+ await program.parseAsync(['node', 'agents', ...args]);
698
+ });
699
+ }
700
+ /**
701
+ * Removed `hq` command — the JSON bridge for the interactive Agents HQ floor
702
+ * (`agents hq floor --json`). No UI ever consumed it (apps/ext has zero
703
+ * references) and it had no external users, so it is gone with no replacement.
704
+ * Kept as a hidden tombstone so a stale invocation gets a clear message and a
705
+ * non-zero exit instead of commander's raw "unknown command".
706
+ */
707
+ function registerHqTombstoneCommand(p) {
708
+ p.command('hq', { hidden: true })
709
+ .allowUnknownOption()
710
+ .allowExcessArguments()
711
+ .action(() => {
712
+ die('"agents hq" was removed (internal Agents HQ floor bridge, no longer used).');
713
+ });
714
+ }
715
+ /**
716
+ * Hidden `agents _internal <sub>` namespace for machine-to-machine calls that
717
+ * are not user-facing. The first subcommand is `friction`, used by shell guard
718
+ * hooks (git-guard, rm-guard, …) to self-report a block into the event log
719
+ * before they exit 2 — they run before any `agents` process exists, so they
720
+ * cannot emit in-process.
721
+ */
722
+ function registerInternalCommand(p) {
723
+ const internal = p.command('_internal', { hidden: true });
724
+ internal
725
+ .command('friction')
726
+ .option('--surface <surface>', 'Subsystem that hit the failure (e.g. guard, teams)')
727
+ .option('--id <failureId>', 'Stable failure slug (e.g. git.reset-hard)')
728
+ .option('--error <message>', 'Human-readable failure reason')
729
+ .option('--command <command>', 'The command that was blocked')
730
+ .action((opts) => {
731
+ if (!opts.surface || !opts.id) {
732
+ process.exit(0); // fail-open: never break the caller
733
+ }
734
+ emitFriction(opts.surface, opts.id, {
735
+ ...(opts.error ? { error: opts.error } : {}),
736
+ ...(opts.command ? { command: opts.command } : {}),
737
+ });
738
+ process.exit(0);
739
+ });
740
+ }
741
+ /** Runtime action for the shared `agents upgrade [version]` command definition. */
742
+ async function runUpgrade(version, options) {
743
+ const { default: ora } = await import('ora');
744
+ const { confirm } = await import('@inquirer/prompts');
745
+ const { isInteractiveTerminal, isPromptCancelled } = await import('./commands/utils.js');
746
+ const target = version ?? 'latest';
747
+ let spinner = ora(version ? `Resolving ${NPM_PACKAGE_NAME}@${target}...` : 'Checking for updates...').start();
748
+ try {
749
+ const metadata = await fetchNpmPackageMetadata(target);
750
+ const resolvedVersion = metadata.version;
751
+ if (resolvedVersion === VERSION) {
752
+ spinner.succeed(`Already on ${VERSION}`);
753
+ return;
754
+ }
755
+ // For `latest` (no explicit version) skip when already ahead. When a
756
+ // version is named explicitly, honor it even if it's a downgrade.
757
+ if (!version && compareVersions(resolvedVersion, VERSION) <= 0) {
758
+ spinner.succeed(`Already ahead of latest (${VERSION} >= ${resolvedVersion})`);
759
+ return;
760
+ }
761
+ const direction = compareVersions(resolvedVersion, VERSION) < 0 ? 'Downgrade' : 'Upgrade';
762
+ spinner.succeed(`Resolved ${NPM_PACKAGE_NAME}@${resolvedVersion}`);
763
+ printResolvedPackage(metadata);
764
+ if (isInteractiveTerminal() && !options.yes) {
765
+ const approved = await confirm({
766
+ message: `Install ${NPM_PACKAGE_NAME}@${resolvedVersion}?`,
767
+ default: false,
768
+ });
769
+ if (!approved) {
770
+ console.log(chalk.gray('Upgrade cancelled'));
771
+ return;
772
+ }
773
+ }
774
+ spinner = ora(`${direction === 'Downgrade' ? 'Downgrading' : 'Upgrading'} ${VERSION} -> ${resolvedVersion}...`).start();
775
+ await installResolvedPackage(metadata);
776
+ spinner.succeed(`${direction}d to ${resolvedVersion}`);
777
+ // After a successful upgrade, drop latent pre-fix / npx-cache /
778
+ // unsafe-legacy copies so the new binary is not shadowed (RUSH-2415).
779
+ try {
780
+ const runningRoot = resolveRunningPackageRoot(__dirname);
781
+ const purge = remediateStaleAgentsCliInstalls({
782
+ runningRoot,
783
+ runningVersion: resolvedVersion,
784
+ });
785
+ if (purge.removed.length > 0) {
786
+ console.log(chalk.gray(`Purged ${purge.removed.length} stale agents-cli install${purge.removed.length === 1 ? '' : 's'} (npx-cache / legacy / pre-1.22.30).`));
787
+ }
788
+ if (purge.failed.length > 0) {
789
+ console.log(chalk.yellow(`Could not purge ${purge.failed.length} stale install${purge.failed.length === 1 ? '' : 's'}; re-run agents doctor --fix.`));
790
+ }
791
+ }
792
+ catch {
793
+ /* best-effort; upgrade already succeeded */
794
+ }
795
+ // Only show the changelog for a genuine upgrade range.
796
+ if (compareVersions(resolvedVersion, VERSION) > 0) {
797
+ await showWhatsNew(VERSION, resolvedVersion);
798
+ }
799
+ }
800
+ catch (err) {
801
+ if (isPromptCancelled(err))
802
+ return;
803
+ spinner.fail(`Upgrade failed: ${err instanceof Error ? err.message : String(err)}`);
804
+ console.log(chalk.gray(`Run manually: agents upgrade ${version ? version + ' ' : ''}--yes`));
805
+ }
806
+ }
807
+ function registerUpgradeRuntimeCommand(p) {
808
+ registerUpgradeCommand(p, runUpgrade);
809
+ }
810
+ // --- Lazy registration orchestration -----------------------------------------
811
+ /** Import a command module via its loader and register it on the program. */
812
+ async function reg(loader) {
813
+ (await loader())(program);
814
+ }
815
+ /**
816
+ * Register exactly the command(s) the requested top-level name needs.
817
+ * Returns false when the name maps to no known command (typo / unknown).
818
+ *
819
+ * Lazy commands (sessions/teams/cloud) are intentionally NOT handled here — they
820
+ * must register after applyGlobalHelpConventions to match main's ordering.
821
+ * Inline aliases/tombstones load their target module via COMMAND_LOADERS.
822
+ */
823
+ async function registerEagerForRequest(name) {
824
+ switch (name) {
825
+ case 'perms':
826
+ // The action re-parses as `permissions`, so that target must exist too.
827
+ registerPermsAliasCommand(program);
828
+ for (const loader of COMMAND_LOADERS['permissions'] ?? [])
829
+ await reg(loader);
830
+ return true;
831
+ case 'exec':
832
+ registerExecAliasCommand(program);
833
+ for (const loader of COMMAND_LOADERS['run'] ?? [])
834
+ await reg(loader);
835
+ return true;
836
+ case 'jobs':
837
+ case 'cron':
838
+ registerJobsCronAliasCommand(program, name);
839
+ for (const loader of COMMAND_LOADERS['routines'] ?? [])
840
+ await reg(loader);
841
+ return true;
842
+ case 'check':
843
+ // The action re-parses as `doctor --check`, so doctor must exist too.
844
+ registerCheckTombstoneCommand(program);
845
+ for (const loader of COMMAND_LOADERS['doctor'] ?? [])
846
+ await reg(loader);
847
+ return true;
848
+ case 'resources':
849
+ // The action re-parses as `view --merged`, so view must exist too.
850
+ registerResourcesTombstoneCommand(program);
851
+ for (const loader of COMMAND_LOADERS['view'] ?? [])
852
+ await reg(loader);
853
+ return true;
854
+ case 'hq':
855
+ registerHqTombstoneCommand(program);
856
+ return true;
857
+ case '_internal':
858
+ registerInternalCommand(program);
859
+ return true;
860
+ case 'upgrade':
861
+ registerUpgradeRuntimeCommand(program);
862
+ return true;
863
+ }
864
+ const loaders = COMMAND_LOADERS[name];
865
+ if (!loaders)
866
+ return false;
867
+ for (const loader of loaders)
868
+ await reg(loader);
869
+ return true;
870
+ }
871
+ // Safety-net for unknown commands that still reach commander (should be rare
872
+ // after the pre-parse spellcheck below). Candidates come from the plain-string
873
+ // KNOWN_TOP_LEVEL_COMMANDS set so this path never depends on every module
874
+ // having been registered (RUSH-2329).
875
+ program.on('command:*', (operands) => {
876
+ const unknown = operands[0];
877
+ const { closest, minDist } = closestTopLevelCommand(unknown, KNOWN_TOP_LEVEL_COMMANDS);
878
+ if (minDist === 1 && closest && !RETIRED_TOP_LEVEL_COMMANDS.has(unknown)) {
879
+ const args = process.argv.slice(2);
880
+ args[0] = closest;
881
+ // The typo'd name was unknown, so the top-level --host router (which ran
882
+ // before commander parsing, against the ORIGINAL name) could not have
883
+ // routed it - it correctly fell through to reach this handler at all
884
+ // (that fallthrough is this ticket's own fix). But falling through to a
885
+ // plain local re-parse means a routing flag on a corrected REAL
886
+ // host-routable command (e.g. `docto --host box`, corrected to `doctor`)
887
+ // silently ran LOCALLY instead of remotely, with no error - worse than
888
+ // the loud "does not support --host" this ticket replaced. Re-run the
889
+ // router with the CORRECTED name before falling through to local parse;
890
+ // it already no-ops when no routing flag is present. RUSH-2022 review r2.
891
+ void (async () => {
892
+ // Register only the corrected command — never the full tree (RUSH-2329).
893
+ if (LAZY_COMMAND_NAMES.has(closest)) {
894
+ for (const loader of COMMAND_LOADERS[closest] ?? [])
895
+ await reg(loader);
896
+ }
897
+ else {
898
+ await registerEagerForRequest(closest);
899
+ }
900
+ // Same RUSH-2374 gate as the main router: typo corrections with no routing
901
+ // flag must not load the hosts graph just to no-op.
902
+ if (hasHostRoutingFlag(args)) {
903
+ const { maybeRunOnHost } = await import('./lib/hosts/passthrough.js');
904
+ if (await maybeRunOnHost(closest, args)) {
905
+ process.exit(process.exitCode ?? 0);
906
+ }
907
+ }
908
+ program.parse(['node', 'agents', ...args]);
909
+ })();
910
+ return;
911
+ }
912
+ console.error(`error: unknown command '${unknown}'`);
913
+ if (closest && minDist <= 3) {
914
+ console.error(`(Did you mean ${closest}?)`);
915
+ }
916
+ process.exit(1);
917
+ });
918
+ // Parse the invocation shape up front: the first non-flag token is the command,
919
+ // and the doc flags (--version/--help/-h) drive both the registration strategy
920
+ // and whether the update check + background sync run at all.
921
+ const passedArgs = process.argv.slice(2);
922
+ // Commander owns `--version` on the root command and otherwise intercepts it
923
+ // even after `sessions`, before the subcommand can parse its version filter.
924
+ // Rewrite only that value-taking nested form; bare `agents --version` and every
925
+ // other command retain the root documentation flag unchanged.
926
+ if (passedArgs[0] === 'sessions') {
927
+ const nestedVersionIndex = passedArgs.indexOf('--version', 1);
928
+ if (nestedVersionIndex >= 0) {
929
+ const nestedVersion = passedArgs[nestedVersionIndex + 1];
930
+ if (!nestedVersion || nestedVersion.startsWith('-')) {
931
+ console.error("error: option '--version <version>' argument missing");
932
+ process.exit(1);
933
+ }
934
+ passedArgs[nestedVersionIndex] = '--session-version';
935
+ process.argv[nestedVersionIndex + 2] = '--session-version';
936
+ }
937
+ }
938
+ const requestedCommand = passedArgs.find((arg) => !arg.startsWith('-'));
939
+ const verboseStartup = passedArgs.includes('--verbose');
940
+ // Help and version output are pure documentation — they must never gate on
941
+ // setup, otherwise `agents <cmd> --help` becomes useless on a fresh box.
942
+ const helpOrVersionRequested = passedArgs.some((arg) => arg === '--help' || arg === '-h' || arg === '--version' || arg === '-V');
943
+ // White-label: a brand can hide built-in top-level commands. A hidden command
944
+ // must behave as if it doesn't exist under this brand (unknown-command +
945
+ // spellcheck), while `agents` itself is unaffected. `brandDisabled` is empty for
946
+ // the unbranded CLI, so all of this is a no-op there.
947
+ const brandDisabled = disabledCommandsForActiveBrand();
948
+ const requestedIsDisabled = requestedCommand !== undefined && brandDisabled.has(requestedCommand);
949
+ // `--host` passthrough: run this invocation on a remote machine over SSH instead
950
+ // of locally. Handled before any local command registration / update check /
951
+ // background sync — a remote run needs none of that. Only the allowlisted
952
+ // read-only + config + teams commands route here; `run`/`sessions` are absent
953
+ // from the table and fall through to their own richer `--host` handling below.
954
+ // `--help`/`--version` stay local (docs must work without a reachable host).
955
+ //
956
+ // RUSH-2374: gate the dynamic import on a routing flag actually being present.
957
+ // Without this, every named invocation paid ~187ms to load passthrough.js only
958
+ // for maybeRunOnHost to return false after four flagValue scans. The presence
959
+ // scan itself is the same work those four scans do, at ~0.001ms on an 11-token
960
+ // argv — free next to the module graph it avoids on the majority path.
961
+ if (requestedCommand !== undefined &&
962
+ !helpOrVersionRequested &&
963
+ !requestedIsDisabled &&
964
+ hasHostRoutingFlag(passedArgs)) {
965
+ const { maybeRunOnHost } = await import('./lib/hosts/passthrough.js');
966
+ if (await maybeRunOnHost(requestedCommand, passedArgs)) {
967
+ process.exit(process.exitCode ?? 0);
968
+ }
969
+ }
970
+ // Register only the command(s) this invocation actually uses. Lazy commands
971
+ // (sessions/teams/cloud) are handled after applyGlobalHelpConventions below.
972
+ const isLazyRequest = requestedCommand !== undefined && LAZY_COMMAND_NAMES.has(requestedCommand);
973
+ // Set when the requested name maps to no command. Spellcheck uses the plain
974
+ // KNOWN_TOP_LEVEL_COMMANDS string set — never registerAllEagerCommands just to
975
+ // build the candidate list (RUSH-2329; was 250-330ms of module evaluation).
976
+ let requestedIsUnknown = false;
977
+ if (requestedIsDisabled) {
978
+ // Brand hid this command: resolve as unknown without loading the full tree.
979
+ requestedIsUnknown = true;
980
+ }
981
+ else if (requestedCommand !== undefined && !isLazyRequest) {
982
+ const known = await registerEagerForRequest(requestedCommand);
983
+ if (!known) {
984
+ requestedIsUnknown = true;
985
+ }
986
+ }
987
+ // When requestedCommand is undefined (bare invocation, --version, --help, -h) no
988
+ // command modules are needed: --version is built in and the root help text is a
989
+ // static string.
990
+ // Mirror main: help conventions are applied after the eager command tree and
991
+ // before the lazy commands, so the latter inherit the root's custom help
992
+ // formatter instead of getting the per-command recursive pass.
993
+ applyGlobalHelpConventions(program);
994
+ // Lazy commands pull in the SQLite-backed session/cloud stack; register them
995
+ // only when explicitly requested, keeping lightweight commands off that path.
996
+ if (isLazyRequest && !requestedIsDisabled) {
997
+ for (const loader of COMMAND_LOADERS[requestedCommand])
998
+ await reg(loader);
999
+ }
1000
+ else if (requestedIsUnknown && requestedCommand) {
1001
+ // Spellcheck from the plain-string name set. KNOWN_TOP_LEVEL_COMMANDS already
1002
+ // includes lazy names (sessions/teams/cloud/…) and inline aliases/tombstones,
1003
+ // so `agents session` still suggests `sessions` without loading either module.
1004
+ // Insertion order matches COMMAND_LOADERS key order + INLINE_COMMAND_NAMES,
1005
+ // preserving the historical first-seen tie-break of registerAllEagerCommands.
1006
+ const candidates = [...KNOWN_TOP_LEVEL_COMMANDS].filter((name) => !brandDisabled.has(name));
1007
+ const { closest, minDist } = closestTopLevelCommand(requestedCommand, candidates);
1008
+ if (minDist === 1 &&
1009
+ closest &&
1010
+ !requestedIsDisabled &&
1011
+ !RETIRED_TOP_LEVEL_COMMANDS.has(requestedCommand)) {
1012
+ // Auto-correct: register ONLY the corrected command, then re-route --host
1013
+ // and reparse under the real name (RUSH-2329 + RUSH-2022 review r2).
1014
+ passedArgs[0] = closest;
1015
+ // Keep process.argv in sync for the command:* safety-net and any code that
1016
+ // re-reads argv after this point.
1017
+ const argvCmdIndex = process.argv.findIndex((a, i) => i >= 2 && !a.startsWith('-'));
1018
+ if (argvCmdIndex >= 0)
1019
+ process.argv[argvCmdIndex] = closest;
1020
+ if (LAZY_COMMAND_NAMES.has(closest)) {
1021
+ for (const loader of COMMAND_LOADERS[closest] ?? [])
1022
+ await reg(loader);
1023
+ }
1024
+ else {
1025
+ await registerEagerForRequest(closest);
1026
+ }
1027
+ if (!helpOrVersionRequested && hasHostRoutingFlag(passedArgs)) {
1028
+ const { maybeRunOnHost } = await import('./lib/hosts/passthrough.js');
1029
+ if (await maybeRunOnHost(closest, passedArgs)) {
1030
+ process.exit(process.exitCode ?? 0);
1031
+ }
1032
+ }
1033
+ }
1034
+ else {
1035
+ // No auto-correct: print the suggestion and exit without loading modules.
1036
+ console.error(`error: unknown command '${requestedCommand}'`);
1037
+ if (closest && minDist <= 3) {
1038
+ console.error(`(Did you mean ${closest}?)`);
1039
+ }
1040
+ process.exit(1);
1041
+ }
1042
+ }
1043
+ // White-label: remove any commands this brand disabled so they resolve as
1044
+ // unknown. Unbranded or nothing-disabled → no-op. After auto-correct we may
1045
+ // have registered a non-disabled command; strip only if it is still listed.
1046
+ if (brandDisabled.size > 0) {
1047
+ const kept = program.commands.filter((c) => !brandDisabled.has(c.name()));
1048
+ if (kept.length !== program.commands.length) {
1049
+ program.commands = kept;
1050
+ }
1051
+ }
1052
+ // Pure documentation paths (--version / --help / -h) return immediately: skip
1053
+ // the update check (PATH scan + cache read) and the detached background sync
1054
+ // (spawns a child process) that every other invocation runs.
1055
+ if (!helpOrVersionRequested) {
1056
+ // Run update check before parsing so the upgrade notice/prompt precedes output.
1057
+ await checkForUpdates();
1058
+ // Fire-and-forget the background sync. System repo gets a real fast-forward
1059
+ // pull (read-only locally, safe). User repo and extras get fetch-only + a
1060
+ // status marker that `agents doctor` surfaces as a repo-behind warning.
1061
+ const { spawnDetachedSync } = await import('./lib/auto-pull.js');
1062
+ spawnDetachedSync();
1063
+ }
1064
+ // First-run experience: no args + no config yet + TTY -> launch interactive setup.
1065
+ // Skipped when stdin/stdout isn't a terminal (CI, pipes) or when user passes any args.
1066
+ const metaFilePath = path.join(getUserAgentsDir(), 'agents.yaml');
1067
+ const firstRun = passedArgs.length === 0 &&
1068
+ !fs.existsSync(metaFilePath) &&
1069
+ process.stdin.isTTY &&
1070
+ process.stdout.isTTY;
1071
+ if (firstRun) {
1072
+ try {
1073
+ const { runSetup } = await import('./commands/setup.js');
1074
+ await runSetup(program);
1075
+ }
1076
+ catch (err) {
1077
+ if (!(err instanceof Error && err.name === 'ExitPromptError')) {
1078
+ throw err;
1079
+ }
1080
+ }
1081
+ process.exit(0);
1082
+ }
1083
+ // Every command requires the system repo to be cloned first. `setup` is the
1084
+ // command that does the cloning; `uninstall` is its reverse and must run even
1085
+ // from a broken/half-setup state (that is exactly when you want to tear down).
1086
+ const SETUP_EXEMPT_COMMANDS = new Set(['setup', 'help', 'uninstall']);
1087
+ // Fold legacy ~/.agents-system/ into ~/.agents/.system/ BEFORE ensureInitialized
1088
+ // runs. ensureInitialized checks for .git inside the new path; if the user is
1089
+ // upgrading from a layout where .git lives under the legacy path, the check
1090
+ // would fail and exit before the migrator ever runs. Also runs outside the
1091
+ // sentinel guard below because the sentinel was set by pre-fold releases and
1092
+ // would otherwise skip this step on every existing install. Idempotent —
1093
+ // no-ops when legacy is missing or already a symlink.
1094
+ //
1095
+ // Skipped for --help/--version (RUSH-2454): pure documentation paths must not
1096
+ // load any migration graph. Loaded from migrate-fold.js (leaf: fs + createLink),
1097
+ // not migrate.js, so a real command pays only the fold hop unless the v19
1098
+ // sentinel is missing and runMigration() is required below.
1099
+ if (process.env.AGENTS_SKIP_MIGRATION !== '1' && !helpOrVersionRequested) {
1100
+ try {
1101
+ const { foldLegacySystemRepo } = await import('./lib/migrate-fold.js');
1102
+ foldLegacySystemRepo();
1103
+ }
1104
+ catch { /* must never block CLI startup */ }
1105
+ }
1106
+ if (!firstRun &&
1107
+ requestedCommand &&
1108
+ !SETUP_EXEMPT_COMMANDS.has(requestedCommand) &&
1109
+ !helpOrVersionRequested) {
1110
+ const { ensureInitialized } = await import('./commands/setup.js');
1111
+ await ensureInitialized(program);
1112
+ }
1113
+ // One-shot idempotent migrations (split-layout, legacy file moves).
1114
+ // Each step is internally guarded by existence checks so it's safe to run
1115
+ // every invocation. A sentinel file in the system dir short-circuits the
1116
+ // scan once a migration version has run, so the hot path stays cheap.
1117
+ // AGENTS_SKIP_MIGRATION=1 disables the bootstrap-time run for tests and
1118
+ // scripted invocations that prepare their own legacy fixtures.
1119
+ //
1120
+ // Skipped for --help/--version (RUSH-2454): same pure-docs gate as fold, the
1121
+ // update check, background sync, ensureInitialized, and the menu-bar self-heal.
1122
+ // The sentinel check itself is pure fs and does not load migrate.js — only a
1123
+ // missing/stale sentinel pays for `await import('./lib/migrate.js')` (which
1124
+ // pulls the hosts/routine/teams/daemon/menubar graph).
1125
+ if (process.env.AGENTS_SKIP_MIGRATION !== '1' && !helpOrVersionRequested) {
1126
+ try {
1127
+ const sentinel = getMigratedSentinelPath();
1128
+ // Sentinel is keyed to the migration SCHEMA version, not the binary version.
1129
+ // Bumping the suffix re-runs migrations for every user; binary releases that
1130
+ // don't change the schema must NOT re-run (they would destroy user content
1131
+ // when migration steps overlap with user-authored paths). See issue #20.
1132
+ const sentinelValue = 'v19';
1133
+ let needRun = true;
1134
+ try {
1135
+ if (fs.existsSync(sentinel) && fs.readFileSync(sentinel, 'utf-8').trim() === sentinelValue) {
1136
+ needRun = false;
1137
+ }
1138
+ }
1139
+ catch { /* best-effort — fall through to run */ }
1140
+ if (needRun) {
1141
+ const { runMigration } = await import('./lib/migrate.js');
1142
+ await runMigration();
1143
+ try {
1144
+ fs.mkdirSync(path.dirname(sentinel), { recursive: true });
1145
+ fs.writeFileSync(sentinel, sentinelValue);
1146
+ }
1147
+ catch { /* best-effort */ }
1148
+ }
1149
+ }
1150
+ catch { /* migration must never block CLI startup */ }
1151
+ }
1152
+ // Auto-enable the macOS menu-bar helper once, for every user. Best-effort and
1153
+ // idempotent: installMenubarLaunchAgentOnUpgrade() no-ops when not on darwin,
1154
+ // when the user ran `agents menubar disable` (sticky opt-out), when the service
1155
+ // is already installed, or when no helper bundle ships with this build. This is
1156
+ // a lightweight startup self-heal (two existsSync checks then return) rather
1157
+ // than a migration-sentinel bump, so it covers fresh installs AND upgrades
1158
+ // without re-running the full migration for the whole user base (issue #20).
1159
+ // Skipped for --help/--version: those are pure documentation paths, so they
1160
+ // pay neither the dynamic import (child_process, the version/layout resolver,
1161
+ // the bundle installer) nor the self-heal's filesystem checks — same gate the
1162
+ // update check, background sync, and ensureInitialized above already use.
1163
+ if (process.platform === 'darwin' &&
1164
+ process.env.AGENTS_SKIP_MIGRATION !== '1' &&
1165
+ !helpOrVersionRequested) {
1166
+ try {
1167
+ const { installMenubarLaunchAgentOnUpgrade } = await import('./lib/menubar/install-menubar.js');
1168
+ installMenubarLaunchAgentOnUpgrade();
1169
+ }
1170
+ catch { /* never block CLI startup on the menu bar */ }
1171
+ }
1172
+ // Bare invocation prints the root help. Commander only auto-displays help on
1173
+ // an empty parse when subcommands are registered, and the lazy-startup path
1174
+ // registers none for a bare call — without this branch, `agents` exits
1175
+ // silently. Runs after first-run setup and migrations so those still fire;
1176
+ // exits 0 to match `agents --help` (and the pre-fix exit code).
1177
+ if (passedArgs.length === 0) {
1178
+ program.outputHelp();
1179
+ process.exit(0);
1180
+ }
1181
+ try {
1182
+ await maybeBootstrapShimIntegration(requestedCommand, helpOrVersionRequested, verboseStartup);
1183
+ await program.parseAsync();
1184
+ }
1185
+ catch (err) {
1186
+ if (err instanceof Error && err.name === 'ExitPromptError') {
1187
+ process.exit(130);
1188
+ }
1189
+ // Browser-daemon-not-running and CDP-not-reachable surface as typed errors
1190
+ // from src/lib/browser/. Don't dump a Node stacktrace for these — they are
1191
+ // user-actionable, not engineering bugs. See issues #41 and #43.
1192
+ if (err instanceof Error) {
1193
+ const isBrowserDaemonNotRunning = err.name === 'BrowserDaemonNotRunningError';
1194
+ const isBrowserCdpUnreachable = err.name === 'BrowserCdpConnectionError';
1195
+ const isBrowserIpcDown = err.message.startsWith('IPC error:') &&
1196
+ (err.message.includes('ECONNREFUSED') || err.message.includes('ENOENT'));
1197
+ if (isBrowserDaemonNotRunning || isBrowserCdpUnreachable || isBrowserIpcDown) {
1198
+ console.error(err.message);
1199
+ process.exit(1);
1200
+ }
1201
+ // A --host targeting a password-auth device throws this from resolveHost.
1202
+ // It carries an actionable message (switch to key auth);
1203
+ // handling it here covers every resolveHost caller (run, teams,
1204
+ // secrets --host) at the source instead of a catch at each call site.
1205
+ if (err.name === 'DeviceOffloadUnsupportedError') {
1206
+ console.error(err.message);
1207
+ process.exit(1);
1208
+ }
1209
+ }
1210
+ throw err;
1211
+ }