@phnx-labs/agents-cli 1.22.84 → 1.22.86

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +80 -53
  3. package/dist/bootstrap.d.ts +7 -6
  4. package/dist/bootstrap.js +20 -27
  5. package/dist/cli/command-registry.js +5 -1
  6. package/dist/commands/accounts.d.ts +6 -13
  7. package/dist/commands/accounts.js +330 -143
  8. package/dist/commands/alias.js +7 -3
  9. package/dist/commands/apply.js +1 -1
  10. package/dist/commands/auth-mint.d.ts +7 -3
  11. package/dist/commands/auth-mint.js +21 -9
  12. package/dist/commands/auth.js +2 -3
  13. package/dist/commands/browser.js +10 -10
  14. package/dist/commands/daemon.d.ts +7 -3
  15. package/dist/commands/daemon.js +40 -25
  16. package/dist/commands/doctor.js +28 -2
  17. package/dist/commands/exec.js +87 -39
  18. package/dist/commands/fleet-capture.js +21 -2
  19. package/dist/commands/harness-wizard.js +2 -2
  20. package/dist/commands/lease.js +7 -12
  21. package/dist/commands/profiles.d.ts +2 -2
  22. package/dist/commands/profiles.js +20 -13
  23. package/dist/commands/repo.js +3 -3
  24. package/dist/commands/run-account-picker.d.ts +3 -3
  25. package/dist/commands/run-account-picker.js +29 -30
  26. package/dist/commands/secrets-passthrough.d.ts +20 -0
  27. package/dist/commands/secrets-passthrough.js +43 -0
  28. package/dist/commands/setup-accounts.d.ts +3 -2
  29. package/dist/commands/setup-accounts.js +4 -4
  30. package/dist/commands/setup-secrets.d.ts +16 -21
  31. package/dist/commands/setup-secrets.js +43 -224
  32. package/dist/commands/ssh.js +2 -2
  33. package/dist/commands/sync.js +1 -1
  34. package/dist/commands/update.d.ts +10 -0
  35. package/dist/commands/update.js +43 -40
  36. package/dist/commands/versions.d.ts +11 -11
  37. package/dist/commands/versions.js +131 -83
  38. package/dist/commands/view.d.ts +1 -9
  39. package/dist/commands/view.js +41 -55
  40. package/dist/commands/webhook.js +5 -5
  41. package/dist/commands/workflows.js +2 -1
  42. package/dist/index.d.ts +12 -12
  43. package/dist/index.js +13 -42
  44. package/dist/lib/account-capabilities.d.ts +1 -1
  45. package/dist/lib/account-capabilities.js +1 -1
  46. package/dist/lib/account-catalog.d.ts +121 -6
  47. package/dist/lib/account-catalog.js +421 -7
  48. package/dist/lib/account-registry.d.ts +8 -30
  49. package/dist/lib/account-registry.js +36 -79
  50. package/dist/lib/account-schema.d.ts +1 -1
  51. package/dist/lib/account-schema.js +1 -1
  52. package/dist/lib/accounting/account-pool-collect.js +16 -4
  53. package/dist/lib/accounting/rotate.d.ts +20 -7
  54. package/dist/lib/accounting/rotate.js +89 -13
  55. package/dist/lib/accounting/usage.js +11 -16
  56. package/dist/lib/accounts/add.d.ts +138 -0
  57. package/dist/lib/accounts/add.js +651 -0
  58. package/dist/lib/accounts/migrate.d.ts +117 -0
  59. package/dist/lib/accounts/migrate.js +536 -0
  60. package/dist/lib/accounts/slots.d.ts +13 -0
  61. package/dist/lib/accounts/slots.js +100 -0
  62. package/dist/lib/agent-spec/agents.d.ts +16 -0
  63. package/dist/lib/agent-spec/agents.js +38 -4
  64. package/dist/lib/app-bundle-install.js +5 -4
  65. package/dist/lib/auth-health.d.ts +3 -0
  66. package/dist/lib/auth-health.js +11 -0
  67. package/dist/lib/auth-mint.d.ts +52 -16
  68. package/dist/lib/auth-mint.js +117 -39
  69. package/dist/lib/browser/chrome.d.ts +1 -1
  70. package/dist/lib/browser/chrome.js +5 -5
  71. package/dist/lib/byok-usage.js +3 -3
  72. package/dist/lib/claude-account-token.d.ts +47 -3
  73. package/dist/lib/claude-account-token.js +172 -50
  74. package/dist/lib/cloud/antigravity.js +4 -4
  75. package/dist/lib/cloud/cursor.js +4 -4
  76. package/dist/lib/crabbox/cli.d.ts +1 -1
  77. package/dist/lib/crabbox/cli.js +6 -6
  78. package/dist/lib/crabbox/runtimes.d.ts +3 -3
  79. package/dist/lib/crabbox/runtimes.js +5 -5
  80. package/dist/lib/daemon/account-state-daemon-service.d.ts +37 -1
  81. package/dist/lib/daemon/account-state-daemon-service.js +199 -3
  82. package/dist/lib/daemon/auth-sync-service.js +32 -1
  83. package/dist/lib/daemon/daemon.d.ts +6 -16
  84. package/dist/lib/daemon/daemon.js +52 -97
  85. package/dist/lib/daemon/daemon.test-fixture.d.ts +7 -9
  86. package/dist/lib/daemon/daemon.test-fixture.js +22 -40
  87. package/dist/lib/daemon/harness-update-service.d.ts +1 -1
  88. package/dist/lib/daemon/harness-update-service.js +1 -1
  89. package/dist/lib/daemon/runner.d.ts +15 -3
  90. package/dist/lib/daemon/runner.js +49 -24
  91. package/dist/lib/daemon-health.d.ts +1 -2
  92. package/dist/lib/daemon-health.js +7 -6
  93. package/dist/lib/daemon-services.d.ts +1 -1
  94. package/dist/lib/daemon-services.js +1 -11
  95. package/dist/lib/daemon-webhooks.d.ts +8 -7
  96. package/dist/lib/daemon-webhooks.js +10 -9
  97. package/dist/lib/device-config.js +1 -1
  98. package/dist/lib/devices/doctor-findings.d.ts +1 -1
  99. package/dist/lib/devices/harness-inventory.d.ts +39 -0
  100. package/dist/lib/devices/harness-inventory.js +126 -4
  101. package/dist/lib/doctor-diff.js +2 -1
  102. package/dist/lib/exec-account-home.d.ts +38 -0
  103. package/dist/lib/exec-account-home.js +164 -0
  104. package/dist/lib/exec.d.ts +30 -0
  105. package/dist/lib/exec.js +101 -31
  106. package/dist/lib/fleet/apply.d.ts +2 -2
  107. package/dist/lib/fleet/apply.js +4 -4
  108. package/dist/lib/fleet/auth-sync.js +4 -3
  109. package/dist/lib/fleet-shared-repo-sync.js +6 -9
  110. package/dist/lib/harness/adapter.d.ts +7 -7
  111. package/dist/lib/harness/adapter.js +10 -6
  112. package/dist/lib/harness/adapters/grok.js +8 -3
  113. package/dist/lib/harness/adapters/muse.js +1 -1
  114. package/dist/lib/harness/adapters/opencode.js +12 -3
  115. package/dist/lib/harness-auth-capabilities.d.ts +61 -0
  116. package/dist/lib/harness-auth-capabilities.js +52 -0
  117. package/dist/lib/helper-versions.d.ts +6 -4
  118. package/dist/lib/helper-versions.js +5 -4
  119. package/dist/lib/hosts/credential-transport.d.ts +10 -0
  120. package/dist/lib/hosts/credential-transport.js +37 -0
  121. package/dist/lib/hosts/dispatch.d.ts +7 -0
  122. package/dist/lib/hosts/dispatch.js +13 -6
  123. package/dist/lib/identity/client.d.ts +3 -3
  124. package/dist/lib/identity/client.js +3 -3
  125. package/dist/lib/installations/index.d.ts +1 -1
  126. package/dist/lib/installations/index.js +1 -1
  127. package/dist/lib/installations/migrate.js +9 -0
  128. package/dist/lib/installations/resolve.js +1 -1
  129. package/dist/lib/installations/shims.d.ts +21 -4
  130. package/dist/lib/installations/shims.js +86 -5
  131. package/dist/lib/installations/store.d.ts +43 -0
  132. package/dist/lib/installations/store.js +75 -7
  133. package/dist/lib/installations/versions.js +1 -1
  134. package/dist/lib/menubar/install-menubar.d.ts +3 -3
  135. package/dist/lib/menubar/install-menubar.js +3 -3
  136. package/dist/lib/native-accounts.d.ts +35 -0
  137. package/dist/lib/native-accounts.js +41 -0
  138. package/dist/lib/net-close.d.ts +11 -0
  139. package/dist/lib/net-close.js +25 -0
  140. package/dist/lib/openclaw-keychain.js +27 -1
  141. package/dist/lib/profiles.js +22 -9
  142. package/dist/lib/project-resources.js +1 -1
  143. package/dist/lib/reserved-stores.d.ts +53 -0
  144. package/dist/lib/reserved-stores.js +120 -0
  145. package/dist/lib/secrets-client.d.ts +199 -0
  146. package/dist/lib/secrets-client.js +718 -0
  147. package/dist/lib/secrets-policy.d.ts +235 -0
  148. package/dist/lib/secrets-policy.js +505 -0
  149. package/dist/lib/secrets-types.d.ts +188 -0
  150. package/dist/lib/secrets-types.js +24 -0
  151. package/dist/lib/self-heal/checks/shims.js +5 -3
  152. package/dist/lib/service-manifest.js +3 -4
  153. package/dist/lib/session/db.d.ts +18 -0
  154. package/dist/lib/session/db.js +47 -14
  155. package/dist/lib/session/sync/config.js +2 -2
  156. package/dist/lib/sha256-asset.d.ts +11 -16
  157. package/dist/lib/sha256-asset.js +11 -16
  158. package/dist/lib/share/config.js +14 -5
  159. package/dist/lib/signin-badge.d.ts +26 -0
  160. package/dist/lib/signin-badge.js +42 -0
  161. package/dist/lib/staleness/detectors/workflows.js +3 -105
  162. package/dist/lib/staleness/writers/workflows.js +2 -1
  163. package/dist/lib/state.d.ts +7 -4
  164. package/dist/lib/state.js +18 -7
  165. package/dist/lib/sync-umbrella.js +2 -2
  166. package/dist/lib/teams/agents.js +1 -1
  167. package/dist/lib/types.d.ts +74 -33
  168. package/dist/lib/view-types.d.ts +6 -3
  169. package/dist/lib/workflows-registry.d.ts +71 -0
  170. package/dist/lib/workflows-registry.js +280 -0
  171. package/dist/lib/workflows.d.ts +21 -21
  172. package/dist/lib/workflows.js +12 -327
  173. package/package.json +2 -3
  174. package/scripts/postinstall.js +35 -30
  175. package/dist/commands/secrets-import.d.ts +0 -18
  176. package/dist/commands/secrets-import.js +0 -74
  177. package/dist/commands/secrets-migrate.d.ts +0 -25
  178. package/dist/commands/secrets-migrate.js +0 -334
  179. package/dist/commands/secrets-rotate-passphrase.d.ts +0 -17
  180. package/dist/commands/secrets-rotate-passphrase.js +0 -96
  181. package/dist/commands/secrets-sync.d.ts +0 -11
  182. package/dist/commands/secrets-sync.js +0 -153
  183. package/dist/commands/secrets-vault.d.ts +0 -10
  184. package/dist/commands/secrets-vault.js +0 -130
  185. package/dist/commands/secrets.d.ts +0 -212
  186. package/dist/commands/secrets.js +0 -3030
  187. package/dist/lib/accounts/connect.d.ts +0 -176
  188. package/dist/lib/accounts/connect.js +0 -453
  189. package/dist/lib/daemon/keychain-reap-service.d.ts +0 -17
  190. package/dist/lib/daemon/keychain-reap-service.js +0 -32
  191. package/dist/lib/daemon/secrets-broker-service.d.ts +0 -21
  192. package/dist/lib/daemon/secrets-broker-service.js +0 -51
  193. package/dist/lib/secrets/agent.d.ts +0 -358
  194. package/dist/lib/secrets/agent.js +0 -1291
  195. package/dist/lib/secrets/audit.d.ts +0 -46
  196. package/dist/lib/secrets/audit.js +0 -101
  197. package/dist/lib/secrets/bundles.d.ts +0 -290
  198. package/dist/lib/secrets/bundles.js +0 -1547
  199. package/dist/lib/secrets/download-keychain.d.ts +0 -47
  200. package/dist/lib/secrets/download-keychain.js +0 -70
  201. package/dist/lib/secrets/drivers/rush.d.ts +0 -14
  202. package/dist/lib/secrets/drivers/rush.js +0 -90
  203. package/dist/lib/secrets/fallback.d.ts +0 -48
  204. package/dist/lib/secrets/fallback.js +0 -48
  205. package/dist/lib/secrets/filestore.d.ts +0 -222
  206. package/dist/lib/secrets/filestore.js +0 -1099
  207. package/dist/lib/secrets/headless.d.ts +0 -43
  208. package/dist/lib/secrets/headless.js +0 -56
  209. package/dist/lib/secrets/icloud-import.d.ts +0 -79
  210. package/dist/lib/secrets/icloud-import.js +0 -206
  211. package/dist/lib/secrets/index.d.ts +0 -451
  212. package/dist/lib/secrets/index.js +0 -1568
  213. package/dist/lib/secrets/install-helper.d.ts +0 -72
  214. package/dist/lib/secrets/install-helper.js +0 -245
  215. package/dist/lib/secrets/lease.d.ts +0 -25
  216. package/dist/lib/secrets/lease.js +0 -44
  217. package/dist/lib/secrets/linux.d.ts +0 -77
  218. package/dist/lib/secrets/linux.js +0 -393
  219. package/dist/lib/secrets/list-filter.d.ts +0 -109
  220. package/dist/lib/secrets/list-filter.js +0 -261
  221. package/dist/lib/secrets/mcp.d.ts +0 -93
  222. package/dist/lib/secrets/mcp.js +0 -211
  223. package/dist/lib/secrets/profiles.d.ts +0 -10
  224. package/dist/lib/secrets/profiles.js +0 -13
  225. package/dist/lib/secrets/push.d.ts +0 -133
  226. package/dist/lib/secrets/push.js +0 -273
  227. package/dist/lib/secrets/rc-hygiene.d.ts +0 -63
  228. package/dist/lib/secrets/rc-hygiene.js +0 -143
  229. package/dist/lib/secrets/read-backoff.d.ts +0 -27
  230. package/dist/lib/secrets/read-backoff.js +0 -64
  231. package/dist/lib/secrets/reaper.d.ts +0 -90
  232. package/dist/lib/secrets/reaper.js +0 -243
  233. package/dist/lib/secrets/remote.d.ts +0 -152
  234. package/dist/lib/secrets/remote.js +0 -344
  235. package/dist/lib/secrets/reserved-sync.d.ts +0 -66
  236. package/dist/lib/secrets/reserved-sync.js +0 -147
  237. package/dist/lib/secrets/scope.d.ts +0 -26
  238. package/dist/lib/secrets/scope.js +0 -29
  239. package/dist/lib/secrets/session-store.d.ts +0 -107
  240. package/dist/lib/secrets/session-store.js +0 -342
  241. package/dist/lib/secrets/sync-backend.d.ts +0 -48
  242. package/dist/lib/secrets/sync-backend.js +0 -13
  243. package/dist/lib/secrets/sync-commands.d.ts +0 -21
  244. package/dist/lib/secrets/sync-commands.js +0 -21
  245. package/dist/lib/secrets/sync.d.ts +0 -48
  246. package/dist/lib/secrets/sync.js +0 -237
  247. package/dist/lib/secrets/unlock-hints.d.ts +0 -27
  248. package/dist/lib/secrets/unlock-hints.js +0 -36
  249. package/dist/lib/secrets/usage-db.d.ts +0 -46
  250. package/dist/lib/secrets/usage-db.js +0 -96
  251. package/dist/lib/secrets/vault-age-helper.d.ts +0 -1
  252. package/dist/lib/secrets/vault-age-helper.js +0 -34
  253. package/dist/lib/secrets/vault.d.ts +0 -49
  254. package/dist/lib/secrets/vault.js +0 -399
  255. package/dist/lib/secrets/windows.d.ts +0 -81
  256. package/dist/lib/secrets/windows.js +0 -556
  257. package/scripts/install-helper.js +0 -97
  258. /package/dist/lib/{secrets/sync-passphrase.d.ts → sync-passphrase.d.ts} +0 -0
  259. /package/dist/lib/{secrets/sync-passphrase.js → sync-passphrase.js} +0 -0
