@phnx-labs/agents-cli 1.22.35 → 1.22.38

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