@phnx-labs/agents-cli 1.22.46 → 1.22.49

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 (348) hide show
  1. package/CHANGELOG.md +663 -0
  2. package/LICENSE +102 -182
  3. package/README.md +96 -49
  4. package/dist/bootstrap.js +4 -2
  5. package/dist/cli/command-registry.d.ts +2 -1
  6. package/dist/cli/command-registry.js +6 -2
  7. package/dist/commands/accounts.d.ts +58 -0
  8. package/dist/commands/accounts.js +243 -25
  9. package/dist/commands/artifacts.js +7 -6
  10. package/dist/commands/attach.js +1 -1
  11. package/dist/commands/auth.js +7 -5
  12. package/dist/commands/browser-sessions-picker.js +9 -7
  13. package/dist/commands/browser.js +711 -62
  14. package/dist/commands/config.d.ts +0 -1
  15. package/dist/commands/config.js +26 -24
  16. package/dist/commands/cost.d.ts +1 -1
  17. package/dist/commands/daemon.d.ts +21 -0
  18. package/dist/commands/daemon.js +147 -12
  19. package/dist/commands/detach.d.ts +13 -0
  20. package/dist/commands/detach.js +11 -8
  21. package/dist/commands/exec.js +107 -53
  22. package/dist/commands/feed-watch.d.ts +2 -0
  23. package/dist/commands/feed-watch.js +28 -0
  24. package/dist/commands/feed.js +27 -0
  25. package/dist/commands/feedback.js +3 -19
  26. package/dist/commands/focus.d.ts +14 -0
  27. package/dist/commands/focus.js +48 -5
  28. package/dist/commands/go.d.ts +3 -0
  29. package/dist/commands/go.js +16 -3
  30. package/dist/commands/insights.d.ts +1 -1
  31. package/dist/commands/insights.js +2 -2
  32. package/dist/commands/inspect.js +2 -2
  33. package/dist/commands/lease.d.ts +1 -1
  34. package/dist/commands/lease.js +7 -6
  35. package/dist/commands/mailboxes.js +4 -3
  36. package/dist/commands/menubar.js +11 -4
  37. package/dist/commands/projects.d.ts +16 -1
  38. package/dist/commands/projects.js +58 -64
  39. package/dist/commands/resume.js +1 -1
  40. package/dist/commands/route.js +4 -4
  41. package/dist/commands/routines.js +4 -3
  42. package/dist/commands/run-broadcast.d.ts +1 -1
  43. package/dist/commands/run-broadcast.js +1 -1
  44. package/dist/commands/sessions-picker-factory.d.ts +2 -1
  45. package/dist/commands/sessions-picker-factory.js +1 -1
  46. package/dist/commands/sessions-picker.d.ts +22 -0
  47. package/dist/commands/sessions-picker.js +48 -9
  48. package/dist/commands/sessions-stop.d.ts +24 -0
  49. package/dist/commands/sessions-stop.js +66 -0
  50. package/dist/commands/sessions-trace.d.ts +82 -0
  51. package/dist/commands/sessions-trace.js +387 -0
  52. package/dist/commands/sessions.js +5 -0
  53. package/dist/commands/setup-secrets.js +23 -0
  54. package/dist/commands/share.d.ts +20 -5
  55. package/dist/commands/share.js +152 -52
  56. package/dist/commands/ssh.d.ts +15 -0
  57. package/dist/commands/ssh.js +243 -18
  58. package/dist/commands/teams.js +34 -2
  59. package/dist/commands/traces.d.ts +2 -0
  60. package/dist/commands/traces.js +184 -0
  61. package/dist/commands/view.d.ts +2 -0
  62. package/dist/commands/view.js +32 -62
  63. package/dist/commands/watchdog.js +9 -14
  64. package/dist/index.d.ts +1 -0
  65. package/dist/index.js +5 -0
  66. package/dist/lib/account-capabilities.d.ts +4 -0
  67. package/dist/lib/account-capabilities.js +13 -9
  68. package/dist/lib/account-registry.d.ts +2 -0
  69. package/dist/lib/account-registry.js +30 -2
  70. package/dist/lib/accounting/rotate.d.ts +9 -0
  71. package/dist/lib/accounting/rotate.js +12 -2
  72. package/dist/lib/accounting/usage.d.ts +84 -6
  73. package/dist/lib/accounting/usage.js +293 -75
  74. package/dist/lib/agent-modes.js +29 -1
  75. package/dist/lib/agent-spec/agents.d.ts +4 -3
  76. package/dist/lib/agent-spec/agents.js +25 -15
  77. package/dist/lib/analytics/mix-commands.d.ts +1 -1
  78. package/dist/lib/analytics/mix-commands.js +2 -2
  79. package/dist/lib/auth-health.d.ts +13 -3
  80. package/dist/lib/auth-health.js +50 -8
  81. package/dist/lib/browser/devices.d.ts +19 -0
  82. package/dist/lib/browser/devices.js +23 -0
  83. package/dist/lib/browser/drivers/ssh.d.ts +16 -2
  84. package/dist/lib/browser/drivers/ssh.js +36 -9
  85. package/dist/lib/browser/ipc.d.ts +1 -1
  86. package/dist/lib/browser/ipc.js +40 -9
  87. package/dist/lib/browser/profiles.d.ts +104 -65
  88. package/dist/lib/browser/profiles.js +277 -165
  89. package/dist/lib/browser/registry.d.ts +39 -0
  90. package/dist/lib/browser/registry.js +149 -0
  91. package/dist/lib/browser/remote-control.d.ts +28 -2
  92. package/dist/lib/browser/remote-control.js +35 -3
  93. package/dist/lib/browser/resolve-target.d.ts +68 -0
  94. package/dist/lib/browser/resolve-target.js +250 -0
  95. package/dist/lib/browser/runtime-state.d.ts +44 -11
  96. package/dist/lib/browser/runtime-state.js +98 -15
  97. package/dist/lib/browser/service.d.ts +65 -9
  98. package/dist/lib/browser/service.js +196 -61
  99. package/dist/lib/browser/sessions-list.d.ts +1 -3
  100. package/dist/lib/browser/sessions-list.js +3 -16
  101. package/dist/lib/browser/task-index.d.ts +70 -0
  102. package/dist/lib/browser/task-index.js +180 -0
  103. package/dist/lib/browser/types.d.ts +12 -1
  104. package/dist/lib/byok-usage.js +2 -2
  105. package/dist/lib/claude-statusline.d.ts +29 -0
  106. package/dist/lib/claude-statusline.js +139 -0
  107. package/dist/lib/cloud/host.d.ts +1 -1
  108. package/dist/lib/cloud/host.js +1 -1
  109. package/dist/lib/cloud/rush.js +1 -1
  110. package/dist/lib/codex-policy.d.ts +11 -1
  111. package/dist/lib/codex-policy.js +20 -5
  112. package/dist/lib/computer/computer-rpc.js +2 -2
  113. package/dist/lib/computer/download.d.ts +15 -15
  114. package/dist/lib/computer/download.js +35 -98
  115. package/dist/lib/computer/ssh-tunnel.d.ts +0 -3
  116. package/dist/lib/computer/ssh-tunnel.js +12 -22
  117. package/dist/lib/config-keys.d.ts +6 -11
  118. package/dist/lib/config-keys.js +18 -19
  119. package/dist/lib/crabbox/cli.d.ts +1 -1
  120. package/dist/lib/crabbox/cli.js +2 -2
  121. package/dist/lib/crabbox/lease.d.ts +9 -0
  122. package/dist/lib/crabbox/lease.js +70 -15
  123. package/dist/lib/crabbox/runtimes.js +1 -1
  124. package/dist/lib/daemon/account-state-daemon-service.d.ts +16 -0
  125. package/dist/lib/daemon/account-state-daemon-service.js +26 -0
  126. package/dist/lib/daemon/browser-ipc-service.d.ts +20 -0
  127. package/dist/lib/daemon/browser-ipc-service.js +46 -0
  128. package/dist/lib/daemon/daemon.d.ts +3 -0
  129. package/dist/lib/daemon/daemon.js +212 -444
  130. package/dist/lib/daemon/device-probe-service.d.ts +26 -0
  131. package/dist/lib/daemon/device-probe-service.js +47 -0
  132. package/dist/lib/daemon/keychain-reap-service.d.ts +17 -0
  133. package/dist/lib/daemon/keychain-reap-service.js +32 -0
  134. package/dist/lib/daemon/monitor-engine-service.d.ts +22 -0
  135. package/dist/lib/daemon/monitor-engine-service.js +29 -0
  136. package/dist/lib/daemon/runner.js +15 -2
  137. package/dist/lib/daemon/secrets-broker-service.d.ts +20 -0
  138. package/dist/lib/daemon/secrets-broker-service.js +66 -0
  139. package/dist/lib/daemon/self-heal-service.d.ts +27 -0
  140. package/dist/lib/daemon/self-heal-service.js +48 -0
  141. package/dist/lib/daemon/service.d.ts +102 -0
  142. package/dist/lib/daemon/service.js +98 -0
  143. package/dist/lib/daemon/session-index-service.d.ts +22 -0
  144. package/dist/lib/daemon/session-index-service.js +40 -0
  145. package/dist/lib/daemon/state-dir-check-service.d.ts +43 -0
  146. package/dist/lib/daemon/state-dir-check-service.js +63 -0
  147. package/dist/lib/daemon/supervisor.d.ts +87 -0
  148. package/dist/lib/daemon/supervisor.js +298 -0
  149. package/dist/lib/daemon/watchdog-service.d.ts +19 -0
  150. package/dist/lib/daemon/watchdog-service.js +40 -0
  151. package/dist/lib/daemon-health.d.ts +15 -0
  152. package/dist/lib/daemon-health.js +29 -5
  153. package/dist/lib/daemon-services.d.ts +8 -1
  154. package/dist/lib/daemon-services.js +55 -0
  155. package/dist/lib/daemon-ticks.d.ts +13 -2
  156. package/dist/lib/daemon-ticks.js +22 -20
  157. package/dist/lib/device-config.d.ts +25 -10
  158. package/dist/lib/device-config.js +90 -39
  159. package/dist/lib/devices/config-migration.d.ts +7 -0
  160. package/dist/lib/devices/config-migration.js +67 -3
  161. package/dist/lib/devices/health.d.ts +27 -2
  162. package/dist/lib/devices/health.js +39 -9
  163. package/dist/lib/devices/interactive-host.d.ts +15 -0
  164. package/dist/lib/devices/interactive-host.js +53 -0
  165. package/dist/lib/devices/registry.d.ts +61 -4
  166. package/dist/lib/devices/registry.js +94 -38
  167. package/dist/lib/devices/stats-cache.d.ts +26 -0
  168. package/dist/lib/devices/stats-cache.js +59 -7
  169. package/dist/lib/devices/worker-pick.d.ts +48 -0
  170. package/dist/lib/devices/worker-pick.js +113 -0
  171. package/dist/lib/exec.d.ts +70 -14
  172. package/dist/lib/exec.js +110 -38
  173. package/dist/lib/feed/answer.d.ts +29 -0
  174. package/dist/lib/feed/answer.js +145 -0
  175. package/dist/lib/feed/attention.d.ts +83 -0
  176. package/dist/lib/feed/attention.js +0 -0
  177. package/dist/lib/feed/feed.d.ts +109 -0
  178. package/dist/lib/feed/feed.js +154 -3
  179. package/dist/lib/feed/pr-status.d.ts +14 -0
  180. package/dist/lib/feed/pr-status.js +47 -0
  181. package/dist/lib/feed/watch.d.ts +59 -0
  182. package/dist/lib/feed/watch.js +180 -0
  183. package/dist/lib/fleet/capture.js +6 -0
  184. package/dist/lib/fleet/remote-login.js +8 -3
  185. package/dist/lib/fleet/types.d.ts +17 -0
  186. package/dist/lib/harness/adapter.d.ts +11 -0
  187. package/dist/lib/harness/adapters/claude.js +38 -24
  188. package/dist/lib/harness/adapters/codex.js +2 -4
  189. package/dist/lib/harness/adapters/cursor.js +8 -11
  190. package/dist/lib/helper-download.d.ts +108 -0
  191. package/dist/lib/helper-download.js +194 -0
  192. package/dist/lib/helper-versions.d.ts +52 -0
  193. package/dist/lib/helper-versions.js +58 -0
  194. package/dist/lib/hooks/cache.js +19 -0
  195. package/dist/lib/hooks/install.js +2 -2
  196. package/dist/lib/hooks/match.d.ts +3 -0
  197. package/dist/lib/hooks/match.js +25 -0
  198. package/dist/lib/hosts/credentials.d.ts +1 -1
  199. package/dist/lib/hosts/credentials.js +1 -1
  200. package/dist/lib/hosts/dispatch.d.ts +7 -1
  201. package/dist/lib/hosts/dispatch.js +24 -5
  202. package/dist/lib/hosts/passthrough.d.ts +8 -15
  203. package/dist/lib/hosts/passthrough.js +35 -16
  204. package/dist/lib/hosts/providers/devices.d.ts +1 -1
  205. package/dist/lib/hosts/providers/devices.js +1 -1
  206. package/dist/lib/hosts/reconnect.d.ts +179 -21
  207. package/dist/lib/hosts/reconnect.js +266 -45
  208. package/dist/lib/hosts/registry.js +7 -0
  209. package/dist/lib/installations/migrate.d.ts +4 -5
  210. package/dist/lib/installations/migrate.js +6 -7
  211. package/dist/lib/installations/shims.d.ts +1 -1
  212. package/dist/lib/installations/shims.js +23 -13
  213. package/dist/lib/installations/versions.js +25 -2
  214. package/dist/lib/linear-autoclose.d.ts +1 -1
  215. package/dist/lib/linear-autoclose.js +1 -1
  216. package/dist/lib/loop.d.ts +2 -2
  217. package/dist/lib/loop.js +1 -1
  218. package/dist/lib/menubar/download-menubar.d.ts +47 -0
  219. package/dist/lib/menubar/download-menubar.js +60 -0
  220. package/dist/lib/menubar/install-menubar.d.ts +44 -7
  221. package/dist/lib/menubar/install-menubar.js +105 -23
  222. package/dist/lib/menubar/notify-desktop.d.ts +3 -3
  223. package/dist/lib/menubar/notify-desktop.js +6 -6
  224. package/dist/lib/model-tiers.d.ts +1 -1
  225. package/dist/lib/open-url.d.ts +60 -2
  226. package/dist/lib/open-url.js +167 -12
  227. package/dist/lib/project-probe.d.ts +1 -1
  228. package/dist/lib/project-probe.js +1 -1
  229. package/dist/lib/project-pull.d.ts +1 -1
  230. package/dist/lib/project-pull.js +2 -2
  231. package/dist/lib/projects.d.ts +1 -1
  232. package/dist/lib/projects.js +1 -1
  233. package/dist/lib/refresh-coordinator.d.ts +1 -1
  234. package/dist/lib/refresh-coordinator.js +1 -1
  235. package/dist/lib/sandbox.js +6 -7
  236. package/dist/lib/secrets/download-keychain.d.ts +47 -0
  237. package/dist/lib/secrets/download-keychain.js +70 -0
  238. package/dist/lib/secrets/install-helper.d.ts +20 -1
  239. package/dist/lib/secrets/install-helper.js +44 -3
  240. package/dist/lib/session/active.d.ts +67 -0
  241. package/dist/lib/session/active.js +86 -2
  242. package/dist/lib/session/db.d.ts +11 -1
  243. package/dist/lib/session/db.js +142 -8
  244. package/dist/lib/session/discover.d.ts +23 -1
  245. package/dist/lib/session/discover.js +44 -10
  246. package/dist/lib/session/highlights.d.ts +49 -1
  247. package/dist/lib/session/highlights.js +95 -0
  248. package/dist/lib/session/host-link.d.ts +16 -2
  249. package/dist/lib/session/host-link.js +7 -1
  250. package/dist/lib/session/parse.js +33 -1
  251. package/dist/lib/session/prompt.d.ts +35 -0
  252. package/dist/lib/session/prompt.js +56 -0
  253. package/dist/lib/session/remote/remote-list.d.ts +9 -0
  254. package/dist/lib/session/remote/remote-list.js +19 -2
  255. package/dist/lib/session/remote/watch.d.ts +1 -1
  256. package/dist/lib/session/remote/watch.js +17 -3
  257. package/dist/lib/session/render.js +7 -3
  258. package/dist/lib/session/session-cache.d.ts +53 -0
  259. package/dist/lib/session/session-cache.js +93 -1
  260. package/dist/lib/session/state.js +1 -1
  261. package/dist/lib/session/tool-calls.d.ts +2 -0
  262. package/dist/lib/session/tool-calls.js +1 -1
  263. package/dist/lib/session/trajectory-compare.d.ts +47 -0
  264. package/dist/lib/session/trajectory-compare.js +116 -0
  265. package/dist/lib/session/trajectory-html.d.ts +19 -0
  266. package/dist/lib/session/trajectory-html.js +770 -0
  267. package/dist/lib/session/trajectory-lineage.d.ts +92 -0
  268. package/dist/lib/session/trajectory-lineage.js +195 -0
  269. package/dist/lib/session/trajectory-text.d.ts +37 -0
  270. package/dist/lib/session/trajectory-text.js +284 -0
  271. package/dist/lib/session/trajectory.d.ts +102 -0
  272. package/dist/lib/session/trajectory.js +384 -0
  273. package/dist/lib/session/types.d.ts +15 -0
  274. package/dist/lib/sha256-asset.d.ts +26 -0
  275. package/dist/lib/sha256-asset.js +41 -0
  276. package/dist/lib/share/backend.d.ts +95 -0
  277. package/dist/lib/share/backend.js +139 -0
  278. package/dist/lib/share/delete.d.ts +6 -2
  279. package/dist/lib/share/delete.js +8 -9
  280. package/dist/lib/share/html.d.ts +22 -0
  281. package/dist/lib/share/html.js +88 -0
  282. package/dist/lib/share/provision.d.ts +18 -4
  283. package/dist/lib/share/provision.js +38 -4
  284. package/dist/lib/share/publish.d.ts +39 -15
  285. package/dist/lib/share/publish.js +55 -26
  286. package/dist/lib/share/worker-template.js +171 -11
  287. package/dist/lib/sqlite.js +16 -3
  288. package/dist/lib/ssh-exec.d.ts +65 -0
  289. package/dist/lib/ssh-exec.js +120 -4
  290. package/dist/lib/startup/command-registry.d.ts +3 -1
  291. package/dist/lib/startup/command-registry.js +6 -3
  292. package/dist/lib/startup/dev-build.js +3 -3
  293. package/dist/lib/state.d.ts +2 -2
  294. package/dist/lib/state.js +11 -5
  295. package/dist/lib/subagents-registry.js +1 -1
  296. package/dist/lib/teams/agents.js +9 -0
  297. package/dist/lib/terminal/index.d.ts +1 -1
  298. package/dist/lib/terminal/inject.d.ts +19 -0
  299. package/dist/lib/terminal/inject.js +19 -9
  300. package/dist/lib/terminal/types.d.ts +1 -1
  301. package/dist/lib/terminal/types.js +1 -1
  302. package/dist/lib/tmux/orphan-reap.js +1 -1
  303. package/dist/lib/tmux/session.d.ts +46 -13
  304. package/dist/lib/tmux/session.js +204 -27
  305. package/dist/lib/traces/backend.d.ts +29 -0
  306. package/dist/lib/traces/backend.js +46 -0
  307. package/dist/lib/traces/classify.d.ts +52 -0
  308. package/dist/lib/traces/classify.js +72 -0
  309. package/dist/lib/traces/config.d.ts +10 -0
  310. package/dist/lib/traces/config.js +2 -0
  311. package/dist/lib/traces/provision.d.ts +19 -0
  312. package/dist/lib/traces/provision.js +30 -0
  313. package/dist/lib/traces/sync.d.ts +150 -0
  314. package/dist/lib/traces/sync.js +447 -0
  315. package/dist/lib/traces/worker-template.d.ts +4 -0
  316. package/dist/lib/traces/worker-template.js +159 -0
  317. package/dist/lib/types.d.ts +34 -19
  318. package/dist/lib/types.js +14 -0
  319. package/dist/lib/usage-backoff.d.ts +2 -1
  320. package/dist/lib/usage-backoff.js +60 -19
  321. package/dist/lib/usage-refresh.d.ts +18 -4
  322. package/dist/lib/usage-refresh.js +68 -19
  323. package/dist/lib/watchdog/log.d.ts +1 -1
  324. package/dist/lib/watchdog/log.js +1 -1
  325. package/dist/lib/watchdog/runner.d.ts +36 -45
  326. package/dist/lib/watchdog/runner.js +150 -119
  327. package/dist/lib/watchdog/service.d.ts +0 -1
  328. package/dist/lib/watchdog/service.js +0 -1
  329. package/dist/lib/watchdog/watchdog-agent.d.ts +38 -0
  330. package/dist/lib/watchdog/watchdog-agent.js +59 -0
  331. package/dist/lib/watchdog/watchdog.d.ts +11 -2
  332. package/dist/lib/watchdog/watchdog.js +37 -104
  333. package/dist/lib/workflows.d.ts +1 -1
  334. package/package.json +5 -7
  335. package/dist/commands/usage.d.ts +0 -27
  336. package/dist/commands/usage.js +0 -96
  337. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  338. package/dist/lib/menubar/MenubarHelper.app/Contents/Info.plist +0 -24
  339. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  340. package/dist/lib/menubar/MenubarHelper.app/Contents/Resources/AppIcon.icns +0 -0
  341. package/dist/lib/menubar/MenubarHelper.app/Contents/_CodeSignature/CodeResources +0 -128
  342. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  343. package/dist/lib/secrets/Agents CLI.app/Contents/Info.plist +0 -24
  344. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  345. package/dist/lib/secrets/Agents CLI.app/Contents/_CodeSignature/CodeResources +0 -123
  346. package/dist/lib/secrets/Agents CLI.app/Contents/embedded.provisionprofile +0 -0
  347. package/dist/lib/usage-fleet.d.ts +0 -32
  348. package/dist/lib/usage-fleet.js +0 -125
