@phnx-labs/agents-cli 1.22.116 → 1.22.118

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 (254) hide show
  1. package/CHANGELOG.md +247 -0
  2. package/dist/bootstrap.js +4 -11
  3. package/dist/commands/browser.d.ts +52 -70
  4. package/dist/commands/browser.js +192 -3562
  5. package/dist/commands/daemon.d.ts +39 -2
  6. package/dist/commands/daemon.js +72 -73
  7. package/dist/commands/events.js +1 -1
  8. package/dist/commands/exec.d.ts +18 -0
  9. package/dist/commands/exec.js +104 -12
  10. package/dist/commands/repo.js +13 -49
  11. package/dist/commands/route.js +5 -5
  12. package/dist/commands/routines.js +39 -12
  13. package/dist/commands/sessions-render.d.ts +1 -1
  14. package/dist/commands/sessions-render.js +0 -1
  15. package/dist/commands/sessions.test-fixture.d.ts +0 -1
  16. package/dist/commands/sessions.test-fixture.js +0 -37
  17. package/dist/commands/setup-browser.d.ts +17 -12
  18. package/dist/commands/setup-browser.js +52 -87
  19. package/dist/commands/setup-preferences.d.ts +0 -17
  20. package/dist/commands/setup-preferences.js +18 -49
  21. package/dist/commands/setup-term.d.ts +4 -3
  22. package/dist/commands/setup-term.js +5 -4
  23. package/dist/commands/setup.js +11 -11
  24. package/dist/commands/ssh.js +10 -1
  25. package/dist/commands/sync.js +6 -44
  26. package/dist/commands/teams.js +0 -1
  27. package/dist/commands/update.d.ts +19 -0
  28. package/dist/commands/update.js +34 -6
  29. package/dist/commands/view.d.ts +12 -1
  30. package/dist/commands/view.js +47 -2
  31. package/dist/index.js +107 -26
  32. package/dist/lib/account-capabilities.js +0 -2
  33. package/dist/lib/account-catalog.d.ts +38 -0
  34. package/dist/lib/account-catalog.js +110 -26
  35. package/dist/lib/account-provider-registry.js +1 -1
  36. package/dist/lib/account-registry.d.ts +1 -1
  37. package/dist/lib/account-registry.js +36 -2
  38. package/dist/lib/accounting/rotate.js +22 -8
  39. package/dist/lib/accounting/usage-ingest.d.ts +6 -1
  40. package/dist/lib/accounting/usage-ingest.js +115 -45
  41. package/dist/lib/accounting/usage-sync.d.ts +134 -31
  42. package/dist/lib/accounting/usage-sync.js +227 -66
  43. package/dist/lib/accounting/usage.d.ts +16 -3
  44. package/dist/lib/accounting/usage.js +26 -8
  45. package/dist/lib/accounts/slots.d.ts +8 -0
  46. package/dist/lib/accounts/slots.js +17 -0
  47. package/dist/lib/add-dir.js +0 -1
  48. package/dist/lib/agent-cli-commands.js +0 -1
  49. package/dist/lib/agent-spec/agents.d.ts +6 -7
  50. package/dist/lib/agent-spec/agents.js +6 -60
  51. package/dist/lib/auth-health.d.ts +69 -1
  52. package/dist/lib/auth-health.js +133 -4
  53. package/dist/lib/browser/context.d.ts +82 -0
  54. package/dist/lib/browser/context.js +88 -0
  55. package/dist/lib/browser/paths.d.ts +25 -0
  56. package/dist/lib/browser/paths.js +51 -0
  57. package/dist/lib/browser/record.d.ts +38 -0
  58. package/dist/lib/browser/record.js +57 -0
  59. package/dist/lib/browser/sessions-list.js +1 -2
  60. package/dist/lib/browser-client.d.ts +158 -0
  61. package/dist/lib/browser-client.js +234 -0
  62. package/dist/lib/channels/owner-sink.d.ts +1 -18
  63. package/dist/lib/channels/owner-sink.js +29 -82
  64. package/dist/lib/channels/providers/rush.d.ts +7 -7
  65. package/dist/lib/channels/providers/rush.js +127 -57
  66. package/dist/lib/claude-statusline.d.ts +1 -1
  67. package/dist/lib/claude-statusline.js +15 -3
  68. package/dist/lib/commands.js +1 -1
  69. package/dist/lib/crabbox/lease.js +1 -1
  70. package/dist/lib/crabbox/runtimes.js +1 -3
  71. package/dist/lib/daemon/account-state-daemon-service.js +1 -0
  72. package/dist/lib/daemon/auth-sync-service.d.ts +21 -18
  73. package/dist/lib/daemon/auth-sync-service.js +37 -43
  74. package/dist/lib/daemon/daemon.d.ts +25 -14
  75. package/dist/lib/daemon/daemon.js +92 -154
  76. package/dist/lib/daemon/runner.d.ts +2 -2
  77. package/dist/lib/daemon/runner.js +32 -9
  78. package/dist/lib/daemon/self-update-service.d.ts +35 -34
  79. package/dist/lib/daemon/self-update-service.js +40 -36
  80. package/dist/lib/daemon/service.d.ts +12 -8
  81. package/dist/lib/daemon/supervisor.d.ts +65 -54
  82. package/dist/lib/daemon/supervisor.js +136 -171
  83. package/dist/lib/daemon/testdata/process-view-start.js +5 -2
  84. package/dist/lib/daemon/usage-sync-service.d.ts +21 -30
  85. package/dist/lib/daemon/usage-sync-service.js +46 -78
  86. package/dist/lib/daemon-health.d.ts +22 -5
  87. package/dist/lib/daemon-health.js +47 -3
  88. package/dist/lib/daemon-services.d.ts +1 -1
  89. package/dist/lib/daemon-services.js +0 -10
  90. package/dist/lib/daemon-ticks.js +4 -2
  91. package/dist/lib/devices/doctor-findings.js +8 -11
  92. package/dist/lib/devices/harness-inventory.js +14 -5
  93. package/dist/lib/exec-account-home.js +5 -0
  94. package/dist/lib/exec.js +0 -11
  95. package/dist/lib/feed/events.js +0 -1
  96. package/dist/lib/feed/tool-activity.js +5 -2
  97. package/dist/lib/fleet-shared-state.d.ts +45 -0
  98. package/dist/lib/fleet-shared-state.js +78 -9
  99. package/dist/lib/fs-atomic.js +25 -0
  100. package/dist/lib/harness-auth-capabilities.js +0 -1
  101. package/dist/lib/helper-versions.js +1 -1
  102. package/dist/lib/hooks/cache.d.ts +7 -0
  103. package/dist/lib/hooks/cache.js +27 -2
  104. package/dist/lib/hooks/install.js +7 -1
  105. package/dist/lib/hosts/ready.d.ts +38 -8
  106. package/dist/lib/hosts/ready.js +62 -14
  107. package/dist/lib/hosts/remote-cmd.js +1 -0
  108. package/dist/lib/installations/active-check.d.ts +27 -0
  109. package/dist/lib/installations/active-check.js +61 -0
  110. package/dist/lib/installations/migrate.js +6 -3
  111. package/dist/lib/installations/shims.js +9 -7
  112. package/dist/lib/installations/update.js +21 -15
  113. package/dist/lib/installations/versions.js +12 -1
  114. package/dist/lib/manifest.js +1 -1
  115. package/dist/lib/models.js +0 -2
  116. package/dist/lib/open-url.js +28 -54
  117. package/dist/lib/placement.d.ts +2 -0
  118. package/dist/lib/placement.js +5 -0
  119. package/dist/lib/profiles.js +0 -2
  120. package/dist/lib/refresh.js +1 -1
  121. package/dist/lib/routine-readiness.d.ts +35 -0
  122. package/dist/lib/routine-readiness.js +74 -16
  123. package/dist/lib/rules/compile.js +6 -0
  124. package/dist/lib/sandbox.js +0 -2
  125. package/dist/lib/secrets-policy.d.ts +89 -11
  126. package/dist/lib/secrets-policy.js +175 -28
  127. package/dist/lib/session/db.d.ts +1 -1
  128. package/dist/lib/session/db.js +65 -18
  129. package/dist/lib/session/discover.js +1 -283
  130. package/dist/lib/session/mirror.d.ts +4 -3
  131. package/dist/lib/session/mirror.js +4 -3
  132. package/dist/lib/session/parse.d.ts +0 -2
  133. package/dist/lib/session/parse.js +1 -138
  134. package/dist/lib/session/throughput.d.ts +1 -3
  135. package/dist/lib/session/throughput.js +0 -20
  136. package/dist/lib/session/types.d.ts +2 -2
  137. package/dist/lib/session/types.js +2 -2
  138. package/dist/lib/sessions-client.d.ts +60 -1
  139. package/dist/lib/sessions-client.js +227 -4
  140. package/dist/lib/setup-tool-status.js +8 -12
  141. package/dist/lib/signin-badge.d.ts +3 -2
  142. package/dist/lib/signin-badge.js +6 -3
  143. package/dist/lib/smart-launch.js +4 -1
  144. package/dist/lib/staleness/detectors/commands.d.ts +1 -1
  145. package/dist/lib/staleness/detectors/hooks.d.ts +1 -1
  146. package/dist/lib/staleness/detectors/mcp.d.ts +1 -1
  147. package/dist/lib/staleness/detectors/permissions.d.ts +1 -1
  148. package/dist/lib/staleness/detectors/plugins.d.ts +1 -1
  149. package/dist/lib/staleness/detectors/rules.d.ts +1 -1
  150. package/dist/lib/staleness/detectors/skills.d.ts +1 -1
  151. package/dist/lib/staleness/detectors/subagents.d.ts +1 -1
  152. package/dist/lib/staleness/detectors/workflows.d.ts +1 -1
  153. package/dist/lib/staleness/writers/commands.d.ts +1 -1
  154. package/dist/lib/staleness/writers/hooks.d.ts +1 -1
  155. package/dist/lib/staleness/writers/mcp.d.ts +1 -1
  156. package/dist/lib/staleness/writers/permissions.d.ts +1 -1
  157. package/dist/lib/staleness/writers/plugins.d.ts +1 -1
  158. package/dist/lib/staleness/writers/rules.d.ts +1 -1
  159. package/dist/lib/staleness/writers/skills.d.ts +1 -1
  160. package/dist/lib/staleness/writers/sources.js +5 -0
  161. package/dist/lib/staleness/writers/subagents.d.ts +1 -1
  162. package/dist/lib/staleness/writers/workflows.d.ts +1 -1
  163. package/dist/lib/state.d.ts +6 -4
  164. package/dist/lib/state.js +7 -17
  165. package/dist/lib/teams/parsers.d.ts +1 -1
  166. package/dist/lib/teams/parsers.js +1 -136
  167. package/dist/lib/teams/placement-probe.js +50 -15
  168. package/dist/lib/teams/scheduler.d.ts +7 -0
  169. package/dist/lib/types.d.ts +10 -1
  170. package/dist/lib/types.js +1 -1
  171. package/dist/lib/view-types.d.ts +32 -0
  172. package/dist/lib/watchdog/read.js +0 -1
  173. package/dist/lib/watchdog/rotate.js +1 -2
  174. package/dist/lib/watchdog/runner.js +2 -2
  175. package/dist/lib/watchdog/watchdog.d.ts +1 -1
  176. package/dist/lib/watchdog/watchdogTail.js +0 -31
  177. package/package.json +1 -1
  178. package/scripts/postinstall.js +8 -8
  179. package/dist/browser.d.ts +0 -2
  180. package/dist/browser.js +0 -17
  181. package/dist/commands/browser-picker.d.ts +0 -18
  182. package/dist/commands/browser-picker.js +0 -97
  183. package/dist/lib/browser/arc-discovery.d.ts +0 -52
  184. package/dist/lib/browser/arc-discovery.js +0 -197
  185. package/dist/lib/browser/arc-dom.d.ts +0 -14
  186. package/dist/lib/browser/arc-dom.js +0 -121
  187. package/dist/lib/browser/caller-identity.d.ts +0 -40
  188. package/dist/lib/browser/caller-identity.js +0 -175
  189. package/dist/lib/browser/cdp.d.ts +0 -51
  190. package/dist/lib/browser/cdp.js +0 -254
  191. package/dist/lib/browser/chrome.d.ts +0 -141
  192. package/dist/lib/browser/chrome.js +0 -704
  193. package/dist/lib/browser/chromium-discovery.d.ts +0 -28
  194. package/dist/lib/browser/chromium-discovery.js +0 -96
  195. package/dist/lib/browser/devices.d.ts +0 -34
  196. package/dist/lib/browser/devices.js +0 -61
  197. package/dist/lib/browser/domain-skills.d.ts +0 -71
  198. package/dist/lib/browser/domain-skills.js +0 -195
  199. package/dist/lib/browser/drivers/arc.d.ts +0 -40
  200. package/dist/lib/browser/drivers/arc.js +0 -276
  201. package/dist/lib/browser/drivers/firefox.d.ts +0 -99
  202. package/dist/lib/browser/drivers/firefox.js +0 -377
  203. package/dist/lib/browser/drivers/local.d.ts +0 -55
  204. package/dist/lib/browser/drivers/local.js +0 -266
  205. package/dist/lib/browser/drivers/ssh.d.ts +0 -120
  206. package/dist/lib/browser/drivers/ssh.js +0 -467
  207. package/dist/lib/browser/editor.d.ts +0 -3
  208. package/dist/lib/browser/editor.js +0 -50
  209. package/dist/lib/browser/ffmpeg.d.ts +0 -12
  210. package/dist/lib/browser/ffmpeg.js +0 -184
  211. package/dist/lib/browser/firefox-discovery.d.ts +0 -69
  212. package/dist/lib/browser/firefox-discovery.js +0 -162
  213. package/dist/lib/browser/har.d.ts +0 -85
  214. package/dist/lib/browser/har.js +0 -77
  215. package/dist/lib/browser/hygiene.d.ts +0 -97
  216. package/dist/lib/browser/hygiene.js +0 -153
  217. package/dist/lib/browser/index.d.ts +0 -5
  218. package/dist/lib/browser/index.js +0 -5
  219. package/dist/lib/browser/input.d.ts +0 -7
  220. package/dist/lib/browser/input.js +0 -92
  221. package/dist/lib/browser/ipc.d.ts +0 -204
  222. package/dist/lib/browser/ipc.js +0 -1453
  223. package/dist/lib/browser/login-detection.d.ts +0 -87
  224. package/dist/lib/browser/login-detection.js +0 -274
  225. package/dist/lib/browser/profiles.d.ts +0 -422
  226. package/dist/lib/browser/profiles.js +0 -1157
  227. package/dist/lib/browser/refs.d.ts +0 -89
  228. package/dist/lib/browser/refs.js +0 -191
  229. package/dist/lib/browser/registry.d.ts +0 -70
  230. package/dist/lib/browser/registry.js +0 -252
  231. package/dist/lib/browser/remote-control.d.ts +0 -59
  232. package/dist/lib/browser/remote-control.js +0 -85
  233. package/dist/lib/browser/resolve-target.d.ts +0 -66
  234. package/dist/lib/browser/resolve-target.js +0 -264
  235. package/dist/lib/browser/runtime-state.d.ts +0 -291
  236. package/dist/lib/browser/runtime-state.js +0 -584
  237. package/dist/lib/browser/secret-ref.d.ts +0 -10
  238. package/dist/lib/browser/secret-ref.js +0 -14
  239. package/dist/lib/browser/service.d.ts +0 -812
  240. package/dist/lib/browser/service.js +0 -4693
  241. package/dist/lib/browser/stream.d.ts +0 -17
  242. package/dist/lib/browser/stream.js +0 -72
  243. package/dist/lib/browser/task-index.d.ts +0 -78
  244. package/dist/lib/browser/task-index.js +0 -199
  245. package/dist/lib/browser/types.d.ts +0 -647
  246. package/dist/lib/browser/types.js +0 -61
  247. package/dist/lib/browser/upload.d.ts +0 -29
  248. package/dist/lib/browser/upload.js +0 -298
  249. package/dist/lib/daemon/browser-ipc-service.d.ts +0 -20
  250. package/dist/lib/daemon/browser-ipc-service.js +0 -46
  251. package/dist/lib/daemon/browser-task-reap-service.d.ts +0 -14
  252. package/dist/lib/daemon/browser-task-reap-service.js +0 -26
  253. package/dist/lib/fleet-shared-repo-sync.d.ts +0 -55
  254. package/dist/lib/fleet-shared-repo-sync.js +0 -490
