@phnx-labs/agents-cli 1.22.84 → 1.22.86

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 (259) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +80 -53
  3. package/dist/bootstrap.d.ts +7 -6
  4. package/dist/bootstrap.js +20 -27
  5. package/dist/cli/command-registry.js +5 -1
  6. package/dist/commands/accounts.d.ts +6 -13
  7. package/dist/commands/accounts.js +330 -143
  8. package/dist/commands/alias.js +7 -3
  9. package/dist/commands/apply.js +1 -1
  10. package/dist/commands/auth-mint.d.ts +7 -3
  11. package/dist/commands/auth-mint.js +21 -9
  12. package/dist/commands/auth.js +2 -3
  13. package/dist/commands/browser.js +10 -10
  14. package/dist/commands/daemon.d.ts +7 -3
  15. package/dist/commands/daemon.js +40 -25
  16. package/dist/commands/doctor.js +28 -2
  17. package/dist/commands/exec.js +87 -39
  18. package/dist/commands/fleet-capture.js +21 -2
  19. package/dist/commands/harness-wizard.js +2 -2
  20. package/dist/commands/lease.js +7 -12
  21. package/dist/commands/profiles.d.ts +2 -2
  22. package/dist/commands/profiles.js +20 -13
  23. package/dist/commands/repo.js +3 -3
  24. package/dist/commands/run-account-picker.d.ts +3 -3
  25. package/dist/commands/run-account-picker.js +29 -30
  26. package/dist/commands/secrets-passthrough.d.ts +20 -0
  27. package/dist/commands/secrets-passthrough.js +43 -0
  28. package/dist/commands/setup-accounts.d.ts +3 -2
  29. package/dist/commands/setup-accounts.js +4 -4
  30. package/dist/commands/setup-secrets.d.ts +16 -21
  31. package/dist/commands/setup-secrets.js +43 -224
  32. package/dist/commands/ssh.js +2 -2
  33. package/dist/commands/sync.js +1 -1
  34. package/dist/commands/update.d.ts +10 -0
  35. package/dist/commands/update.js +43 -40
  36. package/dist/commands/versions.d.ts +11 -11
  37. package/dist/commands/versions.js +131 -83
  38. package/dist/commands/view.d.ts +1 -9
  39. package/dist/commands/view.js +41 -55
  40. package/dist/commands/webhook.js +5 -5
  41. package/dist/commands/workflows.js +2 -1
  42. package/dist/index.d.ts +12 -12
  43. package/dist/index.js +13 -42
  44. package/dist/lib/account-capabilities.d.ts +1 -1
  45. package/dist/lib/account-capabilities.js +1 -1
  46. package/dist/lib/account-catalog.d.ts +121 -6
  47. package/dist/lib/account-catalog.js +421 -7
  48. package/dist/lib/account-registry.d.ts +8 -30
  49. package/dist/lib/account-registry.js +36 -79
  50. package/dist/lib/account-schema.d.ts +1 -1
  51. package/dist/lib/account-schema.js +1 -1
  52. package/dist/lib/accounting/account-pool-collect.js +16 -4
  53. package/dist/lib/accounting/rotate.d.ts +20 -7
  54. package/dist/lib/accounting/rotate.js +89 -13
  55. package/dist/lib/accounting/usage.js +11 -16
  56. package/dist/lib/accounts/add.d.ts +138 -0
  57. package/dist/lib/accounts/add.js +651 -0
  58. package/dist/lib/accounts/migrate.d.ts +117 -0
  59. package/dist/lib/accounts/migrate.js +536 -0
  60. package/dist/lib/accounts/slots.d.ts +13 -0
  61. package/dist/lib/accounts/slots.js +100 -0
  62. package/dist/lib/agent-spec/agents.d.ts +16 -0
  63. package/dist/lib/agent-spec/agents.js +38 -4
  64. package/dist/lib/app-bundle-install.js +5 -4
  65. package/dist/lib/auth-health.d.ts +3 -0
  66. package/dist/lib/auth-health.js +11 -0
  67. package/dist/lib/auth-mint.d.ts +52 -16
  68. package/dist/lib/auth-mint.js +117 -39
  69. package/dist/lib/browser/chrome.d.ts +1 -1
  70. package/dist/lib/browser/chrome.js +5 -5
  71. package/dist/lib/byok-usage.js +3 -3
  72. package/dist/lib/claude-account-token.d.ts +47 -3
  73. package/dist/lib/claude-account-token.js +172 -50
  74. package/dist/lib/cloud/antigravity.js +4 -4
  75. package/dist/lib/cloud/cursor.js +4 -4
  76. package/dist/lib/crabbox/cli.d.ts +1 -1
  77. package/dist/lib/crabbox/cli.js +6 -6
  78. package/dist/lib/crabbox/runtimes.d.ts +3 -3
  79. package/dist/lib/crabbox/runtimes.js +5 -5
  80. package/dist/lib/daemon/account-state-daemon-service.d.ts +37 -1
  81. package/dist/lib/daemon/account-state-daemon-service.js +199 -3
  82. package/dist/lib/daemon/auth-sync-service.js +32 -1
  83. package/dist/lib/daemon/daemon.d.ts +6 -16
  84. package/dist/lib/daemon/daemon.js +52 -97
  85. package/dist/lib/daemon/daemon.test-fixture.d.ts +7 -9
  86. package/dist/lib/daemon/daemon.test-fixture.js +22 -40
  87. package/dist/lib/daemon/harness-update-service.d.ts +1 -1
  88. package/dist/lib/daemon/harness-update-service.js +1 -1
  89. package/dist/lib/daemon/runner.d.ts +15 -3
  90. package/dist/lib/daemon/runner.js +49 -24
  91. package/dist/lib/daemon-health.d.ts +1 -2
  92. package/dist/lib/daemon-health.js +7 -6
  93. package/dist/lib/daemon-services.d.ts +1 -1
  94. package/dist/lib/daemon-services.js +1 -11
  95. package/dist/lib/daemon-webhooks.d.ts +8 -7
  96. package/dist/lib/daemon-webhooks.js +10 -9
  97. package/dist/lib/device-config.js +1 -1
  98. package/dist/lib/devices/doctor-findings.d.ts +1 -1
  99. package/dist/lib/devices/harness-inventory.d.ts +39 -0
  100. package/dist/lib/devices/harness-inventory.js +126 -4
  101. package/dist/lib/doctor-diff.js +2 -1
  102. package/dist/lib/exec-account-home.d.ts +38 -0
  103. package/dist/lib/exec-account-home.js +164 -0
  104. package/dist/lib/exec.d.ts +30 -0
  105. package/dist/lib/exec.js +101 -31
  106. package/dist/lib/fleet/apply.d.ts +2 -2
  107. package/dist/lib/fleet/apply.js +4 -4
  108. package/dist/lib/fleet/auth-sync.js +4 -3
  109. package/dist/lib/fleet-shared-repo-sync.js +6 -9
  110. package/dist/lib/harness/adapter.d.ts +7 -7
  111. package/dist/lib/harness/adapter.js +10 -6
  112. package/dist/lib/harness/adapters/grok.js +8 -3
  113. package/dist/lib/harness/adapters/muse.js +1 -1
  114. package/dist/lib/harness/adapters/opencode.js +12 -3
  115. package/dist/lib/harness-auth-capabilities.d.ts +61 -0
  116. package/dist/lib/harness-auth-capabilities.js +52 -0
  117. package/dist/lib/helper-versions.d.ts +6 -4
  118. package/dist/lib/helper-versions.js +5 -4
  119. package/dist/lib/hosts/credential-transport.d.ts +10 -0
  120. package/dist/lib/hosts/credential-transport.js +37 -0
  121. package/dist/lib/hosts/dispatch.d.ts +7 -0
  122. package/dist/lib/hosts/dispatch.js +13 -6
  123. package/dist/lib/identity/client.d.ts +3 -3
  124. package/dist/lib/identity/client.js +3 -3
  125. package/dist/lib/installations/index.d.ts +1 -1
  126. package/dist/lib/installations/index.js +1 -1
  127. package/dist/lib/installations/migrate.js +9 -0
  128. package/dist/lib/installations/resolve.js +1 -1
  129. package/dist/lib/installations/shims.d.ts +21 -4
  130. package/dist/lib/installations/shims.js +86 -5
  131. package/dist/lib/installations/store.d.ts +43 -0
  132. package/dist/lib/installations/store.js +75 -7
  133. package/dist/lib/installations/versions.js +1 -1
  134. package/dist/lib/menubar/install-menubar.d.ts +3 -3
  135. package/dist/lib/menubar/install-menubar.js +3 -3
  136. package/dist/lib/native-accounts.d.ts +35 -0
  137. package/dist/lib/native-accounts.js +41 -0
  138. package/dist/lib/net-close.d.ts +11 -0
  139. package/dist/lib/net-close.js +25 -0
  140. package/dist/lib/openclaw-keychain.js +27 -1
  141. package/dist/lib/profiles.js +22 -9
  142. package/dist/lib/project-resources.js +1 -1
  143. package/dist/lib/reserved-stores.d.ts +53 -0
  144. package/dist/lib/reserved-stores.js +120 -0
  145. package/dist/lib/secrets-client.d.ts +199 -0
  146. package/dist/lib/secrets-client.js +718 -0
  147. package/dist/lib/secrets-policy.d.ts +235 -0
  148. package/dist/lib/secrets-policy.js +505 -0
  149. package/dist/lib/secrets-types.d.ts +188 -0
  150. package/dist/lib/secrets-types.js +24 -0
  151. package/dist/lib/self-heal/checks/shims.js +5 -3
  152. package/dist/lib/service-manifest.js +3 -4
  153. package/dist/lib/session/db.d.ts +18 -0
  154. package/dist/lib/session/db.js +47 -14
  155. package/dist/lib/session/sync/config.js +2 -2
  156. package/dist/lib/sha256-asset.d.ts +11 -16
  157. package/dist/lib/sha256-asset.js +11 -16
  158. package/dist/lib/share/config.js +14 -5
  159. package/dist/lib/signin-badge.d.ts +26 -0
  160. package/dist/lib/signin-badge.js +42 -0
  161. package/dist/lib/staleness/detectors/workflows.js +3 -105
  162. package/dist/lib/staleness/writers/workflows.js +2 -1
  163. package/dist/lib/state.d.ts +7 -4
  164. package/dist/lib/state.js +18 -7
  165. package/dist/lib/sync-umbrella.js +2 -2
  166. package/dist/lib/teams/agents.js +1 -1
  167. package/dist/lib/types.d.ts +74 -33
  168. package/dist/lib/view-types.d.ts +6 -3
  169. package/dist/lib/workflows-registry.d.ts +71 -0
  170. package/dist/lib/workflows-registry.js +280 -0
  171. package/dist/lib/workflows.d.ts +21 -21
  172. package/dist/lib/workflows.js +12 -327
  173. package/package.json +2 -3
  174. package/scripts/postinstall.js +35 -30
  175. package/dist/commands/secrets-import.d.ts +0 -18
  176. package/dist/commands/secrets-import.js +0 -74
  177. package/dist/commands/secrets-migrate.d.ts +0 -25
  178. package/dist/commands/secrets-migrate.js +0 -334
  179. package/dist/commands/secrets-rotate-passphrase.d.ts +0 -17
  180. package/dist/commands/secrets-rotate-passphrase.js +0 -96
  181. package/dist/commands/secrets-sync.d.ts +0 -11
  182. package/dist/commands/secrets-sync.js +0 -153
  183. package/dist/commands/secrets-vault.d.ts +0 -10
  184. package/dist/commands/secrets-vault.js +0 -130
  185. package/dist/commands/secrets.d.ts +0 -212
  186. package/dist/commands/secrets.js +0 -3030
  187. package/dist/lib/accounts/connect.d.ts +0 -176
  188. package/dist/lib/accounts/connect.js +0 -453
  189. package/dist/lib/daemon/keychain-reap-service.d.ts +0 -17
  190. package/dist/lib/daemon/keychain-reap-service.js +0 -32
  191. package/dist/lib/daemon/secrets-broker-service.d.ts +0 -21
  192. package/dist/lib/daemon/secrets-broker-service.js +0 -51
  193. package/dist/lib/secrets/agent.d.ts +0 -358
  194. package/dist/lib/secrets/agent.js +0 -1291
  195. package/dist/lib/secrets/audit.d.ts +0 -46
  196. package/dist/lib/secrets/audit.js +0 -101
  197. package/dist/lib/secrets/bundles.d.ts +0 -290
  198. package/dist/lib/secrets/bundles.js +0 -1547
  199. package/dist/lib/secrets/download-keychain.d.ts +0 -47
  200. package/dist/lib/secrets/download-keychain.js +0 -70
  201. package/dist/lib/secrets/drivers/rush.d.ts +0 -14
  202. package/dist/lib/secrets/drivers/rush.js +0 -90
  203. package/dist/lib/secrets/fallback.d.ts +0 -48
  204. package/dist/lib/secrets/fallback.js +0 -48
  205. package/dist/lib/secrets/filestore.d.ts +0 -222
  206. package/dist/lib/secrets/filestore.js +0 -1099
  207. package/dist/lib/secrets/headless.d.ts +0 -43
  208. package/dist/lib/secrets/headless.js +0 -56
  209. package/dist/lib/secrets/icloud-import.d.ts +0 -79
  210. package/dist/lib/secrets/icloud-import.js +0 -206
  211. package/dist/lib/secrets/index.d.ts +0 -451
  212. package/dist/lib/secrets/index.js +0 -1568
  213. package/dist/lib/secrets/install-helper.d.ts +0 -72
  214. package/dist/lib/secrets/install-helper.js +0 -245
  215. package/dist/lib/secrets/lease.d.ts +0 -25
  216. package/dist/lib/secrets/lease.js +0 -44
  217. package/dist/lib/secrets/linux.d.ts +0 -77
  218. package/dist/lib/secrets/linux.js +0 -393
  219. package/dist/lib/secrets/list-filter.d.ts +0 -109
  220. package/dist/lib/secrets/list-filter.js +0 -261
  221. package/dist/lib/secrets/mcp.d.ts +0 -93
  222. package/dist/lib/secrets/mcp.js +0 -211
  223. package/dist/lib/secrets/profiles.d.ts +0 -10
  224. package/dist/lib/secrets/profiles.js +0 -13
  225. package/dist/lib/secrets/push.d.ts +0 -133
  226. package/dist/lib/secrets/push.js +0 -273
  227. package/dist/lib/secrets/rc-hygiene.d.ts +0 -63
  228. package/dist/lib/secrets/rc-hygiene.js +0 -143
  229. package/dist/lib/secrets/read-backoff.d.ts +0 -27
  230. package/dist/lib/secrets/read-backoff.js +0 -64
  231. package/dist/lib/secrets/reaper.d.ts +0 -90
  232. package/dist/lib/secrets/reaper.js +0 -243
  233. package/dist/lib/secrets/remote.d.ts +0 -152
  234. package/dist/lib/secrets/remote.js +0 -344
  235. package/dist/lib/secrets/reserved-sync.d.ts +0 -66
  236. package/dist/lib/secrets/reserved-sync.js +0 -147
  237. package/dist/lib/secrets/scope.d.ts +0 -26
  238. package/dist/lib/secrets/scope.js +0 -29
  239. package/dist/lib/secrets/session-store.d.ts +0 -107
  240. package/dist/lib/secrets/session-store.js +0 -342
  241. package/dist/lib/secrets/sync-backend.d.ts +0 -48
  242. package/dist/lib/secrets/sync-backend.js +0 -13
  243. package/dist/lib/secrets/sync-commands.d.ts +0 -21
  244. package/dist/lib/secrets/sync-commands.js +0 -21
  245. package/dist/lib/secrets/sync.d.ts +0 -48
  246. package/dist/lib/secrets/sync.js +0 -237
  247. package/dist/lib/secrets/unlock-hints.d.ts +0 -27
  248. package/dist/lib/secrets/unlock-hints.js +0 -36
  249. package/dist/lib/secrets/usage-db.d.ts +0 -46
  250. package/dist/lib/secrets/usage-db.js +0 -96
  251. package/dist/lib/secrets/vault-age-helper.d.ts +0 -1
  252. package/dist/lib/secrets/vault-age-helper.js +0 -34
  253. package/dist/lib/secrets/vault.d.ts +0 -49
  254. package/dist/lib/secrets/vault.js +0 -399
  255. package/dist/lib/secrets/windows.d.ts +0 -81
  256. package/dist/lib/secrets/windows.js +0 -556
  257. package/scripts/install-helper.js +0 -97
  258. /package/dist/lib/{secrets/sync-passphrase.d.ts → sync-passphrase.d.ts} +0 -0
  259. /package/dist/lib/{secrets/sync-passphrase.js → sync-passphrase.js} +0 -0
