@phnx-labs/agents-cli 1.22.83 → 1.22.85

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 (291) 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/apply.js +1 -1
  9. package/dist/commands/auth-mint.d.ts +7 -3
  10. package/dist/commands/auth-mint.js +21 -9
  11. package/dist/commands/auth.js +2 -3
  12. package/dist/commands/browser.js +10 -10
  13. package/dist/commands/daemon.d.ts +7 -3
  14. package/dist/commands/daemon.js +40 -25
  15. package/dist/commands/doctor.js +28 -2
  16. package/dist/commands/exec.js +87 -39
  17. package/dist/commands/fleet-capture.js +21 -2
  18. package/dist/commands/harness-wizard.js +2 -2
  19. package/dist/commands/lease.js +7 -12
  20. package/dist/commands/profiles.d.ts +2 -2
  21. package/dist/commands/profiles.js +20 -13
  22. package/dist/commands/repo.js +3 -3
  23. package/dist/commands/run-account-picker.d.ts +3 -3
  24. package/dist/commands/run-account-picker.js +29 -30
  25. package/dist/commands/secrets-passthrough.d.ts +20 -0
  26. package/dist/commands/secrets-passthrough.js +43 -0
  27. package/dist/commands/sessions-trace.d.ts +11 -0
  28. package/dist/commands/sessions-trace.js +71 -0
  29. package/dist/commands/setup-accounts.d.ts +3 -2
  30. package/dist/commands/setup-accounts.js +4 -4
  31. package/dist/commands/setup-secrets.d.ts +16 -21
  32. package/dist/commands/setup-secrets.js +43 -224
  33. package/dist/commands/ssh.js +2 -2
  34. package/dist/commands/sync.js +1 -1
  35. package/dist/commands/update.d.ts +10 -0
  36. package/dist/commands/update.js +43 -40
  37. package/dist/commands/versions.d.ts +11 -11
  38. package/dist/commands/versions.js +131 -83
  39. package/dist/commands/view.d.ts +1 -9
  40. package/dist/commands/view.js +41 -55
  41. package/dist/commands/webhook.js +5 -5
  42. package/dist/commands/workflows.js +2 -1
  43. package/dist/index.d.ts +12 -12
  44. package/dist/index.js +13 -42
  45. package/dist/lib/account-capabilities.d.ts +1 -1
  46. package/dist/lib/account-capabilities.js +1 -1
  47. package/dist/lib/account-catalog.d.ts +121 -6
  48. package/dist/lib/account-catalog.js +421 -7
  49. package/dist/lib/account-registry.d.ts +8 -30
  50. package/dist/lib/account-registry.js +36 -79
  51. package/dist/lib/account-schema.d.ts +1 -1
  52. package/dist/lib/account-schema.js +1 -1
  53. package/dist/lib/accounting/account-pool-collect.js +16 -4
  54. package/dist/lib/accounting/rotate.d.ts +20 -7
  55. package/dist/lib/accounting/rotate.js +89 -13
  56. package/dist/lib/accounting/usage.js +11 -16
  57. package/dist/lib/accounts/add.d.ts +138 -0
  58. package/dist/lib/accounts/add.js +651 -0
  59. package/dist/lib/accounts/migrate.d.ts +117 -0
  60. package/dist/lib/accounts/migrate.js +536 -0
  61. package/dist/lib/accounts/slots.d.ts +13 -0
  62. package/dist/lib/accounts/slots.js +100 -0
  63. package/dist/lib/agent-spec/agents.d.ts +16 -0
  64. package/dist/lib/agent-spec/agents.js +32 -4
  65. package/dist/lib/app-bundle-install.js +5 -4
  66. package/dist/lib/auth-health.d.ts +3 -0
  67. package/dist/lib/auth-health.js +11 -0
  68. package/dist/lib/auth-mint.d.ts +52 -16
  69. package/dist/lib/auth-mint.js +117 -39
  70. package/dist/lib/browser/chrome.d.ts +1 -1
  71. package/dist/lib/browser/chrome.js +5 -5
  72. package/dist/lib/byok-usage.js +3 -3
  73. package/dist/lib/claude-account-token.d.ts +47 -3
  74. package/dist/lib/claude-account-token.js +172 -50
  75. package/dist/lib/cloud/antigravity.js +4 -4
  76. package/dist/lib/cloud/cursor.js +4 -4
  77. package/dist/lib/crabbox/cli.d.ts +1 -1
  78. package/dist/lib/crabbox/cli.js +6 -6
  79. package/dist/lib/crabbox/runtimes.d.ts +3 -3
  80. package/dist/lib/crabbox/runtimes.js +5 -5
  81. package/dist/lib/daemon/account-state-daemon-service.d.ts +37 -1
  82. package/dist/lib/daemon/account-state-daemon-service.js +199 -3
  83. package/dist/lib/daemon/auth-sync-service.js +28 -1
  84. package/dist/lib/daemon/daemon.d.ts +6 -16
  85. package/dist/lib/daemon/daemon.js +52 -97
  86. package/dist/lib/daemon/daemon.test-fixture.d.ts +7 -9
  87. package/dist/lib/daemon/daemon.test-fixture.js +22 -40
  88. package/dist/lib/daemon/harness-update-service.d.ts +1 -1
  89. package/dist/lib/daemon/harness-update-service.js +1 -1
  90. package/dist/lib/daemon/runner.d.ts +15 -3
  91. package/dist/lib/daemon/runner.js +49 -24
  92. package/dist/lib/daemon-health.d.ts +1 -2
  93. package/dist/lib/daemon-health.js +7 -6
  94. package/dist/lib/daemon-services.d.ts +1 -1
  95. package/dist/lib/daemon-services.js +1 -11
  96. package/dist/lib/daemon-ticks.js +23 -2
  97. package/dist/lib/daemon-webhooks.d.ts +8 -7
  98. package/dist/lib/daemon-webhooks.js +10 -9
  99. package/dist/lib/device-config.js +1 -1
  100. package/dist/lib/devices/doctor-findings.d.ts +1 -1
  101. package/dist/lib/devices/harness-inventory.d.ts +39 -0
  102. package/dist/lib/devices/harness-inventory.js +126 -4
  103. package/dist/lib/doctor-diff.js +2 -1
  104. package/dist/lib/exec-account-home.d.ts +38 -0
  105. package/dist/lib/exec-account-home.js +164 -0
  106. package/dist/lib/exec.d.ts +30 -0
  107. package/dist/lib/exec.js +101 -31
  108. package/dist/lib/feed/activity-stream.d.ts +60 -0
  109. package/dist/lib/feed/activity-stream.js +271 -0
  110. package/dist/lib/feed/activity.d.ts +7 -0
  111. package/dist/lib/feed/activity.js +10 -4
  112. package/dist/lib/feed/watch.d.ts +9 -3
  113. package/dist/lib/feed/watch.js +72 -36
  114. package/dist/lib/fleet/apply.d.ts +2 -2
  115. package/dist/lib/fleet/apply.js +4 -4
  116. package/dist/lib/fleet/auth-sync.js +4 -3
  117. package/dist/lib/fleet-shared-repo-sync.js +6 -9
  118. package/dist/lib/fleet-shared-state.d.ts +6 -0
  119. package/dist/lib/harness/adapter.d.ts +7 -7
  120. package/dist/lib/harness/adapter.js +10 -6
  121. package/dist/lib/harness/adapters/grok.js +8 -3
  122. package/dist/lib/harness/adapters/muse.js +1 -1
  123. package/dist/lib/harness/adapters/opencode.js +12 -3
  124. package/dist/lib/harness-auth-capabilities.d.ts +61 -0
  125. package/dist/lib/harness-auth-capabilities.js +52 -0
  126. package/dist/lib/helper-versions.d.ts +6 -4
  127. package/dist/lib/helper-versions.js +5 -4
  128. package/dist/lib/hosts/credential-transport.d.ts +10 -0
  129. package/dist/lib/hosts/credential-transport.js +37 -0
  130. package/dist/lib/hosts/dispatch.d.ts +7 -0
  131. package/dist/lib/hosts/dispatch.js +13 -6
  132. package/dist/lib/identity/client.d.ts +3 -3
  133. package/dist/lib/identity/client.js +3 -3
  134. package/dist/lib/installations/index.d.ts +1 -1
  135. package/dist/lib/installations/index.js +1 -1
  136. package/dist/lib/installations/migrate.js +9 -0
  137. package/dist/lib/installations/resolve.js +1 -1
  138. package/dist/lib/installations/shims.d.ts +15 -0
  139. package/dist/lib/installations/shims.js +69 -0
  140. package/dist/lib/installations/store.d.ts +43 -0
  141. package/dist/lib/installations/store.js +75 -7
  142. package/dist/lib/installations/versions.js +1 -1
  143. package/dist/lib/menubar/install-menubar.d.ts +3 -3
  144. package/dist/lib/menubar/install-menubar.js +3 -3
  145. package/dist/lib/native-accounts.d.ts +35 -0
  146. package/dist/lib/native-accounts.js +41 -0
  147. package/dist/lib/net-close.d.ts +11 -0
  148. package/dist/lib/net-close.js +25 -0
  149. package/dist/lib/openclaw-keychain.js +27 -1
  150. package/dist/lib/profiles.js +22 -9
  151. package/dist/lib/project-resources.js +1 -1
  152. package/dist/lib/reserved-stores.d.ts +53 -0
  153. package/dist/lib/reserved-stores.js +120 -0
  154. package/dist/lib/secrets-client.d.ts +191 -0
  155. package/dist/lib/secrets-client.js +710 -0
  156. package/dist/lib/secrets-policy.d.ts +222 -0
  157. package/dist/lib/secrets-policy.js +483 -0
  158. package/dist/lib/secrets-types.d.ts +188 -0
  159. package/dist/lib/secrets-types.js +24 -0
  160. package/dist/lib/service-manifest.js +3 -4
  161. package/dist/lib/session/active.d.ts +38 -4
  162. package/dist/lib/session/active.js +36 -4
  163. package/dist/lib/session/bash-command.js +10 -0
  164. package/dist/lib/session/db.d.ts +65 -2
  165. package/dist/lib/session/db.js +178 -18
  166. package/dist/lib/session/mirror.d.ts +5 -0
  167. package/dist/lib/session/mirror.js +166 -0
  168. package/dist/lib/session/parse.d.ts +49 -0
  169. package/dist/lib/session/parse.js +324 -30
  170. package/dist/lib/session/prompt.d.ts +93 -2
  171. package/dist/lib/session/prompt.js +266 -15
  172. package/dist/lib/session/remote/peer-stream.d.ts +47 -0
  173. package/dist/lib/session/remote/peer-stream.js +142 -0
  174. package/dist/lib/session/remote/watch.d.ts +1 -1
  175. package/dist/lib/session/remote/watch.js +41 -42
  176. package/dist/lib/session/session-cache.d.ts +9 -0
  177. package/dist/lib/session/session-cache.js +40 -2
  178. package/dist/lib/session/sync/config.js +2 -2
  179. package/dist/lib/session/timeline-pass.d.ts +129 -0
  180. package/dist/lib/session/timeline-pass.js +323 -0
  181. package/dist/lib/session/timeline.d.ts +182 -0
  182. package/dist/lib/session/timeline.js +636 -0
  183. package/dist/lib/session/types.d.ts +155 -1
  184. package/dist/lib/sha256-asset.d.ts +11 -16
  185. package/dist/lib/sha256-asset.js +11 -16
  186. package/dist/lib/share/config.js +14 -5
  187. package/dist/lib/signin-badge.d.ts +26 -0
  188. package/dist/lib/signin-badge.js +42 -0
  189. package/dist/lib/staleness/detectors/workflows.js +3 -105
  190. package/dist/lib/staleness/writers/workflows.js +2 -1
  191. package/dist/lib/state.d.ts +7 -4
  192. package/dist/lib/state.js +18 -7
  193. package/dist/lib/summarizer/pass.d.ts +2 -0
  194. package/dist/lib/summarizer/pass.js +14 -1
  195. package/dist/lib/summarizer/summarize.d.ts +7 -0
  196. package/dist/lib/summarizer/summarize.js +3 -0
  197. package/dist/lib/sync-umbrella.js +2 -2
  198. package/dist/lib/teams/agents.js +1 -1
  199. package/dist/lib/types.d.ts +74 -33
  200. package/dist/lib/view-types.d.ts +6 -3
  201. package/dist/lib/workflows-registry.d.ts +71 -0
  202. package/dist/lib/workflows-registry.js +280 -0
  203. package/dist/lib/workflows.d.ts +21 -21
  204. package/dist/lib/workflows.js +12 -327
  205. package/package.json +2 -3
  206. package/scripts/postinstall.js +4 -29
  207. package/dist/commands/secrets-import.d.ts +0 -18
  208. package/dist/commands/secrets-import.js +0 -74
  209. package/dist/commands/secrets-migrate.d.ts +0 -25
  210. package/dist/commands/secrets-migrate.js +0 -334
  211. package/dist/commands/secrets-rotate-passphrase.d.ts +0 -17
  212. package/dist/commands/secrets-rotate-passphrase.js +0 -96
  213. package/dist/commands/secrets-sync.d.ts +0 -11
  214. package/dist/commands/secrets-sync.js +0 -153
  215. package/dist/commands/secrets-vault.d.ts +0 -10
  216. package/dist/commands/secrets-vault.js +0 -130
  217. package/dist/commands/secrets.d.ts +0 -212
  218. package/dist/commands/secrets.js +0 -3030
  219. package/dist/lib/accounts/connect.d.ts +0 -170
  220. package/dist/lib/accounts/connect.js +0 -383
  221. package/dist/lib/daemon/keychain-reap-service.d.ts +0 -17
  222. package/dist/lib/daemon/keychain-reap-service.js +0 -32
  223. package/dist/lib/daemon/secrets-broker-service.d.ts +0 -21
  224. package/dist/lib/daemon/secrets-broker-service.js +0 -51
  225. package/dist/lib/secrets/agent.d.ts +0 -358
  226. package/dist/lib/secrets/agent.js +0 -1291
  227. package/dist/lib/secrets/audit.d.ts +0 -46
  228. package/dist/lib/secrets/audit.js +0 -101
  229. package/dist/lib/secrets/bundles.d.ts +0 -290
  230. package/dist/lib/secrets/bundles.js +0 -1547
  231. package/dist/lib/secrets/download-keychain.d.ts +0 -47
  232. package/dist/lib/secrets/download-keychain.js +0 -70
  233. package/dist/lib/secrets/drivers/rush.d.ts +0 -14
  234. package/dist/lib/secrets/drivers/rush.js +0 -90
  235. package/dist/lib/secrets/fallback.d.ts +0 -48
  236. package/dist/lib/secrets/fallback.js +0 -48
  237. package/dist/lib/secrets/filestore.d.ts +0 -222
  238. package/dist/lib/secrets/filestore.js +0 -1099
  239. package/dist/lib/secrets/headless.d.ts +0 -43
  240. package/dist/lib/secrets/headless.js +0 -56
  241. package/dist/lib/secrets/icloud-import.d.ts +0 -79
  242. package/dist/lib/secrets/icloud-import.js +0 -206
  243. package/dist/lib/secrets/index.d.ts +0 -451
  244. package/dist/lib/secrets/index.js +0 -1568
  245. package/dist/lib/secrets/install-helper.d.ts +0 -72
  246. package/dist/lib/secrets/install-helper.js +0 -245
  247. package/dist/lib/secrets/lease.d.ts +0 -25
  248. package/dist/lib/secrets/lease.js +0 -44
  249. package/dist/lib/secrets/linux.d.ts +0 -77
  250. package/dist/lib/secrets/linux.js +0 -393
  251. package/dist/lib/secrets/list-filter.d.ts +0 -109
  252. package/dist/lib/secrets/list-filter.js +0 -261
  253. package/dist/lib/secrets/mcp.d.ts +0 -93
  254. package/dist/lib/secrets/mcp.js +0 -211
  255. package/dist/lib/secrets/profiles.d.ts +0 -10
  256. package/dist/lib/secrets/profiles.js +0 -13
  257. package/dist/lib/secrets/push.d.ts +0 -133
  258. package/dist/lib/secrets/push.js +0 -273
  259. package/dist/lib/secrets/rc-hygiene.d.ts +0 -63
  260. package/dist/lib/secrets/rc-hygiene.js +0 -143
  261. package/dist/lib/secrets/read-backoff.d.ts +0 -27
  262. package/dist/lib/secrets/read-backoff.js +0 -64
  263. package/dist/lib/secrets/reaper.d.ts +0 -90
  264. package/dist/lib/secrets/reaper.js +0 -243
  265. package/dist/lib/secrets/remote.d.ts +0 -152
  266. package/dist/lib/secrets/remote.js +0 -344
  267. package/dist/lib/secrets/reserved-sync.d.ts +0 -66
  268. package/dist/lib/secrets/reserved-sync.js +0 -147
  269. package/dist/lib/secrets/scope.d.ts +0 -26
  270. package/dist/lib/secrets/scope.js +0 -29
  271. package/dist/lib/secrets/session-store.d.ts +0 -107
  272. package/dist/lib/secrets/session-store.js +0 -342
  273. package/dist/lib/secrets/sync-backend.d.ts +0 -48
  274. package/dist/lib/secrets/sync-backend.js +0 -13
  275. package/dist/lib/secrets/sync-commands.d.ts +0 -21
  276. package/dist/lib/secrets/sync-commands.js +0 -21
  277. package/dist/lib/secrets/sync.d.ts +0 -48
  278. package/dist/lib/secrets/sync.js +0 -237
  279. package/dist/lib/secrets/unlock-hints.d.ts +0 -27
  280. package/dist/lib/secrets/unlock-hints.js +0 -36
  281. package/dist/lib/secrets/usage-db.d.ts +0 -46
  282. package/dist/lib/secrets/usage-db.js +0 -96
  283. package/dist/lib/secrets/vault-age-helper.d.ts +0 -1
  284. package/dist/lib/secrets/vault-age-helper.js +0 -34
  285. package/dist/lib/secrets/vault.d.ts +0 -49
  286. package/dist/lib/secrets/vault.js +0 -399
  287. package/dist/lib/secrets/windows.d.ts +0 -81
  288. package/dist/lib/secrets/windows.js +0 -556
  289. package/scripts/install-helper.js +0 -97
  290. /package/dist/lib/{secrets/sync-passphrase.d.ts → sync-passphrase.d.ts} +0 -0
  291. /package/dist/lib/{secrets/sync-passphrase.js → sync-passphrase.js} +0 -0