@@ -1,124 +1,89 @@
1
1
  /**
2
- * `agents setup browser` — interactive wizard to get `agents browser` working on
3
- * a fresh machine: detect an installed Chromium-family browser, create the
4
- * `default` profile pinned to it, optionally make it this machine's default, and
5
- * point the user at the one manual step we can't automate (first-run + sign-in).
2
+ * `agents setup browser` — get `agents browser` working on a fresh machine:
3
+ * install the standalone `browser` CLI (@phnx-labs/browser-cli), let it detect
4
+ * installed browsers and create machine-local profiles (`browser profiles seed`),
5
+ * then pick this machine's default (`browser use`).
6
6
  *
7
- * Idempotent: re-running shows the current default profile and offers to change
8
- * the pinned browser or re-point the device default.
7
+ * The engine owns profile declarations and browser detection now (PHNX-4101), so
8
+ * this wizard DELEGATES to it rather than crafting profiles itself. What stays
9
+ * agents-cli's is the onboarding flow and the readiness check over the shared
10
+ * `browser.profile` config key both CLIs read/write.
11
+ *
12
+ * Idempotent: `profiles seed` leaves existing profiles untouched, and `use`
13
+ * re-points the device default.
9
14
  */
10
15
  import { openSetupTerminal } from './setup-terminal.js';
