@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,556 +0,0 @@
1
- /**
2
- * Windows secret storage via Windows Credential Manager (wincred).
3
- *
4
- * Primary backend: the Credential Manager `advapi32` API (CredReadW /
5
- * CredWriteW / CredDeleteW / CredEnumerateW), reached through a static
6
- * PowerShell script that P/Invokes the C# shim below. PowerShell (Windows
7
- * PowerShell 5.1) ships with every supported Windows, so there is no separate
8
- * install step. Items are stored as CRED_TYPE_GENERIC with
9
- * CRED_PERSIST_LOCAL_MACHINE — device-local, matching the biometry-bound model
10
- * on macOS (src/lib/secrets/index.ts).
11
- *
12
- * Zero injection surface: the PS script is a single STATIC constant. All
13
- * dynamic data rides in the child ENV (target name, list prefix) or STDIN (the
14
- * secret value) — nothing is string-interpolated into the script. The child is
15
- * spawned with a spawnSync ARGV ARRAY (`-EncodedCommand <base64>`), never a
16
- * shell string.
17
- *
18
- * Headless fallback: when Credential Manager is unreachable (no logon session —
19
- * ERROR_NO_SUCH_LOGON_SESSION 1312 — or powershell.exe missing from PATH), we
20
- * transparently switch to the AES-256-GCM encrypted-file store in
21
- * ./filestore.ts, exactly like the Linux locked-collection fallback. The
22
- * decision is cached per process; one stderr line is emitted the first time.
23
- *
24
- * Item names are stored VERBATIM as the credential TargetName
25
- * (`agents-cli.bundles.<name>` / `agents-cli.secrets.<bundle>.<key>` — the
26
- * scheme shared with the file store, see ./filestore.ts) so `list` returns item
27
- * names directly.
28
- */
29
- import { spawnSync } from 'child_process';
30
- import { encodePwshBase64 } from '../pwsh.js';
31
- import { fileStore, fileDir, fileStoreHasItems, machinePassphraseExists, _resetFileStoreForTest, } from './filestore.js';
32
- import { noteNativeShadow, _resetFallbackNoticeForTest, } from './fallback.js';
33
- // Re-exported so importers (and tests) can keep reaching these via './windows.js'.
34
- export { encryptForFallback, decryptForFallback, fileBackend, } from './filestore.js';
35
- const POWERSHELL = 'powershell.exe';
36
- /**
37
- * CRED_MAX_CREDENTIAL_BLOB_SIZE — Credential Manager rejects a generic
38
- * credential blob larger than 2560 bytes with an opaque CredWrite failure. We
39
- * guard against it in `set` with a clear message. Only pathologically large
40
- * bundle metadata could hit this; such an item should live in a file-backed
41
- * bundle (AGENTS_SECRETS_PASSPHRASE) instead.
42
- */
43
- export const CRED_MAX_CREDENTIAL_BLOB_SIZE = 2560;
44
- /**
45
- * The static PowerShell driver. Dispatches on $env:AGENTS_CRED_OP; reads the
46
- * target name from $env:AGENTS_CRED_TARGET, the list prefix from
47
- * $env:AGENTS_CRED_PREFIX, and (for `set`) the raw secret value from stdin.
48
- * Nothing dynamic is interpolated into this string.
49
- *
50
- * Exit codes: 0 = success, 3 = clean "not found", 1 = error (message on stderr,
51
- * carrying the Win32 code so the Node side can detect an unavailable store).
52
- * `get` emits the blob as base64 (dodges PowerShell CRLF/encoding corruption);
53
- * `list` prints one target name per line.
54
- */
55
- const PS_SCRIPT = `
56
- $ErrorActionPreference = 'Stop'
57
- # Output must not depend on the console codepage: a windowsHide child owns a
58
- # fresh hidden console whose codepage is the OEM default (cp437), not the
59
- # terminal's UTF-8 — the Node side always decodes stdout/stderr as UTF-8.
60
- [Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false)
61
- Add-Type -TypeDefinition @'
62
- using System;
63
- using System.Runtime.InteropServices;
64
- using System.Collections.Generic;
65
-
66
- public static class AgentsCred {
67
- const uint CRED_TYPE_GENERIC = 1;
68
- const uint CRED_PERSIST_LOCAL_MACHINE = 2;
69
- const int ERROR_NOT_FOUND = 1168;
70
-
71
- [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
72
- struct CREDENTIAL {
73
- public uint Flags;
74
- public uint Type;
75
- public IntPtr TargetName;
76
- public IntPtr Comment;
77
- public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
78
- public uint CredentialBlobSize;
79
- public IntPtr CredentialBlob;
80
- public uint Persist;
81
- public uint AttributeCount;
82
- public IntPtr Attributes;
83
- public IntPtr TargetAlias;
84
- public IntPtr UserName;
85
- }
86
-
87
- [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
88
- static extern bool CredReadW(string target, uint type, uint flags, out IntPtr cred);
89
- [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
90
- static extern bool CredWriteW(ref CREDENTIAL cred, uint flags);
91
- [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
92
- static extern bool CredDeleteW(string target, uint type, uint flags);
93
- [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
94
- static extern bool CredEnumerateW(string filter, uint flags, out uint count, out IntPtr creds);
95
- [DllImport("advapi32.dll", SetLastError = false)]
96
- static extern void CredFree(IntPtr buffer);
97
-
98
- public static bool Has(string target) {
99
- IntPtr p;
100
- if (CredReadW(target, CRED_TYPE_GENERIC, 0, out p)) { CredFree(p); return true; }
101
- int err = Marshal.GetLastWin32Error();
102
- if (err == ERROR_NOT_FOUND) return false;
103
- throw new Exception("CredMan error " + err);
104
- }
105
-
106
- public static byte[] Get(string target) {
107
- IntPtr p;
108
- if (!CredReadW(target, CRED_TYPE_GENERIC, 0, out p)) {
109
- int err = Marshal.GetLastWin32Error();
110
- if (err == ERROR_NOT_FOUND) throw new Exception("NOTFOUND");
111
- throw new Exception("CredMan error " + err);
112
- }
113
- try {
114
- CREDENTIAL cred = (CREDENTIAL)Marshal.PtrToStructure(p, typeof(CREDENTIAL));
115
- byte[] blob = new byte[cred.CredentialBlobSize];
116
- if (cred.CredentialBlobSize > 0)
117
- Marshal.Copy(cred.CredentialBlob, blob, 0, (int)cred.CredentialBlobSize);
118
- return blob;
119
- } finally { CredFree(p); }
120
- }
121
-
122
- public static void Set(string target, byte[] blob) {
123
- CREDENTIAL cred = new CREDENTIAL();
124
- cred.Type = CRED_TYPE_GENERIC;
125
- cred.TargetName = Marshal.StringToCoTaskMemUni(target);
126
- cred.CredentialBlobSize = (uint)blob.Length;
127
- cred.CredentialBlob = (blob.Length > 0) ? Marshal.AllocCoTaskMem(blob.Length) : IntPtr.Zero;
128
- if (blob.Length > 0) Marshal.Copy(blob, 0, cred.CredentialBlob, blob.Length);
129
- cred.Persist = CRED_PERSIST_LOCAL_MACHINE;
130
- cred.UserName = Marshal.StringToCoTaskMemUni(Environment.UserName);
131
- try {
132
- if (!CredWriteW(ref cred, 0)) {
133
- int err = Marshal.GetLastWin32Error();
134
- throw new Exception("CredMan error " + err);
135
- }
136
- } finally {
137
- Marshal.FreeCoTaskMem(cred.TargetName);
138
- if (cred.CredentialBlob != IntPtr.Zero) Marshal.FreeCoTaskMem(cred.CredentialBlob);
139
- Marshal.FreeCoTaskMem(cred.UserName);
140
- }
141
- }
142
-
143
- public static bool Delete(string target) {
144
- if (CredDeleteW(target, CRED_TYPE_GENERIC, 0)) return true;
145
- int err = Marshal.GetLastWin32Error();
146
- if (err == ERROR_NOT_FOUND) return false;
147
- throw new Exception("CredMan error " + err);
148
- }
149
-
150
- public static List<string> List(string filter) {
151
- uint count;
152
- IntPtr credsPtr;
153
- List<string> results = new List<string>();
154
- string f = string.IsNullOrEmpty(filter) ? null : filter;
155
- if (!CredEnumerateW(f, 0, out count, out credsPtr)) {
156
- int err = Marshal.GetLastWin32Error();
157
- if (err == ERROR_NOT_FOUND) return results;
158
- throw new Exception("CredMan error " + err);
159
- }
160
- try {
161
- for (int i = 0; i < count; i++) {
162
- IntPtr credPtr = Marshal.ReadIntPtr(credsPtr, i * IntPtr.Size);
163
- CREDENTIAL cred = (CREDENTIAL)Marshal.PtrToStructure(credPtr, typeof(CREDENTIAL));
164
- if (cred.TargetName != IntPtr.Zero)
165
- results.Add(Marshal.PtrToStringUni(cred.TargetName));
166
- }
167
- } finally { CredFree(credsPtr); }
168
- return results;
169
- }
170
- }
171
- '@
172
-
173
- try {
174
- $op = $env:AGENTS_CRED_OP
175
- $target = $env:AGENTS_CRED_TARGET
176
- switch ($op) {
177
- 'has' {
178
- if ([AgentsCred]::Has($target)) { exit 0 } else { exit 3 }
179
- }
180
- 'get' {
181
- $blob = [AgentsCred]::Get($target)
182
- [Console]::Out.Write([Convert]::ToBase64String($blob))
183
- exit 0
184
- }
185
- 'set' {
186
- # Raw bytes, not [Console]::In text — text decoding runs through the
187
- # console codepage and corrupts non-ASCII UTF-8 from the Node side.
188
- $stdin = [Console]::OpenStandardInput()
189
- $ms = New-Object System.IO.MemoryStream
190
- $stdin.CopyTo($ms)
191
- [AgentsCred]::Set($target, $ms.ToArray())
192
- exit 0
193
- }
194
- 'delete' {
195
- if ([AgentsCred]::Delete($target)) { exit 0 } else { exit 3 }
196
- }
197
- 'list' {
198
- $prefix = $env:AGENTS_CRED_PREFIX
199
- $filter = ''
200
- if (-not [string]::IsNullOrEmpty($prefix)) { $filter = $prefix + '*' }
201
- foreach ($n in [AgentsCred]::List($filter)) { [Console]::Out.WriteLine($n) }
202
- exit 0
203
- }
204
- default {
205
- [Console]::Error.Write('unknown op: ' + $op)
206
- exit 1
207
- }
208
- }
209
- } catch {
210
- $m = $_.Exception.Message
211
- if ($m -match 'NOTFOUND') { exit 3 }
212
- [Console]::Error.Write($m)
213
- exit 1
214
- }
215
- `;
216
- const ENCODED_SCRIPT = encodePwshBase64(PS_SCRIPT);
217
- function runCred(op, opts) {
218
- const env = { ...process.env, AGENTS_CRED_OP: op };
219
- if (opts.target !== undefined)
220
- env.AGENTS_CRED_TARGET = opts.target;
221
- if (opts.prefix !== undefined)
222
- env.AGENTS_CRED_PREFIX = opts.prefix;
223
- // ARGV ARRAY — never a shell string. -EncodedCommand rides base64 of the
224
- // UTF-16LE script with zero escaping hazards.
225
- const result = spawnSync(POWERSHELL, ['-NoProfile', '-NonInteractive', '-EncodedCommand', ENCODED_SCRIPT], {
226
- env,
227
- input: opts.input,
228
- stdio: ['pipe', 'pipe', 'pipe'],
229
- maxBuffer: 16 * 1024 * 1024,
230
- // Never flash a console window when the caller has no console of its own
231
- // (the scheduler daemon resolves bundles through here every sync cycle).
232
- windowsHide: true,
233
- });
234
- return {
235
- status: result.status,
236
- stdout: result.stdout?.toString() ?? '',
237
- stderr: result.stderr?.toString() ?? '',
238
- spawnError: !!result.error,
239
- };
240
- }
241
- // ---------- powershell availability ----------
242
- function powershellAvailable() {
243
- const result = spawnSync(POWERSHELL, ['-NoProfile', '-NonInteractive', '-Command', 'exit 0'], {
244
- stdio: ['ignore', 'ignore', 'ignore'],
245
- windowsHide: true,
246
- });
247
- return !result.error && result.status === 0;
248
- }
249
- let checkedAvailability = false;
250
- let isAvailable = false;
251
- // ---------- file fallback state ----------
252
- let useFileFallback = false;
253
- let warnedFallback = false;
254
- // Set once Credential Manager is observed unreachable in this process, so the
255
- // read-through in get/has stops re-probing it (and re-emitting notices).
256
- let nativeUnreachable = false;
257
- function activateFileFallback() {
258
- if (useFileFallback)
259
- return;
260
- useFileFallback = true;
261
- if (!warnedFallback) {
262
- warnedFallback = true;
263
- process.stderr.write(`[agents] using the encrypted file store at ${fileDir()}\n`);
264
- }
265
- }
266
- /**
267
- * Credential Manager is "unavailable" (as opposed to a plain not-found or a
268
- * malformed item) when there's no logon session to hold credentials
269
- * (ERROR_NO_SUCH_LOGON_SESSION 1312 — common under a service account / SSH
270
- * session with no interactive logon) or powershell.exe can't be spawned at all.
271
- * Those cases route to the encrypted-file fallback, exactly like the Linux
272
- * locked-collection error.
273
- */
274
- function isCredManUnavailableError(r) {
275
- if (r.spawnError)
276
- return true; // powershell.exe not found
277
- return /\b1312\b/.test(r.stderr) || /NO_SUCH_LOGON_SESSION/i.test(r.stderr);
278
- }
279
- /**
280
- * Decide which backend a given op should use. Activates the file fallback if a
281
- * previous run already committed to it (encrypted items on disk), or if
282
- * powershell.exe is missing and a passphrase source exists (explicit
283
- * AGENTS_SECRETS_PASSPHRASE, a provisioned machine-local key, or a headless
284
- * context). The disk-items check makes the fallback persistent across the many
285
- * short-lived `agents secrets ...` Node processes.
286
- */
287
- function preflight() {
288
- if (useFileFallback)
289
- return 'file';
290
- if (fileStoreHasItems()) {
291
- activateFileFallback();
292
- return 'file';
293
- }
294
- if (!checkedAvailability) {
295
- isAvailable = powershellAvailable();
296
- checkedAvailability = true;
297
- }
298
- if (!isAvailable) {
299
- if (process.env.AGENTS_SECRETS_PASSPHRASE || machinePassphraseExists() || !process.stdin.isTTY) {
300
- activateFileFallback();
301
- return 'file';
302
- }
303
- throw new Error('powershell.exe not found on PATH; cannot reach Windows Credential Manager.\n' +
304
- 'Set AGENTS_SECRETS_PASSPHRASE to use the encrypted-file fallback.');
305
- }
306
- return 'credman';
307
- }
308
- /**
309
- * True when secret operations currently route to the encrypted-file store
310
- * instead of Windows Credential Manager. Mirrors linux.ts:usesFileFallback so
311
- * `listBundles()` doesn't double-count file-backed bundles under the fallback.
312
- */
313
- export function usesFileFallback() {
314
- try {
315
- return preflight() === 'file';
316
- }
317
- catch {
318
- return false;
319
- }
320
- }
321
- // ---------- Credential Manager ops with fallback ----------
322
- export function hasCredManToken(item) {
323
- if (preflight() === 'file') {
324
- if (fileStore.has(item))
325
- return true;
326
- // Read through to Credential Manager so an item that predates the fallback
327
- // isn't silently shadowed by the file store.
328
- const probe = readNativeCredItemRaw(item);
329
- if (probe.value !== undefined) {
330
- noteNativeShadow('shadowed', fileDir());
331
- return true;
332
- }
333
- if (probe.unavailable)
334
- noteNativeShadow('locked', fileDir());
335
- return false;
336
- }
337
- const r = runCred('has', { target: item });
338
- if (r.status === 0)
339
- return true;
340
- if (r.status === 3)
341
- return false;
342
- if (isCredManUnavailableError(r)) {
343
- nativeUnreachable = true;
344
- activateFileFallback();
345
- return fileStore.has(item);
346
- }
347
- return false;
348
- }
349
- export function getCredManToken(item) {
350
- if (preflight() === 'file') {
351
- if (fileStore.has(item))
352
- return fileStore.get(item);
353
- const probe = readNativeCredItemRaw(item);
354
- if (probe.value !== undefined) {
355
- noteNativeShadow('shadowed', fileDir());
356
- return probe.value;
357
- }
358
- if (probe.unavailable)
359
- noteNativeShadow('locked', fileDir());
360
- throw new Error(`Secret '${item}' not found in the file store or Credential Manager.`);
361
- }
362
- const r = runCred('get', { target: item });
363
- if (r.status === 0) {
364
- // stdout is base64 of the raw UTF-8 blob (dodges PowerShell encoding corruption).
365
- return Buffer.from(r.stdout.trim(), 'base64').toString('utf8');
366
- }
367
- if (r.status === 3)
368
- throw new Error(`Secret '${item}' not found in Credential Manager.`);
369
- if (isCredManUnavailableError(r)) {
370
- nativeUnreachable = true;
371
- activateFileFallback();
372
- return fileStore.get(item);
373
- }
374
- throw new Error(`Failed to read secret '${item}': ${r.stderr.trim() || 'unknown error'}`);
375
- }
376
- export function setCredManToken(item, value) {
377
- if (!value || !value.trim())
378
- throw new Error('Secret value is empty.');
379
- if (preflight() === 'file') {
380
- fileStore.set(item, value);
381
- return;
382
- }
383
- const byteLen = Buffer.byteLength(value, 'utf8');
384
- if (byteLen > CRED_MAX_CREDENTIAL_BLOB_SIZE) {
385
- throw new Error(`Secret '${item}' is ${byteLen} bytes, exceeding the Windows Credential Manager limit of ` +
386
- `${CRED_MAX_CREDENTIAL_BLOB_SIZE} bytes (CRED_MAX_CREDENTIAL_BLOB_SIZE). ` +
387
- 'Use a file-backed bundle (set AGENTS_SECRETS_PASSPHRASE) for values this large.');
388
- }
389
- const r = runCred('set', { target: item, input: value });
390
- if (r.status === 0)
391
- return;
392
- if (isCredManUnavailableError(r)) {
393
- nativeUnreachable = true;
394
- activateFileFallback();
395
- fileStore.set(item, value);
396
- return;
397
- }
398
- throw new Error(`Failed to store secret '${item}': ${r.stderr.trim() || 'unknown error'}`);
399
- }
400
- export function deleteCredManToken(item) {
401
- if (preflight() === 'file')
402
- return fileStore.delete(item);
403
- const r = runCred('delete', { target: item });
404
- if (r.status === 0)
405
- return true;
406
- if (r.status === 3)
407
- return false;
408
- if (isCredManUnavailableError(r)) {
409
- nativeUnreachable = true;
410
- activateFileFallback();
411
- return fileStore.delete(item);
412
- }
413
- return false;
414
- }
415
- export function listCredManItems(prefix) {
416
- if (preflight() === 'file')
417
- return fileStore.list(prefix);
418
- const r = runCred('list', { prefix });
419
- if (r.status === 0)
420
- return parseWindowsCredList(r.stdout, prefix);
421
- if (isCredManUnavailableError(r)) {
422
- nativeUnreachable = true;
423
- activateFileFallback();
424
- return fileStore.list(prefix);
425
- }
426
- return [];
427
- }
428
- /**
429
- * Parse the target names printed by the `list` op (one per line), keeping only
430
- * those starting with `prefix` and deduping. Same contract as
431
- * parseSecretToolItems (linux.ts). Exported for tests.
432
- */
433
- export function parseWindowsCredList(output, prefix) {
434
- const items = output
435
- .split(/\r?\n/)
436
- .map((s) => s.trim())
437
- .filter((s) => s.length > 0)
438
- .filter((s) => s.startsWith(prefix));
439
- return [...new Set(items)]; // dedupe
440
- }
441
- // ---------- native-direct helpers (bypass preflight routing) ----------
442
- //
443
- // Always talk to Credential Manager regardless of the file fallback. They power
444
- // (a) the read-through that keeps the file store from shadowing credman items,
445
- // and (b) `import-keyring`.
446
- /**
447
- * Read one item straight from Credential Manager. `{value}` on hit,
448
- * `{unavailable:true}` when the store is unreachable, `{}` on a plain miss.
449
- * Never throws, never emits.
450
- */
451
- function readNativeCredItemRaw(item) {
452
- if (nativeUnreachable)
453
- return { unavailable: true };
454
- if (!powershellAvailable())
455
- return {};
456
- const r = runCred('get', { target: item });
457
- if (r.status === 0)
458
- return { value: Buffer.from(r.stdout.trim(), 'base64').toString('utf8') };
459
- if (r.status === 3)
460
- return {};
461
- if (isCredManUnavailableError(r)) {
462
- nativeUnreachable = true;
463
- return { unavailable: true };
464
- }
465
- return {};
466
- }
467
- /**
468
- * Enumerate agents-cli credentials under `prefix`. Windows credentials have no
469
- * service scoping — the target IS the identifier — so we NEVER enumerate with an
470
- * empty filter (that returns unrelated machine credentials). The filter is
471
- * floored to the `agents-cli.` namespace; bare items (unprefixed targets) are
472
- * therefore out of scope for auto-discovery on Windows.
473
- */
474
- function listNativeCredItemsRaw(prefix) {
475
- if (!powershellAvailable())
476
- return { items: [], locked: false, available: false };
477
- const floor = prefix && prefix.startsWith('agents-cli.') ? prefix : 'agents-cli.';
478
- const r = runCred('list', { prefix: floor });
479
- if (r.status === 0)
480
- return { items: parseWindowsCredList(r.stdout, floor), locked: false, available: true };
481
- if (isCredManUnavailableError(r)) {
482
- nativeUnreachable = true;
483
- return { items: [], locked: true, available: true };
484
- }
485
- return { items: [], locked: false, available: true };
486
- }
487
- /**
488
- * Copy agents-cli credentials from Credential Manager into the file store (the
489
- * `import-keyring` backend for Windows). Requires a reachable store; items
490
- * already in the file store are left untouched.
491
- */
492
- export function importNativeCredManItems(prefix, commit) {
493
- const { items, locked, available } = listNativeCredItemsRaw(prefix);
494
- if (!available || locked)
495
- return { available, locked, results: [] };
496
- const results = [];
497
- for (const item of items) {
498
- if (fileStore.has(item)) {
499
- results.push({ item, status: 'exists' });
500
- continue;
501
- }
502
- const probe = readNativeCredItemRaw(item);
503
- if (probe.value === undefined) {
504
- results.push({ item, status: 'failed', detail: probe.unavailable ? 'credential manager unavailable' : 'unreadable' });
505
- continue;
506
- }
507
- if (commit)
508
- fileStore.set(item, probe.value);
509
- results.push({ item, status: commit ? 'imported' : 'would-import' });
510
- }
511
- return { available, locked, results };
512
- }
513
- /**
514
- * KeychainBackend implementation for Windows. Routes through Windows Credential
515
- * Manager (via PowerShell P/Invoke) with a transparent encrypted-file fallback
516
- * when the credential store is unreachable.
517
- */
518
- export const windowsBackend = {
519
- has(item) {
520
- return hasCredManToken(item);
521
- },
522
- get(item) {
523
- return getCredManToken(item);
524
- },
525
- set(item, value) {
526
- setCredManToken(item, value);
527
- },
528
- delete(item) {
529
- return deleteCredManToken(item);
530
- },
531
- list(prefix) {
532
- return listCredManItems(prefix);
533
- },
534
- };
535
- /**
536
- * Test-only: reset module state so independent test cases don't bleed
537
- * availability / fallback decisions across each other. Pass `forceAvailable` to
538
- * pin the powershell-availability probe (skips the real spawn); pass `fileDir`
539
- * to redirect the encrypted-file store to a temp dir. File-store state lives in
540
- * ./filestore.ts and is reset there.
541
- */
542
- export function _resetForTest(opts = {}) {
543
- _resetFileStoreForTest({ fileDir: opts.fileDir ?? null, passphrase: opts.passphrase ?? null });
544
- useFileFallback = opts.forceFileFallback ?? false;
545
- warnedFallback = false;
546
- nativeUnreachable = false;
547
- _resetFallbackNoticeForTest();
548
- if (opts.forceAvailable === undefined || opts.forceAvailable === null) {
549
- checkedAvailability = false;
550
- isAvailable = false;
551
- }
552
- else {
553
- checkedAvailability = true;
554
- isAvailable = opts.forceAvailable;
555
- }
556
- }
@@ -1,97 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Copy the bundled `Agents CLI.app` to a stable user-scoped path so its
4
- * keychain ACLs survive future npm installs/updates.
5
- *
6
- * Source: dist/lib/secrets/Agents CLI.app (in the installed npm package)
7
- * bin/Agents CLI.app (in a raw working tree)
8
- * Destination: ~/Library/Application Support/agents-cli/Agents CLI.app
9
- *
10
- * Invoked by scripts/postinstall.js on global install, and by
11
- * scripts/install.sh for dev installs. Pure Node, no agents-cli imports,
12
- * so it runs before the package's TS is wired up.
13
- *
14
- * macOS only — silently no-ops on other platforms so postinstall stays clean.
15
- */
16
-
17
- import { spawnSync } from 'child_process';
18
- import * as fs from 'fs';
19
- import * as os from 'os';
20
- import * as path from 'path';
21
- import { fileURLToPath } from 'url';
22
-
23
- const APP_BUNDLE_NAME = 'Agents CLI.app';
24
- const INSTALL_DIR_NAME = 'agents-cli';
25
-
26
- function destAppPath() {
27
- return path.join(os.homedir(), 'Library', 'Application Support', INSTALL_DIR_NAME, APP_BUNDLE_NAME);
28
- }
29
-
30
- function findSourceApp() {
31
- const here = path.dirname(fileURLToPath(import.meta.url));
32
- // From scripts/install-helper.js, look in the installed npm layout first
33
- // (../dist/lib/secrets/...) then a raw repo layout (../bin/...).
34
- const candidates = [
35
- path.resolve(here, '..', 'dist', 'lib', 'secrets', APP_BUNDLE_NAME),
36
- path.resolve(here, '..', 'bin', APP_BUNDLE_NAME),
37
- ];
38
- for (const c of candidates) {
39
- if (fs.existsSync(c)) return c;
40
- }
41
- return null;
42
- }
43
-
44
- function codesignVerify(appPath) {
45
- const r = spawnSync('codesign', ['--verify', '--deep', '--strict', appPath], {
46
- stdio: ['ignore', 'pipe', 'pipe'],
47
- encoding: 'utf-8',
48
- });
49
- return { ok: r.status === 0, output: (r.stderr || r.stdout || '').toString().trim() };
50
- }
51
-
52
- function copyAppBundle(src, dest) {
53
- fs.mkdirSync(path.dirname(dest), { recursive: true });
54
- if (fs.existsSync(dest)) fs.rmSync(dest, { recursive: true, force: true });
55
- const r = spawnSync('cp', ['-R', src, dest], { stdio: ['ignore', 'pipe', 'pipe'], encoding: 'utf-8' });
56
- if (r.status !== 0) {
57
- const msg = (r.stderr || r.stdout || '').toString().trim();
58
- throw new Error(`Failed to copy ${src} -> ${dest}: ${msg || 'unknown error'}`);
59
- }
60
- }
61
-
62
- function main() {
63
- if (process.platform !== 'darwin') return;
64
- const force = process.argv.includes('--force');
65
- const dest = destAppPath();
66
-
67
- if (!force && fs.existsSync(dest) && codesignVerify(dest).ok) {
68
- return;
69
- }
70
-
71
- const src = findSourceApp();
72
- if (!src) {
73
- // No source bundle to install. Stay silent during postinstall so we don't
74
- // create noise on Linux/Windows or on builds that intentionally omit the
75
- // helper. The lazy staleness check in getKeychainHelperPath() surfaces a
76
- // clearer error if a user actually needs the helper.
77
- return;
78
- }
79
-
80
- try {
81
- copyAppBundle(src, dest);
82
- } catch (err) {
83
- process.stderr.write(`agents-cli: failed to install Keychain helper: ${err.message}\n`);
84
- return;
85
- }
86
-
87
- const verify = codesignVerify(dest);
88
- if (!verify.ok) {
89
- process.stderr.write(
90
- `agents-cli: installed helper failed codesign verification at ${dest}\n${verify.output}\n`
91
- );
92
- return;
93
- }
94
- process.stdout.write(` Installed Keychain helper: ${dest}\n`);
95
- }
96
-
97
- main();