@phnx-labs/agents-cli 1.22.114 → 1.22.115

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 (176) hide show
  1. package/CHANGELOG.md +110 -0
  2. package/README.md +22 -102
  3. package/dist/browser.js +0 -0
  4. package/dist/cli/command-registry.js +0 -7
  5. package/dist/commands/accounts.d.ts +3 -77
  6. package/dist/commands/accounts.js +13 -366
  7. package/dist/commands/audit.js +1 -1
  8. package/dist/commands/auth.js +0 -2
  9. package/dist/commands/browser.d.ts +53 -0
  10. package/dist/commands/browser.js +199 -9
  11. package/dist/commands/doctor.d.ts +2 -3
  12. package/dist/commands/doctor.js +0 -11
  13. package/dist/commands/events.d.ts +1 -3
  14. package/dist/commands/events.js +4 -7
  15. package/dist/commands/exec.d.ts +0 -1
  16. package/dist/commands/exec.js +2 -7
  17. package/dist/commands/feed-watch.js +52 -8
  18. package/dist/commands/focus.js +1 -1
  19. package/dist/commands/go.d.ts +0 -17
  20. package/dist/commands/go.js +2 -19
  21. package/dist/commands/logs.js +1 -1
  22. package/dist/commands/mcp.js +8 -83
  23. package/dist/commands/memory.js +4 -47
  24. package/dist/commands/message.js +4 -4
  25. package/dist/commands/plugins.js +1 -93
  26. package/dist/commands/repo.js +0 -44
  27. package/dist/commands/resume.js +4 -1
  28. package/dist/commands/secrets-passthrough.js +2 -2
  29. package/dist/commands/send.d.ts +4 -4
  30. package/dist/commands/send.js +6 -51
  31. package/dist/commands/sessions-backup-setup.js +1 -1
  32. package/dist/commands/sessions-resume.js +0 -1
  33. package/dist/commands/sessions-share.d.ts +5 -7
  34. package/dist/commands/sessions-share.js +98 -49
  35. package/dist/commands/sessions.js +1 -8
  36. package/dist/commands/setup-browser.js +18 -2
  37. package/dist/commands/setup-computer.js +20 -6
  38. package/dist/commands/setup-secrets.js +24 -4
  39. package/dist/commands/setup-terminal.d.ts +3 -0
  40. package/dist/commands/setup-terminal.js +22 -0
  41. package/dist/commands/setup.d.ts +1 -1
  42. package/dist/commands/setup.js +20 -10
  43. package/dist/commands/skills.js +0 -8
  44. package/dist/commands/ssh.d.ts +6 -0
  45. package/dist/commands/ssh.js +92 -233
  46. package/dist/commands/sync.js +14 -5
  47. package/dist/commands/teams.js +1 -1
  48. package/dist/commands/traces.js +1 -1
  49. package/dist/lib/accounts/add.d.ts +0 -5
  50. package/dist/lib/accounts/add.js +1 -7
  51. package/dist/lib/artifacts-client.d.ts +20 -0
  52. package/dist/lib/artifacts-client.js +46 -0
  53. package/dist/lib/auth-mint.js +2 -2
  54. package/dist/lib/browser/runtime-state.d.ts +55 -0
  55. package/dist/lib/browser/runtime-state.js +99 -18
  56. package/dist/lib/browser/service.js +21 -1
  57. package/dist/lib/cli-resources.js +3 -1
  58. package/dist/lib/cloud/dispatch.js +1 -1
  59. package/dist/lib/cloudflare/creds.d.ts +10 -0
  60. package/dist/lib/cloudflare/creds.js +46 -0
  61. package/dist/lib/cloudflare/provision.d.ts +35 -0
  62. package/dist/lib/cloudflare/provision.js +144 -0
  63. package/dist/lib/computer/sessions-list.d.ts +55 -0
  64. package/dist/lib/computer/sessions-list.js +168 -1
  65. package/dist/lib/daemon/daemon.js +8 -1
  66. package/dist/lib/daemon/feed-stream-service.d.ts +23 -0
  67. package/dist/lib/daemon/feed-stream-service.js +40 -0
  68. package/dist/lib/daemon-services.d.ts +1 -1
  69. package/dist/lib/daemon-services.js +5 -0
  70. package/dist/lib/devices/connect.d.ts +49 -5
  71. package/dist/lib/devices/connect.js +169 -21
  72. package/dist/lib/feed/envelope.d.ts +79 -0
  73. package/dist/lib/feed/envelope.js +23 -0
  74. package/dist/lib/feed/events.d.ts +6 -0
  75. package/dist/lib/feed/events.js +8 -0
  76. package/dist/lib/feed/hub-server.d.ts +86 -0
  77. package/dist/lib/feed/hub-server.js +334 -0
  78. package/dist/lib/feed/hub.d.ts +95 -0
  79. package/dist/lib/feed/hub.js +255 -0
  80. package/dist/lib/feed/tool-activity.d.ts +108 -0
  81. package/dist/lib/feed/tool-activity.js +313 -0
  82. package/dist/lib/feed/tools.d.ts +198 -0
  83. package/dist/lib/feed/tools.js +265 -0
  84. package/dist/lib/feed/watch.d.ts +50 -50
  85. package/dist/lib/feed/watch.js +147 -16
  86. package/dist/lib/format.d.ts +1 -1
  87. package/dist/lib/format.js +1 -1
  88. package/dist/lib/git.d.ts +0 -16
  89. package/dist/lib/git.js +0 -58
  90. package/dist/lib/helper-versions.js +1 -1
  91. package/dist/lib/hosts/remote-cmd.d.ts +51 -1
  92. package/dist/lib/hosts/remote-cmd.js +125 -8
  93. package/dist/lib/hosts/remote-cmd.test-fixture.d.ts +2 -0
  94. package/dist/lib/hosts/remote-cmd.test-fixture.js +22 -0
  95. package/dist/lib/mcp.js +17 -11
  96. package/dist/lib/probe.d.ts +4 -1
  97. package/dist/lib/probe.js +5 -2
  98. package/dist/lib/pwsh.d.ts +33 -0
  99. package/dist/lib/pwsh.js +56 -0
  100. package/dist/lib/redact.d.ts +8 -0
  101. package/dist/lib/redact.js +11 -0
  102. package/dist/lib/refresh.d.ts +6 -2
  103. package/dist/lib/refresh.js +92 -72
  104. package/dist/lib/secrets-cli.d.ts +11 -0
  105. package/dist/lib/secrets-cli.js +30 -0
  106. package/dist/lib/secrets-client.js +3 -2
  107. package/dist/lib/session/detached.d.ts +7 -0
  108. package/dist/lib/session/detached.js +29 -0
  109. package/dist/lib/session/remote/peer-stream.d.ts +24 -2
  110. package/dist/lib/session/remote/peer-stream.js +33 -6
  111. package/dist/lib/session/sync/backend.d.ts +3 -3
  112. package/dist/lib/session/sync/backend.js +3 -3
  113. package/dist/lib/session/sync/provision.d.ts +1 -1
  114. package/dist/lib/session/sync/provision.js +2 -2
  115. package/dist/lib/sessions-client.js +0 -3
  116. package/dist/lib/setup-tool-install.d.ts +3 -0
  117. package/dist/lib/setup-tool-install.js +26 -0
  118. package/dist/lib/setup-tool-status.d.ts +22 -0
  119. package/dist/lib/setup-tool-status.js +215 -0
  120. package/dist/lib/share-runtime.d.ts +11 -0
  121. package/dist/lib/share-runtime.js +63 -0
  122. package/dist/lib/smart-launch.d.ts +1 -5
  123. package/dist/lib/smart-launch.js +3 -11
  124. package/dist/lib/ssh-exec.d.ts +44 -0
  125. package/dist/lib/ssh-exec.js +119 -0
  126. package/dist/lib/startup/command-registry.d.ts +6 -4
  127. package/dist/lib/startup/command-registry.js +10 -7
  128. package/dist/lib/state.js +2 -2
  129. package/dist/lib/storage/selection.d.ts +2 -2
  130. package/dist/lib/storage/selection.js +2 -2
  131. package/dist/lib/sync-umbrella.d.ts +5 -0
  132. package/dist/lib/sync-umbrella.js +18 -10
  133. package/dist/lib/traces/backend.d.ts +1 -2
  134. package/dist/lib/traces/backend.js +1 -2
  135. package/dist/lib/traces/provision.d.ts +1 -1
  136. package/dist/lib/traces/provision.js +2 -2
  137. package/dist/lib/types.d.ts +8 -6
  138. package/package.json +2 -3
  139. package/dist/commands/artifacts-setup.d.ts +0 -53
  140. package/dist/commands/artifacts-setup.js +0 -161
  141. package/dist/commands/artifacts.d.ts +0 -18
  142. package/dist/commands/artifacts.js +0 -58
  143. package/dist/commands/attach.d.ts +0 -12
  144. package/dist/commands/attach.js +0 -86
  145. package/dist/commands/auth-mint.d.ts +0 -12
  146. package/dist/commands/auth-mint.js +0 -108
  147. package/dist/commands/reconnect.d.ts +0 -46
  148. package/dist/commands/reconnect.js +0 -115
  149. package/dist/commands/share.d.ts +0 -293
  150. package/dist/commands/share.js +0 -1424
  151. package/dist/lib/share/analytics.d.ts +0 -13
  152. package/dist/lib/share/analytics.js +0 -45
  153. package/dist/lib/share/backend.d.ts +0 -120
  154. package/dist/lib/share/backend.js +0 -176
  155. package/dist/lib/share/capture.d.ts +0 -31
  156. package/dist/lib/share/capture.js +0 -174
  157. package/dist/lib/share/config.d.ts +0 -72
  158. package/dist/lib/share/config.js +0 -211
  159. package/dist/lib/share/delete.d.ts +0 -123
  160. package/dist/lib/share/delete.js +0 -173
  161. package/dist/lib/share/html.d.ts +0 -20
  162. package/dist/lib/share/html.js +0 -88
  163. package/dist/lib/share/http-error.d.ts +0 -53
  164. package/dist/lib/share/http-error.js +0 -65
  165. package/dist/lib/share/og.d.ts +0 -26
  166. package/dist/lib/share/og.js +0 -84
  167. package/dist/lib/share/provision.d.ts +0 -127
  168. package/dist/lib/share/provision.js +0 -285
  169. package/dist/lib/share/publish.d.ts +0 -379
  170. package/dist/lib/share/publish.js +0 -818
  171. package/dist/lib/share/worker-template.d.ts +0 -27
  172. package/dist/lib/share/worker-template.js +0 -2424
  173. package/dist/lib/storage/index.d.ts +0 -14
  174. package/dist/lib/storage/index.js +0 -14
  175. package/dist/lib/storage/visibility.d.ts +0 -82
  176. package/dist/lib/storage/visibility.js +0 -99
