@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
@@ -1,26 +1,29 @@
1
1
  import { resolveAgentName } from './agents.js';
2
- import { AUTH_BUNDLE, claudeAccountTokenKey, isValidClaudeSetupToken, readClaudeAccountEmail, } from './claude-account-token.js';
2
+ import { AUTH_BUNDLE, claudeAccountTokenKey, isValidClaudeSetupToken, readClaudeAccountEmail, invalidateClaudeSetupTokenCache, } from './claude-account-token.js';
3
3
  import { addAccount, findAccount, readAccountRegistry, setAccountSecret } from './account-registry.js';
4
- import { bundleBackend, bundleExists, keychainRef, readBundle, rotateBundleSecret, writeBundleWithItems, } from './secrets/bundles.js';
5
- import { secretsKeychainItem } from './secrets/index.js';
4
+ import { bundleBackendSync, bundleExists, bundleExistsSync, keychainRef, pushBundleToHost, readBundleSync, rotateBundleSecretSync, secretsKeychainItem, storeSetSync, writeBundleWithItemsSync, } from './secrets-client.js';
5
+ import { assertStorableCredentialKind, reservedStoreName, } from './secrets-policy.js';
6
6
  import { getBinaryPath, getGlobalDefault, getVersionHomePath, listInstalledVersions } from './installations/versions.js';
7
7
  import { shellQuote } from './ssh-exec.js';
8
8
  import { defaultPtyDriver, } from './fleet/remote-login.js';
9
9
  import { showUrl } from './open-url.js';
10
10
  import { loadDevices } from './devices/registry.js';
11
11
  import { isSelfHost } from './devices/self-host.js';
12
- import { pushBundleToHost } from './secrets/push.js';
13
- import { assertCredentialTransportHostPinned, resolveHostSshTarget } from './secrets/remote.js';
12
+ import { assertCredentialTransportHostPinned, resolveHostSshTarget } from './hosts/credential-transport.js';
14
13
  import { resolveRemoteOsSync } from './hosts/remote-os.js';
15
14
  /** Well-formed Claude setup-token as it appears inside a TTY blob. */
16
15
  export const CLAUDE_SETUP_TOKEN_CAPTURE_RE = /sk-ant-oat01-[A-Za-z0-9_-]+/;
17
16
  const ACCOUNT_NAME_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/;
18
17
  const EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
19
18
  /**
20
- * Harnesses that expose an interactive setup-token mint. Native device-code
21
- * login (codex/droid/kimi/grok) stays on `agents fleet login`; API keys stay
22
- * on `agents accounts add`. Adding a harness here without a real mint command
23
- * is a lying table — do not.
19
+ * Durable-credential flows per harness. Only Claude exposes an interactive
20
+ * setup-token MINT; every other harness's durable credential is a provider API
21
+ * key, COLLECTED via `--api-key` or a prompt by `accounts add` (never derived
22
+ * from OAuth). The api-key entries describe collection only — they have no
23
+ * mint argv, and `mintAndSeed` refuses them with the `accounts add` pointer.
24
+ * Native device-code login (kimi/antigravity) stays on `agents fleet login`.
25
+ * The apiKeyEnv values are pinned to HARNESS_AUTH's `api-key:<ENV>` worker
26
+ * kinds by auth-mint.test.ts — keep them in lockstep.
24
27
  */