@@ -0,0 +1,184 @@
1
+ import chalk from 'chalk';
2
+ import { setHelpSections } from '../lib/help.js';
3
+ import { syncTraces } from '../lib/traces/sync.js';
4
+ import { readSyncLedger } from '../lib/traces/sync.js';
5
+ import { managedTracesBaseUrl, resolveTracesBackend } from '../lib/traces/backend.js';
6
+ import { showUrl } from '../lib/open-url.js';
7
+ import { DEFAULT_CF_BUNDLE, readCloudflareCreds } from '../lib/share/config.js';
8
+ import { PHOENIX_ID_BASE } from '../lib/identity/client.js';
9
+ import { DEFAULT_BUCKET_NAME, DEFAULT_WORKER_NAME } from '../lib/traces/config.js';
10
+ import { DEFAULT_TRACES_DOMAIN } from '../lib/traces/backend.js';
11
+ import { provisionTraces } from '../lib/traces/provision.js';
12
+ // ---------------------------------------------------------------------------
13
+ // Help text
14
+ // ---------------------------------------------------------------------------
15
+ const SYNC_EXAMPLES = `
16
+ $ agents traces sync
17
+ Push this device's derived, redacted trajectories to your Phoenix account.
18
+
19
+ $ agents traces sync --limit 20
20
+ Sync at most 20 sessions (useful for a first run on a large fleet device).
21
+
22
+ $ agents traces sync --dry-run --out ./trace-preview
23
+ Compute the shards from your real sessions.db and WRITE them locally — no
24
+ Phoenix sign-in, no worker, no upload. Point a console at ./trace-preview to
25
+ see your real trajectories before wiring the hosted path.
26
+ `.trimStart();
27
+ const SYNC_NOTES = `
28
+ Reads from the local sessions.db, computes a derived SessionTrajectory (steps +
29
+ gaps + stats, no raw transcript text), redacts secrets, and PUTs each to
30
+ the agents-traces store.
31
+
32
+ An incremental gate (traces-sync.json ledger) means re-runs only upload
33
+ sessions whose file_mtime_ms changed since the last sync.
34
+
35
+ Sign in first with: agents auth login
36
+ `.trimStart();
37
+ const STATUS_EXAMPLES = `
38
+ $ agents traces status
39
+ Show what's been synced — session count and last sync time for this device.
40
+ `.trimStart();
41
+ const OPEN_EXAMPLES = `
42
+ $ agents traces open
43
+ Open the Phoenix Evals console for your account.
44
+ `.trimStart();
45
+ const SETUP_EXAMPLES = `
46
+ $ agents traces setup
47
+ Provision the private agents-traces Worker, R2 bucket, and managed domain.
48
+
49
+ $ agents traces setup --account <id> --token <token>
50
+ Provision non-interactively with explicit Cloudflare credentials.
51
+ `.trimStart();
52
+ // ---------------------------------------------------------------------------
53
+ // Command handlers
54
+ // ---------------------------------------------------------------------------
55
+ async function handleSync(opts) {
56
+ const limit = opts.limit !== undefined ? parseInt(opts.limit, 10) : undefined;
57
+ const dryRun = opts.dryRun === true;
58
+ if (dryRun && !opts.out) {
59
+ console.error(chalk.red('traces sync --dry-run requires --out <dir>'));
60
+ process.exitCode = 1;
61
+ return;
62
+ }
63
+ // A dry-run computes locally and needs no Phoenix backend; a real sync does.
64
+ if (!dryRun) {
65
+ try {
66
+ resolveTracesBackend();
67
+ }
68
+ catch (err) {
69
+ console.error(chalk.red(err.message));
70
+ process.exitCode = 1;
71
+ return;
72
+ }
73
+ }
74
+ console.log(chalk.dim(dryRun ? `Computing traces locally → ${opts.out}` : 'Syncing traces…'));
75
+ let result;
76
+ try {
77
+ result = await syncTraces({ limit, dryRun, outDir: opts.out });
78
+ }
79
+ catch (err) {
80
+ console.error(chalk.red(`Sync failed: ${err.message}`));
81
+ process.exitCode = 1;
82
+ return;
83
+ }
84
+ const parts = [];
85
+ if (result.uploaded > 0) {
86
+ parts.push(chalk.green(`${result.uploaded} ${dryRun ? 'written' : 'uploaded'}`));
87
+ }
88
+ if (result.skipped > 0) {
89
+ parts.push(chalk.dim(`${result.skipped} skipped`));
90
+ }
91
+ if (result.errors > 0) {
92
+ parts.push(chalk.yellow(`${result.errors} errors`));
93
+ }
94
+ if (parts.length === 0) {
95
+ parts.push(chalk.dim('nothing new'));
96
+ }
97
+ console.log(parts.join(chalk.dim(' · ')));
98
+ }
99
+ async function handleStatus() {
100
+ const ledger = readSyncLedger();
101
+ if (!ledger.lastSyncMtime) {
102
+ console.log(chalk.dim('No sync recorded on this device. Run: agents traces sync'));
103
+ return;
104
+ }
105
+ const when = new Date(ledger.lastSyncMtime).toLocaleString();
106
+ console.log(`Last sync: ${chalk.bold(when)}`);
107
+ console.log(chalk.dim('Run `agents traces sync` to push new sessions.'));
108
+ }
109
+ async function handleOpen() {
110
+ // M2 console — for now open the base URL which will route to the console once deployed.
111
+ const url = managedTracesBaseUrl();
112
+ console.log(chalk.dim(`Opening ${url}`));
113
+ const outcome = await showUrl(url);
114
+ if (outcome.via === 'none') {
115
+ console.log(url);
116
+ }
117
+ }
118
+ async function handleSetup(opts) {
119
+ const { input } = await import('@inquirer/prompts');
120
+ const { apiToken, accountId: bundledAccountId } = readCloudflareCreds(opts.bundle, {
121
+ apiToken: opts.token,
122
+ accountId: opts.account,
123
+ });
124
+ const accountId = opts.account ?? bundledAccountId ?? await input({ message: 'Cloudflare account id' });
125
+ if (!accountId)
126
+ throw new Error('A Cloudflare account id is required.');
127
+ const result = await provisionTraces({
128
+ apiToken,
129
+ accountId,
130
+ workerName: opts.worker,
131
+ bucketName: opts.bucket,
132
+ domain: opts.domain,
133
+ phoenixIdBase: PHOENIX_ID_BASE,
134
+ });
135
+ console.log(chalk.green(`Traces endpoint ready → ${chalk.bold(result.baseUrl)}`));
136
+ }
137
+ // ---------------------------------------------------------------------------
138
+ // Registration
139
+ // ---------------------------------------------------------------------------
140
+ export function registerTracesCommands(program) {
141
+ const tracesCmd = program
142
+ .command('traces')
143
+ .description("Sync this device's derived, redacted trajectories to your Phoenix account");
144
+ const setupCmd = tracesCmd
145
+ .command('setup')
146
+ .description('Provision the private Cloudflare Worker and R2 bucket for traces')
147
+ .option('--bundle <name>', 'secrets bundle holding the Cloudflare API token', DEFAULT_CF_BUNDLE)
148
+ .option('--worker <name>', 'Worker name', DEFAULT_WORKER_NAME)
149
+ .option('--bucket <name>', 'R2 bucket name', DEFAULT_BUCKET_NAME)
150
+ .option('--account <id>', 'Cloudflare account id (else read from the bundle / prompt)')
151
+ .option('--token <token>', 'Cloudflare API token (else read from the --bundle)')
152
+ .option('--domain <host>', 'custom domain to map', DEFAULT_TRACES_DOMAIN)
153
+ .action(async (opts) => {
154
+ try {
155
+ await handleSetup(opts);
156
+ }
157
+ catch (err) {
158
+ console.error(chalk.red(err.message));
159
+ process.exitCode = 1;
160
+ }
161
+ });
162
+ setHelpSections(setupCmd, { examples: SETUP_EXAMPLES });
163
+ // sync
164
+ const syncCmd = tracesCmd
165
+ .command('sync')
166
+ .description('Push derived, redacted trajectories (incremental)')
167
+ .option('--limit <n>', 'Maximum number of sessions to sync in this run')
168
+ .option('--dry-run', 'Compute the shards from real sessions.db and WRITE them locally (no auth, no upload)')
169
+ .option('--out <dir>', 'Directory to write index.json + sessions/<id>.json (required with --dry-run)')
170
+ .action(handleSync);
171
+ setHelpSections(syncCmd, { examples: SYNC_EXAMPLES, notes: SYNC_NOTES });
172
+ // status
173
+ const statusCmd = tracesCmd
174
+ .command('status')
175
+ .description('Show last sync time for this device')
176
+ .action(handleStatus);
177
+ setHelpSections(statusCmd, { examples: STATUS_EXAMPLES });
178
+ // open
179
+ const openCmd = tracesCmd
180
+ .command('open')
181
+ .description('Open the Phoenix Evals console')
182
+ .action(handleOpen);
183
+ setHelpSections(openCmd, { examples: OPEN_EXAMPLES });
184
+ }
@@ -10,10 +10,12 @@ import type { Command } from 'commander';
10
10
  import { accountDisplayLabel } from '../lib/agents.js';