@@ -1,27 +1,29 @@
1
1
  /**
2
2
  * `agents sessions share <id>` — publish one session transcript as a link.
3
3
  *
4
- * Composes three pieces that already existed but were never wired together:
5
- * `renderSessionMarkdownDocument()` (redacted transcript),
6
- * `renderSessionHtmlDocument()` (self-contained branded page), and
7
- * `publishFile()` (the R2-backed share Worker). Before this, sharing a session
8
- * meant three commands and a detour through the external artifacts-cli.
4
+ * Renders the session locally — `renderSessionMarkdownDocument()` (redacted
5
+ * transcript) and `renderSessionHtmlDocument()` (self-contained branded page) —
6
+ * then publishes it through the standalone `artifacts` CLI (`artifacts share`),
7
+ * the single home for artifact sharing since PHNX-3992. agents-cli holds no
8
+ * share engine of its own; this command contributes the session-specific
9
+ * rendering and redaction, and forwards the finished page to `artifacts share`.
9
10
  *
10
- * Unlisted by default, unlike `agents artifacts share`. A transcript carries file
11
- * paths, command output, error text, and whatever a tool printed — strictly more
12
- * than a plan does — so it does not belong in the public `/<user>` gallery unless
13
- * the operator asks for it with `--public`. The URL itself stays world-readable:
14
- * unlisted is a capability URL, not a secret.
11
+ * Unlisted by default, unlike `artifacts share` (public by default). A transcript
12
+ * carries file paths, command output, error text, and whatever a tool printed —
13
+ * strictly more than a plan does — so it does not belong in the public `/<user>`
14
+ * gallery unless the operator asks for it with `--public`. The URL itself stays
15
+ * world-readable: unlisted is a capability URL, not a secret.
15
16
  */
