flowviant 0.103.0 → 0.104.0

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 (140) hide show
  1. package/bin/cli.mjs +87 -474
  2. package/bin/lib/admission.mjs +19 -3
  3. package/bin/lib/agentPlan.mjs +2 -2
  4. package/bin/lib/agentTaskKinds.mjs +93 -0
  5. package/bin/lib/agySessions.mjs +158 -0
  6. package/bin/lib/artifactPolicy.mjs +44 -0
  7. package/bin/lib/artifactPreview.mjs +52 -0
  8. package/bin/lib/artifactReporter.mjs +163 -0
  9. package/bin/lib/artifacts.mjs +47 -227
  10. package/bin/lib/authReject.mjs +16 -4
  11. package/bin/lib/authproxy.mjs +47 -304
  12. package/bin/lib/boxIdentity.mjs +245 -0
  13. package/bin/lib/childEnv.mjs +11 -9
  14. package/bin/lib/claude.mjs +14 -851
  15. package/bin/lib/claudeAuth.mjs +3 -3
  16. package/bin/lib/claudePosture.mjs +367 -0
  17. package/bin/lib/claudeSessions.mjs +411 -0
  18. package/bin/lib/claudeStream.mjs +207 -0
  19. package/bin/lib/cliLimit.mjs +34 -0
  20. package/bin/lib/commandSpecs.mjs +173 -0
  21. package/bin/lib/config.mjs +11 -42
  22. package/bin/lib/credentialDate.mjs +22 -0
  23. package/bin/lib/credentials.mjs +21 -26
  24. package/bin/lib/daemonLogging.mjs +55 -0
  25. package/bin/lib/daemonState.mjs +64 -0
  26. package/bin/lib/deploy.mjs +148 -647
  27. package/bin/lib/deployCheckout.mjs +265 -0
  28. package/bin/lib/deployConfig.mjs +142 -0
  29. package/bin/lib/deployRunner.mjs +311 -0
  30. package/bin/lib/deployWire.mjs +41 -0
  31. package/bin/lib/desktopContract.mjs +56 -105
  32. package/bin/lib/env.mjs +49 -484
  33. package/bin/lib/fleet.mjs +183 -2383
  34. package/bin/lib/fleetJobs.mjs +120 -0
  35. package/bin/lib/fleetPost.mjs +40 -0
  36. package/bin/lib/fleetReports.mjs +392 -0
  37. package/bin/lib/fleetRoster.mjs +470 -0
  38. package/bin/lib/fleetStartup.mjs +196 -0
  39. package/bin/lib/fleetWake.mjs +45 -0
  40. package/bin/lib/fleetWire.mjs +46 -0
  41. package/bin/lib/git.mjs +2 -2
  42. package/bin/lib/gitMergeIdentity.mjs +51 -0
  43. package/bin/lib/grant.mjs +5 -3
  44. package/bin/lib/help.mjs +3 -125
  45. package/bin/lib/holder.mjs +482 -0
  46. package/bin/lib/instance.mjs +124 -46
  47. package/bin/lib/knowledge.mjs +16 -799
  48. package/bin/lib/knowledgeFetch.mjs +38 -0
  49. package/bin/lib/knowledgeFileSync.mjs +112 -0
  50. package/bin/lib/knowledgeFiles.mjs +487 -0
  51. package/bin/lib/knowledgeLibrary.mjs +249 -0
  52. package/bin/lib/landed.mjs +2 -1
  53. package/bin/lib/listeners.mjs +99 -141
  54. package/bin/lib/localSessions.mjs +30 -555
  55. package/bin/lib/login.mjs +3 -2
  56. package/bin/lib/loginPath.mjs +3 -5
  57. package/bin/lib/machineDisconnect.mjs +203 -0
  58. package/bin/lib/machineEnv.mjs +22 -0
  59. package/bin/lib/machineLimits.mjs +61 -0
  60. package/bin/lib/machineListing.mjs +338 -0
  61. package/bin/lib/machines.mjs +78 -455
  62. package/bin/lib/machinesCommand.mjs +214 -0
  63. package/bin/lib/mcp-cli.mjs +2 -1
  64. package/bin/lib/patchRevert.mjs +60 -0
  65. package/bin/lib/prWorkflow.mjs +157 -0
  66. package/bin/lib/preflight.mjs +2 -1
  67. package/bin/lib/pressurePolicy.mjs +104 -0
  68. package/bin/lib/preview.mjs +2 -272
  69. package/bin/lib/previewFramePolicy.mjs +112 -0
  70. package/bin/lib/previewGatePolicy.mjs +236 -0
  71. package/bin/lib/previewRegistry.mjs +235 -0
  72. package/bin/lib/printable.mjs +2 -2
  73. package/bin/lib/procRegistry.mjs +28 -18
  74. package/bin/lib/procScan.mjs +146 -0
  75. package/bin/lib/processes.mjs +29 -37
  76. package/bin/lib/projectToolRuntimes.mjs +138 -0
  77. package/bin/lib/projectTools.mjs +13 -88
  78. package/bin/lib/prompts.mjs +28 -42
  79. package/bin/lib/repoPath.mjs +26 -0
  80. package/bin/lib/resources.mjs +7 -69
  81. package/bin/lib/resumeLost.mjs +107 -0
  82. package/bin/lib/runTurn.mjs +324 -0
  83. package/bin/lib/runtimeAntigravity.mjs +230 -0
  84. package/bin/lib/runtimeCapabilities.mjs +334 -0
  85. package/bin/lib/runtimeClaude.mjs +126 -0
  86. package/bin/lib/runtimeCodex.mjs +217 -0
  87. package/bin/lib/runtimeDetection.mjs +121 -0
  88. package/bin/lib/runtimeEvents.mjs +482 -0
  89. package/bin/lib/runtimeOfferings.mjs +4 -1
  90. package/bin/lib/runtimes.mjs +42 -1402
  91. package/bin/lib/safeFileName.mjs +74 -0
  92. package/bin/lib/standDownExit.mjs +181 -0
  93. package/bin/lib/standDownGate.mjs +48 -0
  94. package/bin/lib/startProject.mjs +279 -0
  95. package/bin/lib/trace.mjs +3 -3
  96. package/bin/lib/turnLock.mjs +68 -0
  97. package/bin/lib/turnProfile.mjs +77 -0
  98. package/bin/lib/uninstall.mjs +55 -45
  99. package/bin/lib/uninstallCopies.mjs +112 -0
  100. package/bin/lib/update.mjs +30 -17
  101. package/bin/lib/uplinkScrub.mjs +265 -0
  102. package/bin/lib/vault.mjs +22 -151
  103. package/bin/lib/vaultDiff.mjs +183 -0
  104. package/bin/lib/versionOrder.mjs +42 -0
  105. package/bin/lib/views.mjs +17 -17
  106. package/bin/lib/wikiRunner.mjs +630 -0
  107. package/bin/lib/work.mjs +188 -3034
  108. package/bin/lib/workAdoptCarry.mjs +94 -0
  109. package/bin/lib/workAgentCheck.mjs +278 -0
  110. package/bin/lib/workAgentMerges.mjs +54 -142
  111. package/bin/lib/workAgentPlans.mjs +14 -12
  112. package/bin/lib/workAgentPrecheck.mjs +393 -0
  113. package/bin/lib/workAgentPublish.mjs +177 -0
  114. package/bin/lib/workAgentReview.mjs +8 -666
  115. package/bin/lib/workAgentTurnBegun.mjs +109 -0
  116. package/bin/lib/workAgentTurnExecution.mjs +604 -0
  117. package/bin/lib/workAgentTurnOutcome.mjs +207 -0
  118. package/bin/lib/workAgentTurnReports.mjs +189 -0
  119. package/bin/lib/workAgentTurns.mjs +91 -898
  120. package/bin/lib/workAttachments.mjs +93 -0
  121. package/bin/lib/workBrain.mjs +53 -0
  122. package/bin/lib/workChildren.mjs +110 -0
  123. package/bin/lib/workCommandAudit.mjs +62 -0
  124. package/bin/lib/workDiffs.mjs +2 -1
  125. package/bin/lib/workNarration.mjs +152 -0
  126. package/bin/lib/workPlaceReports.mjs +426 -0
  127. package/bin/lib/workPlaces.mjs +228 -0
  128. package/bin/lib/workPreviews.mjs +31 -9
  129. package/bin/lib/workProcesses.mjs +4 -5
  130. package/bin/lib/workPullRequests.mjs +52 -126
  131. package/bin/lib/workReportQueue.mjs +254 -0
  132. package/bin/lib/workRetire.mjs +185 -0
  133. package/bin/lib/workSessionGroups.mjs +166 -0
  134. package/bin/lib/workSessionRuntime.mjs +164 -0
  135. package/bin/lib/workSessionTokens.mjs +68 -0
  136. package/bin/lib/workSessionTurns.mjs +892 -0
  137. package/bin/lib/workShip.mjs +9 -29
  138. package/bin/lib/workToolLog.mjs +76 -0
  139. package/package.json +3 -2
  140. package/bin/lib/patch.mjs +0 -363