11
11
  import type { AccountInfo } from '../lib/agents.js';
12
12
  import type { AgentId } from '../lib/types.js';
13
+ import type { FormatUsageSummaryOpts, UsageInfo } from '../lib/accounting/usage.js';
13
14
  import type { ResourceSection, ViewJsonAgent } from '../lib/view-types.js';
14
15
  export type { ResourceItemJson, ResourceSection, SyncState, VersionResourcesJson, ViewJsonAgent, ViewJsonVersion } from '../lib/view-types.js';
15
16
  import { type ProfileSummary } from '../lib/profiles.js';
16
17
  import { type ByokUsageResult } from '../lib/byok-usage.js';
18
+ export declare function viewUsageSummaryOptions(agentId: AgentId, signedIn: boolean, usageInfo: UsageInfo | undefined, maxWindows: number | undefined, version?: string): FormatUsageSummaryOpts;
17
19
  /** Shared account identity formatter, re-exported for the view-specific tests. */
18
20
  export declare const accountColumnLabel: typeof accountDisplayLabel;
19
21
  /**
@@ -7,8 +7,8 @@ import * as path from 'path';
7
7
  import { AGENTS, ALL_AGENT_IDS, accountDisplayLabel, getAllCliStates, getUnmanagedCliState, getAccountInfo, resolveAgentName, formatAgentError, agentLabel, colorAgent, } from '../lib/agents.js';
8
8
  import { ambientClaudeToken, loginHint } from '../lib/signin-badge.js';
9
9
  import { machineId } from '../lib/machine-id.js';
10
- import { authCacheKey, formatCheckedAge, readAuthHealthCache } from '../lib/auth-health.js';
11
- import { agentReportsUsage, deriveUsageStatusFromSnapshot, formatUsageSection, formatUsageSummary, formatUsageStatusBadge, getUsageInfoForIdentity, getUsageInfoByIdentity, getUsageLookupKey, isUsageHeadlessScopeError, } from '../lib/accounting/usage.js';
10
+ import { authCacheKey, readAuthHealthCache } from '../lib/auth-health.js';
11
+ import { agentReportsUsage, classifyUsageErrorKind, deriveUsageStatusFromSnapshot, formatUsageSection, formatUsageSummary, formatUsageStatusBadge, getUsageBenignState, getUsageInfoForIdentity, getUsageInfoByIdentity, getUsageLookupKey, isUsageHeadlessScopeError, } from '../lib/accounting/usage.js';
12
12
  import { readManifest } from '../lib/manifest.js';
13
13
  import { listInstalledVersions, listInstalledVersionDirs, getGlobalDefault, getVersionHomePath, getVersionDir, removeVersion, printTrashFooter, reconcileStaleLatestForAgent, isGlobalBinaryAgent, getLiveVersion, isVersionIsolated, getIsolatedDefault, } from '../lib/installations/versions.js';
14
14
  import { ensureVersionedAliasCurrent, removeShim, } from '../lib/installations/shims.js';
@@ -32,6 +32,25 @@ import { renderHarnessDetail } from './harness.js';
32
32
  import { confirm } from '@inquirer/prompts';
33
33
  import { formatPath, isInteractiveTerminal, isPromptCancelled } from './utils.js';
34
34
  import { terminalWidth, truncateToWidth, stringWidth, padToWidth } from '../lib/session/width.js';
35
+ export function viewUsageSummaryOptions(agentId, signedIn, usageInfo, maxWindows, version) {
36
+ const headless = isUsageHeadlessScopeError(usageInfo?.error);
37
+ const benignState = usageInfo ? getUsageBenignState(usageInfo) : null;
38
+ return {
39
+ unavailable: agentReportsUsage(agentId) && signedIn && !usageInfo?.snapshot && !headless && !benignState,
40
+ unverified: !headless && !!usageInfo?.snapshot && !!usageInfo.error,
41
+ headless,
42
+ maxWindows,
43
+ expectedWindows: agentId === 'claude'
44
+ ? [{ key: 'session', shortLabel: 'S' }, { key: 'week', shortLabel: 'W' }]
45
+ : undefined,
46
+ errorKind: classifyUsageErrorKind(usageInfo?.error),
47
+ errorDetail: usageInfo?.error ?? null,
48
+ benignState,
49
+ noRecentUsageLabel: agentId === 'grok'
50
+ ? `run grok${version ? `@${version}` : ''} once to refresh usage`
51
+ : null,
52
+ };
53
+ }
35
54
  /** Shared account identity formatter, re-exported for the view-specific tests. */