@@ -0,0 +1,43 @@
1
+ import chalk from 'chalk';
2
+ import { spawnSync } from 'node:child_process';
3
+ import { buildServeEnv, invocation, resolveSecretsBin, SecretsClientError } from '../lib/secrets-client.js';
4
+ const INSTALL_HINT = 'npm i -g @phnx-labs/secrets-cli';
5
+ export function registerSecretsCommands(program) {
6
+ program
7
+ .command('secrets')
8
+ .description('Named bundles of env variables — passthrough to the standalone `secrets` CLI. Run `agents secrets --help` (or `agents setup secrets`) for the full subcommand list.')
9
+ .allowUnknownOption()
10
+ .allowExcessArguments()
11
+ // Hand `-h`/`--help` through too — the real subcommand help lives in the
12
+ // installed `secrets` binary, not in this passthrough.
13
+ .helpOption(false)
14
+ .action(() => {
15
+ let bin;
16
+ try {
17
+ bin = resolveSecretsBin();
18
+ }
19
+ catch (err) {
20
+ if (err instanceof SecretsClientError && err.code === 'SECRETS_BIN_MISSING') {
21
+ console.error(chalk.red('The standalone `secrets` CLI is not installed.'));
22
+ console.error(chalk.gray(`Install it, then re-run this command:`));
23
+ console.error(chalk.cyan(` ${INSTALL_HINT}`));
24
+ process.exit(1);
25
+ }
26
+ throw err;
27
+ }
28
+ // Everything after the literal `secrets` token on the real argv — the
29
+ // subcommand + its own flags, verbatim, never re-parsed by commander.
30
+ const secretsIndex = process.argv.indexOf('secrets');
31
+ const forwarded = secretsIndex >= 0 ? process.argv.slice(secretsIndex + 1) : [];
32
+ const { command, prefix } = invocation(bin);
33
+ const res = spawnSync(command, [...prefix, ...forwarded], {
34
+ stdio: 'inherit',
35
+ env: buildServeEnv(),
36
+ });
37
+ if (res.error) {
38
+ console.error(chalk.red(`Failed to run \`secrets\`: ${res.error.message}`));
39
+ process.exit(1);
40
+ }
41
+ process.exit(res.status ?? 1);
42
+ });
43
+ }
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * `agents setup accounts` — mint a Claude setup-token so unattended usage/probe
3
- * is not stuck on "usage pending". Delegates to the same mint engine as
4
- * `agents accounts mint` / `agents auth mint`.
3
+ * is not stuck on "usage pending". Delegates to the same mint engine as the
4
+ * mint step of `agents accounts add claude [name]` /
5
+ * `agents accounts login claude#<name>`.
5
6
  */
