@phnx-labs/agents-cli 1.22.83 → 1.22.85

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (291) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +80 -53
  3. package/dist/bootstrap.d.ts +7 -6
  4. package/dist/bootstrap.js +20 -27
  5. package/dist/cli/command-registry.js +5 -1
  6. package/dist/commands/accounts.d.ts +6 -13
  7. package/dist/commands/accounts.js +330 -143
  8. package/dist/commands/apply.js +1 -1
  9. package/dist/commands/auth-mint.d.ts +7 -3
  10. package/dist/commands/auth-mint.js +21 -9
  11. package/dist/commands/auth.js +2 -3
  12. package/dist/commands/browser.js +10 -10
  13. package/dist/commands/daemon.d.ts +7 -3
  14. package/dist/commands/daemon.js +40 -25
  15. package/dist/commands/doctor.js +28 -2
  16. package/dist/commands/exec.js +87 -39
  17. package/dist/commands/fleet-capture.js +21 -2
  18. package/dist/commands/harness-wizard.js +2 -2
  19. package/dist/commands/lease.js +7 -12
  20. package/dist/commands/profiles.d.ts +2 -2
  21. package/dist/commands/profiles.js +20 -13
  22. package/dist/commands/repo.js +3 -3
  23. package/dist/commands/run-account-picker.d.ts +3 -3
  24. package/dist/commands/run-account-picker.js +29 -30
  25. package/dist/commands/secrets-passthrough.d.ts +20 -0
  26. package/dist/commands/secrets-passthrough.js +43 -0
  27. package/dist/commands/sessions-trace.d.ts +11 -0
  28. package/dist/commands/sessions-trace.js +71 -0
  29. package/dist/commands/setup-accounts.d.ts +3 -2
  30. package/dist/commands/setup-accounts.js +4 -4
  31. package/dist/commands/setup-secrets.d.ts +16 -21
  32. package/dist/commands/setup-secrets.js +43 -224
  33. package/dist/commands/ssh.js +2 -2
  34. package/dist/commands/sync.js +1 -1
  35. package/dist/commands/update.d.ts +10 -0
  36. package/dist/commands/update.js +43 -40
  37. package/dist/commands/versions.d.ts +11 -11
  38. package/dist/commands/versions.js +131 -83
  39. package/dist/commands/view.d.ts +1 -9
  40. package/dist/commands/view.js +41 -55
  41. package/dist/commands/webhook.js +5 -5
  42. package/dist/commands/workflows.js +2 -1
  43. package/dist/index.d.ts +12 -12
  44. package/dist/index.js +13 -42
  45. package/dist/lib/account-capabilities.d.ts +1 -1
  46. package/dist/lib/account-capabilities.js +1 -1
  47. package/dist/lib/account-catalog.d.ts +121 -6
  48. package/dist/lib/account-catalog.js +421 -7
  49. package/dist/lib/account-registry.d.ts +8 -30
  50. package/dist/lib/account-registry.js +36 -79
  51. package/dist/lib/account-schema.d.ts +1 -1
  52. package/dist/lib/account-schema.js +1 -1
  53. package/dist/lib/accounting/account-pool-collect.js +16 -4
  54. package/dist/lib/accounting/rotate.d.ts +20 -7
  55. package/dist/lib/accounting/rotate.js +89 -13
  56. package/dist/lib/accounting/usage.js +11 -16
  57. package/dist/lib/accounts/add.d.ts +138 -0
  58. package/dist/lib/accounts/add.js +651 -0
  59. package/dist/lib/accounts/migrate.d.ts +117 -0
  60. package/dist/lib/accounts/migrate.js +536 -0
  61. package/dist/lib/accounts/slots.d.ts +13 -0
  62. package/dist/lib/accounts/slots.js +100 -0
  63. package/dist/lib/agent-spec/agents.d.ts +16 -0
  64. package/dist/lib/agent-spec/agents.js +32 -4
  65. package/dist/lib/app-bundle-install.js +5 -4
  66. package/dist/lib/auth-health.d.ts +3 -0
  67. package/dist/lib/auth-health.js +11 -0
  68. package/dist/lib/auth-mint.d.ts +52 -16
  69. package/dist/lib/auth-mint.js +117 -39
  70. package/dist/lib/browser/chrome.d.ts +1 -1
  71. package/dist/lib/browser/chrome.js +5 -5
  72. package/dist/lib/byok-usage.js +3 -3
  73. package/dist/lib/claude-account-token.d.ts +47 -3
  74. package/dist/lib/claude-account-token.js +172 -50
  75. package/dist/lib/cloud/antigravity.js +4 -4
  76. package/dist/lib/cloud/cursor.js +4 -4
  77. package/dist/lib/crabbox/cli.d.ts +1 -1
  78. package/dist/lib/crabbox/cli.js +6 -6
  79. package/dist/lib/crabbox/runtimes.d.ts +3 -3
  80. package/dist/lib/crabbox/runtimes.js +5 -5
  81. package/dist/lib/daemon/account-state-daemon-service.d.ts +37 -1
  82. package/dist/lib/daemon/account-state-daemon-service.js +199 -3
  83. package/dist/lib/daemon/auth-sync-service.js +28 -1
  84. package/dist/lib/daemon/daemon.d.ts +6 -16
  85. package/dist/lib/daemon/daemon.js +52 -97
  86. package/dist/lib/daemon/daemon.test-fixture.d.ts +7 -9
  87. package/dist/lib/daemon/daemon.test-fixture.js +22 -40
  88. package/dist/lib/daemon/harness-update-service.d.ts +1 -1
  89. package/dist/lib/daemon/harness-update-service.js +1 -1
  90. package/dist/lib/daemon/runner.d.ts +15 -3
  91. package/dist/lib/daemon/runner.js +49 -24
  92. package/dist/lib/daemon-health.d.ts +1 -2
  93. package/dist/lib/daemon-health.js +7 -6
  94. package/dist/lib/daemon-services.d.ts +1 -1
  95. package/dist/lib/daemon-services.js +1 -11
  96. package/dist/lib/daemon-ticks.js +23 -2
  97. package/dist/lib/daemon-webhooks.d.ts +8 -7
  98. package/dist/lib/daemon-webhooks.js +10 -9
  99. package/dist/lib/device-config.js +1 -1
  100. package/dist/lib/devices/doctor-findings.d.ts +1 -1
  101. package/dist/lib/devices/harness-inventory.d.ts +39 -0
  102. package/dist/lib/devices/harness-inventory.js +126 -4
  103. package/dist/lib/doctor-diff.js +2 -1
  104. package/dist/lib/exec-account-home.d.ts +38 -0
  105. package/dist/lib/exec-account-home.js +164 -0
  106. package/dist/lib/exec.d.ts +30 -0
  107. package/dist/lib/exec.js +101 -31
  108. package/dist/lib/feed/activity-stream.d.ts +60 -0
  109. package/dist/lib/feed/activity-stream.js +271 -0
  110. package/dist/lib/feed/activity.d.ts +7 -0
  111. package/dist/lib/feed/activity.js +10 -4
  112. package/dist/lib/feed/watch.d.ts +9 -3
  113. package/dist/lib/feed/watch.js +72 -36
  114. package/dist/lib/fleet/apply.d.ts +2 -2
  115. package/dist/lib/fleet/apply.js +4 -4
  116. package/dist/lib/fleet/auth-sync.js +4 -3
  117. package/dist/lib/fleet-shared-repo-sync.js +6 -9
  118. package/dist/lib/fleet-shared-state.d.ts +6 -0
  119. package/dist/lib/harness/adapter.d.ts +7 -7
  120. package/dist/lib/harness/adapter.js +10 -6
  121. package/dist/lib/harness/adapters/grok.js +8 -3
  122. package/dist/lib/harness/adapters/muse.js +1 -1
  123. package/dist/lib/harness/adapters/opencode.js +12 -3
  124. package/dist/lib/harness-auth-capabilities.d.ts +61 -0
  125. package/dist/lib/harness-auth-capabilities.js +52 -0
  126. package/dist/lib/helper-versions.d.ts +6 -4
  127. package/dist/lib/helper-versions.js +5 -4
  128. package/dist/lib/hosts/credential-transport.d.ts +10 -0
  129. package/dist/lib/hosts/credential-transport.js +37 -0
  130. package/dist/lib/hosts/dispatch.d.ts +7 -0
  131. package/dist/lib/hosts/dispatch.js +13 -6
  132. package/dist/lib/identity/client.d.ts +3 -3
  133. package/dist/lib/identity/client.js +3 -3
  134. package/dist/lib/installations/index.d.ts +1 -1
  135. package/dist/lib/installations/index.js +1 -1
  136. package/dist/lib/installations/migrate.js +9 -0
  137. package/dist/lib/installations/resolve.js +1 -1
  138. package/dist/lib/installations/shims.d.ts +15 -0
  139. package/dist/lib/installations/shims.js +69 -0
  140. package/dist/lib/installations/store.d.ts +43 -0
  141. package/dist/lib/installations/store.js +75 -7
  142. package/dist/lib/installations/versions.js +1 -1
  143. package/dist/lib/menubar/install-menubar.d.ts +3 -3
  144. package/dist/lib/menubar/install-menubar.js +3 -3
  145. package/dist/lib/native-accounts.d.ts +35 -0
  146. package/dist/lib/native-accounts.js +41 -0
  147. package/dist/lib/net-close.d.ts +11 -0
  148. package/dist/lib/net-close.js +25 -0
  149. package/dist/lib/openclaw-keychain.js +27 -1
  150. package/dist/lib/profiles.js +22 -9
  151. package/dist/lib/project-resources.js +1 -1
  152. package/dist/lib/reserved-stores.d.ts +53 -0
  153. package/dist/lib/reserved-stores.js +120 -0
  154. package/dist/lib/secrets-client.d.ts +191 -0
  155. package/dist/lib/secrets-client.js +710 -0
  156. package/dist/lib/secrets-policy.d.ts +222 -0
  157. package/dist/lib/secrets-policy.js +483 -0
  158. package/dist/lib/secrets-types.d.ts +188 -0
  159. package/dist/lib/secrets-types.js +24 -0
  160. package/dist/lib/service-manifest.js +3 -4
  161. package/dist/lib/session/active.d.ts +38 -4
  162. package/dist/lib/session/active.js +36 -4
  163. package/dist/lib/session/bash-command.js +10 -0
  164. package/dist/lib/session/db.d.ts +65 -2
  165. package/dist/lib/session/db.js +178 -18
  166. package/dist/lib/session/mirror.d.ts +5 -0
  167. package/dist/lib/session/mirror.js +166 -0
  168. package/dist/lib/session/parse.d.ts +49 -0
  169. package/dist/lib/session/parse.js +324 -30
  170. package/dist/lib/session/prompt.d.ts +93 -2
  171. package/dist/lib/session/prompt.js +266 -15
  172. package/dist/lib/session/remote/peer-stream.d.ts +47 -0
  173. package/dist/lib/session/remote/peer-stream.js +142 -0
  174. package/dist/lib/session/remote/watch.d.ts +1 -1
  175. package/dist/lib/session/remote/watch.js +41 -42
  176. package/dist/lib/session/session-cache.d.ts +9 -0
  177. package/dist/lib/session/session-cache.js +40 -2
  178. package/dist/lib/session/sync/config.js +2 -2
  179. package/dist/lib/session/timeline-pass.d.ts +129 -0
  180. package/dist/lib/session/timeline-pass.js +323 -0
  181. package/dist/lib/session/timeline.d.ts +182 -0
  182. package/dist/lib/session/timeline.js +636 -0
  183. package/dist/lib/session/types.d.ts +155 -1
  184. package/dist/lib/sha256-asset.d.ts +11 -16
  185. package/dist/lib/sha256-asset.js +11 -16
  186. package/dist/lib/share/config.js +14 -5
  187. package/dist/lib/signin-badge.d.ts +26 -0
  188. package/dist/lib/signin-badge.js +42 -0
  189. package/dist/lib/staleness/detectors/workflows.js +3 -105
  190. package/dist/lib/staleness/writers/workflows.js +2 -1
  191. package/dist/lib/state.d.ts +7 -4
  192. package/dist/lib/state.js +18 -7
  193. package/dist/lib/summarizer/pass.d.ts +2 -0
  194. package/dist/lib/summarizer/pass.js +14 -1
  195. package/dist/lib/summarizer/summarize.d.ts +7 -0
  196. package/dist/lib/summarizer/summarize.js +3 -0
  197. package/dist/lib/sync-umbrella.js +2 -2
  198. package/dist/lib/teams/agents.js +1 -1
  199. package/dist/lib/types.d.ts +74 -33
  200. package/dist/lib/view-types.d.ts +6 -3
  201. package/dist/lib/workflows-registry.d.ts +71 -0
  202. package/dist/lib/workflows-registry.js +280 -0
  203. package/dist/lib/workflows.d.ts +21 -21
  204. package/dist/lib/workflows.js +12 -327
  205. package/package.json +2 -3
  206. package/scripts/postinstall.js +4 -29
  207. package/dist/commands/secrets-import.d.ts +0 -18
  208. package/dist/commands/secrets-import.js +0 -74
  209. package/dist/commands/secrets-migrate.d.ts +0 -25
  210. package/dist/commands/secrets-migrate.js +0 -334
  211. package/dist/commands/secrets-rotate-passphrase.d.ts +0 -17
  212. package/dist/commands/secrets-rotate-passphrase.js +0 -96
  213. package/dist/commands/secrets-sync.d.ts +0 -11
  214. package/dist/commands/secrets-sync.js +0 -153
  215. package/dist/commands/secrets-vault.d.ts +0 -10
  216. package/dist/commands/secrets-vault.js +0 -130
  217. package/dist/commands/secrets.d.ts +0 -212
  218. package/dist/commands/secrets.js +0 -3030
  219. package/dist/lib/accounts/connect.d.ts +0 -170
  220. package/dist/lib/accounts/connect.js +0 -383
  221. package/dist/lib/daemon/keychain-reap-service.d.ts +0 -17
  222. package/dist/lib/daemon/keychain-reap-service.js +0 -32
  223. package/dist/lib/daemon/secrets-broker-service.d.ts +0 -21
  224. package/dist/lib/daemon/secrets-broker-service.js +0 -51
  225. package/dist/lib/secrets/agent.d.ts +0 -358
  226. package/dist/lib/secrets/agent.js +0 -1291
  227. package/dist/lib/secrets/audit.d.ts +0 -46
  228. package/dist/lib/secrets/audit.js +0 -101
  229. package/dist/lib/secrets/bundles.d.ts +0 -290
  230. package/dist/lib/secrets/bundles.js +0 -1547
  231. package/dist/lib/secrets/download-keychain.d.ts +0 -47
  232. package/dist/lib/secrets/download-keychain.js +0 -70
  233. package/dist/lib/secrets/drivers/rush.d.ts +0 -14
  234. package/dist/lib/secrets/drivers/rush.js +0 -90
  235. package/dist/lib/secrets/fallback.d.ts +0 -48
  236. package/dist/lib/secrets/fallback.js +0 -48
  237. package/dist/lib/secrets/filestore.d.ts +0 -222
  238. package/dist/lib/secrets/filestore.js +0 -1099
  239. package/dist/lib/secrets/headless.d.ts +0 -43
  240. package/dist/lib/secrets/headless.js +0 -56
  241. package/dist/lib/secrets/icloud-import.d.ts +0 -79
  242. package/dist/lib/secrets/icloud-import.js +0 -206
  243. package/dist/lib/secrets/index.d.ts +0 -451
  244. package/dist/lib/secrets/index.js +0 -1568
  245. package/dist/lib/secrets/install-helper.d.ts +0 -72
  246. package/dist/lib/secrets/install-helper.js +0 -245
  247. package/dist/lib/secrets/lease.d.ts +0 -25
  248. package/dist/lib/secrets/lease.js +0 -44
  249. package/dist/lib/secrets/linux.d.ts +0 -77
  250. package/dist/lib/secrets/linux.js +0 -393
  251. package/dist/lib/secrets/list-filter.d.ts +0 -109
  252. package/dist/lib/secrets/list-filter.js +0 -261
  253. package/dist/lib/secrets/mcp.d.ts +0 -93
  254. package/dist/lib/secrets/mcp.js +0 -211
  255. package/dist/lib/secrets/profiles.d.ts +0 -10
  256. package/dist/lib/secrets/profiles.js +0 -13
  257. package/dist/lib/secrets/push.d.ts +0 -133
  258. package/dist/lib/secrets/push.js +0 -273
  259. package/dist/lib/secrets/rc-hygiene.d.ts +0 -63
  260. package/dist/lib/secrets/rc-hygiene.js +0 -143
  261. package/dist/lib/secrets/read-backoff.d.ts +0 -27
  262. package/dist/lib/secrets/read-backoff.js +0 -64
  263. package/dist/lib/secrets/reaper.d.ts +0 -90
  264. package/dist/lib/secrets/reaper.js +0 -243
  265. package/dist/lib/secrets/remote.d.ts +0 -152
  266. package/dist/lib/secrets/remote.js +0 -344
  267. package/dist/lib/secrets/reserved-sync.d.ts +0 -66
  268. package/dist/lib/secrets/reserved-sync.js +0 -147
  269. package/dist/lib/secrets/scope.d.ts +0 -26
  270. package/dist/lib/secrets/scope.js +0 -29
  271. package/dist/lib/secrets/session-store.d.ts +0 -107
  272. package/dist/lib/secrets/session-store.js +0 -342
  273. package/dist/lib/secrets/sync-backend.d.ts +0 -48
  274. package/dist/lib/secrets/sync-backend.js +0 -13
  275. package/dist/lib/secrets/sync-commands.d.ts +0 -21
  276. package/dist/lib/secrets/sync-commands.js +0 -21
  277. package/dist/lib/secrets/sync.d.ts +0 -48
  278. package/dist/lib/secrets/sync.js +0 -237
  279. package/dist/lib/secrets/unlock-hints.d.ts +0 -27
  280. package/dist/lib/secrets/unlock-hints.js +0 -36
  281. package/dist/lib/secrets/usage-db.d.ts +0 -46
  282. package/dist/lib/secrets/usage-db.js +0 -96
  283. package/dist/lib/secrets/vault-age-helper.d.ts +0 -1
  284. package/dist/lib/secrets/vault-age-helper.js +0 -34
  285. package/dist/lib/secrets/vault.d.ts +0 -49
  286. package/dist/lib/secrets/vault.js +0 -399
  287. package/dist/lib/secrets/windows.d.ts +0 -81
  288. package/dist/lib/secrets/windows.js +0 -556
  289. package/scripts/install-helper.js +0 -97
  290. /package/dist/lib/{secrets/sync-passphrase.d.ts → sync-passphrase.d.ts} +0 -0
  291. /package/dist/lib/{secrets/sync-passphrase.js → sync-passphrase.js} +0 -0