36
55
  export const accountColumnLabel = accountDisplayLabel;
37
56
  /**
@@ -79,8 +98,6 @@ export function compareAccountOrderedVersions(a, b, globalDefault) {
79
98
  * views leave the cap unset and show every blocking window.
80
99
  */
81
100
  const OVERVIEW_MAX_USAGE_WINDOWS = 2;
82
- /** Fixed width for the last-active column ("just now", "8h ago", "2d ago"). */
83
- const LAST_ACTIVE_COL_WIDTH = 10;
84
101
  /**
85
102
  * Join fixed view columns with a consistent two-space gutter. Empty trailing
86
103
  * columns are dropped so a row without an auth chip does not grow a dangling
@@ -299,22 +316,6 @@ function renderHostClisSection(cwd) {
299
316
  console.log(` ${chalk.red('error')} ${chalk.gray(err.file)}: ${chalk.gray(err.reason)}`);
300
317
  }
301
318
  }
302
- /**
303
- * A compact live-auth chip for a version row, read from the fleet auth-health
304
- * cache (written by `agents fleet ping` / the daemon). Empty when the install
305
- * hasn't been probed — this is the local "signed in" flag's ground-truth
306
- * companion: ● live, ○ revoked (re-login), ◐ unverified/limited, plus its age.
307
- */
308
- function liveAuthChip(cache, host, agentId, version) {
309
- const h = cache[authCacheKey(host, agentId, version)];
310
- if (!h)
311
- return '';
312
- const glyph = h.verdict === 'live' ? chalk.green('●')
313
- : h.verdict === 'revoked' ? chalk.red('○')
314
- : h.verdict === 'expired' ? chalk.yellow('○')
315
- : chalk.yellow('◐');
316
- return `${glyph} ${chalk.gray(formatCheckedAge(h.checkedAt))}`;
317
- }
318
319
  async function showInstalledVersions(filterAgentId, viewOpts) {
319
320
  const spinnerText = filterAgentId
320
321
  ? `Checking ${agentLabel(filterAgentId)} agents...`
@@ -365,9 +366,6 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
365
366
  }