6
7
  import type { Command } from 'commander';
7
8
  export declare function runAccountsSetupWizard(): Promise<void>;
@@ -6,13 +6,13 @@ export async function runAccountsSetupWizard() {
6
6
  const status = hasMintedSetupToken();
7
7
  if (status.ready) {
8
8
  console.log(chalk.green(`Setup-token already minted (${status.detail}).`));
9
- console.log(chalk.gray('Re-mint with: agents accounts mint claude'));
9
+ console.log(chalk.gray('Re-mint with: agents accounts login claude#<name>'));
10
10
  return;
11
11
  }
12
12
  if (!isInteractiveTerminal()) {
13
13
  console.log(chalk.yellow('No Claude setup-token on this machine.'));
14
- console.log(chalk.gray('Mint one with: agents accounts mint claude'));
15
- console.log(chalk.gray('Already have a token: agents accounts mint claude --token-stdin'));
14
+ console.log(chalk.gray('Mint one with: agents accounts add claude [name]'));
15
+ console.log(chalk.gray('Already have a token: agents accounts add claude [name]'));
16
16
  process.exitCode = 1;
17
17
  return;
18
18
  }
@@ -21,7 +21,7 @@ export async function runAccountsSetupWizard() {
21
21
  default: true,
22
22
  });
23
23
  if (!proceed) {
24
- console.log(chalk.gray('Skipped. Run `agents accounts mint claude` when ready.'));
24
+ console.log(chalk.gray('Skipped. Run `agents accounts add claude [name]` when ready.'));
25
25
  return;
26
26
  }