11
16
  import chalk from 'chalk';
12
- import { setConfigValue } from '../lib/device-config.js';
13
- import { listInstalledBrowsers } from '../lib/browser/chrome.js';
14
- import { DEFAULT_BROWSER_PROFILE_NAME, getAutoDetectedProfile, createProfile, findFreeProfilePort, getConfiguredDefaultProfileName, } from '../lib/browser/profiles.js';
15
- import { DEFAULT_VIEWPORT } from '../lib/browser/devices.js';
17
+ import { getConfigValue } from '../lib/device-config.js';
16
18
  import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
17
- import { defaultBrowserChoice } from './setup-preferences.js';
18
19
  import { installSetupTool } from '../lib/setup-tool-install.js';
19
20
  import { refreshToolSetup } from '../lib/setup-tool-status.js';
20
- const INSTALL_HINT = 'Install one of: Google Chrome, Brave, Microsoft Edge, Chromium, Comet, or Arc, then re-run `agents setup browser`.\n' +
21
- '(This wizard picks a Chromium-family browser. An installed Firefox is not auto-picked here; its profiles are ' +
22
- 'discovered from profiles.ini and selected with `agents browser use firefox-<name>`. Safari is not supported.)';
21
+ import { browserInstalled, runBrowser } from '../lib/browser-client.js';
22
+ import { buildBrowserContext } from '../lib/browser/context.js';
23
+ /** The device-local default profile, as both agents-cli and browser-cli see it. */
24
+ function configuredDefaultProfile() {
25
+ return getConfigValue('browser.profile').value || undefined;
26
+ }
27
+ /** Run one standalone `browser` verb, inheriting the terminal, and return its exit code. */
28
+ async function runBrowserVerb(argv) {
29
+ const { exitCode } = await runBrowser({ argv, context: await buildBrowserContext() });
30
+ return exitCode;
31
+ }
23
32
  /**
24
- * Interactive browser setup. Returns true if a usable default profile exists
25
- * afterwards, false if none exists / the user backed out. Non-interactively it
26
- * only RECOGNIZES an existing profile — it never creates one (PHNX-3296), since
27
- * silently minting a logged-out `auto-chrome` on a headless box is the exact bug
28
- * that removed; a headless box gets its browser from the fleet hub. Never throws
29
- * on cancel — the `agents setup` hub relies on that.
33
+ * Interactive browser setup. Returns true if a default profile is configured
34
+ * afterwards, false if none is / the user backed out. Non-interactively it only
35
+ * RECOGNIZES an existing default — it never creates one (PHNX-3296), since
36
+ * silently minting a logged-out profile on a headless box is the exact bug that
37
+ * removed; a headless box gets its browser from the fleet hub. Never throws on
38
+ * cancel — the `agents setup` hub relies on that.
30
39
  */
