@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,261 +0,0 @@
1
- /**
2
- * Filters for `agents secrets list`.
3
- *
4
- * The listing had no filtering at all: `--device`/`--device` pick a machine and
5
- * `--json` picks a format, but nothing selected over the bundles themselves. On
6
- * a fleet with fifty-odd bundles the answerable questions — which ones read with
7
- * no Touch ID at all, which hold a raw literal, which have already expired, what
8
- * has not been touched in three months — meant piping the table through grep, or
9
- * were simply unanswerable.
10
- *
11
- * Everything here is pure: parsing produces a `SecretsListFilter`, and
12
- * `bundleMatchesFilter` is a predicate over a bundle plus the ambient facts it
13
- * cannot derive itself (which bundles the broker currently holds, and the
14
- * current time). That keeps the whole surface unit-testable without a keychain.
15
- *
16
- * Shape follows the `agents sessions` house style: comma-separated lists, an
17
- * unknown value is a loud error naming the valid set (never a silent ignore),
18
- * and every axis narrows independently so they AND-compose.
19
- */
20
- import { parseCommaSeparatedList } from '../format.js';
21
- // From the leaf module, NOT discover.js — the latter imports `../sqlite.js`, so
22
- // reaching through it for one parser would pull node:sqlite into every `agents
23
- // secrets` invocation and print Node's SQLite ExperimentalWarning on stderr.
24
- import { parseTimeFilter } from '../text/relative-time.js';
25
- import { describeBundle, bundlePolicy, SECRET_TYPES, } from './bundles.js';
26
- /** Ref kinds a var's value can have, from `describeBundle`. */
27
- export const REF_KINDS = ['literal', 'keychain', 'env', 'file', 'exec'];
28
- const POLICIES = ['always', 'hold', 'never'];
29
- const BACKENDS = ['keychain', 'file', 'vault'];
30
- /** Default window for `--expiring` with no argument — matches the EXPIRING column. */
31
- export const DEFAULT_EXPIRING_DAYS = 30;
32
- /**
33
- * Validate one comma-separated enum list. An unknown value throws and names the
34
- * whole valid set — a silent ignore would let `--policy hodl` quietly return
35
- * every bundle, which reads as "nothing matches that" and is worse than an error.
36
- * Values are lowercased, matching `parsePolicyOpt`'s handling of policy names.
37
- */
38
- export function parseEnumList(raw, flag, valid) {
39
- const parts = parseCommaSeparatedList(raw).map((s) => s.toLowerCase());
40
- if (parts.length === 0) {
41
- throw new Error(`${flag} requires at least one value. Valid values: ${valid.join(', ')}`);
42
- }
43
- for (const p of parts) {
44
- if (!valid.includes(p)) {
45
- throw new Error(`Invalid value "${p}" for ${flag}. Valid values: ${valid.join(', ')}`);
46
- }
47
- }
48
- // De-dup so `--policy hold,hold` behaves like `--policy hold`.
49
- return [...new Set(parts)];
50
- }
51
- /** Parse `--expiring` — a bare flag means the default window, a value means N days. */
52
- function parseExpiringDays(raw) {
53
- if (raw === undefined || raw === false)
54
- return undefined;
55
- if (raw === true || raw === '')
56
- return DEFAULT_EXPIRING_DAYS;
57
- const n = Number(raw);
58
- // 0 is rejected rather than accepted as a no-op: the window is `0 <= d < N`,
59
- // so `--expiring 0` can never match anything, not even a key expiring today.
60
- // A flag that silently returns nothing is worse than one that says why.
61
- if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) {
62
- throw new Error(`Invalid --expiring '${raw}'. Use a whole number of days >= 1, e.g. --expiring 7. ` +
63
- 'For keys that have already lapsed, use --expired.');
64
- }
65
- return n;
66
- }
67
- /** Build a validated filter from commander's option bag. Throws on bad input. */
68
- export function parseListFilters(opts, query) {
69
- if (opts.held && opts.notHeld) {
70
- throw new Error('--held and --not-held are mutually exclusive');
71
- }
72
- const filter = {};
73
- const trimmedQuery = query?.trim();
74
- if (trimmedQuery)
75
- filter.query = trimmedQuery.toLowerCase();
76
- if (opts.policy)
77
- filter.policy = parseEnumList(opts.policy, '--policy', POLICIES);
78
- if (opts.backend)
79
- filter.backend = parseEnumList(opts.backend, '--backend', BACKENDS);
80
- if (opts.type)
81
- filter.type = parseEnumList(opts.type, '--type', SECRET_TYPES);
82
- if (opts.kind)
83
- filter.kind = parseEnumList(opts.kind, '--kind', REF_KINDS);
84
- if (opts.held)
85
- filter.held = true;
86
- if (opts.notHeld)
87
- filter.held = false;
88
- if (opts.expired)
89
- filter.expired = true;
90
- const expiringDays = parseExpiringDays(opts.expiring);
91
- if (expiringDays !== undefined)
92
- filter.expiringDays = expiringDays;
93
- if (opts.unused) {
94
- // parseTimeFilter turns '90d' into "the epoch-ms 90 days ago"; a bundle is
95
- // unused when its last_used predates that instant.
96
- const cutoff = parseTimeFilter(opts.unused);
97
- if (!cutoff) {
98
- throw new Error(`Invalid --unused '${opts.unused}'. Use e.g. 30d, 4w, 3mo, or an ISO date.`);
99
- }
100
- filter.unusedBefore = cutoff;
101
- }
102
- return filter;
103
- }
104
- /** True when any axis is set — used to decide whether the empty state should
105
- * explain itself rather than claim there are no bundles at all. */
106
- export function filterIsActive(f) {
107
- return Object.keys(f).length > 0;
108
- }
109
- /** Whole days from now until end-of-day UTC of an ISO date. Negative once past.
110
- * Mirrors the `daysUntil` used by the human render so the filter and the column
111
- * can never disagree about whether something has expired. */
112
- function daysUntil(iso, now) {
113
- const target = new Date(`${iso}T23:59:59Z`).getTime();
114
- return Math.floor((target - now) / (24 * 60 * 60 * 1000));
115
- }
116
- /** Expiry tallies for one bundle: how many vars are already past, and how many
117
- * fall due within `withinDays`. */
118
- export function bundleExpiry(b, now, withinDays = DEFAULT_EXPIRING_DAYS) {
119
- let expired = 0;
120
- let soon = 0;
121
- for (const m of Object.values(b.meta ?? {})) {
122
- if (!m.expires)
123
- continue;
124
- const d = daysUntil(m.expires, now);
125
- if (d < 0)
126
- expired++;
127
- else if (d < withinDays)
128
- soon++;
129
- }
130
- return { expired, soon };
131
- }
132
- /** Does this bundle satisfy every set axis? Pure. */
133
- export function bundleMatchesFilter(b, f, ctx) {
134
- if (f.query) {
135
- const haystack = `${b.name} ${b.description ?? ''}`.toLowerCase();
136
- if (!haystack.includes(f.query))
137
- return false;
138
- }
139
- if (f.policy && !f.policy.includes(bundlePolicy(b)))
140
- return false;
141
- if (f.backend && !f.backend.includes(b.backend ?? 'keychain'))
142
- return false;
143
- if (f.held !== undefined) {
144
- // A lapsed entry is not held — same liveness rule the POLICY column uses.
145
- const exp = ctx.held.get(b.name);
146
- const isHeld = exp !== undefined && exp > ctx.now;
147
- if (isHeld !== f.held)
148
- return false;
149
- }
150
- if (f.type) {
151
- const types = Object.values(b.meta ?? {}).map((m) => m.type).filter(Boolean);
152
- if (!types.some((t) => f.type.includes(t)))
153
- return false;
154
- }
155
- if (f.kind) {
156
- const kinds = describeBundle(b).map((e) => e.kind);
157
- if (!kinds.some((k) => f.kind.includes(k)))
158
- return false;
159
- }
160
- if (f.expired || f.expiringDays !== undefined) {
161
- const { expired, soon } = bundleExpiry(b, ctx.now, f.expiringDays ?? DEFAULT_EXPIRING_DAYS);
162
- if (f.expired && expired === 0)
163
- return false;
164
- if (f.expiringDays !== undefined && soon === 0)
165
- return false;
166
- }
167
- if (f.unusedBefore !== undefined) {
168
- // Never used counts as unused — it is the strongest form of the answer.
169
- if (b.last_used && new Date(b.last_used).getTime() >= f.unusedBefore)
170
- return false;
171
- }
172
- return true;
173
- }
174
- /** Sort fields for `--sort`. `name` is the default and matches `listBundles()`.
175
- * `used` is most-recently-used first and `uses` is most-frequently-accessed
176
- * first; both read the value-free usage read-model (lib/secrets/usage-db.ts). */
177
- export const SORT_FIELDS = ['name', 'used', 'uses', 'created', 'updated', 'expiry'];
178
- export function parseSortField(raw) {
179
- if (!raw)
180
- return 'name';
181
- const v = raw.toLowerCase();
182
- if (!SORT_FIELDS.includes(v)) {
183
- throw new Error(`Invalid --sort '${raw}'. Valid values: ${SORT_FIELDS.join(', ')}`);
184
- }
185
- return v;
186
- }
187
- /** Epoch-ms of a bundle's soonest expiry, or Infinity when nothing expires — so
188
- * `--sort expiry` puts the most urgent first and never-expiring bundles last. */
189
- function soonestExpiry(b) {
190
- let soonest = Infinity;
191
- for (const m of Object.values(b.meta ?? {})) {
192
- if (!m.expires)
193
- continue;
194
- const t = new Date(`${m.expires}T23:59:59Z`).getTime();
195
- if (t < soonest)
196
- soonest = t;
197
- }
198
- return soonest;
199
- }
200
- /** Sort a copy. Time fields are most-recent-first (the useful direction for
201
- * "what did I touch lately"); `uses` is most-frequently-accessed first; `expiry`
202
- * is soonest-first; ties fall back to name so the order is stable. `usage`
203
- * carries the value-free read-model facts `used`/`uses` rank on — when omitted,
204
- * `used` falls back to the throttled `last_used` stamp and `uses` sees zero. */
205
- export function sortBundles(bundles, field, usage) {
206
- const stamp = (iso) => (iso ? new Date(iso).getTime() : 0);
207
- const byName = (a, z) => a.name.localeCompare(z.name);
208
- // `used` combines the throttled keychain stamp with the exact usage-DB recency
209
- // so a just-recorded access ranks a bundle even before its stamp catches up.
210
- const usedMs = (b) => Math.max(stamp(b.last_used), stamp(usage?.get(b.name)?.lastUsedAt));
211
- const usesOf = (b) => usage?.get(b.name)?.uses ?? 0;
212
- const out = [...bundles];
213
- if (field === 'name')
214
- return out.sort(byName);
215
- out.sort((a, z) => {
216
- if (field === 'expiry') {
217
- const d = soonestExpiry(a) - soonestExpiry(z);
218
- return d !== 0 ? d : byName(a, z);
219
- }
220
- if (field === 'used') {
221
- const d = usedMs(z) - usedMs(a);
222
- return d !== 0 ? d : byName(a, z);
223
- }
224
- if (field === 'uses') {
225
- const d = usesOf(z) - usesOf(a);
226
- return d !== 0 ? d : byName(a, z);
227
- }
228
- const key = field === 'created' ? 'created_at' : 'updated_at';
229
- const d = stamp(z[key]) - stamp(a[key]);
230
- return d !== 0 ? d : byName(a, z);
231
- });
232
- return out;
233
- }
234
- /** Human summary of the active filters, for the empty state. `sessions` only
235
- * echoes --project/--all on a miss, which leaves you guessing which flag emptied
236
- * the list; naming every active axis is the difference between "nothing matched"
237
- * and knowing what to relax. */
238
- export function describeFilter(f) {
239
- const parts = [];
240
- if (f.query)
241
- parts.push(`matching "${f.query}"`);
242
- if (f.policy)
243
- parts.push(`policy ${f.policy.join('/')}`);
244
- if (f.backend)
245
- parts.push(`backend ${f.backend.join('/')}`);
246
- if (f.type)
247
- parts.push(`type ${f.type.join('/')}`);
248
- if (f.kind)
249
- parts.push(`kind ${f.kind.join('/')}`);
250
- if (f.held === true)
251
- parts.push('currently held');
252
- if (f.held === false)
253
- parts.push('not currently held');
254
- if (f.expired)
255
- parts.push('with an expired key');
256
- if (f.expiringDays !== undefined)
257
- parts.push(`expiring within ${f.expiringDays}d`);
258
- if (f.unusedBefore !== undefined)
259
- parts.push('unused since the given cutoff');
260
- return parts.join(', ');
261
- }
@@ -1,93 +0,0 @@
1
- /**
2
- * Zero-knowledge / JIT secret delivery over MCP (issue #333).
3
- *
4
- * A stdio MCP server exposing a single `get_secret(bundle, key)` tool. Unlike
5
- * `agents secrets exec`, which spawns the child process with EVERY resolved
6
- * value baked into `process.env` (any subprocess can `printenv` the lot — see
7
- * buildSecretsExecEnv in src/commands/secrets.ts), this server hands an
8
- * MCP-speaking agent framework ONE credential at a time, by name, at call
9
- * time. The raw value is returned only inside the tool result: it never enters
10
- * the child's environment, is never logged, and never appears in `tools/list`
11
- * metadata (names only).
12
- *
13
- * Resolution reuses the canonical bundle read path
14
- * (readAndResolveBundleEnv -> getKeychainToken); no keychain access is
15
- * re-implemented here. On Linux the libsecret backend (or its encrypted-file
16
- * fallback for locked/headless collections) applies transparently through that
17
- * abstraction, so the server stays backend-agnostic and works unchanged on
18
- * macOS.
19
- *
20
- * Transport: the MCP stdio transport is newline-delimited JSON-RPC 2.0 (one
21
- * message per line, UTF-8, no embedded newlines). That framing is trivial, so
22
- * we implement it directly rather than pull in the @modelcontextprotocol/sdk
23
- * server dependency — keeping the tree dependency-free and the handler pure and
24
- * unit-testable in-process.
25
- */
26
- /** MCP protocol revision this server negotiates. */
27
- export declare const MCP_PROTOCOL_VERSION = "2024-11-05";
28
- /** The single tool this server exposes. */
29
- export declare const GET_SECRET_TOOL = "get_secret";
30
- /** serverInfo.name reported at `initialize`. */
31
- export declare const MCP_SERVER_NAME = "agents-secrets";
32
- export interface JsonRpcRequest {
33
- jsonrpc?: string;
34
- id?: string | number | null;
35
- method?: string;
36
- params?: unknown;
37
- }
38
- interface JsonRpcSuccess {
39
- jsonrpc: '2.0';
40
- id: string | number | null;
41
- result: unknown;
42
- }
43
- interface JsonRpcErrorResponse {
44
- jsonrpc: '2.0';
45
- id: string | number | null;
46
- error: {
47
- code: number;
48
- message: string;
49
- };
50
- }
51
- export type JsonRpcResponse = JsonRpcSuccess | JsonRpcErrorResponse;
52
- /** Per-bundle metadata for `tools/list` — names only, NEVER values. */
53
- export interface BundleKeyMetadata {
54
- bundle: string;
55
- description?: string;
56
- keys: string[];
57
- }
58
- /**
59
- * Enumerate every bundle and its key names WITHOUT resolving or reading any
60
- * value. Mirrors how `agents secrets list` surfaces bundle metadata: names are
61
- * safe to expose, values are not. `listBundles()` reads only the bundle
62
- * metadata items (var definitions), never the secret items behind them.
63
- */
64
- export declare function listSecretMetadata(): BundleKeyMetadata[];
65
- /**
66
- * Resolve a single secret value by bundle + key through the canonical read
67
- * path. Throws a clear error when the bundle or the key is absent.
68
- *
69
- * The bundle metadata is read first (readBundle) so a missing key is reported
70
- * without triggering resolution — and, on macOS, without prompting Touch ID —
71
- * for the other keys in the bundle.
72
- */
73
- export declare function resolveSecret(bundle: string, key: string): string;
74
- /**
75
- * Handle one parsed JSON-RPC request. Returns the response object, or `null`
76
- * for notifications (which take no reply). Pure — no I/O, no logging — so tests
77
- * drive it directly.
78
- */
79
- export declare function handleMcpRequest(req: JsonRpcRequest, ctx?: {
80
- version?: string;
81
- }): JsonRpcResponse | null;
82
- /**
83
- * Run the stdio MCP server: read newline-delimited JSON-RPC requests from
84
- * `input`, write responses to `output`. Resolves when the input stream ends.
85
- * Streams are injectable so tests can drive a full request/response loop
86
- * without spawning a process.
87
- */
88
- export declare function runSecretsMcpServer(opts?: {
89
- input?: NodeJS.ReadableStream;
90
- output?: NodeJS.WritableStream;
91
- version?: string;
92
- }): Promise<void>;
93
- export {};
@@ -1,211 +0,0 @@
1
- /**
2
- * Zero-knowledge / JIT secret delivery over MCP (issue #333).
3
- *
4
- * A stdio MCP server exposing a single `get_secret(bundle, key)` tool. Unlike
5
- * `agents secrets exec`, which spawns the child process with EVERY resolved
6
- * value baked into `process.env` (any subprocess can `printenv` the lot — see
7
- * buildSecretsExecEnv in src/commands/secrets.ts), this server hands an
8
- * MCP-speaking agent framework ONE credential at a time, by name, at call
9
- * time. The raw value is returned only inside the tool result: it never enters
10
- * the child's environment, is never logged, and never appears in `tools/list`
11
- * metadata (names only).
12
- *
13
- * Resolution reuses the canonical bundle read path
14
- * (readAndResolveBundleEnv -> getKeychainToken); no keychain access is
15
- * re-implemented here. On Linux the libsecret backend (or its encrypted-file
16
- * fallback for locked/headless collections) applies transparently through that
17
- * abstraction, so the server stays backend-agnostic and works unchanged on
18
- * macOS.
19
- *
20
- * Transport: the MCP stdio transport is newline-delimited JSON-RPC 2.0 (one
21
- * message per line, UTF-8, no embedded newlines). That framing is trivial, so
22
- * we implement it directly rather than pull in the @modelcontextprotocol/sdk
23
- * server dependency — keeping the tree dependency-free and the handler pure and
24
- * unit-testable in-process.
25
- */
26
- import * as readline from 'readline';
27
- import { listBundles, readAndResolveBundleEnv, readBundle, validateBundleName, } from './bundles.js';
28
- /** MCP protocol revision this server negotiates. */
29
- export const MCP_PROTOCOL_VERSION = '2024-11-05';
30
- /** The single tool this server exposes. */
31
- export const GET_SECRET_TOOL = 'get_secret';
32
- /** serverInfo.name reported at `initialize`. */
33
- export const MCP_SERVER_NAME = 'agents-secrets';
34
- // JSON-RPC 2.0 error codes we use.
35
- const JSONRPC_PARSE_ERROR = -32700;
36
- const JSONRPC_INVALID_REQUEST = -32600;
37
- const JSONRPC_METHOD_NOT_FOUND = -32601;
38
- const JSONRPC_INVALID_PARAMS = -32602;
39
- /**
40
- * Enumerate every bundle and its key names WITHOUT resolving or reading any
41
- * value. Mirrors how `agents secrets list` surfaces bundle metadata: names are
42
- * safe to expose, values are not. `listBundles()` reads only the bundle
43
- * metadata items (var definitions), never the secret items behind them.
44
- */
45
- export function listSecretMetadata() {
46
- return listBundles()
47
- .map((b) => ({
48
- bundle: b.name,
49
- description: b.description,
50
- keys: Object.keys(b.vars).sort(),
51
- }))
52
- .sort((a, b) => a.bundle.localeCompare(b.bundle));
53
- }
54
- /**
55
- * Resolve a single secret value by bundle + key through the canonical read
56
- * path. Throws a clear error when the bundle or the key is absent.
57
- *
58
- * The bundle metadata is read first (readBundle) so a missing key is reported
59
- * without triggering resolution — and, on macOS, without prompting Touch ID —
60
- * for the other keys in the bundle.
61
- */
62
- export function resolveSecret(bundle, key) {
63
- validateBundleName(bundle);
64
- // readBundle throws `Secrets bundle '<name>' not found.` for a missing bundle.
65
- const meta = readBundle(bundle);
66
- if (!Object.prototype.hasOwnProperty.call(meta.vars, key)) {
67
- const available = Object.keys(meta.vars).sort();
68
- throw new Error(`Key '${key}' not found in bundle '${bundle}'.` +
69
- (available.length ? ` Available keys: ${available.join(', ')}.` : ' Bundle has no keys.'));
70
- }
71
- // An MCP `get_secret` tool call is a program asking for a value, never a human
72
- // at a Touch ID sheet — so the read is always `agentOnly` (SEC-13: never pop
73
- // biometry on its own). A `never`/no-ACL or broker-held bundle resolves
74
- // silently; a locked bundle THROWS the actionable "unlock <name>" message,
75
- // which propagates as the MCP tool error (the caller surfaces it) rather than
76
- // popping an unanswerable prompt.
77
- const { env } = readAndResolveBundleEnv(bundle, { caller: 'secrets-mcp', keys: [key], keyMode: 'storage', agentOnly: true });
78
- const value = env[key];
79
- if (value === undefined) {
80
- throw new Error(`Key '${key}' in bundle '${bundle}' could not be resolved.`);
81
- }
82
- return value;
83
- }
84
- /** Build the `get_secret` tool definition, advertising available names only. */
85
- function getSecretToolDefinition() {
86
- let catalog = '(no bundles configured)';
87
- try {
88
- const meta = listSecretMetadata();
89
- if (meta.length > 0) {
90
- catalog = meta
91
- .map((b) => `${b.bundle}: ${b.keys.length ? b.keys.join(', ') : '(no keys)'}`)
92
- .join(' | ');
93
- }
94
- }
95
- catch {
96
- // Listing is best-effort decoration; never fail tools/list because the
97
- // keychain enumeration hiccuped.
98
- catalog = '(unavailable)';
99
- }
100
- return {
101
- name: GET_SECRET_TOOL,
102
- description: 'Fetch a single secret value by bundle + key, resolved at call time. The value ' +
103
- 'is returned only in this tool result and never enters the process environment. ' +
104
- `Available bundles and keys (names only): ${catalog}`,
105
- inputSchema: {
106
- type: 'object',
107
- properties: {
108
- bundle: { type: 'string', description: 'Secrets bundle name' },
109
- key: { type: 'string', description: 'Key / env-var name within the bundle' },
110
- },
111
- required: ['bundle', 'key'],
112
- additionalProperties: false,
113
- },
114
- };
115
- }
116
- function success(id, result) {
117
- return { jsonrpc: '2.0', id, result };
118
- }
119
- function rpcError(id, code, message) {
120
- return { jsonrpc: '2.0', id, error: { code, message } };
121
- }
122
- /**
123
- * A tool-execution failure. Per the MCP spec, tool errors are reported IN the
124
- * result (`isError: true`) — not as a JSON-RPC error — so the model sees and
125
- * can react to the message. The value channel (`content`) still carries no
126
- * secret here.
127
- */
128
- function toolError(id, message) {
129
- return success(id, { content: [{ type: 'text', text: message }], isError: true });
130
- }
131
- function handleToolCall(id, params) {
132
- const p = (params ?? {});
133
- if (p.name !== GET_SECRET_TOOL) {
134
- return rpcError(id, JSONRPC_INVALID_PARAMS, `Unknown tool: ${String(p.name)}`);
135
- }
136
- const args = (p.arguments ?? {});
137
- if (typeof args.bundle !== 'string' || !args.bundle || typeof args.key !== 'string' || !args.key) {
138
- return toolError(id, `${GET_SECRET_TOOL} requires non-empty string 'bundle' and 'key' arguments.`);
139
- }
140
- try {
141
- const value = resolveSecret(args.bundle, args.key);
142
- return success(id, { content: [{ type: 'text', text: value }] });
143
- }
144
- catch (err) {
145
- return toolError(id, err.message);
146
- }
147
- }
148
- /**
149
- * Handle one parsed JSON-RPC request. Returns the response object, or `null`
150
- * for notifications (which take no reply). Pure — no I/O, no logging — so tests
151
- * drive it directly.
152
- */
153
- export function handleMcpRequest(req, ctx = {}) {
154
- const id = req.id ?? null;
155
- const isNotification = req.id === undefined || req.id === null;
156
- switch (req.method) {
157
- case 'initialize':
158
- return success(id, {
159
- protocolVersion: MCP_PROTOCOL_VERSION,
160
- capabilities: { tools: {} },
161
- serverInfo: { name: MCP_SERVER_NAME, version: ctx.version ?? '0.0.0' },
162
- });
163
- case 'notifications/initialized':
164
- case 'initialized':
165
- return null; // notification — no response
166
- case 'ping':
167
- return success(id, {});
168
- case 'tools/list':
169
- return success(id, { tools: [getSecretToolDefinition()] });
170
- case 'tools/call':
171
- return handleToolCall(id, req.params);
172
- default:
173
- if (isNotification)
174
- return null; // unknown notification — stay silent
175
- return rpcError(id, JSONRPC_METHOD_NOT_FOUND, `Method not found: ${String(req.method)}`);
176
- }
177
- }
178
- function writeMessage(output, msg) {
179
- output.write(JSON.stringify(msg) + '\n');
180
- }
181
- /**
182
- * Run the stdio MCP server: read newline-delimited JSON-RPC requests from
183
- * `input`, write responses to `output`. Resolves when the input stream ends.
184
- * Streams are injectable so tests can drive a full request/response loop
185
- * without spawning a process.
186
- */
187
- export async function runSecretsMcpServer(opts = {}) {
188
- const input = opts.input ?? process.stdin;
189
- const output = opts.output ?? process.stdout;
190
- const rl = readline.createInterface({ input, crlfDelay: Infinity });
191
- for await (const line of rl) {
192
- const trimmed = line.trim();
193
- if (!trimmed)
194
- continue;
195
- let req;
196
- try {
197
- req = JSON.parse(trimmed);
198
- }
199
- catch {
200
- writeMessage(output, rpcError(null, JSONRPC_PARSE_ERROR, 'Parse error'));
201
- continue;
202
- }
203
- if (!req || typeof req !== 'object' || typeof req.method !== 'string') {
204
- writeMessage(output, rpcError((req && req.id) ?? null, JSONRPC_INVALID_REQUEST, 'Invalid Request'));
205
- continue;
206
- }
207
- const res = handleMcpRequest(req, { version: opts.version });
208
- if (res)
209
- writeMessage(output, res);
210
- }
211
- }
@@ -1,10 +0,0 @@
1
- /**
2
- * Thin compatibility shim over the shared secrets library.
3
- *
4
- * Profile tokens are stored at `agents-cli.<provider>.token`; secrets bundles
5
- * live at `agents-cli.secrets.<bundle>.<KEY>`. Both go through the same
6
- * keychain primitives in ./index.ts.
7
- */
8
- export { hasKeychainToken, getKeychainToken, setKeychainToken, deleteKeychainToken, } from './index.js';
9
- /** Map a provider name to its keychain item identifier. */
10
- export declare function keychainItemName(provider: string): string;
@@ -1,13 +0,0 @@
1
- /**
2
- * Thin compatibility shim over the shared secrets library.
3
- *
4
- * Profile tokens are stored at `agents-cli.<provider>.token`; secrets bundles
5
- * live at `agents-cli.secrets.<bundle>.<KEY>`. Both go through the same
6
- * keychain primitives in ./index.ts.
7
- */
8
- export { hasKeychainToken, getKeychainToken, setKeychainToken, deleteKeychainToken, } from './index.js';
9
- import { profileKeychainItem } from './index.js';
10
- /** Map a provider name to its keychain item identifier. */
11
- export function keychainItemName(provider) {
12
- return profileKeychainItem(provider);
13
- }