@@ -0,0 +1,245 @@
1
+ /**
2
+ * THE BOX KEYPAIR — this box's durable identity, and the one thing in this file.
3
+ *
4
+ * A persistent X25519 keypair at `~/.flowviant/env-keypair.json` (0600). Its
5
+ * public half rides EVERY roster poll as `envpub`, and that is what tells two
6
+ * computers apart when the server decides which of them is this project's
7
+ * machine (holdership, 2026-09-14) and which rows belong to which box in the
8
+ * registry (`machine_box`, 0.91.0). It is an identity LABEL for arbitration,
9
+ * never an authorization: nothing is GRANTED by it — the credential grants, the
10
+ * pubkey only disambiguates boxes.
11
+ *
12
+ * SPLIT OUT OF `env.mjs` (2026-09-26, the SOLID pass). That module held three
13
+ * things with three unrelated reasons to change — this identity, the checkout's
14
+ * env comparison scan (still `env.mjs`), and the uplink scrubber
15
+ * (`uplinkScrub.mjs`) — and they shared a file only because all three were
16
+ * what was left standing when the secrets vault they once served was deleted
17
+ * (2026-09-21; `env.mjs` keeps that obituary). Nothing here reads a `.env`,
18
+ * and nothing there touches this file. The `env-` in the file name is a fossil
19
+ * of the vault and stays, because the PATH is the identity: see below.
20
+ *
21
+ * WHAT MUST NOT CHANGE: the keypair file, its path, and its 2026-09-14
22
+ * correctness rule (only ENOENT mints; every other read failure RETHROWS).
23
+ * Rotating this box's identity would make the same physical machine arrive at
24
+ * the roster as a stranger — a new row in the registry, and a standby of
25
+ * itself for the whole holder-claim window.
26
+ */
27
+
28
+ import {
29
+ readFileSync,
30
+ mkdirSync,
31
+ openSync,
32
+ writeSync,
33
+ fsyncSync,
34
+ closeSync,
35
+ linkSync,
36
+ unlinkSync,
37
+ } from 'node:fs';
38
+ import { randomBytes } from 'node:crypto';
39
+ import { homedir } from 'node:os';
40
+ import { join, dirname } from 'node:path';
41
+ // STILL SUMO, and deliberately not narrowed to the standard build in the same
42
+ // pass that deleted the vault. `crypto_box_keypair` exists in both, but the
43
+ // sumo package is what is installed, what every other daemon on npm resolves,
44
+ // and swapping the dependency is a separate change with its own install-time
45
+ // failure modes. Nothing here needs Argon2 any more; nothing here forbids the
46
+ // narrowing either.
47
+ import sodium from 'libsodium-wrappers-sumo';
48
+
49
+ const B64 = () => sodium.base64_variants.ORIGINAL;
50
+ const KEYPAIR_PATH = join(homedir(), '.flowviant', 'env-keypair.json');
51
+
52
+ let keypair = null; // { publicKey: Uint8Array, privateKey: Uint8Array }
53
+
54
+ /*
55
+ * `sodiumReady` IS DELETED (2026-09-26, the SOLID pass). It moved here out of
56
+ * `env.mjs` with no caller anywhere in the daemon: every door below awaits
57
+ * `sodium.ready` itself, so a separate warm-up export was a door nobody used.
58
+ */
59
+
60
+ /*
61
+ * `pubkeyEmoji` IS DELETED (2026-09-20), and so is its byte-identical twin in
62
+ * the web app.
63
+ *
64
+ * It existed for exactly one gesture: the terminal printed eight glyphs, the
65
+ * browser's approve card printed the same eight, and a human compared them
66
+ * before pressing Approve. Registering became enrolling, and then the whole
67
+ * enrolment went with the vault — so there is no approve card, no comparison,
68
+ * and nobody to make it. A fingerprint kept byte-identical across two repos
69
+ * for nobody to look at is worse than none: it reads like a live MITM defence
70
+ * and defends nothing.
71
+ */
72
+
73
+ /** This machine's persistent keypair (created on first use, 0600). */
74
+ export async function ensureKeypair() {
75
+ await sodium.ready;
76
+ if (keypair) return keypair;
77
+ let raw = null;
78
+ try {
79
+ raw = readFileSync(KEYPAIR_PATH, 'utf8');
80
+ } catch (e) {
81
+ /**
82
+ * ONLY "THERE IS NO FILE" IS A FIRST RUN, and the bare catch that used to
83
+ * stand here said every failure was one.
84
+ *
85
+ * This keypair is the box's DURABLE IDENTITY — since 2026-09-14 it is what
86
+ * tells two computers apart when the server decides which of them is this
87
+ * project's machine, and since 0.91.0 it is the key of this box's row in
88
+ * the machine registry. Regenerating it on a truncated file or an
89
+ * unreadable one OVERWRITES that identity: the box arrives at the roster as
90
+ * a stranger and stands itself down as a standby of itself.
91
+ *
92
+ * (It was ALSO what the project's secret vault was sealed to, until the
93
+ * vault was deleted 2026-09-21. That was the loudest consequence and it is
94
+ * gone; the identity consequence is the one that survives, and it is on its
95
+ * own sufficient.)
96
+ *
97
+ * A file we cannot read is not a file we may replace. Rethrown, the caller
98
+ * that can survive it does: `envQueryParams` is wrapped, so the poll simply
99
+ * carries no `envpub`, and a poll with no envpub is EXEMPT from arbitration
100
+ * — the documented fail-open arm, reached honestly instead of by minting a
101
+ * new box every restart.
102
+ */
103
+ if (e?.code !== 'ENOENT') throw e;
104
+ }
105
+ if (raw !== null) {
106
+ const stored = JSON.parse(raw);
107
+ keypair = {
108
+ publicKey: sodium.from_base64(stored.pub, B64()),
109
+ privateKey: sodium.from_base64(stored.priv, B64()),
110
+ };
111
+ return keypair;
112
+ }
113
+ const minted = sodium.crypto_box_keypair();
114
+ const winner = mintKeypairFile(
115
+ KEYPAIR_PATH,
116
+ JSON.stringify({
117
+ pub: sodium.to_base64(minted.publicKey, B64()),
118
+ priv: sodium.to_base64(minted.privateKey, B64()),
119
+ })
120
+ );
121
+ keypair = {
122
+ publicKey: sodium.from_base64(winner.pub, B64()),
123
+ privateKey: sodium.from_base64(winner.priv, B64()),
124
+ };
125
+ return keypair;
126
+ }
127
+
128
+ /**
129
+ * WRITE THE BOX'S IDENTITY ONCE, ATOMICALLY, AND LET THE FIRST WRITER WIN
130
+ * (2026-09-24, the audit). Returns the `{ pub, priv }` that is ON DISK after
131
+ * the call — ours, or the one a concurrent first start got there with.
132
+ *
133
+ * It was a plain `writeFileSync` reached by every process that saw ENOENT, so
134
+ * two daemons starting together on a fresh box (two projects, two units at
135
+ * boot) each minted a key and the LAST writer won: the first kept a key in
136
+ * memory the disk no longer held and arrived, after its next re-exec, as a
137
+ * different box. And a crash inside the write left a torn file that every later
138
+ * start refuses to read — the only-ENOENT-mints rule doing its job over a file
139
+ * nothing can fix.
140
+ *
141
+ * So the key goes to a private temp file, is fsync'd, and is LINKED into place:
142
+ * `link` fails with EEXIST instead of replacing, so exactly one identity is ever
143
+ * published and every loser reads the winner's. The file at the real path is
144
+ * either absent or complete — never half of one.
145
+ */
146
+ export function mintKeypairFile(path, body) {
147
+ mkdirSync(dirname(path), { recursive: true });
148
+ const tmp = `${path}.${process.pid}.${randomBytes(6).toString('hex')}.tmp`;
149
+ try {
150
+ const fd = openSync(tmp, 'wx', 0o600);
151
+ try {
152
+ writeSync(fd, body);
153
+ fsyncSync(fd);
154
+ } finally {
155
+ closeSync(fd);
156
+ }
157
+ try {
158
+ linkSync(tmp, path);
159
+ return JSON.parse(body);
160
+ } catch (e) {
161
+ if (e?.code === 'EEXIST') {
162
+ // Somebody else published first. Theirs is the identity; ours is dropped.
163
+ return JSON.parse(readFileSync(path, 'utf8'));
164
+ }
165
+ // A filesystem with no hard links (some network and FUSE mounts answer
166
+ // EPERM/ENOTSUP) must not cost the box its identity — the plain write
167
+ // this replaced worked there. Fall back to an EXCLUSIVE create: still
168
+ // first-writer-wins, only without the torn-write guarantee.
169
+ if (!['EPERM', 'ENOTSUP', 'EOPNOTSUPP', 'ENOSYS', 'EXDEV'].includes(e?.code)) throw e;
170
+ let fd2;
171
+ try {
172
+ fd2 = openSync(path, 'wx', 0o600);
173
+ } catch (e2) {
174
+ if (e2?.code !== 'EEXIST') throw e2;
175
+ return JSON.parse(readFileSync(path, 'utf8'));
176
+ }
177
+ try {
178
+ writeSync(fd2, body);
179
+ fsyncSync(fd2);
180
+ } finally {
181
+ closeSync(fd2);
182
+ }
183
+ return JSON.parse(body);
184
+ }
185
+ } finally {
186
+ try {
187
+ unlinkSync(tmp);
188
+ } catch {
189
+ /* already gone */
190
+ }
191
+ }
192
+ }
193
+
194
+ export function myPubB64() {
195
+ return keypair ? sodium.to_base64(keypair.publicKey, B64()) : null;
196
+ }
197
+
198
+ /**
199
+ * THIS BOX'S PUBLIC KEY, READ AND NEVER WRITTEN (2026-09-19, the review).
200
+ *
201
+ * `flowviant machines` is a VIEW-ONLY command — that narrowness is the whole
202
+ * reason a third terminal command was allowed to exist at all — and it was
203
+ * calling `ensureKeypair`, which on a box with no keypair CREATES ONE: a
204
+ * filesystem write, 0600, minting this machine's durable identity, from a
205
+ * command whose entire job is to print a list. Listing must not enrol.
206
+ *
207
+ * It reads the stored file and nothing else. No sodium, no parse of the private
208
+ * half, no creation. NULL for every failure — absent, unreadable, malformed —
209
+ * and null simply means no row gets the "← this box" mark, which is honest: a
210
+ * box that has never run a daemon has never polled, so it is not in the listing
211
+ * to be marked. Claiming a row is you on a guess would be worse than the mark
212
+ * being absent.
213
+ */
214
+ export function readStoredPubB64() {
215
+ try {
216
+ const stored = JSON.parse(readFileSync(KEYPAIR_PATH, 'utf8'));
217
+ return typeof stored?.pub === 'string' && stored.pub ? stored.pub : null;
218
+ } catch {
219
+ return null;
220
+ }
221
+ }
222
+
223
+ /**
224
+ * THE IDENTITY PARAMS THE ROSTER POLL CARRIES — one key, and it is the box.
225
+ *
226
+ * It used to carry two more: `envv` (the materialized bundle version, which fed
227
+ * the Settings "env vN" chip) and `envskip` (the target files the materializer
228
+ * REFUSED to write, with the empty string as a real "measured, refused nothing"
229
+ * report). Both were facts about the vault and both died with it; the server
230
+ * stopped reading them in the same change.
231
+ *
232
+ * WHAT DID NOT CHANGE IS `envpub`: the same base64 of the same public key out
233
+ * of the same file. Holdership arbitration, the displaced signal and the box
234
+ * registry all key on it, so a box that upgrades to this release must be the
235
+ * SAME box to the server it was before — no new row, no re-claim, no standby
236
+ * of itself. That continuity is the point of this function still existing at
237
+ * all.
238
+ *
239
+ * Absence keeps its reserved meaning: a poll with no `envpub` is EXEMPT from
240
+ * arbitration rather than treated as an unknown box.
241
+ */
242
+ export async function envQueryParams() {
243
+ await ensureKeypair();
244
+ return { envpub: myPubB64() };
245
+ }
@@ -27,6 +27,7 @@
27
27
  * confinement is a separate uid or a namespace and is not in this product. No
28
28
  * surface may describe anything here as "sandboxed" or "isolated".
29
29
  */
30
+ import { MACHINE_CREDENTIAL_ENV } from './machineEnv.mjs';
30
31
 
31
32
  /**
32
33
  * The complete kept set. Two groups, and both are here for a reason that bit
@@ -160,8 +161,7 @@ export function childEnv({ cwd, extra, deploy = false } = {}) {
160
161
  * purpose: the default is what almost every caller gets, and the sample is
161
162
  * about the default. */
162
163
  export const DROPPED_SAMPLE = [
163
- 'FLOWVIANT_MACHINE_TOKEN',
164
- 'FLOWVIANT_FLEET',
164
+ ...MACHINE_CREDENTIAL_ENV,
165
165
  'FLOWVIANT_FLEET_URL',
166
166
  'FLOWVIANT_API_URL',
167
167
  'FLOWVIANT_MCP_URL',
@@ -192,16 +192,16 @@ export const DEPLOY_KEEP_NAMES = [...DEPLOY_KEEP];
192
192
  * deploy credential is almost never in one: it is exported in the shell they
193
193
  * started the daemon in, which is exactly why `DEPLOY_KEEP` above exists. So
194
194
  * the one lane that PASSES a secret into a command it then streams to the
195
- * server had stopped redacting it. `deploy.mjs`'s own header was meanwhile
196
- * still asserting the opposite — "strictly more of what a deploy log can
195
+ * server had stopped redacting it. `deploy.mjs`'s own header (that argument
196
+ * now heads deployRunner.mjs) was meanwhile still asserting the opposite — "strictly more of what a deploy log can
197
197
  * contain than the vault ever delivered" — which is the defended-local-decision
