@phnx-labs/agents-cli 1.22.41 → 1.22.42

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 (167) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +9 -3
  3. package/dist/bin/agents +0 -0
  4. package/dist/cli/command-registry.d.ts +2 -0
  5. package/dist/cli/command-registry.js +4 -0
  6. package/dist/commands/accounts.d.ts +29 -0
  7. package/dist/commands/accounts.js +220 -29
  8. package/dist/commands/alias.js +1 -1
  9. package/dist/commands/auth.d.ts +9 -0
  10. package/dist/commands/auth.js +104 -0
  11. package/dist/commands/browser-sessions-picker.js +42 -62
  12. package/dist/commands/browser.js +30 -38
  13. package/dist/commands/computer-actions.d.ts +1 -1
  14. package/dist/commands/computer-actions.js +1 -1
  15. package/dist/commands/computer-sessions-picker.js +32 -55
  16. package/dist/commands/computer.d.ts +1 -1
  17. package/dist/commands/computer.js +2 -2
  18. package/dist/commands/doctor.d.ts +1 -1
  19. package/dist/commands/doctor.js +3 -3
  20. package/dist/commands/feed.js +1 -1
  21. package/dist/commands/hooks.js +1 -1
  22. package/dist/commands/import.js +1 -1
  23. package/dist/commands/insights.js +163 -85
  24. package/dist/commands/inspect.js +2 -2
  25. package/dist/commands/mine.js +1 -1
  26. package/dist/commands/org.d.ts +6 -0
  27. package/dist/commands/org.js +174 -0
  28. package/dist/commands/packages.js +1 -1
  29. package/dist/commands/prune.js +1 -1
  30. package/dist/commands/routines.js +1 -1
  31. package/dist/commands/run-account-picker.d.ts +19 -0
  32. package/dist/commands/run-account-picker.js +81 -0
  33. package/dist/commands/sessions-picker-factory.d.ts +40 -0
  34. package/dist/commands/sessions-picker-factory.js +67 -0
  35. package/dist/commands/setup-browser.js +4 -4
  36. package/dist/commands/setup-mine.js +1 -1
  37. package/dist/commands/setup-preferences.js +2 -2
  38. package/dist/commands/setup.js +5 -3
  39. package/dist/commands/sync.js +1 -1
  40. package/dist/commands/versions.js +1 -1
  41. package/dist/commands/view.js +1 -1
  42. package/dist/lib/account-capabilities.d.ts +10 -0
  43. package/dist/lib/account-capabilities.js +34 -0
  44. package/dist/lib/agent-spec/agents.d.ts +584 -0
  45. package/dist/lib/agent-spec/agents.js +3127 -0
  46. package/dist/lib/agent-spec/provider.js +1 -1
  47. package/dist/lib/agents.d.ts +2 -584
  48. package/dist/lib/agents.js +2 -3127
  49. package/dist/lib/browser/drivers/local.d.ts +8 -2
  50. package/dist/lib/browser/drivers/local.js +8 -2
  51. package/dist/lib/browser/drivers/ssh.d.ts +8 -2
  52. package/dist/lib/browser/drivers/ssh.js +10 -4
  53. package/dist/lib/browser/hygiene.d.ts +1 -1
  54. package/dist/lib/browser/hygiene.js +5 -1
  55. package/dist/lib/browser/profiles.d.ts +74 -1
  56. package/dist/lib/browser/profiles.js +114 -5
  57. package/dist/lib/browser/runtime-state.d.ts +11 -5
  58. package/dist/lib/browser/runtime-state.js +13 -9
  59. package/dist/lib/browser/service.d.ts +68 -30
  60. package/dist/lib/browser/service.js +305 -194
  61. package/dist/lib/browser/types.d.ts +95 -3
  62. package/dist/lib/browser/types.js +51 -0
  63. package/dist/lib/catchup.js +1 -1
  64. package/dist/lib/computer/actions.d.ts +1 -1
  65. package/dist/lib/computer/actions.js +1 -1
  66. package/dist/lib/{computer-rpc.js → computer/computer-rpc.js} +5 -5
  67. package/dist/lib/computer/dispatch.d.ts +1 -1
  68. package/dist/lib/computer/download.d.ts +1 -1
  69. package/dist/lib/computer/download.js +3 -3
  70. package/dist/lib/{ssh-tunnel.d.ts → computer/ssh-tunnel.d.ts} +1 -1
  71. package/dist/lib/{ssh-tunnel.js → computer/ssh-tunnel.js} +12 -12
  72. package/dist/lib/daemon/daemon.js +1 -1
  73. package/dist/lib/{runner.d.ts → daemon/runner.d.ts} +10 -10
  74. package/dist/lib/{runner.js → daemon/runner.js} +40 -114
  75. package/dist/lib/devices/doctor-findings.d.ts +1 -1
  76. package/dist/lib/devices/doctor-findings.js +3 -3
  77. package/dist/lib/devices/fleet-inventory.js +1 -1
  78. package/dist/lib/devices/harness-inventory.js +1 -1
  79. package/dist/lib/devices/registry.js +4 -22
  80. package/dist/lib/doctor-diff.d.ts +1 -1
  81. package/dist/lib/doctor-diff.js +1 -1
  82. package/dist/lib/drift.js +1 -1
  83. package/dist/lib/entitlement.d.ts +31 -0
  84. package/dist/lib/entitlement.js +137 -0
  85. package/dist/lib/exec.js +39 -202
  86. package/dist/lib/feed/feed.js +2 -8
  87. package/dist/lib/fs-atomic.d.ts +7 -0
  88. package/dist/lib/fs-atomic.js +9 -0
  89. package/dist/lib/harness/adapter.d.ts +160 -0
  90. package/dist/lib/harness/adapter.js +47 -0
  91. package/dist/lib/harness/adapters/claude.d.ts +2 -0
  92. package/dist/lib/harness/adapters/claude.js +110 -0
  93. package/dist/lib/harness/adapters/codex.d.ts +2 -0
  94. package/dist/lib/harness/adapters/codex.js +83 -0
  95. package/dist/lib/harness/adapters/copilot.d.ts +2 -0
  96. package/dist/lib/harness/adapters/copilot.js +23 -0
  97. package/dist/lib/harness/adapters/cursor.d.ts +2 -0
  98. package/dist/lib/harness/adapters/cursor.js +38 -0
  99. package/dist/lib/harness/adapters/droid.d.ts +2 -0
  100. package/dist/lib/harness/adapters/droid.js +17 -0
  101. package/dist/lib/harness/adapters/grok.d.ts +2 -0
  102. package/dist/lib/harness/adapters/grok.js +14 -0
  103. package/dist/lib/harness/adapters/kimi.d.ts +2 -0
  104. package/dist/lib/harness/adapters/kimi.js +49 -0
  105. package/dist/lib/harness/adapters/muse.d.ts +2 -0
  106. package/dist/lib/harness/adapters/muse.js +44 -0
  107. package/dist/lib/harness/adapters/opencode.d.ts +2 -0
  108. package/dist/lib/harness/adapters/opencode.js +14 -0
  109. package/dist/lib/harness/exec-config-version.d.ts +8 -0
  110. package/dist/lib/harness/exec-config-version.js +22 -0
  111. package/dist/lib/harness/index.d.ts +1 -0
  112. package/dist/lib/harness/index.js +34 -0
  113. package/dist/lib/{hooks.d.ts → hooks/install.d.ts} +1 -1
  114. package/dist/lib/{hooks.js → hooks/install.js} +11 -11
  115. package/dist/lib/import.js +1 -1
  116. package/dist/lib/installations/index.d.ts +4 -4
  117. package/dist/lib/installations/index.js +3 -4
  118. package/dist/lib/installations/migrate.js +6 -6
  119. package/dist/lib/{shims.d.ts → installations/shims.d.ts} +2 -2
  120. package/dist/lib/{shims.js → installations/shims.js} +12 -114
  121. package/dist/lib/installations/store.d.ts +141 -3
  122. package/dist/lib/installations/store.js +475 -5
  123. package/dist/lib/installations/versions.d.ts +1 -126
  124. package/dist/lib/installations/versions.js +5 -465
  125. package/dist/lib/isolation-boundary-report.d.ts +1 -1
  126. package/dist/lib/isolation-boundary-report.js +1 -1
  127. package/dist/lib/mailbox-gc.js +2 -3
  128. package/dist/lib/menubar/MenubarHelper.app/Contents/CodeResources +0 -0
  129. package/dist/lib/menubar/MenubarHelper.app/Contents/MacOS/MenubarHelper +0 -0
  130. package/dist/lib/monitors/dispatch.d.ts +1 -1
  131. package/dist/lib/monitors/dispatch.js +2 -2
  132. package/dist/lib/monitors/state.d.ts +1 -1
  133. package/dist/lib/plugins/plugins.js +1 -1
  134. package/dist/lib/plugins/skills.js +1 -1
  135. package/dist/lib/prix-account.d.ts +158 -0
  136. package/dist/lib/prix-account.js +214 -0
  137. package/dist/lib/project-key.js +2 -2
  138. package/dist/lib/project-launch.js +1 -1
  139. package/dist/lib/refresh.js +2 -2
  140. package/dist/lib/resource-inventory.d.ts +1 -1
  141. package/dist/lib/resource-inventory.js +1 -1
  142. package/dist/lib/rules/rules.js +1 -1
  143. package/dist/lib/scheduling/routines.js +1 -1
  144. package/dist/lib/secrets/Agents CLI.app/Contents/CodeResources +0 -0
  145. package/dist/lib/secrets/Agents CLI.app/Contents/MacOS/Agents CLI +0 -0
  146. package/dist/lib/self-heal/checks/hook-runtime.js +1 -1
  147. package/dist/lib/self-heal/checks/path.js +1 -1
  148. package/dist/lib/self-heal/checks/shadowing.js +1 -1
  149. package/dist/lib/self-heal/checks/shims.js +1 -1
  150. package/dist/lib/session/discover.d.ts +39 -0
  151. package/dist/lib/session/discover.js +3 -3
  152. package/dist/lib/session/parse.js +24 -42
  153. package/dist/lib/session/recovery.js +1 -1
  154. package/dist/lib/staleness/prune.d.ts +1 -1
  155. package/dist/lib/staleness/prune.js +1 -1
  156. package/dist/lib/staleness/writers/hooks.js +1 -1
  157. package/dist/lib/startup/command-registry.js +1 -1
  158. package/dist/lib/teams/agents.js +2 -19
  159. package/dist/lib/teams/parsers.js +20 -26
  160. package/dist/lib/teams/registry.js +2 -21
  161. package/dist/lib/triggers/handlers.js +1 -1
  162. package/dist/lib/triggers/webhook.js +1 -1
  163. package/dist/lib/uninstall.js +1 -1
  164. package/dist/lib/version.d.ts +9 -2
  165. package/dist/lib/version.js +11 -9
  166. package/package.json +1 -1
  167. /package/dist/lib/{computer-rpc.d.ts → computer/computer-rpc.d.ts} +0 -0
