@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
@@ -17,6 +17,7 @@
17
17
  * and any UI that collapses blocks under deliverables.
18
18
  */
19
19
  import { detectTicket, extractPrUrl } from './session/state.js';
20
+ import { deriveBlockState } from './feed/feed.js';
20
21
  const UNASSIGNED = {
21
22
  key: 'unassigned',
22
23
  kind: 'unassigned',
@@ -132,8 +133,14 @@ export function outcomeForBlock(block) {
132
133
  text: blockScanText(block),
133
134
  });
134
135
  }
136
+ /**
137
+ * Openness is the canonical {@link deriveBlockState}, never a raw `block.answer`
138
+ * test: a PENDING claim carries an answer record while the block is still
139
+ * `open`, so reading the field directly showed a claimed-but-undelivered
140
+ * question as answered in the operator's own feed (PHNX-3999).
141
+ */
135
142
  function isOpen(block) {
136
- return !block.answer && !block.parkedAt && !block.continuedAt && !block.defaultedAt;
143
+ return deriveBlockState(block) === 'open' && !block.parkedAt && !block.continuedAt && !block.defaultedAt;
137
144
  }
138
145
  /**
139
146
  * Collapse blocks under their outcome. Order: needs-you outcomes first
@@ -156,7 +163,7 @@ export function groupBlocksByOutcome(blocks) {
156
163
  for (const b of members) {
157
164
  const next = b.parkedAt
158
165
  ? 'parked'
159
- : b.answer || b.defaultedAt || b.continuedAt ? 'answered' : 'open';
166
+ : isOpen(b) ? 'open' : 'answered';
160
167
  const current = states.get(b.mailboxId);
161
168
  // One agent occupies one state. Its most actionable block wins.
162
169
  if (!current || next === 'open' || (next === 'parked' && current === 'answered')) {
@@ -16,7 +16,7 @@ import * as fs from 'fs';
16
16
  import * as path from 'path';
17
17
  import * as yaml from 'yaml';
18
18
  import { getUserAgentsDir } from './state.js';
19
- import { recordAnswer, recordDefaulted, recordMessageReceipt, recordParked, } from './feed/feed.js';
19
+ import { deriveBlockState, recordAnswer, recordDefaulted, recordMessageReceipt, recordParked, } from './feed/feed.js';
20
20
  import { enqueue, mailboxDir } from './mailbox.js';
21
21
  const POLICY_FILE = 'feed-policy.yaml';
22
22
  const COST_RANK = { low: 0, medium: 1, high: 2 };
@@ -60,7 +60,10 @@ export function blockClass(block) {
60
60
  return block.blockClass === 'decision' ? 'decision' : 'approval';
61
61
  }
62
62
  export function isPhoneUrgent(block, policy) {
63
- if (block.answer)
63
+ // Openness is the canonical deriveBlockState, not a raw `block.answer`: a
64
+ // pending, unconfirmed claim sets `answer` while state stays `open`, and such
65
+ // a block is still waiting on the operator, so it must not silence the alert.
66
+ if (deriveBlockState(block) !== 'open')
64
67
  return false; // already answered
65
68
  const cost = block.costOfDelay ?? 'low';
66
69
  return COST_RANK[cost] >= COST_RANK[policy.phoneNotifyThreshold];
@@ -86,7 +89,10 @@ export function isTimedOut(block, policy, now) {
86
89
  * Caller is responsible for persistence/logging side effects not owned by feed.ts.
87
90
  */
