@phnx-labs/agents-cli 1.22.114 → 1.22.116

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 (243) hide show
  1. package/CHANGELOG.md +245 -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 +210 -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 +214 -148
  17. package/dist/commands/feed-watch.js +52 -8
  18. package/dist/commands/feed.js +65 -14
  19. package/dist/commands/focus.js +1 -1
  20. package/dist/commands/go.d.ts +0 -17
  21. package/dist/commands/go.js +2 -19
  22. package/dist/commands/logs.js +1 -1
  23. package/dist/commands/mcp.js +8 -83
  24. package/dist/commands/memory.js +4 -47
  25. package/dist/commands/message.js +4 -4
  26. package/dist/commands/plugins.js +1 -93
  27. package/dist/commands/repo.js +0 -44
  28. package/dist/commands/resume.js +4 -1
  29. package/dist/commands/secrets-passthrough.js +2 -2
  30. package/dist/commands/send.d.ts +4 -4
  31. package/dist/commands/send.js +6 -51
  32. package/dist/commands/sessions-backup-setup.js +1 -1
  33. package/dist/commands/sessions-picker.d.ts +11 -0
  34. package/dist/commands/sessions-picker.js +88 -7
  35. package/dist/commands/sessions-resume.js +0 -1
  36. package/dist/commands/sessions-share.d.ts +5 -7
  37. package/dist/commands/sessions-share.js +98 -49
  38. package/dist/commands/sessions.d.ts +21 -2
  39. package/dist/commands/sessions.js +158 -11
  40. package/dist/commands/setup-browser.js +18 -2
  41. package/dist/commands/setup-computer.js +20 -6
  42. package/dist/commands/setup-secrets.d.ts +2 -2
  43. package/dist/commands/setup-secrets.js +24 -4
  44. package/dist/commands/setup-term.d.ts +24 -0
  45. package/dist/commands/setup-term.js +70 -0
  46. package/dist/commands/setup-terminal.d.ts +3 -0
  47. package/dist/commands/setup-terminal.js +22 -0
  48. package/dist/commands/setup.d.ts +1 -1
  49. package/dist/commands/setup.js +29 -11
  50. package/dist/commands/skills.js +0 -8
  51. package/dist/commands/ssh.d.ts +6 -0
  52. package/dist/commands/ssh.js +93 -302
  53. package/dist/commands/sync.js +14 -5
  54. package/dist/commands/teams.js +1 -1
  55. package/dist/commands/traces.js +1 -1
  56. package/dist/lib/accounting/rotate.d.ts +63 -1
  57. package/dist/lib/accounting/rotate.js +56 -0
  58. package/dist/lib/accounts/add.d.ts +0 -5
  59. package/dist/lib/accounts/add.js +3 -9
  60. package/dist/lib/accounts/slots.js +32 -2
  61. package/dist/lib/answer-router.d.ts +11 -2
  62. package/dist/lib/answer-router.js +26 -2
  63. package/dist/lib/artifacts-client.d.ts +20 -0
  64. package/dist/lib/artifacts-client.js +46 -0
  65. package/dist/lib/auth-mint.d.ts +5 -4
  66. package/dist/lib/auth-mint.js +6 -5
  67. package/dist/lib/browser/drivers/arc.d.ts +1 -1
  68. package/dist/lib/browser/runtime-state.d.ts +55 -0
  69. package/dist/lib/browser/runtime-state.js +99 -18
  70. package/dist/lib/browser/service.d.ts +10 -0
  71. package/dist/lib/browser/service.js +229 -37
  72. package/dist/lib/browser/types.d.ts +18 -0
  73. package/dist/lib/cli-resources.js +3 -1
  74. package/dist/lib/cloud/dispatch.js +1 -1
  75. package/dist/lib/cloudflare/creds.d.ts +10 -0
  76. package/dist/lib/cloudflare/creds.js +46 -0
  77. package/dist/lib/cloudflare/provision.d.ts +35 -0
  78. package/dist/lib/cloudflare/provision.js +144 -0
  79. package/dist/lib/computer/sessions-list.d.ts +55 -0
  80. package/dist/lib/computer/sessions-list.js +168 -1
  81. package/dist/lib/config-keys.d.ts +1 -1
  82. package/dist/lib/config-keys.js +5 -0
  83. package/dist/lib/daemon/daemon.js +8 -1
  84. package/dist/lib/daemon/feed-stream-service.d.ts +23 -0
  85. package/dist/lib/daemon/feed-stream-service.js +40 -0
  86. package/dist/lib/daemon-services.d.ts +1 -1
  87. package/dist/lib/daemon-services.js +5 -0
  88. package/dist/lib/device-config.js +61 -0
  89. package/dist/lib/devices/connect.d.ts +49 -5
  90. package/dist/lib/devices/connect.js +169 -21
  91. package/dist/lib/devices/doctor-findings.js +2 -6
  92. package/dist/lib/feed/answer.d.ts +153 -4
  93. package/dist/lib/feed/answer.js +716 -105
  94. package/dist/lib/feed/envelope.d.ts +79 -0
  95. package/dist/lib/feed/envelope.js +23 -0
  96. package/dist/lib/feed/events.d.ts +6 -0
  97. package/dist/lib/feed/events.js +8 -0
  98. package/dist/lib/feed/feed.d.ts +61 -1
  99. package/dist/lib/feed/feed.js +226 -14
  100. package/dist/lib/feed/hub-server.d.ts +141 -0
  101. package/dist/lib/feed/hub-server.js +586 -0
  102. package/dist/lib/feed/hub.d.ts +95 -0
  103. package/dist/lib/feed/hub.js +255 -0
  104. package/dist/lib/feed/pr-status.d.ts +8 -0
  105. package/dist/lib/feed/pr-status.js +9 -1
  106. package/dist/lib/feed/tool-activity.d.ts +108 -0
  107. package/dist/lib/feed/tool-activity.js +313 -0
  108. package/dist/lib/feed/tools.d.ts +198 -0
  109. package/dist/lib/feed/tools.js +265 -0
  110. package/dist/lib/feed/watch.d.ts +50 -50
  111. package/dist/lib/feed/watch.js +147 -16
  112. package/dist/lib/feed-outcome.d.ts +1 -1
  113. package/dist/lib/feed-outcome.js +9 -2
  114. package/dist/lib/feed-policy.js +9 -3
  115. package/dist/lib/fleet/auth-sync.d.ts +2 -55
  116. package/dist/lib/fleet/auth-sync.js +2 -89
  117. package/dist/lib/format.d.ts +1 -1
  118. package/dist/lib/format.js +1 -1
  119. package/dist/lib/git.d.ts +0 -16
  120. package/dist/lib/git.js +0 -58
  121. package/dist/lib/harness-auth-capabilities.js +7 -2
  122. package/dist/lib/helper-versions.js +1 -1
  123. package/dist/lib/hosts/dispatch.d.ts +20 -1
  124. package/dist/lib/hosts/dispatch.js +52 -30
  125. package/dist/lib/hosts/remote-cmd.d.ts +72 -1
  126. package/dist/lib/hosts/remote-cmd.js +151 -10
  127. package/dist/lib/hosts/remote-cmd.test-fixture.d.ts +2 -0
  128. package/dist/lib/hosts/remote-cmd.test-fixture.js +22 -0
  129. package/dist/lib/mailbox.d.ts +12 -0
  130. package/dist/lib/mailbox.js +16 -2
  131. package/dist/lib/mcp.js +17 -11
  132. package/dist/lib/menubar/snapshot.d.ts +51 -0
  133. package/dist/lib/menubar/snapshot.js +42 -3
  134. package/dist/lib/open-url.js +2 -2
  135. package/dist/lib/probe.d.ts +4 -1
  136. package/dist/lib/probe.js +5 -2
  137. package/dist/lib/projects.d.ts +23 -0
  138. package/dist/lib/projects.js +78 -0
  139. package/dist/lib/pwsh.d.ts +33 -0
  140. package/dist/lib/pwsh.js +56 -0
  141. package/dist/lib/redact.d.ts +8 -0
  142. package/dist/lib/redact.js +11 -0
  143. package/dist/lib/refresh.d.ts +6 -2
  144. package/dist/lib/refresh.js +92 -72
  145. package/dist/lib/secrets-cli.d.ts +11 -0
  146. package/dist/lib/secrets-cli.js +30 -0
  147. package/dist/lib/secrets-client.js +3 -2
  148. package/dist/lib/session/active.d.ts +1 -0
  149. package/dist/lib/session/active.js +8 -0
  150. package/dist/lib/session/db.d.ts +67 -3
  151. package/dist/lib/session/db.js +381 -126
  152. package/dist/lib/session/detached.d.ts +7 -0
  153. package/dist/lib/session/detached.js +29 -0
  154. package/dist/lib/session/prompt.d.ts +23 -7
  155. package/dist/lib/session/prompt.js +46 -8
  156. package/dist/lib/session/remote/peer-stream.d.ts +24 -2
  157. package/dist/lib/session/remote/peer-stream.js +33 -6
  158. package/dist/lib/session/remote/remote-list.d.ts +20 -0
  159. package/dist/lib/session/remote/remote-list.js +22 -6
  160. package/dist/lib/session/remote/watch.d.ts +12 -0
  161. package/dist/lib/session/remote/watch.js +9 -0
  162. package/dist/lib/session/remote-preview-cache.d.ts +29 -0
  163. package/dist/lib/session/remote-preview-cache.js +373 -0
  164. package/dist/lib/session/sync/backend.d.ts +3 -3
  165. package/dist/lib/session/sync/backend.js +3 -3
  166. package/dist/lib/session/sync/provision.d.ts +1 -1
  167. package/dist/lib/session/sync/provision.js +2 -2
  168. package/dist/lib/session/tail.d.ts +50 -0
  169. package/dist/lib/session/tail.js +219 -0
  170. package/dist/lib/sessions-client.js +0 -3
  171. package/dist/lib/setup-tool-install.d.ts +3 -0
  172. package/dist/lib/setup-tool-install.js +27 -0
  173. package/dist/lib/setup-tool-status.d.ts +22 -0
  174. package/dist/lib/setup-tool-status.js +220 -0
  175. package/dist/lib/share-runtime.d.ts +11 -0
  176. package/dist/lib/share-runtime.js +63 -0
  177. package/dist/lib/signin-badge.d.ts +19 -4
  178. package/dist/lib/signin-badge.js +29 -11
  179. package/dist/lib/smart-launch.d.ts +1 -5
  180. package/dist/lib/smart-launch.js +3 -11
  181. package/dist/lib/ssh-exec.d.ts +44 -0
  182. package/dist/lib/ssh-exec.js +119 -0
  183. package/dist/lib/startup/command-registry.d.ts +6 -4
  184. package/dist/lib/startup/command-registry.js +10 -7
  185. package/dist/lib/state.js +2 -2
  186. package/dist/lib/storage/selection.d.ts +2 -2
  187. package/dist/lib/storage/selection.js +2 -2
  188. package/dist/lib/sync-umbrella.d.ts +5 -0
  189. package/dist/lib/sync-umbrella.js +18 -10
  190. package/dist/lib/term-driver.d.ts +24 -0
  191. package/dist/lib/term-driver.js +36 -0
  192. package/dist/lib/terminal/index.d.ts +1 -1
  193. package/dist/lib/terminal/index.js +1 -1
  194. package/dist/lib/terminal/inject.d.ts +38 -0
  195. package/dist/lib/terminal/inject.js +55 -9
  196. package/dist/lib/terminal/transport.d.ts +15 -5
  197. package/dist/lib/terminal/transport.js +61 -11
  198. package/dist/lib/traces/backend.d.ts +1 -2
  199. package/dist/lib/traces/backend.js +1 -2
  200. package/dist/lib/traces/provision.d.ts +1 -1
  201. package/dist/lib/traces/provision.js +2 -2
  202. package/dist/lib/types.d.ts +8 -6
  203. package/package.json +2 -3
  204. package/dist/commands/artifacts-setup.d.ts +0 -53
  205. package/dist/commands/artifacts-setup.js +0 -161
  206. package/dist/commands/artifacts.d.ts +0 -18
  207. package/dist/commands/artifacts.js +0 -58
  208. package/dist/commands/attach.d.ts +0 -12
  209. package/dist/commands/attach.js +0 -86
  210. package/dist/commands/auth-mint.d.ts +0 -12
  211. package/dist/commands/auth-mint.js +0 -108
  212. package/dist/commands/reconnect.d.ts +0 -46
  213. package/dist/commands/reconnect.js +0 -115
  214. package/dist/commands/share.d.ts +0 -293
  215. package/dist/commands/share.js +0 -1424
  216. package/dist/lib/fleet/remote-login.d.ts +0 -170
  217. package/dist/lib/fleet/remote-login.js +0 -568
  218. package/dist/lib/share/analytics.d.ts +0 -13
  219. package/dist/lib/share/analytics.js +0 -45
  220. package/dist/lib/share/backend.d.ts +0 -120
  221. package/dist/lib/share/backend.js +0 -176
  222. package/dist/lib/share/capture.d.ts +0 -31
  223. package/dist/lib/share/capture.js +0 -174
  224. package/dist/lib/share/config.d.ts +0 -72
  225. package/dist/lib/share/config.js +0 -211
  226. package/dist/lib/share/delete.d.ts +0 -123
  227. package/dist/lib/share/delete.js +0 -173
  228. package/dist/lib/share/html.d.ts +0 -20
  229. package/dist/lib/share/html.js +0 -88
  230. package/dist/lib/share/http-error.d.ts +0 -53
  231. package/dist/lib/share/http-error.js +0 -65
  232. package/dist/lib/share/og.d.ts +0 -26
  233. package/dist/lib/share/og.js +0 -84
  234. package/dist/lib/share/provision.d.ts +0 -127
  235. package/dist/lib/share/provision.js +0 -285
  236. package/dist/lib/share/publish.d.ts +0 -379
  237. package/dist/lib/share/publish.js +0 -818
  238. package/dist/lib/share/worker-template.d.ts +0 -27
  239. package/dist/lib/share/worker-template.js +0 -2424
  240. package/dist/lib/storage/index.d.ts +0 -14
  241. package/dist/lib/storage/index.js +0 -14
  242. package/dist/lib/storage/visibility.d.ts +0 -82
  243. package/dist/lib/storage/visibility.js +0 -99
