@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
@@ -843,6 +843,23 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
843
843
  console.error(chalk.red(`Invalid account picker target: ${agentSpec}. Use agents run <agent>@.`));
844
844
  process.exit(1);
845
845
  }
846
+ // Peel `#name` off before --device dispatch so the selector rides the hop
847
+ // unchanged and the peer resolves ITS slot (PHNX-3940 T5). The later local
848
+ // parse is idempotent when the values match.
849
+ {
850
+ const labelParts = normalizedAgentSpec.split('#');
851
+ if (labelParts.length > 2 || labelParts[1] === '') {
852
+ console.error(chalk.red(`Invalid account label in '${normalizedAgentSpec}'.`));
853
+ process.exit(1);
854
+ }
855
+ const specAccountLabel = labelParts[1];
856
+ if (specAccountLabel && options.account && specAccountLabel !== options.account) {
857
+ console.error(chalk.red(`Account '${specAccountLabel}' from the agent spec conflicts with --account '${options.account}'.`));
858
+ process.exit(1);
859
+ }
860
+ if (specAccountLabel)
861
+ options.account = specAccountLabel;
862
+ }
846
863
  // Hard-deprecated harnesses cannot be run — point the user at the successor.
847
864
  const runBaseAgentName = normalizedAgentSpec.split('#')[0].split('@')[0];
848
865
  const runBaseAgentId = resolveAgentName(runBaseAgentName);
@@ -1130,11 +1147,11 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
1130
1147
  // with an actionable hint instead of hard-failing the run (F5).