88
91
  export function applyPolicyToBlock(block, policy, now, root, mailboxRoot) {
89
- if (block.answer || block.parkedAt || block.defaultedAt) {
92
+ // A pending, unconfirmed claim sets `block.answer` but keeps state `open`
93
+ // (deriveBlockState), so it must still be able to time out and escalate;
94
+ // parked/defaulted are separate terminal markers deriveBlockState omits.
95
+ if (deriveBlockState(block) !== 'open' || block.parkedAt || block.defaultedAt) {
90
96
  return { blockId: block.blockId, action: 'none' };
91
97
  }
92
98
  if (!isTimedOut(block, policy, now)) {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Native-login inventory for `agents apply` and `agents fleet login`.
2
+ * Native-login inventory for `agents apply`.
3
3
  * Native OAuth/session files are identified only to report device readiness;
4
4
  * agents-cli never serializes or materializes them on another device.
5
5
  *
@@ -28,7 +28,7 @@ export declare const KEYCHAIN_BOUND_ON_MAC: ReadonlySet<string>;
28
28
  * collapsing the fleet to a single working login (droid/WorkOS collapsed 10 boxes
29
29
  * to 1 overnight — RUSH-1958). Add any newly-discovered single-use-rotation
30
30
  * harness here; the predicate below is the one place the propagation decision is
31
- * made. See also `remote-login.ts` and `usage.ts` for the per-machine-login policy.
31
+ * made. See also `usage.ts` for the per-machine-login policy.
32
32
  */
33
33
  export declare const SINGLE_USE_ROTATING_REFRESH_AGENTS: ReadonlySet<string>;
34
34
  /**
@@ -48,59 +48,6 @@ export declare function isCredentialSafeToPropagate(_agent: string): boolean;
48
48
  export declare function hasPortableAuthFiles(agent: string): boolean;
49
49
  /** Which agents `apply` can propagate auth for at all. */
50
50
  export declare function isPropagatableAgent(agent: string): boolean;
51
- /**
52
- * The shape of a login flow — enough for `agents fleet login` to drive a remote
53
- * box's device-code OAuth over SSH and scrape the verification URL + user code.
54
- *
55
- * `flowType` decides remotability:
56
- * - `device-code` — the remote box prints a URL + short code the human enters in
57
- * a browser on THIS machine. The only flow `fleet login` can orchestrate.
58
- * - `loopback` — the CLI opens a browser and listens on 127.0.0.1 on the
59
- * remote box; the redirect must reach that box's own loopback, so it can only
60
- * be completed with a browser on the same machine. Non-remotable.
61
- * - `api-key` — a pasted key, not an OAuth handshake. Non-remotable.
62
- * - `unknown` — flow not yet characterized (no captured output). Non-remotable
63
- * until someone captures the real pattern and fills the regexes.
64
- */
65
- export type LoginFlowType = 'device-code' | 'loopback' | 'api-key' | 'unknown';
66
- export interface LoginFlow {
67
- /**
68
- * The exact command that starts the login flow on the remote box, run as
69
- * `ssh -tt <box> <loginCommand>`. Bare `<cli>` for agents whose device flow
70
- * starts on launch (droid, kimi, antigravity), `<cli> login` for
71
- * codex/grok, `<cli> auth login` for opencode.
72
- */
73
- loginCommand: string;
74
- flowType: LoginFlowType;
75
- /**
76
- * Keystrokes sent after launch to force the device-code path when the CLI
77
- * presents a menu (codex: pick "Sign in with Device Code") or requires a
78
- * sub-command inside a TUI (kimi: `/login`). Sent verbatim through the PTY,
79
- * so it must include the submitting CR (`\r`). Absent when the flow needs no
80
- * steering.
81
- */
82
- deviceCodeSelect?: string;
83
- /** Captures the verification URL from scraped login output (group 1 preferred). */
84
- verificationUrlRegex?: RegExp;
85
- /** Captures the user code from scraped login output (group 1 preferred). */
86
- userCodeRegex?: RegExp;
87
- /**
88
- * Home-relative credential file that appears / bumps mtime on success — the
89
- * completion signal. Mirrors {@link FLEET_AUTH_FILES} (first entry for agents
90
- * with several).
91
- */
92
- successFile: string;
93
- }
94
- /**
95
- * Per-agent login flows, populated from live login-output captures (the only
96
- * ground truth — no fixtures exist upstream). Agents whose real device-code
97
- * output has not been captured are marked honestly (`unknown` / no regexes) so
98
- * `fleet login` flags them non-remotable instead of guessing.
99
- *
100
- * Only the `device-code` entries are ones `fleet login` drives; the rest are
101
- * carried so the command can explain WHY a logged-out pair is not remotable.
102
- */
103
- export declare const FLEET_LOGIN_FLOWS: Record<string, LoginFlow>;
104
51
  export interface SnapshotOptions {
105
52
  /** Home directory to read credential files from. */
106
53
  home: string;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Native-login inventory for `agents apply` and `agents fleet login`.
2
+ * Native-login inventory for `agents apply`.
3
3
  * Native OAuth/session files are identified only to report device readiness;
4
4
  * agents-cli never serializes or materializes them on another device.
5
5
  *
@@ -35,7 +35,7 @@ export const KEYCHAIN_BOUND_ON_MAC = new Set(['claude', 'antigravity']);
35
35
  * collapsing the fleet to a single working login (droid/WorkOS collapsed 10 boxes
36
36
  * to 1 overnight — RUSH-1958). Add any newly-discovered single-use-rotation
37
37
  * harness here; the predicate below is the one place the propagation decision is
38
- * made. See also `remote-login.ts` and `usage.ts` for the per-machine-login policy.
38
+ * made. See also `usage.ts` for the per-machine-login policy.
39
39
  */
40
40
  export const SINGLE_USE_ROTATING_REFRESH_AGENTS = new Set(['droid']);
41
41
  /**
@@ -61,93 +61,6 @@ export function hasPortableAuthFiles(agent) {
61
61
  export function isPropagatableAgent(agent) {
62
62
  return hasPortableAuthFiles(agent) && isCredentialSafeToPropagate(agent);
63
63
  }
64
- /** The success-file for an agent — the primary portable credential (first spec). */
65
- function successFileFor(agent) {
66
- const specs = FLEET_AUTH_FILES[agent];
67
- if (!specs || specs.length === 0)
68
- throw new Error(`no auth file spec for agent '${agent}'`);
69
- return specs[0].rel;
70
- }
71
- /**
72
- * Per-agent login flows, populated from live login-output captures (the only
73
- * ground truth — no fixtures exist upstream). Agents whose real device-code
74
- * output has not been captured are marked honestly (`unknown` / no regexes) so
75
- * `fleet login` flags them non-remotable instead of guessing.
76
- *
77
- * Only the `device-code` entries are ones `fleet login` drives; the rest are
78
- * carried so the command can explain WHY a logged-out pair is not remotable.
79
- */
80
- export const FLEET_LOGIN_FLOWS = {
81
- // droid: launches its TUI and prints, verbatim:
82
- // "If the link does not open automatically, please visit
83
- // https://auth.factory.ai/device and enter code MJQW-NQRM to complete
84
- // authentication." then "Waiting for authentication to complete...".
85
- droid: {
86
- loginCommand: 'droid',
87
- flowType: 'device-code',
88
- verificationUrlRegex: /please visit\s+(https:\/\/\S+?)\s+and enter code/i,
89
- userCodeRegex: /enter code\s+([A-Z0-9]{4}-[A-Z0-9]{4})/i,
90
- successFile: successFileFor('droid'),
91
- },
92
- // codex login shows a numbered menu; option 2 ("Sign in with Device Code",
93
- // "Sign in from another device with a one-time code") is the remotable one.
94
- // The post-selection URL/code output was not captured this session, so the
95
- // regexes are best-effort generic device-code patterns — TODO: tighten once a
96
- // real codex device-code screen is captured.
97
- codex: {
98
- loginCommand: 'codex login',
99
- flowType: 'device-code',
100
- deviceCodeSelect: '\x1b[B\r', // down-arrow once (to option 2) + Enter
101
- verificationUrlRegex: /(https:\/\/\S*(?:device|activate|login|auth)\S*)/i,
102
- userCodeRegex: /\b([A-Z0-9]{4}-[A-Z0-9]{4})\b/,
103
- successFile: successFileFor('codex'),
104
- },
105
- // kimi: launch bare `kimi`, then `/login` inside the TUI, which prints
106
- // "Select a platform and authenticate". Post-selection URL/code not captured —
107
- // best-effort regexes, TODO: tighten with a real capture.
108
- kimi: {
109
- loginCommand: 'kimi',
110
- flowType: 'device-code',
111
- deviceCodeSelect: '/login\r',
112
- verificationUrlRegex: /(https:\/\/\S+)/i,
113
- userCodeRegex: /\b([A-Z0-9]{4}-[A-Z0-9]{4})\b/,
114
- successFile: successFileFor('kimi'),
115
- },
116
- // grok: `grok login`. No captured pattern this session — flowType is
117
- // device-code (best guess for the remotable path) but regexes are left unset
118
- // (TODO: capture grok's login output), so scrapeLogin yields nothing until a
119
- // real pattern is added and driveRemoteLogin will time out rather than guess.
120
- grok: {
121
- loginCommand: 'grok login',
122
- flowType: 'device-code',
123
- successFile: successFileFor('grok'),
124
- },
125
- // antigravity: bare `agy`, Google OAuth. Keychain-bound on macOS and loopback
126
- // (browser + 127.0.0.1 listener on the box itself) — non-remotable.
127
- antigravity: {
128
- loginCommand: 'agy',
129
- flowType: 'loopback',
130
- successFile: successFileFor('antigravity'),
131
- },
132
- // opencode: `opencode auth login`. No captured pattern — unknown/non-remotable.
133
- opencode: {
134
- loginCommand: 'opencode auth login',
135
- flowType: 'unknown',
136
- successFile: successFileFor('opencode'),
137
- },
138
- // claude: interactive `/login` inside the TUI is a loopback browser flow, and
139
- // the token is keychain-bound on macOS. That native login stays per-box and
140
- // is never copied. The shareable `claude setup-token` mint is a different
141
- // credential class — minted by `agents accounts add claude [name]` /
142
- // `agents accounts login claude#<name>` (`lib/auth-mint.ts`), not as a
143
- // fleet-login device-code flow (a setup-token is account-scoped, not
144
- // per-machine).
145
- claude: {
146
- loginCommand: 'claude',
147
- flowType: 'loopback',
148
- successFile: successFileFor('claude'),
149
- },
150
- };
151
64
  /**
152
65
  * Capture portable credential files for the given agents from a source home.
153
66
  * Returns the readable file payloads plus the list of agents whose auth is
@@ -61,7 +61,7 @@ export declare function relTime(iso: string): string;
61
61
  export declare function humanDuration(ms: number): string;
62
62
  /**
63
63
  * Human-readable byte size. Previously copy-pasted into five files
64
- * (`commands/prune.ts`, `commands/share.ts`, `commands/inspect.ts`,
64
+ * (`commands/prune.ts`, `commands/inspect.ts`,
65
65
  * `commands/sessions.ts`, `lib/browser/sessions-list.ts`) — this is the
66
66
  * canonical home.
67
67
  */
@@ -115,7 +115,7 @@ export function humanDuration(ms) {
115
115
  }
116
116
  /**
117
117
  * Human-readable byte size. Previously copy-pasted into five files
118
- * (`commands/prune.ts`, `commands/share.ts`, `commands/inspect.ts`,
118
+ * (`commands/prune.ts`, `commands/inspect.ts`,
119
119
  * `commands/sessions.ts`, `lib/browser/sessions-list.ts`) — this is the
120
120
  * canonical home.
121
121
  */
package/dist/lib/git.d.ts CHANGED
@@ -106,22 +106,6 @@ export declare function readRepoState(repoPath: string): RepoStateSnapshot | nul
106
106
  export declare function resolveSnapshotSha(repoRoot: string): string | undefined;
107
107
  /** Test seam: clear the memoized snapshot-sha cache between test cases. */
108
108
  export declare function _resetSnapshotShaCacheForTest(): void;
109
- /**
110
- * Resolve the GitHub username synchronously for `agents artifacts share`. Order:
111
- * 1. `AGENTS_SHARE_GITHUB_USER` env override
112
- * 2. `git config --global github.user`
113
- * Falls back to null so callers can decide whether to require auth or proceed
114
- * without a namespace.
115
- */
116
- export declare function resolveGitHubUsernameSync(): string | null;
117
- /**
118
- * Resolve the GitHub username asynchronously. Order:
119
- * 1. `AGENTS_SHARE_GITHUB_USER` env override
120
- * 2. `gh api user --jq ".login"`
121
- * 3. `git config --global github.user`
122
- * Returns null if none succeed.
123
- */
124
- export declare function resolveGitHubUsername(): Promise<string | null>;
125
109
  /**
126
110
  * Get the remote URL for origin in a git repo.
127
111
  */
package/dist/lib/git.js CHANGED
@@ -389,64 +389,6 @@ export function resolveSnapshotSha(repoRoot) {
389
389
  export function _resetSnapshotShaCacheForTest() {
390
390
  _snapshotShaCache.clear();
391
391
  }
392
- /**
393
- * Get the current GitHub username using gh CLI.
394
- * Returns null if gh is not installed or user is not authenticated.
395
- */
396
- async function getGitHubUsername() {
397
- try {
398
- const { exec } = await import('child_process');
399
- const { promisify } = await import('util');
400
- const execAsync = promisify(exec);
401
- const { stdout } = await execAsync('gh api user --jq ".login"');
402
- return stdout.trim() || null;
403
- }
404
- catch {
405
- /* gh CLI not installed or not authenticated */
406
- return null;
407
- }
408
- }
409
- const GITHUB_USER_ENV = 'AGENTS_SHARE_GITHUB_USER';
410
- /** Best-effort sync read of `github.user` from git config. */
411
- function readGitConfigUser() {
412
- try {
413
- return execFileSync('git', ['config', '--global', 'github.user'], { stdio: ['ignore', 'pipe', 'ignore'] })
414
- .toString()
415
- .trim() || null;
416
- }
417
- catch {
418
- return null;
419
- }
420
- }
421
- /**
422
- * Resolve the GitHub username synchronously for `agents artifacts share`. Order:
423
- * 1. `AGENTS_SHARE_GITHUB_USER` env override
424
- * 2. `git config --global github.user`
425
- * Falls back to null so callers can decide whether to require auth or proceed
426
- * without a namespace.
427
- */
428
- export function resolveGitHubUsernameSync() {
429
- const env = process.env[GITHUB_USER_ENV]?.trim();
430
- if (env)
431
- return env;
432
- return readGitConfigUser();
433
- }
434
- /**
435
- * Resolve the GitHub username asynchronously. Order:
436
- * 1. `AGENTS_SHARE_GITHUB_USER` env override
437
- * 2. `gh api user --jq ".login"`
438
- * 3. `git config --global github.user`
439
- * Returns null if none succeed.
440
- */
441
- export async function resolveGitHubUsername() {
442
- const env = process.env[GITHUB_USER_ENV]?.trim();
443
- if (env)
444
- return env;
445
- const gh = await getGitHubUsername();
446
- if (gh)
447
- return gh;
448
- return readGitConfigUser();
449
- }
450
392
  /**
451
393
  * Get the remote URL for origin in a git repo.
452
394
  */
@@ -1,14 +1,19 @@
1
1
  export const HARNESS_AUTH = {
2
2
  claude: { login: ['auth', 'login'], status: ['auth', 'status'], identity: 'strong', worker: 'setup-token', slotEnv: 'CLAUDE_CONFIG_DIR' },
3
3
  codex: { login: ['login'], status: ['login', 'status'], identity: 'strong', worker: ['api-key:OPENAI_API_KEY', 'per-device:device-auth'], slotEnv: 'CODEX_HOME' },
4
- grok: { login: ['login'], status: null, identity: 'strong', worker: 'api-key:XAI_API_KEY', slotEnv: 'GROK_HOME' },
4
+ // `--device-auth` pins the device-code flow: bare `grok login` defaults to
5
+ // `--oauth`, the loopback browser flow, which opens whatever browser profile
6
+ // the OS defaults to. The device-code screen prints the URL + code instead,
7
+ // so the human finishes it in the browser profile they choose — which is
8
+ // what makes it usable over an SSH shell on a worker.
9
+ grok: { login: ['login', '--device-auth'], status: null, identity: 'strong', worker: 'api-key:XAI_API_KEY', slotEnv: 'GROK_HOME' },
5
10
  // auth.json has no email claim; identity is the sorted provider-id join
6
11
  // (`resolveOpenCodeAccountId`). NATIVE_ACCOUNT_CAPABILITIES.opencode.inspection
7
12
  // is already 'opaque'.
8
13
  opencode: { login: ['auth', 'login'], status: ['auth', 'list'], identity: 'opaque', worker: 'api-key:provider', slotEnv: 'XDG_DATA_HOME' },
9
14
  cursor: { login: ['login'], status: ['status'], identity: 'strong', worker: 'api-key:CURSOR_API_KEY', slotEnv: null },
10
15
  // No finite login argv — launch bare `kimi`, then `/login` in the TUI
11
- // (`loginHint('kimi') === 'kimi'`, fleet/auth-sync `loginCommand: 'kimi'`).
16
+ // (`loginHint('kimi') === 'kimi'`).
12
17
  kimi: { login: null, status: null, identity: 'opaque', worker: 'none', slotEnv: 'KIMI_CODE_HOME' },
13
18
  antigravity: { login: null, status: null, identity: 'opaque', worker: 'none', slotEnv: null },
14
19
  droid: { login: null, status: null, identity: 'opaque', worker: 'api-key:FACTORY_API_KEY', slotEnv: null },
@@ -41,7 +41,7 @@
41
41
  * it would claim a distribution responsibility agents-cli no longer has.
42
42
  */
43
43
  export const HELPER_RELEASES = {
44
- menubar: { tagPrefix: 'menubar', floor: '1.4.0' },
44
+ menubar: { tagPrefix: 'menubar', floor: '1.5.0' },
45
45
  };
46
46
  /** The release tag for one helper at one version, e.g. `menubar/v1.0.0`. */
47
47
  export function helperTag(helper, version) {
@@ -1,4 +1,5 @@
1
1
  import type { Host } from './types.js';
2
+ import { remoteShellFor } from './remote-cmd.js';
2
3
  import { type HostTask } from './tasks.js';
3
4
  import { type HostCredentials } from './credentials.js';
4
5
  import { deriveMirroredCwd, homeRemainder, remoteCdPrefix } from '../project-root.js';
@@ -31,6 +32,13 @@ export declare function withActorEnv(env?: Record<string, string>): Record<strin
31
32
  * place.
32
33
  */
33
34
  export declare function remoteRunShellPrelude(agent: string, extra?: Record<string, string>): string;
35
+ /**
36
+ * The env every remote `agents run` dispatch carries across the SSH hop —
37
+ * actor provenance, the `run auto` chain-hop guard, and the caller's path
38
+ * markers. `remoteRunShellPrelude` renders it as POSIX exports; a Windows peer
39
+ * gets the same record as `$env:` assignments in its PowerShell script.
40
+ */
41
+ export declare function remoteRunEnv(agent: string, extra?: Record<string, string>): Record<string, string>;
34
42
  /**
35
43
  * Launch a detached login-shell command in its own Unix session/process group.
36
44
  *
@@ -146,7 +154,7 @@ export interface DispatchOptions {
146
154
  copyCreds?: HostCredentials;
147
155
  }
148
156
  export declare function buildRunForwardedArgs(opts: DispatchOptions): string[];
149
- interface InteractiveDispatchOptions {
157
+ export interface InteractiveDispatchOptions {
150
158
  agent: string;
151
159
  /** Explicit agent version pin (e.g. "2.1.207") to forward as `agent@version`. */
152
160
  version?: string;
@@ -199,6 +207,17 @@ interface InteractiveDispatchOptions {
199
207
  * infer headless from the prompt).
200
208
  */
201
209
  export declare function buildInteractiveRunForwardedArgs(opts: InteractiveDispatchOptions): string[];
210
+ /**
211
+ * The remote command of an interactive host dispatch, by peer shell.
212
+ *
213
+ * A Windows peer's sshd hands the command to PowerShell, which cannot parse the
214
+ * POSIX `export …; { cd … || cd "$HOME"; } && agents …` form (a `run --device
215
+ * <windows box>` from a TTY died with `At line:1 char:420` at the `||`). That
216
+ * peer gets the rendered PowerShell every other `--device` site uses — env,
217
+ * mirrored cwd and argv inside one `-EncodedCommand` — the interactive analogue
218
+ * of `buildWindowsDetachedLaunchCommand`.
219
+ */
220
+ export declare function buildInteractiveRemoteCommand(remoteShell: ReturnType<typeof remoteShellFor>, opts: InteractiveDispatchOptions): string;
202
221
  /**
203
222
  * Run an agent interactively on a host, forwarding the local TTY over SSH.
204
223
  * Returns the SSH exit code. The remote `agents` CLI is responsible for its own
@@ -13,7 +13,7 @@ import { randomUUID } from 'crypto';
13
13
  import { sshExec, sshStream, shellQuote } from '../ssh-exec.js';
14
14
  import { hostIdentityArgs, sshTargetFor } from './types.js';
15
15
  import { ensureHostReady } from './ready.js';
16
- import { encodePowershell, powershellQuote, remoteShellFor, posixEnvExports } from './remote-cmd.js';
16
+ import { buildWindowsAgentsCommand, encodePowershell, powershellQuote, remoteShellFor, posixEnvExports, windowsRemotePath, windowsSetLocation } from './remote-cmd.js';
17
17
  import { resolveRemoteOsSync } from './remote-os.js';
18
18
  import { resolveActor, actorEnv } from '../actor.js';
19
19
  import { saveTask, updateTask, terminalPatch } from './tasks.js';
@@ -85,10 +85,19 @@ export function withActorEnv(env) {
85
85
  * place.
86
86
  */
87
87
  export function remoteRunShellPrelude(agent, extra = {}) {
88
- const guard = agent === RUN_AUTO_KEYWORD ? { [RUN_AUTO_HOST_RESOLVED_ENV]: '1' } : {};
89
- const exports = posixEnvExports(withActorEnv({ ...guard, ...extra }));
88
+ const exports = posixEnvExports(remoteRunEnv(agent, extra));
90
89
  return exports ? `${exports}; ` : '';
91
90
  }
91
+ /**
92
+ * The env every remote `agents run` dispatch carries across the SSH hop —
93
+ * actor provenance, the `run auto` chain-hop guard, and the caller's path
94
+ * markers. `remoteRunShellPrelude` renders it as POSIX exports; a Windows peer
95
+ * gets the same record as `$env:` assignments in its PowerShell script.
96
+ */
97
+ export function remoteRunEnv(agent, extra = {}) {
98
+ const guard = agent === RUN_AUTO_KEYWORD ? { [RUN_AUTO_HOST_RESOLVED_ENV]: '1' } : {};
99
+ return withActorEnv({ ...guard, ...extra });
100
+ }
92
101
  /**
93
102
  * Launch a detached login-shell command in its own Unix session/process group.
94
103
  *
@@ -107,21 +116,12 @@ export function buildDetachedLaunchCommand(inner) {
107
116
  ].join(' ');
108
117
  return `bash -lc ${shellQuote(`node -e ${shellQuote(nodeScript)}`)}`;
109
118
  }
110
- function windowsRemotePath(path) {
111
- return path.startsWith('$HOME/')
112
- ? `(Join-Path $HOME ${powershellQuote(path.slice('$HOME/'.length))})`
113
- : powershellQuote(path);
114
- }
115
119
  /** Build the detached PowerShell launch protocol used by Windows SSH hosts. */
116
120
  export function buildWindowsDetachedLaunchCommand(opts) {
117
121
  const log = windowsRemotePath(opts.remoteLog);
118
122
  const exit = windowsRemotePath(opts.remoteExit);
119
123
  const env = Object.entries(opts.env).map(([key, value]) => `$env:${key} = ${powershellQuote(value)}`);
120
- const cwd = opts.remoteCwd
121
- ? opts.mirrorCwd
122
- ? `try { Set-Location -LiteralPath ${powershellQuote(opts.remoteCwd)} } catch { Set-Location -LiteralPath $HOME }`
123
- : `Set-Location -LiteralPath ${powershellQuote(opts.remoteCwd)}`
124
- : '';
124
+ const cwd = opts.remoteCwd ? windowsSetLocation(opts.remoteCwd, opts.mirrorCwd) : '';
125
125
  const inner = [
126
126
  `$ProgressPreference = 'SilentlyContinue'`,
127
127
  `$PSDefaultParameterValues['Out-File:Encoding'] = 'utf8'`,
@@ -273,7 +273,7 @@ async function launchDetached(host, target, opts) {
273
273
  mirrorCwd: opts.mirrorCwd,
274
274
  remoteLog,
275
275
  remoteExit,
276
- env: withActorEnv(opts.agentLabel === RUN_AUTO_KEYWORD ? { [RUN_AUTO_HOST_RESOLVED_ENV]: '1' } : {}),
276
+ env: remoteRunEnv(opts.agentLabel),
277
277
  })
278
278
  : `mkdir -p ${REMOTE_DIR}; ${buildDetachedLaunchCommand(inner)}`;
279
279
  const res = sshExec(target, launch, {
@@ -471,19 +471,17 @@ export function buildInteractiveRunForwardedArgs(opts) {
471
471
  return args;
472
472
  }
473
473
  /**
474
- * Run an agent interactively on a host, forwarding the local TTY over SSH.
475
- * Returns the SSH exit code. The remote `agents` CLI is responsible for its own
476
- * tmux wrapping; the local machine is just the transport.
474
+ * The remote command of an interactive host dispatch, by peer shell.
475
+ *
476
+ * A Windows peer's sshd hands the command to PowerShell, which cannot parse the
477
+ * POSIX `export …; { cd … || cd "$HOME"; } && agents …` form (a `run --device
478
+ * <windows box>` from a TTY died with `At line:1 char:420` at the `||`). That
479
+ * peer gets the rendered PowerShell every other `--device` site uses — env,
480
+ * mirrored cwd and argv inside one `-EncodedCommand` — the interactive analogue
481
+ * of `buildWindowsDetachedLaunchCommand`.
477
482
  */
478
- export async function runInteractiveOnHost(host, opts) {
479
- const target = sshTargetFor(host);
480
- // Concrete version pins fail loud here (RUSH-2313) so we never open a TTY
481
- // to a box that cannot run the pin.
482
- const { warnings } = ensureHostReady(host, { agent: opts.agent, version: opts.version });
483
- for (const w of warnings)
484
- process.stderr.write(`[hosts] warning: ${w}\n`);
485
- const invocation = ['agents', ...buildInteractiveRunForwardedArgs(opts)].map(shellQuote).join(' ');
486
- const cwd = remoteCdPrefix(opts.remoteCwd, { mirror: opts.mirrorCwd });
483
+ export function buildInteractiveRemoteCommand(remoteShell, opts) {
484
+ const forwardedArgs = buildInteractiveRunForwardedArgs(opts);
487
485
  // Forward actor provenance so the interactive remote run inherits it rather
488
486
  // than re-resolving from this box's SSH_CONNECTION (RUSH-2028); a `run auto`
489
487
  // dispatch also gets the chain-hop guard (remoteRunShellPrelude).
@@ -496,11 +494,35 @@ export async function runInteractiveOnHost(host, opts) {
496
494
  // longer forces the tmux wrap on a TTY-followed run — the peer's
497
495
  // tmux.enabled decides that. Set here, on the interactive path only:
498
496
  // `launchDetached` already setsids the headless one.
499
- const prelude = remoteRunShellPrelude(opts.agent, { [REMOTE_INTERACTIVE_ENV]: '1' });
500
- let remoteCmd = `${prelude}${cwd}${invocation}`;
501
- if (opts.copyCreds) {
502
- remoteCmd = wrapHostCommandWithCredentials(remoteCmd, opts.copyCreds);
497
+ const env = { [REMOTE_INTERACTIVE_ENV]: '1' };
498
+ if (remoteShell === 'powershell') {
499
+ if (opts.copyCreds)
500
+ throw new Error('--copy-creds cannot ride an interactive dispatch to a Windows host');
501
+ return buildWindowsAgentsCommand({
502
+ args: forwardedArgs,
503
+ env: remoteRunEnv(opts.agent, env),
504
+ cwd: opts.remoteCwd,
505
+ mirrorCwd: opts.mirrorCwd,
506
+ });
503
507
  }
508
+ const invocation = ['agents', ...forwardedArgs].map(shellQuote).join(' ');
509
+ const cwd = remoteCdPrefix(opts.remoteCwd, { mirror: opts.mirrorCwd });
510
+ const remoteCmd = `${remoteRunShellPrelude(opts.agent, env)}${cwd}${invocation}`;
511
+ return opts.copyCreds ? wrapHostCommandWithCredentials(remoteCmd, opts.copyCreds) : remoteCmd;
512
+ }
513
+ /**
514
+ * Run an agent interactively on a host, forwarding the local TTY over SSH.
515
+ * Returns the SSH exit code. The remote `agents` CLI is responsible for its own
516
+ * tmux wrapping; the local machine is just the transport.
517
+ */
518
+ export async function runInteractiveOnHost(host, opts) {
519
+ const target = sshTargetFor(host);
520
+ // Concrete version pins fail loud here (RUSH-2313) so we never open a TTY
521
+ // to a box that cannot run the pin.
522
+ const { warnings } = ensureHostReady(host, { agent: opts.agent, version: opts.version });
523
+ for (const w of warnings)
524
+ process.stderr.write(`[hosts] warning: ${w}\n`);
525
+ const remoteCmd = buildInteractiveRemoteCommand(remoteShellFor(host.os ?? resolveRemoteOsSync(host.name)), opts);
504
526
  // Credentials ride this stream when --copy-creds is set: verify the host key
505
527
  // strictly against the managed pin and force a fresh connection so a stale
506
528
  // accept-new control socket can't bypass the check (RUSH-1767).