25
28
  export const MINT_FLOWS = {
26
29
  claude: {
@@ -32,9 +35,15 @@ export const MINT_FLOWS = {
32
35
  verificationUrlRegex: /(https:\/\/[^\s"'<>]+)/i,
33
36
  tokenCapture: CLAUDE_SETUP_TOKEN_CAPTURE_RE,
34
37
  },
38
+ codex: { harness: 'codex', provider: 'openai', auth: 'api-key', mintArgs: null, apiKeyEnv: 'OPENAI_API_KEY' },
39
+ grok: { harness: 'grok', provider: 'xai', auth: 'api-key', mintArgs: null, apiKeyEnv: 'XAI_API_KEY' },
40
+ opencode: { harness: 'opencode', provider: 'opencode', auth: 'api-key', mintArgs: null, apiKeyEnv: 'OPENCODE_API_KEY' },
41
+ cursor: { harness: 'cursor', provider: 'cursor', auth: 'api-key', mintArgs: null, apiKeyEnv: 'CURSOR_API_KEY' },
42
+ droid: { harness: 'droid', provider: 'factory', auth: 'api-key', mintArgs: null, apiKeyEnv: 'FACTORY_API_KEY' },
35
43
  };
44
+ /** Harnesses with an interactive setup-token mint (api-key flows are collection, not mint). */
36
45
  export function listMintableHarnesses() {
37
- return Object.keys(MINT_FLOWS);
46
+ return Object.values(MINT_FLOWS).filter((f) => f.auth === 'setup-token').map((f) => f.harness);
38
47
  }
39
48
  export function getMintFlow(harnessRaw) {
40
49
  const harness = resolveAgentName(harnessRaw);
@@ -42,8 +51,12 @@ export function getMintFlow(harnessRaw) {
42
51
  throw new Error(`Unknown harness '${harnessRaw}'. Interactive setup-token mint is implemented for: ${listMintableHarnesses().join(', ')}.`);
43
52
  }
44
53
  const flow = MINT_FLOWS[harness];
45
- if (flow)
54
+ if (flow?.auth === 'setup-token')
46
55
  return flow;
56
+ if (flow) {
57
+ throw new Error(`'${harness}' has no derivable token — its durable credential is an API key (${flow.apiKeyEnv}). `
58
+ + `Collect it with: agents accounts add ${harness} <name> --api-key <key>.`);
59
+ }
47
60
  throw new Error(unmintableMessage(harness));
48
61
  }
49
62
  export function unmintableMessage(harness) {
@@ -76,6 +89,8 @@ export function extractClaudeSetupToken(screen) {
76
89
  }
77
90
  /** First https URL on the screen, trailing punctuation stripped. */
78
91
  export function extractMintUrl(screen, flow) {
92
+ if (!flow.verificationUrlRegex)
93
+ return undefined;
79
94
  const text = stripAnsi(screen);
80
95
  const m = text.match(flow.verificationUrlRegex);
81
96
  if (!m)
@@ -154,14 +169,15 @@ export function resolveMintIdentity(input) {
154
169
  export function seedReservedAuthToken(email, token) {
155
170
  const cleaned = assertValidSetupToken(token);
156
171
  const key = claudeAccountTokenKey(email);
157
- if (bundleExists(AUTH_BUNDLE)) {
158
- const backend = bundleBackend(AUTH_BUNDLE);
172
+ if (bundleExistsSync(AUTH_BUNDLE)) {
173
+ const backend = bundleBackendSync(AUTH_BUNDLE);
159
174
  if (backend !== 'file') {
160
175
  throw new Error(`Reserved bundle '${AUTH_BUNDLE}' exists with backend '${backend}', but usage/probe only reads a FILE-backed auth bundle. Recreate it with: agents secrets create ${AUTH_BUNDLE} --backend file --policy never --i-understand --force`);
161
176
  }
162
- const bundle = readBundle(AUTH_BUNDLE);
177
+ const bundle = readBundleSync(AUTH_BUNDLE);
163
178
  if (key in bundle.vars) {
164
- rotateBundleSecret(bundle, key, { newValue: cleaned, meta: { type: 'token' } });
179
+ rotateBundleSecretSync(bundle, key, { newValue: cleaned, meta: { type: 'token' } });
180
+ invalidateClaudeSetupTokenCache();
165
181
  return { key };
166
182
  }
167
183
  const item = secretsKeychainItem(AUTH_BUNDLE, key);
@@ -169,7 +185,8 @@ export function seedReservedAuthToken(email, token) {
169
185
  if (!bundle.meta)
170
186
  bundle.meta = {};
171
187
  bundle.meta[key] = { type: 'token' };
172
- writeBundleWithItems(bundle, new Map([[item, cleaned]]));
188
+ writeBundleWithItemsSync(bundle, new Map([[item, cleaned]]));
189
+ invalidateClaudeSetupTokenCache();
173
190
  return { key };
174
191
  }
175
192
  const item = secretsKeychainItem(AUTH_BUNDLE, key);
@@ -181,9 +198,59 @@ export function seedReservedAuthToken(email, token) {
181
198
  vars: { [key]: keychainRef(key) },
182
199
  meta: { [key]: { type: 'token' } },
183
200
  };
184
- writeBundleWithItems(bundle, new Map([[item, cleaned]]));
201
+ writeBundleWithItemsSync(bundle, new Map([[item, cleaned]]));
202
+ invalidateClaudeSetupTokenCache();
185
203
  return { key };
186
204
  }
205
+ /**
206
+ * The env var a harness's durable worker credential injects as on a worker
207
+ * (claude's setup-token rides CLAUDE_CODE_OAUTH_TOKEN; api-key harnesses ride
208
+ * their MINT_FLOWS apiKeyEnv). Throws for a harness with no portable worker
209
+ * credential — kimi/antigravity log in per box.
210
+ */
211
+ export function workerCredentialEnv(harness) {
212
+ if (harness === 'claude')
213
+ return 'CLAUDE_CODE_OAUTH_TOKEN';
214
+ const flow = MINT_FLOWS[harness];
215
+ if (flow?.auth === 'api-key' && flow.apiKeyEnv)
216
+ return flow.apiKeyEnv;
217
+ throw new Error(`No portable worker credential env for '${harness}' — it logs in per box.`);
218
+ }
219
+ /**
220
+ * The reserved-store key for one account's worker credential:
221
+ * `<ENV>_<accountId>` — keyed by account id, never by name or email, so a
222
+ * rename or an email change never breaks the key. Hyphens are stripped because
223
+ * the bundle key grammar (`BUNDLE_KEY_PATTERN`) is env-var-shaped.
224
+ */
225
+ export function workerCredentialStoreKey(harness, accountId) {
226
+ const slug = accountId.replace(/-/g, '');
227
+ if (!/^[A-Za-z0-9_]+$/.test(slug) || !slug) {
228
+ throw new Error(`Invalid account id '${accountId}' for a worker credential key.`);
229
+ }
230
+ return `${workerCredentialEnv(harness)}_${slug}`;
231
+ }
232
+ /**
233
+ * Write (or rotate) one account's worker credential in the reserved
234
+ * `__<harness>__` store (PHNX-3940). Only non-rotating kinds may enter
235
+ * (`assertStorableCredentialKind` refuses a rotating OAuth/session credential at
236
+ * this boundary — RUSH-1958). FILE-backed, policy `never`, same contract as the
237
+ * legacy `auth` bundle: a keychain- or vault-backed store of this name fails
238
+ * loud, since worker provisioning reads the file backend.
239
+ */
240
+ export function seedReservedStoreKey(harness, kind, key, value) {
241
+ assertStorableCredentialKind(kind, harness);
242
+ const name = reservedStoreName(harness);
243
+ const cleaned = value.trim();
244
+ if (!cleaned)
245
+ throw new Error(`Empty ${kind} for reserved store '${name}' key ${key}.`);
246
+ // A `__<harness>__` reserved store is written and read as raw FILE-backed items,
247
+ // never as a standalone bundle: the standalone's bundle-name validation rejects
248
+ // the `__`-wrapped reserved name, so the worker credential rides the raw item
249
+ // directly, symmetric with how `readReservedCredential` reads it. A first write
250
+ // and a rotation are the same operation — `store.set` overwrites the item value.
251
+ storeSetSync('file', secretsKeychainItem(name, key), cleaned);
252
+ return { bundle: name, key };
253
+ }
187
254
  /**
188
255
  * Create or rotate the named provider account that `agents run --account` and
189
256
  * `agents accounts sync` consume. Existing account of a different kind fails
@@ -211,6 +278,9 @@ function emitMintProgress(line, json) {
211
278
  * Tears the session down on the way out (success, timeout, or throw).
212
279
  */
213
280
  export async function driveSetupTokenMint(command, flow, opts = {}) {
281
+ if (flow.auth !== 'setup-token' || !flow.tokenCapture) {
282
+ throw new Error(`Harness '${flow.harness}' has no interactive mint command.`);
283
+ }
214
284
  const driver = opts.driver ?? defaultPtyDriver();
215
285
  const initialDelayMs = opts.drive?.initialDelayMs ?? 1500;
216
286
  const pollMs = opts.drive?.pollMs ?? 500;
@@ -274,12 +344,16 @@ export async function driveSetupTokenMint(command, flow, opts = {}) {
274
344
  await driver.stop(id).catch(() => { });
275
345
  }
276
346
  }
277
- export function buildMintCommand(flow, bin, home) {
278
- if (!flow.mintArgs) {
347
+ export function buildMintCommand(flow, bin, home, env = {}) {
348
+ if (flow.auth !== 'setup-token' || !flow.mintArgs) {
279
349
  throw new Error(`Harness '${flow.harness}' has no interactive mint command.`);
280
350
  }
281
351
  const args = flow.mintArgs.map(shellQuote).join(' ');
282
- return `HOME=${shellQuote(home)} ${shellQuote(bin)} ${args}`;
352
+ const envPrefix = [
353
+ `HOME=${shellQuote(home)}`,
354
+ ...Object.entries(env).map(([k, v]) => `${k}=${shellQuote(v)}`),
355
+ ].join(' ');
356
+ return `${envPrefix} ${shellQuote(bin)} ${args}`;
283
357
  }
284
358
  export function resolveMintInstallation(harness) {
285
359
  const installed = listInstalledVersions(harness);
@@ -382,7 +456,7 @@ async function syncMintedBundles(accountName, device) {
382
456
  const account = findAccount(accountName);
383
457
  if (!account)
384
458
  throw new Error(`Unknown provider account '${accountName}'.`);
385
- const accountPush = pushBundleToHost(accountName, device, {
459
+ const accountPush = await pushBundleToHost(accountName, device, {
386
460
  remoteBackend,
387
461
  force: true,
388
462
  operation: 'accounts mint --fleet',
@@ -397,8 +471,8 @@ async function syncMintedBundles(accountName, device) {
397
471
  });
398
472
  if (!accountPush.ok)
399
473
  throw new Error(accountPush.message);
400
- if (bundleExists(AUTH_BUNDLE)) {
401
- const authPush = pushBundleToHost(AUTH_BUNDLE, device, {
474
+ if (await bundleExists(AUTH_BUNDLE)) {
475
+ const authPush = await pushBundleToHost(AUTH_BUNDLE, device, {
402
476
  remoteBackend: 'file',
403
477
  force: true,
404
478
  operation: 'accounts mint --fleet',
@@ -417,32 +491,36 @@ async function syncMintedBundles(accountName, device) {
417
491
  /**
418
492
  * True when a Claude setup-token is already seeded on this box (setup status).
419
493
  *
420
- * Read-only status probe (`agents setup`, `agents doctor`) — the reserved
421
- * auth-bundle check below MUST NOT crash the whole command because the
422
- * keychain could not be reached (e.g. the macOS Keychain helper source is
423
- * unavailable — PHNX-3385); `bundleExists()`/`hasKeychainToken()` document
424
- * themselves as failing loud, but that contract is for destructive-write
425
- * guards, not a diagnostic. An unreachable keychain here just means "cannot
426
- * confirm the reserved bundle," reported honestly rather than propagated.
494
+ * Read-only status probe (`agents setup`, `agents doctor`) — neither the
495
+ * account-registry read nor the reserved auth-bundle check MUST crash the whole
496
+ * command because the secret store could not be reached (the macOS Keychain
497
+ * helper source unavailable — PHNX-3385 — or the standalone `secrets` CLI
498
+ * missing / unspawnable); the client's calls fail loud by contract, but that
499
+ * contract is for destructive-write guards, not a diagnostic. An unreachable
500
+ * store here just means "cannot confirm," reported honestly rather than
501
+ * propagated.
427
502
  */
428
503
  export function hasMintedSetupToken() {
429
- const records = Object.values(readAccountRegistry().accounts);
430
- const setup = records.filter((a) => a.auth === 'setup-token' && a.provider === 'anthropic');
431
- if (setup.length) {
432
- return { ready: true, detail: `${setup.length} Claude setup-token account${setup.length === 1 ? '' : 's'}` };
433
- }
434
504
  try {
435
- if (bundleExists(AUTH_BUNDLE) && bundleBackend(AUTH_BUNDLE) === 'file') {
436
- const bundle = readBundle(AUTH_BUNDLE);
505
+ // The registry is the account bundles behind the standalone `secrets` CLI, so
506
+ // this read is as reachable — or as unreachable — as the auth-bundle read
507
+ // below and sits inside the same guard.
508
+ const records = Object.values(readAccountRegistry().accounts);
509
+ const setup = records.filter((a) => a.auth === 'setup-token' && a.provider === 'anthropic');
510
+ if (setup.length) {
511
+ return { ready: true, detail: `${setup.length} Claude setup-token account${setup.length === 1 ? '' : 's'}` };
512
+ }
513
+ if (bundleExistsSync(AUTH_BUNDLE) && bundleBackendSync(AUTH_BUNDLE) === 'file') {
514
+ const bundle = readBundleSync(AUTH_BUNDLE);
437
515
  const keys = Object.keys(bundle.vars).filter((k) => k.startsWith('CLAUDE_CODE_OAUTH_TOKEN_'));
438
516
  if (keys.length)
439
517
  return { ready: true, detail: `reserved auth bundle (${keys.length} account key${keys.length === 1 ? '' : 's'})` };
440
518
  }
441
519
  }
442
520
  catch (err) {
443
- return { ready: false, detail: `could not check the reserved auth bundle: ${err.message}` };
521
+ return { ready: false, detail: `could not check the secret store: ${err.message}` };
444
522
  }
445
- return { ready: false, detail: 'no Claude setup-token minted — agents accounts mint claude' };
523
+ return { ready: false, detail: 'no Claude setup-token minted — agents accounts add claude [name]' };
446
524
  }
447
525
  function sleep(ms) {
448
526
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -49,7 +49,7 @@ export interface LaunchResult {
49
49
  * needs them can `agents secrets unlock <bundle>`). Exported so the no-prompt
50
50
  * behavior is testable on the real keychain path without spawning a browser.
51
51
  */
52
- export declare function resolveProfileSecretsEnv(secrets?: string): Record<string, string>;
52
+ export declare function resolveProfileSecretsEnv(secrets?: string): Promise<Record<string, string>>;
53
53
  export declare function launchBrowser(profileName: string, browserType: BrowserType, port: number, options?: ChromeOptions, secrets?: string, customBinary?: string, isElectron?: boolean): Promise<LaunchResult>;
54
54
  export declare function attachToChrome(port: number): Promise<string>;
55
55
  export declare function killChrome(pid: number): void;
@@ -4,7 +4,7 @@ import * as path from 'path';
4
4
  import * as os from 'os';
5
5
  import { getProfileRuntimeDir } from './profiles.js';
6
6
  import { discoverBrowserWsUrl, registerPipeTransport } from './cdp.js';
7
- import { readAndResolveBundleEnv, bundleExists } from '../secrets/bundles.js';
7
+ import { readAndResolveBundleEnv, bundleExists } from '../secrets-client.js';
8
8
  import { writeProfileRuntime, readProfileRuntime } from './runtime-state.js';
9
9
  // Windows install roots. Resolve from the environment (fall back to the usual
10
10
  // defaults) so per-user installs under %LOCALAPPDATA% and 64-bit Program Files
@@ -247,11 +247,11 @@ export function listInstalledBrowsers(platform = os.platform()) {
247
247
  * needs them can `agents secrets unlock <bundle>`). Exported so the no-prompt
248
248
  * behavior is testable on the real keychain path without spawning a browser.
249
249
  */
250
- export function resolveProfileSecretsEnv(secrets) {
251
- if (!secrets || !bundleExists(secrets))
250
+ export async function resolveProfileSecretsEnv(secrets) {
251
+ if (!secrets || !(await bundleExists(secrets)))
252
252
  return {};
253
253
  try {
254
- const { env } = readAndResolveBundleEnv(secrets, { caller: 'browser profile', agentOnly: true });
254
+ const { env } = await readAndResolveBundleEnv(secrets, { caller: 'browser profile', agentOnly: true });
255
255
  return env;
256
256
  }
257
257
  catch {
@@ -321,7 +321,7 @@ isElectron = false) {
321
321
  ];
322
322
  // Profile secrets: agentOnly (SEC-13) — a locked bundle resolves to an empty map
323
323
  // (launch proceeds without them), never a Touch ID prompt. See resolveProfileSecretsEnv.
324
- const env = { ...process.env, ...resolveProfileSecretsEnv(secrets) };
324
+ const env = { ...process.env, ...(await resolveProfileSecretsEnv(secrets)) };
325
325
  const child = spawn(browserPath, args, {
326
326
  detached: true,
327
327
  stdio: ['ignore', 'pipe', 'pipe', 'pipe', 'pipe'],
@@ -7,7 +7,7 @@
7
7
  * same bar style the native usage system uses, just sourced from the provider
8
8
  * API rather than the CLI's own usage tracking.
9
9
  */
10
- import { hasKeychainToken, getKeychainToken } from './secrets/profiles.js';
10
+ import { getKeychainTokenSync, hasKeychainTokenSync } from './secrets-client.js';
11
11
  import { renderBar, getUsageColor } from './accounting/usage.js';
12
12
  import * as fs from 'node:fs';
13
13
  import * as path from 'node:path';
@@ -74,9 +74,9 @@ function resolveByokToken(profile) {
74
74
  if (!keychainItem)
75
75
  return null;
76
76
  try {
77
- if (!hasKeychainToken(keychainItem))
77
+ if (!hasKeychainTokenSync(keychainItem))
78
78
  return null;
79
- return getKeychainToken(keychainItem);
79
+ return getKeychainTokenSync(keychainItem);
80
80
  }
81
81
  catch {
82
82
  return null;
@@ -1,3 +1,5 @@
1
+ import { ReservedBundleWrongBackendError } from './reserved-stores.js';
2
+ import type { DeviceAccountSlot, NativeAccountRecord } from './types.js';
1
3
  /**
2
4
  * Reserved FILE-BASED secrets bundle holding long-lived, non-rotating Claude
3
5
  * setup-tokens. Usage/probe reads authenticate with these instead of Claude
@@ -6,8 +8,11 @@
6
8
  * bare key, so one account's token can't be misapplied to another in a
7
9
  * multi-account fleet.
8
10
  */
9
- /** Alias of the secrets-layer reserved name so mint/seed shares one source of truth. */
11
+ /** Alias of the reserved-store name so mint/seed shares one source of truth. */
10
12
  export declare const AUTH_BUNDLE = "auth";
13
+ export { ReservedBundleWrongBackendError };
14
+ /** Drop every memoized setup-token; called after this process seeds or rotates one. */
15
+ export declare function invalidateClaudeSetupTokenCache(): void;
11
16
  /** True only for a clean, single-line `sk-ant-oat01-…` token — see {@link SETUP_TOKEN_RE}. */
12
17
  export declare function isValidClaudeSetupToken(value: string): boolean;
13
18
  /** The per-account key an email maps to inside the `auth` bundle. */
@@ -24,8 +29,8 @@ export declare function readClaudeAccountEmail(home?: string): string | null;
24
29
  export declare function resolveClaudeSetupToken(home?: string): string | null;
25
30
  /**
26
31
  * Resolve a long-lived setup-token for an EXPLICIT account email, independent of
27
- * any version home's `.claude.json`. This is what lets `agents accounts attach`
28
- * provision a headless worker home that has never had an interactive login: the
32
+ * any version home's `.claude.json`. This is what lets worker-slot provisioning
33
+ * seed a headless worker home that has never had an interactive login: the
29
34
  * account's non-rotating setup-token is already fleet-synced in the file-based
30
35
  * `auth` bundle, keyed by email ({@link claudeAccountTokenKey}), so we can write
31
36
  * the home's `.oauth_token` from it without the circular
@@ -47,4 +52,43 @@ export declare function resolveClaudeSetupTokenForEmail(email: string, cacheKey?
47
52
  * It never copies a rotating OAuth credential (`.credentials.json`) — the setup-token
48
53
  * stays the credential of record.
49
54
  */
55
+ /**
56
+ * Write a Claude worker slot's `.oauth_token` (0600) from a durable setup-token,
57
+ * the way the pre-slot worker home was provisioned. Refuses a malformed token so
58
+ * a corrupt bundle entry can never reach the auth header (see {@link SETUP_TOKEN_RE}).
59
+ * `home` is the slot dir; the claude adapter shim reads `$CLAUDE_CONFIG_DIR/.oauth_token`
60
+ * where `CLAUDE_CONFIG_DIR` is `<home>/.claude`. Returns the written path.
61
+ */
62
+ export declare function writeClaudeWorkerOauthToken(home: string, token: string): string;
63
+ /**
64
+ * Read one non-rotating credential value from a reserved store by its storage
65
+ * key. Returns null when the bundle or key is absent. Used to materialize a
66
+ * worker slot from a synced durable credential (setup-token / API key).
67
+ *
68
+ * A reserved `__<harness>__` store is file-backed by design (headless, fleet-
69
+ * shareable) and its item is read directly, symmetric with how it is written —
70
+ * `readAndResolveBundleEnv`'s name validation does not yet accept a reserved
71
+ * name on the READ path (a secrets-track seam; see the PR body). A non-reserved
72
+ * bundle (the legacy `auth` alias, a provider bundle) goes through the normal
73
+ * resolver so refs/expiry/lease gates still apply.
74
+ */
75
+ export declare function readReservedCredential(bundle: string, key: string): string | null;
76
+ /**
77
+ * Materialize a worker slot for a portable account from its synced durable
78
+ * credential (PHNX-3940 T6 — the generalization of the pre-slot Claude worker-home
79
+ * provisioning). Creates the HOME-shaped slot dir (T1 `ensureSlot`), then, for a
80
+ * durable harness, writes the credential into it the way the Claude worker home is
81
+ * provisioned today: for `claude`, the setup-token → `.oauth_token` (0600) + the
82
+ * seeded identity email (the read-side join in agent-spec then completes the uuids
83
+ * from the registry row). API-key harnesses need no file — the key is injected at
84
+ * spawn from the reserved store (T5) — so their slot is created and recorded
85
+ * `durable` with no write. A per-device harness (`worker: 'none'`) gets a
86
+ * `per-device` slot and no credential — it logs in per box.
87
+ *
88
+ * This is worker-side reconciliation: it runs on the box where the key landed and
89
+ * NEVER transports anything (the SSH push that delivered the key is the daemon's
90
+ * job — invariant 1). Fails loud when a durable claude account has no resolvable
91
+ * token on this device rather than recording a slot that cannot authenticate.
92
+ */
93
+ export declare function provisionWorkerSlot(account: NativeAccountRecord): DeviceAccountSlot;
50
94
  export declare function seedClaudeWorkerHomeIdentity(versionHome: string, email: string): void;