1131
1148
  if (netMode === 'tailscale') {
1132
1149
  const { pickTailscaleBundleFromList } = await import('../lib/crabbox/cli.js');
1133
- const { listBundles } = await import('../lib/secrets/bundles.js');
1150
+ const { listBundles } = await import('../lib/secrets-client.js');
1134
1151
  let hasKey = !!process.env.CRABBOX_TAILSCALE_AUTH_KEY;
1135
1152
  if (!hasKey) {
1136
1153
  try {
1137
- hasKey = !!pickTailscaleBundleFromList(listBundles());
1154
+ hasKey = !!pickTailscaleBundleFromList(await listBundles());
1138
1155
  }
1139
1156
  catch {
1140
1157
  hasKey = false;
@@ -1800,12 +1817,16 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
1800
1817
  }
1801
1818
  process.exit(resumeExit);
1802
1819
  }
1803
- const [{ buildExecCommand, parseExecEnv, execAgent, runWithFallback, normalizeMode, resolveMode, implicitModeFor, headlessPlanStallCommand, nativeResume, resolveInteractive, inferredInteractiveWithoutTty }, { ALL_AGENT_IDS, ACCOUNT_INSPECTION_AGENT_IDS, agentLabel, supportsAccountInspection }, { profileExists, readProfile, resolveProfileForRun }, { readAndResolveBundleEnv, describeBundle, assertRemoteBundleFlagsUnsupported }, { splitBundleRef, resolveHostSshTarget, remoteResolveEnv }, { getConfiguredRunStrategy, normalizeRunStrategy, resolveRunVersion, rotationFailoverChain, shouldArmRotationFailover, RUN_STRATEGIES, collectHarnessCandidates, pickHarnessWeighted, classifyHarnessCandidates, formatHarnessPickBanner, formatNoHealthyHarnessError, formatNoHealthyAccountError, formatNoVerifiedUsageError, signInRecoverableCandidates }, { getGlobalDefault, getVersionHomePath, resolveVersion, resolveVersionAlias, ensureAgentRunnable }, { buildDiscoveredPlugin, loadPluginManifest, syncPluginToVersion }, { parseWorkflowFrontmatter, resolveWorkflowRef, resolveAllowedSubagents, pruneStaleWorkflowSubagents, ensureSubagentDispatchTool }, { resolveRunDefaults }, { getMcpServersByName, buildWorkflowMcpConfig }, { supports, capableAgents }, { shareRuntimeEnv },] = await Promise.all([
1820
+ const [{ buildExecCommand, parseExecEnv, execAgent, runWithFallback, normalizeMode, resolveMode, implicitModeFor, headlessPlanStallCommand, nativeResume, resolveInteractive, inferredInteractiveWithoutTty }, { ALL_AGENT_IDS, ACCOUNT_INSPECTION_AGENT_IDS, agentLabel, supportsAccountInspection }, { profileExists, readProfile, resolveProfileForRun },
1821
+ // Engine DATA ops resolve through the standalone `secrets` process client
1822
+ // (PHNX-3989); the agents-owned `bundle@host` policy helpers stay in agents-cli.
1823
+ { readAndResolveBundleEnv, describeBundle, remoteResolveEnv }, { assertRemoteBundleFlagsUnsupported, splitBundleRef, resolveSecretsContextForRun }, { resolveHostSshTarget }, { getConfiguredRunStrategy, normalizeRunStrategy, resolveRunVersion, rotationFailoverChain, shouldArmRotationFailover, RUN_STRATEGIES, collectHarnessCandidates, pickHarnessWeighted, classifyHarnessCandidates, formatHarnessPickBanner, formatNoHealthyHarnessError, formatNoHealthyAccountError, formatNoVerifiedUsageError, signInRecoverableCandidates }, { getGlobalDefault, getVersionHomePath, resolveVersion, resolveVersionAlias, ensureAgentRunnable }, { buildDiscoveredPlugin, loadPluginManifest, syncPluginToVersion }, { parseWorkflowFrontmatter, resolveWorkflowRef, resolveAllowedSubagents, pruneStaleWorkflowSubagents, ensureSubagentDispatchTool }, { resolveRunDefaults }, { getMcpServersByName, buildWorkflowMcpConfig }, { supports, capableAgents }, { shareRuntimeEnv },] = await Promise.all([
1804
1824
  import('../lib/exec.js'),
1805
1825
  import('../lib/agents.js'),
1806
1826
  import('../lib/profiles.js'),
1807
- import('../lib/secrets/bundles.js'),
1808
- import('../lib/secrets/remote.js'),
1827
+ import('../lib/secrets-client.js'),
1828
+ import('../lib/secrets-policy.js'),
1829
+ import('../lib/hosts/credential-transport.js'),
1809
1830
  import('../lib/accounting/rotate.js'),
1810
1831
  import('../lib/installations/versions.js'),
1811
1832
  import('../lib/plugins/plugins.js'),
@@ -1844,6 +1865,7 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
1844
1865
  let profileEnv;
1845
1866
  let accountEnv;
1846
1867
  let accountConfigVersion;
1868
+ let execHome;
1847
1869
  let profileProvider;
1848
1870
  let fromProfile = false;
1849
1871
  let profileName;
@@ -2169,6 +2191,10 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
2169
2191
  if (!selected)
2170
2192
  return;
2171
2193
  version = selected.version;
2194
+ if (selected.nativeAccount)
2195
+ options.account = selected.nativeAccount;
2196
+ if (selected.slotDir)
2197
+ execHome = selected.slotDir;
2172
2198
  if (!options.quiet) {
2173
2199
  const identity = selected.accountLabel || 'signed-in account';
2174
2200
  process.stderr.write(chalk.gray(`[agents] selected ${identity} · ${agent}@${selected.version} for this run\n`));
@@ -2204,35 +2230,49 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
2204
2230
  process.exit(1);
2205
2231
  }
2206
2232
  if (spawnAccount.kind === 'native') {
2207
- // A native login is owned by the harness and read from its own home —
2208
- // it cannot be forwarded to another machine, and no secret/env is
2209
- // injected. Fail closed for a remote target.
2233
+ // `#name` / `--account` on a --device run is forwarded unchanged at
2234
+ // dispatch time; the peer resolves ITS slot. This block is local-only.
2210
2235
  const remoteTarget = options.host || options.device;
2211
- if (remoteTarget) {
2212
- console.error(chalk.red(`Account '${spawnAccount.name}' is a device-local ${spawnAccount.agent} login and cannot be forwarded to '${remoteTarget}'. Sign in on that device and name the login there.`));
2213
- process.exit(1);
2214
- }
2215
- const { CONFIG_ENV_ISOLATED_AGENTS } = await import('../lib/installations/shims.js');
2216
- if (!CONFIG_ENV_ISOLATED_AGENTS.includes(agent)) {
2217
- console.error(chalk.red(`${agent} cannot select native accounts independently of its installed version.`));
2218
- process.exit(1);
2219
- }
2220
- const { resolveAccountVersion } = await import('../lib/accounting/rotate.js');
2221
- const { nativeAccountHome } = await import('../lib/account-registry.js');
2222
- const { readMeta } = await import('../lib/state.js');
2223
- accountConfigVersion = await resolveAccountVersion(agent, spawnAccount.identityKey, nativeAccountHome(spawnAccount.id, readMeta())) ?? undefined;
2224
- if (!accountConfigVersion) {
2225
- console.error(chalk.red(`No installed ${spawnAccount.agent} version is signed in as the identity labeled '${spawnAccount.name}'. Sign in as that identity, or label a different account.`));
2226
- process.exit(1);
2236
+ if (!remoteTarget) {
2237
+ const { adoptedConfigPointsAtHome, adoptedSymlinkMismatchError, isSymlinkAdoptedHarness, resolveNativeSpawnHome, symlinkAdoptedAccountError, } = await import('../lib/exec-account-home.js');
2238
+ const { readMeta } = await import('../lib/state.js');
2239
+ const meta = readMeta();
2240
+ if (isSymlinkAdoptedHarness(agent)) {
2241
+ const defaultName = meta.accounts?.defaults?.[agent];
2242
+ if (spawnAccount.name !== defaultName) {
2243
+ console.error(chalk.red(symlinkAdoptedAccountError(agent, spawnAccount.name, defaultName)));
2244
+ process.exit(1);
2245
+ }
2246
+ }
2247
+ try {
2248
+ const resolved = await resolveNativeSpawnHome(agent, spawnAccount, meta);
2249
+ if (isSymlinkAdoptedHarness(agent) && !adoptedConfigPointsAtHome(agent, resolved.execHome)) {
2250
+ console.error(chalk.red(adoptedSymlinkMismatchError(agent, spawnAccount.name, resolved.execHome)));
2251
+ process.exit(1);
2252
+ }
2253
+ execHome = resolved.execHome;
2254
+ if (resolved.source === 'legacy-home') {
2255
+ accountConfigVersion = resolved.label;
2256
+ // A leftover version-labeled home is both the spawn HOME and
2257
+ // the account's installation — the pre-T5 `resolveAccountVersion`
2258
+ // behavior. An explicit `@<label>` still controls the binary.
2259
+ if (!version && !fromProfile && resolved.label)
2260
+ version = resolved.label;
2261
+ }
2262
+ }
2263
+ catch (err) {
2264
+ console.error(chalk.red(err.message));
2265
+ process.exit(1);
2266
+ }
2267
+ // Slot / provisioned: binary comes from the one managed install unless `@<label>` pins it.
2268
+ if (!version && !fromProfile) {
2269
+ const { ensureHarnessInstallation } = await import('../lib/installations/store.js');
2270
+ const { installation } = await ensureHarnessInstallation(agent);
2271
+ version = installation.label;
2272
+ }
2273
+ if (!options.quiet)
2274
+ process.stderr.write(chalk.gray(`[agents] account '${spawnAccount.name}' · ${agent}\n`));
2227
2275
  }
2228
- // An account-only run executes the account's own stable installation.
2229
- // This keeps its model/mode configuration and binary update policy
2230
- // together. An explicit installation or profile still controls the
2231
- // executable independently; configVersion continues to select auth.
2232
- if (!version && !fromProfile)
2233
- version = accountConfigVersion;
2234
- if (!options.quiet)
2235
- process.stderr.write(chalk.gray(`[agents] account '${spawnAccount.name}' · ${agent}\n`));
2236
2276
  }
2237
2277
  else {
2238
2278
  accountEnv = spawnAccount.env;
@@ -2713,10 +2753,12 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
2713
2753
  authedViaSetupToken = resolveClaudeSetupToken(getVersionHomePath('claude', version)) !== null;
2714
2754
  }
2715
2755
  if (!info.signedIn && !authedViaSetupToken) {
2716
- const { connectSupported } = await import('../lib/accounts/connect.js');
2717
- const hint = connectSupported(agent)
2718
- ? `agents accounts connect ${agent}${spawnAccount?.kind === 'native' ? ` ${spawnAccount.name}` : ''}`
2719
- : loginHint(agent);
2756
+ const { addSupported } = await import('../lib/accounts/add.js');
2757
+ const hint = spawnAccount?.kind === 'native'
2758
+ ? `agents accounts login ${agent}#${spawnAccount.name}`
2759
+ : addSupported(agent)
2760
+ ? `agents accounts add ${agent} <name>`
2761
+ : loginHint(agent);
2720
2762
  process.stderr.write(chalk.yellow(`${agent} looks logged out — sign in with: ${hint}. Launching anyway...\n`));
2721
2763
  }
2722
2764
  }
@@ -2805,6 +2847,11 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
2805
2847
  ? options.secretsKeys.split(',').map((k) => k.trim()).filter(Boolean)
2806
2848
  : undefined;
2807
2849
  let secretsEnv = {};
2850
+ // Resource-profile scoping (CTX-1): agents-cli computes which bundle
2851
+ // names the active profile allows and forwards it as the client's
2852
+ // `allowedBundles` on every local resolution — the standalone has no
2853
+ // concept of a profile. undefined (no active profile) is full trust.
2854
+ const secretsContext = await resolveSecretsContextForRun(agent);
2808
2855
  for (const bundleRef of options.secrets) {
2809
2856
  try {
2810
2857
  const { bundle: bundleName, host } = splitBundleRef(bundleRef);
@@ -2822,7 +2869,7 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
2822
2869
  secretsEnv = { ...secretsEnv, ...bundleEnv };
2823
2870
  }
2824
2871
  else {
2825
- const { bundle, env: bundleEnv } = readAndResolveBundleEnv(bundleName, {
2872
+ const { bundle, env: bundleEnv } = await readAndResolveBundleEnv(bundleName, {
2826
2873
  caller: `agent ${agent}`,
2827
2874
  agent,
2828
2875
  keys: secretsKeysSubset,
@@ -2834,8 +2881,8 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
2834
2881
  // isHeadlessSecretsContext() left `--interactive` launches (the watchdog's
2835
2882
  // `agents run auto --interactive`) able to prompt, piling up helper sheets.
2836
2883
  agentOnly: true,
2837
- });
2838
- const entries = describeBundle(bundle);
2884
+ }, secretsContext);
2885
+ const entries = await describeBundle(bundle, secretsContext);
2839
2886
  const counts = {};
2840
2887
  for (const e of entries) {
2841
2888
  counts[e.kind] = (counts[e.kind] || 0) + 1;
@@ -2881,6 +2928,7 @@ agents run auto --device yosemite-s0 "fix the flaky test" # pin the device
2881
2928
  harnessName: profileName,
2882
2929
  version,
2883
2930
  configVersion: accountConfigVersion,
2931
+ execHome,
2884
2932
  prompt,
2885
2933
  interactive: options.interactive || forceInteractive,
2886
2934
  mode: requestedMode,
@@ -15,7 +15,7 @@ import * as yaml from 'yaml';
15
15
  import { loadDevices } from '../lib/devices/registry.js';
16
16
  import { readMeta, updateMeta, getDevicePinsPath } from '../lib/state.js';
17
17
  import { machineId } from '../lib/machine-id.js';
18
- import { listBundles } from '../lib/secrets/bundles.js';
18
+ import { listBundles, SecretsClientError } from '../lib/secrets-client.js';
19
19
  import { listJobs } from '../lib/scheduling/routines.js';
20
20
  import { captureFleet } from '../lib/fleet/capture.js';
21
21
  /** Read THIS machine's pins file into `name -> [id@latest]`. Only used with
@@ -37,6 +37,25 @@ function agentsFromPins(names) {
37
37
  const ids = pins?.agents ? Object.keys(pins.agents) : [];
38
38
  return ids.length > 0 ? { [self]: ids.map((id) => `${id}@latest`) } : {};
39
39
  }
40
+ /**
41
+ * The names to snapshot into `fleet.secretsBundles` — best-effort. Capture's
42
+ * primary job (device/agent/routine snapshot) is unrelated to secrets, so a
43
+ * missing standalone `secrets` install (DIST-1: no fallback engine) degrades
44
+ * this ONE optional field to empty with a warning rather than failing the
45
+ * whole capture. A real store error still surfaces.
46
+ */
47
+ async function captureSecretsBundleNames() {
48
+ try {
49
+ return (await listBundles()).map((b) => b.name);
50
+ }
51
+ catch (err) {
52
+ if (err instanceof SecretsClientError && err.code === 'SECRETS_BIN_MISSING') {
53
+ console.error(chalk.yellow('warning: standalone `secrets` CLI not installed — capturing with no secrets bundles.'));
54
+ return [];
55
+ }
56
+ throw err;
57
+ }
58
+ }
40
59
  async function runCapture(opts) {
41
60
  const meta = readMeta();
42
61
  // Roster: every registered device name.
@@ -68,7 +87,7 @@ async function runCapture(opts) {
68
87
  // Browser profiles are intentionally NOT captured — the central `browser:`
69
88
  // block already syncs via the repo, and its ssh:// endpoints can carry
70
89
  // `user@host`, which must never be copied into the fleet: block.
71
- secretsBundles: listBundles().map((b) => b.name),
90
+ secretsBundles: await captureSecretsBundleNames(),
72
91
  routines: listJobs().map((j) => j.name),
73
92
  };
74
93
  // Discovery decisions and dismissals are device-scoped now (PHNX-3315): they
@@ -29,7 +29,7 @@
29
29
  import chalk from 'chalk';
30
30
  import { baseUrlEnvKeyForHost, authEnvKeyForHost, modelEnvKeyForHost, validateProfileName, listProfiles, } from '../lib/profiles.js';
31
31
  import { listPresets, getPreset } from '../lib/profiles-presets.js';
32
- import { listBundles } from '../lib/secrets/bundles.js';
32
+ import { listBundles } from '../lib/secrets-client.js';
33
33
  import { AGENTS, ALL_AGENT_IDS, isSelfUpdatingAgent, resolveAgentName } from '../lib/agents.js';
34
34
  import { readAccountRegistry } from '../lib/account-registry.js';
35
35
  /**
@@ -127,7 +127,7 @@ function knownProviders() {
127
127
  * is today's behavior, lifted verbatim; RUSH-2220 enriches the bundle browse.
128
128
  */
129
129
  async function askKeySource(io, draft, provider) {
130
- const bundles = listBundles();
130
+ const bundles = await listBundles();
131
131
  const source = bundles.length > 0
132
132
  ? await io.select({
133
133
  message: `How should '${provider}' get its key?`,
@@ -11,8 +11,7 @@ import chalk from 'chalk';
11
11
  import { showUrl } from '../lib/open-url.js';
12
12
  import { crabboxList, crabboxStop, reapSafeOrphans, reapOrphans, setLeaseSecretsBundle } from '../lib/crabbox/cli.js';
13
13
  import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
14
- import { bundleExists, readBundle, writeBundle, keychainRef, bundleItemStore } from '../lib/secrets/bundles.js';
15
- import { secretsKeychainItem } from '../lib/secrets/index.js';
14
+ import { bundleExists, keychainRef, readBundle, secretsKeychainItem, writeBundleWithItems } from '../lib/secrets-client.js';
16
15
  function fmtIdle(box) {
17
16
  if (box.lastTouchedAt === null)
18
17
  return 'idle ?';
@@ -108,13 +107,11 @@ async function captureTailscaleAuthKey() {
108
107
  console.error(chalk.dim('Skipped Tailscale setup — public-IP leases only.'));
109
108
  return;
110
109
  }
111
- const bundle = bundleExists(TAILSCALE_BUNDLE)
112
- ? readBundle(TAILSCALE_BUNDLE)
110
+ const bundle = (await bundleExists(TAILSCALE_BUNDLE))
111
+ ? await readBundle(TAILSCALE_BUNDLE)
113
112
  : { name: TAILSCALE_BUNDLE, description: 'Tailscale ephemeral auth key for crabbox tailnet leases', vars: {} };
114
- const store = bundleItemStore(bundle.backend);
115
- store.set(secretsKeychainItem(TAILSCALE_BUNDLE, TAILSCALE_KEY), key);
116
113
  bundle.vars[TAILSCALE_KEY] = keychainRef(TAILSCALE_KEY);
117
- writeBundle(bundle);
114
+ await writeBundleWithItems(bundle, new Map([[secretsKeychainItem(TAILSCALE_BUNDLE, TAILSCALE_KEY), key]]));
118
115
  console.error(chalk.green(`✔ Stored Tailscale auth key in keychain bundle '${TAILSCALE_BUNDLE}'.`));
119
116
  console.error(chalk.dim(' Add --tailscale to a lease (reuse defaults to it) to reach the box over your tailnet.'));
120
117
  }
@@ -176,13 +173,11 @@ export async function runLeaseSetup(opts = {}) {
176
173
  else
177
174
  spinner.succeed('Token valid — Hetzner API reachable.');
178
175
  // Store into the `hetzner.com` keychain bundle (mirrors writeSyncBundle).
179
- const bundle = bundleExists(HETZNER_BUNDLE)
180
- ? readBundle(HETZNER_BUNDLE)
176
+ const bundle = (await bundleExists(HETZNER_BUNDLE))
177
+ ? await readBundle(HETZNER_BUNDLE)
181
178
  : { name: HETZNER_BUNDLE, description: 'Hetzner Cloud API token for crabbox leases', vars: {} };
182
- const store = bundleItemStore(bundle.backend);
183
- store.set(secretsKeychainItem(HETZNER_BUNDLE, HCLOUD_KEY), token);
184
179
  bundle.vars[HCLOUD_KEY] = keychainRef(HCLOUD_KEY);
185
- writeBundle(bundle);
180
+ await writeBundleWithItems(bundle, new Map([[secretsKeychainItem(HETZNER_BUNDLE, HCLOUD_KEY), token]]));
186
181
  setLeaseSecretsBundle(HETZNER_BUNDLE);
187
182
  console.error(chalk.green(`\n✔ Stored in keychain bundle '${HETZNER_BUNDLE}' and set as the default lease provider.`));
188
183
  console.error(chalk.dim(' Run `agents run <agent> "…" --lease` — no env var, no flag needed.'));
@@ -38,8 +38,8 @@ export interface AddProfileOptions {
38
38
  *
39
39
  * Reads the bundle value via {@link getKeychainToken} — this pops Touch ID on
40
40
  * macOS once, for that bundle-namespaced item (`agents-cli.secrets.*`, gated by
41
- * `keychainItemRequiresUserPresence` in `../lib/secrets/index.ts`) — then writes
42
- * it to `keychainItemName(provider)`, a plain `agents-cli.<provider>.token`
41
+ * the standalone's user-presence rule for `agents-cli.secrets.*` items) — then writes
42
+ * it to `profileKeychainItem(provider)`, a plain `agents-cli.<provider>.token`
43
43
  * item. That item matches neither the `agents-cli.secrets.` nor
44
44
  * `agents-cli.bundles.` prefix, so every later read of the harness's own key is
45
45
  * silent — no repeat Touch ID prompt.
@@ -10,9 +10,7 @@ import chalk from 'chalk';
10
10
  import { readStdinSync } from '../lib/format.js';
11
11
  import { writeProfile, profileFromPreset, profileFromHostModel, baseUrlEnvKeyForHost, authEnvKeyForHost, validateProfileName, profileExists, } from '../lib/profiles.js';
12
12
  import { getPreset, listPresets } from '../lib/profiles-presets.js';
13
- import { hasKeychainToken, keychainItemName, setKeychainToken, getKeychainToken, } from '../lib/secrets/profiles.js';
14
- import { parseBundleValue, secretsKeychainItem } from '../lib/secrets/index.js';
15
- import { readBundle } from '../lib/secrets/bundles.js';
13
+ import { getKeychainToken, hasKeychainToken, isSecretsClientError, parseBundleValue, profileKeychainItem, readBundle, secretsKeychainItem, setKeychainToken, } from '../lib/secrets-client.js';
16
14
  import { isInteractiveTerminal } from './utils.js';
17
15
  import { ALL_AGENT_IDS } from '../lib/agents.js';
18
16
  import { findAccount } from '../lib/account-registry.js';
@@ -28,7 +26,7 @@ export function buildProfileFromCollection(name, preset, collected, version) {
28
26
  env: { ...preset.env, ...collected },
29
27
  auth: {
30
28
  envVar: preset.authEnvVar,
31
- keychainItem: keychainItemName(preset.provider),
29
+ keychainItem: profileKeychainItem(preset.provider),
32
30
  },
33
31
  authOptional: preset.authOptional,
34
32
  description: preset.description,
@@ -47,8 +45,8 @@ async function promptForSecret(message) {
47
45
  /** Read all available data from stdin synchronously, trimmed. */
48
46
  /** Ensure a provider API key exists in keychain, prompting or reading stdin if missing. */
49
47
  export async function ensureProviderToken(provider, signupUrl, fromStdin) {
50
- const item = keychainItemName(provider);
51
- if (hasKeychainToken(item)) {
48
+ const item = profileKeychainItem(provider);
49
+ if (await hasKeychainToken(item)) {
52
50
  return;
53
51
  }
54
52
  let token;
@@ -62,7 +60,7 @@ export async function ensureProviderToken(provider, signupUrl, fromStdin) {
62
60
  const hint = signupUrl ? ` (get one at ${signupUrl})` : '';
63
61
  token = await promptForSecret(`Enter API key for ${provider}${hint}`);
64
62
  }
65
- setKeychainToken(item, token);
63
+ await setKeychainToken(item, token);
66
64
  console.log(chalk.green(`Stored in keychain: ${item}`));
67
65
  }
68
66
  /**
@@ -72,8 +70,8 @@ export async function ensureProviderToken(provider, signupUrl, fromStdin) {
72
70
  *
73
71
  * Reads the bundle value via {@link getKeychainToken} — this pops Touch ID on
74
72
  * macOS once, for that bundle-namespaced item (`agents-cli.secrets.*`, gated by
75
- * `keychainItemRequiresUserPresence` in `../lib/secrets/index.ts`) — then writes
76
- * it to `keychainItemName(provider)`, a plain `agents-cli.<provider>.token`
73
+ * the standalone's user-presence rule for `agents-cli.secrets.*` items) — then writes
74
+ * it to `profileKeychainItem(provider)`, a plain `agents-cli.<provider>.token`
77
75
  * item. That item matches neither the `agents-cli.secrets.` nor
78
76
  * `agents-cli.bundles.` prefix, so every later read of the harness's own key is
79
77
  * silent — no repeat Touch ID prompt.
@@ -106,7 +104,16 @@ export async function applyFromSecrets(profile, spec, explicitAuthProvider, opts
106
104
  const sep = spec.indexOf(':');
107
105
  const bundleName = sep === -1 ? spec : spec.slice(0, sep);
108
106
  const requestedKey = sep === -1 ? undefined : spec.slice(sep + 1);
109
- const bundle = readBundle(bundleName);
107
+ let bundle;
108
+ try {
109
+ bundle = await readBundle(bundleName);
110
+ }
111
+ catch (err) {
112
+ // The standalone reports only a code; name the bundle the user asked for.
113
+ if (!isSecretsClientError(err, 'NOT_FOUND'))
114
+ throw err;
115
+ throw new Error(`Secrets bundle '${bundleName}' not found. List bundles with 'agents secrets list'.`);
116
+ }
110
117
  const keys = Object.keys(bundle.vars);
111
118
  const key = requestedKey ?? (keys.length === 1 ? keys[0] : undefined);
112
119
  if (!key) {
@@ -123,7 +130,7 @@ export async function applyFromSecrets(profile, spec, explicitAuthProvider, opts
123
130
  value = parsed.literal;
124
131
  }
125
132
  else if (parsed.ref.provider === 'keychain') {
126
- value = getKeychainToken(secretsKeychainItem(bundle.name, parsed.ref.value));
133
+ value = await getKeychainToken(secretsKeychainItem(bundle.name, parsed.ref.value));
127
134
  }
128
135
  else {
129
136
  throw new Error(`Bundle '${bundleName}' key '${key}' is a '${parsed.ref.provider}:' reference, not a keychain-backed secret — --from-secrets only copies keychain-backed values.`);
@@ -134,8 +141,8 @@ export async function applyFromSecrets(profile, spec, explicitAuthProvider, opts
134
141
  `on a fork, so it never silently overwrites the source harness's shared keychain item.`);
135
142
  }
136
143
  const provider = explicitAuthProvider || (profile.auth ? profile.provider : undefined) || bundleName;
137
- const item = keychainItemName(provider);
138
- setKeychainToken(item, value);
144
+ const item = profileKeychainItem(provider);
145
+ await setKeychainToken(item, value);
139
146
  if (!profile.auth) {
140
147
  const envVar = authEnvKeyForHost(profile.host.agent);
141
148
  if (!envVar) {
@@ -64,7 +64,7 @@ async function publishUserRepoAccountState(provisionAuth) {
64
64
  if (usage.error)
65
65
  console.error(chalk.yellow(`Usage snapshot: ${usage.error}`));
66
66
  if (provisionAuth) {
67
- const { syncReservedAuthBundle } = await import('../lib/secrets/reserved-sync.js');
67
+ const { syncReservedAuthBundle } = await import('../lib/secrets-policy.js');
68
68
  const auth = await syncReservedAuthBundle();
69
69
  if (auth.pushed.length > 0)
70
70
  console.log(chalk.gray(`Auth bundle: pushed to ${auth.pushed.join(', ')}`));
@@ -72,7 +72,7 @@ async function publishUserRepoAccountState(provisionAuth) {
72
72
  console.error(chalk.yellow(`Auth bundle: ${err.device}: ${err.message}`));
73
73
  return;
74
74
  }
75
- const { publishReservedAuthVerdict } = await import('../lib/secrets/reserved-sync.js');
75
+ const { publishReservedAuthVerdict } = await import('../lib/secrets-policy.js');
76
76
  const auth = await publishReservedAuthVerdict();
77
77
  if (auth.error)
78
78
  console.error(chalk.yellow(`Auth verdict: ${auth.device}: ${auth.error}`));
@@ -85,7 +85,7 @@ async function consumeUserRepoAccountState() {
85
85
  console.log(chalk.gray(`Usage snapshot: merged ${usage.merged} row(s) from ${usage.sources.join(', ')}`));
86
86
  for (const err of usage.errors)
87
87
  console.error(chalk.yellow(`Usage snapshot: ${err.device}: ${err.message}`));
88
- const { syncReservedAuthBundle } = await import('../lib/secrets/reserved-sync.js');
88
+ const { syncReservedAuthBundle } = await import('../lib/secrets-policy.js');
89
89
  const auth = await syncReservedAuthBundle();
90
90
  if (auth.pushed.length > 0)
91
91
  console.log(chalk.gray(`Auth bundle: pushed to ${auth.pushed.join(', ')}`));
@@ -12,7 +12,7 @@ export interface RunAccountChoice {
12
12
  */
13
13
  signInRequired: boolean;
14
14
  }
15
- /** One named account row for `agents accounts switch` (reuses this picker's layout). */
15
+ /** One named account row for `agents accounts default` (reuses this picker's layout). */
16
16
  export interface SwitchAccountRow {
17
17
  accountName: string;
18
18
  kind: 'provider' | 'native';
@@ -25,8 +25,8 @@ export declare function formatAccountLimits(candidate: RotateCandidate): string;
25
25
  /** Build aligned picker rows with usable accounts first and unsafe rows disabled. */
26
26
  export declare function buildRunAccountChoices(candidates: RotateCandidate[], globalDefault: string | null): RunAccountChoice[];
27
27
  /**
28
- * Aligned picker rows for `accounts switch`. Same columns as the run picker
29
- * (identity, status, limits) but the value is the named account to set-default.
28
+ * Aligned picker rows for `accounts default`. Same columns as the run picker
29
+ * (identity, status, limits) but the value is the named account to make default.
30
30
  * Rows stay selectable: setting a default is not a launch.
31
31
  */
32
32
  export declare function buildSwitchAccountChoices(rows: SwitchAccountRow[]): RunAccountChoice[];
@@ -4,6 +4,7 @@ import { agentLabel } from '../lib/agents.js';
4
4
  import { loginHint } from '../lib/signin-badge.js';
5
5
  import { collectRunCandidates, readinessFromCandidate, isSignInRecoverable, } from '../lib/accounting/rotate.js';
6
6
  import { compareVersions, getGlobalDefault } from '../lib/installations/versions.js';
7
+ import { verdictLabel } from '../lib/auth-health.js';
7
8
  import { isInteractiveTerminal, isPromptCancelled, requireInteractiveSelection } from './utils.js';
8
9
  const CANCEL_SELECTION = '__agents_cancel_account_selection__';
9
10
  const WINDOW_ORDER = ['session', 'week', 'sonnet_week', 'month'];
@@ -65,28 +66,28 @@ export function buildRunAccountChoices(candidates, globalDefault) {
65
66
  const readiness = readinessFromCandidate(candidate);
66
67
  const disabled = disabledReason(candidate, readiness);
67
68
  const signInRequired = isSignInRecoverable(readiness);
68
- const version = candidate.version === globalDefault
69
- ? `${candidate.version} (default)`
70
- : candidate.version;
71
- // `revoked` is signed-in-but-rejected, so it reads as a re-login rather
72
- // than "logged out"; every throttle is still a signed-in account.
73
- const authReason = readiness.ready ? null : readiness.reason;
74
- const status = authReason === 'revoked'
69
+ const account = candidate.nativeAccount
70
+ || candidate.accountLabel
71
+ || 'account unavailable';
72
+ const marked = candidate.version === globalDefault && !candidate.nativeAccount
73
+ ? `${account} (default)`
74
+ : account;
75
+ const verdict = !readiness.ready && readiness.reason === 'revoked'
75
76
  ? 'needs re-login'
76
- : authReason === 'signed_out'
77
+ : !readiness.ready && readiness.reason === 'signed_out'
77
78
  ? 'logged out'
78
- : 'logged in';
79
+ : candidate.authVerdict
80
+ ? verdictLabel(candidate.authVerdict).toLowerCase()
81
+ : 'live';
79
82
  return {
80
83
  candidate,
81
- account: candidate.accountLabel || 'account unavailable',
82
- version,
83
- status,
84
- plan: candidate.usageSnapshot?.plan ?? candidate.plan ?? 'plan unavailable',
84
+ account: marked,
85
+ verdict,
85
86
  // An auth-blocked row shows what picking it DOES; its quota is moot until
86
87
  // there is a credential to spend it with.
87
- limits: authReason === 'revoked'
88
+ limits: !readiness.ready && readiness.reason === 'revoked'
88
89
  ? 'launch to re-authenticate'
89
- : authReason === 'signed_out'
90
+ : !readiness.ready && readiness.reason === 'signed_out'
90
91
  ? 'launch to sign in'
91
92
  : formatAccountLimits(candidate),
92
93
  disabled,
@@ -107,23 +108,22 @@ export function buildRunAccountChoices(candidates, globalDefault) {
107
108
  return compareVersions(b.candidate.version, a.candidate.version);
108
109
  });
109
110
  const accountWidth = Math.max(0, ...rows.map((row) => row.account.length));
110
- const versionWidth = Math.max(0, ...rows.map((row) => row.version.length));
111
- const statusWidth = Math.max(0, ...rows.map((row) => row.status.length));
112
- const planWidth = Math.max(0, ...rows.map((row) => row.plan.length));
111
+ const verdictWidth = Math.max(0, ...rows.map((row) => row.verdict.length));
113
112
  return rows.map((row) => ({
114
113
  name: [
115
114
  row.account.padEnd(accountWidth),
116
- row.version.padEnd(versionWidth),
117
- row.status.padEnd(statusWidth),
118
- row.plan.padEnd(planWidth),
115
+ row.verdict.padEnd(verdictWidth),
119
116
  row.limits,
120
117
  ].join(' '),
121
- value: row.candidate.version,
118
+ value: row.candidate.nativeAccount ?? row.candidate.version,
122
119
  disabled: row.disabled,
123
120
  ready: row.ready,
124
121
  signInRequired: row.signInRequired,
125
122
  }));
126
123
  }
124
+ function candidatePickerValue(candidate) {
125
+ return candidate.nativeAccount ?? candidate.version;
126
+ }
127
127
  function switchRowStatus(row) {
128
128
  if (!row.candidate) {
129
129
  return {
@@ -151,8 +151,8 @@ function switchRowStatus(row) {
151
151
  return { status, limits, ready: readiness.ready };
152
152
  }
153
153
  /**
154
- * Aligned picker rows for `accounts switch`. Same columns as the run picker
155
- * (identity, status, limits) but the value is the named account to set-default.
154
+ * Aligned picker rows for `accounts default`. Same columns as the run picker
155
+ * (identity, status, limits) but the value is the named account to make default.
156
156
  * Rows stay selectable: setting a default is not a launch.
157
157
  */
158
158
  export function buildSwitchAccountChoices(rows) {
@@ -184,12 +184,11 @@ export function buildSwitchAccountChoices(rows) {
184
184
  export async function pickSwitchAccount(agent, rows) {
185
185
  if (!isInteractiveTerminal()) {
186
186
  requireInteractiveSelection(`Selecting a ${agentLabel(agent)} account`, [
187
- `agents accounts switch ${agent} <account>`,
188
- `agents accounts set-default ${agent} <account>`,
187
+ `agents accounts default ${agent} <account>`,
189
188
  ]);
190
189
  }
191
190
  if (rows.length === 0) {
192
- throw new Error(`No named accounts for ${agent}. Add one with 'agents accounts add <name> --provider <p>' or 'agents accounts name ${agent}@<version> <name>'.`);
191
+ throw new Error(`No named accounts for ${agent}. Add one with 'agents accounts add ${agent} [name]'.`);
193
192
  }
194
193
  const choices = buildSwitchAccountChoices(rows).map(({ ready: _ready, signInRequired: _signInRequired, ...choice }) => choice);
195
194
  try {
@@ -292,16 +291,16 @@ export async function pickRunAccountCandidate(agent) {
292
291
  });
293
292
  }
294
293
  try {
295
- const version = await select({
294
+ const picked = await select({
296
295
  message: needsSignIn
297
296
  ? `Select a ${agentLabel(agent)} account for this run (pick a logged-out one to sign in):`
298
297
  : `Select a ${agentLabel(agent)} account for this run:`,
299
298
  choices: promptChoices,
300
299
  loop: false,
301
300
  });
302
- if (version === CANCEL_SELECTION)
301
+ if (picked === CANCEL_SELECTION)
303
302
  return null;
304
- return candidates.find((candidate) => candidate.version === version) ?? null;
303
+ return candidates.find((candidate) => candidatePickerValue(candidate) === picked) ?? null;
305
304
  }
306
305
  catch (err) {
307
306
  if (isPromptCancelled(err))
@@ -0,0 +1,20 @@
1
+ /**
2
+ * `agents secrets` — thin passthrough to the standalone `secrets` CLI
3
+ * (PHNX-3989, DIST-1). Every bundle/keychain/vault operation now lives in
4
+ * `@phnx-labs/secrets-cli`, not in this repo; this command re-declares no
5
+ * subcommand or flag of its own. `agents secrets <anything>` execs the
6
+ * installed executable with the same argv, `SECRETS_HOME` defaulted to this
7
+ * box's existing store (`buildServeEnv`, matching the process client's own
8
+ * default so an interactive `secrets list` and an injected `readBundle` see
9
+ * the same store), and `SECRETS_SCOPE`/`SECRETS_CONTEXT` passed through
10
+ * unmodified from the ambient environment — this is the operator's own
11
+ * terminal, not an injected agent exec, so there is no separate scope to
12
+ * compute here (that stays in `lib/exec.ts` for the run/inject path).
13
+ *
14
+ * A missing executable fails loud with install guidance — no fallback to the
15
+ * retired in-repo engine (`commands/secrets.ts` and its siblings), which stays
16
+ * in the tree, unregistered, until every consumer has moved off it (tasks.md
17
+ * item 7).
18
+ */
19
+ import type { Command } from 'commander';
20
+ export declare function registerSecretsCommands(program: Command): void;