366
367
  }
367
368
  // Shim healing is silent — users don't need to know about internal repairs
368
- const selfHost = machineId();
369
- // Read the auth-health cache once (not per version row — see the batching note above).
370
- const authCache = readAuthHealthCache();
371
369
  // Pre-fetch account info for all versions in parallel. Spinner stays up through
372
370
  // account + usage so a multi-account cold path doesn't leave a blank terminal
373
371
  // after "Checking…" vanishes (the hang the screenshots caught).
@@ -535,15 +533,7 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
535
533
  const info = rawInfo ? mergeCanonical(rawInfo) : undefined;
536
534
  const usageKey = getUsageLookupKey(info);
537
535
  const usageInfo = usageKey ? usageByKey.get(usageKey) : undefined;
538
- const headless = isUsageHeadlessScopeError(usageInfo?.error);
539
- const usageUnavailable = agentReportsUsage(agentId) && !!info?.signedIn && !usageInfo?.snapshot && !headless;
540
- const usageUnverified = !headless && !!usageInfo?.snapshot && !!usageInfo.error;
541
- const usageStr = formatUsageSummary(info?.plan || null, usageInfo?.snapshot || null, maxPlanWidth, {
542
- unavailable: usageUnavailable,
543
- unverified: usageUnverified,
544
- headless,
545
- maxWindows: usageWindowCap,
546
- });
536
+ const usageStr = formatUsageSummary(info?.plan || null, usageInfo?.snapshot || null, maxPlanWidth, viewUsageSummaryOptions(agentId, !!info?.signedIn, usageInfo, usageWindowCap, v));
547
537
  maxUsageWidth = Math.max(maxUsageWidth, stringWidth(usageStr));