@@ -1,133 +0,0 @@
1
- /**
2
- * Serialize a resolved env map to `.env` lines that round-trip losslessly through
3
- * `parseDotenv` on the remote: `KEY="VALUE"`. parseDotenv strips exactly one outer
4
- * quote pair and takes the inner bytes verbatim (no unescaping), so any single-line
5
- * value survives unchanged with no escaping. Newlines would break its line-based
6
- * parse, so multi-line values are rejected rather than silently corrupted.
7
- */
8
- export declare function bundleEnvToDotenv(env: Record<string, string>): string;
9
- /** Where the bundle should live ON THE REMOTE. */
10
- export type RemoteBackend = 'keychain' | 'file';
11
- /** A bundle read once, ready to push to any number of hosts. */
12
- export interface ResolvedBundleForPush {
13
- /** key -> value. Never logged; only its KEY NAMES are ever surfaced. */
14
- env: Record<string, string>;
15
- /** The same values as a dotenv blob, shipped over ssh stdin (never argv). */
16
- dotenv: string;
17
- keyCount: number;
18
- }
19
- export interface PushBundleOptions {
20
- remoteBackend: RemoteBackend;
21
- /** Overwrite a key that already exists on the remote. */
22
- force?: boolean;
23
- /**
24
- * Ignored. File-backend export never forwards AGENTS_SECRETS_PASSPHRASE
25
- * (PHNX-2371); the remote auto-provisions its own machine-local key.
26
- * Kept on the options type so existing callers that passed one still compile.
27
- */
28
- passphrase?: string;
29
- /** Label for the audit trail — `export --device` vs `fleet apply`. */
30
- operation: string;
31
- /** Preserve an automation account's permanent prompt-free policy remotely. */
32
- policyNever?: boolean;
33
- /** Permit a human-invoked push to read locally without requiring the agent broker. */
34
- agentOnly?: boolean;
35
- /** Non-secret literals whose bundle value kind must survive the dotenv transport. */
36
- literalValues?: Record<string, string>;
37
- /** Per-SSH-operation deadline. Async daemon callers must set this explicitly. */
38
- timeoutMs?: number;
39
- }
40
- export interface PushBundleResult {
41
- ok: boolean;
42
- host: string;
43
- bundle: string;
44
- keyCount: number;
45
- /** One line for the caller to render. Never contains a secret value. */
46
- message: string;
47
- }
48
- export interface LiteralRestorePlan {
49
- key: string;
50
- removeArgs: string[];
51
- addArgs: string[];
52
- }
53
- /** Preserve the literal-vs-secret schema that dotenv transport cannot encode. */
54
- export declare function planLiteralRestoration(bundle: string, literalValues?: Record<string, string>): LiteralRestorePlan[];
55
- /**
56
- * Read and resolve a bundle once, for pushing to one or more hosts.
57
- *
58
- * `keyMode: 'storage'` is fixed — a push always sends storage-shaped values.
59
- *
60
- * `agentOnly` is the caller's to decide, and it is NOT a security level: it
61
- * selects the broker-only read (fail fast with the `agents secrets unlock` hint)
62
- * over the interactive read (macOS may raise a Touch ID sheet). The headless
63
- * guard exists because an AGENT LAUNCH must never enumerate the keychain — a
64
- * per-bundle prefix becomes a broad `agents-cli.` scan after service-name
65
- * hashing, and macOS evaluates unrelated biometry ACLs during it, one sheet per
66
- * installed version (bundles.ts, RUSH-2440). A human at a TTY is not that case:
67
- * they can answer one prompt, and the interactive sibling reads (`view --reveal`,
68
- * `exec`) already let them. NOT `get` — it is unconditionally `agentOnly` on
69
- * purpose, because prompting would hang a `$(agents secrets get …)` capture
70
- * mid-pipeline (SEC-13b). It defaults to `true` so an automated caller that says
71
- * nothing stays broker-only.
72
- */
73
- export declare function resolveBundleForPush(bundle: string, caller: string, opts?: {
74
- agentOnly?: boolean;
75
- }): ResolvedBundleForPush;
76
- /**
77
- * WHICH transport a (backend, target-OS) pair selects, and the exact bytes it
78
- * will send. One of four outcomes, and picking the wrong one is silent: a
79
- * Windows target handed a POSIX `bash -lc` produces garbage, and a Windows
80
- * target handed `--from -` hangs forever on a stdin the `agents.ps1` shim never
81
- * forwards. Neither shows up as a failed ssh, so the selection is the thing
82
- * worth pinning.
83
- *
84
- * Separated from execution so it is decidable without a network: the branch is
85
- * chosen from the device registry alone, and `pushResolvedBundleToHost` below
86
- * does nothing but run what this returns.
87
- */
88
- export type PushTransport =
89
- /** No supported command exists for this pair — fail loud, never a wrong path. */
90
- {
91
- kind: 'refuse';
92
- message: string;
93
- }
94
- /**
95
- * A command sent over the raw ssh engine, with the .env on stdin. `multiplex`
96
- * is always `false`: a push ships credential bytes, so its connection must not
97
- * reuse — or leave behind — a persistent authenticated control master to the
98
- * destination the way the default fan-out connections do (RUSH-2527). This
99
- * mirrors the `--copy-creds` dispatch posture (`multiplex: !opts.copyCreds`).
100
- */
101
- | {
102
- kind: 'ssh';
103
- remoteCmd: string;
104
- input: string;
105
- multiplex: false;
106
- }
107
- /** The OS-aware `agents secrets` wrapper, the READ inverse's own path. Never multiplexed — see the `ssh` kind. */
108
- | {
109
- kind: 'remote-secrets';
110
- args: string[];
111
- input: string;
112
- multiplex: false;
113
- };
114
- /** Choose the transport for one push. Pure: registry read in, plan out. */
115
- export declare function planPushTransport(resolved: ResolvedBundleForPush, bundle: string, host: string, opts: PushBundleOptions): PushTransport;
116
- /**
117
- * Push an already-resolved bundle to ONE host.
118
- *
119
- * Drives the remote's own `agents secrets import --from -`, so the values land
120
- * in the remote's chosen backend and the .env is read off ssh stdin rather than
121
- * parsed by a remote shell. `import` auto-creates the bundle.
122
- */
123
- export declare function pushResolvedBundleToHost(resolved: ResolvedBundleForPush, bundle: string, host: string, opts: PushBundleOptions): PushBundleResult;
124
- /** Resolve and push in one call — for a single host. */
125
- export declare function pushBundleToHost(bundle: string, host: string, opts: PushBundleOptions): PushBundleResult;
126
- /**
127
- * Async, kill-bounded push for daemon services. It preserves the foreground
128
- * primitive's transport, read-back verification, and literal restoration; only
129
- * the SSH execution strategy differs.
130
- */
131
- export declare function pushResolvedBundleToHostAsync(resolved: ResolvedBundleForPush, bundle: string, host: string, opts: PushBundleOptions): Promise<PushBundleResult>;
132
- /** Resolve locally once, then run the daemon-safe async push. */
133
- export declare function pushBundleToHostAsync(bundle: string, host: string, opts: PushBundleOptions): Promise<PushBundleResult>;
@@ -1,273 +0,0 @@
1
- /**
2
- * Push one bundle's values to a remote host over SSH — the provisioning
3
- * primitive behind `agents secrets export --device` and, from RUSH-1968, behind
4
- * `agents fleet apply --provision-secrets`.
5
- *
6
- * This logic used to live inline in the `export --device` command action. It moved
7
- * here because `lib/fleet/apply.ts` needs it and a lib MUST NOT import a command
8
- * module — and because the absence of a callable primitive is part of why
9
- * `fleet apply` never provisioned secrets at all, leaving an operator to
10
- * hand-export the file store's master key across the fleet.
11
- *
12
- * Two rules shape the shape of this module:
13
- *
14
- * - **It never prints.** The lib layer stays `console.*`-free (SEC-14/SEC-16), so
15
- * every outcome is returned as data and the CALLER renders it. That is also
16
- * what lets `fleet apply` fold a push into its own per-device report instead of
17
- * interleaving stray lines into it.
18
- * - **Resolve once, push N times.** `resolveBundleForPush` is separate because
19
- * reading a bundle can prompt (Touch ID); doing it per host would prompt per
20
- * host. `export --device a,b,c` resolves once and pushes three times.
21
- */
22
- import { sshExec, sshExecAsync } from '../ssh-exec.js';
23
- import { remoteShellFor, buildWindowsStdinImportCommand } from '../hosts/remote-cmd.js';
24
- import { resolveRemoteOsSync } from '../hosts/remote-os.js';
25
- import { remoteSecretsRaw, remoteSecretsRawAsync, verifyRemoteKeychainPush, verifyRemoteKeychainPushAsync, keychainWriteFailureMessage, buildRemoteFileImportCommand, credentialTransportSshOpts, } from './remote.js';
26
- import { readAndResolveBundleEnv } from './bundles.js';
27
- /**
28
- * Serialize a resolved env map to `.env` lines that round-trip losslessly through
29
- * `parseDotenv` on the remote: `KEY="VALUE"`. parseDotenv strips exactly one outer
30
- * quote pair and takes the inner bytes verbatim (no unescaping), so any single-line
31
- * value survives unchanged with no escaping. Newlines would break its line-based
32
- * parse, so multi-line values are rejected rather than silently corrupted.
33
- */
34
- export function bundleEnvToDotenv(env) {
35
- const lines = [];
36
- for (const [k, v] of Object.entries(env)) {
37
- if (/[\r\n]/.test(v)) {
38
- throw new Error(`Key '${k}' has a multi-line value; the SSH .env transport can't carry newlines. ` +
39
- `Set it directly on the remote with 'agents secrets add ${k} --value-stdin'.`);
40
- }
41
- lines.push(`${k}="${v}"`);
42
- }
43
- return lines.join('\n') + '\n';
44
- }
45
- /** Preserve the literal-vs-secret schema that dotenv transport cannot encode. */
46
- export function planLiteralRestoration(bundle, literalValues = {}) {
47
- return Object.entries(literalValues).map(([key, value]) => ({
48
- key,
49
- removeArgs: ['remove', bundle, key, '--yes'],
50
- addArgs: ['add', bundle, key, '--value', value],
51
- }));
52
- }
53
- /**
54
- * Read and resolve a bundle once, for pushing to one or more hosts.
55
- *
56
- * `keyMode: 'storage'` is fixed — a push always sends storage-shaped values.
57
- *
58
- * `agentOnly` is the caller's to decide, and it is NOT a security level: it
59
- * selects the broker-only read (fail fast with the `agents secrets unlock` hint)
60
- * over the interactive read (macOS may raise a Touch ID sheet). The headless
61
- * guard exists because an AGENT LAUNCH must never enumerate the keychain — a
62
- * per-bundle prefix becomes a broad `agents-cli.` scan after service-name
63
- * hashing, and macOS evaluates unrelated biometry ACLs during it, one sheet per
64
- * installed version (bundles.ts, RUSH-2440). A human at a TTY is not that case:
65
- * they can answer one prompt, and the interactive sibling reads (`view --reveal`,
66
- * `exec`) already let them. NOT `get` — it is unconditionally `agentOnly` on
67
- * purpose, because prompting would hang a `$(agents secrets get …)` capture
68
- * mid-pipeline (SEC-13b). It defaults to `true` so an automated caller that says
69
- * nothing stays broker-only.
70
- */
71
- export function resolveBundleForPush(bundle, caller, opts = {}) {
72
- const agentOnly = opts.agentOnly ?? true;
73
- const { env } = readAndResolveBundleEnv(bundle, { caller, keyMode: 'storage', agentOnly });
74
- return { env, dotenv: bundleEnvToDotenv(env), keyCount: Object.keys(env).length };
75
- }
76
- function isPowershellTarget(host) {
77
- return remoteShellFor(resolveRemoteOsSync(host.split('@').pop() ?? host)) === 'powershell';
78
- }
79
- /** Choose the transport for one push. Pure: registry read in, plan out. */
80
- export function planPushTransport(resolved, bundle, host, opts) {
81
- const powershell = isPowershellTarget(host);
82
- if (opts.remoteBackend === 'file') {
83
- // Both file-backend paths build a POSIX `bash -lc` command. Refuse a Windows
84
- // target cleanly rather than emit broken PowerShell (fail loud at the
85
- // boundary, never a silent wrong path).
86
- if (powershell) {
87
- return { kind: 'refuse', message: 'file backend export to a Windows target is not yet supported' };
88
- }
89
- const { remoteCmd, input } = buildRemoteFileImportCommand(bundle, resolved.dotenv, {
90
- force: opts.force,
91
- policyNever: opts.policyNever,
92
- });
93
- return { kind: 'ssh', remoteCmd, input, multiplex: false };
94
- }
95
- if (powershell) {
96
- // Keychain on a Windows target: the `agents.ps1` shim doesn't forward
97
- // ssh-piped stdin to node, so `--from -` would hang. Bridge the piped .env
98
- // through PowerShell into a temp file and import `--from <file>` (deleted
99
- // afterwards). Same hardened ssh engine; the .env still only ever crosses
100
- // the wire over ssh stdin.
101
- return {
102
- kind: 'ssh',
103
- remoteCmd: buildWindowsStdinImportCommand(bundle, { force: opts.force, policyNever: opts.policyNever }),
104
- input: resolved.dotenv,
105
- multiplex: false,
106
- };
107
- }
108
- // Keychain on a POSIX target: OS-aware wrapping + the hardened ssh engine
109
- // (BatchMode, ConnectTimeout, keepalive, control-socket reuse) via the same
110
- // path the READ inverse (`remoteResolveEnv`) uses.
111
- return {
112
- kind: 'remote-secrets',
113
- args: [
114
- 'import', bundle, '--from', '-',
115
- ...(opts.force ? ['--force'] : []),
116
- ...(opts.policyNever ? ['--policy', 'never', '--i-understand'] : []),
117
- ],
118
- input: resolved.dotenv,
119
- multiplex: false,
120
- };
121
- }
122
- /**
123
- * Push an already-resolved bundle to ONE host.
124
- *
125
- * Drives the remote's own `agents secrets import --from -`, so the values land
126
- * in the remote's chosen backend and the .env is read off ssh stdin rather than
127
- * parsed by a remote shell. `import` auto-creates the bundle.
128
- */
129
- export function pushResolvedBundleToHost(resolved, bundle, host, opts) {
130
- const fail = (message) => ({ ok: false, host, bundle, keyCount: resolved.keyCount, message });
131
- const plan = planPushTransport(resolved, bundle, host, opts);
132
- if (plan.kind === 'refuse')
133
- return fail(plan.message);
134
- // Every ssh below rides the credential-transport posture: the managed host key
135
- // is pinned (a changed key is refused) and the connection never reuses or
136
- // leaves a persistent authenticated control master to the destination
137
- // (RUSH-2527, `credentialTransportSshOpts`). The plan's `multiplex: false` is
138
- // the raw-`ssh` branch's half of that; the `remote-secrets` branch and the
139
- // read-back/policy/literal follow-ups pass `secret: true` for the same posture.
140
- const res = plan.kind === 'ssh'
141
- ? sshExec(host, plan.remoteCmd, { input: plan.input, timeoutMs: opts.timeoutMs, hostKeyOpts: credentialTransportSshOpts(host).hostKeyOpts, multiplex: plan.multiplex })
142
- : remoteSecretsRaw(host, plan.args, { input: plan.input, timeoutMs: opts.timeoutMs, osLookupName: host, secret: true });
143
- if (res.code === null) {
144
- return fail(res.stderr.trim() || (res.timedOut ? 'ssh timed out' : 'ssh failed'));
145
- }
146
- if (res.code !== 0) {
147
- const msg = (res.stderr || res.stdout || '').trim();
148
- return fail(`remote import failed (exit ${res.code})${msg ? `: ${msg}` : ''}`);
149
- }
150
- // A successful remote import is not proof the keys are readable. Two silent
151
- // failures hide behind exit 0:
152
- // - keychain: a macOS login keychain is locked under headless SSH, so the
153
- // write lands metadata but no readable value items.
154
- // - file: a forwarded AGENTS_SECRETS_PASSPHRASE keys ciphertext to a secret
155
- // the destination daemon does not hold (PHNX-2371).
156
- // Read the bundle back the way a later resolve will and FAIL LOUDLY.
157
- const verdict = verifyRemoteKeychainPush(host, bundle, Object.keys(resolved.env), { osLookupName: host, secret: true, timeoutMs: opts.timeoutMs });
158
- if (!verdict.ok) {
159
- if (opts.remoteBackend === 'keychain' && verdict.kind === 'locked-keychain') {
160
- return fail(keychainWriteFailureMessage(host, bundle, verdict.reason));
161
- }
162
- if (opts.remoteBackend === 'file') {
163
- return fail(`${host}: pushed '${bundle}' but the remote could not decrypt it (${verdict.reason}). ` +
164
- `File-backend export never forwards AGENTS_SECRETS_PASSPHRASE — the remote keys ` +
165
- `the bundle under its own machine-local key. If the destination still cannot read, ` +
166
- `unset a stale AGENTS_SECRETS_PASSPHRASE there (\`agents doctor\`).`);
167
- }
168
- return fail(`pushed '${bundle}' but could not verify it on the remote: ${verdict.reason}`);
169
- }
170
- for (const step of planLiteralRestoration(bundle, opts.literalValues)) {
171
- const removed = remoteSecretsRaw(host, step.removeArgs, { osLookupName: host, secret: true, timeoutMs: opts.timeoutMs });
172
- if (removed.code !== 0) {
173
- const msg = (removed.stderr || removed.stdout || '').trim();
174
- return fail(`pushed '${bundle}' but could not replace transported ${step.key}${msg ? `: ${msg}` : ''}`);
175
- }
176
- const literal = remoteSecretsRaw(host, step.addArgs, { osLookupName: host, secret: true, timeoutMs: opts.timeoutMs });
177
- if (literal.code !== 0) {
178
- const msg = (literal.stderr || literal.stdout || '').trim();
179
- return fail(`pushed '${bundle}' but could not preserve literal ${step.key}${msg ? `: ${msg}` : ''}`);
180
- }
181
- }
182
- const remoteMsg = (res.stdout || '').trim().split('\n').map((l) => l.trim()).filter(Boolean).pop();
183
- return {
184
- ok: true,
185
- host,
186
- bundle,
187
- keyCount: resolved.keyCount,
188
- message: remoteMsg || `${resolved.keyCount} key(s) exported`,
189
- };
190
- }
191
- /** Resolve and push in one call — for a single host. */
192
- export function pushBundleToHost(bundle, host, opts) {
193
- return pushResolvedBundleToHost(resolveBundleForPush(bundle, opts.operation, { agentOnly: opts.agentOnly }), bundle, host, opts);
194
- }
195
- /**
196
- * Async, kill-bounded push for daemon services. It preserves the foreground
197
- * primitive's transport, read-back verification, and literal restoration; only
198
- * the SSH execution strategy differs.
199
- */
200
- export async function pushResolvedBundleToHostAsync(resolved, bundle, host, opts) {
201
- const fail = (message) => ({ ok: false, host, bundle, keyCount: resolved.keyCount, message });
202
- const plan = planPushTransport(resolved, bundle, host, opts);
203
- if (plan.kind === 'refuse')
204
- return fail(plan.message);
205
- const res = plan.kind === 'ssh'
206
- ? await sshExecAsync(host, plan.remoteCmd, {
207
- input: plan.input,
208
- timeoutMs: opts.timeoutMs,
209
- hostKeyOpts: credentialTransportSshOpts(host).hostKeyOpts,
210
- multiplex: plan.multiplex,
211
- })
212
- : await remoteSecretsRawAsync(host, plan.args, {
213
- input: plan.input,
214
- timeoutMs: opts.timeoutMs,
215
- osLookupName: host,
216
- secret: true,
217
- });
218
- if (res.code === null)
219
- return fail(res.stderr.trim() || (res.timedOut ? 'ssh timed out' : 'ssh failed'));
220
- if (res.code !== 0) {
221
- const msg = (res.stderr || res.stdout || '').trim();
222
- return fail(`remote import failed (exit ${res.code})${msg ? `: ${msg}` : ''}`);
223
- }
224
- const verdict = await verifyRemoteKeychainPushAsync(host, bundle, Object.keys(resolved.env), {
225
- osLookupName: host,
226
- secret: true,
227
- timeoutMs: opts.timeoutMs,
228
- });
229
- if (!verdict.ok) {
230
- if (opts.remoteBackend === 'keychain' && verdict.kind === 'locked-keychain') {
231
- return fail(keychainWriteFailureMessage(host, bundle, verdict.reason));
232
- }
233
- if (opts.remoteBackend === 'file') {
234
- return fail(`${host}: pushed '${bundle}' but the remote could not decrypt it (${verdict.reason}). ` +
235
- `File-backend export never forwards AGENTS_SECRETS_PASSPHRASE — the remote keys ` +
236
- `the bundle under its own machine-local key. If the destination still cannot read, ` +
237
- `unset a stale AGENTS_SECRETS_PASSPHRASE there (\`agents doctor\`).`);
238
- }
239
- return fail(`pushed '${bundle}' but could not verify it on the remote: ${verdict.reason}`);
240
- }
241
- for (const step of planLiteralRestoration(bundle, opts.literalValues)) {
242
- const removed = await remoteSecretsRawAsync(host, step.removeArgs, {
243
- osLookupName: host,
244
- secret: true,
245
- timeoutMs: opts.timeoutMs,
246
- });
247
- if (removed.code !== 0) {
248
- const msg = (removed.stderr || removed.stdout || '').trim();
249
- return fail(`pushed '${bundle}' but could not replace transported ${step.key}${msg ? `: ${msg}` : ''}`);
250
- }
251
- const literal = await remoteSecretsRawAsync(host, step.addArgs, {
252
- osLookupName: host,
253
- secret: true,
254
- timeoutMs: opts.timeoutMs,
255
- });
256
- if (literal.code !== 0) {
257
- const msg = (literal.stderr || literal.stdout || '').trim();
258
- return fail(`pushed '${bundle}' but could not preserve literal ${step.key}${msg ? `: ${msg}` : ''}`);
259
- }
260
- }
261
- const remoteMsg = (res.stdout || '').trim().split('\n').map((line) => line.trim()).filter(Boolean).pop();
262
- return {
263
- ok: true,
264
- host,
265
- bundle,
266
- keyCount: resolved.keyCount,
267
- message: remoteMsg || `${resolved.keyCount} key(s) exported`,
268
- };
269
- }
270
- /** Resolve locally once, then run the daemon-safe async push. */
271
- export function pushBundleToHostAsync(bundle, host, opts) {
272
- return pushResolvedBundleToHostAsync(resolveBundleForPush(bundle, opts.operation, { agentOnly: opts.agentOnly }), bundle, host, opts);
273
- }
@@ -1,63 +0,0 @@
1
- /**
2
- * Detect credentials exported from shell rc files.
3
- *
4
- * A secret exported from `~/.zshenv`, `~/.zshrc`, `~/.bashrc`, `~/.profile`, …
5
- * is inherited by every process the login shell spawns and is readable from
6
- * `/proc/<pid>/environ` by any same-user process — the master passphrase leak
7
- * behind RUSH-1968. `.zshenv` in particular is sourced by *every* zsh
8
- * invocation, including non-interactive `ssh host 'cmd'`, so the value lands in
9
- * the environment of essentially everything the box runs.
10
- *
11
- * The keychain-backed store (`agents secrets`) is the sanctioned home for
12
- * credentials; the file-store master passphrase belongs in the off-env 0600 key
13
- * file (`~/.agents/.secrets-key/passphrase`), never a shell rc export.
14
- *
15
- * This scanner powers a warn-level advisory in `agents doctor`. It reads only
16
- * the exported variable *name* and its line number — never the value — so a
17
- * finding can be printed, logged, or shipped without leaking the secret.
18
- */
19
- /** Shell rc files sourced at login/startup, in the order a report lists them. */
20
- export declare const RC_FILENAMES: readonly [".zshenv", ".zprofile", ".zshrc", ".zlogin", ".bash_profile", ".bash_login", ".bashrc", ".profile", ".kshrc"];
21
- /**
22
- * A single credential-shaped export found in an rc file. Carries the variable
23
- * NAME and location only — the value is never captured.
24
- */
25
- export interface RcSecretFinding {
26
- /** Basename of the rc file, e.g. `.zshenv`. */
27
- file: string;
28
- /** 1-based line number of the export. */
29
- line: number;
30
- /** The exported variable name (e.g. `AGENTS_SECRETS_PASSPHRASE`). Never the value. */
31
- name: string;
32
- /** The file-store master passphrase gets called out separately — it is the highest-severity case. */
33
- isMasterPassphrase: boolean;
34
- }
35
- /** The file-store master key. Its own resolution prefers an off-env 0600 file, so
36
- * a shell-rc export is always wrong once the store exists (RUSH-1968). */
37
- export declare const MASTER_PASSPHRASE = "AGENTS_SECRETS_PASSPHRASE";
38
- /**
39
- * True when the file-store master key is live in THIS process's environment.
40
- *
41
- * The scanner above reads FILES, and that leaves a hole: a value inherited by a
42
- * long-lived process outlives the rc line that set it, so deleting the export
43
- * makes `scanRcExports` report clean while every shell, editor and agent started
44
- * beforehand still carries the key and passes it to everything it spawns.
45
- * Returns a boolean — never the value — so a finding built from it can be
46
- * printed, logged, or shipped without leaking the secret.
47
- */
48
- export declare function masterPassphraseInEnv(): boolean;
49
- /** True if a variable name looks like it holds a credential value. */
50
- export declare function isCredentialName(name: string): boolean;
51
- /**
52
- * Scan one rc file's contents for credential-shaped exports. Pure — takes the
53
- * text, returns findings (names + line numbers only, never values). A trailing
54
- * `# comment` is stripped before matching so a commented-out export is ignored;
55
- * the variable name always precedes any inline `#`, so stripping never loses it.
56
- */
57
- export declare function scanRcExports(basename: string, content: string): RcSecretFinding[];
58
- /**
59
- * Scan the current user's shell rc files. Reads each file that exists under
60
- * `homeDir` (default `os.homedir()`) and aggregates the findings. Missing or
61
- * unreadable files are skipped silently — an advisory, not a hard requirement.
62
- */
63
- export declare function scanUserRcFiles(homeDir?: string): RcSecretFinding[];
@@ -1,143 +0,0 @@
1
- /**
2
- * Detect credentials exported from shell rc files.
3
- *
4
- * A secret exported from `~/.zshenv`, `~/.zshrc`, `~/.bashrc`, `~/.profile`, …
5
- * is inherited by every process the login shell spawns and is readable from
6
- * `/proc/<pid>/environ` by any same-user process — the master passphrase leak
7
- * behind RUSH-1968. `.zshenv` in particular is sourced by *every* zsh
8
- * invocation, including non-interactive `ssh host 'cmd'`, so the value lands in
9
- * the environment of essentially everything the box runs.
10
- *
11
- * The keychain-backed store (`agents secrets`) is the sanctioned home for
12
- * credentials; the file-store master passphrase belongs in the off-env 0600 key
13
- * file (`~/.agents/.secrets-key/passphrase`), never a shell rc export.
14
- *
15
- * This scanner powers a warn-level advisory in `agents doctor`. It reads only
16
- * the exported variable *name* and its line number — never the value — so a
17
- * finding can be printed, logged, or shipped without leaking the secret.
18
- */
19
- import * as fs from 'fs';
20
- import * as os from 'os';
21
- import * as path from 'path';
22
- /** Shell rc files sourced at login/startup, in the order a report lists them. */
23
- export const RC_FILENAMES = [
24
- '.zshenv',
25
- '.zprofile',
26
- '.zshrc',
27
- '.zlogin',
28
- '.bash_profile',
29
- '.bash_login',
30
- '.bashrc',
31
- '.profile',
32
- '.kshrc',
33
- ];
34
- /** The file-store master key. Its own resolution prefers an off-env 0600 file, so
35
- * a shell-rc export is always wrong once the store exists (RUSH-1968). */
36
- export const MASTER_PASSPHRASE = 'AGENTS_SECRETS_PASSPHRASE';
37
- /**
38
- * True when the file-store master key is live in THIS process's environment.
39
- *
40
- * The scanner above reads FILES, and that leaves a hole: a value inherited by a
41
- * long-lived process outlives the rc line that set it, so deleting the export
42
- * makes `scanRcExports` report clean while every shell, editor and agent started
43
- * beforehand still carries the key and passes it to everything it spawns.
44
- * Returns a boolean — never the value — so a finding built from it can be
45
- * printed, logged, or shipped without leaking the secret.
46
- */
47
- export function masterPassphraseInEnv() {
48
- return (process.env[MASTER_PASSPHRASE] ?? '').length > 0;
49
- }
50
- /**
51
- * Last `_`-delimited segment values that mark a variable as credential-shaped.
52
- * Matched against the FINAL segment (segment equality, not substring) so
53
- * `X_API_KEY` fires on `KEY` while `SOME_KEY_PATH` (ends `PATH`) and `MONKEY`
54
- * (single segment, not equal to `KEY`) do not.
55
- */
56
- const CREDENTIAL_SEGMENTS = new Set([
57
- 'PASSPHRASE',
58
- 'PASSWORD',
59
- 'PASSWD',
60
- 'SECRET',
61
- 'SECRETS',
62
- 'TOKEN',
63
- 'KEY',
64
- 'APIKEY',
65
- 'PAT',
66
- 'CREDENTIAL',
67
- 'CREDENTIALS',
68
- ]);
69
- /**
70
- * Final segments that look credential-shaped but name a file path, socket, or
71
- * config knob rather than a secret value — excluded to cut false positives.
72
- * Applied to the whole name's final segment before the credential check.
73
- */
74
- const BENIGN_FINAL_SEGMENTS = new Set([
75
- 'PATH',
76
- 'FILE',
77
- 'DIR',
78
- 'SOCK',
79
- 'SOCKET',
80
- 'ID',
81
- 'NAME',
82
- 'PARALLELISM', // TOKENIZERS_PARALLELISM and friends
83
- ]);
84
- /** True if a variable name looks like it holds a credential value. */
85
- export function isCredentialName(name) {
86
- if (name === MASTER_PASSPHRASE)
87
- return true;
88
- const segments = name.toUpperCase().split('_');
89
- const last = segments[segments.length - 1];
90
- if (BENIGN_FINAL_SEGMENTS.has(last))
91
- return false;
92
- return CREDENTIAL_SEGMENTS.has(last);
93
- }
94
- // Matches an env assignment that puts a value in the environment:
95
- // export NAME=... (sh/bash/zsh)
96
- // typeset -x NAME=... / declare -x NAME=... (bash/zsh export-attribute form)
97
- const EXPORT_RE = /^\s*(?:export\s+|(?:typeset|declare)\s+-[A-Za-z]*x[A-Za-z]*\s+)([A-Za-z_][A-Za-z0-9_]*)\s*=/;
98
- /**
99
- * Scan one rc file's contents for credential-shaped exports. Pure — takes the
100
- * text, returns findings (names + line numbers only, never values). A trailing
101
- * `# comment` is stripped before matching so a commented-out export is ignored;
102
- * the variable name always precedes any inline `#`, so stripping never loses it.
103
- */
104
- export function scanRcExports(basename, content) {
105
- const out = [];
106
- const lines = content.split('\n');
107
- for (let i = 0; i < lines.length; i++) {
108
- const code = lines[i].replace(/#.*$/, '');
109
- const m = EXPORT_RE.exec(code);
110
- if (!m)
111
- continue;
112
- const name = m[1];
113
- if (!isCredentialName(name))
114
- continue;
115
- out.push({
116
- file: basename,
117
- line: i + 1,
118
- name,
119
- isMasterPassphrase: name === MASTER_PASSPHRASE,
120
- });
121
- }
122
- return out;
123
- }
124
- /**
125
- * Scan the current user's shell rc files. Reads each file that exists under
126
- * `homeDir` (default `os.homedir()`) and aggregates the findings. Missing or
127
- * unreadable files are skipped silently — an advisory, not a hard requirement.
128
- */
129
- export function scanUserRcFiles(homeDir = os.homedir()) {
130
- const out = [];
131
- for (const name of RC_FILENAMES) {
132
- const fp = path.join(homeDir, name);
133
- let content;
134
- try {
135
- content = fs.readFileSync(fp, 'utf8');
136
- }
137
- catch {
138
- continue; // absent or unreadable — nothing to report
139
- }
140
- out.push(...scanRcExports(name, content));
141
- }
142
- return out;
143
- }
@@ -1,27 +0,0 @@
1
- /**
2
- * Negative memo for failed/cancelled macOS keychain reads (the "back-off").
3
- *
4
- * A cancelled Touch ID sheet is the user saying "not now" — but a polling
5
- * caller (AGI EXT host's `agents view` loop, a watch script)
6
- * retries the same read a few seconds later and pops the sheet again, forever.
7
- * The headless guard (index.ts `assertRawKeychainReadAllowed`) covers the
8
- * no-TTY case; this memo covers a context that CAN prompt but just had its
9
- * prompt cancelled or fail: the next read of the same item within the TTL
10
- * throws the back-off error instead of re-prompting. Any successful read (or
11
- * write) of the item clears the memo.
12
- *
13
- * Stored as regenerable state under `~/.agents/.cache/keychain-read-backoff/`,
14
- * one file per item (filename is a hash of the item name; the file carries no
15
- * secret material — a name and a deadline only). All operations are
16
- * best-effort: a lost memo costs at most one extra prompt, never a read.
17
- */
18
- /** How long a failed/cancelled read suppresses retries of the same item. */
19
- export declare const KEYCHAIN_READ_BACKOFF_TTL_MS: number;
20
- /** Test seam: point the memo at a temp dir so tests never touch the real cache. */
21
- export declare function setKeychainReadBackoffDirForTest(dir: string | null): void;
22
- /** True while `key` is inside the back-off window opened by a failed/cancelled read. */
23
- export declare function isKeychainReadBackedOff(key: string, now?: number): boolean;
24
- /** Open (or refresh) the back-off window for `key` after a failed/cancelled read. */
25
- export declare function noteKeychainReadFailure(key: string, now?: number): void;
26
- /** Clear the memo: a successful read or write of the item resets the back-off. */
27
- export declare function clearKeychainReadBackoff(key: string): void;