@@ -0,0 +1,104 @@
1
+ import chalk from 'chalk';
2
+ import { setHelpSections } from '../lib/help.js';
3
+ import { clearPrixSession, fetchWhoAmI, pollDeviceToken, PrixApiError, readPrixSession, resolvePrixToken, startDeviceAuthorization, writePrixSession, } from '../lib/prix-account.js';
4
+ function sleep(ms) {
5
+ return new Promise(resolve => setTimeout(resolve, ms));
6
+ }
7
+ /**
8
+ * `agents auth login` — RFC 8628 device-code flow against the already-shipped
9
+ * `/api/v1/auth/device/*` routes. If the live API rejects the request shape
10
+ * (a route this spec missed, or the endpoint moved), this fails loud with the
11
+ * `rush login` fallback rather than silently stubbing a fake session.
12
+ */
13
+ export async function runAuthLogin() {
14
+ let authorization;
15
+ try {
16
+ authorization = await startDeviceAuthorization();
17
+ }
18
+ catch (err) {
19
+ const message = err instanceof Error ? err.message : String(err);
20
+ const existing = resolvePrixToken();
21
+ if (existing?.source === 'rush') {
22
+ console.log(chalk.yellow(`Device login is unavailable right now (${message}), but you're already signed in via 'rush login'.`));
23
+ console.log("Run 'agents auth whoami' to confirm.");
24
+ return;
25
+ }
26
+ throw new Error(`Device login is unavailable right now (${message}). Run 'rush login' instead, then 'agents auth whoami' to confirm.`);
27
+ }
28
+ console.log(`To sign in, open:\n\n ${chalk.cyan(authorization.verification_uri_complete)}\n`);
29
+ console.log(chalk.gray(`(code: ${authorization.user_code})`));
30
+ const deadline = Date.now() + authorization.expires_in * 1000;
31
+ let interval = Math.max(authorization.interval, 1) * 1000;
32
+ while (Date.now() < deadline) {
33
+ await sleep(interval);
34
+ const poll = await pollDeviceToken(authorization.device_code);
35
+ if (poll.status === 'pending')
36
+ continue;
37
+ if (poll.status === 'slow_down') {
38
+ interval += 1000;
39
+ continue;
40
+ }
41
+ if (poll.status === 'expired')
42
+ throw new Error('Login code expired. Run `agents auth login` again.');
43
+ if (poll.status === 'denied')
44
+ throw new Error('Login was denied.');
45
+ writePrixSession({
46
+ access_token: poll.access_token,
47
+ refresh_token: poll.refresh_token,
48
+ expires_at: poll.expires_in ? Date.now() + poll.expires_in * 1000 : undefined,
49
+ email: poll.user.email,
50
+ userId: poll.user.id,
51
+ });
52
+ console.log(chalk.green(`Signed in as ${poll.user.email}.`));
53
+ return;
54
+ }
55
+ throw new Error('Login code expired. Run `agents auth login` again.');
56
+ }
57
+ async function runAuthWhoami(json) {
58
+ const resolved = resolvePrixToken();
59
+ if (!resolved)
60
+ throw new Error("Not signed in. Run 'agents auth login' first.");
61
+ let who;
62
+ try {
63
+ who = await fetchWhoAmI(resolved.token);
64
+ }
65
+ catch (err) {
66
+ if (err instanceof PrixApiError && err.status === 401) {
67
+ throw new Error(`Signed-in token is no longer valid (${resolved.source === 'agents' ? "run 'agents auth login'" : "run 'rush login'"} again).`);
68
+ }
69
+ throw err;
70
+ }
71
+ if (json) {
72
+ console.log(JSON.stringify({ ...who, source: resolved.source }, null, 2));
73
+ return;
74
+ }
75
+ console.log(`${chalk.bold(who.email)} ${chalk.gray(who.userId)}`);
76
+ console.log(chalk.gray(`signed in via ${resolved.source === 'agents' ? "'agents auth login'" : "'rush login' (shared session)"}`));
77
+ }
78
+ function runAuthLogout() {
79
+ const cleared = clearPrixSession();
80
+ const rushRemains = resolvePrixToken()?.source === 'rush';
81
+ if (!cleared && !readPrixSession()) {
82
+ console.log(chalk.gray('Not signed in via `agents auth login` — nothing to clear.'));
83
+ }
84
+ else {
85
+ console.log(chalk.green('Signed out.'));
86
+ }
87
+ if (rushRemains) {
88
+ console.log(chalk.gray("You're still signed in via `rush login` — `agents auth whoami` will use that session. Run `rush logout` to clear it too."));
89
+ }
90
+ }
91
+ export function registerAuthCommand(program) {
92
+ const auth = program.command('auth').description('Sign in to your Rush account (shared by `agents org` and paid tiers)');
93
+ auth.command('login').description('Sign in via the device-code flow').action(async () => runAuthLogin());
94
+ auth.command('whoami').description('Show the signed-in account').option('--json', 'Machine-readable output')
95
+ .action((o, command) => runAuthWhoami(!!(o.json || command.optsWithGlobals().json)));
96
+ auth.command('logout').description('Clear the local `agents auth login` session').action(() => runAuthLogout());
97
+ setHelpSections(auth, {
98
+ examples: `agents auth login
99
+ agents auth whoami
100
+ agents auth whoami --json
101
+ agents auth logout`,
102
+ notes: "`agents auth login` writes its own session, separate from \`rush login\`'s \`~/.rush/user.yaml\` — \`agents auth logout\` never signs you out of \`rush\`. If you're already signed in via \`rush login\`, \`agents auth whoami\`/\`agents org\` use that session automatically; no separate login is required.",
103
+ });
104
+ }
@@ -7,36 +7,16 @@
7
7
  * Reuses the same `itemPicker` + `buildPreview` primitives as the ordinary