31
40
  export async function runBrowserWizard() {
32
41
  if (!isInteractiveTerminal()) {
33
- // Non-interactive / headless: recognize an existing default, but NEVER mint
34
- // one. Silently auto-detecting an installed browser and creating an
35
- // `auto-chrome` here is exactly the bug PHNX-3296 removed — a headless box
36
- // gets its browser from the fleet hub (`browser.device`), or from an
37
- // interactive pick later.
38
- const existing = await getAutoDetectedProfile();
42
+ const existing = configuredDefaultProfile();
39
43
  if (existing) {
40
- console.log(chalk.dim(`Browser profile "${existing.name}" already exists.`));
44
+ console.log(chalk.dim(`Default browser profile "${existing}" already configured on this machine.`));
41
45
  return true;
42
46
  }
43
47
  console.log(chalk.dim('No default browser profile on this machine. Re-run `agents setup browser` in an ' +
44
48
  'interactive terminal to pick one, or use the fleet hub: agents config set browser.device <host>.'));
45
49
  return false;
46
50
  }
47
- const installed = listInstalledBrowsers();
48
- if (installed.length === 0) {
49
- console.error(chalk.red('No supported browser found on this machine.'));
50
- console.log(chalk.dim(INSTALL_HINT));
51
- return false;
52
- }
53
- const { confirm, select } = await import('@inquirer/prompts');
54
- // If a default profile already exists, this is a reconfigure.
55
- const existing = await getAutoDetectedProfile();
56
- if (existing) {
57
- console.log(chalk.dim(`Browser profile "${existing.name}" already exists (${existing.browser}${existing.binary ? ` · ${existing.binary}` : ''}).`));
58
- const change = await confirm({ message: 'Re-create it (e.g. to pin a different browser)?', default: false });
59
- if (!change) {
60
- await maybeSetDeviceDefault(existing.name, confirm);
61
- printOnboardingNextStep(existing.name);
62
- return true;
51
+ if (!browserInstalled()) {
52
+ console.log(chalk.dim('Installing the standalone Browser CLI (@phnx-labs/browser-cli)…'));
53
+ if (!(await installSetupTool('browser'))) {
54
+ console.error(chalk.red('Could not install @phnx-labs/browser-cli. Install it manually with `npm i -g @phnx-labs/browser-cli`, then re-run.'));
55
+ return false;
63
56
  }
64
- // Re-create: drop the old one so createProfile doesn't collide.
65
- const { deleteProfile } = await import('../lib/browser/profiles.js');
66
- await deleteProfile(existing.name);
67
57
  }
68
- // Pick which installed browser to pin (auto-select if only one). The picker
69
- // highlights the same browser auto-detect would win — choosing is an
70
- // override of the default, never a guess.
71
- let chosen = installed[0];
72
- if (installed.length > 1) {
73
- const value = await select({
74
- message: 'Which browser should the default profile use?',
75
- default: defaultBrowserChoice(installed) ?? undefined,
76
- choices: installed.map((b) => ({ name: `${b.browserType} ${chalk.dim(b.binary)}`, value: b.browserType })),
77
- });
78
- chosen = installed.find((b) => b.browserType === value) ?? installed[0];
58
+ // Let the engine detect installed browsers and create a machine-local profile
59
+ // for each (idempotent), then open its own picker to set this machine's default.
60
+ const seedCode = await runBrowserVerb(['profiles', 'seed']);
61
+ if (seedCode !== 0) {
62
+ console.error(chalk.red('`browser profiles seed` failed — see the output above.'));
63
+ return false;
79
64
  }
80
- const freePort = await findFreeProfilePort();
81
- const profile = {
82
- name: DEFAULT_BROWSER_PROFILE_NAME,
83
- description: `${chosen.browserType} profile (agents setup browser)`,
84
- browser: chosen.browserType,
85
- binary: chosen.binary,
86
- endpoints: [`cdp://127.0.0.1:${freePort}`],
87
- viewport: { width: DEFAULT_VIEWPORT.width, height: DEFAULT_VIEWPORT.height },
88
- };
89
- await createProfile(profile);
90
- console.log(chalk.green(`\nCreated browser profile "${profile.name}" → ${chosen.browserType} (CDP 127.0.0.1:${freePort}).`));
91
- await maybeSetDeviceDefault(profile.name, confirm);
92
- printOnboardingNextStep(profile.name);
93
- return true;
94
- }
95
- /** Offer to make `name` this machine's default browser profile (device-local). */
96
- async function maybeSetDeviceDefault(name, confirm) {
97
- const current = getConfiguredDefaultProfileName();
98
- if (current === name)
99
- return; // already the device default
100
- const set = await confirm({
101
- message: `Make "${name}" this machine's default browser profile?`,
102
- default: true,
103
- });
104
- if (set) {
105
- setConfigValue('browser.profile', name);
106
- console.log(chalk.dim(`Bare \`agents browser start\` will now use "${name}" on this machine.`));
65
+ await runBrowserVerb(['use']);
66
+ const chosen = configuredDefaultProfile();
67
+ if (!chosen) {
68
+ console.log(chalk.dim('No default profile chosen. Run `agents browser use <name>` later to set one.'));
69
+ return false;
107
70
  }
71
+ printOnboardingNextStep(chosen);
72
+ return true;
108
73
  }
109
- /** The one step we can't automate: Chrome's first-run + your own sign-in. */
74
+ /** The one step we can't automate: the browser's first-run + your own sign-in. */
110
75
  function printOnboardingNextStep(name) {
111
76
  console.log(chalk.bold('\nOne manual step left:'));
112
77
  console.log(' ' +
113
78
  chalk.cyan(`agents browser start --profile ${name}`) +
114
- chalk.dim(' # finish Chrome first-run + sign in to any sites you want automated'));
79
+ chalk.dim(' # finish the browser first-run + sign in to any sites you want automated'));
115
80
  console.log(chalk.dim(` Then check it's ready: agents browser profiles doctor ${name}`));
116
81
  }
117
82
  /** Register `agents setup browser` under the parent `setup` command. */
118
83
  export function registerSetupBrowserCommand(setupCmd) {
119
84
  setupCmd
120
85
  .command('browser')
121
- .description('Set up `agents browser` — detect an installed browser and create the default profile.')
86
+ .description('Set up `agents browser` — install the Browser CLI, seed profiles, and pick this machine\'s default.')
122
87
  .option('--install-only', 'Install the standalone Browser CLI without changing profiles or starting a browser')
123
88
  .option('--terminal [backend]', 'Open interactive setup in a detected or selected terminal')
124
89
  .action(async (options) => {
@@ -21,14 +21,6 @@ export declare function macDeviceNames(reg: DeviceRegistry): string[];
21
21
  * first candidate. null when there are no candidates.
22
22
  */
23
23
  export declare function defaultInteractiveHostChoice(candidates: string[], self?: string): string | null;
24
- /**
25
- * The browser picker's highlighted default — the same browser auto-detect
26
- * would win, since `listInstalledBrowsers` returns platform priority order
27
- * (macOS: chrome first). null when nothing is installed.
28
- */
29
- export declare function defaultBrowserChoice<T extends {
30
- browserType: string;
31
- }>(installed: T[]): T['browserType'] | null;
32
24
  /**
33
25
  * Offer to set the interactive host when none is configured and the registry
34
26
  * has more than one macOS device. Returns true when a host was set. Silent
@@ -53,15 +45,6 @@ export declare function maybePickInteractiveHost(): Promise<boolean>;
53
45
  export declare function maybePickBrowserProfile(deps?: {
54
46
  /** Force interactivity in a test; defaults to a real TTY probe. */
55
47
  interactive?: boolean;
56
- /** Inject the prompt so the pick is testable without a TTY. */
57
- select?: (config: {
58
- message: string;
59
- default?: string;
60
- choices: Array<{
61
- name: string;
62
- value: string;
63
- }>;
64
- }) => Promise<string>;
65
48
  }): Promise<boolean>;
66
49
  /**
67
50
  * The bare-`agents setup` preferences step: interactive host, then browser.
@@ -13,9 +13,8 @@
13
13
  * partial state.
14
14
  */
15
15
  import chalk from 'chalk';
16
- import { listInstalledBrowsers } from '../lib/browser/chrome.js';
17
- import { DEFAULT_BROWSER_PROFILE_NAME, getAutoDetectedProfile, createProfile, findFreeProfilePort, getConfiguredDefaultProfileName, } from '../lib/browser/profiles.js';
18
- import { DEFAULT_VIEWPORT } from '../lib/browser/devices.js';
16
+ import { browserInstalled, runBrowser } from '../lib/browser-client.js';
17
+ import { buildBrowserContext } from '../lib/browser/context.js';
19
18
  import { getConfigValue, setConfigValue } from '../lib/device-config.js';
20
19
  import { loadDevices } from '../lib/devices/registry.js';
21
20
  import { machineId } from '../lib/machine-id.js';
@@ -38,14 +37,6 @@ export function defaultInteractiveHostChoice(candidates, self = machineId()) {
38
37
  return null;
39
38
  return candidates.includes(self) ? self : candidates[0];
40
39
  }
41
- /**
42
- * The browser picker's highlighted default — the same browser auto-detect
43
- * would win, since `listInstalledBrowsers` returns platform priority order
44
- * (macOS: chrome first). null when nothing is installed.
45
- */
46
- export function defaultBrowserChoice(installed) {
47
- return installed.length > 0 ? installed[0].browserType : null;
48
- }
49
40
  /**
50
41
  * Offer to set the interactive host when none is configured and the registry
51
42
  * has more than one macOS device. Returns true when a host was set. Silent
@@ -94,48 +85,26 @@ export async function maybePickBrowserProfile(deps = {}) {
94
85
  const interactive = deps.interactive ?? isInteractiveTerminal();
95
86
  if (!interactive)
96
87
  return false;
97
- if (getConfiguredDefaultProfileName())
88
+ // Already chosen (by an earlier setup, `agents browser use`, or browser-cli
89
+ // directly) — never re-pin behind the user's back.
90
+ if (getConfigValue('browser.profile').value)
98
91
  return false;
99
- if (await getAutoDetectedProfile())
92
+ // The engine owns detection and profile creation (PHNX-4101); it is optional,
93
+ // so skip the pick quietly when it is not installed rather than nagging.
94
+ if (!browserInstalled())
100
95
  return false;
101
- const installed = listInstalledBrowsers();
102
- if (installed.length === 0)
96
+ // Let the engine detect installed browsers and create a machine-local profile
97
+ // for each (idempotent), then open its own picker to set this machine's default.
98
+ const context = await buildBrowserContext();
99
+ const seed = await runBrowser({ argv: ['profiles', 'seed'], context });
100
+ if (seed.exitCode !== 0)
103
101
  return false;
104
- const config = {
105
- message: 'Which browser should agents drive on THIS machine?',
106
- default: defaultBrowserChoice(installed) ?? SKIP,
107
- choices: [
108
- ...installed.map((b) => ({ name: `${b.browserType} ${chalk.dim(b.binary)}`, value: b.browserType })),
109
- { name: `None ${chalk.dim('— this box uses the fleet hub (agents config set browser.device <host>)')}`, value: SKIP },
110
- ],
111
- };
112
- // The prompt is the only non-testable boundary here, so it is injectable —
113
- // everything after (createProfile, setConfigValue) runs for real.
114
- let picked;
115
- if (deps.select) {
116
- picked = await deps.select(config);
117
- }
118
- else {
119
- const { select } = await import('@inquirer/prompts');
120
- picked = await select(config);
121
- }
122
- if (picked === SKIP)
102
+ await runBrowser({ argv: ['use'], context });
103
+ const chosen = getConfigValue('browser.profile').value;
104
+ if (!chosen)
123
105
  return false;
124
- const chosen = installed.find((b) => b.browserType === picked) ?? installed[0];
125
- const freePort = await findFreeProfilePort();
126
- const profile = {
127
- name: DEFAULT_BROWSER_PROFILE_NAME,
128
- description: `${chosen.browserType} profile (chosen during setup)`,
129
- browser: chosen.browserType,
130
- binary: chosen.binary,
131
- endpoints: [`cdp://127.0.0.1:${freePort}`],
132
- viewport: { width: DEFAULT_VIEWPORT.width, height: DEFAULT_VIEWPORT.height },
133
- };
134
- await createProfile(profile);
135
- // The same key `agents browser use` writes (device-local).
136
- setConfigValue('browser.profile', profile.name);
137
- console.log(chalk.green(`Browser: '${chosen.browserType}'`) +
138
- chalk.dim(` — profile "${profile.name}" is this machine's default (agents browser use to change).`));
106
+ console.log(chalk.green(`Browser: '${chosen}'`) +
107
+ chalk.dim(' — this machine\'s default (agents browser use to change).'));
139
108
  return true;
140
109
  }
141
110
  /**
@@ -2,9 +2,10 @@
2
2
  * `agents setup term` — install the standalone `term` CLI if missing (PHNX-4092).
3
3
  *
4
4
  * The PTY engine lives in `@phnx-labs/term-cli` (extracted PHNX-4091);
5
- * agents-cli never rebundles it. The OAuth device-code driver (`agents fleet
6
- * login`, `agents auth mint`) spawns `term` on demand and fails loud when it is
7
- * absent, so onboarding installs it here like every other standalone tool.
5
+ * agents-cli never rebundles it. The setup-token mint behind `agents accounts
6
+ * add` / `agents accounts login` (auth-mint.ts → term-driver.ts) spawns `term`
7
+ * on demand and fails loud when it is absent, so onboarding installs it here
8
+ * like every other standalone tool.
8
9
  *
9
10
  * Unlike browser/computer/secrets there is nothing to configure — no profile,
10
11
  * no OS permission, no migrate step. A missing binary is a routine install
@@ -2,9 +2,10 @@
2
2
  * `agents setup term` — install the standalone `term` CLI if missing (PHNX-4092).
3
3
  *
4
4
  * The PTY engine lives in `@phnx-labs/term-cli` (extracted PHNX-4091);
5
- * agents-cli never rebundles it. The OAuth device-code driver (`agents fleet
6
- * login`, `agents auth mint`) spawns `term` on demand and fails loud when it is
7
- * absent, so onboarding installs it here like every other standalone tool.
5
+ * agents-cli never rebundles it. The setup-token mint behind `agents accounts
6
+ * add` / `agents accounts login` (auth-mint.ts → term-driver.ts) spawns `term`
7
+ * on demand and fails loud when it is absent, so onboarding installs it here
8
+ * like every other standalone tool.
8
9
  *
9
10
  * Unlike browser/computer/secrets there is nothing to configure — no profile,
10
11
  * no OS permission, no migrate step. A missing binary is a routine install
@@ -45,7 +46,7 @@ export async function runTermWizard() {
45
46
  export function registerSetupTermCommand(setupCmd) {
46
47
  setupCmd
47
48
  .command('term')
48
- .description('Install the standalone `term` CLI (the PTY engine fleet login and auth mint spawn) if missing.')
49
+ .description('Install the standalone `term` CLI (the PTY engine `agents accounts add`/`login` spawn) if missing.')
49
50
  .option('--install-only', 'Install the standalone term CLI (identical to the wizard; term needs no further setup)')
50
51
  .option('--terminal [backend]', 'Open interactive setup in a detected or selected terminal')
51
52
  .action(async (options) => {
@@ -31,8 +31,7 @@ import { registerAliasCommand } from './alias.js';
31
31
  import { registerBetaCommands } from './beta.js';
32
32
  import { addUrlSchemeSubcommands } from './open.js';
33
33
  import { runPreferencesStep } from './setup-preferences.js';
34
- import { getConfiguredDefaultProfileName, getProfile, getAutoDetectedProfile, isProfileLaunchableHere } from '../lib/browser/profiles.js';
35
- import { listInstalledBrowsers } from '../lib/browser/chrome.js';
34
+ import { browserInstalled } from '../lib/browser-client.js';
36
35
  import { probeComputerTrust } from './computer.js';
37
36
  import { loadDevices } from '../lib/devices/registry.js';
38
37
  import { getConfigValue } from '../lib/device-config.js';
@@ -269,12 +268,13 @@ export async function ensureInitialized(program) {
269
268
  await runSetup(program, { suppressFooter: true });
270
269
  }
271
270
  export async function getSetupStatus() {
272
- const configuredBrowserProfile = getConfiguredDefaultProfileName();
273
- const browserProfile = configuredBrowserProfile
274
- ? await getProfile(configuredBrowserProfile)
275
- : await getAutoDetectedProfile();
276
- const browserReady = browserProfile !== null && isProfileLaunchableHere(browserProfile);
277
- const installedBrowsers = listInstalledBrowsers();
271
+ // Browser readiness is now config + standalone presence (PHNX-4101): the engine
272
+ // (@phnx-labs/browser-cli) owns profile declarations and launchability, so
273
+ // agents-cli reads the shared default-profile key it and browser-cli both write
274
+ // rather than resolving a profile through a deleted in-repo engine.
275
+ const browserCliInstalled = browserInstalled();
276
+ const configuredBrowserProfile = getConfigValue('browser.profile').value;
277
+ const browserReady = browserCliInstalled && !!configuredBrowserProfile;
278
278
  const computerState = process.platform === 'darwin' ? (await probeComputerTrust() ? 'ready' : 'missing') : 'n/a';
279
279
  const devices = await loadDevices();
280
280
  const coreReady = isGitRepo(getAgentsDir());
@@ -286,10 +286,10 @@ export async function getSetupStatus() {
286
286
  const defaultBrowser = getConfigValue('browser.profile').value;
287
287
  return [
288
288
  { phase: 'core', state: coreReady ? 'ready' : 'missing', detail: coreReady ? 'system repo ready' : 'system repo missing' },
289
- { phase: 'browser', state: browserReady ? 'ready' : 'missing', detail: browserReady ? `profile ${browserProfile.name}` : browserProfile ? `profile ${browserProfile.name} cannot launch here` : installedBrowsers.length ? 'no default profile' : 'no supported browser found' },
289
+ { phase: 'browser', state: browserReady ? 'ready' : 'missing', detail: browserReady ? `profile ${configuredBrowserProfile}` : !browserCliInstalled ? 'browser CLI not installed (npm i -g @phnx-labs/browser-cli)' : 'no default profile (agents setup browser)' },
290
290
  { phase: 'computer', state: computerState, detail: computerState === 'ready' ? 'helper trusted' : computerState === 'n/a' ? 'macOS local setup only' : 'helper not running or not trusted' },
291
291
  { phase: 'secrets', state: secretsReady ? 'ready' : 'missing', detail: secretsReady ? 'defaults chosen' : 'defaults not chosen' },
292
- { phase: 'term', state: termReady ? 'ready' : 'missing', detail: termReady ? 'installed' : 'not installed (fleet login / auth mint spawn it)' },
292
+ { phase: 'term', state: termReady ? 'ready' : 'missing', detail: termReady ? 'installed' : 'not installed (accounts add/login spawn it)' },
293
293
  { phase: 'accounts', state: minted.ready ? 'ready' : 'missing', detail: minted.detail },
294
294
  { phase: 'fleet', state: Object.keys(devices).length ? 'ready' : 'missing', detail: Object.keys(devices).length ? `${Object.keys(devices).length} device${Object.keys(devices).length === 1 ? '' : 's'} registered` : 'no devices registered' },
295
295
  { phase: 'watchdog', state: watchdogEnabled ? 'ready' : 'missing', detail: watchdogEnabled ? 'enabled on this device' : 'disabled on this device' },
@@ -462,7 +462,7 @@ export function registerSetupCommand(program) {
462
462
  agents setup browser # detect a browser + create the default profile
463
463
  agents setup computer # install the signed macOS helper + grant permissions
464
464
  agents setup secrets # choose secrets backend/policy defaults + import
465
- agents setup term # install the standalone term CLI (PTY engine for fleet login / auth mint)
465
+ agents setup term # install the standalone term CLI (PTY engine agents accounts add/login spawn)
466
466
  agents setup accounts # mint a Claude setup-token for unattended usage/probe
467
467
  agents setup fleet # discover Tailscale devices + configure SSH access
468
468
  agents setup watchdog # choose which devices run the daemon watchdog pass
@@ -789,7 +789,16 @@ async function runFleetPing(opts) {
789
789
  if (opts.local) {
790
790
  const { authRows: rows } = await refreshLocalFleetAuthState({ force: true });
791
791
  if (opts.json) {
792
- console.log(JSON.stringify({ host: self, rows }));
792
+ // Per-agent launchability from the SAME run-router path the local readiness
793
+ // check uses (`collectRunCandidates`/`isLaunchableSignedIn`), so a consumer
794
+ // can tell a box that simply could not probe — a worker's setup-token box,
795
+ // or a headed box that spent its usage-endpoint budget, both DROP their
796
+ // `no_evidence` row — from a box with no credential at all. From `rows`
797
+ // alone the two are indistinguishable, which broke `--host` routine
798
+ // readiness on every worker (PHNX-4116).
799
+ const { collectRunCandidates } = await import('../lib/accounting/rotate.js');
800
+ const launchable = (await Promise.all(ALL_AGENT_IDS.map(async (agent) => (await collectRunCandidates(agent)).some((candidate) => candidate.signedIn) ? agent : null))).filter((agent) => agent !== null);
801
+ console.log(JSON.stringify({ host: self, rows, launchable }));
793
802
  }
794
803
  else {
795
804
  for (const line of renderAuthMatrix([{ host: self, rows }], { verbose: opts.verbose }))
@@ -35,8 +35,6 @@ import { Option } from 'commander';
35
35
  import chalk from 'chalk';
36
36
  import { resolveSyncPassphraseFromEnv } from '../lib/sync-passphrase.js';
37
37
  import { agentLabel, resolveAgentName, MANAGED_AGENT_IDS, isAgentHardDeprecated, hardDeprecationError } from '../lib/agents.js';
38
- import { autoEvictCentralBrowserProfiles } from '../lib/browser/registry.js';
39
- import { shouldAutoClaimCentralProfile } from '../lib/browser/profiles.js';
40
38
  import { isVersionInstalled, syncResourcesToVersion, parseAgentSpec, resolveVersion, resolveVersionAlias, listInstalledVersions, healDanglingVersionPointers, getAvailableResources, getActuallySyncedResources, getProjectOnlyResources, getNewResources, hasNewResources, promptResourceSelection, promptNewResourceSelection, buildSelection, mergeRepoScopedSelections, listRepoNames, getVersionHomePath, } from '../lib/installations/versions.js';
41
39
  import { capableAgents } from '../lib/capabilities.js';
42
40
  import { parseHookManifest, registerHooksToSettings } from '../lib/hooks/install.js';
@@ -397,12 +395,9 @@ async function runInteractiveReconcile(opts, outLog, errLog) {
397
395
  else
398
396
  outLog(chalk.yellow(` ! ${repo}: ${(res.error ?? 'pull failed').split('\n')[0]}`));
399
397
  }
400
- // Drain the legacy central `browser:` tombstone now that the repos are pulled.
401
- // Running AFTER the pull is load-bearing: it acts on a view of central that
402
- // already reflects peers' drains, so it never re-claims a profile another box
403
- // already migrated (which would flip that profile's kind identity->fungible).
404
- // The interactive path is always non-quiet, non-json.
405
- evictCentralBrowserProfilesForSync(false, false, outLog, errLog);
398
+ // Central browser-profile claiming is the standalone `browser` CLI's now
399
+ // (PHNX-4101): `browser profiles claim` / `browser profiles prune` own it.
400
+ // agents-cli no longer evicts central `browser:` tombstones on sync.
406
401
  // 2. One selection spanning the chosen repos.
407
402
  const selection = mergeRepoScopedSelections(repos, cwd);
408
403
  const hasResources = selection.memory === 'all' || Object.entries(selection).some(([kind, v]) => kind !== 'memory' && Array.isArray(v) && v.length > 0);
@@ -443,36 +438,6 @@ async function runInteractiveReconcile(opts, outLog, errLog) {
443
438
  if (repairHadFailures(umbrellaRepair))
444
439
  process.exitCode = 1;
445
440
  }
446
- /**
447
- * Drain the legacy central `browser:` tombstone during `agents sync` (PHNX-3315).
448
- * New profiles write the per-device doc, but profiles created before the
449
- * device-scoped store lingered in the shared top-level `agents.yaml` and churned
450
- * every fleet pull until someone ran `agents browser profiles claim` by hand.
451
- * Fold the ones THIS box can host into its device doc — host-gated, and the
452
- * selection is computed under the meta lock (see autoEvictCentralBrowserProfiles)
453
- * so it never races itself. Callers MUST invoke this AFTER the repo pull: acting
454
- * on a pre-pull view of central risks re-claiming a profile a peer already
455
- * drained, which would flip its kind identity->fungible. Non-fatal so a hiccup
456
- * can never wedge the sync.
457
- */
458
- function evictCentralBrowserProfilesForSync(quiet, json, outLog, errLog) {
459
- try {
460
- // Auto-claim ONLY remote (ssh://) tombstones — they are fungible by design,
461
- // so a concurrent cross-machine double-claim is harmless. Local/cdp profiles
462
- // have no per-machine ownership signal and are left central for an explicit
463
- // `agents browser profiles claim` (PHNX-3315 review). See
464
- // shouldAutoClaimCentralProfile.
465
- const result = autoEvictCentralBrowserProfiles(shouldAutoClaimCentralProfile);
466
- if (!quiet && !json && result.claimed.length > 0) {
467
- outLog(chalk.gray(` Claimed ${result.claimed.length} central browser profile(s) into this device: ${result.claimed.join(', ')}`));
468
- }
469
- }
470
- catch (err) {
471
- if (!quiet && !json) {
472
- errLog(chalk.yellow(` ! browser profile eviction skipped: ${err.message}`));
473
- }
474
- }
475
- }
476
441
  /**
477
442
  * The umbrella verb: bare `agents sync` (no agent) makes this machine current.
478
443
  * Resolves the flags + a secrets passphrase (env-only for now; tokenized auth
@@ -550,12 +515,9 @@ async function runUmbrella(opts, quiet, outLog, errLog, json = false) {
550
515
  log: (msg) => { if (!quiet && !json)
551
516
  outLog(chalk.gray(` ${msg}`)); },
552
517
  });
553
- // Drain the legacy central `browser:` tombstone AFTER the umbrella pull, so
554
- // we act on central as converged by this sync rather than a stale pre-pull
555
- // copy that could re-claim a profile a peer already migrated. Skipped under
556
- // --cloud (fetch-only, no local reconcile).
557
- if (!opts.cloud && !kindSelection)
558
- evictCentralBrowserProfilesForSync(quiet, json, outLog, errLog);
518
+ // Central browser-profile claiming moved to the standalone `browser` CLI
519
+ // (PHNX-4101: `browser profiles claim` / `prune`), so `agents sync` no longer
520
+ // drains the central `browser:` tombstone.
559
521
  // Post-reconcile verification (PHNX-3186): re-read every version the reconcile
560
522
  // wrote into and confirm it now matches source. Without this the umbrella
561
523
  // printed `✓ sync: reconciled` unconditionally, the exact false-success the
@@ -42,7 +42,6 @@ import { isPromptCancelled, isInteractiveTerminal, requireDestructiveArg, requir
42
42
  const AGENT_NAMES = {
43
43
  claude: 'Claude',
44
44
  codex: 'Codex',
45
- gemini: 'Gemini',
46
45
  cursor: 'Cursor',
47
46
  opencode: 'OpenCode',
48
47
  grok: 'Grok',
@@ -1,5 +1,24 @@
1
1
  import type { Command } from 'commander';
2
2
  import type { AgentId } from '../lib/types.js';
3
+ /**
4
+ * Split `<agent>[@<selector>]`. The selector names an INSTALLATION — its frozen
5
+ * label, or the release it currently carries — never a release to install; that
6
+ * is `--to`. Keeping them separate is what lets `agents update claude@2.0.65
7
+ * --to 2.0.71` read unambiguously.
8
+ */
9
+ /**
10
+ * `<agent>[@<installed-version>][#<account>]`. The `#<account>` form is the
11
+ * same selector `agents run claude#work` takes: accounts run on the harness's
12
+ * one managed installation (PHNX-3940), so it names WHICH login the user means
13
+ * and resolves to that installation. Refusing it as "Unknown agent
14
+ * 'claude#work'" sent operators hunting for a per-account install that does
15
+ * not exist.
16
+ */
17
+ export declare function parseTarget(raw: string): {
18
+ agent: AgentId;
19
+ selector?: string;
20
+ account?: string;
21
+ };
3
22
  /**
4
23
  * One line naming the installations a bare `agents update <agent>` did NOT
5
24
  * touch — isolated (`--isolated`) copies and legacy pre-v2 per-version homes —
@@ -3,23 +3,48 @@ import { AGENTS, formatAgentError, resolveAgentName } from '../lib/agents.js';
3
3
  import { setHelpSections } from '../lib/help.js';
4
4
  import { describeInstallation, listInstallations, planAutoUpdates, resolveInstallation, runAutoUpdatePass, selectUpdateStrategy, supportsPinnedUpdate, updateInstallation, } from '../lib/installations/index.js';
5
5
  import { resolveManagedInstallation } from '../lib/installations/index.js';
6
+ import { findUnifiedAccount } from '../lib/account-registry.js';
7
+ import { readMeta } from '../lib/state.js';
6
8
  /**
7
9
  * Split `<agent>[@<selector>]`. The selector names an INSTALLATION — its frozen
8
10
  * label, or the release it currently carries — never a release to install; that
9
11
  * is `--to`. Keeping them separate is what lets `agents update claude@2.0.65
10
12
  * --to 2.0.71` read unambiguously.
11
13
  */
12
- function parseTarget(raw) {
13
- const at = raw.indexOf('@');
14
- const name = at === -1 ? raw : raw.slice(0, at);
15
- const selector = at === -1 ? undefined : raw.slice(at + 1).trim();
14
+ /**
15
+ * `<agent>[@<installed-version>][#<account>]`. The `#<account>` form is the
16
+ * same selector `agents run claude#work` takes: accounts run on the harness's
17
+ * one managed installation (PHNX-3940), so it names WHICH login the user means
18
+ * and resolves to that installation. Refusing it as "Unknown agent
19
+ * 'claude#work'" sent operators hunting for a per-account install that does
20
+ * not exist.
21
+ */
22
+ export function parseTarget(raw) {
23
+ const hash = raw.indexOf('#');
24
+ const spec = hash === -1 ? raw : raw.slice(0, hash);
25
+ const account = hash === -1 ? undefined : raw.slice(hash + 1).trim();
26
+ if (hash !== -1 && !account) {
27
+ throw new Error(`Select an account after # in '${raw}', e.g. claude#work — or just <agent>.`);
28
+ }
29
+ if (account?.includes('@')) {
30
+ throw new Error(`Put the installation before the account: <agent>@<installed-version>#<account>, not '${raw}'.`);
31
+ }
32
+ const at = spec.indexOf('@');
33
+ const name = at === -1 ? spec : spec.slice(0, at);
34
+ const selector = at === -1 ? undefined : spec.slice(at + 1).trim();
16
35
  if (at !== -1 && !selector) {
17
36
  throw new Error(`Missing installation in '${raw}'. Use <agent>@<installed-version>, or just <agent>.`);
18
37
  }
19
38
  const agent = resolveAgentName(name);
20
39
  if (!agent)
21
40
  throw new Error(formatAgentError(name));
22
- return { agent, selector };
41
+ if (account) {
42
+ const known = findUnifiedAccount(account, readMeta(), undefined, agent);
43
+ if (!known) {
44
+ throw new Error(`Unknown ${AGENTS[agent].name} account '${account}'. See: agents accounts list ${agent}`);
45
+ }
46
+ }
47
+ return { agent, selector, account };
23
48
  }
24
49
  /**
25
50
  * One line naming the installations a bare `agents update <agent>` did NOT
@@ -219,7 +244,10 @@ export function registerUpdateCommand(program) {
219
244
  if (!target) {
220
245
  throw new Error('Which agent? Use: agents update <agent>[@<installed-version>], or agents update --auto.');
221
246
  }
222
- const { agent, selector } = parseTarget(target);
247
+ const { agent, selector, account } = parseTarget(target);
248
+ if (account && !options.json) {
249
+ console.log(chalk.gray(` account '${account}' runs on the managed ${AGENTS[agent].name} installation; updating that.`));
250
+ }
223
251
  if (options.check) {
224
252
  const plan = (await planAutoUpdates({ agents: [agent] })).filter((entry) => !selector || entry.installation.label === selector || entry.installation.releaseVersion === selector);
225
253
  if (selector && plan.length === 0) {
@@ -4,7 +4,7 @@ import type { AccountInfo } from '../lib/agents.js';
4
4
  import type { AgentId } from '../lib/types.js';
5
5
  import { type ConfiguredDeviceRole } from '../lib/device-config.js';
6
6
  import { type NativeAccountCatalogRow } from '../lib/account-catalog.js';
7
- import type { ResourceSection, ViewJsonAgent } from '../lib/view-types.js';
7
+ import type { ResourceSection, ViewJsonAgent, ViewJsonRunReady } from '../lib/view-types.js';
8
8
  export type { ResourceItemJson, ResourceSection, SyncState, VersionResourcesJson, ViewJsonAgent, ViewJsonVersion } from '../lib/view-types.js';
9
9
  import { type ProfileSummary } from '../lib/profiles.js';
10
10
  import { type ByokUsageResult } from '../lib/byok-usage.js';
@@ -76,6 +76,17 @@ export declare function parseResourceSections(options: {
76
76
  resources?: string | boolean;
77
77
  detailed?: boolean;
78
78
  } & ViewSectionFilter, jsonMode: boolean): Set<ResourceSection>;
79
+ /**
80
+ * The one run-readiness gate for one agent on THIS box (PHNX-4116). Runs the
81
+ * SAME enumeration the local router picks from — `collectRunCandidates` (native
82
+ * slots + version homes) through `readinessFromCandidate` — so the answer
83
+ * `agents view --json` publishes is exactly what a `run` here would find, and a
84
+ * remote `--device auto` dispatcher can read it instead of re-deriving freshness
85
+ * on its own box (which drifted: view.ts lists version homes, the router lists
86
+ * slots). Returns undefined when the agent has no candidates at all (nothing
87
+ * installed), so the field is simply absent rather than a misleading verdict.
88
+ */
89
+ export declare function computeAgentRunReady(agentId: AgentId): Promise<ViewJsonRunReady | undefined>;
79
90
  /**
80
91
  * Collect structured info for one or more agents without rendering to the
81
92
  * terminal. Used by `--json` output and any programmatic consumer (e.g. the