@@ -138,9 +138,10 @@ export const FLEET_LOGIN_FLOWS = {
138
138
  // claude: interactive `/login` inside the TUI is a loopback browser flow, and
139
139
  // the token is keychain-bound on macOS. That native login stays per-box and
140
140
  // is never copied. The shareable `claude setup-token` mint is a different
141
- // credential class — wired as `agents accounts mint claude` / `agents auth mint`
142
- // (`lib/auth-mint.ts`), not as a fleet-login device-code flow (a setup-token
143
- // is account-scoped, not per-machine).
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).
144
145
  claude: {
145
146
  loginCommand: 'claude',
146
147
  flowType: 'loopback',
@@ -267,15 +267,12 @@ async function performFleetSharedRepoSync(root, device, timeoutMs) {
267
267
  }
268
268
  const ownedFile = fleetSharedStatePath(device, root);
269
269
  const relativeOwnedFile = path.relative(root, ownedFile).split(path.sep).join('/');
270
- // The central `agents.yaml` is fleet-shared state too — `agents accounts label`
271
- // writes version-scoped native account rows there (lib/state.ts writeMetaUnlocked),
272
- // as a plain file write with no commit. Publishing only the per-device file and
273
- // then `rebase --autostash`-ing over a dirty central file silently destroyed
274
- // those labels: every box lost its account labels on its next daemon publish
275
- // (PHNX-3887). Commit it alongside the device doc so the rebase carries the
276
- // rows instead of stashing them, which is also what `accounts label --help`
277
- // already promises ("labels live on the central account rows in agents.yaml,
278
- // which repo push/pull already syncs fleet-wide").
270
+ // The central `agents.yaml` is fleet-shared state too — native account rows
271
+ // land there (lib/state.ts writeMetaUnlocked) as a plain file write with no
272
+ // commit. Publishing only the per-device file and then `rebase --autostash`-ing
273
+ // over a dirty central file silently destroyed those names: every box lost its
274
+ // account names on its next daemon publish (PHNX-3887). Commit it alongside the
275
+ // device doc so the rebase carries the rows instead of stashing them.
279
276
  const centralFile = path.join(root, 'agents.yaml');
280
277
  const publishPaths = [relativeOwnedFile];
281
278
  if (fs.existsSync(centralFile))
@@ -150,16 +150,16 @@ export interface HarnessAdapter {
150
150
  routineModeArgs?(cmd: string[], ctx: RoutineLaunchCtx): void;
151
151
  }
152
152
  /**
153
- * The four config-dir env keys a harness pins to its version home. Every
154
- * per-harness branch in the old buildExecEnv deleted the ones it does NOT set,
155
- * so its own config pointer never leaks into a foreign harness's invocation.
156
- * (XDG_CONFIG_HOME / XDG_DATA_HOME — Muse/Cursor — are deliberately absent: the
157
- * old code sets them but never strips them, and this preserves that exactly.)
153
+ * Config-dir env keys a harness pins to its slot / version home. Every
154
+ * per-harness branch in buildExecEnv deletes the ones it does NOT set, so a
155
+ * slot never inherits another account's dir (PHNX-3940 T5). GROK_HOME,
156
+ * OPENCODE_CONFIG_DIR, and the XDG pair were previously omitted, which let a
157
+ * parent agent's pin leak into a sibling slot.
158
158
  */
159
- export declare const CONFIG_DIR_ENV_KEYS: readonly ["CLAUDE_CONFIG_DIR", "CODEX_HOME", "COPILOT_HOME", "KIMI_CODE_HOME"];
159
+ export declare const CONFIG_DIR_ENV_KEYS: readonly ["CLAUDE_CONFIG_DIR", "CODEX_HOME", "COPILOT_HOME", "KIMI_CODE_HOME", "GROK_HOME", "OPENCODE_CONFIG_DIR", "XDG_CONFIG_HOME", "XDG_DATA_HOME"];
160
160
  /**
161
161
  * Strip every config-dir env key except the one(s) this harness sets. `keep=[]`
162
- * (the default / no-config-dir harness) deletes all four — the old `else` arm.
162
+ * (the default / no-config-dir harness) deletes all of them — the old `else` arm.
163
163
  */
164
164
  export declare function stripForeignConfigDir(result: NodeJS.ProcessEnv, keep?: readonly string[]): void;
165
165
  export declare function registerHarnessAdapter(adapter: HarnessAdapter): void;
@@ -1,19 +1,23 @@
1
1
  /**
2
- * The four config-dir env keys a harness pins to its version home. Every
3
- * per-harness branch in the old buildExecEnv deleted the ones it does NOT set,
4
- * so its own config pointer never leaks into a foreign harness's invocation.
5
- * (XDG_CONFIG_HOME / XDG_DATA_HOME — Muse/Cursor — are deliberately absent: the
6
- * old code sets them but never strips them, and this preserves that exactly.)
2
+ * Config-dir env keys a harness pins to its slot / version home. Every
3
+ * per-harness branch in buildExecEnv deletes the ones it does NOT set, so a
4
+ * slot never inherits another account's dir (PHNX-3940 T5). GROK_HOME,
5
+ * OPENCODE_CONFIG_DIR, and the XDG pair were previously omitted, which let a
6
+ * parent agent's pin leak into a sibling slot.
7
7
  */
8
8
  export const CONFIG_DIR_ENV_KEYS = [
9
9
  'CLAUDE_CONFIG_DIR',
10
10
  'CODEX_HOME',
11
11
  'COPILOT_HOME',
12
12
  'KIMI_CODE_HOME',
13
+ 'GROK_HOME',
14
+ 'OPENCODE_CONFIG_DIR',
15
+ 'XDG_CONFIG_HOME',
16
+ 'XDG_DATA_HOME',
13
17
  ];
14
18
  /**
15
19
  * Strip every config-dir env key except the one(s) this harness sets. `keep=[]`
16
- * (the default / no-config-dir harness) deletes all four — the old `else` arm.
20
+ * (the default / no-config-dir harness) deletes all of them — the old `else` arm.
17
21
  */
18
22
  export function stripForeignConfigDir(result, keep = []) {
19
23
  for (const key of CONFIG_DIR_ENV_KEYS) {
@@ -1,8 +1,13 @@
1
- // Grok has no exec-time config-dir env pin (buildExecEnv's `else` strips the
2
- // foreign keys — the registry fallback preserves that). It DOES relocate its
3
- // whole config tree via GROK_HOME in the generated shim.
1
+ import * as path from 'path';
2
+ import { stripForeignConfigDir } from '../adapter.js';
4
3
  export const grokAdapter = {
5
4
  id: 'grok',
5
+ applyExecConfigEnv(result, ctx) {
6
+ if (ctx.versionHome) {
7
+ result.GROK_HOME = path.join(ctx.versionHome, '.grok');
8
+ }
9
+ stripForeignConfigDir(result, ['GROK_HOME']);
10
+ },
6
11
  shimConfigEnvBash() {
7
12
  return `
8
13
  # Grok Build uses GROK_HOME to isolate its entire configuration tree
@@ -13,7 +13,7 @@ export const museAdapter = {
13
13
  result.XDG_CONFIG_HOME = path.join(ctx.versionHome, '.config');
14
14
  result.XDG_DATA_HOME = path.join(ctx.versionHome, '.local', 'share');
15
15
  }
16
- stripForeignConfigDir(result);
16
+ stripForeignConfigDir(result, ['XDG_CONFIG_HOME', 'XDG_DATA_HOME']);
17
17
  },
18
18
  shimConfigEnvBash() {
19
19
  return `
@@ -1,8 +1,17 @@
1
- // OpenCode has no exec-time config-dir env pin (buildExecEnv's `else` strips the
2
- // foreign keys — the registry fallback preserves that). It reads config-directory
3
- // resources from OPENCODE_CONFIG_DIR, pinned in the generated shim.
1
+ import * as path from 'path';
2
+ import { stripForeignConfigDir } from '../adapter.js';
3
+ // OpenCode reads plugins/agents/commands from OPENCODE_CONFIG_DIR and auth from
4
+ // $XDG_DATA_HOME/opencode (HARNESS_AUTH.slotEnv). Pin both at the slot / version
5
+ // home so two accounts in one install never share a credential (PHNX-3940 T5).
4
6
  export const opencodeAdapter = {
5
7
  id: 'opencode',
8
+ applyExecConfigEnv(result, ctx) {
9
+ if (ctx.versionHome) {
10
+ result.OPENCODE_CONFIG_DIR = path.join(ctx.versionHome, '.config', 'opencode');
11
+ result.XDG_DATA_HOME = path.join(ctx.versionHome, '.local', 'share');
12
+ }
13
+ stripForeignConfigDir(result, ['OPENCODE_CONFIG_DIR', 'XDG_DATA_HOME']);
14
+ },
6
15
  shimConfigEnvBash() {
7
16
  return `
8
17
  # OpenCode reads plugins, agents, commands, and other config-directory
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Per-harness auth capability table (PHNX-3940).
3
+ *
4
+ * One row per `ALL_AGENT_IDS` entry. Governs `accounts add` / connect login
5
+ * argv, identity strength, the durable worker credential (if any), and the
6
+ * config-dir env that pins a slot. Completeness is pinned by the test against
7
+ * `ALL_AGENT_IDS` — a new harness without a row is a type error and a failing
8
+ * test, never a silent skip.
9
+ *
10
+ * Values are taken from evidence-harness-auth.md (2026-09-06) and the adapters
11
+ * under `lib/harness/adapters/`. `LOGIN_INVOCATIONS` is the connect-era subset
12
+ * (claude, codex) derived from this table so existing connect callers keep
13
+ * working until T4 folds connect into `accounts add`.
14
+ */
15
+ import type { AgentId } from './types.js';
16
+ export type HarnessIdentityKind = 'strong' | 'email' | 'opaque';
17
+ /**
18
+ * Durable worker credential, or `none` when the harness must log in per box.
19
+ * Codex is both: an API key (portable, bills the API) OR a per-device
20
+ * ChatGPT-plan device-auth login (the plan seat; never stored in the reserved
21
+ * store because it is a rotating session).
22
+ */
23
+ export type HarnessWorkerKind = 'setup-token' | 'none' | `api-key:${string}` | `per-device${'' | `:${string}`}`;
24
+ export type HarnessWorker = HarnessWorkerKind | HarnessWorkerKind[];
25
+ export interface HarnessAuthCapability {
26
+ /** argv after the harness binary to start native login, or null when there is no finite login command. */
27
+ login: string[] | null;
28
+ /** argv to probe login status, or null when the CLI has no status command. */
29
+ status: string[] | null;
30
+ identity: HarnessIdentityKind;
31
+ worker: HarnessWorker;
32
+ /** Config-dir env that pins a slot, or null when isolation is HOME-swap only. */
33
+ slotEnv: string | null;
34
+ }
35
+ export declare const HARNESS_AUTH: Record<AgentId, HarnessAuthCapability>;
36
+ export declare function harnessAuth(agent: AgentId): HarnessAuthCapability;
37
+ /** Worker kinds as a flat list (codex's dual path is two entries). */
38
+ export declare function harnessWorkerKinds(agent: AgentId): HarnessWorkerKind[];
39
+ /**
40
+ * True when this harness has no portable worker credential and must log in
41
+ * per box. `worker: 'none'` (kimi, antigravity, …) and a sole `per-device…`
42
+ * kind both count; a dual path like Codex (API key OR device-auth) does not.
43
+ */
44
+ export declare function harnessWorkerIsPerDevice(agent: AgentId): boolean;
45
+ /**
46
+ * Native-login invocation per harness. Only harnesses with a REAL, finite login
47
+ * COMMAND that connect currently drives — connect fails clearly for anything
48
+ * else rather than faking a flow that never signs the user in. Verified against
49
+ * the installed CLIs (PHNX-3940): `claude auth login --help` → "Sign in to your
50
+ * Anthropic account" with `--email`; `codex login` drives the OAuth flow.
51
+ * Args are the `HARNESS_AUTH.login` values for the same ids.
52
+ */
53
+ export interface LoginInvocation {
54
+ /** argv passed to the installed binary to start the native login. */
55
+ args: string[];
56
+ /** Flag that pre-fills the login email (appended as `[emailFlag, email]`), when supported. */
57
+ emailFlag?: string;
58
+ /** One-line hint shown before the login flow takes over. */
59
+ hint?: string;
60
+ }
61
+ export declare const LOGIN_INVOCATIONS: Partial<Record<AgentId, LoginInvocation>>;
@@ -0,0 +1,52 @@
1
+ export const HARNESS_AUTH = {
2
+ claude: { login: ['auth', 'login'], status: ['auth', 'status'], identity: 'strong', worker: 'setup-token', slotEnv: 'CLAUDE_CONFIG_DIR' },
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' },
5
+ // auth.json has no email claim; identity is the sorted provider-id join
6
+ // (`resolveOpenCodeAccountId`). NATIVE_ACCOUNT_CAPABILITIES.opencode.inspection
7
+ // is already 'opaque'.
8
+ opencode: { login: ['auth', 'login'], status: ['auth', 'list'], identity: 'opaque', worker: 'api-key:provider', slotEnv: 'XDG_DATA_HOME' },
9
+ cursor: { login: ['login'], status: ['status'], identity: 'strong', worker: 'api-key:CURSOR_API_KEY', slotEnv: null },
10
+ // No finite login argv — launch bare `kimi`, then `/login` in the TUI
11
+ // (`loginHint('kimi') === 'kimi'`, fleet/auth-sync `loginCommand: 'kimi'`).
12
+ kimi: { login: null, status: null, identity: 'opaque', worker: 'none', slotEnv: 'KIMI_CODE_HOME' },
13
+ antigravity: { login: null, status: null, identity: 'opaque', worker: 'none', slotEnv: null },
14
+ droid: { login: null, status: null, identity: 'opaque', worker: 'api-key:FACTORY_API_KEY', slotEnv: null },
15
+ gemini: { login: null, status: null, identity: 'email', worker: 'none', slotEnv: null },
16
+ copilot: { login: null, status: null, identity: 'opaque', worker: 'none', slotEnv: 'COPILOT_HOME' },
17
+ openclaw: { login: null, status: null, identity: 'opaque', worker: 'none', slotEnv: null },
18
+ amp: { login: null, status: null, identity: 'opaque', worker: 'none', slotEnv: null },
19
+ goose: { login: null, status: null, identity: 'opaque', worker: 'none', slotEnv: null },
20
+ hermes: { login: null, status: null, identity: 'opaque', worker: 'none', slotEnv: null },
21
+ // Auth lives under $XDG_CONFIG_HOME/muse (adapter pins XDG_CONFIG_HOME +
22
+ // XDG_DATA_HOME). slotEnv names the config pin; T5 must not HOME-swap.
23
+ muse: { login: null, status: null, identity: 'email', worker: 'none', slotEnv: 'XDG_CONFIG_HOME' },
24
+ warp: { login: null, status: null, identity: 'opaque', worker: 'none', slotEnv: null },
25
+ };
26
+ export function harnessAuth(agent) {
27
+ const cap = HARNESS_AUTH[agent];
28
+ if (!cap)
29
+ throw new Error(`No harness-auth capability for '${agent}'.`);
30
+ return cap;
31
+ }
32
+ /** Worker kinds as a flat list (codex's dual path is two entries). */
33
+ export function harnessWorkerKinds(agent) {
34
+ const worker = harnessAuth(agent).worker;
35
+ return Array.isArray(worker) ? worker : [worker];
36
+ }
37
+ /**
38
+ * True when this harness has no portable worker credential and must log in
39
+ * per box. `worker: 'none'` (kimi, antigravity, …) and a sole `per-device…`
40
+ * kind both count; a dual path like Codex (API key OR device-auth) does not.
41
+ */
42
+ export function harnessWorkerIsPerDevice(agent) {
43
+ return harnessWorkerKinds(agent).every((kind) => kind === 'none' || kind.startsWith('per-device'));
44
+ }
45
+ export const LOGIN_INVOCATIONS = {
46
+ claude: {
47
+ args: HARNESS_AUTH.claude.login,
48
+ emailFlag: '--email',
49
+ hint: 'Complete the Claude sign-in in your browser.',
50
+ },
51
+ codex: { args: HARNESS_AUTH.codex.login },
52
+ };
@@ -27,7 +27,7 @@
27
27
  * never be derived from `getCliVersion()`.
28
28
  */
29
29
  /** The helpers that have their own release train. */
30
- export type HelperName = 'menubar' | 'keychain' | 'computer-mac' | 'computer-win';
30
+ export type HelperName = 'menubar' | 'computer-mac' | 'computer-win';
31
31
  /** One helper's release identity. */
32
32
  export interface HelperRelease {
33
33
  /** Tag prefix — the release is `<tagPrefix>/v<version>`. */
@@ -41,9 +41,11 @@ export interface HelperRelease {
41
41
  /**
42
42
  * Floors, by helper.
43
43
  *
44
- * `menubar` and `keychain` start at 1.0.0 — the first builds published under
45
- * their own tags rather than the CLI's. They are not "version 1 of the helper";
46
- * they are version 1 of its independent release train.
44
+ * `menubar` starts at 1.0.0 — the first build published under its own tag
45
+ * rather than the CLI's. It is not "version 1 of the helper"; it is version 1
46
+ * of its independent release train. The keychain helper moved with the
47
+ * standalone `secrets` engine (PHNX-3989) — it downloads and verifies its own
48
+ * helper release now, off this table entirely.
47
49
  */
48
50
  export declare const HELPER_RELEASES: Readonly<Record<HelperName, HelperRelease>>;
49
51
  /** The release tag for one helper at one version, e.g. `menubar/v1.0.0`. */
@@ -29,13 +29,14 @@
29
29
  /**
30
30
  * Floors, by helper.
31
31
  *
32
- * `menubar` and `keychain` start at 1.0.0 — the first builds published under
33
- * their own tags rather than the CLI's. They are not "version 1 of the helper";
34
- * they are version 1 of its independent release train.
32
+ * `menubar` starts at 1.0.0 — the first build published under its own tag
33
+ * rather than the CLI's. It is not "version 1 of the helper"; it is version 1
34
+ * of its independent release train. The keychain helper moved with the
35
+ * standalone `secrets` engine (PHNX-3989) — it downloads and verifies its own
36
+ * helper release now, off this table entirely.
35
37
  */
36
38
  export const HELPER_RELEASES = {
37
39
  menubar: { tagPrefix: 'menubar', floor: '1.0.0' },
38
- keychain: { tagPrefix: 'keychain', floor: '1.0.0' },
39
40
  'computer-mac': { tagPrefix: 'computer-mac', floor: '1.0.0' },
40
41
  // The Windows helper is a bare .exe, not an .app bundle, so it does not share
41
42
  // helper-download.ts's zip/codesign/notarize machinery -- but it has the same
@@ -0,0 +1,10 @@
1
+ /** The host part of an ssh target (`user@host` -> `host`) for known_hosts matching. */
2
+ export declare function hostKeyLookupName(target: string): string;
3
+ /** Refuse durable credential transfer until the destination SSH key is pinned. */
4
+ export declare function assertCredentialTransportHostPinned(target: string, pinned?: boolean): void;
5
+ /**
6
+ * Resolve a `--device` value to an ssh target string for a credential-carrying
7
+ * remote operation. Delegates to the same resolver `run --device` uses; on a
8
+ * miss, treats the value as a raw ssh target and validates it.
9
+ */
10
+ export declare function resolveHostSshTarget(nameOrAlias: string): Promise<string>;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Credential transport policy for a fleet host (agents-owned, PHNX-3989).
3
+ *
4
+ * Whether a durable credential may leave this device for a given host is
5
+ * agents fleet policy, not a secrets-engine concern: it hangs off the managed
6
+ * `known_hosts` pin and the hosts/devices registry. The standalone `secrets`
7
+ * CLI only carries the bundle once agents-cli has decided the destination is
8
+ * trustworthy, so these guards live here and are passed INTO the transport,
9
+ * never re-implemented on the engine side.
10
+ */
11
+ import { assertValidSshTarget } from '../ssh-exec.js';
12
+ import { resolveHost } from './registry.js';
13
+ import { sshTargetFor } from './types.js';
14
+ import { isHostPinned } from '../devices/known-hosts.js';
15
+ /** The host part of an ssh target (`user@host` -> `host`) for known_hosts matching. */
16
+ export function hostKeyLookupName(target) {
17
+ return target.split('@').pop() ?? target;
18
+ }
19
+ /** Refuse durable credential transfer until the destination SSH key is pinned. */
20
+ export function assertCredentialTransportHostPinned(target, pinned = isHostPinned(hostKeyLookupName(target))) {
21
+ if (pinned)
22
+ return;
23
+ throw new Error(`Refusing to transfer provider credentials to '${target}' before its SSH host key is pinned. ` +
24
+ `Connect once with 'agents ssh ${target}' and verify the host, then retry.`);
25
+ }
26
+ /**
27
+ * Resolve a `--device` value to an ssh target string for a credential-carrying
28
+ * remote operation. Delegates to the same resolver `run --device` uses; on a
29
+ * miss, treats the value as a raw ssh target and validates it.
30
+ */
31
+ export async function resolveHostSshTarget(nameOrAlias) {
32
+ const host = await resolveHost(nameOrAlias);
33
+ if (host)
34
+ return sshTargetFor(host);
35
+ assertValidSshTarget(nameOrAlias);
36
+ return nameOrAlias;
37
+ }
@@ -170,6 +170,13 @@ export interface DispatchOptions {
170
170
  * (remote-cmd.ts) — keep the two in lockstep; run-forwarding.test.ts asserts
171
171
  * the table side.
172
172
  */
173
+ /** Compose `agent[@version][#account]` so the peer resolves ITS slot (PHNX-3940 T5). */
174
+ export declare function runAgentSpecArg(opts: {
175
+ agent: string;
176
+ version?: string;
177
+ account?: string;
178
+ accountPicker?: boolean;
179
+ }): string;
173
180
  export declare function buildRunForwardedArgs(opts: DispatchOptions): string[];
174
181
  export interface InteractiveDispatchOptions {
175
182
  agent: string;
@@ -359,8 +359,19 @@ async function launchDetached(host, target, opts) {
359
359
  * (remote-cmd.ts) — keep the two in lockstep; run-forwarding.test.ts asserts
360
360
  * the table side.
361
361
  */
362
+ /** Compose `agent[@version][#account]` so the peer resolves ITS slot (PHNX-3940 T5). */
363
+ export function runAgentSpecArg(opts) {
364
+ if (opts.accountPicker)
365
+ return `${opts.agent}@`;
366
+ let spec = opts.agent;
367
+ if (opts.version)
368
+ spec += `@${opts.version}`;
369
+ if (opts.account)
370
+ spec += `#${opts.account}`;
371
+ return spec;
372
+ }
362
373
  export function buildRunForwardedArgs(opts) {
363
- const agentArg = opts.version ? `${opts.agent}@${opts.version}` : opts.agent;
374
+ const agentArg = runAgentSpecArg(opts);
364
375
  const args = ['run', agentArg, opts.prompt, '--quiet'];
365
376
  if (opts.mode)
366
377
  args.push('--mode', opts.mode);
@@ -427,11 +438,7 @@ export function buildInteractiveRunForwardedArgs(opts) {
427
438
  if (opts.version && opts.accountPicker) {
428
439
  throw new Error('Interactive host dispatch cannot combine an account picker with a version pin');
429
440
  }
430
- const agentArg = opts.accountPicker
431
- ? `${opts.agent}@`
432
- : opts.version
433
- ? `${opts.agent}@${opts.version}`
434
- : opts.agent;
441
+ const agentArg = runAgentSpecArg(opts);
435
442
  const args = ['run', agentArg];
436
443
  if (opts.prompt && opts.forceInteractive)
437
444
  args.push(opts.prompt);
@@ -10,9 +10,9 @@
10
10
  * here and nothing else.
11
11
  *
12
12
  * The shape mirrors the seams this repo already proved elsewhere —
13
- * `SyncBackend` (`lib/secrets/sync-backend.ts`) and `CloudProvider`
14
- * (`lib/cloud/types.ts`) — so a second identity backend, if one is ever
15
- * needed, is a swap here rather than a sweep across commands.
13
+ * the bounded secrets process client (`lib/secrets-client.ts`) and
14
+ * `CloudProvider` (`lib/cloud/types.ts`) — so a second identity backend, if
15
+ * one is ever needed, is a swap here rather than a sweep across commands.
16
16
  */
17
17
  /**
18
18
  * Where the account backend lives. Config, never a literal at a call site.
@@ -10,9 +10,9 @@
10
10
  * here and nothing else.
11
11
  *
12
12
  * The shape mirrors the seams this repo already proved elsewhere —
13
- * `SyncBackend` (`lib/secrets/sync-backend.ts`) and `CloudProvider`
14
- * (`lib/cloud/types.ts`) — so a second identity backend, if one is ever
15
- * needed, is a swap here rather than a sweep across commands.
13
+ * the bounded secrets process client (`lib/secrets-client.ts`) and
14
+ * `CloudProvider` (`lib/cloud/types.ts`) — so a second identity backend, if
15
+ * one is ever needed, is a swap here rather than a sweep across commands.
16
16
  */
17
17
  import * as fs from 'fs';
18
18
  import * as path from 'path';
@@ -8,7 +8,7 @@
8
8
  * per-installation isolation track consumes exactly this).
9
9
  */
10
10
  export { type Installation, type InstallationRelease, type UpdateOutcome, type UpdateStrategyId, type UpdatePolicy, } from './types.js';
11
- export { createInstallation, listInstallations, recordRelease, } from './store.js';
11
+ export { createInstallation, listInstallations, recordRelease, MANAGED_INSTALLATION_LABEL, resolveManagedInstallation, ensureHarnessInstallation, type EnsureHarnessInstallationResult, } from './store.js';
12
12
  export { describeInstallation, resolveInstallation, type ResolveInstallationOptions, } from './resolve.js';
13
13
  export { selectUpdateStrategy, supportsPinnedUpdate, type UpdateStrategy, } from './strategies.js';
14
14
  export { updateInstallation, type UpdateInstallationOptions } from './update.js';
@@ -7,7 +7,7 @@
7
7
  * it, so the internal split can change without breaking callers (the Cursor
8
8
  * per-installation isolation track consumes exactly this).
9
9
  */
10
- export { createInstallation, listInstallations, recordRelease, } from './store.js';
10
+ export { createInstallation, listInstallations, recordRelease, MANAGED_INSTALLATION_LABEL, resolveManagedInstallation, ensureHarnessInstallation, } from './store.js';
11
11
  export { describeInstallation, resolveInstallation, } from './resolve.js';
12
12
  export { selectUpdateStrategy, supportsPinnedUpdate, } from './strategies.js';
13
13
  export { updateInstallation } from './update.js';
@@ -2366,4 +2366,13 @@ export async function runMigration() {
2366
2366
  catch {
2367
2367
  // best-effort — a migration must never fail because tmux wasn't reachable
2368
2368
  }
2369
+ // PHNX-3940 T7: report leftover per-account installations. Dry-run only —
2370
+ // moving homes requires an explicit `agents accounts migrate --apply`.
2371
+ try {
2372
+ const { reportAccountSlotMigrationOnUpgrade } = await import('../accounts/migrate.js');
2373
+ await reportAccountSlotMigrationOnUpgrade();
2374
+ }
2375
+ catch {
2376
+ // best-effort — a corrupt install must not block the rest of upgrade
2377
+ }
2369
2378
  }
@@ -21,7 +21,7 @@ export class InstallationNotFoundError extends Error {
21
21
  // Nothing installed is a different problem from "your selector missed", and
22
22
  // the remedy differs — say which one it is rather than printing an empty list.
23
23
  super(available.length === 0
24
- ? `No ${AGENTS[agent].name} installations are managed by agents-cli. Install one with: agents add ${agent}@latest`
24
+ ? `No ${AGENTS[agent].name} installations are managed by agents-cli. Install one with: agents add ${agent}`
25
25
  : `No ${AGENTS[agent].name} installation matches '${selector}'. Installed: ${available.map((i) => describeInstallation(i)).join(', ')}`);
26
26
  this.agent = agent;
27
27
  this.selector = selector;
@@ -201,6 +201,21 @@ export declare const CONFIG_ENV_ISOLATED_AGENTS: readonly AgentId[];
201
201
  * separate from the user's real `~/.<agent>`. See {@link CONFIG_ENV_ISOLATED_AGENTS}.
202
202
  */
203
203
  export declare function supportsIsolatedInstall(agent: AgentId): boolean;
204
+ /**
205
+ * Harnesses that isolate by symlink-adopting `~/.<config>` rather than a
206
+ * config-dir env (PHNX-3940 T5). One active slot per device; `accounts default`
207
+ * repoints the adopted symlink under the auth-op lock.
208
+ */
209
+ export declare function isSymlinkAdoptedHarness(agent: AgentId): boolean;
210
+ /**
211
+ * Repoint this harness's adopted `~/.<config>` symlink at `home`'s config dir.
212
+ * No-op for env-isolated harnesses. Fails loud when the adopted path is a real
213
+ * directory rather than a symlink — adopting that is `agents use`, not default.
214
+ */
215
+ export declare function repointAdoptedConfigToHome(agent: AgentId, home: string): {
216
+ success: boolean;
217
+ error?: string;
218
+ };
204
219
  /**
205
220
  * Generate a versioned alias script that directly execs a specific version.
206
221
  * e.g., claude@2.0.65 -> directly runs that version's binary
@@ -343,10 +358,12 @@ export declare function listShimFileNames(): string[];
343
358
  /**
344
359
  * Prune a stale, orphaned shim: one that is NOT a managed agent shim and NOT a user
345
360
  * alias, whose baked `AGENTS_BIN` points at an install that no longer exists. These
346
- * are legacy `exec "$AGENTS_BIN" <cmd>` command shims (browser/secrets/sessions/…)
347
- * left behind by a removed install — the current source never generates them, and
348
- * they either die with `exit 127` or shadow the real package bin on PATH. Only
349
- * removed when the baked target is gone, so a working shim is never touched.
361
+ * are `exec "$AGENTS_BIN" <cmd>` command shims (browser/sessions/pty/teams) that
362
+ * `scripts/postinstall.js` writes for the LIVE install; one baked at a removed
363
+ * install either dies with `exit 127` or shadows the real package bin on PATH.
364
+ * Only removed when the baked target is gone, so a working shim is never touched —
365
+ * except the `LEGACY_SHIMS_ALWAYS_PRUNED` set, which cannot work at all (and which
366
+ * postinstall no longer writes).
350
367
  * Returns true if removed.
351
368
  */
352
369
  export declare function pruneOrphanedCommandShim(fileName: string): boolean;