@getmikie/cli 1.1.103 → 1.1.104

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.
package/bin/mikie.mjs CHANGED
@@ -9,7 +9,12 @@ import { fileURLToPath } from "node:url";
9
9
  import {
10
10
  launchNativeCaptured,
11
11
  launchNative,
12
+ abandonMaterializedRuntime,
13
+ materializeWindowsRuntime,
14
+ materializePortableRuntime,
12
15
  prepareNativeLaunch,
16
+ retireHistoricalWindowsRuntimes,
17
+ retireHistoricalRuntimes,
13
18
  resolveDependencyPackageJson,
14
19
  resolveNativeBinary,
15
20
  selectPlatformPackage,
@@ -26,6 +31,10 @@ import {
26
31
  parseTrajectoryReconciliationReceipt,
27
32
  runUpdateTransaction,
28
33
  } from "../lib/update.mjs";
34
+ import {
35
+ installedClientRuntimeDigests,
36
+ reconcileInstalledClients,
37
+ } from "../scripts/reconcile-install.mjs";
29
38
 
30
39
  const require = createRequire(import.meta.url);
31
40
  const packageMetadata = require("../package.json");
@@ -143,7 +152,31 @@ All commands execute the compiled Mikie native engine.`);
143
152
  ownership: path.join(customerHome, ".claude", "mikie-evidence-hook-ownership.json"),
144
153
  },
145
154
  transactionRoot,
146
- settleProviderRepair: async (phase, snapshot) => {
155
+ prepareFinalLauncher: (candidate, version) =>
156
+ process.platform === "win32"
157
+ ? materializeWindowsRuntime(candidate.binary, {
158
+ version,
159
+ cleanupHistorical: false,
160
+ })
161
+ : materializePortableRuntime(candidate.binary, {
162
+ version,
163
+ cleanupHistorical: false,
164
+ }),
165
+ retireFinalLauncher: async ({ path: launcher, sha256 }) => {
166
+ const preserveSha256 = await installedClientRuntimeDigests();
167
+ return process.platform === "win32"
168
+ ? retireHistoricalWindowsRuntimes(launcher, {
169
+ expectedSha256: sha256,
170
+ preserveSha256,
171
+ })
172
+ : retireHistoricalRuntimes(launcher, {
173
+ expectedSha256: sha256,
174
+ preserveSha256,
175
+ });
176
+ },
177
+ abandonFinalLauncher: ({ path: launcher, sha256 }) =>
178
+ abandonMaterializedRuntime(launcher, { expectedSha256: sha256 }),
179
+ settleProviderRepair: async (phase, snapshot, launcherState) => {
147
180
  const settlementBinary = snapshot?.preparedCandidate?.binary;
148
181
  const finalLauncher = snapshot?.preparedCandidate?.finalLauncher;
149
182
  if (!settlementBinary || !finalLauncher) {
@@ -156,6 +189,8 @@ All commands execute the compiled Mikie native engine.`);
156
189
  MIKIE_DISTRIBUTION_MODE: "npm",
157
190
  MIKIE_NPM_UPDATE_TOPOLOGY: UpdateTopology.NpmGlobal,
158
191
  MIKIE_NPM_UPDATE_REPAIR_PHASE: phase,
192
+ MIKIE_UPDATE_REPAIR_LAUNCHER_STATE:
193
+ launcherState ?? (phase === "commit" ? "activated" : "prior"),
159
194
  MIKIE_UPDATE_REPAIR_CANDIDATE: settlementBinary,
160
195
  MIKIE_UPDATE_REPAIR_LAUNCHER: finalLauncher,
161
196
  },
@@ -191,6 +226,8 @@ All commands execute the compiled Mikie native engine.`);
191
226
  for (const setting of result.foreignSettings) {
192
227
  console.log(`Preserved foreign setting: ${setting}`);
193
228
  }
229
+ } else if (result.status === "reconciled") {
230
+ console.log(`Reconciled Mikie ${result.version} client launchers.`);
194
231
  }
195
232
  } catch (error) {
196
233
  const message = error instanceof Error ? error.message : "Mikie update failed";
@@ -199,6 +236,7 @@ All commands execute the compiled Mikie native engine.`);
199
236
  }
