@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
@@ -1,12 +1,15 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
1
3
  import chalk from 'chalk';
2
- import { password } from '@inquirer/prompts';
4
+ import { password, select } from '@inquirer/prompts';
5
+ import { resolveClaudeSetupToken } from '../lib/claude-account-token.js';
3
6
  import { setHelpSections } from '../lib/help.js';
4
7
  import { readMeta, updateMeta } from '../lib/state.js';
5
8
  import { ALL_AGENT_IDS, getAccountInfo, resolveAgentName } from '../lib/agents.js';
6
- import { getVersionHomePath, listInstalledVersions } from '../lib/installations/versions.js';
9
+ import { getGlobalDefault, getVersionHomePath, listInstalledVersions } from '../lib/installations/versions.js';
7
10
  import { assertNativeAccountNameable, nativeAccountCapability, nativeIdentityKey } from '../lib/account-capabilities.js';
8
11
  import { collectRunCandidates } from '../lib/accounting/rotate.js';
9
- import { isInteractiveTerminal } from './utils.js';
12
+ import { isInteractiveTerminal, isPromptCancelled, requireInteractiveSelection } from './utils.js';
10
13
  import { buildSwitchAccountChoices, formatAccountLimits, pickSwitchAccount } from './run-account-picker.js';
11
14
  import { profileExists, readProfile } from '../lib/profiles.js';
12
15
  import { pushBundleToHost } from '../lib/secrets/push.js';
@@ -16,7 +19,10 @@ import { runDevicesAccounts } from './ssh.js';
16
19
  import { discoverNativeAccounts } from '../lib/account-catalog.js';
17
20
  import { readAndResolveBundleEnv } from '../lib/secrets/bundles.js';
18
21
  import { getAccountProvider, listAccountProviders } from '../lib/account-provider-registry.js';