@@ -5,6 +5,7 @@ import { password, select } from '@inquirer/prompts';
5
5
  import { readClaudeAccountEmail, resolveClaudeSetupToken, resolveClaudeSetupTokenForEmail, seedClaudeWorkerHomeIdentity } from '../lib/claude-account-token.js';
6
6
  import { setHelpSections } from '../lib/help.js';
7
7
  import { readMeta, updateMeta } from '../lib/state.js';
8
+ import { machineId } from '../lib/machine-id.js';
8
9
  import { ALL_AGENT_IDS, getAccountInfo, resolveAgentName } from '../lib/agents.js';
9
10
  import { getGlobalDefault, getVersionHomePath, listInstalledVersions } from '../lib/installations/versions.js';
10
11
  import { assertNativeAccountNameable, nativeAccountCapability, nativeIdentityKey } from '../lib/account-capabilities.js';
@@ -12,20 +13,56 @@ import { collectRunCandidates } from '../lib/accounting/rotate.js';
12
13
  import { isInteractiveTerminal, isPromptCancelled, requireInteractiveSelection } from './utils.js';
13
14
  import { buildSwitchAccountChoices, formatAccountLimits, pickSwitchAccount } from './run-account-picker.js';
14
15
  import { profileExists, readProfile } from '../lib/profiles.js';