16
17
  import * as fs from 'fs';
17
18
  import * as os from 'os';
18
19
  import * as path from 'path';
20
+ import { spawnSync } from 'node:child_process';
19
21
  import chalk from 'chalk';
20
22
  import { setHelpSections } from '../lib/help.js';
21
- import { knownSecretValuesFromEnv } from '../lib/redact.js';
23
+ import { knownSecretValuesFromEnv, redactEmails } from '../lib/redact.js';
22
24
  import { discoverSessions } from '../lib/session/discover.js';
23
25
  import { renderSessionHtmlDocument } from '../lib/session/share-html.js';
24
- import { publishFile, redactEmails } from '../lib/share/publish.js';
26
+ import { resolveArtifactsBin, invocation, ArtifactsClientError } from '../lib/artifacts-client.js';
25
27
  import { renderSessionMarkdownDocument } from './sessions-render.js';
26
28
  import { selectSessions } from './sessions-export.js';
27
29
  import { parseAgentFilter } from './sessions.js';
@@ -35,23 +37,33 @@ export function defaultSessionSlug(session) {
35
37
  return `session-${session.shortId || session.id}`;
36
38
  }
37
39
  /**
38
- * Map the command's flags onto {@link PublishOptions}.
40
+ * Build the `artifacts share` argv for a session publish.
39
41
  *
40
- * Extracted so the defaults are testable directly. `unlisted` in particular is the
41
- * security-relevant one — it inverts the parent command's public-by-default
42
- * behavior, and a test that re-implements this mapping would still pass with it
43
- * flipped.
42
+ * Extracted so the mapping is testable directly. The security-relevant default
43
+ * is `--visibility unlisted` — it inverts `artifacts share`'s public default, and
44
+ * a test that re-implements this mapping would still pass with it flipped.
44
45
  */