@@ -455,6 +455,84 @@ export function projectNameForCwd(cwd, defs) {
455
455
  }
456
456
  return best ?? weakBest;
457
457
  }
458
+ /**
459
+ * Definition list, memoized against a stamp over the definition FILES, for the
460
+ * per-tick readers (PHNX-3999).
461
+ *
462
+ * {@link listProjectDefs} parses every `<name>.yaml`, which is the right cost for
463
+ * a command but not for a row builder on the `feed watch` / `sessions watch`
464
+ * stream — that runs twice a second for as long as an editor window is open (see
465
+ * `AGENTS.md`, every tick is budgeted). One `readdir` plus one `stat` per
466
+ * definition replaces N YAML parses.
467
+ *
468
+ * The stamp is each file's name + mtime + size, NOT the directory's mtime: editing
469
+ * a definition in place — retargeting a project's `root`, which is exactly what
470
+ * changes which sessions belong to it — moves the FILE's mtime and leaves the
471
+ * directory's untouched, so a directory stamp would serve a stale association for
472
+ * the life of the process. An unreadable directory is not cached: the next call
473
+ * retries.
474
+ */
475
+ let projectDefsMemo = null;
476
+ function projectDefsStamp() {
477
+ try {
478
+ const dir = getProjectsDir();
479
+ const files = fs.readdirSync(dir).filter((f) => f.endsWith('.yaml')).sort();
480
+ return files
481
+ .map((f) => {
482
+ try {
483
+ const st = fs.statSync(path.join(dir, f));
484
+ return `${f}:${st.mtimeMs}:${st.size}`;
485
+ }
486
+ catch {
487
+ // Removed between readdir and stat — its absence is part of the stamp.
488
+ return `${f}:gone`;
489
+ }
490
+ })
491
+ .join('\n');
492
+ }
493
+ catch {
494
+ return null;
495
+ }
496
+ }
497
+ export function listProjectDefsCached() {
498
+ const stamp = projectDefsStamp();
499
+ // No projects dir (or unreadable): nothing is confirmed, and nothing to cache.
500
+ if (stamp === null)
501
+ return [];
502
+ if (projectDefsMemo && projectDefsMemo.stamp === stamp)
503
+ return projectDefsMemo.defs;
504
+ const defs = listProjectDefs();
505
+ projectDefsMemo = { stamp, defs };
506
+ return defs;
507
+ }
508
+ /** Drop the {@link listProjectDefsCached} memo — for tests that rewrite the dir within one mtime tick. */
509
+ export function resetProjectDefsCache() {
510
+ projectDefsMemo = null;
511
+ }
512
+ /**
513
+ * The **confirmed** project for a working directory, or `undefined` when the
514
+ * association is not confirmed (PHNX-3999 F08/F09).
515
+ *
516
+ * Confirmed means exactly one thing: a registered project definition
517
+ * ({@link projectNameForCwd}) whose root contains this path. Being inside *some*
518
+ * git repository is NOT a project association — a repo nobody registered, a
519
+ * checkout of someone else's code, or a loose directory that merely has a
520
+ * `.git` in it would each invent a project name out of a folder, which is the
521
+ * wrong-grouping the owner's recording shows at 01:30–01:56. So is a bare
522
+ * directory basename ({@link resolveProjectKey}, which always answers).
523
+ *
524
+ * A consumer renders `undefined` as Uncategorized and keeps the session
525
+ * reachable in its full list — nothing is hidden by not being grouped.
526
+ *
527
+ * Use {@link resolveProjectNameForCwd} instead when you want a best-effort
528
+ * bucket KEY for joining rows; use this when the answer is shown to a person as
529
+ * "this work belongs to that project".
530
+ */
531
+ export function confirmedProjectForCwd(cwd, defs = listProjectDefsCached()) {
532
+ if (!cwd)
533
+ return undefined;
534
+ return projectNameForCwd(cwd, defs);
535
+ }
458
536
  /**
459
537
  * The canonical project label for a cwd, for every surface that buckets work by
460
538
  * project (the activity timeline, feed posts, the sessions overview): the
@@ -9,3 +9,36 @@
9
9
  * spawns powershell.exe with an argv array).
10
10
  */
