@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
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Per-device account slots (PHNX-3940).
3
+ *
4
+ * An account is a credential slot, not an installation. The binary lives in
5
+ * the one managed harness install; each account gets a HOME-shaped dir under
6
+ * `~/.agents/.history/accounts/<harness>/<accountId>/` with no binary in it.
7
+ * Native OAuth files stay in this dir on the device that minted them and are
8
+ * never copied. Settings and resources are projected through the same writers
9
+ * version homes use (`carryForwardSettings`, `getWriter`) — credentials are
10
+ * excluded by those writers.
11
+ *
12
+ * Slot records live in the device doc (`deviceAccounts.slots`), never central.
13
+ */
14
+ import * as fs from 'node:fs';
15
+ import * as path from 'node:path';
16
+ import { agentConfigDirName } from '../agents.js';
17
+ import { harnessAuth, harnessWorkerIsPerDevice } from '../harness-auth-capabilities.js';
18
+ import { getGlobalDefault, getVersionHomePath, listInstalledVersions } from '../installations/store.js';
19
+ import { carryForwardSettings } from '../settings-manifest.js';
20
+ import { getHistoryDir, updateMeta } from '../state.js';
21
+ import { ALL_RESOURCE_KINDS, getDetector, getWriter, kindToCapability } from '../staleness/registry.js';
22
+ import { supports } from '../capabilities.js';
23
+ import { isAgentId } from '../types.js';
24
+ const AUTH_MODES = ['native', 'durable', 'per-device'];
25
+ export function slotDir(harness, accountId) {
26
+ if (!isAgentId(harness))
27
+ throw new Error(`Unknown harness '${harness}' for a slot dir.`);
28
+ if (!accountId || /[\\/]|\.\./.test(accountId)) {
29
+ throw new Error(`Invalid account id '${accountId}' for a slot dir.`);
30
+ }
31
+ return path.join(getHistoryDir(), 'accounts', harness, accountId);
32
+ }
33
+ export function readSlots(meta) {
34
+ return { ...(meta.deviceAccounts?.slots ?? {}) };
35
+ }
36
+ export function recordSlot(accountId, slot) {
37
+ if (slot.accountId !== accountId) {
38
+ throw new Error(`recordSlot accountId mismatch: key '${accountId}' vs slot.accountId '${slot.accountId}'.`);
39
+ }
40
+ if (!AUTH_MODES.includes(slot.authMode)) {
41
+ throw new Error(`recordSlot: unknown authMode '${slot.authMode}'.`);
42
+ }
43
+ updateMeta((current) => ({
44
+ ...current,
45
+ deviceAccounts: {
46
+ ...current.deviceAccounts,
47
+ slots: { ...current.deviceAccounts?.slots, [accountId]: slot },
48
+ },
49
+ }));
50
+ }
51
+ function defaultAuthMode(harness) {
52
+ return harnessWorkerIsPerDevice(harness) ? 'per-device' : 'native';
53
+ }
54
+ function sourceVersion(harness) {
55
+ return getGlobalDefault(harness) ?? listInstalledVersions(harness)[0] ?? null;
56
+ }
57
+ function projectResources(harness, version, destHome, fromHome) {
58
+ const cwd = process.cwd();
59
+ for (const kind of ALL_RESOURCE_KINDS) {
60
+ if (!supports(harness, kindToCapability(kind), version).ok)
61
+ continue;
62
+ const writer = getWriter(kind, harness);
63
+ if (!writer)
64
+ continue;
65
+ if (kind === 'rules') {
66
+ writer.write({ version, versionHome: destHome, selection: { preset: 'default' }, cwd });
67
+ continue;
68
+ }
69
+ const names = getDetector(kind, harness)?.list({ version, versionHome: fromHome, cwd }) ?? [];
70
+ if (names.length === 0)
71
+ continue;
72
+ writer.write({ version, versionHome: destHome, selection: names, cwd });
73
+ }
74
+ }
75
+ /**
76
+ * Create the HOME-shaped slot dir and project settings/resources from the
77
+ * managed install. Does not copy credentials (carryForwardSettings excludes
78
+ * them; resource writers write skills/hooks/commands, never OAuth files).
79
+ * Does not persist the slot record — call {@link recordSlot} after identity
80
+ * is captured.
81
+ */
82
+ export function ensureSlot(harness, accountId) {
83
+ harnessAuth(harness);
84
+ const dir = slotDir(harness, accountId);
85
+ fs.mkdirSync(path.join(dir, agentConfigDirName(harness)), { recursive: true });
86
+ const version = sourceVersion(harness);
87
+ if (version) {
88
+ const fromHome = getVersionHomePath(harness, version);
89
+ if (fs.existsSync(fromHome)) {
90
+ carryForwardSettings(harness, fromHome, dir);
91
+ projectResources(harness, version, dir, fromHome);
92
+ }
93
+ }
94
+ return {
95
+ accountId,
96
+ slotDir: dir,
97
+ authMode: defaultAuthMode(harness),
98
+ verdict: 'unconfigured',
99
+ };
100
+ }
@@ -236,6 +236,22 @@ export declare function supportsAccountInspection(agentId: AgentId): boolean;
236
236
  export declare function accountDisplayLabel(info?: Pick<AccountInfo, 'email' | 'accountId' | 'signedIn' | 'organizationType' | 'organizationName'> | null): string;