198
198
  * shape: a confident comment over a guarantee that had quietly lapsed.
199
199
  *
200
- * WHY IT LIVES HERE AND NOT IN `env.mjs`. This file is the single definition of
200
+ * WHY IT LIVES HERE AND NOT IN `uplinkScrub.mjs`. This file is the single definition of
201
201
  * what a spawned command may be handed; the redaction list must be the same set
202
202
  * or it rots the moment somebody adds a name to `DEPLOY_KEEP` and forgets the
203
203
  * other half. Deriving it FROM that array is what makes forgetting impossible,
204
- * and it is why the merge happens inside `scanEnvForScrub` rather than at a
204
+ * and it is why the merge happens inside `learnSecrets` rather than at a
205
205
  * call site somebody has to remember. The dependency direction is the only one
206
206
  * that is acyclic besides: this module imports nothing at all.
207
207
  *
@@ -237,7 +237,7 @@ export const DEPLOY_KEEP_NAMES = [...DEPLOY_KEEP];
237
237
  * nothing (the `CF_*` argument above), so they join the REDACTION list only;
238
238
  * `DEPLOY_KEEP` is untouched and `childEnv` still hands none of them to anybody.
239
239
  *
240
- * The machine credential is ALSO caught by shape in `env.mjs` (`fva_…`),
240
+ * The machine credential is ALSO caught by shape in `uplinkScrub.mjs` (`fva_…`),
241
241
  * because the copy that matters most is not in any environment variable at all:
242
242
  * it is every project's `fleetToken` in `~/.flowviant/credentials.json`, plus
243
243
  * the per-turn work/capture tokens the server mints. The names here are belt.
@@ -245,8 +245,10 @@ export const DEPLOY_KEEP_NAMES = [...DEPLOY_KEEP];
245
245
  const REDACT_ONLY = [
246
246
  'CF_API_TOKEN',
247
247
  'CF_ACCOUNT_ID',
248
- 'FLOWVIANT_MACHINE_TOKEN',
249
- 'FLOWVIANT_FLEET',
248
+ // Every machine credential name, from the one list children are stripped
249
+ // of (machineEnv.mjs): a name removed from children is redacted by
250
+ // construction, never by a second hand-kept copy.
251
+ ...MACHINE_CREDENTIAL_ENV,
250
252
  'ANTHROPIC_API_KEY',
251
253
  'ANTHROPIC_AUTH_TOKEN',
252
254
  'CLAUDE_CODE_OAUTH_TOKEN',