548
538
  const statusStr = formatUsageStatusBadge(info?.usageStatus);
549
539
  maxStatusWidth = Math.max(maxStatusWidth, stringWidth(statusStr));
@@ -603,19 +593,12 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
603
593
  }
604
594
  const hasEmail = !!vInfo?.email;
605
595
  const signedIn = !!vInfo?.signedIn;
606
- const headless = isUsageHeadlessScopeError(usageInfo?.error);
607
- const usageUnavailable = agentReportsUsage(agentId) && signedIn && !usageInfo?.snapshot && !headless;
608
- const usageUnverified = !headless && !!usageInfo?.snapshot && !!usageInfo.error;
609
- const usageStr = formatUsageSummary(vInfo?.plan || null, usageInfo?.snapshot || null, maxPlanWidth, {
610
- unavailable: usageUnavailable,
611
- unverified: usageUnverified,
612
- headless,
613
- maxWindows: usageWindowCap,
614
- });
596
+ const usageStr = formatUsageSummary(vInfo?.plan || null, usageInfo?.snapshot || null, maxPlanWidth, viewUsageSummaryOptions(agentId, signedIn, usageInfo, usageWindowCap, version));
615
597
  const hasUsage = usageStr.length > 0;
616
598
  // Only show lastActive for versions with an actual logged-in account.
617
599
  // Otherwise it reflects install time (misleading "just now" for fresh installs).
618
- const activeStr = vInfo && hasEmail ? formatLastActive(vInfo.lastActive) : '';
600
+ const lastActive = vInfo && hasEmail ? formatLastActive(vInfo.lastActive) : '';
601
+ const activeStr = lastActive;
619
602
  const hasActive = activeStr.length > 0;
620
603
  // The model now has its own column above; keep only the run mode here.
621
604
  const runDefaults = resolveRunDefaults(agentId, version);
@@ -652,16 +635,12 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
652
635
  const statusStr = formatUsageStatusBadge(vInfo?.usageStatus);
653
636
  parts.push(padToWidth(statusStr, maxStatusWidth));
654
637
  }
655
- // Fixed-width lastActive so the auth chip (●/○/◐) lines up even when
656
- // some rows have no email-derived lastActive.
657
- parts.push(hasActive ? padToWidth(activeStr, LAST_ACTIVE_COL_WIDTH) : ' '.repeat(LAST_ACTIVE_COL_WIDTH));
638
+ if (hasActive)
639
+ parts.push(activeStr);
658
640
  }
659
641
  if (runDefaultBits.length > 0) {
660
642
  parts.push(chalk.gray(`run ${runDefaultBits.join(' ')}`));
661
643
  }
662
- const authChip = liveAuthChip(authCache, selfHost, agentId, version);
663
- if (authChip)
664
- parts.push(authChip);
665
644
  console.log(joinViewColumns(parts));