27
27
  const result = await mintAndSeed({ harness: 'claude' });
@@ -1,28 +1,23 @@
1
1
  /**
2
- * `agents setup secrets` — guided defaults for `agents secrets` onboarding.
2
+ * `agents setup secrets` — install guidance for the standalone `secrets` CLI,
3
+ * then a pass-through to its own `secrets migrate` onboarding (PHNX-3989).
3
4
  *
4
- * The wizard writes setup preferences and delegates imports to the existing
5
- * `agents secrets import` command, keeping bundle storage as the source of truth.
5
+ * The wizard that used to pick a default backend/policy and delegate imports
6
+ * lived entirely against the in-repo secrets engine. That engine is extracted
7
+ * to `@phnx-labs/secrets-cli`, which owns its own interactive onboarding
8
+ * (`secrets migrate`) — agents-cli never rebundles it (DIST-1), so there is no
9
+ * in-process fallback when it isn't installed: this command names the install
10
+ * command and stops.
6
11
  */
7
12
  import type { Command } from 'commander';
8
- import type { SecretsPolicy } from '../lib/secrets/bundles.js';
9
- interface SetupSecretsOptions {
10
- backend?: string;
11
- policy?: string;
12
- importFrom?: string;
13
- bundle?: string;
14
- vault?: string;
15
- iUnderstand?: boolean;
16
- force?: boolean;
17
- }
18
13
  export declare function setupSecretsPrefsPath(): string;
19
- /** The wizard's `--policy` shares the canonical parser with `agents secrets
20
- * policy`, so it accepts `hold` (and the `daily`/`session` aliases) rather than
21
- * carrying its own copy that knows only the retired vocabulary. `parsePolicyOpt`
22
- * defaults an absent value to `always`; the wizard's own default is `hold`, so
23
- * it is supplied here rather than inherited. */
24
- export declare function parsePolicy(raw: string | undefined): SecretsPolicy;
25
- export declare function runSecretsSetupWizard(opts?: SetupSecretsOptions): Promise<boolean>;
14
+ /** True when the standalone `secrets` executable resolves ($SECRETS_BIN or PATH). */
15
+ export declare function isSecretsCliInstalled(): boolean;
16
+ /**
17
+ * Print install guidance when the standalone is missing, else hand off to its
18
+ * own interactive `secrets migrate`. Returns whether setup is now complete
19
+ * (installed, and — when it ran — `migrate` exited 0).
20
+ */
21
+ export declare function runSecretsSetupWizard(): Promise<boolean>;
26
22
  /** Register `agents setup secrets` under the parent `setup` command. */
27
23
  export declare function registerSetupSecretsCommand(setupCmd: Command): void;
28
- export {};
@@ -1,249 +1,68 @@
1
1
  /**
2
- * `agents setup secrets` — guided defaults for `agents secrets` onboarding.
2
+ * `agents setup secrets` — install guidance for the standalone `secrets` CLI,
3
+ * then a pass-through to its own `secrets migrate` onboarding (PHNX-3989).
3
4
  *
4
- * The wizard writes setup preferences and delegates imports to the existing
5
- * `agents secrets import` command, keeping bundle storage as the source of truth.
5
+ * The wizard that used to pick a default backend/policy and delegate imports
6
+ * lived entirely against the in-repo secrets engine. That engine is extracted
7
+ * to `@phnx-labs/secrets-cli`, which owns its own interactive onboarding
8
+ * (`secrets migrate`) — agents-cli never rebundles it (DIST-1), so there is no
9
+ * in-process fallback when it isn't installed: this command names the install
10
+ * command and stops.
6
11
  */
7
12
  import chalk from 'chalk';
8
13
  import * as fs from 'fs';
9
14
  import * as path from 'path';
10
15
  import { spawnSync } from 'node:child_process';