11
11
  export declare function encodePwshBase64(script: string): string;
12
+ /** Single-quote a value for PowerShell's own parser (embedded `'` doubled). */
13
+ export declare function pwshLiteral(value: string): string;
14
+ /**
15
+ * PowerShell statements that run a NATIVE program with exact argv.
16
+ *
17
+ * Windows has no argv array: a process receives ONE string and splits it itself.
18
+ * PowerShell 5.1 rebuilds that string when it invokes a native program, and its
19
+ * serializer is lossy — measured on a real peer, an EMPTY argument is dropped and
20
+ * an embedded `"` is discarded. So the arguments are pre-escaped by the caller to
21
+ * the `CommandLineToArgvW` rules (`quoteWin32ExecArg`) and handed to .NET as one
22
+ * `Arguments` string, which `CreateProcess` receives essentially verbatim. No shell
23
+ * is involved, so no `%VAR%` expansion and no newline sensitivity.
24
+ *
25
+ * `UseShellExecute = $false` with no redirection leaves the child on the inherited
26
+ * handles, which is what lets a binary stdout stream through unchanged.
27
+ *
28
+ * `WorkingDirectory` is set explicitly from `(Get-Location).ProviderPath`, and that
29
+ * is not belt-and-braces: PowerShell's `Set-Location` moves the SHELL's location
30
+ * without updating the .NET process's OS working directory, so a child launched
31
+ * through `ProcessStartInfo` would otherwise start in whatever directory the
32
+ * process began in — silently ignoring a caller's `cwd` (`--remote-cwd`). The
33
+ * `ProviderPath` form is what yields a real filesystem path rather than a
34
+ * PowerShell provider path.
35
+ *
36
+ * @param fileNameExpr a PowerShell EXPRESSION for the executable (a quoted literal,
37
+ * or something like `$__c.Source`) — not a raw path, so a caller that resolved the
38
+ * target on the peer can pass its variable.
39
+ * @param argumentsExpr a PowerShell EXPRESSION producing the argument string —
40
+ * `pwshLiteral(line)` for a static one, or a concatenation when part of it is
41
+ * only known on the peer (a resolved interpreter entry, say).
42
+ * @param exitVar variable name for the child, so a caller can chain on it.
43
+ */
44
+ export declare function pwshNativeExecStatements(fileNameExpr: string, argumentsExpr: string, exitVar?: string): string[];
package/dist/lib/pwsh.js CHANGED
@@ -11,3 +11,59 @@
11
11
  export function encodePwshBase64(script) {
12
12
  return Buffer.from(script, 'utf16le').toString('base64');
13
13
  }