19
- import { accountBindings, addAccount, addNativeAccount, bindAccount, findAccount, findUnifiedAccount, inspectAccount, listNativeAccounts, readAccountRegistry, removeAccount, renameAccount, setAccountSecret, unbindAccount } from '../lib/account-registry.js';
22
+ import { accountBindings, addAccount, addNativeAccount, bindAccount, findAccount, findUnifiedAccount, inspectAccount, labelNativeAccount, listNativeAccounts, readAccountRegistry, removeAccount, renameAccount, setAccountSecret, unbindAccount } from '../lib/account-registry.js';
23
+ function cleanCommandError(command, err) {
24
+ command.error(err instanceof Error ? err.message : String(err), { exitCode: 1, code: 'accounts.error' });
25
+ }
20
26
  function parseInstallation(raw) {
21
27
  const at = raw.lastIndexOf('@');
22
28
  if (at < 1 || at === raw.length - 1)
@@ -63,6 +69,33 @@ export function classifyAttachTarget(target) {
63
69
  return { kind: 'device-agent', agent };
64
70
  throw new Error(`Unknown attach target '${target}'. Expected an installed <agent>@<version>, a harness id, or an existing custom harness profile.`);
65
71
  }
72
+ /**
73
+ * Persist the attached setup-token to a per-version `.oauth_token` file so an
74
+ * INTERACTIVE Claude launch on a keychain-less Linux worker can authenticate from it.
75
+ * Headless runs inject the token via `buildExecEnv`, but an interactive launch defers to
76
+ * the per-version login (`claudeAdapter.applyExecConfigEnv`), and the shim's Linux fallback
77
+ * (`claudeAdapter.shimConfigEnvBash`) reads exactly this file. Without writing it, a
78
+ * freshly-attached setup-token was invisible to interactive runs on a worker. macOS keeps
79
+ * the credential in the keychain, so `resolveClaudeSetupToken` returns null there and this
80
+ * is a no-op off Linux.
81
+ */
82
+ export function writeClaudeInteractiveOauthToken(target, targetAgent) {
83
+ if (process.platform !== 'linux' || targetAgent !== 'claude' || target.kind !== 'installation')
84
+ return;
85
+ const versionHome = getVersionHomePath('claude', target.version);
86
+ const tokenPath = path.join(versionHome, '.claude', '.oauth_token');
87
+ const token = resolveClaudeSetupToken(versionHome);
88
+ // A re-point (attach B over A, or a detach) can leave no setup-token resolving for
89
+ // this version — B's may not be minted yet. A leftover file from the previous binding
90
+ // would silently authenticate interactive runs as the OLD account (the shim's Linux
91
+ // fallback reads it), so clear it rather than leave it stale.
92
+ if (!token) {
93
+ fs.rmSync(tokenPath, { force: true });
94
+ return;
95
+ }
96
+ fs.mkdirSync(path.dirname(tokenPath), { recursive: true });
97
+ fs.writeFileSync(tokenPath, token, { mode: 0o600 });
98
+ }
66
99
  export function parseBundleKey(raw) {
67
100
  const colon = raw.indexOf(':');
68
101
  if (colon < 1 || colon === raw.length - 1)
@@ -90,18 +123,59 @@ async function printAccounts(json, fleet = false) {
90
123
  console.log(JSON.stringify([...records.map(account => publicAccount(inspectAccount(account.name))), ...native], null, 2));
91
124
  return;
92
125
  }
93
- console.log(chalk.bold('Provider account bundles\n'));
94
- if (!records.length)
95
- console.log(chalk.gray(" None. Add one with 'agents accounts add <name> --provider <provider> --auth <type>'."));
96
- for (const account of records) {
97
- const present = inspectAccount(account.name).secretPresent ? chalk.green('ready') : chalk.red('missing on this device');
98
- console.log(` ${chalk.cyan(account.name)} ${account.provider} ${account.auth} ${present}`);
126
+ console.log(renderAccountList(records, native));
127
+ }
128
+ /** Pure text renderer for `agents accounts list` grouped, column-aligned, labels first-class. */
129
+ export function renderAccountList(records, native) {
130
+ const out = [];
131
+ // Native logins, grouped by harness — the selection surface for `<harness>#<label>`.
132
+ out.push(chalk.bold('Native logins') + chalk.gray(' ') + chalk.gray('run <harness>#<label>'));
133
+ if (!native.length) {
134
+ out.push(chalk.gray(' No signed-in native accounts found.'));
99
135
  }
100
- console.log(chalk.bold('\nNative harness logins\n'));
101
- if (!native.length)
102
- console.log(chalk.gray(' No signed-in native accounts found.'));
103
- for (const account of native)
104
- console.log(` ${account.name ? `${chalk.cyan(account.name)} · ` : ''}${account.display} ${account.agent} ${account.versions.join(', ')}`);
136
+ else {
137
+ const byHarness = new Map();
138
+ for (const acct of native) {
139
+ const list = byHarness.get(acct.agent) ?? [];
140
+ list.push(acct);
141
+ byHarness.set(acct.agent, list);
142
+ }
143
+ const harnessW = Math.max(6, ...[...byHarness.keys()].map(h => h.length));
144
+ const labelW = Math.max(6, ...native.map(a => (a.name ?? '—').length));
145
+ const idW = Math.max(8, ...native.map(a => a.display.length));
146
+ let sawDefault = false;
147
+ for (const [harness, list] of [...byHarness.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
148
+ const defaultVersion = getGlobalDefault(harness);
149
+ list.forEach((acct, i) => {
150
+ const harnessCell = chalk.gray((i === 0 ? harness : '').padEnd(harnessW));
151
+ const rawLabel = acct.name ?? '—';
152
+ const labelCell = acct.name ? chalk.cyan(rawLabel.padEnd(labelW)) : chalk.gray(rawLabel.padEnd(labelW));
153
+ const isDefault = defaultVersion ? acct.versions.includes(defaultVersion) : false;
154
+ if (isDefault)
155
+ sawDefault = true;
156
+ const marker = isDefault ? chalk.green('*') : ' ';
157
+ out.push(` ${harnessCell} ${labelCell}${marker} ${acct.display.padEnd(idW)} ${chalk.gray(acct.versions.join(', '))}`);
158
+ });
159
+ }
160
+ if (sawDefault)
161
+ out.push(chalk.gray('\n * default account for that harness (used when you pass neither @version nor #label)'));
162
+ }
163
+ // Provider bundles — the durable API-key/setup-token accounts, selected with `--account <name>`.
164
+ out.push('');
165
+ out.push(chalk.bold('Provider bundles') + chalk.gray(' run <harness> --account <name>'));
166
+ if (!records.length) {
167
+ out.push(chalk.gray(" None. Add one with 'agents accounts add <name> --provider <provider> --auth <type>'."));
168
+ }
169
+ else {
170
+ const nameW = Math.max(4, ...records.map(a => a.name.length));
171
+ const provW = Math.max(8, ...records.map(a => a.provider.length));
172
+ const authW = Math.max(10, ...records.map(a => a.auth.length));
173
+ for (const account of records) {
174
+ const present = inspectAccount(account.name).secretPresent ? chalk.green('ready') : chalk.red('missing on this device');
175
+ out.push(` ${chalk.cyan(account.name.padEnd(nameW))} ${account.provider.padEnd(provW)} ${account.auth.padEnd(authW)} ${present}`);
176
+ }
177
+ }
178
+ return out.join('\n');
105
179
  }
106
180
  function parseAuth(raw) {
107
181
  if (raw === 'api-key' || raw === 'setup-token' || raw === 'bearer-token')
@@ -113,6 +187,107 @@ function parseHarness(agentRaw) {
113
187
  throw new Error(`Unknown agent '${agentRaw}'.`);
114
188
  return agentRaw;
115
189
  }
190
+ /**
191
+ * Fold signed-in run candidates into distinct identities — the unit a label
192
+ * binds to. The same account signed into several versions is ONE row, so a
193
+ * multi-version single-account harness never demands a selector. Candidates
194
+ * with no stable identity (no accountKey, no email) are dropped: there is
195
+ * nothing durable for a label to bind to. Default-version identity first.
196
+ */
197
+ export function groupLabelIdentities(candidates, defaultVersion) {
198
+ const byKey = new Map();
199
+ for (const candidate of candidates) {
200
+ const key = candidate.accountKey ?? candidate.email?.toLowerCase();
201
+ if (!key)
202
+ continue;
203
+ const row = byKey.get(key) ?? { identityKey: key, email: null, versions: [], isDefault: false };
204
+ row.email ??= candidate.email;
205
+ row.versions.push(candidate.version);
206
+ if (candidate.version === defaultVersion)
207
+ row.isDefault = true;
208
+ byKey.set(key, row);
209
+ }
210
+ return [...byKey.values()].sort((a, b) => a.isDefault !== b.isDefault ? (a.isDefault ? -1 : 1) : (a.email ?? a.identityKey).localeCompare(b.email ?? b.identityKey));
211
+ }
212
+ /**
213
+ * Resolve which signed-in login a bare-harness `accounts label` call means.
214
+ * `collect` is injectable (the `resolveRunVersion` pattern) so tests drive the
215
+ * real selection path against fixture candidates.
216
+ */
217
+ export async function resolveLabelIdentity(agent, accountSelector, collect = collectRunCandidates) {
218
+ const candidates = (await collect(agent)).filter(candidate => candidate.signedIn);
219
+ const identities = groupLabelIdentities(candidates, getGlobalDefault(agent));
220
+ if (identities.length === 0)
221
+ throw new Error(`No signed-in ${agent} account with a stable identity. Run the harness and complete its login first.`);
222
+ const needle = accountSelector?.toLowerCase();
223
+ if (needle) {
224
+ const match = identities.find(identity => identity.email?.toLowerCase() === needle || identity.identityKey.toLowerCase() === needle);
225
+ if (!match)
226
+ throw new Error(`Unknown ${agent} account '${accountSelector}'.`);
227
+ return { kind: 'selected', identity: match };
228
+ }
229
+ if (identities.length === 1)
230
+ return { kind: 'selected', identity: identities[0] };
231
+ return { kind: 'ambiguous', identities };
232
+ }
233
+ /**
234
+ * Body of `accounts label`, exported with the injectable candidate collector
235
+ * so tests exercise everything but the TTY prompt itself.
236
+ */
237
+ export async function runAccountsLabel(source, label, opts, collect = collectRunCandidates) {
238
+ if (source.includes('@')) {
239
+ // <harness>@<version> pins the login by where it is signed in, so a
240
+ // second selector can only contradict it.
241
+ if (opts.account)
242
+ throw new Error(`'${source}' already selects one login; drop --account.`);
243
+ const identity = await nativeIdentityFromSource(source);
244
+ const account = labelNativeAccount(identity.agent, identity.identityKey, identity.identityLabel, label, identity.scope);
245
+ console.log(chalk.green(`Labeled ${identity.agent} account ${identity.identityLabel ?? identity.identityKey} as '${account.name}'.`));
246
+ return;
247
+ }
248
+ const agent = parseHarness(source);
249
+ assertNativeAccountNameable(agent);
250
+ const selection = await resolveLabelIdentity(agent, opts.account, collect);
251
+ let selected;
252
+ if (selection.kind === 'selected') {
253
+ selected = selection.identity;
254
+ }
255
+ else {
256
+ if (!isInteractiveTerminal()) {
257
+ requireInteractiveSelection(`Selecting the ${agent} login to label`, [
258
+ `agents accounts label ${agent} ${label ?? '<label>'} --account <email|id>`,
259
+ `agents accounts label ${agent}@<version> ${label ?? '<label>'}`,
260
+ ]);
261
+ }
262
+ const picked = await pickLabelIdentity(agent, selection.identities);
263
+ if (!picked)
264
+ return;
265
+ selected = picked;
266
+ }
267
+ const account = labelNativeAccount(agent, selected.identityKey, selected.email ?? undefined, label, nativeAccountCapability(agent).scope);
268
+ console.log(chalk.green(`Labeled ${agent} account ${selected.email ?? selected.identityKey} as '${account.name}'.`));
269
+ }
270
+ /** Prompt for the login a label should bind to. A cancelled picker writes nothing. */
271
+ async function pickLabelIdentity(agent, identities) {
272
+ const idW = Math.max(0, ...identities.map(identity => (identity.email ?? identity.identityKey).length));
273
+ const choices = identities.map(identity => ({
274
+ name: [
275
+ (identity.email ?? identity.identityKey).padEnd(idW),
276
+ identity.isDefault ? chalk.green('default') : ' ',
277
+ chalk.gray(identity.versions.join(', ')),
278
+ ].join(' '),
279
+ value: identity.identityKey,
280
+ }));
281
+ try {
282
+ const key = await select({ message: `Select the ${agent} login to label:`, choices, loop: false });
283
+ return identities.find(identity => identity.identityKey === key) ?? null;
284
+ }
285
+ catch (err) {
286
+ if (isPromptCancelled(err))
287
+ return null;
288
+ throw err;
289
+ }
290
+ }
116
291
  function providerAuthenticatesHarness(provider, auth, agent) {
117
292
  try {
118
293
  getAccountProvider(provider).envFor(agent, auth);
@@ -273,12 +448,35 @@ export function registerAccountsCommand(program) {
273
448
  });
274
449
  accounts.command('name <source> <name>')
275
450
  .description('Name a signed-in native installation without copying its OAuth credentials')
276
- .action(async (source, name) => {
277
- const identity = await nativeIdentityFromSource(source);
278
- const account = addNativeAccount(name, identity.agent, identity.identityKey, identity.identityLabel, identity.scope);
279
- console.log(chalk.green(`Named ${source} as ${account.name}.`));
280
- if (account.scope === 'device')
281
- console.log(chalk.gray(`${identity.agent} authentication is device-scoped; attach '${account.name}' to '${identity.agent}', not an individual version.`));
451
+ .action(async (source, name, _o, command) => {
452
+ try {
453
+ const identity = await nativeIdentityFromSource(source);
454
+ const account = addNativeAccount(name, identity.agent, identity.identityKey, identity.identityLabel, identity.scope);
455
+ console.log(chalk.green(`Named ${source} as ${account.name}.`));
456
+ if (account.scope === 'device')
457
+ console.log(chalk.gray(`${identity.agent} authentication is device-scoped; attach '${account.name}' to '${identity.agent}', not an individual version.`));
458
+ }
459
+ catch (err) {
460
+ cleanCommandError(command, err);
461
+ }
462
+ });
463
+ const labelCmd = accounts.command('label <source> [label]')
464
+ .description('Label a native login by harness or <harness>@<version>; the label binds to the account identity, not the version')
465
+ .option('--account <email-or-id>', 'Native identity to label when the harness has multiple logins')
466
+ .action(async (source, label, o, command) => {
467
+ try {
468
+ await runAccountsLabel(source, label, o);
469
+ }
470
+ catch (err) {
471
+ cleanCommandError(command, err);
472
+ }
473
+ });
474
+ setHelpSections(labelCmd, {
475
+ examples: `agents accounts label codex work
476
+ agents accounts label codex@0.146.0 personal
477
+ agents accounts label codex work --account you@example.com
478
+ agents run codex#work`,
479
+ notes: 'The label binds to the signed-in account identity, not the version — codex#work keeps selecting that account after it moves to a newer install. One signed-in login needs no selector; with several, an interactive terminal opens a picker, while scripts pass --account or <harness>@<version>.',
282
480
  });
283
481
  accounts.command('attach <account> <target>')
284
482
  .description('Attach a named account to a native installation or custom harness')
@@ -317,27 +515,46 @@ export function registerAccountsCommand(program) {
317
515
  getAccountProvider(account.provider).envFor(targetAgent, account.auth);
318
516
  }
319
517
  bindAccount(name, target);
518
+ writeClaudeInteractiveOauthToken(t, targetAgent);
320
519
  console.log(chalk.green(`Attached ${account.name} to ${target}.`));
321
520
  });
322
521
  accounts.command('detach <account> <target>')
323
522
  .description('Remove one account attachment')
324
523
  .action((name, target) => {
325
524
  unbindAccount(name, target);
525
+ // With the binding gone, no setup-token resolves for this version home, so this
526
+ // clears any .oauth_token the attach left behind (else interactive runs would keep
527
+ // authenticating as the just-detached account).
528
+ try {
529
+ const t = classifyAttachTarget(target);
530
+ writeClaudeInteractiveOauthToken(t, t.kind === 'profile' ? t.profile.host.agent : t.agent);
531
+ }
532
+ catch { /* an unresolvable target has no version home to clean */ }
326
533
  console.log(chalk.green(`Detached ${name} from ${target}.`));
327
534
  });
328
535
  accounts.command('rename <old> <new>').description('Rename an account without changing its stable id').action((oldName, newName) => renameAccount(oldName, newName));
329
536
  accounts.command('remove <name>').description('Remove an account and its device-local credential').action((name) => removeAccount(name));
330
537
  accounts.command('set-default <agent> <name>')
331
538
  .description('Use this account for a harness when --account is omitted')
332
- .action((agentRaw, name) => {
333
- const { agent, account } = setDefaultAccount(agentRaw, name);
334
- console.log(chalk.green(`${agent} now uses account '${account.name}' unless --account overrides it.`));
539
+ .action((agentRaw, name, _o, command) => {
540
+ try {
541
+ const { agent, account } = setDefaultAccount(agentRaw, name);
542
+ console.log(chalk.green(`${agent} now uses account '${account.name}' unless --account overrides it.`));
543
+ }
544
+ catch (err) {
545
+ cleanCommandError(command, err);
546
+ }
335
547
  });
336
548
  const switchCmd = accounts.command('switch <harness> [account]')
337
549
  .description('Pick the default account for a harness')
338
550
  .option('--json', 'Machine-readable account list or the resulting default')
339
551
  .action(async (harness, account, o, command) => {
340
- await runAccountsSwitch(harness, account, { json: !!(o.json || command.optsWithGlobals().json) });
552
+ try {
553
+ await runAccountsSwitch(harness, account, { json: !!(o.json || command.optsWithGlobals().json) });
554
+ }
555
+ catch (err) {
556
+ cleanCommandError(command, err);
557
+ }
341
558
  });
342
559
  accounts.command('clear-default <agent>')
343
560
  .description('Return a harness to native login or balanced account selection')
@@ -425,6 +642,7 @@ agents run claude`,
425
642
  agents accounts add openrouter-work --provider openrouter --auth api-key --from-secrets openrouter.ai:OPENROUTER_API_KEY
426
643
  agents accounts set-key work
427
644
  agents accounts name claude@2.1.220 work
645
+ agents accounts label codex@0.146.0 personal
428
646
  agents accounts attach work claude@2.1.225
429
647
  agents accounts view work --json
430
648
  agents accounts switch claude
@@ -20,18 +20,19 @@ import { registerArtifactsSetupCommand } from './artifacts-setup.js';
20
20
  export function registerArtifactsCommands(program) {
21
21
  const artifactsCmd = program
22
22
  .command('artifacts')
23
- .description('Publish agent-made artifacts (plans, reports, visuals) to your own Cloudflare R2 and get a shareable link (~$0).');
23
+ .description('Publish agent-made artifacts (plans, reports, visuals) to a shareable link managed if signed in, otherwise your Cloudflare R2.');
24
24
  registerArtifactsSetupCommand(artifactsCmd);
25
25
  registerShareCommands(artifactsCmd);
26
26
  setHelpSections(artifactsCmd, {
27
27
  examples: `
28
- # One-time: provision your own endpoint (or join a teammate's)
28
+ # Signed in? Just publish no Cloudflare setup
29
+ agents auth login
30
+ agents artifacts share ./out/plan.html
31
+
32
+ # Or provision your own Cloudflare endpoint (or join a teammate's)
29
33
  agents artifacts setup
30
34
  agents artifacts share join https://share.agents-cli.sh
31
35
 
32
- # Publish — auto OG cover, default 30d expiry, shareable link
33
- agents artifacts share ./out/plan.html
34
-
35
36
  # What's live in your namespace, and taking one down
36
37
  agents artifacts share list
37
38
  agents artifacts unshare my-plan-a1b2
@@ -50,7 +51,7 @@ export function registerArtifactsCommands(program) {
50
51
  agents artifacts unshare <targets...> is the nested alias of
51
52
  agents artifacts share delete. Top-level \`agents unshare\` is gone.
52
53
 
53
- Full reference: apps/cli/docs/share.md.
54
+ Architecture: cli/docs/observability.md.
54
55
  `,
55
56
  });
56
57
  registerUnshareCommand(artifactsCmd);
@@ -50,7 +50,7 @@ export async function attachAction(id) {
50
50
  // process on the owning machine.
51
51
  const peer = sessionRecoveryPeer(meta);
52
52
  if (peer) {
53
- const routed = await runOnPeer(attachRecoveryArgs(meta), peer, { tty: true });
53
+ const routed = await runOnPeer(attachRecoveryArgs(meta), peer, { tty: true, sessionId: meta.id });
54
54
  if (routed === 'no-target') {
55
55
  console.error(chalk.red(`Cannot attach ${meta.shortId}: origin device ${peer} is not a registered reachable peer.`));
56
56
  process.exitCode = 1;
@@ -3,9 +3,10 @@ import { PhoenixApiError, clearSession, createSpace, createSpaceInvite, fetchWho
3
3
  import { setHelpSections } from '../lib/help.js';
4
4
  import { runOrDie } from '../lib/format.js';
5
5
  /**
6
- * `agents auth` — sign in to Phoenix ID, the account layer behind teams and
7
- * plan tiers. Everything here goes through `lib/identity`; this file builds no
8
- * URLs and reads no credential files of its own.
6
+ * `agents auth` — sign in to Phoenix ID, the account layer behind team spaces.
7
+ * Signing in is optional: every local feature works with no account. Everything
8
+ * here goes through `lib/identity`; this file builds no URLs and reads no
9
+ * credential files of its own.
9
10
  */
10
11
  function sleep(ms) {
11
12
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -90,14 +91,15 @@ async function requireSpace(ref) {
90
91
  export function registerAuthCommand(program) {
91
92
  const auth = program
92
93
  .command('auth')
93
- .description('Sign in to Phoenix ID — the account layer behind spaces and plan tiers');
94
+ .description('Sign in to Phoenix ID — the account layer behind team spaces');
94
95
  setHelpSections(auth, {
95
96
  examples: `agents auth login # device-code sign-in via your browser
96
97
  agents auth whoami # who this machine is signed in as
97
98
  agents auth space create "Design Team" # start a space
98
99
  agents auth space invite ada@example.com # add a teammate
99
100
  agents auth logout # clear this machine only`,
100
- notes: `Sign-in is Google-only and opens a Phoenix-branded page; the CLI never sees a password.
101
+ notes: `Signing in is optional every local feature works with no account; team spaces are what it unlocks.
102
+ Sign-in is Google-only and opens a Phoenix-branded page; the CLI never sees a password.
101
103
  The session lives in this machine's agents state dir, so logging out here signs out nothing else.
102
104
  Point at a different backend with PHOENIX_ID_BASE (defaults to the production service).`,
103
105
  });
@@ -10,12 +10,13 @@
10
10
  * second renderer. Interactive routing and the browse loop live in
11
11
  * `sessions-picker-factory.ts` (shared with the computer twin).
12
12
  */
13
+ import { showFile } from '../lib/open-url.js';
13
14
  import chalk from 'chalk';
14
15
  import { itemPicker } from '../lib/picker.js';
15
16
  import { isPromptCancelled } from './utils.js';
16
17
  import { buildPreview } from './sessions-picker.js';
17
18
  import { createSessionsPickerCommand } from './sessions-picker-factory.js';
18
- import { runBrowserSessions, buildBrowserSessionRows, matchesBrowserSessionRow, openArtifact, formatBytes, } from '../lib/browser/sessions-list.js';
19
+ import { runBrowserSessions, buildBrowserSessionRows, matchesBrowserSessionRow, formatBytes, } from '../lib/browser/sessions-list.js';
19
20
  import { formatRelativeTime } from '../lib/session/relative-time.js';
20
21
  const KIND_LABEL = {
21
22
  screenshot: 'shots',
@@ -89,11 +90,12 @@ function formatArtifactLabel(a) {
89
90
  const age = formatRelativeTime(new Date(a.mtimeMs).toISOString());
90
91
  return `${age.padEnd(11)} ${a.name.padEnd(34)} ${formatBytes(a.bytes).padStart(8)}`;
91
92
  }
92
- /** Open one artifact in the OS default app, printing its path (matches the
93
- * non-interactive `--open` behavior) and any open failure. */
94
- function openAndReport(a) {
93
+ /** Open one artifact, printing its path (matches the non-interactive `--open`
94
+ * behavior) and any open failure. Routes through the same viewer seam as
95
+ * `--open`, so the interactive and non-interactive halves cannot diverge. */
96
+ async function openAndReport(a) {
95
97
  console.log(a.path);
96
- if (!openArtifact(a.path))
98
+ if ((await showFile(a.path)).via === 'none')
97
99
  console.error(`Could not open ${a.path}`);
98
100
  }
99
101
  /** Second-level picker over one row's captures, newest first. Enter opens the
@@ -136,12 +138,12 @@ const browserSessionsPicker = createSessionsPickerCommand({
136
138
  return;
137
139
  }
138
140
  if (row.artifacts.length === 1) {
139
- openAndReport(row.artifacts[0]);
141
+ await openAndReport(row.artifacts[0]);
140
142
  return;
141
143
  }
142
144
  const artifact = await pickArtifact(row);
143
145
  if (artifact)
144
- openAndReport(artifact);
146
+ await openAndReport(artifact);
145
147
  },
146
148
  });
147
149
  /** True when the interactive picker should open instead of the printed table: