@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,237 +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 * as crypto from 'crypto';
14
- import { deleteKeychainToken, getKeychainToken, hasKeychainToken, secretsKeychainItem, setKeychainToken, } from './index.js';
15
- import { readBundle, writeBundle, keychainItemsForBundle, validateBundleName, } from './bundles.js';
16
- import { rushSyncBackend } from './drivers/rush.js';
17
- import { emitSecretAudit } from './audit.js';
18
- // PBKDF2 cost. 600k SHA-256 iters matches OWASP 2023+ guidance and keeps a
19
- // passphrase prompt under a second on the hardware the CLI targets.
20
- const PBKDF2_ITER = 600_000;
21
- export const MIN_PASSPHRASE_LEN = 12;
22
- const KEY_LEN = 32;
23
- const SALT_LEN = 16;
24
- const IV_LEN = 12;
25
- /**
26
- * Active transport backend. Defaults to the Rush driver for backwards
27
- * compatibility with bundles already pushed to api.prix.dev; `setSyncBackend`
28
- * swaps it (a future Supabase driver, or an in-memory double in tests). The
29
- * crypto + snapshot/restore below stay backend-agnostic — the backend only
30
- * ever moves ciphertext envelopes, never plaintext.
31
- */
32
- let backend = rushSyncBackend;
33
- /** Override the sync transport. Returns the previous backend (restore in tests). */
34
- export function setSyncBackend(next) {
35
- const prev = backend;
36
- backend = next;
37
- return prev;
38
- }
39
- function deriveKey(passphrase, salt) {
40
- return crypto.pbkdf2Sync(passphrase, salt, PBKDF2_ITER, KEY_LEN, 'sha256');
41
- }
42
- /** Encrypt a JSON-serializable payload with a passphrase. */
43
- export function encryptBlob(plaintext, passphrase) {
44
- if (!passphrase || passphrase.length < MIN_PASSPHRASE_LEN) {
45
- throw new Error(`Passphrase must be at least ${MIN_PASSPHRASE_LEN} characters.`);
46
- }
47
- const salt = crypto.randomBytes(SALT_LEN);
48
- const iv = crypto.randomBytes(IV_LEN);
49
- const key = deriveKey(passphrase, salt);
50
- const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
51
- const ct = Buffer.concat([cipher.update(plaintext, 'utf-8'), cipher.final()]);
52
- const tag = cipher.getAuthTag();
53
- return {
54
- v: 1,
55
- kdf: 'pbkdf2-sha256',
56
- iter: PBKDF2_ITER,
57
- salt: salt.toString('base64'),
58
- iv: iv.toString('base64'),
59
- ct: ct.toString('base64'),
60
- tag: tag.toString('base64'),
61
- };
62
- }
63
- /** Decrypt an envelope. Throws on bad passphrase (auth tag mismatch). */
64
- export function decryptBlob(envelope, passphrase) {
65
- if (envelope.v !== 1 || envelope.kdf !== 'pbkdf2-sha256') {
66
- throw new Error(`Unsupported envelope version (v${envelope.v}, kdf=${envelope.kdf}).`);
67
- }
68
- const salt = Buffer.from(envelope.salt, 'base64');
69
- const iv = Buffer.from(envelope.iv, 'base64');
70
- const ct = Buffer.from(envelope.ct, 'base64');
71
- const tag = Buffer.from(envelope.tag, 'base64');
72
- const key = deriveKey(passphrase, salt);
73
- const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
74
- decipher.setAuthTag(tag);
75
- try {
76
- return Buffer.concat([decipher.update(ct), decipher.final()]).toString('utf-8');
77
- }
78
- catch {
79
- throw new Error('Decryption failed — wrong passphrase or corrupt blob.');
80
- }
81
- }
82
- function snapshotBundle(name) {
83
- const bundle = readBundle(name);
84
- const secrets = {};
85
- for (const { key, item } of keychainItemsForBundle(bundle)) {
86
- if (!hasKeychainToken(item)) {
87
- throw new Error(`Bundle '${name}' key '${key}': keychain item '${item}' missing — cannot push incomplete bundle.`);
88
- }
89
- const raw = bundle.vars[key];
90
- if (typeof raw !== 'string' || !raw.startsWith('keychain:'))
91
- continue;
92
- const shortId = raw.slice('keychain:'.length);
93
- secrets[shortId] = getKeychainToken(item);
94
- }
95
- return { bundle, secrets };
96
- }
97
- /**
98
- * Materialize a decrypted snapshot locally, atomically.
99
- *
100
- * A naive "set each secret, then write metadata" is not crash-safe: if one
101
- * `setKeychainToken` throws midway, the keychain is left with a half-applied
102
- * set — some items carry the pulled values, others the old ones — and the
103
- * bundle metadata may never be written, leaving orphaned/wrong items readable.
104
- *
105
- * So we snapshot the PRIOR value of every item we're about to touch, apply all
106
- * writes, and on ANY failure roll back to exactly the pre-restore state
107
- * (restore previously-existing items, delete items that didn't exist) before
108
- * rethrowing. Metadata is committed only after every secret write succeeds; if
109
- * that final write fails, the secret writes are rolled back too. Either the
110
- * whole pull lands or the keychain is untouched.
111
- */
112
- function restoreSnapshot(snap) {
113
- const bundle = snap.bundle;
114
- validateBundleName(bundle.name);
115
- // Capture the pre-restore state of every item so a partial failure can be
116
- // undone. hasKeychainToken never prompts; getKeychainToken reads the existing
117
- // value via the same path the rest of the module uses.
118
- const priors = [];
119
- for (const shortId of Object.keys(snap.secrets)) {
120
- const item = secretsKeychainItem(bundle.name, shortId);
121
- if (hasKeychainToken(item)) {
122
- priors.push({ item, existed: true, value: getKeychainToken(item) });
123
- }
124
- else {
125
- priors.push({ item, existed: false });
126
- }
127
- }
128
- // Best-effort reversal to the captured pre-restore state. Each item is
129
- // reverted independently so one failure doesn't abort the rest of the
130
- // rollback. Reversal writes hit the SAME backend that just failed the restore
131
- // (a locked/erroring keyring is the realistic trigger), so a reversal can
132
- // itself throw — those items are collected and returned so the caller can
133
- // report an INCOMPLETE rollback instead of falsely claiming a clean one.
134
- const rollback = () => {
135
- const stillDirty = [];
136
- for (const prior of priors) {
137
- try {
138
- if (prior.existed)
139
- setKeychainToken(prior.item, prior.value);
140
- else
141
- deleteKeychainToken(prior.item);
142
- }
143
- catch {
144
- // Keep undoing the remaining items, but remember this one couldn't be
145
- // reverted — it may now hold an orphaned or wrong value.
146
- stillDirty.push(prior.item);
147
- }
148
- }
149
- return stillDirty;
150
- };
151
- try {
152
- for (const [shortId, value] of Object.entries(snap.secrets)) {
153
- const item = secretsKeychainItem(bundle.name, shortId);
154
- setKeychainToken(item, value);
155
- }
156
- }
157
- catch (err) {
158
- throw new Error(rollbackFailureMessage(bundle.name, 'writing secrets', err, rollback()));
159
- }
160
- // Commit metadata last. If it fails, undo the secret writes so nothing
161
- // partial lingers.
162
- try {
163
- writeBundle(bundle);
164
- }
165
- catch (err) {
166
- throw new Error(rollbackFailureMessage(bundle.name, 'writing metadata', err, rollback()));
167
- }
168
- }
169
- /**
170
- * Build the error thrown when a restore fails. When the rollback reverted every
171
- * touched item (`dirty` empty) we truthfully say the keychain is back to the
172
- * pre-restore state. When some reversal writes ALSO failed we must NOT claim a
173
- * clean rollback — the message names the still-dirty items so the user knows the
174
- * keychain is half-restored and which secrets to check.
175
- */
176
- function rollbackFailureMessage(name, phase, err, dirty) {
177
- if (dirty.length === 0) {
178
- return `Restore of bundle '${name}' failed while ${phase}; ` +
179
- `rolled back to the pre-restore state. (${err.message})`;
180
- }
181
- return `Restore of bundle '${name}' failed while ${phase}, and the rollback was INCOMPLETE — ` +
182
- `these keychain items could not be reverted and may hold orphaned or wrong values: ${dirty.join(', ')}. ` +
183
- `(original error: ${err.message})`;
184
- }
185
- /** Push a local bundle to the remote. Encrypts client-side; the backend only sees ciphertext. */
186
- export async function pushBundle(name, opts) {
187
- validateBundleName(name);
188
- const snap = snapshotBundle(name);
189
- // Push reads every plaintext value and uploads the (client-side-encrypted)
190
- // bundle off-machine — the most sensitive read there is. It bypasses
191
- // readAndResolveBundleEnv, so audit it explicitly. Values never enter the
192
- // payload; only the bundle name and how many keys were read.
193
- emitSecretAudit({
194
- event: 'secrets.get',
195
- bundle: name,
196
- operation: 'sync push',
197
- source: 'sync-push',
198
- status: 'success',
199
- keyCount: Object.keys(snap.secrets).length,
200
- });
201
- const envelope = encryptBlob(JSON.stringify(snap), opts.passphrase);
202
- const updated_at = new Date().toISOString();
203
- const payload = { envelope, updated_at };
204
- await backend.putEnvelope(name, payload);
205
- return { updated_at };
206
- }
207
- /** Pull a bundle by name from the remote and materialize it locally. */
208
- export async function pullBundle(name, opts) {
209
- validateBundleName(name);
210
- const data = await backend.getEnvelope(name);
211
- if (!data) {
212
- throw new Error(`Remote bundle '${name}' not found.`);
213
- }
214
- const plaintext = decryptBlob(data.envelope, opts.passphrase);
215
- const snap = JSON.parse(plaintext);
216
- if (!snap || !snap.bundle || snap.bundle.name !== name) {
217
- throw new Error(`Decrypted payload for '${name}' is malformed (bundle name mismatch).`);
218
- }
219
- // existence check is the caller's responsibility; we trust opts.force.
220
- if (!opts.force) {
221
- const { bundleExists } = await import('./bundles.js');
222
- if (bundleExists(name)) {
223
- throw new Error(`Local bundle '${name}' already exists. Re-run with --force to overwrite.`);
224
- }
225
- }
226
- restoreSnapshot(snap);
227
- return snap.bundle;
228
- }
229
- /** Delete a bundle on the remote. */
230
- export async function deleteRemoteBundle(name) {
231
- validateBundleName(name);
232
- return backend.deleteEnvelope(name);
233
- }
234
- /** List bundles currently stored on the remote for this user. */
235
- export async function listRemoteBundles() {
236
- return backend.listEnvelopes();
237
- }
@@ -1,27 +0,0 @@
1
- /** The subset of a `secrets.get` event this heuristic reads. */
2
- export interface SecretGetRecord {
3
- /** Bundle name the read targeted. */
4
- bundle?: string;
5
- /** Where the value came from: `agent` (broker hit) / `session` (durable
6
- * unlock) are SILENT; anything else hit the keychain and thus prompted. */
7
- source?: string;
8
- }
9
- export interface PromptedBundle {
10
- name: string;
11
- /** Number of prompting (keychain) reads in the window. */
12
- count: number;
13
- }
14
- /**
15
- * From recent `secrets.get` records, find bundles read often enough via the
16
- * keychain (i.e. NOT served silently by the broker or a durable session) to be
17
- * worth unlocking once. A bundle currently `held` is already silent, so it is
18
- * excluded; the caller further drops `never`/no-ACL bundles (which never prompt,
19
- * so unlocking them is a no-op).
20
- *
21
- * @param records recent `secrets.get` events (any order)
22
- * @param held bundle names the broker currently holds (silent reads)
23
- * @param opts.minReads minimum prompting reads to surface a bundle (default 3)
24
- */
25
- export declare function frequentlyPromptedBundles(records: SecretGetRecord[], held: Set<string>, opts?: {
26
- minReads?: number;
27
- }): PromptedBundle[];
@@ -1,36 +0,0 @@
1
- // Usage-based unlock hints: surface bundles you keep getting a Touch ID prompt
2
- // for, so `agents secrets status` can suggest unlocking them once instead of
3
- // prompting on every read. Built on the existing `secrets.get` audit events
4
- // (events.jsonl) — no new tracking. Pure + exported so the threshold/filter
5
- // logic is unit-testable without the events sink or the keychain.
6
- /**
7
- * From recent `secrets.get` records, find bundles read often enough via the
8
- * keychain (i.e. NOT served silently by the broker or a durable session) to be
9
- * worth unlocking once. A bundle currently `held` is already silent, so it is
10
- * excluded; the caller further drops `never`/no-ACL bundles (which never prompt,
11
- * so unlocking them is a no-op).
12
- *
13
- * @param records recent `secrets.get` events (any order)
14
- * @param held bundle names the broker currently holds (silent reads)
15
- * @param opts.minReads minimum prompting reads to surface a bundle (default 3)
16
- */
17
- export function frequentlyPromptedBundles(records, held, opts = {}) {
18
- const minReads = opts.minReads ?? 3;
19
- const counts = new Map();
20
- for (const r of records) {
21
- if (!r.bundle)
22
- continue;
23
- // A broker hit or a durable-session read raised no Touch ID sheet — only a
24
- // read that fell through to the keychain did. Count those; they are the ones
25
- // an unlock would silence.
26
- if (r.source === 'agent' || r.source === 'session')
27
- continue;
28
- if (held.has(r.bundle))
29
- continue;
30
- counts.set(r.bundle, (counts.get(r.bundle) ?? 0) + 1);
31
- }
32
- return [...counts.entries()]
33
- .filter(([, n]) => n >= minReads)
34
- .map(([name, count]) => ({ name, count }))
35
- .sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
36
- }
@@ -1,46 +0,0 @@
1
- /**
2
- * Secrets usage read-model — thin adapter over the analytics usage warehouse
3
- * (`kind=secret`). Kept so `secrets view` / `list` / `activity` keep their API
4
- * while the durable store lives at ~/.agents/.history/analytics/usage.db.
5
- */
6
- export type SecretUsageEvent = 'access' | 'unlock' | 'import' | 'export' | 'create' | 'view';
7
- export declare const SECRET_USAGE_EVENTS: readonly SecretUsageEvent[];
8
- export interface RecordUsageParams {
9
- bundle: string;
10
- event: SecretUsageEvent;
11
- agent?: string;
12
- host?: string;
13
- source?: string;
14
- status?: 'success' | 'error';
15
- keyCount?: number;
16
- }
17
- export interface UsageStat {
18
- count: number;
19
- last: string | null;
20
- }
21
- export interface BundleUsageSummary {
22
- bundle: string;
23
- total: number;
24
- events: Record<SecretUsageEvent, UsageStat>;
25
- lastUsedAt: string | null;
26
- firstUsedAt: string | null;
27
- byAgent: Array<{
28
- agent: string;
29
- count: number;
30
- }>;
31
- }
32
- export interface SecretUsageHistoryEntry {
33
- ts: string;
34
- bundle: string;
35
- event: SecretUsageEvent;
36
- agent: string | null;
37
- host: string | null;
38
- source: string | null;
39
- status: string | null;
40
- keyCount: number | null;
41
- }
42
- export declare function recordSecretUsage(p: RecordUsageParams): void;
43
- export declare function getBundleUsage(bundle: string): BundleUsageSummary | undefined;
44
- export declare function getAllBundleUsage(): Map<string, BundleUsageSummary>;
45
- export declare function getUsageHistory(bundle: string | undefined, limit?: number): SecretUsageHistoryEntry[];
46
- export declare function closeSecretsUsageDb(): void;
@@ -1,96 +0,0 @@
1
- /**
2
- * Secrets usage read-model — thin adapter over the analytics usage warehouse
3
- * (`kind=secret`). Kept so `secrets view` / `list` / `activity` keep their API
4
- * while the durable store lives at ~/.agents/.history/analytics/usage.db.
5
- */
6
- import { recordUsage, getSecretBundleRollup, getSecretBundleAgents, getAllSecretBundleRollups, getSecretHistory, closeUsageDb, } from '../analytics/usage-db.js';
7
- export const SECRET_USAGE_EVENTS = [
8
- 'access',
9
- 'unlock',
10
- 'import',
11
- 'export',
12
- 'create',
13
- 'view',
14
- ];
15
- function emptyEvents() {
16
- return {
17
- access: { count: 0, last: null },
18
- unlock: { count: 0, last: null },
19
- import: { count: 0, last: null },
20
- export: { count: 0, last: null },
21
- create: { count: 0, last: null },
22
- view: { count: 0, last: null },
23
- };
24
- }
25
- function toSummary(bundle, rows, byAgent) {
26
- const events = emptyEvents();
27
- let total = 0;
28
- let lastUsedAt = null;
29
- let firstUsedAt = null;
30
- for (const r of rows) {
31
- if (r.event in events) {
32
- const stat = events[r.event];
33
- stat.count = r.n;
34
- stat.last = r.last;
35
- }
36
- total += r.n;
37
- if (r.last && (!lastUsedAt || r.last > lastUsedAt))
38
- lastUsedAt = r.last;
39
- if (r.first && (!firstUsedAt || r.first < firstUsedAt))
40
- firstUsedAt = r.first;
41
- }
42
- return { bundle, total, events, lastUsedAt, firstUsedAt, byAgent };
43
- }
44
- export function recordSecretUsage(p) {
45
- if (!p.bundle)
46
- return;
47
- const meta = {};
48
- if (p.keyCount != null)
49
- meta.keyCount = p.keyCount;
50
- if (p.host)
51
- meta.host = p.host;
52
- recordUsage({
53
- kind: 'secret',
54
- name: p.bundle,
55
- event: p.event,
56
- agent: p.agent,
57
- source: p.source,
58
- status: p.status,
59
- meta: Object.keys(meta).length ? meta : undefined,
60
- });
61
- }
62
- export function getBundleUsage(bundle) {
63
- const rows = getSecretBundleRollup(bundle);
64
- if (rows.length === 0)
65
- return undefined;
66
- const agents = getSecretBundleAgents(bundle);
67
- return toSummary(bundle, rows, agents.map((a) => ({ agent: a.agent, count: a.n })));
68
- }
69
- export function getAllBundleUsage() {
70
- const out = new Map();
71
- const rows = getAllSecretBundleRollups();
72
- const byBundle = new Map();
73
- for (const r of rows) {
74
- const list = byBundle.get(r.name) ?? [];
75
- list.push(r);
76
- byBundle.set(r.name, list);
77
- }
78
- for (const [bundle, list] of byBundle)
79
- out.set(bundle, toSummary(bundle, list, []));
80
- return out;
81
- }
82
- export function getUsageHistory(bundle, limit = 20) {
83
- return getSecretHistory(bundle, limit).map((r) => ({
84
- ts: r.ts,
85
- bundle: r.bundle,
86
- event: r.event,
87
- agent: r.agent,
88
- host: r.host,
89
- source: r.source,
90
- status: r.status,
91
- keyCount: r.keyCount,
92
- }));
93
- }
94
- export function closeSecretsUsageDb() {
95
- closeUsageDb();
96
- }
@@ -1 +0,0 @@
1
- export declare function runVaultAgeHelperCli(): Promise<void>;
@@ -1,34 +0,0 @@
1
- import { Decrypter, Encrypter, armor } from 'age-encryption';
2
- async function readStdin() {
3
- const chunks = [];
4
- for await (const chunk of process.stdin) {
5
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
6
- }
7
- return Buffer.concat(chunks).toString('utf8');
8
- }
9
- async function runAge(input) {
10
- if (input.action === 'encrypt') {
11
- const encrypter = new Encrypter();
12
- if (input.scryptWorkFactor !== undefined) {
13
- encrypter.setScryptWorkFactor(input.scryptWorkFactor);
14
- }
15
- encrypter.setPassphrase(input.passphrase);
16
- return armor.encode(await encrypter.encrypt(input.plaintext ?? ''));
17
- }
18
- if (input.action === 'decrypt') {
19
- const decrypter = new Decrypter();
20
- decrypter.addPassphrase(input.passphrase);
21
- return await decrypter.decrypt(armor.decode(input.blob ?? ''), 'text');
22
- }
23
- throw new Error('unknown action');
24
- }
25
- export async function runVaultAgeHelperCli() {
26
- try {
27
- const input = JSON.parse(await readStdin());
28
- process.stdout.write(await runAge(input));
29
- }
30
- catch (err) {
31
- console.error(err instanceof Error ? err.message : String(err));
32
- process.exitCode = 1;
33
- }
34
- }
@@ -1,49 +0,0 @@
1
- export interface VaultKey {
2
- passphrase: string;
3
- }
4
- export interface VaultBundleRecord {
5
- metadata?: string;
6
- keys: Record<string, string>;
7
- }
8
- export interface VaultData {
9
- v: 1;
10
- bundles: Record<string, VaultBundleRecord>;
11
- }
12
- export declare const VAULT_SESSION_TTL_MS: number;
13
- export declare const VAULT_SCRYPT_WORK_FACTOR = 18;
14
- export declare function vaultPath(): string;
15
- export declare function vaultExists(): boolean;
16
- export declare function _setVaultPathForTest(filePath: string | null): void;
17
- export declare function _clearVaultDataCacheForTest(): void;
18
- export declare function _resetVaultAgeOperationCountForTest(): void;
19
- export declare function _getVaultAgeOperationCountForTest(): number;
20
- export declare function _setVaultAgeHelperLaunchForTest(launch: {
21
- command: string;
22
- args: string[];
23
- } | null): void;
24
- export declare function encrypt(key: VaultKey, json: VaultData): string;
25
- export declare function decrypt(key: VaultKey, blob: string): VaultData;
26
- export declare function createVault(password: string, opts?: {
27
- overwrite?: boolean;
28
- }): VaultKey;
29
- export declare function joinVault(password: string, sourcePath: string, opts?: {
30
- overwrite?: boolean;
31
- }): VaultKey;
32
- export declare function unlock(password: string): VaultKey;
33
- export declare function cacheVaultKey(key: VaultKey, ttlMs?: number): void;
34
- export declare function clearVaultKey(): void;
35
- export declare function getVaultSession(): {
36
- loggedIn: true;
37
- key: VaultKey;
38
- expiresAt: number;
39
- } | {
40
- loggedIn: false;
41
- expiresAt?: number;
42
- };
43
- export declare function vaultHasItem(item: string): boolean;
44
- export declare function vaultGetItem(item: string): string;
45
- export declare function vaultGetItems(items: string[]): Map<string, string>;
46
- export declare function vaultSetItem(item: string, value: string): void;
47
- export declare function vaultSetItems(items: Map<string, string>): void;
48
- export declare function vaultDeleteItem(item: string): boolean;
49
- export declare function vaultListItems(prefix: string): string[];