14
+ /** Single-quote a value for PowerShell's own parser (embedded `'` doubled). */
15
+ export function pwshLiteral(value) {
16
+ return `'${value.replace(/'/g, "''")}'`;
17
+ }
18
+ /**
19
+ * PowerShell statements that run a NATIVE program with exact argv.
20
+ *
21
+ * Windows has no argv array: a process receives ONE string and splits it itself.
22
+ * PowerShell 5.1 rebuilds that string when it invokes a native program, and its
23
+ * serializer is lossy — measured on a real peer, an EMPTY argument is dropped and
24
+ * an embedded `"` is discarded. So the arguments are pre-escaped by the caller to
25
+ * the `CommandLineToArgvW` rules (`quoteWin32ExecArg`) and handed to .NET as one
26
+ * `Arguments` string, which `CreateProcess` receives essentially verbatim. No shell
27
+ * is involved, so no `%VAR%` expansion and no newline sensitivity.
28
+ *
29
+ * `UseShellExecute = $false` with no redirection leaves the child on the inherited
30
+ * handles, which is what lets a binary stdout stream through unchanged.
31
+ *
32
+ * `WorkingDirectory` is set explicitly from `(Get-Location).ProviderPath`, and that
33
+ * is not belt-and-braces: PowerShell's `Set-Location` moves the SHELL's location
34
+ * without updating the .NET process's OS working directory, so a child launched
35
+ * through `ProcessStartInfo` would otherwise start in whatever directory the
36
+ * process began in — silently ignoring a caller's `cwd` (`--remote-cwd`). The
37
+ * `ProviderPath` form is what yields a real filesystem path rather than a
38
+ * PowerShell provider path.
39
+ *
40
+ * @param fileNameExpr a PowerShell EXPRESSION for the executable (a quoted literal,
41
+ * or something like `$__c.Source`) — not a raw path, so a caller that resolved the
42
+ * target on the peer can pass its variable.
43
+ * @param argumentsExpr a PowerShell EXPRESSION producing the argument string —
44
+ * `pwshLiteral(line)` for a static one, or a concatenation when part of it is
45
+ * only known on the peer (a resolved interpreter entry, say).
46
+ * @param exitVar variable name for the child, so a caller can chain on it.
47
+ */
48
+ export function pwshNativeExecStatements(fileNameExpr, argumentsExpr, exitVar = '$zp') {
49
+ // Deliberately terse. The whole script is base64'd into ONE ssh command line,
50
+ // and OpenSSH-for-Windows caps that far below cmd.exe's 8191 — measured on a
51
+ // real peer, it fails between ~2.8k and ~4.1k characters. UTF-16LE + base64
52
+ // inflates by ~2.7x, so every character here costs almost three on the wire.
53
+ // `System.` is implicit for these types, and `-EA` is the canonical alias.
54
+ return [
55
+ `$zi=New-Object Diagnostics.ProcessStartInfo`,
56
+ `$zi.FileName=${fileNameExpr}`,
57
+ `$zi.Arguments=${argumentsExpr}`,
58
+ `$zi.UseShellExecute=$false`,
59
+ `$zi.WorkingDirectory=(Get-Location).ProviderPath`,
60
+ // The CALLER sets `$ErrorActionPreference='Stop'` for the whole script, so it
61
+ // is not repeated here — every character costs ~2.7 on the wire after base64.
62
+ // The null guard below is what makes a swallowed Start failure impossible to
63
+ // mistake for success: without it the handle is $null, the code is $null, and
64
+ // `exit $null` reports 0.
65
+ `${exitVar}=[Diagnostics.Process]::Start($zi)`,
66
+ `if($null -eq ${exitVar}){throw "start failed: $($zi.FileName)"}`,
67
+ `${exitVar}.WaitForExit()`,
68
+ ];
69
+ }
@@ -23,3 +23,11 @@ export declare function redactSecrets(text: string, knownValues?: readonly strin
23
23
  * credential verbatim even when its format matches no pattern.
24
24
  */
25
25
  export declare function knownSecretValuesFromEnv(env?: NodeJS.ProcessEnv): string[];
26
+ /**
27
+ * Mask email addresses in already-rendered text. A published page's sensitive-
28
+ * content scan refuses a body containing an email (`artifacts share` does the
29
+ * same), and almost every real transcript carries a few — git author addresses,
30
+ * `gh api user`, a pasted log. Masking them here means the page genuinely does
31
+ * not carry them, rather than training callers to pass `--force`.
32
+ */
33
+ export declare function redactEmails(text: string): string;
@@ -185,3 +185,14 @@ export function knownSecretValuesFromEnv(env = process.env) {
185
185
  }
186
186
  return out;
187
187
  }