15
- import { pushBundleToHost } from '../lib/secrets/push.js';
16
- import { assertCredentialTransportHostPinned, resolveHostSshTarget } from '../lib/secrets/remote.js';
16
+ import { assertCredentialTransportHostPinned, resolveHostSshTarget } from '../lib/hosts/credential-transport.js';
17
17
  import { resolveRemoteOsSync } from '../lib/hosts/remote-os.js';
18
- import { runDevicesAccounts } from './ssh.js';
19
- import { collectNativeHomeRows, loadAccountCatalog } from '../lib/account-catalog.js';
20
- import { connectRefusal, connectSupported, runConnect } from '../lib/accounts/connect.js';
18
+ import { renderAccountFleetMatrix } from '../lib/devices/harness-inventory.js';
19
+ import { accountListJson, collectNativeHomeRows, listDevicesWithoutAccountVerdicts, loadAccountCatalog, renderAccountRows, secretsUnavailableNote, } from '../lib/account-catalog.js';
20
+ import { addRefusal, runAdd, runLogin, supportedAddHarnesses } from '../lib/accounts/add.js';
21
+ import { applyAccountMigration, formatMigrationPlan, planAccountMigration } from '../lib/accounts/migrate.js';
22
+ import { isSymlinkAdoptedHarness } from '../lib/installations/shims.js';
23
+ import { ensureAdoptedDefaultRepoint } from '../lib/exec-account-home.js';
21
24
  import { acquireAuthOperationLock } from '../lib/accounts/auth-operation-lock.js';
22
- import { readAndResolveBundleEnv } from '../lib/secrets/bundles.js';
25
+ import { isSecretsClientError, pushBundleToHost, readAndResolveBundleEnv, readBundle } from '../lib/secrets-client.js';
23
26
  import { getAccountProvider, listAccountProviders, providerAuthenticatesHarness } from '../lib/account-provider-registry.js';
24
27
  import { accountBindings, addAccount, addNativeAccount, assertUnambiguousNativeAccount, bindAccount, findAccount, findUnifiedAccount, inspectAccount, labelNativeAccount, listNativeAccounts, nativeAccountHome, parseAccountSelector, readAccountRegistry, removeAccount, renameAccount, setAccountSecret, unbindAccount } from '../lib/account-registry.js';
25
28
  import { registerMintCommand } from './auth-mint.js';