8
8
  * session picker (`sessions-picker.ts`) — same search/filter/quit help, and the
9
9
  * preview pane for a linked task IS the canonical session digest, not a
10
- * second renderer.
10
+ * second renderer. Interactive routing and the browse loop live in
11
+ * `sessions-picker-factory.ts` (shared with the computer twin).
11
12
  */
12
13
  import chalk from 'chalk';
13
14
  import { itemPicker } from '../lib/picker.js';
14
- import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
15
+ import { isPromptCancelled } from './utils.js';
15
16
  import { buildPreview } from './sessions-picker.js';
17
+ import { createSessionsPickerCommand } from './sessions-picker-factory.js';
16
18
  import { runBrowserSessions, buildBrowserSessionRows, matchesBrowserSessionRow, openArtifact, formatBytes, } from '../lib/browser/sessions-list.js';
17
19
  import { formatRelativeTime } from '../lib/session/relative-time.js';
18
- /** True when the interactive picker should open instead of the printed table:
19
- * a real TTY, no `--json`/`--open`, and `--no-interactive` not set. */
20
- export function shouldOpenInteractiveBrowserSessions(opts, isTTY) {
21
- return opts.interactive !== false && !opts.json && opts.open === undefined && isTTY;
22
- }
23
- /**
24
- * Shared entry point for `agents browser sessions` and `agents sessions
25
- * --browser`. Replaces direct calls to `runBrowserSessions` at both call
26
- * sites so the interactive routing decision lives in one place.
27
- */
28
- export async function runBrowserSessionsCommand(opts) {
29
- if (!shouldOpenInteractiveBrowserSessions(opts, isInteractiveTerminal())) {
30
- runBrowserSessions({ profile: opts.profile, open: opts.open, json: opts.json });
31
- return;
32
- }
33
- const rows = buildBrowserSessionRows(opts.profile);
34
- if (rows.length === 0) {
35
- console.log(`No browser captures found${opts.profile ? ` for profile "${opts.profile}"` : ''}.`);
36
- return;
37
- }
38
- await browseAndOpen(rows);
39
- }
40
20
  const KIND_LABEL = {
41
21
  screenshot: 'shots',
42
22
  pdf: 'pdf',
@@ -116,44 +96,6 @@ function openAndReport(a) {
116
96
  if (!openArtifact(a.path))
117
97
  console.error(`Could not open ${a.path}`);
118
98
  }
119
- /** Task-level picker with a captures preview, then an artifact drill-down when
120
- * a task holds more than one capture. Loops until the user quits (esc) so a
121
- * capture-heavy task can be browsed and opened repeatedly in one session. */
122
- async function browseAndOpen(rows) {
123
- for (;;) {
124
- let picked;
125
- try {
126
- picked = await itemPicker({
127
- message: 'Browser sessions:',
128
- items: rows,
129
- filter: (query) => (query.trim() ? rows.filter((r) => matchesBrowserSessionRow(r, query)) : rows),
130
- labelFor: (row) => formatRowLabel(row),
131
- buildPreview: (row) => buildRowPreview(row),
132
- emptyMessage: 'No browser sessions match.',
133
- enterHint: 'open capture',
134
- });
135
- }
136
- catch (err) {
137
- if (isPromptCancelled(err))
138
- return;
139
- throw err;
140
- }
141
- if (!picked)
142
- return;
143
- const row = picked.item;
144
- if (row.artifacts.length === 0) {
145
- console.log('No captures in this task yet.');
146
- continue;
147
- }
148
- if (row.artifacts.length === 1) {
149
- openAndReport(row.artifacts[0]);
150
- continue;
151
- }
152
- const artifact = await pickArtifact(row);
153
- if (artifact)
154
- openAndReport(artifact);
155
- }
156
- }
157
99
  /** Second-level picker over one row's captures, newest first. Enter opens the
158
100
  * highlighted capture; esc returns to the task list. */
159
101
  async function pickArtifact(row) {
@@ -177,3 +119,41 @@ async function pickArtifact(row) {
177
119
  throw err;
178
120
  }
179
121
  }
122
+ const browserSessionsPicker = createSessionsPickerCommand({
123
+ requireOpenUndefined: true,
124
+ runFlat: (opts) => runBrowserSessions({ profile: opts.profile, open: opts.open, json: opts.json }),
125
+ buildRows: (opts) => buildBrowserSessionRows(opts.profile),
126
+ emptyMessage: (opts) => `No browser captures found${opts.profile ? ` for profile "${opts.profile}"` : ''}.`,
127
+ message: 'Browser sessions:',
128
+ matches: matchesBrowserSessionRow,
129
+ labelFor: formatRowLabel,
130
+ buildPreview: buildRowPreview,
131
+ emptyFilterMessage: 'No browser sessions match.',
132
+ enterHint: 'open capture',
133
+ onOpen: async (row) => {
134
+ if (row.artifacts.length === 0) {
135
+ console.log('No captures in this task yet.');
136
+ return;
137
+ }
138
+ if (row.artifacts.length === 1) {
139
+ openAndReport(row.artifacts[0]);
140
+ return;
141
+ }
142
+ const artifact = await pickArtifact(row);
143
+ if (artifact)
144
+ openAndReport(artifact);
145
+ },
146
+ });
147
+ /** True when the interactive picker should open instead of the printed table:
148
+ * a real TTY, no `--json`/`--open`, and `--no-interactive` not set. */
149
+ export function shouldOpenInteractiveBrowserSessions(opts, isTTY) {
150
+ return browserSessionsPicker.shouldOpen(opts, isTTY);
151
+ }
152
+ /**
153
+ * Shared entry point for `agents browser sessions` and `agents sessions
154
+ * --browser`. Replaces direct calls to `runBrowserSessions` at both call
155
+ * sites so the interactive routing decision lives in one place.
156
+ */
157
+ export async function runBrowserSessionsCommand(opts) {
158
+ await browserSessionsPicker.run(opts);
159
+ }
@@ -1,7 +1,7 @@
1
1
  import chalk from 'chalk';
2
2
  import * as fs from 'fs';
3
3
  import * as path from 'path';
4
- import { listProfiles, getProfile, createProfile, deleteProfile, ensureDefaultBrowserProfile, getConfiguredDefaultProfileName, DEFAULT_BROWSER_PROFILE_NAME, getProfileRuntimeDir, extractConfiguredPort, findFreeProfilePort, getEndpointPresets, listProfilesWithScope, formatProfilesTable, } from '../lib/browser/profiles.js';
4
+ import { listProfiles, getProfile, createProfile, deleteProfile, getConfiguredDefaultProfileName, resolveProfileRef, resolveProfileRefForStart, getProfileRuntimeDir, extractConfiguredPort, findFreeProfilePort, getEndpointPresets, listProfilesWithScope, formatProfilesTable, } from '../lib/browser/profiles.js';
5
5
  import { resolveActor } from '../lib/actor.js';
6
6
  import { loginsForProfile, profilesLoggedInto, serviceForUrl, loginsWithAccountsForProfile, accountsForProfile, credKeysForService, AUTH_SIGNATURES, } from '../lib/browser/login-detection.js';
7
7
  import { parseSecretRef } from '../lib/browser/secret-ref.js';
@@ -576,12 +576,12 @@ function registerProfilesCommands(browser) {
576
576
  other machine — pass --fleet only when the profile really is fleet config.
577
577
 
578
578
  In \`list\`, the \`*\` marker means "this machine's configured default"
579
- (\`agents browser start\` with no --profile). It is NOT the same thing as the
580
- profile NAMED \`default\`, which is the auto-detected one; they are often
581
- different profiles.
579
+ (\`agents browser start\` with no --profile). The auto-detected profile is
580
+ named \`auto-chrome\`; \`default\` is only an ALIAS for whichever profile
581
+ this machine is configured to use, resolved the same way by every command.
582
582
 
583
583
  \`prune\` never removes a profile that is in use, the configured default, the
584
- auto \`default\`, or (without --fleet) a fleet-synced one.
584
+ auto-detected \`auto-chrome\`, or (without --fleet) a fleet-synced one.
585
585
  `,
586
586
  });