188
+ const EMAIL_RE = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
189
+ /**
190
+ * Mask email addresses in already-rendered text. A published page's sensitive-
191
+ * content scan refuses a body containing an email (`artifacts share` does the
192
+ * same), and almost every real transcript carries a few — git author addresses,
193
+ * `gh api user`, a pasted log. Masking them here means the page genuinely does
194
+ * not carry them, rather than training callers to pass `--force`.
195
+ */
196
+ export function redactEmails(text) {
197
+ return text.replace(EMAIL_RE, '[EMAIL]');
198
+ }
@@ -6,10 +6,10 @@
6
6
  * The reconcile stage behind `agents sync` (the umbrella `--local` path calls
7
7
  * this; see sync-umbrella.ts) and any other caller that needs to re-derive local
8
8
  * state from declared configuration. Does NOT do any git operations — that lives
9
- * in `agents repo pull`. (Also still reachable via the deprecated, hidden
10
- * `agents repo refresh` alias.)
9
+ * in `agents repo pull`.
11
10
  */
12
11
  import type { AgentId } from './types.js';
12
+ import { type ResourceSelection } from './installations/versions.js';
13
13
  interface RefreshOptions {
14
14
  /** Limit operations to a single agent (claude/codex/etc). Default: all installed. */
15
15
  agentFilter?: AgentId;
@@ -22,6 +22,10 @@ interface RefreshOptions {
22
22
  * (`agents sync --json` / fleet fan-out) so stdout stays a single JSON object.
23
23
  */
24
24
  quiet?: boolean;
25
+ /** Limit reconciliation to the requested resource kinds/names. */
26
+ selection?: ResourceSelection;
27
+ /** Explicit consent for selected plugins that add executable surfaces. */
28
+ allowExecSurfaces?: boolean;
25
29
  }
26
30
  /**
27
31
  * Re-materialize local state from declared configuration: install CLI versions,
@@ -6,8 +6,7 @@
6
6
  * The reconcile stage behind `agents sync` (the umbrella `--local` path calls
7
7
  * this; see sync-umbrella.ts) and any other caller that needs to re-derive local
8
8
  * state from declared configuration. Does NOT do any git operations — that lives
9
- * in `agents repo pull`. (Also still reachable via the deprecated, hidden
10
- * `agents repo refresh` alias.)
9
+ * in `agents repo pull`.
11
10
  */
12
11
  import * as fs from 'fs';
13
12
  import * as path from 'path';
@@ -50,7 +49,7 @@ function migratePromptcutsToRoot(agentsDir, quiet = false) {
50
49
  }
51
50
  }
