@phnx-labs/agents-cli 1.22.84 → 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 (257) hide show
  1. package/CHANGELOG.md +57 -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/setup-accounts.d.ts +3 -2
  28. package/dist/commands/setup-accounts.js +4 -4
  29. package/dist/commands/setup-secrets.d.ts +16 -21
  30. package/dist/commands/setup-secrets.js +43 -224
  31. package/dist/commands/ssh.js +2 -2
  32. package/dist/commands/sync.js +1 -1
  33. package/dist/commands/update.d.ts +10 -0
  34. package/dist/commands/update.js +43 -40
  35. package/dist/commands/versions.d.ts +11 -11
  36. package/dist/commands/versions.js +131 -83
  37. package/dist/commands/view.d.ts +1 -9
  38. package/dist/commands/view.js +41 -55
  39. package/dist/commands/webhook.js +5 -5
  40. package/dist/commands/workflows.js +2 -1
  41. package/dist/index.d.ts +12 -12
  42. package/dist/index.js +13 -42
  43. package/dist/lib/account-capabilities.d.ts +1 -1
  44. package/dist/lib/account-capabilities.js +1 -1
  45. package/dist/lib/account-catalog.d.ts +121 -6
  46. package/dist/lib/account-catalog.js +421 -7
  47. package/dist/lib/account-registry.d.ts +8 -30
  48. package/dist/lib/account-registry.js +36 -79
  49. package/dist/lib/account-schema.d.ts +1 -1
  50. package/dist/lib/account-schema.js +1 -1
  51. package/dist/lib/accounting/account-pool-collect.js +16 -4
  52. package/dist/lib/accounting/rotate.d.ts +20 -7
  53. package/dist/lib/accounting/rotate.js +89 -13
  54. package/dist/lib/accounting/usage.js +11 -16
  55. package/dist/lib/accounts/add.d.ts +138 -0
  56. package/dist/lib/accounts/add.js +651 -0
  57. package/dist/lib/accounts/migrate.d.ts +117 -0
  58. package/dist/lib/accounts/migrate.js +536 -0
  59. package/dist/lib/accounts/slots.d.ts +13 -0
  60. package/dist/lib/accounts/slots.js +100 -0
  61. package/dist/lib/agent-spec/agents.d.ts +16 -0
  62. package/dist/lib/agent-spec/agents.js +32 -4
  63. package/dist/lib/app-bundle-install.js +5 -4
  64. package/dist/lib/auth-health.d.ts +3 -0
  65. package/dist/lib/auth-health.js +11 -0
  66. package/dist/lib/auth-mint.d.ts +52 -16
  67. package/dist/lib/auth-mint.js +117 -39
  68. package/dist/lib/browser/chrome.d.ts +1 -1
  69. package/dist/lib/browser/chrome.js +5 -5
  70. package/dist/lib/byok-usage.js +3 -3
  71. package/dist/lib/claude-account-token.d.ts +47 -3
  72. package/dist/lib/claude-account-token.js +172 -50
  73. package/dist/lib/cloud/antigravity.js +4 -4
  74. package/dist/lib/cloud/cursor.js +4 -4
  75. package/dist/lib/crabbox/cli.d.ts +1 -1
  76. package/dist/lib/crabbox/cli.js +6 -6
  77. package/dist/lib/crabbox/runtimes.d.ts +3 -3
  78. package/dist/lib/crabbox/runtimes.js +5 -5
  79. package/dist/lib/daemon/account-state-daemon-service.d.ts +37 -1
  80. package/dist/lib/daemon/account-state-daemon-service.js +199 -3
  81. package/dist/lib/daemon/auth-sync-service.js +28 -1
  82. package/dist/lib/daemon/daemon.d.ts +6 -16
  83. package/dist/lib/daemon/daemon.js +52 -97
  84. package/dist/lib/daemon/daemon.test-fixture.d.ts +7 -9
  85. package/dist/lib/daemon/daemon.test-fixture.js +22 -40
  86. package/dist/lib/daemon/harness-update-service.d.ts +1 -1
  87. package/dist/lib/daemon/harness-update-service.js +1 -1
  88. package/dist/lib/daemon/runner.d.ts +15 -3
  89. package/dist/lib/daemon/runner.js +49 -24
  90. package/dist/lib/daemon-health.d.ts +1 -2
  91. package/dist/lib/daemon-health.js +7 -6
  92. package/dist/lib/daemon-services.d.ts +1 -1
  93. package/dist/lib/daemon-services.js +1 -11
  94. package/dist/lib/daemon-webhooks.d.ts +8 -7
  95. package/dist/lib/daemon-webhooks.js +10 -9
  96. package/dist/lib/device-config.js +1 -1
  97. package/dist/lib/devices/doctor-findings.d.ts +1 -1
  98. package/dist/lib/devices/harness-inventory.d.ts +39 -0
  99. package/dist/lib/devices/harness-inventory.js +126 -4
  100. package/dist/lib/doctor-diff.js +2 -1
  101. package/dist/lib/exec-account-home.d.ts +38 -0
  102. package/dist/lib/exec-account-home.js +164 -0
  103. package/dist/lib/exec.d.ts +30 -0
  104. package/dist/lib/exec.js +101 -31
  105. package/dist/lib/fleet/apply.d.ts +2 -2
  106. package/dist/lib/fleet/apply.js +4 -4
  107. package/dist/lib/fleet/auth-sync.js +4 -3
  108. package/dist/lib/fleet-shared-repo-sync.js +6 -9
  109. package/dist/lib/harness/adapter.d.ts +7 -7
  110. package/dist/lib/harness/adapter.js +10 -6
  111. package/dist/lib/harness/adapters/grok.js +8 -3
  112. package/dist/lib/harness/adapters/muse.js +1 -1
  113. package/dist/lib/harness/adapters/opencode.js +12 -3
  114. package/dist/lib/harness-auth-capabilities.d.ts +61 -0
  115. package/dist/lib/harness-auth-capabilities.js +52 -0
  116. package/dist/lib/helper-versions.d.ts +6 -4
  117. package/dist/lib/helper-versions.js +5 -4
  118. package/dist/lib/hosts/credential-transport.d.ts +10 -0
  119. package/dist/lib/hosts/credential-transport.js +37 -0
  120. package/dist/lib/hosts/dispatch.d.ts +7 -0
  121. package/dist/lib/hosts/dispatch.js +13 -6
  122. package/dist/lib/identity/client.d.ts +3 -3
  123. package/dist/lib/identity/client.js +3 -3
  124. package/dist/lib/installations/index.d.ts +1 -1
  125. package/dist/lib/installations/index.js +1 -1
  126. package/dist/lib/installations/migrate.js +9 -0
  127. package/dist/lib/installations/resolve.js +1 -1
  128. package/dist/lib/installations/shims.d.ts +15 -0
  129. package/dist/lib/installations/shims.js +69 -0
  130. package/dist/lib/installations/store.d.ts +43 -0
  131. package/dist/lib/installations/store.js +75 -7
  132. package/dist/lib/installations/versions.js +1 -1
  133. package/dist/lib/menubar/install-menubar.d.ts +3 -3
  134. package/dist/lib/menubar/install-menubar.js +3 -3
  135. package/dist/lib/native-accounts.d.ts +35 -0
  136. package/dist/lib/native-accounts.js +41 -0
  137. package/dist/lib/net-close.d.ts +11 -0
  138. package/dist/lib/net-close.js +25 -0
  139. package/dist/lib/openclaw-keychain.js +27 -1
  140. package/dist/lib/profiles.js +22 -9
  141. package/dist/lib/project-resources.js +1 -1
  142. package/dist/lib/reserved-stores.d.ts +53 -0
  143. package/dist/lib/reserved-stores.js +120 -0
  144. package/dist/lib/secrets-client.d.ts +191 -0
  145. package/dist/lib/secrets-client.js +710 -0
  146. package/dist/lib/secrets-policy.d.ts +222 -0
  147. package/dist/lib/secrets-policy.js +483 -0
  148. package/dist/lib/secrets-types.d.ts +188 -0
  149. package/dist/lib/secrets-types.js +24 -0
  150. package/dist/lib/service-manifest.js +3 -4
  151. package/dist/lib/session/db.d.ts +18 -0
  152. package/dist/lib/session/db.js +47 -14
  153. package/dist/lib/session/sync/config.js +2 -2
  154. package/dist/lib/sha256-asset.d.ts +11 -16
  155. package/dist/lib/sha256-asset.js +11 -16
  156. package/dist/lib/share/config.js +14 -5
  157. package/dist/lib/signin-badge.d.ts +26 -0
  158. package/dist/lib/signin-badge.js +42 -0
  159. package/dist/lib/staleness/detectors/workflows.js +3 -105
  160. package/dist/lib/staleness/writers/workflows.js +2 -1
  161. package/dist/lib/state.d.ts +7 -4
  162. package/dist/lib/state.js +18 -7
  163. package/dist/lib/sync-umbrella.js +2 -2
  164. package/dist/lib/teams/agents.js +1 -1
  165. package/dist/lib/types.d.ts +74 -33
  166. package/dist/lib/view-types.d.ts +6 -3
  167. package/dist/lib/workflows-registry.d.ts +71 -0
  168. package/dist/lib/workflows-registry.js +280 -0
  169. package/dist/lib/workflows.d.ts +21 -21
  170. package/dist/lib/workflows.js +12 -327
  171. package/package.json +2 -3
  172. package/scripts/postinstall.js +4 -29
  173. package/dist/commands/secrets-import.d.ts +0 -18
  174. package/dist/commands/secrets-import.js +0 -74
  175. package/dist/commands/secrets-migrate.d.ts +0 -25
  176. package/dist/commands/secrets-migrate.js +0 -334
  177. package/dist/commands/secrets-rotate-passphrase.d.ts +0 -17
  178. package/dist/commands/secrets-rotate-passphrase.js +0 -96
  179. package/dist/commands/secrets-sync.d.ts +0 -11
  180. package/dist/commands/secrets-sync.js +0 -153
  181. package/dist/commands/secrets-vault.d.ts +0 -10
  182. package/dist/commands/secrets-vault.js +0 -130
  183. package/dist/commands/secrets.d.ts +0 -212
  184. package/dist/commands/secrets.js +0 -3030
  185. package/dist/lib/accounts/connect.d.ts +0 -176
  186. package/dist/lib/accounts/connect.js +0 -453
  187. package/dist/lib/daemon/keychain-reap-service.d.ts +0 -17
  188. package/dist/lib/daemon/keychain-reap-service.js +0 -32
  189. package/dist/lib/daemon/secrets-broker-service.d.ts +0 -21
  190. package/dist/lib/daemon/secrets-broker-service.js +0 -51
  191. package/dist/lib/secrets/agent.d.ts +0 -358
  192. package/dist/lib/secrets/agent.js +0 -1291
  193. package/dist/lib/secrets/audit.d.ts +0 -46
  194. package/dist/lib/secrets/audit.js +0 -101
  195. package/dist/lib/secrets/bundles.d.ts +0 -290
  196. package/dist/lib/secrets/bundles.js +0 -1547
  197. package/dist/lib/secrets/download-keychain.d.ts +0 -47
  198. package/dist/lib/secrets/download-keychain.js +0 -70
  199. package/dist/lib/secrets/drivers/rush.d.ts +0 -14
  200. package/dist/lib/secrets/drivers/rush.js +0 -90
  201. package/dist/lib/secrets/fallback.d.ts +0 -48
  202. package/dist/lib/secrets/fallback.js +0 -48
  203. package/dist/lib/secrets/filestore.d.ts +0 -222
  204. package/dist/lib/secrets/filestore.js +0 -1099
  205. package/dist/lib/secrets/headless.d.ts +0 -43
  206. package/dist/lib/secrets/headless.js +0 -56
  207. package/dist/lib/secrets/icloud-import.d.ts +0 -79
  208. package/dist/lib/secrets/icloud-import.js +0 -206
  209. package/dist/lib/secrets/index.d.ts +0 -451
  210. package/dist/lib/secrets/index.js +0 -1568
  211. package/dist/lib/secrets/install-helper.d.ts +0 -72
  212. package/dist/lib/secrets/install-helper.js +0 -245
  213. package/dist/lib/secrets/lease.d.ts +0 -25
  214. package/dist/lib/secrets/lease.js +0 -44
  215. package/dist/lib/secrets/linux.d.ts +0 -77
  216. package/dist/lib/secrets/linux.js +0 -393
  217. package/dist/lib/secrets/list-filter.d.ts +0 -109
  218. package/dist/lib/secrets/list-filter.js +0 -261
  219. package/dist/lib/secrets/mcp.d.ts +0 -93
  220. package/dist/lib/secrets/mcp.js +0 -211
  221. package/dist/lib/secrets/profiles.d.ts +0 -10
  222. package/dist/lib/secrets/profiles.js +0 -13
  223. package/dist/lib/secrets/push.d.ts +0 -133
  224. package/dist/lib/secrets/push.js +0 -273
  225. package/dist/lib/secrets/rc-hygiene.d.ts +0 -63
  226. package/dist/lib/secrets/rc-hygiene.js +0 -143
  227. package/dist/lib/secrets/read-backoff.d.ts +0 -27
  228. package/dist/lib/secrets/read-backoff.js +0 -64
  229. package/dist/lib/secrets/reaper.d.ts +0 -90
  230. package/dist/lib/secrets/reaper.js +0 -243
  231. package/dist/lib/secrets/remote.d.ts +0 -152
  232. package/dist/lib/secrets/remote.js +0 -344
  233. package/dist/lib/secrets/reserved-sync.d.ts +0 -66
  234. package/dist/lib/secrets/reserved-sync.js +0 -147
  235. package/dist/lib/secrets/scope.d.ts +0 -26
  236. package/dist/lib/secrets/scope.js +0 -29
  237. package/dist/lib/secrets/session-store.d.ts +0 -107
  238. package/dist/lib/secrets/session-store.js +0 -342
  239. package/dist/lib/secrets/sync-backend.d.ts +0 -48
  240. package/dist/lib/secrets/sync-backend.js +0 -13
  241. package/dist/lib/secrets/sync-commands.d.ts +0 -21
  242. package/dist/lib/secrets/sync-commands.js +0 -21
  243. package/dist/lib/secrets/sync.d.ts +0 -48
  244. package/dist/lib/secrets/sync.js +0 -237
  245. package/dist/lib/secrets/unlock-hints.d.ts +0 -27
  246. package/dist/lib/secrets/unlock-hints.js +0 -36
  247. package/dist/lib/secrets/usage-db.d.ts +0 -46
  248. package/dist/lib/secrets/usage-db.js +0 -96
  249. package/dist/lib/secrets/vault-age-helper.d.ts +0 -1
  250. package/dist/lib/secrets/vault-age-helper.js +0 -34
  251. package/dist/lib/secrets/vault.d.ts +0 -49
  252. package/dist/lib/secrets/vault.js +0 -399
  253. package/dist/lib/secrets/windows.d.ts +0 -81
  254. package/dist/lib/secrets/windows.js +0 -556
  255. package/scripts/install-helper.js +0 -97
  256. /package/dist/lib/{secrets/sync-passphrase.d.ts → sync-passphrase.d.ts} +0 -0
  257. /package/dist/lib/{secrets/sync-passphrase.js → sync-passphrase.js} +0 -0
