@phnx-labs/agents-cli 1.22.84 → 1.22.86

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (259) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +80 -53
  3. package/dist/bootstrap.d.ts +7 -6
  4. package/dist/bootstrap.js +20 -27
  5. package/dist/cli/command-registry.js +5 -1
  6. package/dist/commands/accounts.d.ts +6 -13
  7. package/dist/commands/accounts.js +330 -143
  8. package/dist/commands/alias.js +7 -3
  9. package/dist/commands/apply.js +1 -1
  10. package/dist/commands/auth-mint.d.ts +7 -3
  11. package/dist/commands/auth-mint.js +21 -9
  12. package/dist/commands/auth.js +2 -3
  13. package/dist/commands/browser.js +10 -10
  14. package/dist/commands/daemon.d.ts +7 -3
  15. package/dist/commands/daemon.js +40 -25
  16. package/dist/commands/doctor.js +28 -2
  17. package/dist/commands/exec.js +87 -39
  18. package/dist/commands/fleet-capture.js +21 -2
  19. package/dist/commands/harness-wizard.js +2 -2
  20. package/dist/commands/lease.js +7 -12
  21. package/dist/commands/profiles.d.ts +2 -2
  22. package/dist/commands/profiles.js +20 -13
  23. package/dist/commands/repo.js +3 -3
  24. package/dist/commands/run-account-picker.d.ts +3 -3
  25. package/dist/commands/run-account-picker.js +29 -30
  26. package/dist/commands/secrets-passthrough.d.ts +20 -0
  27. package/dist/commands/secrets-passthrough.js +43 -0
  28. package/dist/commands/setup-accounts.d.ts +3 -2
  29. package/dist/commands/setup-accounts.js +4 -4
  30. package/dist/commands/setup-secrets.d.ts +16 -21
  31. package/dist/commands/setup-secrets.js +43 -224
  32. package/dist/commands/ssh.js +2 -2
  33. package/dist/commands/sync.js +1 -1
  34. package/dist/commands/update.d.ts +10 -0
  35. package/dist/commands/update.js +43 -40
  36. package/dist/commands/versions.d.ts +11 -11
  37. package/dist/commands/versions.js +131 -83
  38. package/dist/commands/view.d.ts +1 -9
  39. package/dist/commands/view.js +41 -55
  40. package/dist/commands/webhook.js +5 -5
  41. package/dist/commands/workflows.js +2 -1
  42. package/dist/index.d.ts +12 -12
  43. package/dist/index.js +13 -42
  44. package/dist/lib/account-capabilities.d.ts +1 -1
  45. package/dist/lib/account-capabilities.js +1 -1
  46. package/dist/lib/account-catalog.d.ts +121 -6
  47. package/dist/lib/account-catalog.js +421 -7
  48. package/dist/lib/account-registry.d.ts +8 -30
  49. package/dist/lib/account-registry.js +36 -79
  50. package/dist/lib/account-schema.d.ts +1 -1
  51. package/dist/lib/account-schema.js +1 -1
  52. package/dist/lib/accounting/account-pool-collect.js +16 -4
  53. package/dist/lib/accounting/rotate.d.ts +20 -7
  54. package/dist/lib/accounting/rotate.js +89 -13
  55. package/dist/lib/accounting/usage.js +11 -16
  56. package/dist/lib/accounts/add.d.ts +138 -0
  57. package/dist/lib/accounts/add.js +651 -0
  58. package/dist/lib/accounts/migrate.d.ts +117 -0
  59. package/dist/lib/accounts/migrate.js +536 -0
  60. package/dist/lib/accounts/slots.d.ts +13 -0
  61. package/dist/lib/accounts/slots.js +100 -0
  62. package/dist/lib/agent-spec/agents.d.ts +16 -0
  63. package/dist/lib/agent-spec/agents.js +38 -4
  64. package/dist/lib/app-bundle-install.js +5 -4
  65. package/dist/lib/auth-health.d.ts +3 -0
  66. package/dist/lib/auth-health.js +11 -0
  67. package/dist/lib/auth-mint.d.ts +52 -16
  68. package/dist/lib/auth-mint.js +117 -39
  69. package/dist/lib/browser/chrome.d.ts +1 -1
  70. package/dist/lib/browser/chrome.js +5 -5
  71. package/dist/lib/byok-usage.js +3 -3
  72. package/dist/lib/claude-account-token.d.ts +47 -3
  73. package/dist/lib/claude-account-token.js +172 -50
  74. package/dist/lib/cloud/antigravity.js +4 -4
  75. package/dist/lib/cloud/cursor.js +4 -4
  76. package/dist/lib/crabbox/cli.d.ts +1 -1
  77. package/dist/lib/crabbox/cli.js +6 -6
  78. package/dist/lib/crabbox/runtimes.d.ts +3 -3
  79. package/dist/lib/crabbox/runtimes.js +5 -5
  80. package/dist/lib/daemon/account-state-daemon-service.d.ts +37 -1
  81. package/dist/lib/daemon/account-state-daemon-service.js +199 -3
  82. package/dist/lib/daemon/auth-sync-service.js +32 -1
  83. package/dist/lib/daemon/daemon.d.ts +6 -16
  84. package/dist/lib/daemon/daemon.js +52 -97
  85. package/dist/lib/daemon/daemon.test-fixture.d.ts +7 -9
  86. package/dist/lib/daemon/daemon.test-fixture.js +22 -40
  87. package/dist/lib/daemon/harness-update-service.d.ts +1 -1
  88. package/dist/lib/daemon/harness-update-service.js +1 -1
  89. package/dist/lib/daemon/runner.d.ts +15 -3
  90. package/dist/lib/daemon/runner.js +49 -24
  91. package/dist/lib/daemon-health.d.ts +1 -2
  92. package/dist/lib/daemon-health.js +7 -6
  93. package/dist/lib/daemon-services.d.ts +1 -1
  94. package/dist/lib/daemon-services.js +1 -11
  95. package/dist/lib/daemon-webhooks.d.ts +8 -7
  96. package/dist/lib/daemon-webhooks.js +10 -9
  97. package/dist/lib/device-config.js +1 -1
  98. package/dist/lib/devices/doctor-findings.d.ts +1 -1
  99. package/dist/lib/devices/harness-inventory.d.ts +39 -0
  100. package/dist/lib/devices/harness-inventory.js +126 -4
  101. package/dist/lib/doctor-diff.js +2 -1
  102. package/dist/lib/exec-account-home.d.ts +38 -0
  103. package/dist/lib/exec-account-home.js +164 -0
  104. package/dist/lib/exec.d.ts +30 -0
  105. package/dist/lib/exec.js +101 -31
  106. package/dist/lib/fleet/apply.d.ts +2 -2
  107. package/dist/lib/fleet/apply.js +4 -4
  108. package/dist/lib/fleet/auth-sync.js +4 -3
  109. package/dist/lib/fleet-shared-repo-sync.js +6 -9
  110. package/dist/lib/harness/adapter.d.ts +7 -7
  111. package/dist/lib/harness/adapter.js +10 -6
  112. package/dist/lib/harness/adapters/grok.js +8 -3
  113. package/dist/lib/harness/adapters/muse.js +1 -1
  114. package/dist/lib/harness/adapters/opencode.js +12 -3
  115. package/dist/lib/harness-auth-capabilities.d.ts +61 -0
  116. package/dist/lib/harness-auth-capabilities.js +52 -0
  117. package/dist/lib/helper-versions.d.ts +6 -4
  118. package/dist/lib/helper-versions.js +5 -4
  119. package/dist/lib/hosts/credential-transport.d.ts +10 -0
  120. package/dist/lib/hosts/credential-transport.js +37 -0
  121. package/dist/lib/hosts/dispatch.d.ts +7 -0
  122. package/dist/lib/hosts/dispatch.js +13 -6
  123. package/dist/lib/identity/client.d.ts +3 -3
  124. package/dist/lib/identity/client.js +3 -3
  125. package/dist/lib/installations/index.d.ts +1 -1
  126. package/dist/lib/installations/index.js +1 -1
  127. package/dist/lib/installations/migrate.js +9 -0
  128. package/dist/lib/installations/resolve.js +1 -1
  129. package/dist/lib/installations/shims.d.ts +21 -4
  130. package/dist/lib/installations/shims.js +86 -5
  131. package/dist/lib/installations/store.d.ts +43 -0
  132. package/dist/lib/installations/store.js +75 -7
  133. package/dist/lib/installations/versions.js +1 -1
  134. package/dist/lib/menubar/install-menubar.d.ts +3 -3
  135. package/dist/lib/menubar/install-menubar.js +3 -3
  136. package/dist/lib/native-accounts.d.ts +35 -0
  137. package/dist/lib/native-accounts.js +41 -0
  138. package/dist/lib/net-close.d.ts +11 -0
  139. package/dist/lib/net-close.js +25 -0
  140. package/dist/lib/openclaw-keychain.js +27 -1
  141. package/dist/lib/profiles.js +22 -9
  142. package/dist/lib/project-resources.js +1 -1
  143. package/dist/lib/reserved-stores.d.ts +53 -0
  144. package/dist/lib/reserved-stores.js +120 -0
  145. package/dist/lib/secrets-client.d.ts +199 -0
  146. package/dist/lib/secrets-client.js +718 -0
  147. package/dist/lib/secrets-policy.d.ts +235 -0
  148. package/dist/lib/secrets-policy.js +505 -0
  149. package/dist/lib/secrets-types.d.ts +188 -0
  150. package/dist/lib/secrets-types.js +24 -0
  151. package/dist/lib/self-heal/checks/shims.js +5 -3
  152. package/dist/lib/service-manifest.js +3 -4
  153. package/dist/lib/session/db.d.ts +18 -0
  154. package/dist/lib/session/db.js +47 -14
  155. package/dist/lib/session/sync/config.js +2 -2
  156. package/dist/lib/sha256-asset.d.ts +11 -16
  157. package/dist/lib/sha256-asset.js +11 -16
  158. package/dist/lib/share/config.js +14 -5
  159. package/dist/lib/signin-badge.d.ts +26 -0
  160. package/dist/lib/signin-badge.js +42 -0
  161. package/dist/lib/staleness/detectors/workflows.js +3 -105
  162. package/dist/lib/staleness/writers/workflows.js +2 -1
  163. package/dist/lib/state.d.ts +7 -4
  164. package/dist/lib/state.js +18 -7
  165. package/dist/lib/sync-umbrella.js +2 -2
  166. package/dist/lib/teams/agents.js +1 -1
  167. package/dist/lib/types.d.ts +74 -33
  168. package/dist/lib/view-types.d.ts +6 -3
  169. package/dist/lib/workflows-registry.d.ts +71 -0
  170. package/dist/lib/workflows-registry.js +280 -0
  171. package/dist/lib/workflows.d.ts +21 -21
  172. package/dist/lib/workflows.js +12 -327
  173. package/package.json +2 -3
  174. package/scripts/postinstall.js +35 -30
  175. package/dist/commands/secrets-import.d.ts +0 -18
  176. package/dist/commands/secrets-import.js +0 -74
  177. package/dist/commands/secrets-migrate.d.ts +0 -25
  178. package/dist/commands/secrets-migrate.js +0 -334
  179. package/dist/commands/secrets-rotate-passphrase.d.ts +0 -17
  180. package/dist/commands/secrets-rotate-passphrase.js +0 -96
  181. package/dist/commands/secrets-sync.d.ts +0 -11
  182. package/dist/commands/secrets-sync.js +0 -153
  183. package/dist/commands/secrets-vault.d.ts +0 -10
  184. package/dist/commands/secrets-vault.js +0 -130
  185. package/dist/commands/secrets.d.ts +0 -212
  186. package/dist/commands/secrets.js +0 -3030
  187. package/dist/lib/accounts/connect.d.ts +0 -176
  188. package/dist/lib/accounts/connect.js +0 -453
  189. package/dist/lib/daemon/keychain-reap-service.d.ts +0 -17
  190. package/dist/lib/daemon/keychain-reap-service.js +0 -32
  191. package/dist/lib/daemon/secrets-broker-service.d.ts +0 -21
  192. package/dist/lib/daemon/secrets-broker-service.js +0 -51
  193. package/dist/lib/secrets/agent.d.ts +0 -358
  194. package/dist/lib/secrets/agent.js +0 -1291
  195. package/dist/lib/secrets/audit.d.ts +0 -46
  196. package/dist/lib/secrets/audit.js +0 -101
  197. package/dist/lib/secrets/bundles.d.ts +0 -290
  198. package/dist/lib/secrets/bundles.js +0 -1547
  199. package/dist/lib/secrets/download-keychain.d.ts +0 -47
  200. package/dist/lib/secrets/download-keychain.js +0 -70
  201. package/dist/lib/secrets/drivers/rush.d.ts +0 -14
  202. package/dist/lib/secrets/drivers/rush.js +0 -90
  203. package/dist/lib/secrets/fallback.d.ts +0 -48
  204. package/dist/lib/secrets/fallback.js +0 -48
  205. package/dist/lib/secrets/filestore.d.ts +0 -222
  206. package/dist/lib/secrets/filestore.js +0 -1099
  207. package/dist/lib/secrets/headless.d.ts +0 -43
  208. package/dist/lib/secrets/headless.js +0 -56
  209. package/dist/lib/secrets/icloud-import.d.ts +0 -79
  210. package/dist/lib/secrets/icloud-import.js +0 -206
  211. package/dist/lib/secrets/index.d.ts +0 -451
  212. package/dist/lib/secrets/index.js +0 -1568
  213. package/dist/lib/secrets/install-helper.d.ts +0 -72
  214. package/dist/lib/secrets/install-helper.js +0 -245
  215. package/dist/lib/secrets/lease.d.ts +0 -25
  216. package/dist/lib/secrets/lease.js +0 -44
  217. package/dist/lib/secrets/linux.d.ts +0 -77
  218. package/dist/lib/secrets/linux.js +0 -393
  219. package/dist/lib/secrets/list-filter.d.ts +0 -109
  220. package/dist/lib/secrets/list-filter.js +0 -261
  221. package/dist/lib/secrets/mcp.d.ts +0 -93
  222. package/dist/lib/secrets/mcp.js +0 -211
  223. package/dist/lib/secrets/profiles.d.ts +0 -10
  224. package/dist/lib/secrets/profiles.js +0 -13
  225. package/dist/lib/secrets/push.d.ts +0 -133
  226. package/dist/lib/secrets/push.js +0 -273
  227. package/dist/lib/secrets/rc-hygiene.d.ts +0 -63
  228. package/dist/lib/secrets/rc-hygiene.js +0 -143
  229. package/dist/lib/secrets/read-backoff.d.ts +0 -27
  230. package/dist/lib/secrets/read-backoff.js +0 -64
  231. package/dist/lib/secrets/reaper.d.ts +0 -90
  232. package/dist/lib/secrets/reaper.js +0 -243
  233. package/dist/lib/secrets/remote.d.ts +0 -152
  234. package/dist/lib/secrets/remote.js +0 -344
  235. package/dist/lib/secrets/reserved-sync.d.ts +0 -66
  236. package/dist/lib/secrets/reserved-sync.js +0 -147
  237. package/dist/lib/secrets/scope.d.ts +0 -26
  238. package/dist/lib/secrets/scope.js +0 -29
  239. package/dist/lib/secrets/session-store.d.ts +0 -107
  240. package/dist/lib/secrets/session-store.js +0 -342
  241. package/dist/lib/secrets/sync-backend.d.ts +0 -48
  242. package/dist/lib/secrets/sync-backend.js +0 -13
  243. package/dist/lib/secrets/sync-commands.d.ts +0 -21
  244. package/dist/lib/secrets/sync-commands.js +0 -21
  245. package/dist/lib/secrets/sync.d.ts +0 -48
  246. package/dist/lib/secrets/sync.js +0 -237
  247. package/dist/lib/secrets/unlock-hints.d.ts +0 -27
  248. package/dist/lib/secrets/unlock-hints.js +0 -36
  249. package/dist/lib/secrets/usage-db.d.ts +0 -46
  250. package/dist/lib/secrets/usage-db.js +0 -96
  251. package/dist/lib/secrets/vault-age-helper.d.ts +0 -1
  252. package/dist/lib/secrets/vault-age-helper.js +0 -34
  253. package/dist/lib/secrets/vault.d.ts +0 -49
  254. package/dist/lib/secrets/vault.js +0 -399
  255. package/dist/lib/secrets/windows.d.ts +0 -81
  256. package/dist/lib/secrets/windows.js +0 -556
  257. package/scripts/install-helper.js +0 -97
  258. /package/dist/lib/{secrets/sync-passphrase.d.ts → sync-passphrase.d.ts} +0 -0
  259. /package/dist/lib/{secrets/sync-passphrase.js → sync-passphrase.js} +0 -0