52
51
  export async function refresh(options = {}) {
53
- const { agentFilter, skipPrompts = false, skipClis = false, quiet = false } = options;
52
+ const { agentFilter, skipPrompts = false, skipClis = false, quiet = false, selection: requestedSelection, allowExecSurfaces = false, } = options;
54
53
  const agentsDir = getUserAgentsDir();
55
54
  // Gate every human progress line so --json / fleet fan-out can parse stdout.
56
55
  const log = (...args) => { if (!quiet)
@@ -59,7 +58,8 @@ export async function refresh(options = {}) {
59
58
  // synced list cannot also mean "declined and here is why" (RUSH-2700).
60
59
  const declined = [];
61
60
  const reconciled = [];
62
- migratePromptcutsToRoot(agentsDir, quiet);
61
+ if (!requestedSelection)
62
+ migratePromptcutsToRoot(agentsDir, quiet);
63
63
  const manifest = readManifest(agentsDir);
64
64
  if (!manifest) {
65
65
  log(chalk.gray(`No ${MANIFEST_FILENAME} found`));
@@ -94,17 +94,23 @@ export async function refresh(options = {}) {
94
94
  }
95
95
  }
96
96
  // 2. Register MCP servers
97
- if (manifest?.mcp && Object.keys(manifest.mcp).length > 0) {
97
+ if ((!requestedSelection || requestedSelection.mcp) && manifest?.mcp && Object.keys(manifest.mcp).length > 0) {
98
98
  log(chalk.bold('\nMCP Servers:\n'));
99
99
  for (const [name, config] of Object.entries(manifest.mcp)) {
100
- if (!config.command || config.transport === 'http')
100
+ if (Array.isArray(requestedSelection?.mcp) && !requestedSelection.mcp.includes(name))
101
101
  continue;
102
+ const transport = config.transport || 'stdio';
103
+ const commandOrUrl = transport === 'http' ? config.url : config.command;
104
+ if (!commandOrUrl) {
105
+ log(` ${chalk.cyan(name)}: ${chalk.yellow(`missing ${transport === 'http' ? 'url' : 'command'}`)}`);
106
+ continue;
107
+ }
102
108
  const scopedAgents = (config.agents ? [...config.agents] : [...capableAgents('mcp')]).filter((id) => !agentFilter || id === agentFilter);
103
109
  const scopedVersions = config.agentVersions
104
110
  ? Object.fromEntries(Object.entries(config.agentVersions).filter(([agentId]) => !agentFilter || agentId === agentFilter))
105
111
  : undefined;
106
112
  const targets = resolveConfiguredAgentTargets(scopedAgents, scopedVersions, capableAgents('mcp'));
107
- const results = await registerMcpToTargets(targets, name, config.command, config.scope || 'user', config.transport || 'stdio');
113
+ const results = await registerMcpToTargets(targets, name, commandOrUrl, config.scope || 'user', transport);
108
114
  for (const result of results) {
109
115
  if (result.success) {
110
116
  const label = result.version
@@ -116,22 +122,25 @@ export async function refresh(options = {}) {
116
122
  }
117
123
  }
118
124
  // 3. Sync resources into version homes.
119
- // Unattended (`skipPrompts` / `agents sync --yes --local`): every installed
120
- // version — otherwise non-default homes keep stale hooks after a system
121
- // update (fleet multi-harness: only the default version was refreshed).
122
- // Interactive: default only; re-run `agents sync <agent>@all` for the rest.
125
+ // Unattended (`skipPrompts` / `agents sync --yes --local`) and explicit
126
+ // resource selectors: every installed version. Otherwise non-default homes
127
+ // keep stale resources after a system update or named plugin sync.
128
+ // Interactive full reconcile: default only.
123
129
  const cliStates = await getAllCliStates();
124
130
  const agentsToSync = agentFilter ? [agentFilter] : MANAGED_AGENT_IDS;
125
131
  const available = getAvailableResources();
126
132
  for (const agentId of agentsToSync) {
127
- if (!cliStates[agentId]?.installed && listInstalledVersions(agentId).length === 0)
133
+ const installedVersions = listInstalledVersions(agentId);
134
+ if (!cliStates[agentId]?.installed && installedVersions.length === 0)
128
135
  continue;
129
136
  const defaultVer = getGlobalDefault(agentId);
130
- if (!defaultVer)
137
+ if (!defaultVer && !requestedSelection)
131
138
  continue;
132
- const versionsToSync = skipPrompts
133
- ? listInstalledVersions(agentId)
139
+ const versionsToSync = requestedSelection || skipPrompts
140
+ ? installedVersions
134
141
  : [defaultVer];
142
+ if (versionsToSync.length === 0)
143
+ continue;
135
144
  // Interactive-only: getActuallySyncedResources walks every skill tree with
136
145
  // content compares (~1s/agent on a full install). The unattended path
137
146
  // (`skipPrompts` / `agents sync --yes`) never reads these — it always
@@ -139,7 +148,7 @@ export async function refresh(options = {}) {
139
148
  let actuallySynced;
140
149
  let newResources;
141
150
  let hasAnySynced = false;
142
- if (!skipPrompts) {
151
+ if (!skipPrompts && !requestedSelection) {
143
152
  actuallySynced = getActuallySyncedResources(agentId, defaultVer);
144
153
  newResources = getNewResources(available, actuallySynced, getProjectOnlyResources());
145
154
  hasAnySynced = actuallySynced.commands.length > 0 ||
@@ -153,7 +162,10 @@ export async function refresh(options = {}) {
153
162
  try {
154
163
  let selection;
155
164
  let forceFullSync = false;
156
- if (skipPrompts) {
165
+ if (requestedSelection) {
166
+ selection = requestedSelection;
167
+ }
168
+ else if (skipPrompts) {
157
169
  forceFullSync = true;
158
170
  }
159
171
  else if (!hasAnySynced) {
@@ -176,7 +188,11 @@ export async function refresh(options = {}) {
176
188
  for (const ver of versionsToSync) {
177
189
  // Pass the already-built `available` so each version does not re-scan
178
190
  // resource trees (RUSH-2320 #5).
179
- const syncResult = syncResourcesToVersion(agentId, ver, selection, { available, ...(forceFullSync ? { force: true } : {}) });
191
+ const syncResult = syncResourcesToVersion(agentId, ver, selection, {
192
+ available,
193
+ allowExecSurfaces,
194
+ ...(forceFullSync || requestedSelection ? { force: true } : {}),
195
+ });
180
196
  reconciled.push({ agent: agentId, version: ver });
181
197
  if (syncResult.commands)
182
198
  kinds.add('commands');
@@ -214,7 +230,7 @@ export async function refresh(options = {}) {
214
230
  }
215
231
  }
216
232
  // 4. Register hooks as lifecycle events (same version set as resource sync)
217
- const hookManifest = parseHookManifest();
233
+ const hookManifest = requestedSelection ? {} : parseHookManifest();
218
234
  if (Object.keys(hookManifest).length > 0) {
219
235
  let hookRegistered = 0;
220
236
  const hookAgents = new Set(capableAgents('hooks'));
@@ -242,25 +258,27 @@ export async function refresh(options = {}) {
242
258
  // 5. Auto-add shims to PATH
243
259
  // Refresh the gh overload shim so `gh pr checks` escapes the GraphQL rate limit
244
260
  // for every user, transparently (PHNX-3501). Idempotent; POSIX-only in v1.
245
- try {
246
- ensureGhOverloadShim();
247
- }
248
- catch {
249
- // Never let a shim-write hiccup break sync — real gh stays fine without it.
250
- }
251
- if (!isShimsInPath()) {
252
- const pathResult = addShimsToPath();
253
- if (pathResult.success && !pathResult.alreadyPresent) {
254
- log(chalk.green(`\nAdded shims to ${pathResult.location}`));
255
- log(chalk.gray(pathResult.reloadHint));
261
+ if (!requestedSelection) {
262
+ try {
263
+ ensureGhOverloadShim();
264
+ }
265
+ catch {
266
+ // Never let a shim-write hiccup break sync — real gh stays fine without it.
256
267
  }
257
- else if (!pathResult.success) {
258
- log(chalk.yellow('\nCould not auto-add shims to PATH:'));
259
- log(chalk.gray(getPathSetupInstructions()));
268
+ if (!isShimsInPath()) {
269
+ const pathResult = addShimsToPath();
270
+ if (pathResult.success && !pathResult.alreadyPresent) {
271
+ log(chalk.green(`\nAdded shims to ${pathResult.location}`));
272
+ log(chalk.gray(pathResult.reloadHint));
273
+ }
274
+ else if (!pathResult.success) {
275
+ log(chalk.yellow('\nCould not auto-add shims to PATH:'));
276
+ log(chalk.gray(getPathSetupInstructions()));
277
+ }
260
278
  }
261
279
  }
262
280
  // 6. Prompt for missing default versions
263
- if (!skipPrompts) {
281
+ if (!skipPrompts && !requestedSelection) {
264
282
  const agentsNeedingDefault = [];
265
283
  for (const agentId of agentsToSync) {
266
284
  const versions = listInstalledVersions(agentId);
@@ -307,53 +325,55 @@ export async function refresh(options = {}) {
307
325
  }
308
326
  }
309
327
  // 7. Install declared host-CLIs
310
- try {
311
- const { statuses, errors } = listCliStatus(process.cwd());
312
- for (const err of errors) {
313
- log(chalk.yellow(` CLI manifest parse error: ${err.file}: ${err.reason}`));
314
- }
315
- const missing = statuses.filter((s) => !s.installed);
316
- if (missing.length > 0) {
317
- log(chalk.bold('\nDeclared CLIs missing from this host:'));
318
- for (const s of missing) {
319
- const method = selectInstallMethod(s.manifest);
320
- const action = method ? describeMethod(method) : chalk.red('no compatible install method');
321
- log(` ${chalk.cyan(s.manifest.name.padEnd(20))} ${chalk.gray(action)}`);
328
+ if (!requestedSelection) {
329
+ try {
330
+ const { statuses, errors } = listCliStatus(process.cwd());
331
+ for (const err of errors) {
332
+ log(chalk.yellow(` CLI manifest parse error: ${err.file}: ${err.reason}`));
322
333
  }
323
- log('');
324
- if (!skipPrompts) {
325
- const proceed = await confirm({ message: `Install ${missing.length} missing CLI(s) now?`, default: true });
326
- if (proceed) {
327
- for (const s of missing) {
328
- log(chalk.bold(`\n→ ${s.manifest.name}`));
329
- const result = installCli(s.manifest);
330
- if (result.error) {
331
- log(chalk.red(` ${result.error}`));
332
- continue;
333
- }
334
- if (result.installed) {
335
- log(chalk.green(` installed`));
336
- if (s.manifest.postInstall) {
337
- log(chalk.gray(s.manifest.postInstall.trim().split('\n').map((l) => ' ' + l).join('\n')));
334
+ const missing = statuses.filter((s) => !s.installed);
335
+ if (missing.length > 0) {
336
+ log(chalk.bold('\nDeclared CLIs missing from this host:'));
337
+ for (const s of missing) {
338
+ const method = selectInstallMethod(s.manifest);
339
+ const action = method ? describeMethod(method) : chalk.red('no compatible install method');
340
+ log(` ${chalk.cyan(s.manifest.name.padEnd(20))} ${chalk.gray(action)}`);
341
+ }
342
+ log('');
343
+ if (!skipPrompts) {
344
+ const proceed = await confirm({ message: `Install ${missing.length} missing CLI(s) now?`, default: true });
345
+ if (proceed) {
346
+ for (const s of missing) {
347
+ log(chalk.bold(`\n→ ${s.manifest.name}`));
348
+ const result = installCli(s.manifest);
349
+ if (result.error) {
350
+ log(chalk.red(` ${result.error}`));
351
+ continue;
352
+ }
353
+ if (result.installed) {
354
+ log(chalk.green(` installed`));
355
+ if (s.manifest.postInstall) {
356
+ log(chalk.gray(s.manifest.postInstall.trim().split('\n').map((l) => ' ' + l).join('\n')));
357
+ }
358
+ }
359
+ else {
360
+ log(chalk.yellow(` install ran but \`${describeCheck(s.manifest.check)}\` still fails`));
338
361
  }
339
362
  }
340
- else {
341
- log(chalk.yellow(` install ran but \`${describeCheck(s.manifest.check)}\` still fails`));
342
- }
363
+ }
364
+ else {
365
+ log(chalk.gray(`Skipped. Run 'agents cli install' later.`));
343
366
  }
344
367
  }
345
368
  else {
346
- log(chalk.gray(`Skipped. Run 'agents cli install' later.`));
369
+ log(chalk.gray(`Run 'agents cli install' to install them.`));
347
370
  }
348
371
  }
349
- else {
350
- log(chalk.gray(`Run 'agents cli install' to install them.`));
351
- }
352
372
  }
353
- }
354
- catch (err) {
355
- if (!isPromptCancelled(err)) {
356
- log(chalk.yellow(`CLI install skipped: ${err.message}`));
373
+ catch (err) {
374
+ if (!isPromptCancelled(err)) {
375
+ log(chalk.yellow(`CLI install skipped: ${err.message}`));
376
+ }
357
377
  }
358
378
  }
359
379
  // A resource agents-cli refused to write is reported, never swallowed —
@@ -0,0 +1,11 @@
1
+ export declare const SECRETS_CLI_NAME = "secrets";
2
+ export declare const SECRETS_CLI_PACKAGE = "@phnx-labs/secrets-cli";
3
+ /** Published standalone `agents setup secrets` installs; bump with the protocol. */
4
+ export declare const SECRETS_CLI_VERSION = "0.1.5";
5
+ export declare const SECRETS_CLI_SPEC = "@phnx-labs/secrets-cli@0.1.5";
6
+ export declare const SECRETS_CLI_INSTALL_HINT = "npm i -g @phnx-labs/secrets-cli@0.1.5";
7
+ /**
8
+ * True when `$SECRETS_BIN` is set or a real `secrets` executable is on PATH
9
+ * outside agents-cli's shims dir. Does not spawn the binary.
10
+ */
11
+ export declare function isSecretsPresent(): boolean;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The standalone `secrets` CLI that agents-cli talks to (PHNX-3989).
3
+ *
4
+ * The engine lives in `@phnx-labs/secrets-cli`. This module is the single pin
5
+ * and presence check shared by the process client (`secrets-client.ts`) and
6
+ * `agents setup secrets` — it carries no storage. Presence is `findInPath`,
7
+ * which skips `~/.agents/.cache/shims`: a leftover alias there `exec`s
8
+ * `agents secrets` and would recurse (agi-cli#3532).
9
+ *
10
+ * Keep this file free of `cli-resources` imports: `secrets-client.ts` loads it,
11
+ * and a cycle through the host-CLI parser would pull yaml/spawn into the
12
+ * process-client module graph.
13
+ */
14
+ import { findInPath } from './agent-spec/agents.js';
15
+ export const SECRETS_CLI_NAME = 'secrets';
16
+ export const SECRETS_CLI_PACKAGE = '@phnx-labs/secrets-cli';
17
+ /** Published standalone `agents setup secrets` installs; bump with the protocol. */
18
+ export const SECRETS_CLI_VERSION = '0.1.5';
19
+ export const SECRETS_CLI_SPEC = `${SECRETS_CLI_PACKAGE}@${SECRETS_CLI_VERSION}`;
20
+ export const SECRETS_CLI_INSTALL_HINT = `npm i -g ${SECRETS_CLI_SPEC}`;
21
+ /**
22
+ * True when `$SECRETS_BIN` is set or a real `secrets` executable is on PATH
23
+ * outside agents-cli's shims dir. Does not spawn the binary.
24
+ */
25
+ export function isSecretsPresent() {
26
+ const explicit = process.env.SECRETS_BIN?.trim();
27
+ if (explicit)
28
+ return true;
29
+ return findInPath(SECRETS_CLI_NAME) !== null;
30
+ }
@@ -49,6 +49,7 @@
49
49
  */
50
50
  import { spawn, spawnSync } from 'node:child_process';
51
51
  import { findInPath } from './agent-spec/agents.js';
52
+ import { SECRETS_CLI_INSTALL_HINT } from './secrets-cli.js';
52
53
  import { getUserAgentsDir } from './state.js';
53
54
  /**
54
55
  * Wire contract, mirrored from `secrets-cli/src/protocol.ts`. Both sides MUST
@@ -216,8 +217,8 @@ export function resolveSecretsBin() {
216
217
  const resolved = explicit && explicit.length > 0 ? explicit : findInPath('secrets');
217
218
  if (!resolved) {
218
219
  throw new SecretsClientError('SECRETS_BIN_MISSING', 'The standalone `secrets` CLI was not found. Install it with:\n' +
219
- ' npm i -g @phnx-labs/secrets-cli\n' +
220
- 'or point $SECRETS_BIN at its executable.');
220
+ ` ${SECRETS_CLI_INSTALL_HINT}\n` +
221
+ 'or run `agents setup secrets`.');
221
222
  }
222
223
  cachedBin = resolved;
223
224
  return resolved;
@@ -71,6 +71,7 @@ export declare function activeSessionProjectKey(s: Pick<ActiveSession, 'cwd' | '
71
71
  export declare function serializeActiveSessionsForJson(sessions: ActiveSession[]): Array<Omit<ActiveSession, 'viewingIn'> & {
72
72
  ticketId: string | null;
73
73
  project: string;
74
+ confirmedProject: string | null;
74
75
  prLink: string | null;
75
76
  viewingIn: string | null;
76
77
  }>;
@@ -38,6 +38,7 @@ import { computeTokPerSec } from './throughput.js';
38
38
  import { inferSessionState } from './state.js';
39
39
  import { isSessionTrackedAgent, SESSION_AGENTS, AG_TMUX_NAME_RE } from './types.js';
40
40
  import { AGENTS } from '../agents.js';
41
+ import { confirmedProjectForCwd, listProjectDefsCached } from '../projects.js';
41
42
  import { detectProvenance } from './provenance.js';
42
43
  import { loadDevices } from '../devices/registry.js';
43
44
  import { machineId, normalizeHost } from '../machine-id.js';
@@ -227,10 +228,17 @@ export function activeSessionProjectKey(s) {
227
228
  return s.context === 'cloud' ? 'cloud' : 'other';
228
229
  }
229
230
  export function serializeActiveSessionsForJson(sessions) {
231
+ // One definition read for the whole batch, not per row (PHNX-3999 F08/F09).
232
+ const defs = listProjectDefsCached();
230
233
  return sessions.map((s) => ({
231
234
  ...s,
232
235
  ticketId: s.ticket?.id ?? null,
233
236
  project: activeSessionProjectKey(s),
237
+ // The project a person is shown this row under, or null for Uncategorized.
238
+ // `project` above is the always-present join KEY (basename of the cwd, or the
239
+ // explicit cloud/other bucket) and stays exactly as it was — grouping by it is
240
+ // what filed unbound directories as projects of their own.
241
+ confirmedProject: confirmedProjectForCwd(s.cwd, defs) ?? null,
234
242
  prLink: s.pr?.url ?? null,
235
243
  viewingIn: viewingInLabel(s) ?? null,
236
244
  }));