237
237
  /** Return the email address associated with the agent's auth config, or null. */
238
238
  export declare function getAccountEmail(agentId: AgentId, home?: string): Promise<string | null>;
239
+ /**
240
+ * Extract full account information (identity, plan, usage status, credits) from
241
+ * the agent's local auth/config files. Supports Claude, Codex, and Gemini.
242
+ */
243
+ /**
244
+ * Resolve a file-auth agent's credential file. Sign-in is account-global, but
245
+ * each installed version gets an isolated home; the credential physically lives
246
+ * only in the home the user logged in under (the one the `~/.<config>` symlink
247
+ * targets). Check the per-version `base` first, then fall back to the active
248
+ * config location under the real HOME so every installed version reflects the
249
+ * true account state (droid/antigravity/kimi all stored login per-version-home
250
+ * and showed non-active versions as "not signed in"). Returns the first
251
+ * existing path, or null.
252
+ */
253
+ /** True when `dir` is under the per-account slot root (`~/.agents/.history/accounts/`). */
254
+ export declare function isAccountSlotDir(dir: string): boolean;
239
255
  /** Where an agent's credential file lives, split into the per-version copy and
240
256
  * the active/global copy under the real HOME. */
241
257
  export interface CredentialPresence {
@@ -22,7 +22,8 @@ import { execFileShellSpec } from '../platform/index.js';
22
22
  import { latestFileMtimeMs } from '../fs-walk.js';
23
23
  import { damerauLevenshtein } from '../fuzzy.js';
24
24
  import { probeCapture } from '../probe.js';
25
- import { getCacheDir, getVersionsDir, getShimsDir, getHistoryDir, getCliVersionCachePath } from '../state.js';
25
+ import { getCacheDir, getVersionsDir, getShimsDir, getHistoryDir, getCliVersionCachePath, readMeta } from '../state.js';
26
+ import { registeredNativeAccountForEmail, parseNativeIdentityKey } from '../native-accounts.js';
26
27
  import { resolveVersion, getVersionHomePath, getBinaryPath } from '../installations/versions.js';
27
28
  import { supports } from '../capabilities.js';
28
29
  import { MCP_TARGETS } from '../mcp-registry.js';
@@ -126,6 +127,12 @@ export function findInPath(command, options = {}) {
126
127
  const stat = fs.statSync(full);
127
128
  if (!stat.isFile())
128
129
  continue;
130
+ // A shell only runs an executable file; `which` skips a mode-644 namesake and
131
+ // so must this, or a stray non-executable `secrets`/`claude` file earlier on
132
+ // PATH would shadow the real binary further down. On win32 the mode bit is
133
+ // meaningless (PATHEXT decides), so the check is POSIX-only.
134
+ if (process.platform !== 'win32')
135
+ fs.accessSync(full, fs.constants.X_OK);
129
136
  const native = resolveNativeBinaryPath(command, full, options);
130
137
  if (native)
131
138
  return native;
@@ -1384,6 +1391,12 @@ export async function getAccountEmail(agentId, home) {
1384
1391
  * and showed non-active versions as "not signed in"). Returns the first
1385
1392
  * existing path, or null.
1386
1393
  */
1394
+ /** True when `dir` is under the per-account slot root (`~/.agents/.history/accounts/`). */
1395
+ export function isAccountSlotDir(dir) {
1396
+ const root = path.resolve(getHistoryDir(), 'accounts');
1397
+ const resolved = path.resolve(dir);
1398
+ return resolved === root || resolved.startsWith(root + path.sep);
1399
+ }
1387
1400
  function resolveAccountCredentialPath(base, ...segments) {
1388
1401
  const perVersion = path.join(base, ...segments);
1389
1402
  try {
@@ -1391,6 +1404,10 @@ function resolveAccountCredentialPath(base, ...segments) {
1391
1404
  return perVersion;
1392
1405
  }
1393
1406
  catch { /* unreadable */ }
1407
+ // A slot is the account's own HOME (PHNX-3940 T5). Never inherit another
1408
+ // account's adopted ~/.<config> — that is the cross-account leak.
1409
+ if (isAccountSlotDir(base))
1410
+ return null;
1394
1411
  const active = path.join(process.env.AGENTS_REAL_HOME || os.homedir(), ...segments);
1395
1412
  if (active !== perVersion) {
1396
1413
  try {
@@ -1900,13 +1917,30 @@ export function readClaudeHomeConfig(base) {
1900
1917
  const oa = config.oauthAccount;
1901
1918
  if (!oa)
1902
1919
  return null;
1903
- const accountId = normalizeIdentityPart(oa.accountUuid);
1904
- const organizationId = normalizeIdentityPart(oa.organizationUuid);
1920
+ const email = normalizeIdentityPart(oa.emailAddress);
1921
+ let accountId = normalizeIdentityPart(oa.accountUuid);
1922
+ let organizationId = normalizeIdentityPart(oa.organizationUuid);
1923
+ // Worker slot provisioned from a durable setup-token: only the email was
1924
+ // seeded on disk (seedClaudeWorkerHomeIdentity), so accountUuid/organizationUuid
1925
+ // are absent and both accountKey and usageKey would be null — the row reads
1926
+ // "not connected here" (PHNX-3940, sibling worker-identity diagnosis). Complete
1927
+ // the identity from the fleet-synced registry row for this email, keyed exactly
1928
+ // as a headed login's home carries it. Guarded on email-only so a headed home
1929
+ // (uuids on disk) never pays the readMeta() read, and fails CLOSED to email-only
1930
+ // when zero or several rows match (registeredNativeAccountForEmail never guesses).
1931
+ if (email && !accountId && !organizationId) {
1932
+ const registered = registeredNativeAccountForEmail(readMeta(), 'claude', email);
1933
+ const parts = registered ? parseNativeIdentityKey('claude', registered.identityKey) : null;
1934
+ if (parts) {
1935
+ accountId = normalizeIdentityPart(parts.account);
1936
+ organizationId = normalizeIdentityPart(parts.org);
1937
+ }
1938
+ }
1905
1939
  return {
1906
1940
  path: activeFile,
1907
1941
  config,
1908
1942
  identity: {
1909
- email: oa.emailAddress || null,
1943
+ email: email ?? (oa.emailAddress || null),
1910
1944
  accountId,
1911
1945
  organizationId,
1912
1946
  organizationName: oa.organizationName ?? null,
@@ -1,10 +1,11 @@
1
1
  /**
2
2
  * Atomic, serialized install of a macOS `.app` bundle to a stable user path.
3
3
  *
4
- * Shared by the two helpers agents-cli installs on darwin — the secrets keychain
5
- * helper (`lib/secrets/install-helper.ts`) and the menu-bar helper
6
- * (`lib/menubar/install-menubar.ts`) — both of which are (re)installed on the hot
7
- * path of ordinary `agents` invocations. Both previously did a non-atomic
4
+ * Used by the menu-bar helper (`lib/menubar/install-menubar.ts`), which is
5
+ * (re)installed on the hot path of ordinary `agents` invocations. (A second
6
+ * helper this module used to also serve, the secrets keychain broker, moved
7
+ * out of this repo entirely with the standalone `secrets` engine —
8
+ * PHNX-3989 — and installs itself now.) It previously did a non-atomic
8
9
  * `rm -rf dest` + `cp -R src dest` straight onto the live bundle. That copy takes
9
10
  * long enough that a concurrent reader (Gatekeeper, or an exec of the bundle) sees
10
11
  * a half-written `.app` — a truncated Mach-O / mismatched `_CodeSignature` hash —
@@ -24,6 +24,8 @@ export interface AuthHealth {
24
24
  detail?: string;
25
25
  /** account label for display (email / id), when known. Never part of the key. */
26
26
  account?: string;
27
+ /** Stable registered account id. Display labels are never used as identity. */
28
+ accountId?: string;
27
29
  }
28
30
  /** Maximum age of an auth verdict used for automatic routing decisions. */
29
31
  export declare const AUTH_PROBE_MAX_AGE_MS: number;
@@ -173,6 +175,7 @@ export interface AuthProbeRow {
173
175
  agent: AgentId;
174
176
  version: string;
175
177
  account?: string;
178
+ accountId?: string;
176
179
  health: AuthHealth;
177
180
  }
178
181
  /** One installed (agent, version) home, tagged with its resolved account label. */
@@ -255,6 +255,7 @@ export function readFleetAuthRows(host) {
255
255
  agent: agent,
256
256
  version: identity.slice(separator + 1),
257
257
  account: health.account,
258
+ accountId: health.accountId,
258
259
  health,
259
260
  });
260
261
  }
@@ -422,6 +423,14 @@ export async function probeLocalFleetAuth(opts) {
422
423
  inst.info = await getAccountInfo(inst.agent, inst.home).catch(() => null);
423
424
  inst.account = authAccountLabel(inst.info);
424
425
  }));
426
+ const [{ readMeta }, { findNativeAccountByIdentity }] = await Promise.all([
427
+ import('./state.js'),
428
+ import('./account-registry.js'),
429
+ ]);
430
+ const meta = readMeta();
431
+ for (const inst of installs) {
432
+ inst.accountId = findNativeAccountByIdentity(meta, inst.agent, inst.info)?.id;
433
+ }
425
434
  // Probe once per (agent, account) — but only for the network-probing agents
426
435
  // that can actually 429; best-effort agents stay per-install (see
427
436
  // groupFleetAuthInstalls). Groups run in parallel: they target distinct
@@ -430,12 +439,14 @@ export async function probeLocalFleetAuth(opts) {
430
439
  const rep = group.probe;
431
440
  const health = await probeAuthHealth(rep.agent, rep.home, { cliVersion: opts?.cliVersion, info: rep.info, forceLive: opts?.forceLive, signal: opts?.signal });
432
441
  health.account = authAccountLabel(rep.info);
442
+ health.accountId = rep.accountId;
433
443
  if (health.verdict === 'unconfigured')
434
444
  return [];
435
445
  return group.members.map((inst) => ({
436
446
  agent: inst.agent,
437
447
  version: inst.version,
438
448
  account: health.account,
449
+ accountId: inst.accountId ?? health.accountId,
439
450
  // A distinct object per row so a later mutation of one can't bleed across.
440
451
  health: { ...health },
441
452
  }));
@@ -15,25 +15,34 @@
15
15
  */
16
16
  import type { AgentId } from './types.js';
17
17
  import { type CredentialAccount } from './account-registry.js';
18
+ import { type StorableCredentialKind } from './secrets-policy.js';
18
19
  import { type DriveOptions, type PtyDriver } from './fleet/remote-login.js';
19
20
  /** Well-formed Claude setup-token as it appears inside a TTY blob. */
20
21
  export declare const CLAUDE_SETUP_TOKEN_CAPTURE_RE: RegExp;
21
22
  export interface MintFlow {
22
23
  harness: AgentId;
23
24
  provider: string;
24
- auth: 'setup-token';
25
- /** Interactive mint argv after HOME=… <bin>. Null when stdin-seed only. */
25
+ auth: 'setup-token' | 'api-key';
26
+ /** Interactive mint argv after HOME=… <bin>. Null when stdin-seed only or an api-key collection flow. */
26
27
  mintArgs: string[] | null;
27
- verificationUrlRegex: RegExp;
28
- tokenCapture: RegExp;
28
+ /** api-key flows: the env var the collected key injects as on a worker (e.g. OPENAI_API_KEY). */
29
+ apiKeyEnv?: string;
30
+ /** setup-token flows: authorize-URL and token patterns scraped from the PTY. */
31
+ verificationUrlRegex?: RegExp;
32
+ tokenCapture?: RegExp;
29
33
  }
30
34
  /**
31
- * Harnesses that expose an interactive setup-token mint. Native device-code
32
- * login (codex/droid/kimi/grok) stays on `agents fleet login`; API keys stay
33
- * on `agents accounts add`. Adding a harness here without a real mint command
34
- * is a lying table — do not.
35
+ * Durable-credential flows per harness. Only Claude exposes an interactive
36
+ * setup-token MINT; every other harness's durable credential is a provider API
37
+ * key, COLLECTED via `--api-key` or a prompt by `accounts add` (never derived
38
+ * from OAuth). The api-key entries describe collection only — they have no
39
+ * mint argv, and `mintAndSeed` refuses them with the `accounts add` pointer.
40
+ * Native device-code login (kimi/antigravity) stays on `agents fleet login`.
41
+ * The apiKeyEnv values are pinned to HARNESS_AUTH's `api-key:<ENV>` worker
42
+ * kinds by auth-mint.test.ts — keep them in lockstep.
35
43
  */
36
44
  export declare const MINT_FLOWS: Record<string, MintFlow>;
45
+ /** Harnesses with an interactive setup-token mint (api-key flows are collection, not mint). */
37
46
  export declare function listMintableHarnesses(): AgentId[];
38
47
  export declare function getMintFlow(harnessRaw: string): MintFlow;
39
48
  export declare function unmintableMessage(harness: string): string;
@@ -75,6 +84,32 @@ export declare function resolveMintIdentity(input: ResolveMintIdentityInput): Re
75
84
  export declare function seedReservedAuthToken(email: string, token: string): {
76
85
  key: string;
77
86
  };
87
+ /**
88
+ * The env var a harness's durable worker credential injects as on a worker
89
+ * (claude's setup-token rides CLAUDE_CODE_OAUTH_TOKEN; api-key harnesses ride
90
+ * their MINT_FLOWS apiKeyEnv). Throws for a harness with no portable worker
91
+ * credential — kimi/antigravity log in per box.
92
+ */
93
+ export declare function workerCredentialEnv(harness: AgentId): string;
94
+ /**
95
+ * The reserved-store key for one account's worker credential:
96
+ * `<ENV>_<accountId>` — keyed by account id, never by name or email, so a
97
+ * rename or an email change never breaks the key. Hyphens are stripped because
98
+ * the bundle key grammar (`BUNDLE_KEY_PATTERN`) is env-var-shaped.
99
+ */
100
+ export declare function workerCredentialStoreKey(harness: AgentId, accountId: string): string;
101
+ /**
102
+ * Write (or rotate) one account's worker credential in the reserved
103
+ * `__<harness>__` store (PHNX-3940). Only non-rotating kinds may enter
104
+ * (`assertStorableCredentialKind` refuses a rotating OAuth/session credential at
105
+ * this boundary — RUSH-1958). FILE-backed, policy `never`, same contract as the
106
+ * legacy `auth` bundle: a keychain- or vault-backed store of this name fails
107
+ * loud, since worker provisioning reads the file backend.
108
+ */
109
+ export declare function seedReservedStoreKey(harness: AgentId, kind: StorableCredentialKind, key: string, value: string): {
110
+ bundle: string;
111
+ key: string;
112
+ };
78
113
  /**
79
114
  * Create or rotate the named provider account that `agents run --account` and
80
115
  * `agents accounts sync` consume. Existing account of a different kind fails
@@ -104,7 +139,7 @@ export interface DriveSetupTokenMintOpts extends MintDriveHooks {
104
139
  * Tears the session down on the way out (success, timeout, or throw).
105
140
  */
106
141
  export declare function driveSetupTokenMint(command: string, flow: MintFlow, opts?: DriveSetupTokenMintOpts): Promise<DriveMintResult>;
107
- export declare function buildMintCommand(flow: MintFlow, bin: string, home: string): string;
142
+ export declare function buildMintCommand(flow: MintFlow, bin: string, home: string, env?: Record<string, string>): string;
108
143
  export declare function resolveMintInstallation(harness: AgentId): {
109
144
  version: string;
110
145
  bin: string;
@@ -146,13 +181,14 @@ export declare function resolveSyncTargets(fleet: boolean, devices: string[]): P
146
181
  /**
147
182
  * True when a Claude setup-token is already seeded on this box (setup status).
148
183
  *
149
- * Read-only status probe (`agents setup`, `agents doctor`) — the reserved
150
- * auth-bundle check below MUST NOT crash the whole command because the
151
- * keychain could not be reached (e.g. the macOS Keychain helper source is
152
- * unavailable — PHNX-3385); `bundleExists()`/`hasKeychainToken()` document
153
- * themselves as failing loud, but that contract is for destructive-write
154
- * guards, not a diagnostic. An unreachable keychain here just means "cannot
155
- * confirm the reserved bundle," reported honestly rather than propagated.
184
+ * Read-only status probe (`agents setup`, `agents doctor`) — neither the
185
+ * account-registry read nor the reserved auth-bundle check MUST crash the whole
186
+ * command because the secret store could not be reached (the macOS Keychain
187
+ * helper source unavailable — PHNX-3385 — or the standalone `secrets` CLI
188
+ * missing / unspawnable); the client's calls fail loud by contract, but that
189
+ * contract is for destructive-write guards, not a diagnostic. An unreachable
190
+ * store here just means "cannot confirm," reported honestly rather than
191
+ * propagated.
156
192
  */
157
193
  export declare function hasMintedSetupToken(): {
158
194
  ready: boolean;