587
587
  profiles
@@ -846,33 +846,20 @@ function registerTaskCommands(browser) {
846
846
  console.error(err instanceof Error ? err.message : String(err));
847
847
  process.exit(1);
848
848
  }
849
- let profileName = opts.profile;
850
- if (!profileName) {
851
- try {
852
- const detected = await ensureDefaultBrowserProfile();
853
- profileName = detected.name;
854
- }
855
- catch (err) {
856
- console.error(err instanceof Error ? err.message : String(err));
857
- process.exit(1);
858
- }
849
+ // One resolution order for every command (RUSH-2709): `--profile default`
850
+ // means the same profile here as it does in stop / status / navigate.
851
+ // `start` is the one command that LAUNCHES, so its implicit path goes
852
+ // through ensureDefaultBrowserProfile, which additionally verifies the
853
+ // resolved default can launch on THIS machine and regenerates it when it
854
+ // can't (the "Custom binary not found" repair) — a filter-only command
855
+ // must not warn or rewrite a profile, so that check lives only here.
856
+ let profileName;
857
+ try {
858
+ profileName = await resolveProfileRefForStart(opts.profile);
859
859
  }
860
- else if (profileName === DEFAULT_BROWSER_PROFILE_NAME) {
861
- // Explicit `--profile default` honors the configured default too, so an
862
- // agent that hardcodes the reserved `default` name still lands on the
863
- // user's chosen profile (e.g. their logged-in Comet) rather than a
864
- // literal `default` (which auto-detects Chrome). Narrowly scoped: only
865
- // the reserved name, only here in `start`.
866
- const configured = getConfiguredDefaultProfileName();
867
- if (configured && configured !== DEFAULT_BROWSER_PROFILE_NAME) {
868
- const target = await getProfile(configured);
869
- if (target) {
870
- profileName = target.name;
871
- }
872
- else {
873
- console.error(`warning: configured default profile "${configured}" not found; using "${DEFAULT_BROWSER_PROFILE_NAME}".`);
874
- }
875
- }
860
+ catch (err) {
861
+ console.error(err instanceof Error ? err.message : String(err));
862
+ process.exit(1);
876
863
  }
877
864
  // Pre-check the profile locally so we fail fast with a helpful error
878
865
  // instead of round-tripping a generic "Profile not found" through the daemon.
@@ -1001,15 +988,16 @@ function registerTaskCommands(browser) {
1001
988
  .option('-p, --profile <name>', 'Detach the whole profile (incl. composite "name@endpoint") instead of stopping a single task')
1002
989
  .action(async (opts) => {
1003
990
  if (opts.profile) {
991
+ const profile = (await resolveProfileRef(opts.profile)) ?? opts.profile;
1004
992
  const response = await sendIPCRequest({
1005
993
  action: 'stop',
1006
- profile: opts.profile,
994
+ profile,
1007
995
  });
1008
996
  if (!response.ok) {
1009
997
  console.error(response.error);
1010
998
  process.exit(1);
1011
999
  }
1012
- console.log(`Stopped profile: ${opts.profile}`);
1000
+ console.log(`Stopped profile: ${profile}`);
1013
1001
  return;
1014
1002
  }
1015
1003
  const task = resolveTaskName(opts);
@@ -1083,7 +1071,7 @@ function registerTaskCommands(browser) {
1083
1071
  action: 'navigate',
1084
1072
  task,
1085
1073
  url,
1086
- profile: opts.profile,
1074
+ profile: opts.profile ? await resolveProfileRef(opts.profile) : undefined,
1087
1075
  });
1088
1076
  if (!response.ok) {
1089
1077
  console.error(response.error);
@@ -1108,7 +1096,7 @@ function registerTaskCommands(browser) {
1108
1096
  action: 'tab-add',
1109
1097
  task,
1110
1098
  url: opts.url,
1111
- profile: opts.profile,
1099
+ profile: opts.profile ? await resolveProfileRef(opts.profile) : undefined,
1112
1100
  });
1113
1101
  if (!response.ok) {
1114
1102
  console.error(response.error);
@@ -1369,7 +1357,7 @@ function registerTaskCommands(browser) {
1369
1357
  try {
1370
1358
  response = await sendIPCRequest({
1371
1359
  action: 'status',
1372
- profile: opts.profile,
1360
+ profile: opts.profile ? await resolveProfileRef(opts.profile) : undefined,
1373
1361
  }, { autoStartDaemon: false });
1374
1362
  }
1375
1363
  catch (err) {
@@ -1458,7 +1446,11 @@ function registerTaskCommands(browser) {
1458
1446
  // pid 0 means the daemon attached to a browser we didn't launch — no
1459
1447
  // tracked pid. Render it as "attached" rather than the literal 0.
1460
1448
  const pidLabel = profile.pid ? `pid ${profile.pid}` : 'attached';
1461
- console.log(`\n${profile.name} (${portLabel}, ${pidLabel})`);
1449
+ // `profile.name` is the BARE profile; the endpoint is its own field,
1450
+ // so the row reads `comet-local (endpoint: endpoint-0, port …)`
1451
+ // instead of the raw `comet-local@endpoint-0` key (RUSH-2709).
1452
+ const endpointLabel = profile.endpoint ? `endpoint: ${profile.endpoint}, ` : '';
1453
+ console.log(`\n${profile.name} (${endpointLabel}${portLabel}, ${pidLabel})`);
1462
1454
  if (profile.tasks.length === 0) {
1463
1455
  console.log(' No active tasks');
1464
1456
  }
@@ -1493,7 +1485,7 @@ function registerTaskCommands(browser) {
1493
1485
  .action(async (opts) => {
1494
1486
  const response = await sendIPCRequest({
1495
1487
  action: 'status',
1496
- profile: opts.profile,
1488
+ profile: opts.profile ? await resolveProfileRef(opts.profile) : undefined,
1497
1489
  });
1498
1490
  if (!response.ok) {
1499
1491
  if (opts.json) {
@@ -1,5 +1,5 @@
1
1
  import { Command } from 'commander';
2
- import { type ComputerClient, type RPCResponse } from '../lib/computer-rpc.js';
2
+ import { type ComputerClient, type RPCResponse } from '../lib/computer/computer-rpc.js';
3
3
  export { emitComputerAction, pickTarget, resolveTargetPidDecision, type AppInfo } from '../lib/computer/actions.js';
4
4
  export declare function parseXY(s: string, flag: string): {
5
5
  x: number;
@@ -9,7 +9,7 @@
9
9
  import { execFileSync } from 'child_process';
10
10
  import * as fs from 'fs';
11
11
  import * as path from 'path';
12
- import { openComputerClient, describeTransport, } from '../lib/computer-rpc.js';
12
+ import { openComputerClient, describeTransport, } from '../lib/computer/computer-rpc.js';
13
13
  import { emitComputerAction, resolveTargetPidDecision } from '../lib/computer/actions.js';
14
14
  export { emitComputerAction, pickTarget, resolveTargetPidDecision } from '../lib/computer/actions.js';
15
15
  // Pure target picker — exercised by unit tests. Precedence: explicit --pid,
@@ -13,39 +13,14 @@
13
13
  * computer run has no on-disk artifact of its own to open (see
14
14
  * `lib/computer/sessions-list.ts`'s module docblock), so `enter` prints the
15
15
  * run's full action list rather than opening a file, and the picker keeps
16
- * browsing afterward instead of exiting.
16
+ * browsing afterward instead of exiting. Interactive routing and the browse
17
+ * loop live in `sessions-picker-factory.ts` (shared with the browser twin).
17
18
  */
18
19
  import chalk from 'chalk';
19
- import { itemPicker } from '../lib/picker.js';
20
- import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
21
20
  import { buildPreview } from './sessions-picker.js';
21
+ import { createSessionsPickerCommand } from './sessions-picker-factory.js';
22
22
  import { runComputerSessions, printComputerSessionRows, buildComputerSessionRows, matchesComputerSessionRow, formatRowActions, } from '../lib/computer/sessions-list.js';
23
23
  import { formatRelativeTime } from '../lib/session/relative-time.js';
24
- /** True when the interactive picker should open instead of the printed table:
25
- * a real TTY, no `--json`, and `--no-interactive` not set. */
26
- export function shouldOpenInteractiveComputerSessions(opts, isTTY) {
27
- return opts.interactive !== false && !opts.json && isTTY;
28
- }
29
- /**
30
- * Shared entry point for `agents computer sessions` and `agents sessions
31
- * --computer`. Mirrors `runBrowserSessionsCommand`'s interactive-routing
32
- * split so both call sites stay in lockstep.
33
- */
34
- export async function runComputerSessionsCommand(opts) {
35
- if (!shouldOpenInteractiveComputerSessions(opts, isInteractiveTerminal())) {
36
- if (opts.rows)
37
- printComputerSessionRows(opts.rows, { limit: opts.limit, json: opts.json });
38
- else
39
- runComputerSessions({ machine: opts.machine, limit: opts.limit, json: opts.json });
40
- return;
41
- }
42
- const rows = opts.rows ?? buildComputerSessionRows({ machine: opts.machine });
43
- if (rows.length === 0) {
44
- console.log(`No computer actions recorded${opts.machine ? ` for machine "${opts.machine}"` : ''}.`);
45
- return;
46
- }
47
- await browseComputerSessions(rows);
48
- }
49
24
  function rowLinkSummary(row) {
50
25
  if (row.linkStatus === 'linked' && row.linkedSession) {
51
26
  const s = row.linkedSession;
@@ -111,31 +86,33 @@ function printRunDetail(row) {
111
86
  console.log('(no driving actions recorded)');
112
87
  console.log('');
113
88
  }
114
- /** Run-level picker with an action-list preview. Loops until the user quits
115
- * (esc) so a session's whole run history can be browsed repeatedly in one
116
- * sitting — mirroring `browseAndOpen`'s loop, minus the artifact drill-down
117
- * a computer run has none of. */
118
- async function browseComputerSessions(rows) {
119
- for (;;) {
120
- let picked;
121
- try {
122
- picked = await itemPicker({
123
- message: 'Computer sessions:',
124
- items: rows,
125
- filter: (query) => (query.trim() ? rows.filter((r) => matchesComputerSessionRow(r, query)) : rows),
126
- labelFor: (row) => formatRowLabel(row),
127
- buildPreview: (row) => buildRowPreview(row),
128
- emptyMessage: 'No computer sessions match.',
129
- enterHint: 'view actions',
130
- });
131
- }
132
- catch (err) {
133
- if (isPromptCancelled(err))
134
- return;
135
- throw err;
136
- }
137
- if (!picked)
138
- return;
139
- printRunDetail(picked.item);
140
- }
89
+ const computerSessionsPicker = createSessionsPickerCommand({
90
+ runFlat: (opts) => {
91
+ if (opts.rows)
92
+ printComputerSessionRows(opts.rows, { limit: opts.limit, json: opts.json });
93
+ else
94
+ runComputerSessions({ machine: opts.machine, limit: opts.limit, json: opts.json });
95
+ },
96
+ buildRows: (opts) => opts.rows ?? buildComputerSessionRows({ machine: opts.machine }),
97
+ emptyMessage: (opts) => `No computer actions recorded${opts.machine ? ` for machine "${opts.machine}"` : ''}.`,
98
+ message: 'Computer sessions:',
99
+ matches: matchesComputerSessionRow,
100
+ labelFor: formatRowLabel,
101
+ buildPreview: buildRowPreview,
102
+ emptyFilterMessage: 'No computer sessions match.',
103
+ enterHint: 'view actions',
104
+ onOpen: printRunDetail,
105
+ });
106
+ /** True when the interactive picker should open instead of the printed table:
107
+ * a real TTY, no `--json`, and `--no-interactive` not set. */
108
+ export function shouldOpenInteractiveComputerSessions(opts, isTTY) {
109
+ return computerSessionsPicker.shouldOpen(opts, isTTY);
110
+ }
111
+ /**
112
+ * Shared entry point for `agents computer sessions` and `agents sessions
113
+ * --computer`. Mirrors `runBrowserSessionsCommand`'s interactive-routing
114
+ * split so both call sites stay in lockstep.
115
+ */
116
+ export async function runComputerSessionsCommand(opts) {
117
+ await computerSessionsPicker.run(opts);
141
118
  }
@@ -1,5 +1,5 @@
1
1
  import { Command } from 'commander';
2
- import { resolveHelperExec, resolveSocketPath } from '../lib/computer-rpc.js';
2
+ import { resolveHelperExec, resolveSocketPath } from '../lib/computer/computer-rpc.js';
3
3
  /**
4
4
  * Pure platform gate. The computer subsystem is macOS-only for LOCAL driving
5
5
  * (Accessibility / launchctl). It is NOT blocked off macOS when a remote daemon
@@ -3,8 +3,8 @@ import * as fs from 'fs';
3
3
  import * as os from 'os';
4
4
  import * as path from 'path';
5
5
  import { registerCommandGroups } from '../lib/help.js';
6
- import { openComputerClient, resolveHelperApp, resolveHelperExec, resolveSocketPath, resolveLogPath, resolvePolicyPath, resolvePeersPath, resolveTcpEndpoint, loadComputerAllowList, loadDefaultPeers, writeComputerPolicy, writeComputerPeers, } from '../lib/computer-rpc.js';
7
- import { setupRemoteHelper, startRemoteTunnel, stopRemoteHelper, hydrateRemoteEnvFromState, readRemoteState, resolveRemoteDevice, REMOTE_TASK_NAME, WIN_HELPER_EXE, } from '../lib/ssh-tunnel.js';
6
+ import { openComputerClient, resolveHelperApp, resolveHelperExec, resolveSocketPath, resolveLogPath, resolvePolicyPath, resolvePeersPath, resolveTcpEndpoint, loadComputerAllowList, loadDefaultPeers, writeComputerPolicy, writeComputerPeers, } from '../lib/computer/computer-rpc.js';
7
+ import { setupRemoteHelper, startRemoteTunnel, stopRemoteHelper, hydrateRemoteEnvFromState, readRemoteState, resolveRemoteDevice, REMOTE_TASK_NAME, WIN_HELPER_EXE, } from '../lib/computer/ssh-tunnel.js';
8
8
  import { sshExec } from '../lib/ssh-exec.js';
9
9
  import { encodePowershell } from '../lib/hosts/remote-cmd.js';
10
10
  import { registerActionCommands, withClient, unwrap, pickTarget, emitComputerAction } from './computer-actions.js';
@@ -25,7 +25,7 @@ import type { Command } from 'commander';
25
25
  import { type FleetInventory } from '../lib/devices/fleet-divergence.js';
26
26
  import { type DoctorFinding } from '../lib/devices/doctor-findings.js';
27
27
  import { type VersionResourceReport } from '../lib/doctor-diff.js';
28
- import { type DuplicateVersionHook } from '../lib/hooks.js';
28
+ import { type DuplicateVersionHook } from '../lib/hooks/install.js';
29
29
  import { type SyncStatusRow, type OrphanRow } from '../lib/drift.js';
30
30
  import { type FetchStatusMarker } from '../lib/auto-pull.js';
31
31
  export declare function wrapLine(prefix: string, text: string, width?: number): string[];
@@ -1,4 +1,4 @@
1
- import { IsolationBoundaryError } from '../lib/shims.js';
1
+ import { IsolationBoundaryError } from '../lib/installations/shims.js';
2
2
  import { explainIsolationBoundary } from '../lib/isolation-boundary-report.js';
3
3
  import { addHostOption } from '../lib/hosts/option.js';
4
4
  import { buildRemoteAgentsInvocation } from '../lib/hosts/remote-cmd.js';
@@ -23,7 +23,7 @@ import { getVersionHomePath, listInstalledVersions, } from '../lib/installations
23
23
  import { resolveAgentTargets, AgentSpecError } from '../lib/agent-spec/index.js';
24
24
  import { loadManifest, isStale } from '../lib/staleness/index.js';
25
25
  import { diffVersionResources, DOCTOR_ALL_KINDS, } from '../lib/doctor-diff.js';
26
- import { checkVersionHookWiring, inspectDuplicateVersionHooks, registerHooksToSettings, repairManagedHookRuntimeArtifacts } from '../lib/hooks.js';
26
+ import { checkVersionHookWiring, inspectDuplicateVersionHooks, registerHooksToSettings, repairManagedHookRuntimeArtifacts } from '../lib/hooks/install.js';
27
27
  import { isVersionIsolated } from '../lib/installations/versions.js';
28
28
  import { computeDrift, checkSyncStatus, countOrphans, computeSourceBehind } from '../lib/drift.js';
29
29
  import { readAuthHealthCache, summarizeHostAuth } from '../lib/auth-health.js';
@@ -1523,7 +1523,7 @@ export function registerDoctorCommand(program) {
1523
1523
  console.error(chalk.red('--adopt and --release are mutually exclusive; pass only one.'));
1524
1524
  process.exit(1);
1525
1525
  }
1526
- const { adoptShadowingLauncher, releaseAdoptedLauncher } = await import('../lib/shims.js');
1526
+ const { adoptShadowingLauncher, releaseAdoptedLauncher } = await import('../lib/installations/shims.js');
1527
1527
  const raw = (opts.adopt || opts.release);
1528
1528
  const agent = resolveAgentName(raw);
1529
1529
  if (!agent) {
@@ -449,7 +449,7 @@ export function registerFeedCommand(program) {
449
449
  setupWarnings.push(activityInstall.error);
450
450
  if (!hookInstall.error || !activityInstall.error) {
451
451
  const [{ iterHooksCapableVersions, parseHookManifest, registerHooksToSettings }, { getVersionHomePath }] = await Promise.all([
452
- import('../lib/hooks.js'),
452
+ import('../lib/hooks/install.js'),
453
453
  import('../lib/installations/versions.js'),
454
454
  ]);
455
455
  const manifest = parseHookManifest({ warn: false });
@@ -7,7 +7,7 @@ import { checkbox } from '@inquirer/prompts';
7
7
  import { AGENTS, resolveAgentName, formatAgentError, agentLabel, } from '../lib/agents.js';
8
8
  import { supports, capableAgents } from '../lib/capabilities.js';
9
9
  import { cloneRepo } from '../lib/git.js';
10
- import { discoverHooksFromRepo, installHooksCentrally, listCentralHooks, listInstalledHooksWithScope, getHookInfo, parseHookManifest, iterHooksCapableVersions, removeHookFromVersion, } from '../lib/hooks.js';
10
+ import { discoverHooksFromRepo, installHooksCentrally, listCentralHooks, listInstalledHooksWithScope, getHookInfo, parseHookManifest, iterHooksCapableVersions, removeHookFromVersion, } from '../lib/hooks/install.js';
11
11
  import { listInstalledVersions, getGlobalDefault, syncResourcesToVersion, promptAgentVersionSelection, getVersionHomePath, resolveInstalledAgentTargets, } from '../lib/installations/versions.js';
12
12
  import { recordVersionResources } from '../lib/state.js';
13
13
  import { isPromptCancelled, isInteractiveTerminal, parseCommaSeparatedList, printWithPager, requireInteractiveSelection, promptRemovalTargets, resolveAgentTargetsAutoInstalling, resolveListFilterOrExit, } from './utils.js';
@@ -21,7 +21,7 @@
21
21
  * management — same behavior as the first-run `agents setup` import flow.
22
22
  */
23
23
  import { withIsolationBoundary } from '../lib/isolation-boundary-report.js';
24
- import { assertIsolationBoundary, createVersionedAlias } from '../lib/shims.js';
24
+ import { assertIsolationBoundary, createVersionedAlias } from '../lib/installations/shims.js';
25
25
  import { markVersionIsolated } from '../lib/installations/versions.js';
26
26
  import chalk from 'chalk';
27
27
  import ora from 'ora';