666
645
  if (showPaths) {
667
646
  const versionDir = getVersionDir(agentId, version);
@@ -731,12 +710,7 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
731
710
  gMaxStatusWidth = Math.max(gMaxStatusWidth, stringWidth(formatUsageStatusBadge(gInfo?.usageStatus)));
732
711
  const gUsageKey = getUsageLookupKey(gInfo);
733
712
  const gUsage = gUsageKey ? usageByKey.get(gUsageKey) : undefined;
734
- const gHeadless = isUsageHeadlessScopeError(gUsage?.error);
735
- const gUsageStr = formatUsageSummary(gInfo?.plan || null, gUsage?.snapshot || null, 3, {
736
- unverified: !gHeadless && !!gUsage?.snapshot && !!gUsage.error,
737
- headless: gHeadless,
738
- maxWindows: usageWindowCap,
739
- });
713
+ const gUsageStr = formatUsageSummary(gInfo?.plan || null, gUsage?.snapshot || null, 3, viewUsageSummaryOptions(agentId, !!gInfo?.signedIn, gUsage, usageWindowCap));
740
714
  gMaxUsageWidth = Math.max(gMaxUsageWidth, stringWidth(gUsageStr));
741
715
  const gDisplay = accountColumnLabel(gInfo);
742
716
  if (gDisplay)
@@ -754,13 +728,9 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
754
728
  const parts = [` ${verLabel}${padding}`];
755
729
  const gUsageKey = getUsageLookupKey(gInfo);
756
730
  const gUsage = gUsageKey ? usageByKey.get(gUsageKey) : undefined;
757
- const gHeadless = isUsageHeadlessScopeError(gUsage?.error);
758
- const gUsageStr = formatUsageSummary(gInfo?.plan || null, gUsage?.snapshot || null, 3, {
759
- unverified: !gHeadless && !!gUsage?.snapshot && !!gUsage.error,
760
- headless: gHeadless,
761
- maxWindows: usageWindowCap,
762
- });
763
- const gActiveStr = gInfo ? formatLastActive(gInfo.lastActive) : '';
731
+ const gUsageStr = formatUsageSummary(gInfo?.plan || null, gUsage?.snapshot || null, 3, viewUsageSummaryOptions(agentId, !!gInfo?.signedIn, gUsage, usageWindowCap));
732
+ const gLastActive = gInfo ? formatLastActive(gInfo.lastActive) : '';
733
+ const gActiveStr = gLastActive;
764
734
  if (gInfo?.email || gUsageStr || gActiveStr || gInfo?.signedIn) {
765
735
  const gDisplay = accountColumnLabel(gInfo);
766
736
  parts.push(gDisplay ? chalk.cyan(padToWidth(gDisplay, gMaxEmail)) : ' '.repeat(gMaxEmail));
@@ -771,7 +741,7 @@ async function showInstalledVersions(filterAgentId, viewOpts) {
771
741
  parts.push(padToWidth(formatUsageStatusBadge(gInfo?.usageStatus), gMaxStatusWidth));
772
742
  }
773
743
  if (gActiveStr)
774
- parts.push(padToWidth(gActiveStr, LAST_ACTIVE_COL_WIDTH));
744
+ parts.push(gActiveStr);
775
745
  console.log(joinViewColumns(parts));
776
746
  if (showPaths && cliState?.path) {
777
747
  console.log(chalk.gray(` ${cliState.path}`));
@@ -144,8 +144,7 @@ export function registerWatchdogCommand(program) {
144
144
  .option('--cooldown <dur>', 'Minimum time between nudges to the same session', humanMs(DEFAULT_THRESHOLDS.cooldownMs))
145
145
  .option('--dormant <dur>', 'Idle time after which a session is left alone (dormant)', humanMs(DEFAULT_THRESHOLDS.dormantMs))
146
146
  .option('--text <text>', 'Nudge text delivered into the terminal', 'Continue.')
147
- .option('--smart', 'Use the LLM decider (agents run) instead of the deterministic path (non-reproducible)')
148
- .option('--smart-agent <agent>', 'Agent the --smart decider runs as', 'claude')
147
+ .option('--smart-agent <agent>', 'Agent the watchdog decider runs as', 'claude')
149
148
  .option('--allow-ghostty-focus', 'Permit the coarse, focus-stealing Ghostty path (off by default)')
150
149
  .option('--verbose', 'Show healthy and non-actionable session inspections too')
151
150
  .option('--json', 'Emit the tick result as JSON (for the menu-bar / scripts)')
@@ -163,7 +162,6 @@ export function registerWatchdogCommand(program) {
163
162
  const tickOnce = async (willInject, sessions) => runWatchdogPass({
164
163
  nudge: willInject,
165
164
  nudgeText: opts.text,
166
- smart: opts.smart === true,
167
165
  smartAgent: opts.smartAgent,
168
166
  thresholds,
169
167
  allowGhosttyFocus: opts.allowGhosttyFocus === true,
@@ -233,17 +231,14 @@ export function registerWatchdogCommand(program) {
233
231
  agents watchdog rotate off
234
232
  `,
235
233
  notes: `
236
- Decision path: a cheap deterministic pre-filter resolves the obvious cases
237
- (clearly complete -> skip; a clear promise-without-toolcall -> nudge) and
238
- ESCALATES the judgment-heavy cases -- a session parked on a question, or an
239
- ambiguous stall -- to a smart brain. The brain drives the agent to finish
240
- end-to-end when it asked a needless / already-authorized question or paused
241
- with work left, and leaves it for the human on genuine cases (credentials,
242
- an irreversible or outward-facing action, a real ambiguous decision, or a
243
- completed task). The brain is a customizable 'watchdog' workflow (drop a
244
- WORKFLOW.md in project/user workflows/ to override the prompt + model);
245
- absent one, the built-in prompt runs via 'agents run --mode plan'. Pass
246
- --smart to force every stalled candidate through the brain.
234
+ Decision path: the watchdog is an AGENT, not a heuristic script. Every idle
235
+ session on this machine (its task + transcript tail) is handed to ONE
236
+ 'agents run --mode plan' call per tick, which judges each: idle-but-unfinished
237
+ (was given a task, went quiet, not finished/handed off) -> NUDGE that drives
238
+ it to finish; idle-and-done or genuinely-needs-human -> SKIP. The agent is a
239
+ customizable 'watchdog' workflow (drop a WORKFLOW.md in project/user
240
+ workflows/ to override the prompt + model); absent one, the built-in prompt
241
+ runs. Nothing is nudged when nothing is idle (no agent is spawned).
247
242
 
248
243
  Delivery (answer-router): a running agent is steered via its mailbox; a
249
244
  parked-on-question agent is answered into its EXACT split -- tmux / iTerm /
package/dist/index.d.ts CHANGED
@@ -14,6 +14,7 @@
14
14
  * - `__vault-age-helper`
15
15
  * - `__secrets-get` / `__secrets-ping` / `__secrets-lock` (SYNC_* tokens)
16
16
  * - `__shim`
17
+ * - `__claude-statusline`
17
18
  * - `__daemon-run`
18
19
  *
19
20
  * The tokens are imported from the leaf module sync-commands.ts — the SAME
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@
14
14
  * - `__vault-age-helper`
15
15
  * - `__secrets-get` / `__secrets-ping` / `__secrets-lock` (SYNC_* tokens)
16
16
  * - `__shim`
17
+ * - `__claude-statusline`
17
18
  * - `__daemon-run`
18
19
  *
19
20
  * The tokens are imported from the leaf module sync-commands.ts — the SAME
@@ -70,6 +71,10 @@ if (process.argv[2] === '__shim') {
70
71
  const code = await execShimPassthrough(agent, rawArgs, process.cwd(), pinned || undefined);
71
72
  process.exit(code);
72
73
  }
74
+ if (process.argv[2] === '__claude-statusline') {
75
+ const { runClaudeStatusLine } = await import('./lib/claude-statusline.js');
76
+ process.exit(await runClaudeStatusLine());
77
+ }
73
78
  if (process.argv[2] === '__daemon-run') {
74
79
  const { runDaemon, log: daemonLog } = await import('./lib/daemon/daemon.js');
75
80
  // RUSH-2418: the daemon is the one always-on process here, and it ran with no
@@ -19,6 +19,10 @@ export interface NativeAccountCapability {
19
19
  scope: 'version' | 'device' | 'unsupported';
20
20
  status: 'supported' | 'conditional' | 'discovery-only' | 'unsupported';
21
21
  }
22
+ /** Native-login selector coverage required by RUSH-3053. */
23
+ export declare const NATIVE_ACCOUNT_SELECTOR_AGENTS: readonly ["claude", "codex", "cursor", "grok", "kimi"];
24
+ /** Explicit dispositions for config-isolated harnesses outside that selector. */
25
+ export declare const NATIVE_ACCOUNT_SELECTOR_EXCLUSIONS: Partial<Record<AgentId, string>>;
22
26
  /** Canonical truth for native-account naming and attachment semantics. */
23
27
  export declare const NATIVE_ACCOUNT_CAPABILITIES: Record<AgentId, NativeAccountCapability>;
24
28
  export declare function nativeAccountCapability(agent: AgentId): NativeAccountCapability;
@@ -1,10 +1,21 @@
1
+ /** Native-login selector coverage required by RUSH-3053. */
2
+ export const NATIVE_ACCOUNT_SELECTOR_AGENTS = ['claude', 'codex', 'cursor', 'grok', 'kimi'];
3
+ /** Explicit dispositions for config-isolated harnesses outside that selector. */
4
+ export const NATIVE_ACCOUNT_SELECTOR_EXCLUSIONS = {
5
+ copilot: 'no inspectable native identity',
6
+ opencode: 'provider-set identity is not safely attributable to one native login',
7
+ muse: 'email-only conditional identity is outside the RUSH-3053 harness contract',
8
+ };
1
9
  /** Canonical truth for native-account naming and attachment semantics. */
2
10
  export const NATIVE_ACCOUNT_CAPABILITIES = {
3
- // Version-scoped with a strong account key the only harnesses whose native
4
- // identity is safe to pin per installed version.
11
+ // Config-isolated harnesses with a stable native identity. A labeled launch
12
+ // may use the auth/config home from one installed version with another binary.
5
13
  claude: { inspection: 'strong', scope: 'version', status: 'supported' },
6
14
  codex: { inspection: 'strong', scope: 'version', status: 'supported' },
7
15
  grok: { inspection: 'strong', scope: 'version', status: 'supported' },
16
+ cursor: { inspection: 'strong', scope: 'version', status: 'supported' },
17
+ // Kimi exposes a stable opaque id but no email, so it requires a manual label.
18
+ kimi: { inspection: 'opaque', scope: 'version', status: 'supported' },
8
19
  // Version-scoped but only an email identity — nameable only when that email is
9
20
  // present (the resolver rejects an emailless Muse login).
10
21
  muse: { inspection: 'email', scope: 'version', status: 'conditional' },
@@ -17,12 +28,8 @@ export const NATIVE_ACCOUNT_CAPABILITIES = {
17
28
  // "unsupported" is correct until a device-scoped identity key (with a stable
18
29
  // device id) exists to validate against.
19
30
  antigravity: { inspection: 'opaque', scope: 'device', status: 'unsupported' },
20
- kimi: { inspection: 'opaque', scope: 'device', status: 'unsupported' },
21
31
  droid: { inspection: 'opaque', scope: 'device', status: 'unsupported' },
22
32
  opencode: { inspection: 'opaque', scope: 'device', status: 'unsupported' },
23
- // Cursor multi-account isolation is unresolved (RUSH-2400) — blocked from
24
- // native naming/attachment; its API-key path is a provider account instead.
25
- cursor: { inspection: 'none', scope: 'unsupported', status: 'unsupported' },
26
33
  // Discoverable in the list, but not nameable.
27
34
  gemini: { inspection: 'email', scope: 'unsupported', status: 'discovery-only' },
28
35
  copilot: { inspection: 'none', scope: 'unsupported', status: 'unsupported' },
@@ -62,9 +69,6 @@ export function nativeAccountNamingRefusal(agent) {
62
69
  if (cap.scope === 'device') {
63
70
  return `${agent} accounts can't be isolated by agents-cli yet (device-scoped login). ${suffix}`;
64
71
  }
65
- if (agent === 'cursor') {
66
- return `${agent} accounts can't be isolated by agents-cli yet (multi-account isolation unresolved). ${suffix}`;
67
- }
68
72
  if (cap.status === 'discovery-only') {
69
73
  return `${agent} native accounts are discovery-only; agents-cli cannot name or attach this login. ${suffix}`;
70
74
  }
@@ -48,6 +48,8 @@ export declare function listNativeAccounts(meta: Pick<Meta, 'accounts'>): Native
48
48
  */
49
49
  export declare function findUnifiedAccount(nameOrId: string, meta: Pick<Meta, 'accounts'>, doc?: AccountRegistryDocument): UnifiedAccount | null;
50
50
  export declare function addNativeAccount(name: string, agent: AgentId, identityKey: string, identityLabel: string | undefined, scope: 'version' | 'device'): NativeAccount;
51
+ /** Create or replace the version-independent label for one native identity. */
52
+ export declare function labelNativeAccount(agent: AgentId, identityKey: string, identityLabel: string | undefined, label: string | undefined, scope: 'version' | 'device'): NativeAccount;
51
53
  export declare function bindAccount(nameOrId: string, target: string): UnifiedAccount;
52
54
  export declare function unbindAccount(nameOrId: string, target: string): void;
53
55
  export declare function accountBindings(accountId: string, meta: Pick<Meta, 'accounts'>): string[];
@@ -25,12 +25,17 @@ import { bundleExists, deleteBundle, listBundles, readBundle, renameBundle, writ
25
25
  import { getAccountProvider } from './account-provider-registry.js';
26
26
  import { accountSecretItem, buildAccountBundle, parseAccountBundle } from './account-schema.js';
27
27
  const NAME = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
28
+ const NATIVE_LABEL = /^[a-zA-Z0-9][a-zA-Z0-9@._+-]*$/;
28
29
  const AUTH_KINDS = ['api-key', 'setup-token', 'bearer-token'];
29
30
  export function accountRegistryPath(base = getUserAgentsDir()) { return path.join(base, 'accounts.yaml'); }
30
31
  function assertName(name) {
31
32
  if (!NAME.test(name))
32
33
  throw new Error('Account name must start with a letter or number and contain only letters, numbers, dot, underscore, or dash.');
33
34
  }
35
+ function assertNativeLabel(label) {
36
+ if (!NATIVE_LABEL.test(label))
37
+ throw new Error('Account label must start with a letter or number and contain only letters, numbers, @, dot, underscore, plus, or dash.');
38
+ }
34
39
  function isAccountAuthKind(value) {
35
40
  return typeof value === 'string' && AUTH_KINDS.includes(value);
36
41
  }
@@ -143,7 +148,8 @@ export function listNativeAccounts(meta) {
143
148
  * able to omit it.
144
149
  */
145
150
  export function findUnifiedAccount(nameOrId, meta, doc) {
146
- const native = listNativeAccounts(meta).find(account => account.id === nameOrId || account.name === nameOrId);
151
+ const needle = nameOrId.toLowerCase();
152
+ const native = listNativeAccounts(meta).find(account => account.id === nameOrId || account.name.toLowerCase() === needle || account.identityLabel?.toLowerCase() === needle);
147
153
  if (native)
148
154
  return native;
149
155
  const provider = findAccount(nameOrId, doc ?? readAccountRegistry());
@@ -154,7 +160,7 @@ function assertUniqueUnifiedName(name, meta, doc) {
154
160
  throw new Error(`Account '${name}' already exists.`);
155
161
  }
156
162
  export function addNativeAccount(name, agent, identityKey, identityLabel, scope) {
157
- assertName(name);
163
+ assertNativeLabel(name);
158
164
  const meta = readMeta();
159
165
  assertUniqueUnifiedName(name, meta);
160
166
  const duplicate = listNativeAccounts(meta).find(account => account.agent === agent && account.identityKey === identityKey);
@@ -170,6 +176,28 @@ export function addNativeAccount(name, agent, identityKey, identityLabel, scope)
170
176
  }));
171
177
  return account;
172
178
  }
179
+ /** Create or replace the version-independent label for one native identity. */
180
+ export function labelNativeAccount(agent, identityKey, identityLabel, label, scope) {
181
+ const resolvedLabel = label ?? identityLabel;
182
+ if (!resolvedLabel)
183
+ throw new Error(`${agent} does not expose an email; pass a manual label.`);
184
+ assertNativeLabel(resolvedLabel);
185
+ const meta = readMeta();
186
+ const existing = listNativeAccounts(meta).find(account => account.agent === agent && account.identityKey === identityKey);
187
+ const collision = findUnifiedAccount(resolvedLabel, meta);
188
+ if (collision && collision.id !== existing?.id)
189
+ throw new Error(`Account '${resolvedLabel}' already exists.`);
190
+ if (!existing)
191
+ return addNativeAccount(resolvedLabel, agent, identityKey, identityLabel, scope);
192
+ updateMeta(current => ({
193
+ ...current,
194
+ accounts: {
195
+ ...current.accounts,
196
+ native: { ...current.accounts?.native, [existing.id]: { ...current.accounts?.native?.[existing.id], name: resolvedLabel, identityLabel } },
197
+ },
198
+ }));
199
+ return { ...existing, name: resolvedLabel, identityLabel };
200
+ }
173
201
  export function bindAccount(nameOrId, target) {
174
202
  const meta = readMeta();
175
203
  const account = findUnifiedAccount(nameOrId, meta);
@@ -118,6 +118,15 @@ export declare const USAGE_DECISION_MAX_AGE_MS: number;
118
118
  /**
119
119
  * Whether this candidate's usage number is recent enough to route on. A missing
120
120
  * snapshot is unverified by definition — there is no number to trust.
121
+ *
122
+ * A snapshot with NO windows is unverified for the same reason, however fresh
123
+ * it is: it carries a subscription plan and no utilization, so there is still
124
+ * no number. Freshness alone would make a meterless harness (Grok reports a
125
+ * tier and no meters) verify against nothing — and since `preferVerified`
126
+ * narrows the pool to verified candidates, the one account whose billing log
127
+ * was touched most recently would win every draw, then win again because
128
+ * running it refreshes that log. That self-reinforcing pin is exactly what the
129
+ * narrowing rule below exists to prevent.
121
130
  */
122
131
  export declare function isUsageVerified(candidate: RotateCandidate, nowMs?: number): boolean;
123
132
  /**
@@ -113,10 +113,20 @@ export const USAGE_DECISION_MAX_AGE_MS = 5 * 60 * 1000;
113
113
  /**
114
114
  * Whether this candidate's usage number is recent enough to route on. A missing
115
115
  * snapshot is unverified by definition — there is no number to trust.
116
+ *
117
+ * A snapshot with NO windows is unverified for the same reason, however fresh
118
+ * it is: it carries a subscription plan and no utilization, so there is still
119
+ * no number. Freshness alone would make a meterless harness (Grok reports a
120
+ * tier and no meters) verify against nothing — and since `preferVerified`
121
+ * narrows the pool to verified candidates, the one account whose billing log
122
+ * was touched most recently would win every draw, then win again because
123
+ * running it refreshes that log. That self-reinforcing pin is exactly what the
124
+ * narrowing rule below exists to prevent.
116
125
  */
117
126
  export function isUsageVerified(candidate, nowMs = Date.now()) {
118
- const capturedAt = candidate.usageSnapshot?.capturedAt;
119
- if (!capturedAt)
127
+ const snapshot = candidate.usageSnapshot;
128
+ const capturedAt = snapshot?.capturedAt;
129
+ if (!capturedAt || !snapshot?.windows.length)
120
130
  return false;
121
131
  return nowMs - capturedAt.getTime() <= USAGE_DECISION_MAX_AGE_MS;
122
132
  }