26
- /** Comma-joined list of harnesses `accounts connect` can drive today. */
27
- function connectSupportedList() {
28
- return ALL_AGENT_IDS.filter(connectSupported).join(', ');
29
+ /** Comma-joined list of harnesses `accounts add` can drive today. */
30
+ function addSupportedList() {
31
+ return supportedAddHarnesses().join(', ');
32
+ }
33
+ /**
34
+ * Legacy-verb retirement: the command is created with `{ hidden: true }` (help
35
+ * no longer lists it) and this hook prints the pointer to its replacement when
36
+ * it runs. Verbs keep executing for one release.
37
+ */
38
+ function retireVerb(cmd, pointer) {
39
+ cmd.hook('preAction', () => {
40
+ console.error(chalk.gray(`'agents accounts ${cmd.name()}' is replaced by '${pointer}' — still works this release.`));
41
+ });
42
+ }
43
+ /** Shared result printer for `accounts add` / `accounts login` / hidden `connect`. */
44
+ function printAddResult(result, json) {
45
+ if (json) {
46
+ console.log(JSON.stringify(result, null, 2));
47
+ return;
48
+ }
49
+ const who = result.email ?? result.identityKey;
50
+ const verb = result.mode === 'reconnect' ? 'Re-authenticated' : 'Added';
51
+ console.log(chalk.green(`${verb} ${result.agent}#${result.name} (${who}).`));
52
+ const credentialLine = {
53
+ minted: 'worker credential minted for workers',
54
+ stored: 'worker credential stored for workers',
55
+ kept: 'worker credential unchanged',
56
+ skipped: 'no worker credential — runs on this device only',
57
+ 'per-device': 'per-device login — each box signs in for itself',
58
+ };
59
+ console.log(chalk.gray(` ${credentialLine[result.workerCredential]} · provisioning: ${result.provisioning}`));
60
+ if (result.becameDefault)
61
+ console.log(chalk.gray(` Default account for ${result.agent} (first one added).`));
62
+ for (const warning of result.warnings)
63
+ console.log(chalk.yellow(` warning: ${warning}`));
64
+ console.log(chalk.gray(` The native OAuth login stays owned by the harness in its slot; nothing was copied.`));
65
+ console.log(chalk.gray(` Run: agents run ${result.agent}#${result.name}`));
29
66
  }
30
67
  function cleanCommandError(command, err) {
31
68
  command.error(err instanceof Error ? err.message : String(err), { exitCode: 1, code: 'accounts.error' });
@@ -126,9 +163,38 @@ export function parseBundleKey(raw) {
126
163
  throw new Error(`Expected bundle:key, got '${raw}'.`);
127
164
  return { bundle: raw.slice(0, colon), key: raw.slice(colon + 1) };
128
165
  }
129
- function secretFromBundle(raw) {
166
+ /**
167
+ * Copy one value out of an `agents secrets` bundle for `--from-secrets`. The
168
+ * standalone reports failures only as codes, so the bundle, the key, and the
169
+ * repair are named here — and existence is checked first, because a
170
+ * prompt-free (`agentOnly`) read of a bundle that does not exist reports
171
+ * LOCKED rather than NOT_FOUND.
172
+ */
173
+ async function secretFromBundle(raw) {
130
174
  const { bundle, key } = parseBundleKey(raw);
131
- return readAndResolveBundleEnv(bundle, { keys: [key], keyMode: 'storage', agentOnly: true, caller: 'accounts import' }).env[key];
175
+ let vars;
176
+ try {
177
+ vars = (await readBundle(bundle)).vars;
178
+ }
179
+ catch (err) {
180
+ if (!isSecretsClientError(err, 'NOT_FOUND'))
181
+ throw err;
182
+ throw new Error(`Secrets bundle '${bundle}' not found. List bundles with 'agents secrets list'.`);
183
+ }
184
+ if (!(key in vars)) {
185
+ const available = Object.keys(vars).join(', ') || '(none)';
186
+ throw new Error(`Secrets bundle '${bundle}' has no key '${key}'. Available: ${available}.`);
187
+ }
188
+ try {
189
+ const { env } = await readAndResolveBundleEnv(bundle, { keys: [key], keyMode: 'storage', agentOnly: true, caller: 'accounts import' });
190
+ return env[key];
191
+ }
192
+ catch (err) {
193
+ if (!isSecretsClientError(err, 'LOCKED'))
194
+ throw err;
195
+ throw new Error(`Secrets bundle '${bundle}' is not unlocked for a prompt-free read. ` +
196
+ `Unlock it first with 'agents secrets unlock ${bundle}', then retry.`);
197
+ }
132
198
  }
133
199
  /** Interactive secret entry for add/set-key. Ctrl+C must not become accounts.error. */
134
200
  async function promptAccountSecret(message) {
@@ -144,73 +210,37 @@ async function promptAccountSecret(message) {
144
210
  function publicAccount(account) {
145
211
  return { kind: 'provider', id: account.id, name: account.name, provider: account.provider, auth: account.auth, baseUrl: account.baseUrl, policy: account.policy, secretPresent: account.secretPresent };
146
212
  }
147
- async function printAccounts(json, fleet = false) {
148
- if (fleet)
149
- return runDevicesAccounts({ json });
150
- const records = Object.values(readAccountRegistry().accounts).sort((a, b) => a.name.localeCompare(b.name));
213
+ async function printAccounts(json, fleet = false, harnessRaw) {
214
+ const harness = harnessRaw ? parseHarness(harnessRaw) : undefined;
151
215
  const catalog = await loadAccountCatalog();
152
- const native = catalog.native.map(row => ({
153
- ...row, name: row.name ?? undefined, id: row.id ?? row.identityKey,
154
- versions: row.installations.map(home => home.label),
155
- }));
216
+ const note = secretsUnavailableNote(catalog);
217
+ if (note)
218
+ console.error(chalk.yellow(note)); // stderr — never corrupts --json stdout
219
+ const native = harness ? catalog.native.filter((row) => row.agent === harness) : catalog.native;
220
+ const providers = harness
221
+ ? catalog.provider.filter((row) => row.harnesses.includes(harness))
222
+ : catalog.provider;
156
223
  if (json) {
157
- console.log(JSON.stringify([...records.map(account => publicAccount(inspectAccount(account.name))), ...native], null, 2));
224
+ console.log(JSON.stringify(accountListJson(native, providers, harness), null, 2));
158
225
  return;
159
226
  }
160
- console.log(renderAccountList(records, native));
161
- }
162
- /** Pure text renderer for `agents accounts list` — grouped, column-aligned, labels first-class. */
163
- export function renderAccountList(records, native) {
164
- const out = [];
165
- // Native logins, grouped by harness — the selection surface for `<harness>#<label>`.
166
- out.push(chalk.bold('Native logins') + chalk.gray(' ') + chalk.gray('run <harness>#<label>'));
167
- if (!native.length) {
168
- out.push(chalk.gray(' No native accounts found. Connect one: agents accounts connect <harness> [name]'));
169
- }
170
- else {
171
- const byHarness = new Map();
172
- for (const acct of native) {
173
- const list = byHarness.get(acct.agent) ?? [];
174
- list.push(acct);
175
- byHarness.set(acct.agent, list);
176
- }
177
- const harnessW = Math.max(6, ...[...byHarness.keys()].map(h => h.length));
178
- const labelW = Math.max(6, ...native.map(a => (a.name ?? '—').length));
179
- const idW = Math.max(8, ...native.map(a => a.display.length));
180
- let sawDefault = false;
181
- for (const [harness, list] of [...byHarness.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
182
- const defaultVersion = getGlobalDefault(harness);
183
- list.forEach((acct, i) => {
184
- const harnessCell = chalk.gray((i === 0 ? harness : '').padEnd(harnessW));
185
- const rawLabel = acct.name ?? '—';
186
- const labelCell = acct.name ? chalk.cyan(rawLabel.padEnd(labelW)) : chalk.gray(rawLabel.padEnd(labelW));
187
- const isDefault = acct.isDefault ?? (defaultVersion ? acct.versions.includes(defaultVersion) : false);
188
- if (isDefault)
189
- sawDefault = true;
190
- const marker = isDefault ? chalk.green('*') : ' ';
191
- const state = acct.state === 'reconnect-needed' ? 'not connected here' : 'connected';
192
- out.push(` ${harnessCell} ${labelCell}${marker} ${acct.display.padEnd(idW)} ${chalk.gray(state)}`);
193
- });
194
- }
195
- if (sawDefault)
196
- out.push(chalk.gray('\n * default account for that harness (used when you pass neither @version nor #label)'));
197
- }
198
- // Provider bundles — the durable API-key/setup-token accounts, selected with `--account <name>`.
199
- out.push('');
200
- out.push(chalk.bold('Provider bundles') + chalk.gray(' run <harness> --account <name>'));
201
- if (!records.length) {
202
- out.push(chalk.gray(" None. Add one with 'agents accounts add <name> --provider <provider> --auth <type>'."));
203
- }
204
- else {
205
- const nameW = Math.max(4, ...records.map(a => a.name.length));
206
- const provW = Math.max(8, ...records.map(a => a.provider.length));
207
- const authW = Math.max(10, ...records.map(a => a.auth.length));
208
- for (const account of records) {
209
- const present = inspectAccount(account.name).secretPresent ? chalk.green('ready') : chalk.red('missing on this device');
210
- out.push(` ${chalk.cyan(account.name.padEnd(nameW))} ${account.provider.padEnd(provW)} ${account.auth.padEnd(authW)} ${present}`);
211
- }
227
+ if (fleet) {
228
+ const matrixRows = native.map((row) => ({
229
+ harness: row.agent,
230
+ name: row.name,
231
+ identityLabel: row.identityLabel,
232
+ devices: row.devices,
233
+ }));
234
+ const covered = new Set(matrixRows.flatMap((row) => row.devices.map((device) => device.device)));
235
+ const uncoveredDevices = listDevicesWithoutAccountVerdicts().filter((device) => !covered.has(device));
236
+ console.log(renderAccountFleetMatrix(matrixRows, { uncoveredDevices }).join('\n'));
237
+ return;
212
238
  }
213
- return out.join('\n');
239
+ console.log(renderAccountRows(native, { providers, harness, localDevice: machineId() }));
240
+ }
241
+ /** Compatibility export for tests/consumers; the canonical renderer is shared with view. */
242
+ export function renderAccountList(native, providers = [], opts = {}) {
243
+ return renderAccountRows(native, { providers, localDevice: '', ...opts });
214
244
  }
215
245
  function parseAuth(raw) {
216
246
  if (raw === 'api-key' || raw === 'setup-token' || raw === 'bearer-token')
@@ -355,12 +385,19 @@ export function setDefaultAccount(agentRaw, name) {
355
385
  if (account.agent !== agent) {
356
386
  throw new Error(`Account '${account.name}' is a ${account.agent} login and cannot be the default for ${agent}.`);
357
387
  }
358
- assertNativeAccountNameable(account.agent);
388
+ // Adopted harnesses isolate by slot + symlink, not a version home, so the
389
+ // nameable-by-version-home gate does not apply to `accounts default`.
390
+ if (!isSymlinkAdoptedHarness(agent))
391
+ assertNativeAccountNameable(account.agent);
359
392
  }
360
393
  // Reference by NAME, not id: defaults sync fleet-wide with `agents repo push/pull`
361
394
  // while account ids are minted per-device, so an id ref breaks on every other
362
395
  // machine ("Unknown account '<uuid>'"). Names are the portable handle — the
363
396
  // registry resolves both, and existing uuid entries still resolve.
397
+ // Repoint first: a written default whose symlink still points at another
398
+ // account is a wrong home that looks like success.
399
+ if (account.kind === 'native')
400
+ ensureAdoptedDefaultRepoint(agent, account, readMeta());
364
401
  updateMeta(meta => ({ ...meta, accounts: { ...meta.accounts, defaults: { ...meta.accounts?.defaults, [agent]: account.name } } }));
365
402
  return { agent, account };
366
403
  }
@@ -410,7 +447,7 @@ export async function runAccountsSwitch(harness, accountName, opts) {
410
447
  return;
411
448
  }
412
449
  if (!isInteractiveTerminal()) {
413
- throw new Error(`Selecting a ${agent} account requires an interactive terminal.\nUse: agents accounts switch ${agent} <account>\nOr: agents accounts set-default ${agent} <account>`);
450
+ throw new Error(`Selecting a ${agent} account requires an interactive terminal.\nUse: agents accounts default ${agent} <account>`);
414
451
  }
415
452
  const selected = await pickSwitchAccount(agent, rows);
416
453
  if (!selected)
@@ -498,69 +535,221 @@ export async function resolveLogoutTarget(target) {
498
535
  throw new Error(`Unknown target '${target}'. Pass a native harness (claude, codex, …), <harness>@<label>, <harness>#<account>, or a native account name. Provider API-key accounts use \`agents accounts remove\`.`);
499
536
  }
500
537
  export function registerAccountsCommand(program) {
501
- const accounts = program.command('accounts').description('Browse native logins and manage provider account bundles')
538
+ const accounts = program.command('accounts').description('Browse and manage harness accounts')
502
539
  .option('--json', 'Machine-readable account metadata')
503
- .option('--fleet', 'Show harness-native signed-in identities across reachable devices')
540
+ .option('--fleet', 'Show accounts as rows and devices as columns')
504
541
  .action(async (o, command) => {
505
542
  await runAccountsAction(command, () => printAccounts(!!o.json, !!o.fleet));
506
543
  });
507
- accounts.command('list').description('List credential accounts').option('--json', 'Machine-readable account metadata').action(async (o, command) => {
508
- await runAccountsAction(command, () => printAccounts(!!(o.json || command.optsWithGlobals().json)));
509
- });
510
- registerMintCommand(accounts);
511
- const connectCmd = accounts.command('connect <harness> [name]')
512
- .description('Connect a stable account: install the current release into a fresh isolated home and drive the harness native login (reconnect an existing account by name to reuse its home)')
513
- .option('--json', 'Machine-readable connect result')
514
- .action(async (harness, name, o, command) => {
515
- await runAccountsAction(command, async () => {
516
- const agent = parseHarness(harness);
517
- const reason = connectRefusal(agent);
518
- if (reason)
519
- throw new Error(reason);
520
- const result = await runConnect(agent, name, {
521
- meta: readMeta(),
522
- onProgress: (m) => console.log(chalk.gray(` ${m}`)),
523
- });
524
- if (o.json || command.optsWithGlobals().json)
525
- return console.log(JSON.stringify(result, null, 2));
526
- const who = result.email ?? result.identityKey;
527
- const named = result.name ? `'${result.name}' ` : '';
528
- const release = result.releaseVersion ? ` [${result.releaseVersion}]` : '';
529
- console.log(chalk.green(`${result.mode === 'reconnect' ? 'Reconnected' : 'Connected'} ${agent} account ${named}(${who}) in ${agent}@${result.label}${release}.`));
530
- console.log(chalk.gray(' The native OAuth login stays owned by the harness in that home; nothing was copied.'));
531
- });
544
+ const listCmd = accounts.command('list [harness]')
545
+ .description('List accounts with authentication verdict, device coverage, usage, and exact repair command')
546
+ .option('--json', 'Machine-readable account metadata')
547
+ .option('--fleet', 'Show accounts as rows and devices as columns')
548
+ .action(async (harness, o, command) => {
549
+ const globals = command.optsWithGlobals();
550
+ await runAccountsAction(command, () => printAccounts(!!(o.json || globals.json), !!(o.fleet || globals.fleet), harness));
532
551
  });
533
- setHelpSections(connectCmd, {
534
- examples: `agents accounts connect claude work
535
- agents accounts connect codex personal
536
- agents accounts connect claude work # again → reuses work's home, fails closed on a different identity
537
- agents run claude --account work`,
538
- notes: `Each NEW account gets a fresh opaque installation label and its own isolated home, so ten accounts can share the same upstream release while keeping separate logins. Connect installs the current release even when it is already installed under another label, then launches the harness's native login there — the OAuth credential is never copied or fleet-synced. Reconnecting an existing account by name reuses its home and refuses to overwrite a home now signed in as a different identity. Supported for version-scoped, isolable harnesses (${connectSupportedList()}); others fail with a clear reason. Provider API-key/token accounts use 'agents accounts add' instead.`,
552
+ setHelpSections(listCmd, {
553
+ examples: `agents accounts list
554
+ agents accounts list claude
555
+ agents accounts list --json
556
+ agents accounts list --fleet`,
557
+ notes: 'One row per account per harness. STATE is the daemon verdict. WHERE is `this box` when only this device reports (peers on an older release publish no slot verdicts), else live-device coverage. USAGE is the bar + percent. FIX is the exact repair command — empty when there is none. Reserved credential stores are not listed here; `agents secrets` is the place those show.',
539
558
  });
540
- accounts.command('add <name>')
541
- .description('Add a durable API key, setup token, or bearer token')
542
- .requiredOption('--provider <provider>', `Credential provider (${listAccountProviders().join(', ')})`)
543
- .requiredOption('--auth <type>', 'Credential type: api-key | setup-token | bearer-token')
544
- .option('--base-url <url>', 'Optional endpoint override stored with the account')
545
- .option('--from-secrets <bundle:key>', 'Import from an existing agents secrets entry')
546
- .action(async (name, o, command) => {
559
+ registerMintCommand(accounts, undefined, { hidden: true });
560
+ const addCmd = accounts.command('add <target> [name]')
561
+ .description('Add an account. Harness form: add <harness> [name] runs the native login in a fresh credential slot and provisions workers. Provider form: add <name> --provider <p> --auth <t> stores a durable credential.')
562
+ .option('--api-key <key>', 'Worker credential for api-key harnesses (codex/grok/cursor/opencode); prompted when omitted')
563
+ .option('--per-device', 'codex: ChatGPT-plan account — no API key stored; workers log in per box')
564
+ .option('--no-worker-token', 'Skip minting/collecting the worker credential (this device only)')
565
+ .option('--provider <provider>', `Provider form: credential provider (${listAccountProviders().join(', ')})`)
566
+ .option('--auth <type>', 'Provider form: api-key | setup-token | bearer-token')
567
+ .option('--base-url <url>', 'Provider form: optional endpoint override stored with the account')
568
+ .option('--from-secrets <bundle:key>', 'Provider form: import from an existing agents secrets entry')
569
+ .option('--json', 'Machine-readable result (never includes a credential)')
570
+ .action(async (target, name, o, command) => {
547
571
  await runAccountsAction(command, async () => {
572
+ const json = !!(o.json || command.optsWithGlobals().json);
573
+ const agent = resolveAgentName(target);
574
+ const providerFlags = o.provider !== undefined || o.auth !== undefined || o.baseUrl !== undefined || o.fromSecrets !== undefined;
575
+ if (agent) {
576
+ // Harness form: an account is a credential slot, not a bundle.
577
+ if (providerFlags) {
578
+ throw new Error(`'${target}' is a harness id — mixing the harness and provider forms is ambiguous. `
579
+ + `Harness form: agents accounts add ${agent} [name]. `
580
+ + `Provider form: agents accounts add <non-harness-name> --provider <p> --auth <t>.`);
581
+ }
582
+ const reason = addRefusal(agent);
583
+ if (reason)
584
+ throw new Error(reason);
585
+ const result = await runAdd(agent, name, {
586
+ meta: readMeta(),
587
+ onProgress: (m) => { if (!json)
588
+ console.log(chalk.gray(` ${m}`)); },
589
+ apiKey: o.apiKey,
590
+ noWorkerToken: o.workerToken === false,
591
+ perDevice: o.perDevice,
592
+ });
593
+ printAddResult(result, json);
594
+ return;
595
+ }
596
+ // Provider form (the pre-v2 `accounts add <name> --provider …`).
597
+ if (o.apiKey !== undefined || o.perDevice || o.workerToken === false) {
598
+ throw new Error(`'${target}' is not a harness id — --api-key/--per-device/--no-worker-token belong to the harness form: agents accounts add <harness> [name].`);
599
+ }
600
+ if (!o.provider || !o.auth) {
601
+ throw new Error(`'${target}' is not a harness id. Store a provider credential with: agents accounts add ${target} --provider <provider> --auth <type>. `
602
+ + `Add a harness login with: agents accounts add <harness> [name].`);
603
+ }
548
604
  const auth = parseAuth(o.auth);
549
605
  const provider = getAccountProvider(o.provider);
550
606
  if (!provider.authKinds.includes(auth))
551
607
  throw new Error(`Provider '${provider.provider}' does not support ${auth}. Supported: ${provider.authKinds.join(', ')}.`);
552
608
  const secret = o.fromSecrets
553
- ? secretFromBundle(o.fromSecrets)
554
- : await promptAccountSecret(`Enter ${provider.provider} ${auth} for '${name}':`);
609
+ ? await secretFromBundle(o.fromSecrets)
610
+ : await promptAccountSecret(`Enter ${provider.provider} ${auth} for '${target}':`);
555
611
  if (secret === null) {
556
612
  process.exit(130);
557
613
  return;
558
614
  }
559
- const account = addAccount(name, provider.provider, auth, secret, undefined, { baseUrl: o.baseUrl });
615
+ const account = addAccount(target, provider.provider, auth, secret, undefined, { baseUrl: o.baseUrl });
560
616
  console.log(chalk.green(`Added ${account.provider} ${account.auth} account '${account.name}'.`));
561
617
  console.log(chalk.gray(`Secret bundle '${account.name}' is the account and uses policy never, so agent launches never request Touch ID.`));
562
618
  });
563
619
  });
620
+ setHelpSections(addCmd, {
621
+ examples: `agents accounts add claude work
622
+ agents accounts add codex personal --api-key sk-… # or omit --api-key to be prompted
623
+ agents accounts add codex gmail --per-device # ChatGPT-plan seat; workers log in per box
624
+ agents accounts add claude # name derived from the login's email
625
+ agents accounts add work --provider anthropic --auth setup-token # provider form (non-harness name)
626
+ agents run claude#work`,
627
+ notes: `Headed devices only: on a worker, add refuses before any slot, install, or browser — workers are provisioned automatically from the durable credential the add mints (claude: a setup-token; codex/grok/cursor/opencode: an API key via --api-key or a prompt; codex also accepts --per-device for a ChatGPT-plan seat; kimi/antigravity log in per box). An account is a credential SLOT, not a second installation: the harness has one managed install and each account is a HOME-shaped slot with its own native OAuth login (never copied, never fleet-synced). Re-running add for an already-added identity points at 'agents accounts login <harness>#<name>'. Supported: ${addSupportedList()}; other harnesses fail with the reason. An ambient CLAUDE_CODE_OAUTH_TOKEN in your shell refuses the mint (unset it or pass --no-worker-token).`,
628
+ });
629
+ const loginCmd = accounts.command('login <account>')
630
+ .description('Re-authenticate an account into its slot on this device (<harness>#<name>); re-mints and re-syncs the worker credential')
631
+ .option('--api-key <key>', 'Rotate the stored worker API key (api-key harnesses)')
632
+ .option('--no-worker-token', 'Skip re-minting the worker credential')
633
+ .option('--json', 'Machine-readable result (never includes a credential)')
634
+ .action(async (selector, o, command) => {
635
+ await runAccountsAction(command, async () => {
636
+ const json = !!(o.json || command.optsWithGlobals().json);
637
+ const parsed = parseAccountSelector(selector);
638
+ if (!parsed.agent) {
639
+ throw new Error(`Use the harness-qualified form: agents accounts login <harness>#<name> (e.g. claude#work).`);
640
+ }
641
+ const result = await runLogin(parsed.agent, parsed.name, {
642
+ meta: readMeta(),
643
+ onProgress: (m) => { if (!json)
644
+ console.log(chalk.gray(` ${m}`)); },
645
+ apiKey: o.apiKey,
646
+ noWorkerToken: o.workerToken === false,
647
+ });
648
+ printAddResult(result, json);
649
+ });
650
+ });
651
+ setHelpSections(loginCmd, {
652
+ examples: `agents accounts login claude#work
653
+ agents accounts login codex#personal --api-key sk-… # rotate the worker key
654
+ agents accounts login kimi#main # per-device: logs THIS box in`,
655
+ notes: 'Re-runs the native login into the SAME slot (never a new home) and fails closed when the completed login is a different identity than the account. Headed devices only for harnesses with a portable worker credential; a per-device harness (kimi) may log in on any box — that is how that box gets its login. The daemon re-syncs the account row and worker credential after a successful login.',
656
+ });
657
+ const defaultCmd = accounts.command('default <harness> [name]')
658
+ .description('Set the fleet-wide default account for a harness (picker when no name)')
659
+ .option('--json', 'Machine-readable account list or the resulting default')
660
+ .action(async (harness, name, o, command) => {
661
+ await runAccountsAction(command, () => runAccountsSwitch(harness, name, { json: !!(o.json || command.optsWithGlobals().json) }));
662
+ });
663
+ setHelpSections(defaultCmd, {
664
+ examples: `agents accounts default claude
665
+ agents accounts default claude work
666
+ agents accounts default claude --json`,
667
+ notes: 'The one write path for the per-harness default (the hidden set-default/switch share it). Pass a name to skip the picker. Rotation already honors the default.',
668
+ });
669
+ const migrateCmd = accounts.command('migrate')
670
+ .description('Fold leftover per-account installations into 1 harness install + N credential slots')
671
+ .option('--dry-run', 'Print the plan and touch nothing (default)')
672
+ .option('--apply', 'Execute the plan: move homes into slots, trash empties/duplicates, re-index sessions')
673
+ .option('--json', 'Machine-readable plan or apply result')
674
+ .option('--device <name>', 'Run the migration on a fleet device over SSH')
675
+ .action(async (o, command) => {
676
+ await runAccountsAction(command, async () => {
677
+ const json = !!(o.json || command.optsWithGlobals().json);
678
+ if (o.dryRun && o.apply) {
679
+ throw new Error('Pass only one of --dry-run or --apply.');
680
+ }
681
+ const apply = !!o.apply;
682
+ if (o.device) {
683
+ const { isSelfHost } = await import('../lib/devices/self-host.js');
684
+ if (!isSelfHost(o.device)) {
685
+ const { resolveHost } = await import('../lib/hosts/registry.js');
686
+ const { streamAgentsOnHost } = await import('../lib/hosts/passthrough.js');
687
+ const host = await resolveHost(o.device);
688
+ if (!host)
689
+ throw new Error(`Unknown device '${o.device}'.`);
690
+ const flags = [apply ? '--apply' : '--dry-run'];
691
+ if (json)
692
+ flags.push('--json');
693
+ const code = streamAgentsOnHost(host, ['accounts', 'migrate', ...flags], { interactive: false });
694
+ process.exit(code);
695
+ return;
696
+ }
697
+ }
698
+ if (!apply) {
699
+ const plan = await planAccountMigration();
700
+ if (json) {
701
+ console.log(JSON.stringify(plan, null, 2));
702
+ return;
703
+ }
704
+ console.log(formatMigrationPlan(plan));
705
+ if (plan.totals.slots + plan.totals.trash + plan.totals.deferred > 0) {
706
+ console.log();
707
+ console.log('Dry-run: nothing written. Re-run with --apply to execute.');
708
+ console.log('Restore a trashed install with: agents trash restore <agent>@<label>');
709
+ }
710
+ return;
711
+ }
712
+ const result = await applyAccountMigration();
713
+ if (json) {
714
+ console.log(JSON.stringify({
715
+ manifestPath: result.manifestPath,
716
+ sessionsReindexed: result.sessionsReindexed,
717
+ plan: result.plan,
718
+ manifest: result.manifest,
719
+ }, null, 2));
720
+ return;
721
+ }
722
+ console.log(formatMigrationPlan(result.plan));
723
+ console.log();
724
+ console.log(`Wrote manifest: ${result.manifestPath}`);
725
+ console.log(`Re-indexed ${result.sessionsReindexed} session path${result.sessionsReindexed === 1 ? '' : 's'}.`);
726
+ console.log('Restore a trashed install with: agents trash restore <agent>@<label>');
727
+ });
728
+ });
729
+ setHelpSections(migrateCmd, {
730
+ examples: `agents accounts migrate --dry-run
731
+ agents accounts migrate --apply
732
+ agents accounts migrate --dry-run --device worker-1`,
733
+ notes: 'Folds leftover per-account installations (the old connect homes) into one managed install plus a HOME-shaped slot per identity. Empty logged-out homes and duplicate identities go to trash (agents trash restore reverses). A busy home is deferred, never moved. --apply is required to write; this upgrade only prints the dry-run report. Native OAuth files stay on this device inside the moved home.',
734
+ });
735
+ // Hidden legacy verb: connect is the old spelling of add.
736
+ const connectCmd = accounts.command('connect <harness> [name]', { hidden: true })
737
+ .description('Legacy alias of accounts add')
738
+ .option('--json', 'Machine-readable result')
739
+ .action(async (harness, name, o, command) => {
740
+ await runAccountsAction(command, async () => {
741
+ const agent = parseHarness(harness);
742
+ const reason = addRefusal(agent);
743
+ if (reason)
744
+ throw new Error(reason);
745
+ const result = await runAdd(agent, name, {
746
+ meta: readMeta(),
747
+ onProgress: (m) => console.log(chalk.gray(` ${m}`)),
748
+ });
749
+ printAddResult(result, !!(o.json || command.optsWithGlobals().json));
750
+ });
751
+ });
752
+ retireVerb(connectCmd, 'agents accounts add <harness> [name]');
564
753
  accounts.command('set-key <name>')
565
754
  .description('Rotate an account credential without changing its identity')
566
755
  .option('--from-secrets <bundle:key>', 'Import from an existing agents secrets entry')
@@ -570,7 +759,7 @@ agents run claude --account work`,
570
759
  if (!account)
571
760
  throw new Error(`Unknown provider account '${name}'.`);
572
761
  const secret = o.fromSecrets
573
- ? secretFromBundle(o.fromSecrets)
762
+ ? await secretFromBundle(o.fromSecrets)
574
763
  : await promptAccountSecret(`Enter new ${account.provider} ${account.auth} for '${name}':`);
575
764
  if (secret === null) {
576
765
  process.exit(130);
@@ -621,7 +810,7 @@ agents run claude --account work`,
621
810
  agents accounts view codex#icloud`,
622
811
  notes: 'Native names are unique per harness. A bare name that exists for several harnesses is refused — pick one with <harness>#<name>.',
623
812
  });
624
- accounts.command('name <source> <name>')
813
+ retireVerb(accounts.command('name <source> <name>', { hidden: true })
625
814
  .description('Name a signed-in native installation without copying its OAuth credentials')
626
815
  .action(async (source, name, _o, command) => {
627
816
  await runAccountsAction(command, async () => {
@@ -631,13 +820,14 @@ agents accounts view codex#icloud`,
631
820
  if (account.scope === 'device')
632
821
  console.log(chalk.gray(`${identity.agent} authentication is device-scoped; attach '${account.name}' to '${identity.agent}', not an individual version.`));
633
822
  });
634
- });
635
- const labelCmd = accounts.command('label <source> [label]')
823
+ }), 'agents accounts add <harness> <name> (names are chosen at add time)');
824
+ const labelCmd = accounts.command('label <source> [label]', { hidden: true })
636
825
  .description('Label a native login by harness or <harness>@<version>; the label binds to the account identity, not the version')
637
826
  .option('--account <email-or-id>', 'Native identity to label when the harness has multiple logins')
638
827
  .action(async (source, label, o, command) => {
639
828
  await runAccountsAction(command, () => runAccountsLabel(source, label, o));
640
829
  });
830
+ retireVerb(labelCmd, 'agents accounts add <harness> <name> (rename: agents accounts rename <harness>#<old> <new>)');
641
831
  setHelpSections(labelCmd, {
642
832
  examples: `agents accounts label codex work
643
833
  agents accounts label codex@0.146.0 personal
@@ -645,7 +835,7 @@ agents accounts label codex work --account you@example.com
645
835
  agents run codex#work`,
646
836
  notes: 'The label binds to the signed-in account identity, not the version — codex#work keeps selecting that account after it moves to a newer install. Labels live on the central account rows in agents.yaml, which `agents repo push/pull` already syncs fleet-wide, keyed by (agent, identityKey). One signed-in login needs no selector; with several, an interactive terminal opens a picker, while scripts pass --account or <harness>@<version>.',
647
837
  });
648
- accounts.command('attach <account> <target>')
838
+ retireVerb(accounts.command('attach <account> <target>', { hidden: true })
649
839
  .description('Attach a named account to a native installation or custom harness')
650
840
  .action(async (name, target, _o, command) => {
651
841
  await runAccountsAction(command, async () => {
@@ -712,8 +902,8 @@ agents run codex#work`,
712
902
  writeClaudeInteractiveOauthToken(t, targetAgent, account.kind === 'native' && account.agent === 'claude' ? account.identityLabel : undefined);
713
903
  console.log(chalk.green(`Attached ${account.name} to ${target}.`));
714
904
  });
715
- });
716
- accounts.command('detach <account> <target>')
905
+ }), 'agents run <harness>#<name> (accounts select by name; installation bindings are legacy)');
906
+ retireVerb(accounts.command('detach <account> <target>', { hidden: true })
717
907
  .description('Remove one account attachment')
718
908
  .action(async (name, target, _o, command) => {
719
909
  await runAccountsAction(command, () => {
@@ -736,7 +926,7 @@ agents run codex#work`,
736
926
  catch { /* an unresolvable target has no version home to clean */ }
737
927
  console.log(chalk.green(`Detached ${name} from ${target}.`));
738
928
  });
739
- });
929
+ }), 'agents run <harness>#<name> (accounts select by name; installation bindings are legacy)');
740
930
  const renameCmd = accounts.command('rename <old> <new>')
741
931
  .description('Rename an account without changing its stable id. Target may be <harness>#<name> when the name exists for several harnesses')
742
932
  .action(async (oldName, newName, _o, command) => {
@@ -756,20 +946,21 @@ agents accounts rename codex#icloud cloud`,
756
946
  examples: `agents accounts remove claude#icloud`,
757
947
  notes: 'Native names are unique per harness. A bare name that exists for several harnesses is refused — pick one with <harness>#<name>.',
758
948
  });
759
- accounts.command('set-default <agent> <name>')
949
+ retireVerb(accounts.command('set-default <agent> <name>', { hidden: true })
760
950
  .description('Use this account for a harness when --account is omitted')
761
951
  .action(async (agentRaw, name, _o, command) => {
762
952
  await runAccountsAction(command, () => {
763
953
  const { agent, account } = setDefaultAccount(agentRaw, name);
764
954
  console.log(chalk.green(`${agent} now uses account '${account.name}' unless --account overrides it.`));
765
955
  });
766
- });
767
- const switchCmd = accounts.command('switch <harness> [account]')
956
+ }), 'agents accounts default <harness> <name>');
957
+ const switchCmd = accounts.command('switch <harness> [account]', { hidden: true })
768
958
  .description('Pick the default account for a harness')
769
959
  .option('--json', 'Machine-readable account list or the resulting default')
770
960
  .action(async (harness, account, o, command) => {
771
961
  await runAccountsAction(command, () => runAccountsSwitch(harness, account, { json: !!(o.json || command.optsWithGlobals().json) }));
772
962
  });
963
+ retireVerb(switchCmd, 'agents accounts default <harness> [name]');
773
964
  accounts.command('clear-default <agent>')
774
965
  .description('Return a harness to native login or balanced account selection')
775
966
  .action(async (agentRaw, _o, command) => {
@@ -844,7 +1035,7 @@ agents accounts rename codex#icloud cloud`,
844
1035
  AUTH_TYPE: account.auth,
845
1036
  ...(account.baseUrl ? { BASE_URL: account.baseUrl } : {}),
846
1037
  };
847
- const result = pushBundleToHost(account.name, device, {
1038
+ const result = await pushBundleToHost(account.name, device, {
848
1039
  remoteBackend,
849
1040
  force: o.force,
850
1041
  operation: 'accounts sync',
@@ -865,24 +1056,20 @@ agents run claude`,
865
1056
  notes: 'Switch writes the existing per-harness default (same binding as set-default). Rotation already honors it. Pass an account name to skip the picker. Native name/attach is only for harnesses agents-cli can isolate (claude, codex, grok); provider add is unrestricted.',
866
1057
  });
867
1058
  setHelpSections(accounts, {
868
- examples: `agents accounts connect claude work
869
- agents accounts connect codex personal
870
- agents accounts mint claude
871
- agents accounts mint claude --account work --email ada@example.com --fleet
1059
+ examples: `agents accounts add claude work
1060
+ agents accounts add codex personal --api-key sk-…
1061
+ agents accounts login claude#work
1062
+ agents accounts default claude work
872
1063
  agents accounts add work --provider anthropic --auth setup-token
873
1064
  agents accounts add openrouter-work --provider openrouter --auth api-key --from-secrets openrouter.ai:OPENROUTER_API_KEY
874
1065
  agents accounts set-key work
875
- agents accounts name claude@2.1.220 work
876
- agents accounts label codex@0.146.0 personal
877
- agents accounts attach work claude@2.1.225
878
1066
  agents accounts view work --json
879
1067
  agents accounts view codex#icloud
880
- agents accounts switch claude
881
- agents accounts switch claude work
882
- agents accounts set-default claude work
883
1068
  agents accounts sync openrouter-work yosemite-s0
884
- agents run claude --account work
885
- agents accounts logout claude`,
886
- notes: 'Accounts are stable independent of releases: `accounts connect <harness> [name]` gives each new account a fresh isolated home on the current release and drives its native login (reconnect by name to reuse the home). Native account records contain metadata only; harness-owned OAuth credentials are never copied. Provider accounts are explicit portable bundles with policy never. `accounts mint claude` drives `claude setup-token` and seeds both the named account and the reserved file-based auth bundle (same command as `agents auth mint`). `accounts switch` is the fast picker over the same default `set-default` writes. Harness-native OAuth sign-out is `agents accounts logout <harness>` (API-key accounts use `accounts remove`). Synced vault unlock is `agents secrets vault unlock`.',
1069
+ agents run claude#work
1070
+ agents accounts logout claude
1071
+ agents accounts migrate --dry-run
1072
+ agents accounts migrate --apply`,
1073
+ notes: 'An account is a credential SLOT, not an installation: `accounts add <harness> [name]` runs the native login in a fresh HOME-shaped slot of the one managed install, registers the fleet-wide row, and mints the durable worker credential (claude: setup-token; codex/grok/cursor/opencode: --api-key or a prompt) in one step. Headed devices only — workers are provisioned automatically. `accounts login <harness>#<name>` re-auths into the same slot and re-mints; `accounts default <harness> [name]` is the one default write path. Native account records contain metadata only; harness-owned OAuth credentials are never copied. Provider accounts are explicit portable bundles with policy never. Harness-native OAuth sign-out is `agents accounts logout <harness>` (API-key accounts use `accounts remove`). Leftover per-account installations fold with `agents accounts migrate --dry-run|--apply`. Synced vault unlock is `agents secrets vault unlock`. The legacy connect/name/label/mint/attach/detach/switch/set-default verbs still work this release and print their replacement.',
887
1074
  });
888
1075
  }