@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
@@ -1,107 +0,0 @@
1
- /**
2
- * Durable "unlocked session" store (macOS).
3
- *
4
- * The secrets broker (agent.ts) holds unlocked bundles only in RAM, so an
5
- * `agents secrets unlock` grant is lost whenever that process dies: on an
6
- * agents-cli upgrade (the postinstall bounces the daemon) and on system SLEEP.
7
- * This module persists the resolved bundle+env of an unlocked bundle as a
8
- * NO-ACL keychain item — the `set-no-acl` write path, `kSecAttrAccessibleAfter-
9
- * FirstUnlockThisDeviceOnly`, whose reads never pop Touch ID — so the broker can
10
- * REHYDRATE it on start and reads can FALL BACK to it when the broker RAM misses.
11
- *
12
- * Split-default posture: `sleepPersist` is false by default — the item is deleted
13
- * on SLEEP so the bundle re-locks on sleep but survives restart/upgrade; the
14
- * `--durable` flag sets it true so it survives sleep too. Both expire at the TTL.
15
- *
16
- * Cross-platform: a NO-OP off macOS. Linux (secret-service) and Windows
17
- * (Credential Manager) have no broker and resolve secrets durably from the OS
18
- * store on every read with no prompt (see agent.ts:onDarwin gates) — there is no
19
- * ephemeral unlock to persist.
20
- *
21
- * NEVER enumerate session items: once keychain-name hashing (#316) is active a
22
- * `list('agents-cli.session.')` matches nothing. All I/O is by KNOWN name — one
23
- * fixed-name index item lists the held bundles + their metadata, and one blob per
24
- * bundle holds the env. Every adapter call is best-effort and never throws;
25
- * persistence is an optimization, not a correctness dependency.
26
- */
27
- import type { SecretsBundle } from './bundles.js';
28
- import type { SecretLease } from './lease.js';
29
- /** Prefix for all durable session items (device-local, no-ACL). */
30
- export declare const SESSION_ITEM_PREFIX = "agents-cli.session.";
31
- /** Fixed-name index item — the ONLY thing we ever need to find without a known
32
- * bundle name, so it is read/written by this exact name (never enumerated). */
33
- export declare const SESSION_INDEX_ITEM = "agents-cli.session.index";
34
- /** One persisted unlocked bundle (mirrors the broker's StoredBundle + posture). */
35
- export interface SessionEntry {
36
- bundle: SecretsBundle;
37
- env: Record<string, string>;
38
- /** epoch ms; the entry is dead once Date.now() passes this. */
39
- expiresAt: number;
40
- /** true only for `--durable` unlocks — survives SLEEP. */
41
- sleepPersist: boolean;
42
- harness?: string;
43
- lease?: SecretLease;
44
- }
45
- /** Metadata for one held bundle, kept in the index so we can rehydrate / prune
46
- * without reading every blob. */
47
- export interface SessionIndexMeta {
48
- expiresAt: number;
49
- sleepPersist: boolean;
50
- harness?: string;
51
- }
52
- export interface SessionIndex {
53
- bundles: Record<string, SessionIndexMeta>;
54
- }
55
- /** The bundle names in the index that are still within their TTL at `now`. */
56
- export declare function selectRehydratable(index: SessionIndex, now: number): string[];
57
- /** Split an index on a SLEEP event: keep `sleepPersist` entries, report the rest
58
- * (which the caller deletes from the keychain). Default entries re-lock on sleep. */
59
- export declare function pruneOnSleep(index: SessionIndex): {
60
- survivors: SessionIndex;
61
- deletedNames: string[];
62
- };
63
- /** Drop entries past their TTL; report which were dropped so the caller can
64
- * delete their blobs. */
65
- export declare function pruneExpired(index: SessionIndex, now: number): {
66
- survivors: SessionIndex;
67
- expiredNames: string[];
68
- };
69
- /** Insert/replace one bundle in the index (pure). */
70
- export declare function upsertEntry(index: SessionIndex, name: string, meta: SessionIndexMeta): SessionIndex;
71
- /** Remove one bundle from the index (pure). */
72
- export declare function removeEntry(index: SessionIndex, name: string): SessionIndex;
73
- /** Read the session index by its fixed name. `{bundles:{}}` when absent/unreadable. */
74
- export declare function readIndex(): SessionIndex;
75
- /** Persist the index as a no-ACL item. Deletes the item when empty (tidy). */
76
- export declare function writeIndex(index: SessionIndex): void;
77
- /** Persist one unlocked bundle: write its blob no-ACL and record it in the index. */
78
- export declare function saveSession(name: string, entry: SessionEntry): void;
79
- /**
80
- * Read a session honoring the scope chain: the caller's own harness first, then
81
- * the global grant (see bundleScopeChain in scope.ts). This is the durable-store
82
- * twin of the broker's `get` — both must resolve identically, or a bundle would
83
- * be readable from RAM but not after a restart.
84
- */
85
- export declare function resolveSession(name: string, now?: number, harness?: string): {
86
- entry: SessionEntry;
87
- harness: string;
88
- } | null;
89
- /** Read one session blob by known name. Null when absent/expired/malformed. */
90
- export declare function loadSession(name: string, now?: number, harness?: string): SessionEntry | null;
91
- /** Remove every persisted harness grant for one bundle. */
92
- export declare function deleteBundleSessions(name: string): void;
93
- /** Delete one bundle's session blob and prune it from the index. */
94
- export declare function deleteSession(name: string, harness?: string): void;
95
- export declare function deleteLeaseSession(leaseId: string): number;
96
- /** Delete every session blob + the index (for `secrets lock --all`). */
97
- export declare function deleteAllSessions(): void;
98
- /** Rehydrate every unexpired session into `[name, entry]` pairs for the broker to
99
- * load on start; prunes expired index entries + blobs as a side effect. Survives
100
- * upgrade/restart. Empty off darwin or when nothing is held. */
101
- export declare function rehydrateSessions(now?: number): Array<{
102
- name: string;
103
- entry: SessionEntry;
104
- }>;
105
- /** On a SLEEP event: delete every non-`--durable` session (blob + index entry),
106
- * keep the durable ones. Default bundles thus re-lock on sleep. */
107
- export declare function pruneSessionsOnSleep(): void;
@@ -1,342 +0,0 @@
1
- /**
2
- * Durable "unlocked session" store (macOS).
3
- *
4
- * The secrets broker (agent.ts) holds unlocked bundles only in RAM, so an
5
- * `agents secrets unlock` grant is lost whenever that process dies: on an
6
- * agents-cli upgrade (the postinstall bounces the daemon) and on system SLEEP.
7
- * This module persists the resolved bundle+env of an unlocked bundle as a
8
- * NO-ACL keychain item — the `set-no-acl` write path, `kSecAttrAccessibleAfter-
9
- * FirstUnlockThisDeviceOnly`, whose reads never pop Touch ID — so the broker can
10
- * REHYDRATE it on start and reads can FALL BACK to it when the broker RAM misses.
11
- *
12
- * Split-default posture: `sleepPersist` is false by default — the item is deleted
13
- * on SLEEP so the bundle re-locks on sleep but survives restart/upgrade; the
14
- * `--durable` flag sets it true so it survives sleep too. Both expire at the TTL.
15
- *
16
- * Cross-platform: a NO-OP off macOS. Linux (secret-service) and Windows
17
- * (Credential Manager) have no broker and resolve secrets durably from the OS
18
- * store on every read with no prompt (see agent.ts:onDarwin gates) — there is no
19
- * ephemeral unlock to persist.
20
- *
21
- * NEVER enumerate session items: once keychain-name hashing (#316) is active a
22
- * `list('agents-cli.session.')` matches nothing. All I/O is by KNOWN name — one
23
- * fixed-name index item lists the held bundles + their metadata, and one blob per
24
- * bundle holds the env. Every adapter call is best-effort and never throws;
25
- * persistence is an optimization, not a correctness dependency.
26
- */
27
- import { getKeychainToken, setKeychainToken, deleteKeychainToken, isKeychainBackendOverridden } from './index.js';
28
- import { GLOBAL_HARNESS, bundleScopeChain } from './scope.js';
29
- /** Prefix for all durable session items (device-local, no-ACL). */
30
- export const SESSION_ITEM_PREFIX = 'agents-cli.session.';
31
- /** Fixed-name index item — the ONLY thing we ever need to find without a known
32
- * bundle name, so it is read/written by this exact name (never enumerated). */
33
- export const SESSION_INDEX_ITEM = 'agents-cli.session.index';
34
- // ─── Pure core (no I/O — unit-testable on any platform) ─────────────────────
35
- /** The bundle names in the index that are still within their TTL at `now`. */
36
- export function selectRehydratable(index, now) {
37
- return Object.entries(index.bundles)
38
- .filter(([, m]) => now < m.expiresAt)
39
- .map(([name]) => name);
40
- }
41
- /** Split an index on a SLEEP event: keep `sleepPersist` entries, report the rest
42
- * (which the caller deletes from the keychain). Default entries re-lock on sleep. */
43
- export function pruneOnSleep(index) {
44
- const survivors = { bundles: {} };
45
- const deletedNames = [];
46
- for (const [name, m] of Object.entries(index.bundles)) {
47
- if (m.sleepPersist)
48
- survivors.bundles[name] = m;
49
- else
50
- deletedNames.push(name);
51
- }
52
- return { survivors, deletedNames };
53
- }
54
- /** Drop entries past their TTL; report which were dropped so the caller can
55
- * delete their blobs. */
56
- export function pruneExpired(index, now) {
57
- const survivors = { bundles: {} };
58
- const expiredNames = [];
59
- for (const [name, m] of Object.entries(index.bundles)) {
60
- if (now < m.expiresAt)
61
- survivors.bundles[name] = m;
62
- else
63
- expiredNames.push(name);
64
- }
65
- return { survivors, expiredNames };
66
- }
67
- /** Insert/replace one bundle in the index (pure). */
68
- export function upsertEntry(index, name, meta) {
69
- return { bundles: { ...index.bundles, [name]: meta } };
70
- }
71
- /** Remove one bundle from the index (pure). */
72
- export function removeEntry(index, name) {
73
- const { [name]: _drop, ...rest } = index.bundles;
74
- void _drop;
75
- return { bundles: rest };
76
- }
77
- // ─── Keychain adapter (macOS only; best-effort, never throws) ────────────────
78
- /** Whether to actually persist. macOS in production; also whenever a test
79
- * keychain backend is installed, so the adapter is exercisable on Linux CI
80
- * against an in-memory store (mirrors the broker's hermetic-test gating). In real
81
- * Linux/Windows production this is false — secrets already persist in the OS store
82
- * with no broker, so there is nothing to mirror. */
83
- function shouldPersist() {
84
- return process.platform === 'darwin' || isKeychainBackendOverridden();
85
- }
86
- function sessionBlobItem(name, harness) {
87
- return `${SESSION_ITEM_PREFIX}${harness}.${name}`;
88
- }
89
- /** Read the session index by its fixed name. `{bundles:{}}` when absent/unreadable. */
90
- export function readIndex() {
91
- try {
92
- // Written no-ACL (writeIndex below) — attest so a headless resolve stays silent.
93
- const raw = getKeychainToken(SESSION_INDEX_ITEM, { silentNoAcl: true });
94
- const parsed = JSON.parse(raw);
95
- if (parsed && typeof parsed === 'object' && parsed.bundles)
96
- return parsed;
97
- }
98
- catch {
99
- /* absent or malformed → empty */
100
- }
101
- return { bundles: {} };
102
- }
103
- /** Persist the index as a no-ACL item. Deletes the item when empty (tidy). */
104
- export function writeIndex(index) {
105
- try {
106
- if (Object.keys(index.bundles).length === 0) {
107
- deleteKeychainToken(SESSION_INDEX_ITEM);
108
- return;
109
- }
110
- setKeychainToken(SESSION_INDEX_ITEM, JSON.stringify(index), { noAcl: true });
111
- }
112
- catch {
113
- /* best-effort */
114
- }
115
- }
116
- /** Persist one unlocked bundle: write its blob no-ACL and record it in the index. */
117
- export function saveSession(name, entry) {
118
- if (!shouldPersist())
119
- return;
120
- try {
121
- const harness = entry.harness || GLOBAL_HARNESS;
122
- const key = `${harness}:${name}`;
123
- setKeychainToken(sessionBlobItem(name, harness), JSON.stringify({ ...entry, harness }), { noAcl: true });
124
- writeIndex(upsertEntry(readIndex(), key, { expiresAt: entry.expiresAt, sleepPersist: entry.sleepPersist, harness }));
125
- }
126
- catch {
127
- /* best-effort — persistence is an optimization */
128
- }
129
- }
130
- /**
131
- * Read a session honoring the scope chain: the caller's own harness first, then
132
- * the global grant (see bundleScopeChain in scope.ts). This is the durable-store
133
- * twin of the broker's `get` — both must resolve identically, or a bundle would
134
- * be readable from RAM but not after a restart.
135
- */
136
- export function resolveSession(name, now = Date.now(), harness = GLOBAL_HARNESS) {
137
- for (const scope of bundleScopeChain(harness)) {
138
- const entry = loadSession(name, now, scope);
139
- if (entry)
140
- return { entry, harness: scope };
141
- }
142
- return null;
143
- }
144
- /** Read one session blob by known name. Null when absent/expired/malformed. */
145
- export function loadSession(name, now = Date.now(), harness = GLOBAL_HARNESS) {
146
- if (!shouldPersist())
147
- return null;
148
- try {
149
- // Written no-ACL (saveSession) — attest so a headless resolve stays silent.
150
- const raw = getKeychainToken(sessionBlobItem(name, harness), { silentNoAcl: true });
151
- const entry = JSON.parse(raw);
152
- if (!entry || typeof entry !== 'object' || !entry.bundle || !entry.env)
153
- return null;
154
- if (now >= entry.expiresAt) {
155
- deleteSession(name, harness); // drop expired on read
156
- return null;
157
- }
158
- return entry;
159
- }
160
- catch {
161
- return null;
162
- }
163
- }
164
- /** Remove every persisted harness grant for one bundle. */
165
- export function deleteBundleSessions(name) {
166
- if (!shouldPersist())
167
- return;
168
- const index = readIndex();
169
- let next = index;
170
- for (const [key, meta] of Object.entries(index.bundles)) {
171
- const scopedName = key.includes(':') ? key.split(':').slice(1).join(':') : key;
172
- if (scopedName !== name)
173
- continue;
174
- const harness = meta.harness || GLOBAL_HARNESS;
175
- try {
176
- deleteKeychainToken(key.includes(':') ? sessionBlobItem(name, harness) : `${SESSION_ITEM_PREFIX}${name}`);
177
- }
178
- catch { /* keep going */ }
179
- next = removeEntry(next, key);
180
- }
181
- writeIndex(next);
182
- }
183
- /** Delete one bundle's session blob and prune it from the index. */
184
- export function deleteSession(name, harness = GLOBAL_HARNESS) {
185
- if (!shouldPersist())
186
- return;
187
- try {
188
- deleteKeychainToken(sessionBlobItem(name, harness));
189
- writeIndex(removeEntry(readIndex(), `${harness}:${name}`));
190
- }
191
- catch {
192
- /* best-effort */
193
- }
194
- }
195
- export function deleteLeaseSession(leaseId) {
196
- if (!shouldPersist())
197
- return 0;
198
- const index = readIndex();
199
- let deleted = 0;
200
- for (const [key, meta] of Object.entries(index.bundles)) {
201
- const bundleName = key.split(':').slice(1).join(':');
202
- const harness = meta.harness || GLOBAL_HARNESS;
203
- const entry = loadSession(bundleName, Date.now(), harness);
204
- if (entry?.lease?.id !== leaseId)
205
- continue;
206
- deleteSession(bundleName, harness);
207
- deleted++;
208
- }
209
- return deleted;
210
- }
211
- /** Delete every session blob + the index (for `secrets lock --all`). */
212
- export function deleteAllSessions() {
213
- if (!shouldPersist())
214
- return;
215
- try {
216
- for (const name of Object.keys(readIndex().bundles)) {
217
- const meta = readIndex().bundles[name];
218
- const bundleName = name.includes(':') ? name.split(':').slice(1).join(':') : name;
219
- try {
220
- deleteKeychainToken(name.includes(':')
221
- ? sessionBlobItem(bundleName, meta.harness || GLOBAL_HARNESS)
222
- : `${SESSION_ITEM_PREFIX}${bundleName}`);
223
- }
224
- catch { /* keep going */ }
225
- }
226
- deleteKeychainToken(SESSION_INDEX_ITEM);
227
- }
228
- catch {
229
- /* best-effort */
230
- }
231
- }
232
- /** Rehydrate every unexpired session into `[name, entry]` pairs for the broker to
233
- * load on start; prunes expired index entries + blobs as a side effect. Survives
234
- * upgrade/restart. Empty off darwin or when nothing is held. */
235
- export function rehydrateSessions(now = Date.now()) {
236
- if (!shouldPersist())
237
- return [];
238
- const out = [];
239
- try {
240
- const index = readIndex();
241
- // One-time source migration from the pre-harness layout. Move each legacy
242
- // bundle-name index/blob to the explicit cli scope, then delete the old blob.
243
- let migratedIndex = index;
244
- for (const [key, meta] of Object.entries(index.bundles)) {
245
- if (key.includes(':'))
246
- continue;
247
- try {
248
- const raw = getKeychainToken(`${SESSION_ITEM_PREFIX}${key}`, { silentNoAcl: true });
249
- const legacy = JSON.parse(raw);
250
- setKeychainToken(sessionBlobItem(key, GLOBAL_HARNESS), JSON.stringify({ ...legacy, harness: GLOBAL_HARNESS }), { noAcl: true });
251
- deleteKeychainToken(`${SESSION_ITEM_PREFIX}${key}`);
252
- migratedIndex = upsertEntry(removeEntry(migratedIndex, key), `${GLOBAL_HARNESS}:${key}`, {
253
- expiresAt: meta.expiresAt,
254
- sleepPersist: meta.sleepPersist,
255
- harness: GLOBAL_HARNESS,
256
- });
257
- }
258
- catch { /* malformed/absent legacy entry is pruned below */ }
259
- }
260
- // Second source migration: `cli`-scoped grants predate the global scope. `cli`
261
- // was never a harness — it was the default when no AGENTS_AGENT_NAME was set,
262
- // i.e. "unlocked from a terminal for general use", which is exactly the global
263
- // grant. Rewriting them means an unlock a user already paid Touch ID for keeps
264
- // working after upgrade instead of silently becoming unreadable to every agent.
265
- for (const [key, meta] of Object.entries(migratedIndex.bundles)) {
266
- if (!key.startsWith('cli:'))
267
- continue;
268
- const bundleName = key.slice('cli:'.length);
269
- const globalKey = `${GLOBAL_HARNESS}:${bundleName}`;
270
- // A global grant for this bundle can already exist: the user re-runs
271
- // `unlock` on the new code (which writes the global scope) before the
272
- // broker restarts to run this migration. The existing global grant is the
273
- // NEWER one, so the stale `cli` entry is discarded, never merged over it —
274
- // overwriting would restore a superseded token and, if the stale entry had
275
- // expired, hand its expiry to the fresh grant so the prune below deletes a
276
- // valid unlock outright.
277
- if (migratedIndex.bundles[globalKey]) {
278
- try {
279
- deleteKeychainToken(sessionBlobItem(bundleName, 'cli'));
280
- }
281
- catch { /* already gone */ }
282
- migratedIndex = removeEntry(migratedIndex, key);
283
- continue;
284
- }
285
- try {
286
- const raw = getKeychainToken(sessionBlobItem(bundleName, 'cli'), { silentNoAcl: true });
287
- const legacy = JSON.parse(raw);
288
- setKeychainToken(sessionBlobItem(bundleName, GLOBAL_HARNESS), JSON.stringify({ ...legacy, harness: GLOBAL_HARNESS }), { noAcl: true });
289
- deleteKeychainToken(sessionBlobItem(bundleName, 'cli'));
290
- migratedIndex = upsertEntry(removeEntry(migratedIndex, key), globalKey, {
291
- expiresAt: meta.expiresAt,
292
- sleepPersist: meta.sleepPersist,
293
- harness: GLOBAL_HARNESS,
294
- });
295
- }
296
- catch { /* malformed/absent entry is pruned below */ }
297
- }
298
- writeIndex(migratedIndex);
299
- index.bundles = migratedIndex.bundles;
300
- const { survivors, expiredNames } = pruneExpired(index, now);
301
- for (const name of expiredNames) {
302
- const meta = index.bundles[name];
303
- try {
304
- deleteKeychainToken(sessionBlobItem(name.split(':').slice(1).join(':'), meta.harness || GLOBAL_HARNESS));
305
- }
306
- catch { /* keep going */ }
307
- }
308
- if (expiredNames.length)
309
- writeIndex(survivors);
310
- for (const name of selectRehydratable(survivors, now)) {
311
- const meta = survivors.bundles[name];
312
- const bundleName = name.split(':').slice(1).join(':');
313
- const entry = loadSession(bundleName, now, meta.harness || GLOBAL_HARNESS);
314
- if (entry)
315
- out.push({ name: bundleName, entry });
316
- }
317
- }
318
- catch {
319
- /* best-effort */
320
- }
321
- return out;
322
- }
323
- /** On a SLEEP event: delete every non-`--durable` session (blob + index entry),
324
- * keep the durable ones. Default bundles thus re-lock on sleep. */
325
- export function pruneSessionsOnSleep() {
326
- if (!shouldPersist())
327
- return;
328
- try {
329
- const { survivors, deletedNames } = pruneOnSleep(readIndex());
330
- for (const name of deletedNames) {
331
- const meta = readIndex().bundles[name];
332
- try {
333
- deleteKeychainToken(sessionBlobItem(name.split(':').slice(1).join(':'), meta.harness || GLOBAL_HARNESS));
334
- }
335
- catch { /* keep going */ }
336
- }
337
- writeIndex(survivors);
338
- }
339
- catch {
340
- /* best-effort */
341
- }
342
- }
@@ -1,48 +0,0 @@
1
- /**
2
- * Transport seam for encrypted secrets-bundle sync.
3
- *
4
- * A `SyncBackend` moves opaque ciphertext envelopes to and from some remote.
5
- * It NEVER sees plaintext: encryption (`encryptBlob`) happens in `sync.ts`
6
- * before `putEnvelope`, and decryption (`decryptBlob`) happens after
7
- * `getEnvelope`. This mirrors the `KeychainBackend` storage seam
8
- * (`src/lib/secrets/index.ts`) but abstracts *transport* rather than at-rest
9
- * storage — so the high-level push/pull logic in `sync.ts` is decoupled from
10
- * any specific backend (Rush's api.prix.dev, a future Supabase driver, an
11
- * in-memory test double, …).
12
- */
13
- /** Encrypted bundle envelope (AES-256-GCM, key via PBKDF2-SHA256). All byte
14
- * fields are base64. The server only ever stores/returns this — never plaintext. */
15
- export interface EncryptedEnvelope {
16
- v: 1;
17
- kdf: 'pbkdf2-sha256';
18
- iter: number;
19
- salt: string;
20
- iv: string;
21
- ct: string;
22
- tag: string;
23
- }
24
- /** A stored bundle object: the ciphertext envelope plus a last-updated stamp. */
25
- export interface SyncEnvelope {
26
- envelope: EncryptedEnvelope;
27
- updated_at: string;
28
- }
29
- /** Lightweight listing entry returned by `listEnvelopes`. */
30
- export interface RemoteBundleSummary {
31
- name: string;
32
- updated_at: string;
33
- }
34
- /**
35
- * Pluggable transport for encrypted bundles. Implementations handle only the
36
- * wire/storage; the crypto and bundle snapshot/restore stay backend-agnostic
37
- * in `sync.ts`.
38
- */
39
- export interface SyncBackend {
40
- /** Store (create or overwrite) the envelope for `name`. */
41
- putEnvelope(name: string, payload: SyncEnvelope): Promise<void>;
42
- /** Fetch the envelope for `name`, or `null` if the remote has none. */
43
- getEnvelope(name: string): Promise<SyncEnvelope | null>;
44
- /** Delete `name` on the remote. Returns false if it didn't exist. */
45
- deleteEnvelope(name: string): Promise<boolean>;
46
- /** List every bundle the authenticated user has on the remote. */
47
- listEnvelopes(): Promise<RemoteBundleSummary[]>;
48
- }
@@ -1,13 +0,0 @@
1
- /**
2
- * Transport seam for encrypted secrets-bundle sync.
3
- *
4
- * A `SyncBackend` moves opaque ciphertext envelopes to and from some remote.
5
- * It NEVER sees plaintext: encryption (`encryptBlob`) happens in `sync.ts`
6
- * before `putEnvelope`, and decryption (`decryptBlob`) happens after
7
- * `getEnvelope`. This mirrors the `KeychainBackend` storage seam
8
- * (`src/lib/secrets/index.ts`) but abstracts *transport* rather than at-rest
9
- * storage — so the high-level push/pull logic in `sync.ts` is decoupled from
10
- * any specific backend (Rush's api.prix.dev, a future Supabase driver, an
11
- * in-memory test double, …).
12
- */
13
- export {};
@@ -1,21 +0,0 @@
1
- /**
2
- * The argv tokens for the synchronous secrets-broker clients: the tokens
3
- * `agentGetSync`/`agentReachableSync`/`agentEvictSync` spawn, and the tokens
4
- * `index.ts` dispatches on before commander.
5
- *
6
- * This is a LEAF module — it imports nothing, so the CLI entrypoint can bind
7
- * these statically without dragging the secrets graph (and its `state.js` /
8
- * `install-helper.js` / `session-store.js` imports) into every invocation. That
9
- * is the whole reason the file exists: with the tokens in `agent.ts`, `index.ts`
10
- * had to repeat them as string literals, and the two could silently drift —
11
- * a drift with no visible failure mode, because the CLI answers "unknown
12
- * command", the client reads the non-zero exit as "broker down", and every
13
- * secret read falls back to a Touch ID prompt. Exactly the bug this subsystem
14
- * was fixed for. Sharing one binding makes that divergence a type error rather
15
- * than something a test has to notice.
16
- *
17
- * Same leaf discipline, and same motivation, as `lib/cli-entry.ts`.
18
- */
19
- export declare const SYNC_GET_CMD = "__secrets-get";
20
- export declare const SYNC_PING_CMD = "__secrets-ping";
21
- export declare const SYNC_LOCK_CMD = "__secrets-lock";
@@ -1,21 +0,0 @@
1
- /**
2
- * The argv tokens for the synchronous secrets-broker clients: the tokens
3
- * `agentGetSync`/`agentReachableSync`/`agentEvictSync` spawn, and the tokens
4
- * `index.ts` dispatches on before commander.
5
- *
6
- * This is a LEAF module — it imports nothing, so the CLI entrypoint can bind
7
- * these statically without dragging the secrets graph (and its `state.js` /
8
- * `install-helper.js` / `session-store.js` imports) into every invocation. That
9
- * is the whole reason the file exists: with the tokens in `agent.ts`, `index.ts`
10
- * had to repeat them as string literals, and the two could silently drift —
11
- * a drift with no visible failure mode, because the CLI answers "unknown
12
- * command", the client reads the non-zero exit as "broker down", and every
13
- * secret read falls back to a Touch ID prompt. Exactly the bug this subsystem
14
- * was fixed for. Sharing one binding makes that divergence a type error rather
15
- * than something a test has to notice.
16
- *
17
- * Same leaf discipline, and same motivation, as `lib/cli-entry.ts`.
18
- */
19
- export const SYNC_GET_CMD = '__secrets-get';
20
- export const SYNC_PING_CMD = '__secrets-ping';
21
- export const SYNC_LOCK_CMD = '__secrets-lock';
@@ -1,48 +0,0 @@
1
- /**
2
- * Remote sync for secrets bundles — backend-agnostic.
3
- *
4
- * Replaces the previous "leave it to iCloud Keychain" model with explicit
5
- * push/pull. Bundle contents (vars + secret values) are encrypted client-side
6
- * with AES-256-GCM under a key derived from a user-supplied passphrase via
7
- * PBKDF2-SHA256; only the resulting ciphertext envelope is handed to the
8
- * transport. The transport itself is a pluggable `SyncBackend` (see
9
- * `sync-backend.ts`) — the Rush driver (`drivers/rush.ts`, api.prix.dev) is the
10
- * default for backwards compatibility, swappable via `setSyncBackend`. Plaintext
11
- * never leaves this module; the backend only ever sees ciphertext + KDF params.
12
- */
13
- import { type SecretsBundle } from './bundles.js';
14
- import type { SyncBackend, RemoteBundleSummary, EncryptedEnvelope } from './sync-backend.js';
15
- export type { EncryptedEnvelope, RemoteBundleSummary } from './sync-backend.js';
16
- export declare const MIN_PASSPHRASE_LEN = 12;
17
- /** Override the sync transport. Returns the previous backend (restore in tests). */
18
- export declare function setSyncBackend(next: SyncBackend): SyncBackend;
19
- /** Encrypt a JSON-serializable payload with a passphrase. */
20
- export declare function encryptBlob(plaintext: string, passphrase: string): EncryptedEnvelope;
21
- /** Decrypt an envelope. Throws on bad passphrase (auth tag mismatch). */
22
- export declare function decryptBlob(envelope: EncryptedEnvelope, passphrase: string): string;
23
- /** The plaintext we serialize before encrypting: bundle metadata + secret values. */
24
- export interface BundleSnapshot {
25
- bundle: SecretsBundle;
26
- /** keychain shortId -> plaintext value. Only present for keychain: refs. */
27
- secrets: Record<string, string>;
28
- }
29
- /** Options for pushBundle. */
30
- export interface PushOptions {
31
- passphrase: string;
32
- }
33
- /** Push a local bundle to the remote. Encrypts client-side; the backend only sees ciphertext. */
34
- export declare function pushBundle(name: string, opts: PushOptions): Promise<{
35
- updated_at: string;
36
- }>;
37
- /** Options for pullBundle. */
38
- export interface PullOptions {
39
- passphrase: string;
40
- /** When true, overwrite an existing local bundle. */
41
- force?: boolean;
42
- }
43
- /** Pull a bundle by name from the remote and materialize it locally. */
44
- export declare function pullBundle(name: string, opts: PullOptions): Promise<SecretsBundle>;
45
- /** Delete a bundle on the remote. */
46
- export declare function deleteRemoteBundle(name: string): Promise<boolean>;
47
- /** List bundles currently stored on the remote for this user. */
48
- export declare function listRemoteBundles(): Promise<RemoteBundleSummary[]>;