@@ -1,72 +0,0 @@
1
- /**
2
- * Stable install location for the signed macOS Keychain helper.
3
- *
4
- * Why a stable path: every npm publish re-signs `Agents CLI.app` with a fresh
5
- * timestamp, producing a new code signature. macOS Keychain trusted-app ACLs
6
- * are pinned to the exact signature, so the ACL invalidates on every release
7
- * when the helper lives inside the npm package directory. Copying the .app
8
- * once to `~/Library/Application Support/agents-cli/` gives it a path that
9
- * survives `npm i -g`, `scripts/install.sh`, version bumps, etc.
10
- *
11
- * This module is the SINGLE SOURCE OF TRUTH for the helper path. Other
12
- * modules in `src/lib/secrets/` must import `getKeychainHelperPath()` rather
13
- * than recomputing it.
14
- */
15
- /** Redirect the install root (test only). Returns the previous override so callers can restore. */
16
- export declare function setInstallRootForTest(dir: string | null): string | null;
17
- /**
18
- * Idempotent install. Copies the bundled `.app` to the stable user path. Skips
19
- * if the destination already exists, `codesign --verify` passes, AND the
20
- * installed executable matches the bundled one byte-for-byte — a valid
21
- * signature alone is not enough, because an outdated helper signs clean too.
22
- * `forceReinstall=true` skips all checks and always copies.
23
- *
24
- * Notarization is checked via `spctl --assess` after install — a failure is
25
- * logged as a warning but does NOT throw. Notarization checks require network
26
- * access (Gatekeeper ticket lookup) and are not load-bearing for the helper's
27
- * keychain ACL semantics.
28
- */
29
- export declare function ensureKeychainHelperInstalled(opts?: {
30
- forceReinstall?: boolean;
31
- }): void;
32
- /**
33
- * ASYNC, download-capable install — the EXPLICIT onboarding/upgrade path
34
- * (`agents setup secrets`, the bootstrap upgrade self-heal). When a bundled or
35
- * locally-built `.app` ships (the common case, and always true this release
36
- * stage), it installs that copy with NO network. When the tarball lacks the
37
- * bundle (a fresh `npm i -g` whose tarball dropped it), it fetches the signed +
38
- * notarized release asset for the running CLI version — sha256 + codesign +
39
- * Team + notarization verified before install — and installs from the cached
40
- * copy. The sync `getKeychainHelperPath()` never routes here: it stays sync +
41
- * network-free and fails loud toward this command when no source exists.
42
- */
43
- export declare function ensureKeychainHelperInstalledAsync(opts?: {
44
- forceReinstall?: boolean;
45
- }): Promise<void>;
46
- /**
47
- * Return the absolute path to the helper executable. If the installed bundle
48
- * is missing, or is stale relative to the bundled source helper, performs a
49
- * lazy (re)install first. The staleness check is what lets an upgraded CLI
50
- * replace a helper a previous version installed — the install never runs on
51
- * `npm i -g`, so this call site is the only one every machine is guaranteed to
52
- * pass through.
53
- *
54
- * SYNC and network-free by design: this is the secrets hot path, called by
55
- * every keychain op. It never downloads. When no bundled/local source exists
56
- * (a tarball that dropped the bundle), the reinstall's `sourceAppPath()` throws
57
- * loud, directing the user to `agents setup secrets` — the explicit, async,
58
- * download-capable path (`ensureKeychainHelperInstalledAsync`). Throws on
59
- * non-darwin.
60
- */
61
- export declare function getKeychainHelperPath(): string;
62
- /** Diagnostic snapshot of the installed Keychain helper. */
63
- export interface KeychainHelperStatus {
64
- source: string | null;
65
- destination: string;
66
- installed: boolean;
67
- codesignOk: boolean;
68
- codesignOutput: string;
69
- spctlOk: boolean;
70
- spctlOutput: string;
71
- }
72
- export declare function getKeychainHelperStatus(): KeychainHelperStatus;
@@ -1,245 +0,0 @@
1
- /**
2
- * Stable install location for the signed macOS Keychain helper.
3
- *
4
- * Why a stable path: every npm publish re-signs `Agents CLI.app` with a fresh
5
- * timestamp, producing a new code signature. macOS Keychain trusted-app ACLs
6
- * are pinned to the exact signature, so the ACL invalidates on every release
7
- * when the helper lives inside the npm package directory. Copying the .app
8
- * once to `~/Library/Application Support/agents-cli/` gives it a path that
9
- * survives `npm i -g`, `scripts/install.sh`, version bumps, etc.
10
- *
11
- * This module is the SINGLE SOURCE OF TRUTH for the helper path. Other
12
- * modules in `src/lib/secrets/` must import `getKeychainHelperPath()` rather
13
- * than recomputing it.
14
- */
15
- import { fileURLToPath } from 'url';
16
- import { spawnSync } from 'child_process';
17
- import { createHash } from 'crypto';
18
- import * as fs from 'fs';
19
- import * as os from 'os';
20
- import * as path from 'path';
21
- import { copyAppBundle, withInstallLock } from '../app-bundle-install.js';
22
- import { downloadKeychainHelperApp } from './download-keychain.js';
23
- import { helperFloor } from '../helper-versions.js';
24
- const APP_BUNDLE_NAME = 'Agents CLI.app';
25
- const INSTALL_DIR_NAME = 'agents-cli';
26
- let installRootOverride = null;
27
- /** Redirect the install root (test only). Returns the previous override so callers can restore. */
28
- export function setInstallRootForTest(dir) {
29
- const prev = installRootOverride;
30
- installRootOverride = dir;
31
- return prev;
32
- }
33
- /** Absolute path to the installed `.app` bundle directory (not the executable). */
34
- function installedAppPath() {
35
- const root = installRootOverride ?? os.homedir();
36
- return path.join(root, 'Library', 'Application Support', INSTALL_DIR_NAME, APP_BUNDLE_NAME);
37
- }
38
- /** Absolute path to the executable inside the installed `.app` bundle. */
39
- function installedExecutablePath() {
40
- return path.join(installedAppPath(), 'Contents', 'MacOS', 'Agents CLI');
41
- }
42
- /**
43
- * Locate the source `.app` bundle shipped alongside the compiled JS.
44
- *
45
- * Resolution order:
46
- * 1. dist/lib/secrets/Agents CLI.app — sibling of this compiled file (npm install layout)
47
- * 2. <repo>/bin/Agents CLI.app — raw working tree (`bun run dev`, tsx from src/)
48
- *
49
- * Throws if neither exists.
50
- */
51
- function sourceAppPath() {
52
- const candidates = [];
53
- try {
54
- const here = path.dirname(fileURLToPath(import.meta.url));
55
- candidates.push(path.join(here, APP_BUNDLE_NAME));
56
- // tsx/src case: src/lib/secrets/install-helper.ts -> ../../../bin/Agents CLI.app
57
- candidates.push(path.resolve(here, '..', '..', '..', 'bin', APP_BUNDLE_NAME));
58
- }
59
- catch { /* import.meta.url unavailable */ }
60
- for (const c of candidates) {
61
- if (fs.existsSync(c))
62
- return c;
63
- }
64
- throw new Error(`Source ${APP_BUNDLE_NAME} not found. Looked in:\n ${candidates.join('\n ')}\n` +
65
- 'The npm package shipped without the bundled keychain helper. Run `agents setup secrets` to ' +
66
- 'download the signed helper for this version, or reinstall agents-cli.');
67
- }
68
- /** The bundled/local source `.app`, or null when absent (no throw) — the async
69
- * download path resolves the missing bundle instead of failing. */
70
- function bundledSourceAppPathOrNull() {
71
- try {
72
- return sourceAppPath();
73
- }
74
- catch {
75
- return null;
76
- }
77
- }
78
- function assertDarwin() {
79
- if (process.platform !== 'darwin') {
80
- throw new Error('Keychain helper is macOS only.');
81
- }
82
- }
83
- function sha256File(filePath) {
84
- return createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
85
- }
86
- /**
87
- * True when the installed helper executable differs byte-for-byte from the
88
- * bundled source helper. A stale-but-validly-signed helper is exactly how the
89
- * broken 1.20.4 build (signed, but missing the keychain-access-groups
90
- * entitlement) survived upgrades: codesign --verify passes on it, so the
91
- * "exists and verifies" early-return kept it installed forever. Returns false
92
- * when there is no bundled source to compare against (dev installs) or no
93
- * installed copy yet — both cases are decided by the existence checks in the
94
- * callers, not by staleness.
95
- */
96
- function installedHelperIsStale() {
97
- let srcApp;
98
- try {
99
- srcApp = sourceAppPath();
100
- }
101
- catch {
102
- return false;
103
- }
104
- const srcExec = path.join(srcApp, 'Contents', 'MacOS', 'Agents CLI');
105
- const destExec = installedExecutablePath();
106
- if (!fs.existsSync(srcExec) || !fs.existsSync(destExec))
107
- return false;
108
- return sha256File(srcExec) !== sha256File(destExec);
109
- }
110
- function codesignVerify(appPath) {
111
- const r = spawnSync('codesign', ['--verify', '--deep', '--strict', appPath], {
112
- stdio: ['ignore', 'pipe', 'pipe'],
113
- encoding: 'utf-8',
114
- });
115
- return { ok: r.status === 0, output: (r.stderr || r.stdout || '').toString().trim() };
116
- }
117
- function spctlAssess(appPath) {
118
- const r = spawnSync('spctl', ['--assess', '--type', 'execute', '--verbose=2', appPath], {
119
- stdio: ['ignore', 'pipe', 'pipe'],
120
- encoding: 'utf-8',
121
- });
122
- return { ok: r.status === 0, output: (r.stderr || r.stdout || '').toString().trim() };
123
- }
124
- /**
125
- * Idempotent install. Copies the bundled `.app` to the stable user path. Skips
126
- * if the destination already exists, `codesign --verify` passes, AND the
127
- * installed executable matches the bundled one byte-for-byte — a valid
128
- * signature alone is not enough, because an outdated helper signs clean too.
129
- * `forceReinstall=true` skips all checks and always copies.
130
- *
131
- * Notarization is checked via `spctl --assess` after install — a failure is
132
- * logged as a warning but does NOT throw. Notarization checks require network
133
- * access (Gatekeeper ticket lookup) and are not load-bearing for the helper's
134
- * keychain ACL semantics.
135
- */
136
- export function ensureKeychainHelperInstalled(opts = {}) {
137
- assertDarwin();
138
- installKeychainHelperFromSource(sourceAppPath(), opts);
139
- }
140
- /**
141
- * ASYNC, download-capable install — the EXPLICIT onboarding/upgrade path
142
- * (`agents setup secrets`, the bootstrap upgrade self-heal). When a bundled or
143
- * locally-built `.app` ships (the common case, and always true this release
144
- * stage), it installs that copy with NO network. When the tarball lacks the
145
- * bundle (a fresh `npm i -g` whose tarball dropped it), it fetches the signed +
146
- * notarized release asset for the running CLI version — sha256 + codesign +
147
- * Team + notarization verified before install — and installs from the cached
148
- * copy. The sync `getKeychainHelperPath()` never routes here: it stays sync +
149
- * network-free and fails loud toward this command when no source exists.
150
- */
151
- export async function ensureKeychainHelperInstalledAsync(opts = {}) {
152
- assertDarwin();
153
- const bundled = bundledSourceAppPathOrNull();
154
- const src = bundled ?? (await downloadKeychainHelperApp(helperFloor('keychain')));
155
- installKeychainHelperFromSource(src, opts);
156
- }
157
- /**
158
- * Copy `src` (a verified `.app` bundle) to the stable user path, then verify.
159
- * Shared by the sync (bundled-source) and async (download-capable) installers.
160
- */
161
- function installKeychainHelperFromSource(src, opts = {}) {
162
- const dest = installedAppPath();
163
- const upToDate = () => fs.existsSync(dest) && codesignVerify(dest).ok && !installedHelperIsStale();
164
- // Fast path: already installed, valid, and current — no lock, no copy.
165
- if (!opts.forceReinstall && upToDate())
166
- return;
167
- // Serialize the install so concurrent `agents` invocations don't race the
168
- // atomic swap (and don't each run a redundant `cp`). The re-check inside the
169
- // lock means a burst of callers copies once, not N times — the stampede that
170
- // transiently corrupted the bundle and tripped the "damaged" dialog.
171
- withInstallLock(dest, (heartbeat) => {
172
- if (!opts.forceReinstall && upToDate())
173
- return;
174
- copyAppBundle(src, dest);
175
- heartbeat(); // cp -R done; keep the lock fresh across the codesign/spctl spawns
176
- const verify = codesignVerify(dest);
177
- if (!verify.ok) {
178
- throw new Error(`Installed helper failed codesign verification at ${dest}.\n${verify.output}\n` +
179
- 'The bundle may be corrupted. Reinstall agents-cli to restore it; the helper reinstalls automatically on the next secrets use.');
180
- }
181
- heartbeat(); // codesign done; spctl does a network Gatekeeper lookup — refresh again
182
- const assess = spctlAssess(dest);
183
- if (!assess.ok) {
184
- // Warn, do not fail. Gatekeeper ticket lookup needs network; offline
185
- // installs and CI runners commonly fail this check. The ACL semantics
186
- // we care about depend on codesign, not spctl.
187
- process.stderr.write(`agents-cli: notarization check (spctl) did not pass for ${dest}: ${assess.output}\n`);
188
- }
189
- });
190
- }
191
- /**
192
- * Return the absolute path to the helper executable. If the installed bundle
193
- * is missing, or is stale relative to the bundled source helper, performs a
194
- * lazy (re)install first. The staleness check is what lets an upgraded CLI
195
- * replace a helper a previous version installed — the install never runs on
196
- * `npm i -g`, so this call site is the only one every machine is guaranteed to
197
- * pass through.
198
- *
199
- * SYNC and network-free by design: this is the secrets hot path, called by
200
- * every keychain op. It never downloads. When no bundled/local source exists
201
- * (a tarball that dropped the bundle), the reinstall's `sourceAppPath()` throws
202
- * loud, directing the user to `agents setup secrets` — the explicit, async,
203
- * download-capable path (`ensureKeychainHelperInstalledAsync`). Throws on
204
- * non-darwin.
205
- */
206
- export function getKeychainHelperPath() {
207
- assertDarwin();
208
- const exec = installedExecutablePath();
209
- if (!fs.existsSync(exec) || installedHelperIsStale()) {
210
- ensureKeychainHelperInstalled();
211
- }
212
- return exec;
213
- }
214
- export function getKeychainHelperStatus() {
215
- assertDarwin();
216
- const destApp = installedAppPath();
217
- let src = null;
218
- try {
219
- src = sourceAppPath();
220
- }
221
- catch { /* missing source is reported as null */ }
222
- const installed = fs.existsSync(destApp);
223
- if (!installed) {
224
- return {
225
- source: src,
226
- destination: destApp,
227
- installed: false,
228
- codesignOk: false,
229
- codesignOutput: 'not installed',
230
- spctlOk: false,
231
- spctlOutput: 'not installed',
232
- };
233
- }
234
- const cs = codesignVerify(destApp);
235
- const sp = spctlAssess(destApp);
236
- return {
237
- source: src,
238
- destination: destApp,
239
- installed: true,
240
- codesignOk: cs.ok,
241
- codesignOutput: cs.output || 'ok',
242
- spctlOk: sp.ok,
243
- spctlOutput: sp.output || 'ok',
244
- };
245
- }
@@ -1,25 +0,0 @@
1
- export declare const MIN_LEASE_MS: number;
2
- export declare const MAX_LEASE_MS: number;
3
- export interface SecretLease {
4
- id: string;
5
- bundle: string;
6
- keys: string[];
7
- createdAt: number;
8
- expiresAt: number;
9
- harness: string;
10
- sleepPersist: boolean;
11
- }
12
- export declare function clampLeaseTtlMs(ttlMs: number): number;
13
- export declare function normalizeLeaseKeys(keys: readonly string[], availableKeys: readonly string[]): string[];
14
- export declare function createSecretLease(input: {
15
- bundle: string;
16
- keys: readonly string[];
17
- availableKeys: readonly string[];
18
- ttlMs: number;
19
- now?: number;
20
- id?: string;
21
- harness?: string;
22
- sleepPersist?: boolean;
23
- }): SecretLease;
24
- export declare function leaseIsActive(lease: SecretLease, now?: number): boolean;
25
- export declare function selectLeasedEnv(lease: SecretLease, env: Record<string, string>, now?: number): Record<string, string>;
@@ -1,44 +0,0 @@
1
- import { randomBytes } from 'crypto';
2
- import { GLOBAL_HARNESS } from './scope.js';
3
- export const MIN_LEASE_MS = 60 * 1000;
4
- export const MAX_LEASE_MS = 30 * 24 * 60 * 60 * 1000;
5
- export function clampLeaseTtlMs(ttlMs) {
6
- if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
7
- throw new Error('Lease duration must be a positive finite number.');
8
- }
9
- return Math.min(Math.max(Math.floor(ttlMs), MIN_LEASE_MS), MAX_LEASE_MS);
10
- }
11
- export function normalizeLeaseKeys(keys, availableKeys) {
12
- const requested = [...new Set(keys.map((key) => key.trim()).filter(Boolean))].sort();
13
- if (requested.length === 0)
14
- throw new Error('A secret lease requires at least one key.');
15
- const available = new Set(availableKeys);
16
- const missing = requested.filter((key) => !available.has(key));
17
- if (missing.length > 0)
18
- throw new Error(`Unknown secret lease key(s): ${missing.join(', ')}`);
19
- return requested;
20
- }
21
- export function createSecretLease(input) {
22
- const createdAt = input.now ?? Date.now();
23
- const ttlMs = clampLeaseTtlMs(input.ttlMs);
24
- return {
25
- id: input.id ?? randomBytes(12).toString('hex'),
26
- bundle: input.bundle,
27
- keys: normalizeLeaseKeys(input.keys, input.availableKeys),
28
- createdAt,
29
- expiresAt: createdAt + ttlMs,
30
- harness: input.harness ?? GLOBAL_HARNESS,
31
- sleepPersist: input.sleepPersist ?? false,
32
- };
33
- }
34
- export function leaseIsActive(lease, now = Date.now()) {
35
- return now < lease.expiresAt;
36
- }
37
- export function selectLeasedEnv(lease, env, now = Date.now()) {
38
- if (!leaseIsActive(lease, now))
39
- throw new Error(`Secret lease '${lease.id}' has expired.`);
40
- const missing = lease.keys.filter((key) => typeof env[key] !== 'string');
41
- if (missing.length > 0)
42
- throw new Error(`Secret lease '${lease.id}' is missing value(s): ${missing.join(', ')}`);
43
- return Object.fromEntries(lease.keys.map((key) => [key, env[key]]));
44
- }
@@ -1,77 +0,0 @@
1
- /**
2
- * Linux secret storage via libsecret (GNOME Keyring / Secret Service API).
3
- *
4
- * Primary backend: `secret-tool` CLI (libsecret-tools package).
5
- *
6
- * Headless fallback: when the default Secret Service collection is locked
7
- * (common on server-class Linux — no graphical login means the keyring
8
- * passphrase never enters the daemon, so `secret-tool store` fails with
9
- * "Cannot create an item in a locked collection"), we transparently switch
10
- * to the AES-256-GCM encrypted-file store in ./filestore.ts. The decision is
11
- * cached per process; one stderr line is emitted the first time the fallback
12
- * activates.
13
- *
14
- * Secrets stored via secret-tool use:
15
- * service = "agents-cli"
16
- * account = username
17
- * item = the secret identifier
18
- */
19
- import type { KeychainBackend } from './index.js';
20
- import { type NativeImportReport } from './fallback.js';
21
- export { encryptForFallback, decryptForFallback, fileBackend, type EncFile, } from './filestore.js';
22
- /**
23
- * True when secret operations currently route to the encrypted-file store
24
- * instead of the Secret Service (the headless / locked-collection fallback).
25
- *
26
- * Runs the same `preflight()` decision every read and write uses, so it can't
27
- * drift from where bytes actually land. `preflight()` throws only in the
28
- * interactive / no-secret-tool / no-passphrase case — where nothing is stored
29
- * — so treat that as "not on the file path".
30
- *
31
- * `listBundles()` needs this: under the fallback the keychain enumeration
32
- * (`linuxBackend.list`) and the file enumeration read the SAME store, so
33
- * without this signal every file-backed bundle would be listed twice.
34
- */
35
- export declare function usesFileFallback(): boolean;
36
- /** secret-tool lookup attributes:
37
- * service=agents-cli account=<user> item=<itemName> */
38
- export declare function hasSecretToolToken(item: string): boolean;
39
- export declare function getSecretToolToken(item: string): string;
40
- export declare function setSecretToolToken(item: string, value: string): void;
41
- export declare function deleteSecretToolToken(item: string): boolean;
42
- /**
43
- * Parse the item names out of `secret-tool search --all` output, keeping only
44
- * those starting with `prefix`. Exported for tests.
45
- *
46
- * `output` must be the combined stdout+stderr of the search: libsecret splits
47
- * the dump across both streams — the value/label/schema lines go to stdout
48
- * while the `attribute.*` lines (which carry `attribute.item`, the only place
49
- * the item name is reliably machine-readable) go to stderr (observed on
50
- * libsecret 0.21.4). Which stream each line lands on has varied across
51
- * libsecret versions, so callers concatenate both rather than bet on one.
52
- */
53
- export declare function parseSecretToolItems(output: string, prefix: string): string[];
54
- /**
55
- * List secrets by prefix. secret-tool doesn't have a list command,
56
- * so we use secret-tool search which outputs in a specific format.
57
- */
58
- export declare function listSecretToolItems(prefix: string): string[];
59
- /**
60
- * Copy agents-cli items from the keyring into the file store (the `import-keyring`
61
- * backend for Linux). Requires an unlocked keyring; items already in the file
62
- * store are left untouched. With `commit=false` it reports what it *would* do.
63
- */
64
- export declare function importNativeSecretToolItems(prefix: string, commit: boolean): NativeImportReport;
65
- /** KeychainBackend implementation for Linux. Routes through secret-tool
66
- * with a transparent encrypted-file fallback when the default Secret
67
- * Service collection is locked (or libsecret-tools is not installed but
68
- * AGENTS_SECRETS_PASSPHRASE is set). */
69
- export declare const linuxBackend: KeychainBackend;
70
- /** Test-only: reset module state so independent test cases don't bleed
71
- * passphrase / fallback decisions across each other. File-store state (file
72
- * dir + cached passphrase) lives in ./filestore.ts and is reset there. */
73
- export declare function _resetForTest(opts?: {
74
- fileDir?: string | null;
75
- forceFileFallback?: boolean;
76
- passphrase?: string | null;
77
- }): void;