45
- export function buildSharePublishOptions(session, options) {
46
- return {
47
- slug: options.slug ?? defaultSessionSlug(session),
48
- unlisted: options.public !== true,
49
- expire: options.expire,
50
- force: options.force === true,
51
- cover: options.cover !== false,
52
- label: options.label,
53
- meta: { kind: 'session' },
54
- };
46
+ export function buildArtifactsShareArgs(session, options, file) {
47
+ const args = [
48
+ 'share',
49
+ file,
50
+ '--slug',
51
+ options.slug ?? defaultSessionSlug(session),
52
+ '--visibility',
53
+ options.public === true ? 'public' : 'unlisted',
54
+ '--meta',
55
+ 'kind=session',
56
+ '--json',
57
+ ];
58
+ if (options.expire)
59
+ args.push('--expire', options.expire);
60
+ if (options.force === true)
61
+ args.push('--force');
62
+ if (options.cover === false)
63
+ args.push('--no-cover');
64
+ if (options.label)
65
+ args.push('--label', options.label);
66
+ return args;
55
67
  }
56
68
  export function registerSessionsShareCommand(sessionsCmd) {
57
69
  const cmd = sessionsCmd
@@ -59,7 +71,7 @@ export function registerSessionsShareCommand(sessionsCmd) {
59
71
  .description('Publish one session as a redacted, self-contained web page and print the link.')
60
72
  .option('--public', 'List the page in your public share gallery (default: unlisted capability URL)')
61
73
  .option('--slug <slug>', 'URL slug under your namespace (default: session-<shortId>)')
62
- .option('--label <text>', 'Display title in the gallery and `agents artifacts share list`')
74
+ .option('--label <text>', 'Display title in the gallery and `artifacts share list`')
63
75
  .option('--expire <spec>', 'Auto-expire window: 30d, 12h, a date, or never (default: 30d)')
64
76
  .option('--reasoning <mode>', 'Reasoning visibility: omit, fold, or include', 'omit')
65
77
  .option('--force', 'Publish despite the sensitive-content scan flagging the transcript')
@@ -76,22 +88,23 @@ agents sessions share a1b2c3d4 --reasoning fold
76
88
 
77
89
  # A link that does not decay
78
90
  agents sessions share a1b2c3d4 --expire never`,
79
- notes: `Requires a share endpoint: 'agents artifacts share status' shows it, 'agents artifacts setup'
80
- provisions one, 'agents artifacts share join <baseUrl>' uses an existing one.
91
+ notes: `Publishes through the standalone artifacts CLI — install it with
92
+ 'npm i -g @phnx-labs/artifacts-cli'. Sign in with
93
+ 'artifacts auth login' for the managed endpoint (zero Cloudflare setup), or configure
94
+ your own bucket with 'artifacts share setup' / 'artifacts share join <baseUrl>'.
81
95
 
82
96
  Unlisted by default — the URL is world-readable but the page stays out of your public
83
- gallery and out of 'agents artifacts share list'. Pass --public to list it.
97
+ gallery and out of 'artifacts share list'. Pass --public to list it.
84
98
 
85
- Secrets are redacted and the same pre-publish scan as 'agents artifacts share' runs on the
86
- page, so a transcript carrying emails or credential-shaped strings is refused
87
- unless you pass --force. --no-redact (the sessions-level flag) disables redaction
88
- and is a bad idea for anything you publish.
99
+ Secrets are redacted and 'artifacts share' runs its own pre-publish scan on the page,
100
+ so a transcript carrying emails or credential-shaped strings is refused unless you pass
101
+ --force. --no-redact (the sessions-level flag) disables redaction and is a bad idea for
102
+ anything you publish.
89
103
 
90
104
  Re-running with the same session updates the same URL and keeps the prior version
91
- as a revision ('agents artifacts share revisions <slug>').
105
+ as a revision ('artifacts share revisions <slug>').
92
106
 
93
- Manage published sessions with 'agents artifacts share list' and
94
- 'agents artifacts share delete <slug>'.`,
107
+ Manage published sessions with 'artifacts share list' and 'artifacts share delete <slug>'.`,
95
108
  });
96
109
  cmd.action(async (selector, options, command) => {
97
110
  const globals = command.optsWithGlobals();
@@ -125,25 +138,60 @@ Manage published sessions with 'agents artifacts share list' and
125
138
  });
126
139
  // Emails on top of what the renderer masks. Almost every real transcript
127
140
  // carries a few — git author addresses, `gh api user`, a pasted log — and
128
- // publishToEndpoint refuses a body containing any (RUSH-2428). Masking them
129
- // means the published page genuinely does not carry them; the alternative,
130
- // telling people to pass --force, would train everyone to bypass the gate
131
- // that also catches real credentials.
141
+ // `artifacts share` refuses a body containing any (its own scan). Masking
142
+ // them means the published page genuinely does not carry them; the
143
+ // alternative, telling people to pass --force, trains everyone to bypass the
144
+ // gate that also catches real credentials.
132
145
  //
133
146
  // Applied to the RENDERED PAGE, not the Markdown, so the text that is masked
134
- // is exactly the text `scanShareContent` will scan. Markdown escaping stands
147
+ // is exactly the text the publish scan will see. Markdown escaping stands
135
148
  // between the two: `foo\@example.com` hides from the pattern in the Markdown
136
149
  // and reappears as a live address once marked drops the backslash.
137
150
  const page = renderSessionHtmlDocument(session, markdown, { redacted: redact });
138
151
  const html = redact ? redactEmails(page) : page;
139
- // A real file on disk is what publishFile() takes, and the OG capturer opens
140
- // it in a browser. 0600 + a per-run directory keeps the intermediate off a
141
- // world-readable /tmp path while it exists.
152
+ // A real file on disk is what `artifacts share` takes, and its OG capturer
153
+ // opens it in a browser. 0600 + a per-run directory keeps the intermediate
154
+ // off a world-readable /tmp path while it exists.
142
155
  const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'agents-session-share-'));
143
156
  const file = path.join(dir, `${defaultSessionSlug(session)}.html`);
144
157
  try {
145
158
  fs.writeFileSync(file, html, { mode: 0o600 });
146
- const result = await publishFile(file, buildSharePublishOptions(session, options));
159
+ let bin;
160
+ try {
161
+ bin = resolveArtifactsBin();
162
+ }
163
+ catch (err) {
164
+ if (err instanceof ArtifactsClientError) {
165
+ process.stderr.write(chalk.red(err.message + '\n'));
166
+ process.exitCode = 1;
167
+ return;
168
+ }
169
+ throw err;
170
+ }
171
+ const { command: execCommand, prefix } = invocation(bin);
172
+ const args = [...prefix, ...buildArtifactsShareArgs(session, options, file)];
173
+ const proc = spawnSync(execCommand, args, { encoding: 'utf-8' });
174
+ if (proc.error)
175
+ throw proc.error;
176
+ if (proc.status !== 0) {
177
+ process.stderr.write(proc.stderr || chalk.red(`artifacts share exited ${proc.status ?? 'with no status'}\n`));
178
+ process.exitCode = proc.status ?? 1;
179
+ return;
180
+ }
181
+ let result;
182
+ try {
183
+ result = JSON.parse(proc.stdout);
184
+ }
185
+ catch {
186
+ // A 0 exit with non-JSON stdout (a version-skewed `artifacts` that
187
+ // printed a banner before the JSON, or one predating `--json` here) must
188
+ // fail loud with the bytes it actually returned, not an uncaught parse
189
+ // stack trace — mirroring `secrets-client.ts`'s `parseResponse`.
190
+ const preview = proc.stdout.trim().slice(0, 200);
191
+ process.stderr.write(chalk.red(`artifacts share returned a non-JSON response${preview ? `: ${preview}` : ' (empty output)'}\n`));
192
+ process.exitCode = 1;
193
+ return;
194
+ }
147
195
  if (globals.json) {
148
196
  process.stdout.write(JSON.stringify({
149
197
  session: session.id,
@@ -154,9 +202,10 @@ Manage published sessions with 'agents artifacts share list' and
154
202
  return;
155
203
  }
156
204
  process.stdout.write(`${result.url}\n`);
157
- const bits = [result.unlisted ? 'unlisted' : 'public', redact ? 'redacted' : chalk.red('NOT redacted')];
205
+ const visibility = result.visibility ?? (result.unlisted ? 'unlisted' : options.public ? 'public' : 'unlisted');
206
+ const bits = [visibility, redact ? 'redacted' : chalk.red('NOT redacted')];
158
207
  if (result.expiresAt)
159
- bits.push(`expires ${result.expiresAt.slice(0, 10)}`);
208
+ bits.push(`expires ${String(result.expiresAt).slice(0, 10)}`);
160
209
  process.stderr.write(chalk.dim(`${bits.join(' · ')}\n`));
161
210
  }
162
211
  finally {
@@ -64,12 +64,9 @@ import { registerSessionsResumeCommand } from './sessions-resume.js';
64
64
  import { registerSessionsForkCommand } from './fork.js';
65
65
  import { registerSessionsBookmarkCommand } from './sessions-bookmark.js';
66
66
  import { isBookmarked, listBookmarks } from '../lib/session/bookmarks.js';
67
- import { registerGoCommand } from './go.js';
68
67
  import { registerFocusCommand } from './focus.js';
69
- import { registerReconnectCommand } from './reconnect.js';
70
68
  import { registerDetachCommand } from './detach.js';
71
69
  import { registerSessionsStopCommand } from './sessions-stop.js';
72
- import { registerAttachCommand } from './attach.js';
73
70
  import { registerSessionsInjectCommand } from './sessions-inject.js';
74
71
  import { registerSessionsExportCommand } from './sessions-export.js';
75
72
  import { registerSessionsRenderCommand } from './sessions-render.js';
@@ -3963,7 +3960,7 @@ async function resumeOnOwnerIfRemote(session) {
3963
3960
  export async function resumeSessionInPlace(session) {
3964
3961
  // This function is the LOCAL takeover, and every caller is responsible for
3965
3962
  // routing a peer-owned session before it gets here (the picker above,
3966
- // `agents sessions resume`, `sessions attach`). Reaching it with one anyway means a
3963
+ // `agents sessions resume`). Reaching it with one anyway means a
3967
3964
  // caller skipped that step, so refuse rather than start the harness against
3968
3965
  // state this box does not have — the `fs.existsSync(session.cwd)` fallback
3969
3966
  // just below is exactly how that used to pass unnoticed, quietly swapping in
@@ -5317,7 +5314,6 @@ export function registerSessionsCommands(program) {
5317
5314
  resume <id> --attach-only attach only; never fork a copy
5318
5315
  resume multi-select history -> open tabs
5319
5316
  detach <id> the other direction: interactive -> headless
5320
- (retired, still working for one release: sessions attach, sessions go, reconnect)
5321
5317
  - The interactive listing and every live-status flag fold in your other online machines automatically (live over SSH, no sync) — each row is labelled by host, this machine first. Use --local to skip the fan-out; single-id lookups stay local.
5322
5318
  - --all is not a device flag: it widens historical directory and time filters. Fleet collection is already the default. A status flag (--working/--idle/--waiting/--orphan/--crashed/--closed/--abandoned/--queued/--unknown) implies --active; combine status flags for a union.
5323
5319
  - --version <version> requires --agent and is equivalent to --agent <agent@version>.
@@ -5408,12 +5404,9 @@ export function registerSessionsCommands(program) {
5408
5404
  registerSessionsResumeCommand(sessionsCmd);
5409
5405
  registerSessionsForkCommand(sessionsCmd);
5410
5406
  registerSessionsBookmarkCommand(sessionsCmd);
5411
- registerGoCommand(sessionsCmd);
5412
5407
  registerFocusCommand(sessionsCmd);
5413
- registerReconnectCommand(sessionsCmd);
5414
5408
  registerDetachCommand(sessionsCmd);
5415
5409
  registerSessionsStopCommand(sessionsCmd);
5416
- registerAttachCommand(sessionsCmd);
5417
5410
  registerSessionsInjectCommand(sessionsCmd);
5418
5411
  registerSessionsExportCommand(sessionsCmd);
5419
5412
  registerSessionsRenderCommand(sessionsCmd);
@@ -7,6 +7,7 @@
7
7
  * Idempotent: re-running shows the current default profile and offers to change
8
8
  * the pinned browser or re-point the device default.
9
9
  */
10
+ import { openSetupTerminal } from './setup-terminal.js';
10
11
  import chalk from 'chalk';
11
12
  import { setConfigValue } from '../lib/device-config.js';
12
13
  import { listInstalledBrowsers } from '../lib/browser/chrome.js';
@@ -14,6 +15,8 @@ import { DEFAULT_BROWSER_PROFILE_NAME, getAutoDetectedProfile, createProfile, fi
14
15
  import { DEFAULT_VIEWPORT } from '../lib/browser/devices.js';
15
16
  import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
16
17
  import { defaultBrowserChoice } from './setup-preferences.js';
18
+ import { installSetupTool } from '../lib/setup-tool-install.js';
19
+ import { refreshToolSetup } from '../lib/setup-tool-status.js';
17
20
  const INSTALL_HINT = 'Install one of: Google Chrome, Brave, Microsoft Edge, Chromium, Comet, or Arc, then re-run `agents setup browser`.\n' +
18
21
  '(This wizard picks a Chromium-family browser. An installed Firefox is not auto-picked here; its profiles are ' +
19
22
  'discovered from profiles.ini and selected with `agents browser use firefox-<name>`. Safari is not supported.)';
@@ -116,9 +119,22 @@ export function registerSetupBrowserCommand(setupCmd) {
116
119
  setupCmd
117
120
  .command('browser')
118
121
  .description('Set up `agents browser` — detect an installed browser and create the default profile.')
119
- .action(async () => {
122
+ .option('--install-only', 'Install the standalone Browser CLI without changing profiles or starting a browser')
123
+ .option('--terminal [backend]', 'Open interactive setup in a detected or selected terminal')
124
+ .action(async (options) => {
120
125
  try {
121
- await runBrowserWizard();
126
+ if (options.terminal !== undefined) {
127
+ await openSetupTerminal('browser', options.terminal, options.installOnly);
128
+ return;
129
+ }
130
+ if (options.installOnly) {
131
+ if (!(await installSetupTool('browser')))
132
+ process.exitCode = 1;
133
+ return;
134
+ }
135
+ if (!(await runBrowserWizard()))
136
+ process.exitCode = 1;
137
+ await refreshToolSetup('browser');
122
138
  }
123
139
  catch (err) {
124
140
  if (isPromptCancelled(err)) {
@@ -8,12 +8,15 @@
8
8
  * this wizard chains them with the permission hand-holding a fresh machine needs.
9
9
  * Idempotent: re-running re-installs the current helper and re-checks trust.
10
10
  */
11
+ import { openSetupTerminal } from './setup-terminal.js';
11
12
  import os from 'node:os';
12
- import { execFileSync, spawnSync } from 'node:child_process';
13
+ import { execFileSync } from 'node:child_process';
13
14
  import chalk from 'chalk';
14
15
  import { installComputerHelperMacLocal, activateComputerHelperMacLocal, probeComputerTrust, } from './computer.js';
15
16
  import { resolveComputerBin, ComputerClientError } from '../lib/computer-client.js';
16
17
  import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
18
+ import { installSetupTool } from '../lib/setup-tool-install.js';
19
+ import { refreshToolSetup } from '../lib/setup-tool-status.js';
17
20
  const ACCESSIBILITY_PANE = 'x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility';
18
21
  const SCREEN_PANE = 'x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture';
19
22
  const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
@@ -43,9 +46,7 @@ export async function runComputerWizard() {
43
46
  catch (error) {
44
47
  if (!(error instanceof ComputerClientError) || error.code !== 'COMPUTER_BIN_MISSING')
45
48
  throw error;
46
- console.log('Installing @phnx-labs/computer-cli@0.1.2…');
47
- const installed = spawnSync('npm', ['install', '-g', '@phnx-labs/computer-cli@0.1.2'], { stdio: 'inherit' });
48
- if (installed.status !== 0)
49
+ if (!(await installSetupTool('computer')))
49
50
  return false;
50
51
  try {
51
52
  resolveComputerBin();
@@ -136,9 +137,22 @@ export function registerSetupComputerCommand(setupCmd) {
136
137
  setupCmd
137
138
  .command('computer')
138
139
  .description('Set up `agents computer` (macOS) — install the signed helper and grant control permissions.')
139
- .action(async () => {
140
+ .option('--install-only', 'Install the standalone Computer CLI without starting the helper or changing permissions')
141
+ .option('--terminal [backend]', 'Open interactive setup in a detected or selected terminal')
142
+ .action(async (options) => {
140
143
  try {
141
- await runComputerWizard();
144
+ if (options.terminal !== undefined) {
145
+ await openSetupTerminal('computer', options.terminal, options.installOnly);
146
+ return;
147
+ }
148
+ if (options.installOnly) {
149
+ if (!(await installSetupTool('computer')))
150
+ process.exitCode = 1;
151
+ return;
152
+ }
153
+ if (!(await runComputerWizard()))
154
+ process.exitCode = 1;
155
+ await refreshToolSetup('computer');
142
156
  }
143
157
  catch (err) {
144
158
  if (isPromptCancelled(err)) {
@@ -7,6 +7,7 @@
7
7
  * `agents clis install secrets` (system `clis/secrets.yaml`) then a pinned
8
8
  * `npm i -g`. Users do not set extra env vars.
9
9
  */
10
+ import { openSetupTerminal } from './setup-terminal.js';
10
11
  import chalk from 'chalk';
11
12
  import * as fs from 'fs';
12
13
  import * as path from 'path';
@@ -14,7 +15,12 @@ import { spawnSync } from 'node:child_process';
14
15
  import { getHistoryDir } from '../lib/state.js';
15
16
  import { resolveSecretsBin, invocation, SecretsClientError, _resetSecretsClientForTest } from '../lib/secrets-client.js';
16
17
  import { installCli, resolveCliManifest } from '../lib/cli-resources.js';
17
- export const SECRETS_CLI_PACKAGE = '@phnx-labs/secrets-cli@0.1.4';
18
+ import { SECRETS_CLI_SPEC } from '../lib/secrets-cli.js';
19
+ import { refreshToolSetup } from '../lib/setup-tool-status.js';
20
+ import { execFileShellSpec } from '../lib/platform/exec.js';
21
+ // Re-exported for back-compat; the canonical pin lives in `secrets-cli.ts` so a
22
+ // version bump touches ONE place (PHNX-3989 consolidation).
23
+ export const SECRETS_CLI_PACKAGE = SECRETS_CLI_SPEC;
18
24
  export const INSTALL_HINT = `agents clis install secrets # or: npm i -g ${SECRETS_CLI_PACKAGE}`;
19
25
  export function setupSecretsPrefsPath() {
20
26
  return path.join(getHistoryDir(), 'setup', 'secrets.json');
@@ -56,7 +62,8 @@ export function installSecretsCli() {
56
62
  console.error(chalk.gray(result.error));
57
63
  }
58
64
  console.log(chalk.gray(`Installing ${SECRETS_CLI_PACKAGE}…`));
59
- const r = spawnSync('npm', ['install', '-g', SECRETS_CLI_PACKAGE], { stdio: 'inherit' });
65
+ const npm = execFileShellSpec('npm', ['install', '-g', SECRETS_CLI_PACKAGE]);
66
+ const r = spawnSync(npm.command, npm.args, { stdio: 'inherit', shell: npm.shell });
60
67
  _resetSecretsClientForTest();
61
68
  if (r.error) {
62
69
  console.error(chalk.red(`npm install failed: ${r.error.message}`));
@@ -93,8 +100,21 @@ export function registerSetupSecretsCommand(setupCmd) {
93
100
  setupCmd
94
101
  .command('secrets')
95
102
  .description('Install the standalone `secrets` CLI if missing, then run its `secrets migrate` onboarding.')
96
- .action(async () => {
97
- if (!(await runSecretsSetupWizard()))
103
+ .option('--install-only', 'Install the standalone Secrets CLI without migrating or unlocking secrets')
104
+ .option('--terminal [backend]', 'Open interactive setup in a detected or selected terminal')
105
+ .action(async (options) => {
106
+ if (options.terminal !== undefined) {
107
+ try {
108
+ await openSetupTerminal('secrets', options.terminal, options.installOnly);
109
+ }
110
+ catch (error) {
111
+ console.error(error instanceof Error ? error.message : String(error));
112
+ process.exitCode = 1;
113
+ }
114
+ return;
115
+ }
116
+ if (!(options.installOnly ? installSecretsCli() : await runSecretsSetupWizard()))
98
117
  process.exitCode = 1;
118
+ await refreshToolSetup('secrets');
99
119
  });
100
120
  }
@@ -0,0 +1,3 @@
1
+ import type { SetupTool } from '../lib/setup-tool-status.js';
2
+ /** The GUI starts a terminal; the terminal child owns wizard completion. */
3
+ export declare function openSetupTerminal(tool: SetupTool, terminal: boolean | string, installOnly?: boolean): Promise<void>;
@@ -0,0 +1,22 @@
1
+ import os from 'node:os';
2
+ import { currentContext, openRunInTerminal, parseTerminalFlag } from '../lib/terminal/index.js';
3
+ /** The GUI starts a terminal; the terminal child owns wizard completion. */
4
+ export async function openSetupTerminal(tool, terminal, installOnly = false) {
5
+ if (installOnly)
6
+ throw new Error('--terminal and --install-only are separate setup modes.');
7
+ const parsed = parseTerminalFlag(terminal);
8
+ if (parsed.error)
9
+ throw new Error(parsed.error);
10
+ const { readActiveSessionsCache } = await import('../lib/session/session-cache.js');
11
+ const sessions = parsed.backend ? [] : (readActiveSessionsCache('local')?.sessions ?? []).map(({ host, lastActivityMs, startedAtMs }) => ({ host, lastActivityMs, startedAtMs }));
12
+ const result = await openRunInTerminal({
13
+ argv: ['setup', tool],
14
+ forced: parsed.backend,
15
+ cwd: os.homedir(),
16
+ sessions,
17
+ ctx: currentContext(),
18
+ });
19
+ if (!result.ok)
20
+ throw new Error(result.error ?? 'Could not open a terminal for setup.');
21
+ console.log(result.description ?? 'Setup opened in a terminal.');
22
+ }
@@ -31,7 +31,7 @@ export declare function ensureInitialized(program: Command): Promise<void>;
31
31
  * wizard. Never throws — a cancel or an optional wizard's error just skips the
32
32
  * rest and lets core setup complete.
33
33
  */
34
- type SetupPhase = 'browser' | 'computer' | 'share' | 'secrets' | 'accounts' | 'fleet' | 'watchdog' | 'preferences';
34
+ type SetupPhase = 'browser' | 'computer' | 'secrets' | 'accounts' | 'fleet' | 'watchdog' | 'preferences';
35
35
  type SetupStatusState = 'ready' | 'missing' | 'n/a';
36
36
  interface SetupStatusRow {
37
37
  phase: 'core' | SetupPhase;
@@ -20,7 +20,6 @@ import { ensureShimCurrent, switchHomeFileSymlinks, isShimsInPath, addShimsToPat
20
20
  import { setHelpSections } from '../lib/help.js';
21
21
  import { registerSetupBrowserCommand, runBrowserWizard } from './setup-browser.js';
22
22
  import { registerSetupComputerCommand, runComputerWizard } from './setup-computer.js';
23
- import { runShareWizard } from './artifacts-setup.js';
24
23
  import { registerSetupMineCommand } from './setup-mine.js';
25
24
  import { registerSetupSecretsCommand } from './setup-secrets.js';
26
25
  import { registerSetupFleetCommand } from './setup-fleet.js';
@@ -34,10 +33,10 @@ import { runPreferencesStep } from './setup-preferences.js';
34
33
  import { getConfiguredDefaultProfileName, getProfile, getAutoDetectedProfile, isProfileLaunchableHere } from '../lib/browser/profiles.js';
35
34
  import { listInstalledBrowsers } from '../lib/browser/chrome.js';
36
35
  import { probeComputerTrust } from './computer.js';
37
- import { readShareConfig } from '../lib/share/config.js';
38
36
  import { loadDevices } from '../lib/devices/registry.js';
39
37
  import { getConfigValue } from '../lib/device-config.js';
40
38
  import { setupSecretsPrefsPath } from './setup-secrets.js';
39
+ import { SETUP_TOOLS, getCachedToolSetup, refreshToolSetup } from '../lib/setup-tool-status.js';
41
40
  const HOME = os.homedir();
42
41
  /**
43
42
  * Import an existing unmanaged agent installation into agents-cli.
@@ -280,7 +279,6 @@ export async function getSetupStatus() {
280
279
  const coreReady = isGitRepo(getAgentsDir());
281
280
  const secretsReady = fs.existsSync(setupSecretsPrefsPath());
282
281
  const minted = hasMintedSetupToken();
283
- const shareConfig = readShareConfig();
284
282
  const watchdogEnabled = getConfigValue('watchdog.enabled').value === true;
285
283
  const interactiveHost = getConfigValue('interactive.host').value;
286
284
  const defaultBrowser = getConfigValue('browser.profile').value;
@@ -291,7 +289,6 @@ export async function getSetupStatus() {
291
289
  { phase: 'secrets', state: secretsReady ? 'ready' : 'missing', detail: secretsReady ? 'defaults chosen' : 'defaults not chosen' },
292
290
  { phase: 'accounts', state: minted.ready ? 'ready' : 'missing', detail: minted.detail },
293
291
  { 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' },
294
- { phase: 'share', state: shareConfig ? 'ready' : 'missing', detail: shareConfig?.baseUrl ?? 'endpoint not configured' },
295
292
  { phase: 'watchdog', state: watchdogEnabled ? 'ready' : 'missing', detail: watchdogEnabled ? 'enabled on this device' : 'disabled on this device' },
296
293
  { phase: 'preferences', state: interactiveHost || defaultBrowser ? 'ready' : 'missing', detail: [interactiveHost && `host ${interactiveHost}`, defaultBrowser && `browser ${defaultBrowser}`].filter(Boolean).join(' · ') || 'interactive host and browser unset' },
297
294
  ];
@@ -309,8 +306,6 @@ async function runSetupPhase(phase) {
309
306
  await runBrowserWizard();
310
307
  else if (phase === 'computer')
311
308
  await runComputerWizard();
312
- else if (phase === 'share')
313
- await runShareWizard();
314
309
  else if (phase === 'secrets')
315
310
  await import('./setup-secrets.js').then((m) => m.runSecretsSetupWizard());
316
311
  else if (phase === 'accounts')
@@ -372,8 +367,8 @@ export function registerSetupCommand(program) {
372
367
  .option('-f, --force', 'Re-run setup even if ~/.agents/.system/ already exists (use with caution)')
373
368
  .option('--no-system-repo', 'Skip cloning the system repo (you must populate ~/.agents/.system/ yourself)');
374
369
  // Capability subcommands: `agents setup browser|computer|mine|secrets|accounts|fleet|alias|beta`.
375
- // Share/artifact publishing is set up by `agents artifacts setup` (RUSH-2580);
376
- // the hub below still offers it as a phase via runShareWizard.
370
+ // Artifact publishing is no longer set up here — it lives in the standalone
371
+ // `artifacts` CLI (`artifacts share setup`/`join`, PHNX-3992).
377
372
  registerSetupBrowserCommand(setupCmd);
378
373
  registerSetupComputerCommand(setupCmd);
379
374
  registerSetupMineCommand(setupCmd);
@@ -404,9 +399,25 @@ export function registerSetupCommand(program) {
404
399
  `,
405
400
  });
406
401
  setupCmd.command('status')
407
- .description('Show setup readiness for core, browser, computer, secrets, accounts, fleet, share, watchdog, and preferences.')
402
+ .description('Show setup readiness for core, browser, computer, secrets, accounts, fleet, watchdog, and preferences.')
408
403
  .option('--json', 'print machine-readable JSON')
404
+ .option('--tool <name>', 'Read cached standalone tool status: browser, computer, secrets, or all')
405
+ .option('--refresh', 'Explicitly refresh only the selected standalone tool health checks')
409
406
  .action(async (options) => {
407
+ if (options.tool || options.refresh) {
408
+ const selected = options.tool ?? 'all';
409
+ if (selected !== 'all' && !SETUP_TOOLS.includes(selected))
410
+ throw new Error('Unknown tool. Choose browser, computer, secrets, or all.');
411
+ const rows = options.refresh
412
+ ? await refreshToolSetup(selected)
413
+ : getCachedToolSetup().filter((row) => selected === 'all' || row.tool === selected);
414
+ if (options.json)
415
+ console.log(JSON.stringify(rows));
416
+ else
417
+ for (const row of rows)
418
+ console.log(`${row.tool}: ${row.installed === false ? 'not installed' : row.readiness} · ${row.detail}`);
419
+ return;
420
+ }
410
421
  const rows = await getSetupStatus();
411
422
  if (options.json)
412
423
  console.log(JSON.stringify(rows, null, 2));
@@ -443,7 +454,6 @@ export function registerSetupCommand(program) {
443
454
  Capability setup can also be run any time on its own:
444
455
  agents setup browser # detect a browser + create the default profile
445
456
  agents setup computer # install the signed macOS helper + grant permissions
446
- agents artifacts setup # provision or join a Cloudflare share endpoint
447
457
  agents setup secrets # choose secrets backend/policy defaults + import
448
458
  agents setup accounts # mint a Claude setup-token for unattended usage/probe
449
459
  agents setup fleet # discover Tailscale devices + configure SSH access
@@ -511,14 +511,6 @@ Examples:
511
511
  const output = lines.join('\n');
512
512
  printWithPager(output, lines.length);
513
513
  });
514
- // Deprecated alias for 'view'
515
- skillsCmd
516
- .command('info [name]', { hidden: true })
517
- .action(async (name) => {
518
- console.log(chalk.yellow('Deprecated: Use "agents skills view" instead of "agents skills info"\n'));
519
- // Re-execute view command logic
520
- await skillsCmd.commands.find((c) => c.name() === 'view')?.parseAsync(['view', ...(name ? [name] : [])], { from: 'user' });
521
- });
522
514
  }
523
515
  /**
524
516
  * Build the row data for `agents skills list`. Each row = one central skill
@@ -83,5 +83,11 @@ export interface HarnessInventoryOpts {
83
83
  */
84
84
  export declare function collectFleetHarnesses(opts: HarnessInventoryOpts): Promise<HostHarnessResult[]>;
85
85
  export declare function runDevicesAccounts(opts: HarnessInventoryOpts): Promise<void>;
86
+ /**
87
+ * Parse `--argv`'s JSON array, or report exactly what was wrong and return
88
+ * undefined. Fails loud rather than coercing: a caller that meant to pass argv
89
+ * and typo'd the JSON must not silently get shell-string semantics instead.
90
+ */
91
+ export declare function parseArgvJson(raw: string, log?: (line: string) => void): string[] | undefined;
86
92
  /** Register both `agents ssh` and `agents devices`. */
87
93
  export declare function registerSshCommands(program: Command): void;