11
- import { getCliLaunch } from '../lib/cli-entry.js';
12
- import { getHistoryDir, updateMeta } from '../lib/state.js';
13
- import { isInteractiveTerminal, isPromptCancelled } from './utils.js';
14
- import { parsePolicyOpt } from './secrets.js';
15
- function defaultBackendForPlatform(platform = process.platform) {
16
- return platform === 'darwin' ? 'keychain' : 'file';
17
- }
16
+ import { getHistoryDir } from '../lib/state.js';
17
+ import { resolveSecretsBin, invocation, SecretsClientError } from '../lib/secrets-client.js';
18
+ const INSTALL_HINT = 'npm i -g @phnx-labs/secrets-cli';
18
19
  export function setupSecretsPrefsPath() {
19
20
  return path.join(getHistoryDir(), 'setup', 'secrets.json');
20
21
  }
21
- function parseBackend(raw) {
22
- const value = (raw ?? defaultBackendForPlatform()).toLowerCase();
23
- if (value === 'keychain' || value === 'file' || value === 'vault')
24
- return value;
25
- throw new Error(`Invalid --backend '${raw}'. Use keychain, file, or vault.`);
26
- }
27
- /** The wizard's `--policy` shares the canonical parser with `agents secrets
28
- * policy`, so it accepts `hold` (and the `daily`/`session` aliases) rather than
29
- * carrying its own copy that knows only the retired vocabulary. `parsePolicyOpt`
30
- * defaults an absent value to `always`; the wizard's own default is `hold`, so
31
- * it is supplied here rather than inherited. */
32
- export function parsePolicy(raw) {
33
- return parsePolicyOpt(raw ?? 'hold');
34
- }
35
- function parseImportSource(raw) {
36
- const value = (raw ?? 'none').toLowerCase();
37
- if (value === 'none' || value === 'env' || value === '1password' || value === 'icloud')
38
- return value;
39
- throw new Error(`Invalid --import-from '${raw}'. Use none, env, 1password, or icloud.`);
40
- }
41
- function hasCommand(command) {
42
- const res = spawnSync(command, ['--version'], { stdio: 'ignore', windowsHide: true });
43
- return !res.error;
44
- }
45
- function saveSetupSecretsPrefs(prefs) {
46
- const file = setupSecretsPrefsPath();
47
- fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
48
- fs.writeFileSync(file, JSON.stringify(prefs, null, 2) + '\n', { mode: 0o600 });
49
- return file;
50
- }
51
- function applySecretsDefaults(backend, policy) {
52
- updateMeta((meta) => ({
53
- ...meta,
54
- secrets: {
55
- ...meta.secrets,
56
- backend,
57
- ...(policy === 'never' ? {} : { policy }),
58
- },
59
- }));
60
- }
61
- function backendArgs(backend) {
62
- return backend === 'vault' ? ['--synced'] : ['--backend', backend];
63
- }
64
- /**
65
- * Provision the signed macOS keychain broker on the explicit onboarding path.
66
- * This is the async, download-capable trigger the hot sync `getKeychainHelperPath()`
67
- * fails loud toward: with a bundled `.app` it is a local copy (no network); on a
68
- * tarball that lacks the bundle it fetches the verified release asset. Best-effort
69
- * — the wizard's saved prefs must survive an offline provision failure; the next
70
- * secret op re-points here.
71
- */
72
- async function maybeProvisionKeychainHelper(backend) {
73
- if (backend !== 'keychain' || process.platform !== 'darwin')
74
- return;
75
- const { ensureKeychainHelperInstalledAsync } = await import('../lib/secrets/install-helper.js');
22
+ /** True when the standalone `secrets` executable resolves ($SECRETS_BIN or PATH). */
23
+ export function isSecretsCliInstalled() {
76
24
  try {
77
- await ensureKeychainHelperInstalledAsync();
78
- console.log(chalk.gray('keychain helper: installed the signed broker for this version.'));
25
+ resolveSecretsBin();
26
+ return true;
79
27
  }
80
28
  catch (err) {
81
- console.error(chalk.yellow(`keychain helper not installed: ${err.message}\n` +
82
- 'Re-run `agents setup secrets` once online, or reinstall agents-cli.'));
83
- }
84
- }
85
- function printBackendNotes(backend) {
86
- if (backend === 'keychain') {
87
- console.log(chalk.gray('backend: keychain — macOS reads may ask for Touch ID or the device password.'));
88
- }
89
- else if (backend === 'file') {
90
- console.log(chalk.gray('backend: file — encrypted at rest; headless reads need no passphrase by default, via a machine-local key at ~/.agents/.secrets-key/passphrase.'));
91
- }
92
- else {
93
- console.log(chalk.gray('backend: vault — synced ~/.agents/vault.age storage; unlock it with `agents secrets vault unlock`.'));
94
- }
95
- }
96
- function runAgentsSubcommand(args) {
97
- const launch = getCliLaunch(args);
98
- const res = spawnSync(launch.command, launch.args, { stdio: 'inherit', env: process.env });
99
- if ((res.status ?? 1) !== 0) {
100
- throw new Error(`agents ${args.join(' ')} failed with exit ${res.status ?? 1}.`);
29
+ if (err instanceof SecretsClientError && err.code === 'SECRETS_BIN_MISSING')
30
+ return false;
31
+ throw err;
101
32
  }
102
33
  }
103
- async function maybeImportSecrets(source, opts) {
104
- if (source === 'none')
105
- return;
106
- let bundle = opts.bundle;
107
- if (!bundle) {
108
- if (!isInteractiveTerminal()) {
109
- throw new Error(`--bundle is required with --import-from ${source} in a non-interactive shell.`);
110
- }
111
- const { input } = await import('@inquirer/prompts');
112
- bundle = await input({
113
- message: 'Import into which bundle?',
114
- default: 'default',
115
- validate: (v) => (v.trim().length > 0 ? true : 'Enter a bundle name.'),
116
- });
117
- }
118
- const args = ['secrets', 'import', bundle, ...backendArgs(opts.backend)];
119
- if (opts.force)
120
- args.push('--force');
121
- if (source === 'env') {
122
- let envPath = opts.vault;
123
- if (!envPath) {
124
- if (!isInteractiveTerminal()) {
125
- throw new Error('--vault must name the .env path when --import-from env is used non-interactively.');
126
- }
127
- const { input } = await import('@inquirer/prompts');
128
- envPath = await input({
129
- message: '.env file path',
130
- default: '.env',
131
- validate: (v) => (v.trim().length > 0 ? true : 'Enter a .env path.'),
132
- });
133
- }
134
- args.push('--from', envPath);
135
- }
136
- else if (source === '1password') {
137
- const vault = opts.vault;
138
- if (!vault)
139
- throw new Error('--vault <name> is required with --import-from 1password.');
140
- args.push('--from', `1password:${vault}`);
141
- }
142
- else {
143
- args.push('--from', 'icloud');
144
- }
145
- runAgentsSubcommand(args);
146
- }
147
- async function resolveInteractiveChoices(opts) {
148
- if (!isInteractiveTerminal()) {
149
- return {
150
- backend: parseBackend(opts.backend),
151
- policy: parsePolicy(opts.policy),
152
- importSource: parseImportSource(opts.importFrom),
153
- };
154
- }
155
- const { select } = await import('@inquirer/prompts');
156
- const backend = opts.backend ? parseBackend(opts.backend) : await select({
157
- message: 'Default secrets backend',
158
- default: defaultBackendForPlatform(),
159
- choices: [
160
- { name: 'keychain — local OS keychain; reads may ask for Touch ID/device password', value: 'keychain' },
161
- { name: 'file — passphrase-encrypted file store for headless machines', value: 'file' },
162
- { name: 'vault — synced ~/.agents/vault.age file; requires `agents secrets vault unlock`', value: 'vault' },
163
- ],
164
- });
165
- const policy = opts.policy ? parsePolicy(opts.policy) : await select({
166
- message: 'Default prompt policy',
167
- default: 'hold',
168
- choices: [
169
- { name: 'hold — ask once, then hold for the configured window (7 days by default)', value: 'hold' },
170
- { name: 'always — ask on every read', value: 'always' },
171
- { name: 'never — no biometry ACL; use only for automation-only bundles', value: 'never' },
172
- ],
173
- });
174
- const importChoices = [
175
- { name: 'skip import', value: 'none' },
176
- { name: 'import from .env file', value: 'env' },
177
- ...(hasCommand('op') ? [{ name: 'import from 1Password vault', value: '1password' }] : []),
178
- { name: 'import legacy iCloud Keychain bundles', value: 'icloud' },
179
- ];
180
- const importSource = opts.importFrom ? parseImportSource(opts.importFrom) : await select({
181
- message: 'Import existing secrets now?',
182
- default: 'none',
183
- choices: importChoices,
184
- });
185
- return { backend, policy, importSource };
186
- }
187
- function printOnboardingSummary(backend, policy, prefsFile) {
188
- console.log(chalk.bold('\nSecrets setup saved.'));
189
- console.log(chalk.gray(`preferences: ${prefsFile}`));
190
- printBackendNotes(backend);
191
- if (policy === 'never') {
192
- console.log(chalk.yellow('policy: never is saved as a setup preference; create automation-only bundles with --policy never --i-understand.'));
193
- }
194
- else {
195
- console.log(chalk.gray(`policy: ${policy} — used by bundles that do not set their own policy.`));
196
- }
197
- console.log(chalk.bold('\nTry:'));
198
- console.log(chalk.cyan(` agents secrets create prod ${backendArgs(backend).join(' ')} --policy ${policy === 'never' ? 'hold' : policy}`));
199
- console.log(chalk.cyan(' agents secrets add prod API_KEY'));
200
- console.log(chalk.cyan(' agents run claude "deploy" --secrets prod'));
34
+ function recordSetupComplete() {
35
+ const file = setupSecretsPrefsPath();
36
+ fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
37
+ fs.writeFileSync(file, JSON.stringify({ updatedAt: new Date().toISOString() }, null, 2) + '\n', { mode: 0o600 });
201
38
  }
202
- export async function runSecretsSetupWizard(opts = {}) {
203
- const { backend, policy, importSource } = await resolveInteractiveChoices(opts);
204
- if (policy === 'never' && !opts.iUnderstand && !isInteractiveTerminal()) {
205
- throw new Error("Refusing to save policy 'never' non-interactively without --i-understand.");
39
+ /**
40
+ * Print install guidance when the standalone is missing, else hand off to its
41
+ * own interactive `secrets migrate`. Returns whether setup is now complete
42
+ * (installed, and — when it ran — `migrate` exited 0).
43
+ */
44
+ export async function runSecretsSetupWizard() {
45
+ if (!isSecretsCliInstalled()) {
46
+ console.log(chalk.yellow('The standalone `secrets` CLI is not installed.'));
47
+ console.log(chalk.gray('Install it, then re-run `agents setup secrets`:'));
48
+ console.log(chalk.cyan(` ${INSTALL_HINT}`));
49
+ return false;
206
50
  }
207
- applySecretsDefaults(backend, policy);
208
- const prefsFile = saveSetupSecretsPrefs({
209
- defaultBackend: backend,
210
- defaultPolicy: policy,
211
- updatedAt: new Date().toISOString(),
212
- });
213
- // Provision the signed keychain broker before any import subprocess reads it.
214
- await maybeProvisionKeychainHelper(backend);
215
- await maybeImportSecrets(importSource, {
216
- bundle: opts.bundle,
217
- vault: opts.vault,
218
- backend,
219
- force: opts.force,
220
- });
221
- printOnboardingSummary(backend, policy, prefsFile);
222
- return true;
51
+ const bin = resolveSecretsBin();
52
+ const { command, prefix } = invocation(bin);
53
+ const res = spawnSync(command, [...prefix, 'migrate'], { stdio: 'inherit' });
54
+ const ok = (res.status ?? 1) === 0;
55
+ if (ok)
56
+ recordSetupComplete();
57
+ return ok;
223
58
  }
224
59
  /** Register `agents setup secrets` under the parent `setup` command. */
225
60
  export function registerSetupSecretsCommand(setupCmd) {
226
61
  setupCmd
227
62
  .command('secrets')
228
- .description('Configure `agents secrets` defaults and optionally import existing secrets.')
229
- .option('--backend <backend>', 'default backend: keychain, file, or vault')
230
- .option('--policy <policy>', "default prompt policy: hold, always, or never ('daily'/'session' are accepted aliases for 'hold')")
231
- .option('--import-from <source>', 'optional import source: none, env, 1password, or icloud')
232
- .option('--bundle <name>', 'bundle name for optional imports')
233
- .option('--vault <name-or-path>', '1Password vault name, or .env path with --import-from env')
234
- .option('--i-understand', 'allow saving the "never" policy preference without an interactive prompt')
235
- .option('--force', 'pass --force through to optional imports')
236
- .action(async (options) => {
237
- try {
238
- await runSecretsSetupWizard(options);
239
- }
240
- catch (err) {
241
- if (isPromptCancelled(err)) {
242
- console.log(chalk.yellow('\nCancelled'));
243
- return;
244
- }
245
- console.error(chalk.red(err.message));
63
+ .description('Install guidance for the standalone `secrets` CLI, then run its own `secrets migrate` onboarding.')
64
+ .action(async () => {
65
+ if (!(await runSecretsSetupWizard()))
246
66
  process.exitCode = 1;
247
- }
248
67
  });
249
68
  }
@@ -16,7 +16,7 @@ import * as path from 'path';
16
16
  import chalk from 'chalk';
17
17
  import ora from 'ora';
18
18
  import { getCliVersion } from '../lib/version.js';
19
- import { readAndResolveBundleEnv } from '../lib/secrets/bundles.js';
19
+ import { readAndResolveBundleEnv } from '../lib/secrets-client.js';
20
20
  import { machineId } from '../lib/session/sync/config.js';
21
21
  import { assertRegistrableDeviceName } from '../lib/devices/registry.js';
22
22
  import { isDeviceAuto, resolveDeviceAffinity } from '../lib/smart-launch.js';
@@ -2766,7 +2766,7 @@ async function runAskpass() {
2766
2766
  // render load/mem for an uncached password-auth device (RUSH-1970).
2767
2767
  const agentOnly = true;
2768
2768
  try {
2769
- const { env } = readAndResolveBundleEnv(bundle, { caller: 'agents ssh', keys: [key], keyMode: 'storage', agentOnly });
2769
+ const { env } = await readAndResolveBundleEnv(bundle, { caller: 'agents ssh', keys: [key], keyMode: 'storage', agentOnly });
2770
2770
  const value = env[key];
2771
2771
  if (value === undefined) {
2772
2772
  console.error(`askpass: key '${key}' not found in bundle '${bundle}'`);
@@ -33,7 +33,7 @@
33
33
  import * as path from 'path';
34
34
  import { Option } from 'commander';
35
35
  import chalk from 'chalk';
36
- import { resolveSyncPassphraseFromEnv } from '../lib/secrets/sync-passphrase.js';
36
+ import { resolveSyncPassphraseFromEnv } from '../lib/sync-passphrase.js';
37
37
  import { agentLabel, resolveAgentName, MANAGED_AGENT_IDS, isAgentHardDeprecated, hardDeprecationError } from '../lib/agents.js';
38
38
  import { autoEvictCentralBrowserProfiles } from '../lib/browser/registry.js';
39
39
  import { shouldAutoClaimCentralProfile } from '../lib/browser/profiles.js';
@@ -1,2 +1,12 @@
1
1
  import type { Command } from 'commander';
2
+ import type { AgentId } from '../lib/types.js';
3
+ /**
4
+ * One line naming the installations a bare `agents update <agent>` did NOT
5
+ * touch — isolated (`--isolated`) copies and legacy pre-v2 per-version homes —
6
+ * or `null` when the managed install is the only one. Bare `<agent>` moves only
7
+ * the single managed install, so leaving these silent would let a user believe
8
+ * every copy is current (PHNX-3940). Pure over the installation store so the
9
+ * naming logic is unit-testable without running a real update.
10
+ */
11
+ export declare function describeSkippedInstallations(agent: AgentId, managedId: string): string | null;
2
12
  export declare function registerUpdateCommand(program: Command): void;
@@ -1,7 +1,8 @@
1
1
  import chalk from 'chalk';
2
2
  import { AGENTS, formatAgentError, resolveAgentName } from '../lib/agents.js';
3
3
  import { setHelpSections } from '../lib/help.js';
4
- import { describeInstallation, effectiveUpdatePolicy, listInstallations, planAutoUpdates, resolveInstallation, runAutoUpdatePass, selectUpdateStrategy, supportsPinnedUpdate, updateInstallation, } from '../lib/installations/index.js';
4
+ import { describeInstallation, listInstallations, planAutoUpdates, resolveInstallation, runAutoUpdatePass, selectUpdateStrategy, supportsPinnedUpdate, updateInstallation, } from '../lib/installations/index.js';
5
+ import { resolveManagedInstallation } from '../lib/installations/index.js';
5
6
  /**
6
7
  * Split `<agent>[@<selector>]`. The selector names an INSTALLATION — its frozen
7
8
  * label, or the release it currently carries — never a release to install; that
@@ -20,6 +21,22 @@ function parseTarget(raw) {
20
21
  throw new Error(formatAgentError(name));
21
22
  return { agent, selector };
22
23
  }
24
+ /**
25
+ * One line naming the installations a bare `agents update <agent>` did NOT
26
+ * touch — isolated (`--isolated`) copies and legacy pre-v2 per-version homes —
27
+ * or `null` when the managed install is the only one. Bare `<agent>` moves only
28
+ * the single managed install, so leaving these silent would let a user believe
29
+ * every copy is current (PHNX-3940). Pure over the installation store so the
30
+ * naming logic is unit-testable without running a real update.
31
+ */
32
+ export function describeSkippedInstallations(agent, managedId) {
33
+ const skipped = listInstallations(agent).filter((i) => i.id !== managedId);
34
+ if (skipped.length === 0)
35
+ return null;
36
+ const names = skipped.map((i) => `${agent}@${describeInstallation(i)}`).join(', ');
37
+ return `Left ${skipped.length} other ${AGENTS[agent].name} installation${skipped.length === 1 ? '' : 's'} unchanged `
38
+ + `(${names}). Update one with: agents update ${agent}@<label>.`;
39
+ }
23
40
  function serialize(installation) {
24
41
  return {
25
42
  id: installation.id,
@@ -65,7 +82,7 @@ function printInstallations(agent, json) {
65
82
  return;
66
83
  }
67
84
  if (!installations.length) {
68
- console.log(chalk.gray(`No managed ${agent} installations. Install one with: agents add ${agent}@latest`));
85
+ console.log(chalk.gray(`No managed ${agent} installations. Install one with: agents add ${agent}`));
69
86
  return;
70
87
  }
71
88
  console.log(chalk.bold(`${agent} installations\n`));
@@ -222,35 +239,21 @@ export function registerUpdateCommand(program) {
222
239
  printOutcome(outcome, !!options.json);
223
240
  return;
224
241
  }
225
- // Bare `<agent>`, no `@label`: act on every installation of that agent
226
- // that isn't explicitly pinned — the bulk case. A single installation
227
- // keeps the old single-target behavior even if it happens to be pinned,
228
- // since naming the agent with only one installed copy is unambiguous
229
- // about which one is meant; `--to` still applies to it either way.
230
- const installations = listInstallations(agent);
231
- if (installations.length === 0) {
232
- throw new Error(`No ${AGENTS[agent].name} installations are managed by agents-cli. Install one with: agents add ${agent}@latest`);
242
+ // Bare `<agent>`, no `@label`: update the harness's ONE managed
243
+ // installation (PHNX-3940). The `@<label>` selector above remains for
244
+ // legacy multi-install layouts and isolated copies; the automatic pass
245
+ // (`--auto`, the daemon) still sweeps every installation by policy.
246
+ const managed = resolveManagedInstallation(agent);
247
+ if (!managed) {
248
+ throw new Error(`No managed ${AGENTS[agent].name} installation. Install one with: agents add ${agent}`);
233
249
  }
234
- const targets = installations.length === 1
235
- ? installations
236
- : installations.filter((i) => effectiveUpdatePolicy(i) !== 'pinned');
237
- if (installations.length > 1 && targets.length === 0) {
238
- throw new Error(`Every ${AGENTS[agent].name} installation is pinned to a concrete release. `
239
- + `Select one explicitly: agents update ${agent}@<label> --to latest`);
240
- }
241
- let anyError = false;
242
- for (const installation of targets) {
243
- try {
244
- const outcome = await updateOne(agent, installation, options);
245
- printOutcome(outcome, !!options.json);
246
- }
247
- catch (err) {
248
- anyError = true;
249
- fail(err);
250
- }
250
+ const outcome = await updateOne(agent, managed, options);
251
+ printOutcome(outcome, !!options.json);
252
+ if (!options.json) {
253
+ const skippedLine = describeSkippedInstallations(agent, managed.id);
254
+ if (skippedLine)
255
+ console.log(chalk.gray(skippedLine));
251
256
  }
252
- if (anyError)
253
- process.exitCode = 1;
254
257
  }
255
258
  catch (err) {
256
259
  fail(err);
@@ -275,21 +278,21 @@ export function registerUpdateCommand(program) {
275
278
  }
276
279
  });
277
280
  setHelpSections(update, {
278
- examples: `agents update list claude
279
- agents update claude@2.0.65
280
- agents update claude@2.0.65 --to 2.1.220
281
- agents update claude@2.0.65 --to latest
282
- agents update claude
281
+ examples: `agents update claude
282
+ agents update claude --to 2.1.220
283
+ agents update claude --to latest
284
+ agents update list claude
283
285
  agents update --check
284
286
  agents update claude --check --json
285
287
  agents update --auto`,
286
- notes: 'An installation keeps its name for life; only the release inside it moves. That is why a default, a project pin, '
287
- + 'a routine version, or a profile that names claude@2.0.65 keeps working after you update it. '
288
- + 'The selector matches either the installation name or the release it currently carries — when two installations '
289
- + 'share a release, select one by its installation name. '
288
+ notes: 'One managed installation per harness: a bare `<agent>` updates that installation, keeping its name and every '
289
+ + 'reference to it. That is why a default, a project pin, a routine version, or a profile that names claude@main '
290
+ + 'keeps working after you update it. '
291
+ + '`--to <release>` pins the installation to a concrete release (excluded from the automatic pass), `--to latest` '
292
+ + 'unpins it. The legacy `agents update <agent>@<label>` selector still addresses a specific installation by its '
293
+ + 'name or the release it currently carries, for pre-v2 layouts with several installations. '
290
294
  + 'The new release is fetched and launched before it replaces the working one, so a bad release leaves your agent running. '
291
- + 'A bare `<agent>` (no @label) updates every one of its installations that is not pinned; `--to <release>` pins the '
292
- + 'installations it touches, `--to latest` unpins them. Pinned and manual/vendor-managed installations are also skipped '
295
+ + 'Pinned and manual/vendor-managed installations are also skipped '
293
296
  + 'by the automatic background pass (agents config set updates.auto / updates.<agent>.auto). `--check` reports what '
294
297
  + '`--auto` would do without changing anything; `--auto` (no target) runs the exact pass the daemon runs, across every '
295
298
  + 'harness.',
@@ -7,16 +7,16 @@
7
7
  * switching, resource sync prompts, and project-level version pinning.
8
8
  */
9
9
  import type { Command } from 'commander';
10
- /** Bare installs reuse an account home; explicit labels keep their expert semantics. */
11
- export declare function planAccountFirstInstall(input: {
12
- spec: string;
13
- supported: boolean;
14
- isolated: boolean;
15
- labels: string[];
16
- defaultLabel: string | null;
17
- }): {
18
- existingLabel?: string;
19
- installationLabel?: string;
20
- };
10
+ /**
11
+ * What `agents add <spec>` does with the harness's ONE managed installation
12
+ * (PHNX-3940): with no managed install it installs one (label `main`); with
13
+ * one, a bare spec reuses it and an explicit `@<release>` pins that same
14
+ * install — exactly `agents update <agent> --to <release>`. A second home is
15
+ * only ever an `--isolated` copy, which bypasses this plan entirely.
16
+ */
17
+ export declare function planManagedAdd(input: {
18
+ explicitPin: boolean;
19
+ managedInstalled: boolean;
20
+ }): 'reuse' | 'pin' | 'install';
21
21
  /** Register `agents add`, `agents prune`, `agents remove`, and `agents use`. */
22
22
  export declare function registerVersionsCommands(program: Command): void;