@@ -1,393 +0,0 @@
1
- /**
2
- * Linux secret storage via libsecret (GNOME Keyring / Secret Service API).
3
- *
4
- * Primary backend: `secret-tool` CLI (libsecret-tools package).
5
- *
6
- * Headless fallback: when the default Secret Service collection is locked
7
- * (common on server-class Linux — no graphical login means the keyring
8
- * passphrase never enters the daemon, so `secret-tool store` fails with
9
- * "Cannot create an item in a locked collection"), we transparently switch
10
- * to the AES-256-GCM encrypted-file store in ./filestore.ts. The decision is
11
- * cached per process; one stderr line is emitted the first time the fallback
12
- * activates.
13
- *
14
- * Secrets stored via secret-tool use:
15
- * service = "agents-cli"
16
- * account = username
17
- * item = the secret identifier
18
- */
19
- import { spawnSync } from 'child_process';
20
- import * as os from 'os';
21
- import { fileStore, fileDir, fileStoreHasItems, machinePassphraseExists, _resetFileStoreForTest, } from './filestore.js';
22
- import { noteNativeShadow, _resetFallbackNoticeForTest, } from './fallback.js';
23
- // Re-exported so existing importers (and tests) can keep reaching these via
24
- // './linux.js'. The implementations live in ./filestore.ts.
25
- export { encryptForFallback, decryptForFallback, fileBackend, } from './filestore.js';
26
- const SERVICE = 'agents-cli';
27
- // ---------- secret-tool availability ----------
28
- function secretToolAvailable() {
29
- const result = spawnSync('which', ['secret-tool'], {
30
- stdio: ['ignore', 'pipe', 'pipe'],
31
- });
32
- return result.status === 0;
33
- }
34
- let checkedAvailability = false;
35
- let isAvailable = false;
36
- // ---------- file fallback state ----------
37
- let useFileFallback = false;
38
- let warnedFallback = false;
39
- // Set once the keyring is observed locked/unreachable in this process, so the
40
- // read-through in get/has stops re-probing it (and stops re-emitting notices).
41
- let nativeUnreachable = false;
42
- function activateFileFallback() {
43
- if (useFileFallback)
44
- return;
45
- useFileFallback = true;
46
- if (!warnedFallback) {
47
- warnedFallback = true;
48
- process.stderr.write(`[agents] using the encrypted file store at ${fileDir()}\n`);
49
- }
50
- }
51
- function isLockedCollectionError(stderr) {
52
- return /locked collection/i.test(stderr) ||
53
- /Prompt was dismissed/i.test(stderr);
54
- }
55
- /**
56
- * Decide which backend a given op should use. Activates file fallback if
57
- * `secret-tool` is missing and `AGENTS_SECRETS_PASSPHRASE` is set, OR if a
58
- * previous run already committed to the file fallback (encrypted items on
59
- * disk). The latter check is what makes the fallback persistent across the
60
- * many short-lived `agents secrets ...` Node processes a user invokes.
61
- */
62
- function preflight() {
63
- if (useFileFallback)
64
- return 'file';
65
- if (fileStoreHasItems()) {
66
- activateFileFallback();
67
- return 'file';
68
- }
69
- if (!checkedAvailability) {
70
- isAvailable = secretToolAvailable();
71
- checkedAvailability = true;
72
- }
73
- if (!isAvailable) {
74
- // No secret-tool. Route to the encrypted-file fallback whenever a passphrase
75
- // source exists or can be auto-provisioned: an explicit
76
- // AGENTS_SECRETS_PASSPHRASE, an already-provisioned machine-local passphrase,
77
- // or a headless context (no TTY) where getPassphrase() auto-provisions one.
78
- // Only an INTERACTIVE session with none of these gets the install hint —
79
- // installing libsecret is the better fix when someone is at the keyboard.
80
- if (process.env.AGENTS_SECRETS_PASSPHRASE || machinePassphraseExists() || !process.stdin.isTTY) {
81
- activateFileFallback();
82
- return 'file';
83
- }
84
- throw new Error('secret-tool not found. Install libsecret-tools:\n' +
85
- ' Ubuntu/Debian: sudo apt install libsecret-tools\n' +
86
- ' Fedora: sudo dnf install libsecret\n' +
87
- ' Arch: sudo pacman -S libsecret\n' +
88
- '\n' +
89
- 'Alternative: set AGENTS_SECRETS_PASSPHRASE to use the encrypted-file fallback.');
90
- }
91
- return 'secret-tool';
92
- }
93
- /**
94
- * True when secret operations currently route to the encrypted-file store
95
- * instead of the Secret Service (the headless / locked-collection fallback).
96
- *
97
- * Runs the same `preflight()` decision every read and write uses, so it can't
98
- * drift from where bytes actually land. `preflight()` throws only in the
99
- * interactive / no-secret-tool / no-passphrase case — where nothing is stored
100
- * — so treat that as "not on the file path".
101
- *
102
- * `listBundles()` needs this: under the fallback the keychain enumeration
103
- * (`linuxBackend.list`) and the file enumeration read the SAME store, so
104
- * without this signal every file-backed bundle would be listed twice.
105
- */
106
- export function usesFileFallback() {
107
- try {
108
- return preflight() === 'file';
109
- }
110
- catch {
111
- return false;
112
- }
113
- }
114
- // ---------- secret-tool ops with fallback ----------
115
- /** secret-tool lookup attributes:
116
- * service=agents-cli account=<user> item=<itemName> */
117
- export function hasSecretToolToken(item) {
118
- if (preflight() === 'file') {
119
- if (fileStore.has(item))
120
- return true;
121
- // The file store is primary under the fallback, but an item can still live
122
- // only in an (unlocked) keyring that predates it — read through so it isn't
123
- // silently shadowed.
124
- const probe = readNativeItemRaw(item);
125
- if (probe.value !== undefined) {
126
- noteNativeShadow('shadowed', fileDir());
127
- return true;
128
- }
129
- if (probe.locked)
130
- noteNativeShadow('locked', fileDir());
131
- return false;
132
- }
133
- const user = os.userInfo().username;
134
- const result = spawnSync('secret-tool', [
135
- 'lookup',
136
- 'service', SERVICE,
137
- 'account', user,
138
- 'item', item,
139
- ], { stdio: ['ignore', 'pipe', 'pipe'] });
140
- if (result.status === 0) {
141
- return result.stdout?.toString().trim().length > 0;
142
- }
143
- const stderr = result.stderr?.toString() ?? '';
144
- if (isLockedCollectionError(stderr)) {
145
- nativeUnreachable = true;
146
- activateFileFallback();
147
- return fileStore.has(item);
148
- }
149
- return false;
150
- }
151
- export function getSecretToolToken(item) {
152
- if (preflight() === 'file') {
153
- if (fileStore.has(item))
154
- return fileStore.get(item);
155
- const probe = readNativeItemRaw(item);
156
- if (probe.value !== undefined) {
157
- noteNativeShadow('shadowed', fileDir());
158
- return probe.value;
159
- }
160
- if (probe.locked)
161
- noteNativeShadow('locked', fileDir());
162
- throw new Error(`Secret '${item}' not found in the file store or keyring.`);
163
- }
164
- const user = os.userInfo().username;
165
- const result = spawnSync('secret-tool', [
166
- 'lookup',
167
- 'service', SERVICE,
168
- 'account', user,
169
- 'item', item,
170
- ], { stdio: ['ignore', 'pipe', 'pipe'] });
171
- if (result.status === 0) {
172
- const token = result.stdout?.toString().trim();
173
- if (!token)
174
- throw new Error(`Secret '${item}' exists but is empty.`);
175
- return token;
176
- }
177
- const stderr = result.stderr?.toString() ?? '';
178
- if (isLockedCollectionError(stderr)) {
179
- nativeUnreachable = true;
180
- activateFileFallback();
181
- return fileStore.get(item);
182
- }
183
- throw new Error(`Secret '${item}' not found in keyring.`);
184
- }
185
- export function setSecretToolToken(item, value) {
186
- if (!value || !value.trim())
187
- throw new Error('Secret value is empty.');
188
- if (preflight() === 'file')
189
- return fileStore.set(item, value);
190
- const user = os.userInfo().username;
191
- const label = `agents-cli: ${item}`;
192
- const result = spawnSync('secret-tool', [
193
- 'store',
194
- '--label', label,
195
- 'service', SERVICE,
196
- 'account', user,
197
- 'item', item,
198
- ], { input: value, stdio: ['pipe', 'pipe', 'pipe'] });
199
- if (result.status === 0)
200
- return;
201
- const stderr = result.stderr?.toString().trim() ?? '';
202
- if (isLockedCollectionError(stderr)) {
203
- nativeUnreachable = true;
204
- activateFileFallback();
205
- fileStore.set(item, value);
206
- return;
207
- }
208
- throw new Error(`Failed to store secret '${item}': ${stderr || 'unknown error'}\n` +
209
- 'Make sure GNOME Keyring or another Secret Service provider is running,\n' +
210
- 'or set AGENTS_SECRETS_PASSPHRASE to use the encrypted-file fallback.');
211
- }
212
- export function deleteSecretToolToken(item) {
213
- if (preflight() === 'file')
214
- return fileStore.delete(item);
215
- const user = os.userInfo().username;
216
- const result = spawnSync('secret-tool', [
217
- 'clear',
218
- 'service', SERVICE,
219
- 'account', user,
220
- 'item', item,
221
- ], { stdio: ['ignore', 'pipe', 'pipe'] });
222
- if (result.status === 0)
223
- return true;
224
- const stderr = result.stderr?.toString() ?? '';
225
- if (isLockedCollectionError(stderr)) {
226
- nativeUnreachable = true;
227
- activateFileFallback();
228
- return fileStore.delete(item);
229
- }
230
- // secret-tool clear returns 0 whether the item existed or not.
231
- // A non-zero exit that isn't a locked-collection error is a real failure;
232
- // surface that rather than silently swallowing.
233
- return false;
234
- }
235
- /**
236
- * Parse the item names out of `secret-tool search --all` output, keeping only
237
- * those starting with `prefix`. Exported for tests.
238
- *
239
- * `output` must be the combined stdout+stderr of the search: libsecret splits
240
- * the dump across both streams — the value/label/schema lines go to stdout
241
- * while the `attribute.*` lines (which carry `attribute.item`, the only place
242
- * the item name is reliably machine-readable) go to stderr (observed on
243
- * libsecret 0.21.4). Which stream each line lands on has varied across
244
- * libsecret versions, so callers concatenate both rather than bet on one.
245
- */
246
- export function parseSecretToolItems(output, prefix) {
247
- const items = [];
248
- // Parse output format:
249
- // [/org/freedesktop/secrets/collection/login/1]
250
- // label = agents-cli: myitem
251
- // ...
252
- // attribute.item = myitem
253
- const itemRegex = /attribute\.item\s*=\s*(.+)/g;
254
- let match;
255
- while ((match = itemRegex.exec(output)) !== null) {
256
- const itemName = match[1].trim();
257
- if (itemName.startsWith(prefix)) {
258
- items.push(itemName);
259
- }
260
- }
261
- return [...new Set(items)]; // dedupe
262
- }
263
- /**
264
- * List secrets by prefix. secret-tool doesn't have a list command,
265
- * so we use secret-tool search which outputs in a specific format.
266
- */
267
- export function listSecretToolItems(prefix) {
268
- if (preflight() === 'file')
269
- return fileStore.list(prefix);
270
- const result = spawnSync('secret-tool', [
271
- 'search',
272
- '--all',
273
- 'service', SERVICE,
274
- ], { stdio: ['ignore', 'pipe', 'pipe'] });
275
- if (result.status !== 0) {
276
- const stderr = result.stderr?.toString() ?? '';
277
- if (isLockedCollectionError(stderr)) {
278
- activateFileFallback();
279
- return fileStore.list(prefix);
280
- }
281
- return [];
282
- }
283
- const output = `${result.stdout?.toString() || ''}\n${result.stderr?.toString() || ''}`;
284
- return parseSecretToolItems(output, prefix);
285
- }
286
- // ---------- native-direct helpers (bypass preflight routing) ----------
287
- //
288
- // These always talk to secret-tool regardless of whether the process has fallen
289
- // back to the file store. They power (a) the read-through that keeps the file
290
- // store from silently shadowing keyring items, and (b) `import-keyring`.
291
- /**
292
- * Read one item straight from the keyring. Returns `{value}` on a hit,
293
- * `{locked:true}` when the collection is locked/unreachable, and `{}` on a plain
294
- * miss. Never throws and never emits — the caller decides whether to notice.
295
- */
296
- function readNativeItemRaw(item) {
297
- if (nativeUnreachable)
298
- return { locked: true };
299
- if (!secretToolAvailable())
300
- return {};
301
- const user = os.userInfo().username;
302
- const r = spawnSync('secret-tool', [
303
- 'lookup', 'service', SERVICE, 'account', user, 'item', item,
304
- ], { stdio: ['ignore', 'pipe', 'pipe'] });
305
- if (r.status === 0) {
306
- const v = r.stdout?.toString().trim();
307
- return v && v.length ? { value: v } : {};
308
- }
309
- if (isLockedCollectionError(r.stderr?.toString() ?? '')) {
310
- nativeUnreachable = true;
311
- return { locked: true };
312
- }
313
- return {};
314
- }
315
- /**
316
- * Enumerate agents-cli items in the keyring whose name starts with `prefix`.
317
- * `available` is false when secret-tool isn't installed; `locked` is true when
318
- * the collection is locked.
319
- */
320
- function listNativeItemsRaw(prefix) {
321
- if (!secretToolAvailable())
322
- return { items: [], locked: false, available: false };
323
- const r = spawnSync('secret-tool', [
324
- 'search', '--all', 'service', SERVICE,
325
- ], { stdio: ['ignore', 'pipe', 'pipe'] });
326
- if (r.status !== 0) {
327
- const locked = isLockedCollectionError(r.stderr?.toString() ?? '');
328
- if (locked)
329
- nativeUnreachable = true;
330
- return { items: [], locked, available: true };
331
- }
332
- const output = `${r.stdout?.toString() || ''}\n${r.stderr?.toString() || ''}`;
333
- return { items: parseSecretToolItems(output, prefix), locked: false, available: true };
334
- }
335
- /**
336
- * Copy agents-cli items from the keyring into the file store (the `import-keyring`
337
- * backend for Linux). Requires an unlocked keyring; items already in the file
338
- * store are left untouched. With `commit=false` it reports what it *would* do.
339
- */
340
- export function importNativeSecretToolItems(prefix, commit) {
341
- const { items, locked, available } = listNativeItemsRaw(prefix);
342
- if (!available || locked)
343
- return { available, locked, results: [] };
344
- const results = [];
345
- for (const item of items) {
346
- if (fileStore.has(item)) {
347
- results.push({ item, status: 'exists' });
348
- continue;
349
- }
350
- const probe = readNativeItemRaw(item);
351
- if (probe.value === undefined) {
352
- results.push({ item, status: 'failed', detail: probe.locked ? 'keyring locked' : 'unreadable' });
353
- continue;
354
- }
355
- if (commit)
356
- fileStore.set(item, probe.value);
357
- results.push({ item, status: commit ? 'imported' : 'would-import' });
358
- }
359
- return { available, locked, results };
360
- }
361
- /** KeychainBackend implementation for Linux. Routes through secret-tool
362
- * with a transparent encrypted-file fallback when the default Secret
363
- * Service collection is locked (or libsecret-tools is not installed but
364
- * AGENTS_SECRETS_PASSPHRASE is set). */
365
- export const linuxBackend = {
366
- has(item) {
367
- return hasSecretToolToken(item);
368
- },
369
- get(item) {
370
- return getSecretToolToken(item);
371
- },
372
- set(item, value) {
373
- setSecretToolToken(item, value);
374
- },
375
- delete(item) {
376
- return deleteSecretToolToken(item);
377
- },
378
- list(prefix) {
379
- return listSecretToolItems(prefix);
380
- },
381
- };
382
- /** Test-only: reset module state so independent test cases don't bleed
383
- * passphrase / fallback decisions across each other. File-store state (file
384
- * dir + cached passphrase) lives in ./filestore.ts and is reset there. */
385
- export function _resetForTest(opts = {}) {
386
- _resetFileStoreForTest({ fileDir: opts.fileDir ?? null, passphrase: opts.passphrase ?? null });
387
- useFileFallback = opts.forceFileFallback ?? false;
388
- warnedFallback = false;
389
- nativeUnreachable = false;
390
- checkedAvailability = false;
391
- isAvailable = false;
392
- _resetFallbackNoticeForTest();
393
- }
@@ -1,109 +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 { type SecretsBundle, type SecretsPolicy, type SecretsBackend, type SecretType } from './bundles.js';
21
- /** Ref kinds a var's value can have, from `describeBundle`. */
22
- export declare const REF_KINDS: readonly ["literal", "keychain", "env", "file", "exec"];
23
- export type RefKind = typeof REF_KINDS[number];
24
- /** Default window for `--expiring` with no argument — matches the EXPIRING column. */
25
- export declare const DEFAULT_EXPIRING_DAYS = 30;
26
- /** A parsed, validated filter. Every field is optional; absent ⇒ that axis does
27
- * not narrow. All present axes must match (AND). */
28
- export interface SecretsListFilter {
29
- /** Case-insensitive substring over bundle name and description. */
30
- query?: string;
31
- policy?: SecretsPolicy[];
32
- backend?: SecretsBackend[];
33
- type?: SecretType[];
34
- kind?: RefKind[];
35
- /** true ⇒ only bundles the broker holds; false ⇒ only those it does not. */
36
- held?: boolean;
37
- /** Only bundles with at least one var whose `expires` is already past. */
38
- expired?: boolean;
39
- /** Only bundles with at least one var expiring within this many days. */
40
- expiringDays?: number;
41
- /** Only bundles whose `last_used` is older than this epoch-ms (or never used). */
42
- unusedBefore?: number;
43
- }
44
- /** The raw option bag commander hands us. */
45
- export interface SecretsListFilterOpts {
46
- policy?: string;
47
- backend?: string;
48
- type?: string;
49
- kind?: string;
50
- held?: boolean;
51
- notHeld?: boolean;
52
- expired?: boolean;
53
- expiring?: string | boolean;
54
- unused?: string;
55
- }
56
- /**
57
- * Validate one comma-separated enum list. An unknown value throws and names the
58
- * whole valid set — a silent ignore would let `--policy hodl` quietly return
59
- * every bundle, which reads as "nothing matches that" and is worse than an error.
60
- * Values are lowercased, matching `parsePolicyOpt`'s handling of policy names.
61
- */
62
- export declare function parseEnumList<T extends string>(raw: string, flag: string, valid: readonly T[]): T[];
63
- /** Build a validated filter from commander's option bag. Throws on bad input. */
64
- export declare function parseListFilters(opts: SecretsListFilterOpts, query?: string): SecretsListFilter;
65
- /** True when any axis is set — used to decide whether the empty state should
66
- * explain itself rather than claim there are no bundles at all. */
67
- export declare function filterIsActive(f: SecretsListFilter): boolean;
68
- /** Expiry tallies for one bundle: how many vars are already past, and how many
69
- * fall due within `withinDays`. */
70
- export declare function bundleExpiry(b: SecretsBundle, now: number, withinDays?: number): {
71
- expired: number;
72
- soon: number;
73
- };
74
- /** Ambient facts a bundle can't answer about itself. */
75
- export interface FilterContext {
76
- /** Bundle name → hold expiry epoch-ms, from the broker. Empty off macOS. */
77
- held: Map<string, number>;
78
- now: number;
79
- }
80
- /** Does this bundle satisfy every set axis? Pure. */
81
- export declare function bundleMatchesFilter(b: SecretsBundle, f: SecretsListFilter, ctx: FilterContext): boolean;
82
- /** Sort fields for `--sort`. `name` is the default and matches `listBundles()`.
83
- * `used` is most-recently-used first and `uses` is most-frequently-accessed
84
- * first; both read the value-free usage read-model (lib/secrets/usage-db.ts). */
85
- export declare const SORT_FIELDS: readonly ["name", "used", "uses", "created", "updated", "expiry"];
86
- export type SortField = typeof SORT_FIELDS[number];
87
- /**
88
- * The two value-free usage facts `--sort used|uses` needs, keyed by bundle name.
89
- * Kept as a minimal shape (not the full BundleUsageSummary) so this module stays
90
- * pure and unit-testable without opening the SQLite read-model.
91
- */
92
- export interface BundleUsageHint {
93
- /** Most recent recorded event across all kinds, ISO 8601, or null. */
94
- lastUsedAt: string | null;
95
- /** Recorded `access` (read/inject) count — what `--sort uses` ranks on. */
96
- uses: number;
97
- }
98
- export declare function parseSortField(raw: string | undefined): SortField;
99
- /** Sort a copy. Time fields are most-recent-first (the useful direction for
100
- * "what did I touch lately"); `uses` is most-frequently-accessed first; `expiry`
101
- * is soonest-first; ties fall back to name so the order is stable. `usage`
102
- * carries the value-free read-model facts `used`/`uses` rank on — when omitted,
103
- * `used` falls back to the throttled `last_used` stamp and `uses` sees zero. */
104
- export declare function sortBundles(bundles: SecretsBundle[], field: SortField, usage?: Map<string, BundleUsageHint>): SecretsBundle[];
105
- /** Human summary of the active filters, for the empty state. `sessions` only
106
- * echoes --project/--all on a miss, which leaves you guessing which flag emptied
107
- * the list; naming every active axis is the difference between "nothing matched"
108
- * and knowing what to relax. */
109
- export declare function describeFilter(f: SecretsListFilter): string;