200
237
  } else {
201
238
  try {
239
+ await reconcileInstalledClients({ version: packageMetadata.version });
202
240
  const nativeArguments = translateArguments(arguments_);
203
241
  const binary = await prepareNativeLaunch(await resolveNativeBinary(), nativeArguments, {
204
242
  version: packageMetadata.version,
package/lib/launcher.mjs CHANGED
@@ -1,7 +1,19 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { createHash, randomUUID } from "node:crypto";
3
3
  import { constants as fsConstants, createReadStream } from "node:fs";
4
- import { access, copyFile, lstat, mkdir, readdir, rename, rm, stat } from "node:fs/promises";
4
+ import {
5
+ access,
6
+ copyFile,
7
+ lstat,
8
+ mkdir,
9
+ open,
10
+ readFile,
11
+ readdir,
12
+ rename,
13
+ rm,
14
+ rmdir,
15
+ stat,
16
+ } from "node:fs/promises";
5
17
  import path from "node:path";
6
18
  import { spawn } from "node:child_process";
7
19
 
@@ -22,6 +34,10 @@ const MAX_CAPTURED_OUTPUT_BYTES = 1024 * 1024;
22
34
  const SAFE_RUNTIME_VERSION = /^[0-9A-Za-z][0-9A-Za-z._-]{0,127}$/;
23
35
  const SAFE_RUNTIME_DIRECTORY = /^[0-9A-Za-z][0-9A-Za-z._-]{0,127}-[0-9a-f]{64}$/;
24
36
  const HISTORICAL_RUNTIME_GRACE_MS = 24 * 60 * 60 * 1000;
37
+ const MAX_RETAINED_WINDOWS_RUNTIMES = 8;
38
+ const MAX_MATERIALIZED_WINDOWS_RUNTIMES = MAX_RETAINED_WINDOWS_RUNTIMES + 1;
39
+ const ABANDONED_RUNTIME_SCHEMA = "mikie.windows-runtime-abandonment.v1";
40
+ const SAFE_ABANDONMENT_MARKER = /^abandon-[0-9a-f]{64}\.json$/;
25
41
 
26
42
  async function sha256File(file) {
27
43
  const digest = createHash("sha256");
@@ -47,20 +63,33 @@ async function verifyMaterializedRuntime(file, expectedSha256, { allowMissing =
47
63
  }
48
64
  }
49
65
 
50
- async function cleanupHistoricalWindowsRuntimes(runtimeRoot, selectedDirectory) {
66
+ async function cleanupHistoricalWindowsRuntimes(
67
+ runtimeRoot,
68
+ selectedDirectory,
69
+ { graceMs = HISTORICAL_RUNTIME_GRACE_MS, preserveSha256 = [] } = {},
70
+ ) {
71
+ if (
72
+ !Array.isArray(preserveSha256) ||
73
+ preserveSha256.length > MAX_MATERIALIZED_WINDOWS_RUNTIMES ||
74
+ preserveSha256.some((digest) => !/^[0-9a-f]{64}$/.test(digest))
75
+ ) {
76
+ throw new Error("Mikie cached runtime preservation authority is invalid");
77
+ }
78
+ const preserved = new Set(preserveSha256);
51
79
  let entries;
52
80
  try {
53
81
  entries = await readdir(runtimeRoot, { withFileTypes: true });
54
82
  } catch {
55
83
  return;
56
84
  }
57
- const cutoff = Date.now() - HISTORICAL_RUNTIME_GRACE_MS;
85
+ const cutoff = Date.now() - graceMs;
58
86
  await Promise.all(
59
87
  entries.map(async (entry) => {
60
88
  if (
61
89
  !entry.isDirectory() ||
62
90
  entry.name === selectedDirectory ||
63
- !SAFE_RUNTIME_DIRECTORY.test(entry.name)
91
+ !SAFE_RUNTIME_DIRECTORY.test(entry.name) ||
92
+ [...preserved].some((digest) => entry.name.endsWith(`-${digest}`))
64
93
  ) {
65
94
  return;
66
95
  }
@@ -75,35 +104,344 @@ async function cleanupHistoricalWindowsRuntimes(runtimeRoot, selectedDirectory)
75
104
  );
76
105
  }
77
106
 
107
+ function abandonmentMarkerForExecutable(
108
+ runtimeRoot,
109
+ selectedDirectory,
110
+ expectedSha256,
111
+ executable,
112
+ ) {
113
+ const value = {
114
+ schema_version: ABANDONED_RUNTIME_SCHEMA,
115
+ directory: selectedDirectory,
116
+ executable,
117
+ sha256: expectedSha256,
118
+ };
119
+ const bytes = Buffer.from(`${JSON.stringify(value)}\n`);
120
+ const name = `abandon-${createHash("sha256").update(bytes).digest("hex")}.json`;
121
+ return { path: path.join(runtimeRoot, name), value, bytes };
122
+ }
123
+
124
+ async function removeAbandonedRuntime(runtimeRoot, markerPath, value) {
125
+ const runtimeDirectory = path.join(runtimeRoot, value.directory);
126
+ const binary = path.join(runtimeDirectory, value.executable);
127
+ let directoryMetadata;
128
+ try {
129
+ directoryMetadata = await lstat(runtimeDirectory);
130
+ } catch (error) {
131
+ if (error?.code === "ENOENT") {
132
+ await rm(markerPath, { force: true });
133
+ return true;
134
+ }
135
+ throw error;
136
+ }
137
+ if (!directoryMetadata.isDirectory() || directoryMetadata.isSymbolicLink()) {
138
+ throw new Error("Mikie abandoned Windows runtime authority is invalid");
139
+ }
140
+ const entries = await readdir(runtimeDirectory, { withFileTypes: true });
141
+ if (entries.length === 0) {
142
+ try {
143
+ await rmdir(runtimeDirectory);
144
+ await rm(markerPath, { force: true });
145
+ return true;
146
+ } catch (error) {
147
+ if (["EACCES", "EBUSY", "EPERM", "ENOTEMPTY"].includes(error?.code)) return false;
148
+ throw error;
149
+ }
150
+ }
151
+ if (
152
+ entries.length !== 1 ||
153
+ entries[0].name !== value.executable ||
154
+ !entries[0].isFile() ||
155
+ entries[0].isSymbolicLink()
156
+ ) {
157
+ throw new Error("Mikie abandoned Windows runtime contents changed");
158
+ }
159
+ await verifyMaterializedRuntime(binary, value.sha256);
160
+ try {
161
+ await rm(binary, { force: false, maxRetries: 0 });
162
+ await rmdir(runtimeDirectory);
163
+ await rm(markerPath, { force: true });
164
+ return true;
165
+ } catch (error) {
166
+ if (["EACCES", "EBUSY", "EPERM", "ENOTEMPTY"].includes(error?.code)) return false;
167
+ throw error;
168
+ }
169
+ }
170
+
171
+ async function persistAbandonmentMarker(marker) {
172
+ const temporary = `${marker.path}.${process.pid}.${randomUUID()}.tmp`;
173
+ let handle;
174
+ try {
175
+ handle = await open(temporary, "wx", 0o600);
176
+ await handle.writeFile(marker.bytes);
177
+ await handle.sync();
178
+ await handle.close();
179
+ handle = undefined;
180
+ try {
181
+ await rename(temporary, marker.path);
182
+ } catch (error) {
183
+ if (
184
+ !["EEXIST", "EPERM"].includes(error?.code) ||
185
+ !(await readFile(marker.path)).equals(marker.bytes)
186
+ ) {
187
+ throw error;
188
+ }
189
+ }
190
+ } finally {
191
+ await handle?.close().catch(() => {});
192
+ await rm(temporary, { force: true }).catch(() => {});
193
+ }
194
+ }
195
+
196
+ async function scavengeAbandonedWindowsRuntimes(runtimeRoot) {
197
+ let entries;
198
+ try {
199
+ entries = await readdir(runtimeRoot, { withFileTypes: true });
200
+ } catch (error) {
201
+ if (error?.code === "ENOENT") return;
202
+ throw error;
203
+ }
204
+ const markers = entries.filter(
205
+ (entry) => entry.isFile() && !entry.isSymbolicLink() && SAFE_ABANDONMENT_MARKER.test(entry.name),
206
+ );
207
+ if (markers.length > MAX_MATERIALIZED_WINDOWS_RUNTIMES) {
208
+ throw new Error("Mikie abandoned Windows runtime inventory is invalid");
209
+ }
210
+ for (const entry of markers) {
211
+ const markerPath = path.join(runtimeRoot, entry.name);
212
+ const metadata = await lstat(markerPath);
213
+ if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size > 1024) {
214
+ throw new Error("Mikie abandoned Windows runtime marker is invalid");
215
+ }
216
+ const bytes = await readFile(markerPath);
217
+ let value;
218
+ try {
219
+ value = JSON.parse(bytes.toString("utf8"));
220
+ } catch {
221
+ throw new Error("Mikie abandoned Windows runtime marker is invalid");
222
+ }
223
+ if (
224
+ value === null ||
225
+ typeof value !== "object" ||
226
+ Array.isArray(value) ||
227
+ Object.keys(value).sort().join(",") !== "directory,executable,schema_version,sha256" ||
228
+ value.schema_version !== ABANDONED_RUNTIME_SCHEMA ||
229
+ !SAFE_RUNTIME_DIRECTORY.test(value.directory ?? "") ||
230
+ !["mikie", "mikie.exe"].includes(value.executable) ||
231
+ !/^[0-9a-f]{64}$/.test(value.sha256 ?? "") ||
232
+ !value.directory.endsWith(`-${value.sha256}`)
233
+ ) {
234
+ throw new Error("Mikie abandoned Windows runtime marker is invalid");
235
+ }
236
+ const expected = abandonmentMarkerForExecutable(
237
+ runtimeRoot,
238
+ value.directory,
239
+ value.sha256,
240
+ value.executable,
241
+ );
242
+ if (entry.name !== path.basename(expected.path) || !bytes.equals(expected.bytes)) {
243
+ throw new Error("Mikie abandoned Windows runtime marker is invalid");
244
+ }
245
+ await removeAbandonedRuntime(runtimeRoot, markerPath, value);
246
+ }
247
+ }
248
+
249
+ async function enforceMaterializedRuntimeCapacity(runtimeRoot, selectedDirectory) {
250
+ await scavengeAbandonedWindowsRuntimes(runtimeRoot);
251
+ let entries;
252
+ try {
253
+ entries = await readdir(runtimeRoot, { withFileTypes: true });
254
+ } catch (error) {
255
+ if (error?.code === "ENOENT") return;
256
+ throw error;
257
+ }
258
+ const materialized = entries.filter(
259
+ (entry) => entry.isDirectory() && SAFE_RUNTIME_DIRECTORY.test(entry.name),
260
+ );
261
+ if (
262
+ materialized.some((entry) => entry.name === selectedDirectory) ||
263
+ materialized.length < MAX_MATERIALIZED_WINDOWS_RUNTIMES
264
+ ) {
265
+ return;
266
+ }
267
+ throw new Error(
268
+ "Mikie cached Windows runtime retention capacity is full; reconcile exact-owned clients before installing another runtime",
269
+ );
270
+ }
271
+
272
+ /** Retire one digest-pinned candidate after its update transaction fully rolled back. */
273
+ export async function abandonMaterializedWindowsRuntime(
274
+ selectedBinary,
275
+ { expectedSha256 } = {},
276
+ ) {
277
+ if (
278
+ !path.isAbsolute(selectedBinary ?? "") ||
279
+ !/^[a-f0-9]{64}$/.test(expectedSha256 ?? "") ||
280
+ path.basename(selectedBinary).toLowerCase() !== "mikie.exe"
281
+ ) {
282
+ throw new Error("Mikie abandoned Windows runtime authority is invalid");
283
+ }
284
+ const selectedDirectory = path.dirname(selectedBinary);
285
+ const runtimeRoot = path.dirname(selectedDirectory);
286
+ const selectedName = path.basename(selectedDirectory);
287
+ if (
288
+ path.basename(runtimeRoot).toLowerCase() !== "runtime-v1" ||
289
+ !SAFE_RUNTIME_DIRECTORY.test(selectedName) ||
290
+ !selectedName.endsWith(`-${expectedSha256}`)
291
+ ) {
292
+ throw new Error("Mikie abandoned Windows runtime authority is invalid");
293
+ }
294
+ for (const directory of [runtimeRoot, selectedDirectory]) {
295
+ const metadata = await lstat(directory);
296
+ if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
297
+ throw new Error("Mikie abandoned Windows runtime authority is invalid");
298
+ }
299
+ }
300
+ await verifyMaterializedRuntime(selectedBinary, expectedSha256);
301
+ const marker = abandonmentMarkerForExecutable(
302
+ runtimeRoot,
303
+ selectedName,
304
+ expectedSha256,
305
+ "mikie.exe",
306
+ );
307
+ await persistAbandonmentMarker(marker);
308
+ await removeAbandonedRuntime(runtimeRoot, marker.path, marker.value);
309
+ }
310
+
311
+ /** Retire one cross-platform content-addressed candidate after a proven rollback. */
312
+ export async function abandonMaterializedRuntime(selectedBinary, { expectedSha256 } = {}) {
313
+ if (path.basename(selectedBinary ?? "") === "mikie.exe") {
314
+ return abandonMaterializedWindowsRuntime(selectedBinary, { expectedSha256 });
315
+ }
316
+ if (
317
+ !path.isAbsolute(selectedBinary ?? "") ||
318
+ path.basename(selectedBinary) !== "mikie" ||
319
+ !/^[a-f0-9]{64}$/.test(expectedSha256 ?? "")
320
+ ) {
321
+ throw new Error("Mikie abandoned runtime authority is invalid");
322
+ }
323
+ const selectedDirectory = path.dirname(selectedBinary);
324
+ const runtimeRoot = path.dirname(selectedDirectory);
325
+ const selectedName = path.basename(selectedDirectory);
326
+ if (
327
+ path.basename(runtimeRoot) !== "runtime-v1" ||
328
+ !SAFE_RUNTIME_DIRECTORY.test(selectedName) ||
329
+ !selectedName.endsWith(`-${expectedSha256}`)
330
+ ) {
331
+ throw new Error("Mikie abandoned runtime authority is invalid");
332
+ }
333
+ await verifyMaterializedRuntime(selectedBinary, expectedSha256);
334
+ const marker = abandonmentMarkerForExecutable(
335
+ runtimeRoot,
336
+ selectedName,
337
+ expectedSha256,
338
+ "mikie",
339
+ );
340
+ await persistAbandonmentMarker(marker);
341
+ await removeAbandonedRuntime(runtimeRoot, marker.path, marker.value);
342
+ }
343
+
344
+ export async function retireHistoricalWindowsRuntimes(
345
+ selectedBinary,
346
+ { expectedSha256, preserveSha256 = [] } = {},
347
+ ) {
348
+ if (
349
+ !path.isAbsolute(selectedBinary ?? "") ||
350
+ !/^[a-f0-9]{64}$/.test(expectedSha256 ?? "") ||
351
+ path.basename(selectedBinary).toLowerCase() !== "mikie.exe"
352
+ ) {
353
+ throw new Error("Mikie cached Windows runtime retirement authority is invalid");
354
+ }
355
+ const selectedDirectory = path.dirname(selectedBinary);
356
+ const runtimeRoot = path.dirname(selectedDirectory);
357
+ const selectedName = path.basename(selectedDirectory);
358
+ if (
359
+ path.basename(runtimeRoot).toLowerCase() !== "runtime-v1" ||
360
+ !SAFE_RUNTIME_DIRECTORY.test(selectedName) ||
361
+ !selectedName.endsWith(`-${expectedSha256}`)
362
+ ) {
363
+ throw new Error("Mikie cached Windows runtime retirement authority is invalid");
364
+ }
365
+ for (const directory of [runtimeRoot, selectedDirectory]) {
366
+ const metadata = await lstat(directory);
367
+ if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
368
+ throw new Error("Mikie cached Windows runtime retirement authority is invalid");
369
+ }
370
+ }
371
+ await verifyMaterializedRuntime(selectedBinary, expectedSha256);
372
+ await cleanupHistoricalWindowsRuntimes(runtimeRoot, selectedName, {
373
+ graceMs: 0,
374
+ preserveSha256,
375
+ });
376
+ }
377
+
378
+ /** Retire content-addressed runtimes only after every owned client committed migration. */
379
+ export async function retireHistoricalRuntimes(
380
+ selectedBinary,
381
+ { expectedSha256, preserveSha256 = [] } = {},
382
+ ) {
383
+ if (path.basename(selectedBinary ?? "") === "mikie.exe") {
384
+ return retireHistoricalWindowsRuntimes(selectedBinary, {
385
+ expectedSha256,
386
+ preserveSha256,
387
+ });
388
+ }
389
+ if (
390
+ !path.isAbsolute(selectedBinary ?? "") ||
391
+ path.basename(selectedBinary) !== "mikie" ||
392
+ !/^[a-f0-9]{64}$/.test(expectedSha256 ?? "")
393
+ ) {
394
+ throw new Error("Mikie cached runtime retirement authority is invalid");
395
+ }
396
+ const selectedDirectory = path.dirname(selectedBinary);
397
+ const runtimeRoot = path.dirname(selectedDirectory);
398
+ const selectedName = path.basename(selectedDirectory);
399
+ if (
400
+ path.basename(runtimeRoot) !== "runtime-v1" ||
401
+ !SAFE_RUNTIME_DIRECTORY.test(selectedName) ||
402
+ !selectedName.endsWith(`-${expectedSha256}`)
403
+ ) {
404
+ throw new Error("Mikie cached runtime retirement authority is invalid");
405
+ }
406
+ await verifyMaterializedRuntime(selectedBinary, expectedSha256);
407
+ await cleanupHistoricalWindowsRuntimes(runtimeRoot, selectedName, {
408
+ graceMs: 0,
409
+ preserveSha256,
410
+ });
411
+ }
412
+
78
413
  export function shouldStageWindowsRuntime(arguments_) {
79
414
  if (arguments_.length === 0) return true;
80
- if (["mcp", "serve", "run-relay"].includes(arguments_[0])) return true;
415
+ if (["mcp", "serve", "run-relay", "doctor"].includes(arguments_[0])) return true;
416
+ if (arguments_.length === 1 && arguments_[0] === "uninstall") return true;
417
+ if (
418
+ arguments_.length === 2 &&
419
+ arguments_[0] === "setup" &&
420
+ ["codex", "claude", "claude_code"].includes(arguments_[1])
421
+ ) {
422
+ return true;
423
+ }
81
424
  return arguments_[0] === "control" && arguments_[1] === "run";
82
425
  }
83
426
 
84
- export async function materializeWindowsRuntime(
427
+ async function materializeRuntime(
85
428
  binary,
86
- { version, localAppData = process.env.LOCALAPPDATA } = {},
429
+ { version, runtimeRoot, executableName, cleanupHistorical },
87
430
  ) {
88
- if (
89
- !path.isAbsolute(binary) ||
90
- !path.isAbsolute(localAppData ?? "") ||
91
- !SAFE_RUNTIME_VERSION.test(version ?? "")
92
- ) {
93
- throw new Error("Mikie cached Windows runtime authority is invalid");
94
- }
95
431
  const sourceMetadata = await lstat(binary);
96
432
  if (!sourceMetadata.isFile() || sourceMetadata.isSymbolicLink() || sourceMetadata.size === 0) {
97
- throw new Error("Mikie native Windows runtime is invalid");
433
+ throw new Error("Mikie native runtime is invalid");
98
434
  }
99
435
  const sourceSha256 = await sha256File(binary);
100
- const runtimeRoot = path.join(localAppData, "Mikie", "runtime-v1");
101
436
  const selectedDirectory = `${version}-${sourceSha256}`;
102
437
  const runtimeDirectory = path.join(runtimeRoot, selectedDirectory);
103
- const materialized = path.join(runtimeDirectory, "mikie.exe");
438
+ const materialized = path.join(runtimeDirectory, executableName);
439
+ await enforceMaterializedRuntimeCapacity(runtimeRoot, selectedDirectory);
104
440
  await mkdir(runtimeDirectory, { recursive: true });
105
441
  if (await verifyMaterializedRuntime(materialized, sourceSha256, { allowMissing: true })) {
106
- await cleanupHistoricalWindowsRuntimes(runtimeRoot, selectedDirectory);
442
+ if (cleanupHistorical) {
443
+ await cleanupHistoricalWindowsRuntimes(runtimeRoot, selectedDirectory);
444
+ }
107
445
  return materialized;
108
446
  }
109
447
 
@@ -118,7 +456,7 @@ export async function materializeWindowsRuntime(
118
456
  sha256File(binary),
119
457
  ]);
120
458
  if (copiedSha256 !== sourceSha256 || stableSourceSha256 !== sourceSha256) {
121
- throw new Error("Mikie native Windows runtime changed during materialization");
459
+ throw new Error("Mikie native runtime changed during materialization");
122
460
  }
123
461
  try {
124
462
  await rename(temporary, materialized);
@@ -131,13 +469,110 @@ export async function materializeWindowsRuntime(
131
469
  }
132
470
  }
133
471
  await verifyMaterializedRuntime(materialized, sourceSha256);
134
- await cleanupHistoricalWindowsRuntimes(runtimeRoot, selectedDirectory);
472
+ if (cleanupHistorical) {
473
+ await cleanupHistoricalWindowsRuntimes(runtimeRoot, selectedDirectory);
474
+ }
135
475
  return materialized;
136
476
  } finally {
137
477
  await rm(temporary, { force: true }).catch(() => {});
138
478
  }
139
479
  }
140
480
 
481
+ export async function materializeWindowsRuntime(
482
+ binary,
483
+ {
484
+ version,
485
+ localAppData = process.env.LOCALAPPDATA,
486
+ cleanupHistorical = true,
487
+ } = {},
488
+ ) {
489
+ if (
490
+ !path.isAbsolute(binary) ||
491
+ !path.isAbsolute(localAppData ?? "") ||
492
+ !SAFE_RUNTIME_VERSION.test(version ?? "")
493
+ ) {
494
+ throw new Error("Mikie cached Windows runtime authority is invalid");
495
+ }
496
+ return materializeRuntime(binary, {
497
+ version,
498
+ runtimeRoot: path.join(localAppData, "Mikie", "runtime-v1"),
499
+ executableName: "mikie.exe",
500
+ cleanupHistorical,
501
+ });
502
+ }
503
+
504
+ function portableRuntimeRoot(platform, environment) {
505
+ if (platform === "win32") {
506
+ return path.join(environment.LOCALAPPDATA ?? "", "Mikie", "runtime-v1");
507
+ }
508
+ if (platform === "darwin") {
509
+ return path.join(
510
+ environment.HOME ?? "",
511
+ "Library",
512
+ "Application Support",
513
+ "Mikie",
514
+ "runtime-v1",
515
+ );
516
+ }
517
+ const state = environment.XDG_STATE_HOME || path.join(environment.HOME ?? "", ".local", "state");
518
+ return path.join(state, "mikie", "runtime-v1");
519
+ }
520
+
521
+ /** Derive the one immutable runtime path bound to package version and bytes. */
522
+ export function materializedRuntimePath({
523
+ version,
524
+ sha256,
525
+ platform = process.platform,
526
+ environment = process.env,
527
+ } = {}) {
528
+ const runtimeRoot = portableRuntimeRoot(platform, environment);
529
+ if (
530
+ !path.isAbsolute(runtimeRoot) ||
531
+ !SAFE_RUNTIME_VERSION.test(version ?? "") ||
532
+ !/^[0-9a-f]{64}$/.test(sha256 ?? "")
533
+ ) {
534
+ throw new Error("Mikie cached runtime authority is invalid");
535
+ }
536
+ return path.join(
537
+ runtimeRoot,
538
+ `${version}-${sha256}`,
539
+ platform === "win32" ? "mikie.exe" : "mikie",
540
+ );
541
+ }
542
+
543
+ /** Materialize an immutable runtime so npm package replacement never strands a client. */
544
+ export async function materializePortableRuntime(
545
+ binary,
546
+ {
547
+ version,
548
+ platform = process.platform,
549
+ environment = process.env,
550
+ cleanupHistorical = true,
551
+ } = {},
552
+ ) {
553
+ const runtimeRoot = portableRuntimeRoot(platform, environment);
554
+ if (
555
+ !path.isAbsolute(binary) ||
556
+ !path.isAbsolute(runtimeRoot) ||
557
+ !SAFE_RUNTIME_VERSION.test(version ?? "")
558
+ ) {
559
+ throw new Error("Mikie cached runtime authority is invalid");
560
+ }
561
+ if (platform === "win32") {
562
+ return materializeWindowsRuntime(binary, {
563
+ version,
564
+ localAppData: environment.LOCALAPPDATA,
565
+ cleanupHistorical,
566
+ });
567
+ }
568
+ return materializeRuntime(binary, {
569
+ version,
570
+ runtimeRoot,
571
+ executableName: "mikie",
572
+ cleanupHistorical,
573
+ });
574
+ }
575
+
141
576
  export async function prepareNativeLaunch(
142
577
  binary,
143
578
  arguments_,
@@ -147,12 +582,14 @@ export async function prepareNativeLaunch(
147
582
  environment = process.env,
148
583
  } = {},
149
584
  ) {
150
- if (platform !== "win32" || !shouldStageWindowsRuntime(arguments_)) {
585
+ if (!shouldStageWindowsRuntime(arguments_)) {
151
586
  return binary;
152
587
  }
153
- return materializeWindowsRuntime(binary, {
588
+ return materializePortableRuntime(binary, {
154
589
  version,
155
- localAppData: environment.LOCALAPPDATA,
590
+ platform,
591
+ environment,
592
+ cleanupHistorical: false,
156
593
  });
157
594
  }
158
595