@getmikie/cli 1.1.88 → 1.1.89

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,6 +9,7 @@ import { fileURLToPath } from "node:url";
9
9
  import {
10
10
  launchNativeCaptured,
11
11
  launchNative,
12
+ prepareNativeLaunch,
12
13
  resolveNativeBinary,
13
14
  selectPlatformPackage,
14
15
  translateArguments,
@@ -184,8 +185,11 @@ All commands execute the compiled Mikie native engine.`);
184
185
  }
185
186
  } else {
186
187
  try {
187
- const binary = await resolveNativeBinary();
188
- process.exitCode = await launchNative(binary, translateArguments(arguments_));
188
+ const nativeArguments = translateArguments(arguments_);
189
+ const binary = await prepareNativeLaunch(await resolveNativeBinary(), nativeArguments, {
190
+ version: packageMetadata.version,
191
+ });
192
+ process.exitCode = await launchNative(binary, nativeArguments);
189
193
  } catch (error) {
190
194
  const message = error instanceof Error ? error.message : "Mikie is unavailable";
191
195
  console.error(`mikie: ${message}`);
package/lib/launcher.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import { createRequire } from "node:module";
2
- import { access, stat } from "node:fs/promises";
3
- import { constants as fsConstants } from "node:fs";
2
+ import { createHash, randomUUID } from "node:crypto";
3
+ import { constants as fsConstants, createReadStream } from "node:fs";
4
+ import { access, copyFile, lstat, mkdir, readdir, rename, rm, stat } from "node:fs/promises";
4
5
  import path from "node:path";
5
6
  import { spawn } from "node:child_process";
6
7
 
@@ -18,6 +19,142 @@ const SIGNAL_EXIT_CODES = new Map([
18
19
  ]);
19
20
 
20
21
  const MAX_CAPTURED_OUTPUT_BYTES = 1024 * 1024;
22
+ const SAFE_RUNTIME_VERSION = /^[0-9A-Za-z][0-9A-Za-z._-]{0,127}$/;
23
+ const SAFE_RUNTIME_DIRECTORY = /^[0-9A-Za-z][0-9A-Za-z._-]{0,127}-[0-9a-f]{64}$/;
24
+ const HISTORICAL_RUNTIME_GRACE_MS = 24 * 60 * 60 * 1000;
25
+
26
+ async function sha256File(file) {
27
+ const digest = createHash("sha256");
28
+ for await (const chunk of createReadStream(file)) {
29
+ digest.update(chunk);
30
+ }
31
+ return digest.digest("hex");
32
+ }
33
+
34
+ async function verifyMaterializedRuntime(file, expectedSha256, { allowMissing = false } = {}) {
35
+ try {
36
+ const metadata = await lstat(file);
37
+ if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.size === 0) {
38
+ throw new Error("Mikie cached Windows runtime is invalid");
39
+ }
40
+ if ((await sha256File(file)) !== expectedSha256) {
41
+ throw new Error("Mikie cached Windows runtime hash does not match");
42
+ }
43
+ return true;
44
+ } catch (error) {
45
+ if (allowMissing && error?.code === "ENOENT") return false;
46
+ throw error;
47
+ }
48
+ }
49
+
50
+ async function cleanupHistoricalWindowsRuntimes(runtimeRoot, selectedDirectory) {
51
+ let entries;
52
+ try {
53
+ entries = await readdir(runtimeRoot, { withFileTypes: true });
54
+ } catch {
55
+ return;
56
+ }
57
+ const cutoff = Date.now() - HISTORICAL_RUNTIME_GRACE_MS;
58
+ await Promise.all(
59
+ entries.map(async (entry) => {
60
+ if (
61
+ !entry.isDirectory() ||
62
+ entry.name === selectedDirectory ||
63
+ !SAFE_RUNTIME_DIRECTORY.test(entry.name)
64
+ ) {
65
+ return;
66
+ }
67
+ const candidate = path.join(runtimeRoot, entry.name);
68
+ try {
69
+ if ((await lstat(candidate)).mtimeMs > cutoff) return;
70
+ await rm(candidate, { recursive: true, force: true, maxRetries: 0 });
71
+ } catch {
72
+ // Active Windows executables remain locked and are retried by a later launch.
73
+ }
74
+ }),
75
+ );
76
+ }
77
+
78
+ export function shouldStageWindowsRuntime(arguments_) {
79
+ if (arguments_.length === 0) return true;
80
+ if (["mcp", "serve", "run-relay"].includes(arguments_[0])) return true;
81
+ return arguments_[0] === "control" && arguments_[1] === "run";
82
+ }
83
+
84
+ export async function materializeWindowsRuntime(
85
+ binary,
86
+ { version, localAppData = process.env.LOCALAPPDATA } = {},
87
+ ) {
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
+ const sourceMetadata = await lstat(binary);
96
+ if (!sourceMetadata.isFile() || sourceMetadata.isSymbolicLink() || sourceMetadata.size === 0) {
97
+ throw new Error("Mikie native Windows runtime is invalid");
98
+ }
99
+ const sourceSha256 = await sha256File(binary);
100
+ const runtimeRoot = path.join(localAppData, "Mikie", "runtime-v1");
101
+ const selectedDirectory = `${version}-${sourceSha256}`;
102
+ const runtimeDirectory = path.join(runtimeRoot, selectedDirectory);
103
+ const materialized = path.join(runtimeDirectory, "mikie.exe");
104
+ await mkdir(runtimeDirectory, { recursive: true });
105
+ if (await verifyMaterializedRuntime(materialized, sourceSha256, { allowMissing: true })) {
106
+ await cleanupHistoricalWindowsRuntimes(runtimeRoot, selectedDirectory);
107
+ return materialized;
108
+ }
109
+
110
+ const temporary = path.join(
111
+ runtimeDirectory,
112
+ `.mikie-${process.pid}-${randomUUID()}.tmp`,
113
+ );
114
+ try {
115
+ await copyFile(binary, temporary, fsConstants.COPYFILE_EXCL);
116
+ const [copiedSha256, stableSourceSha256] = await Promise.all([
117
+ sha256File(temporary),
118
+ sha256File(binary),
119
+ ]);
120
+ if (copiedSha256 !== sourceSha256 || stableSourceSha256 !== sourceSha256) {
121
+ throw new Error("Mikie native Windows runtime changed during materialization");
122
+ }
123
+ try {
124
+ await rename(temporary, materialized);
125
+ } catch (error) {
126
+ if (
127
+ !["EEXIST", "EPERM"].includes(error?.code) ||
128
+ !(await verifyMaterializedRuntime(materialized, sourceSha256, { allowMissing: true }))
129
+ ) {
130
+ throw error;
131
+ }
132
+ }
133
+ await verifyMaterializedRuntime(materialized, sourceSha256);
134
+ await cleanupHistoricalWindowsRuntimes(runtimeRoot, selectedDirectory);
135
+ return materialized;
136
+ } finally {
137
+ await rm(temporary, { force: true }).catch(() => {});
138
+ }
139
+ }
140
+
141
+ export async function prepareNativeLaunch(
142
+ binary,
143
+ arguments_,
144
+ {
145
+ platform = process.platform,
146
+ version,
147
+ environment = process.env,
148
+ } = {},
149
+ ) {
150
+ if (platform !== "win32" || !shouldStageWindowsRuntime(arguments_)) {
151
+ return binary;
152
+ }
153
+ return materializeWindowsRuntime(binary, {
154
+ version,
155
+ localAppData: environment.LOCALAPPDATA,
156
+ });
157
+ }
21
158
 
22
159
  function currentRuntimeUsesGlibc() {
23
160
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getmikie/cli",
3
- "version": "1.1.88",
3
+ "version": "1.1.89",
4
4
  "description": "Native Mikie relay launcher for MCP clients",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -20,10 +20,10 @@
20
20
  "node": ">=22"
21
21
  },
22
22
  "optionalDependencies": {
23
- "@getmikie/darwin-arm64": "1.1.88",
24
- "@getmikie/darwin-x64": "1.1.88",
25
- "@getmikie/linux-x64-gnu": "1.1.88",
26
- "@getmikie/win32-x64": "1.1.88"
23
+ "@getmikie/darwin-arm64": "1.1.89",
24
+ "@getmikie/darwin-x64": "1.1.89",
25
+ "@getmikie/linux-x64-gnu": "1.1.89",
26
+ "@getmikie/win32-x64": "1.1.89"
27
27
  },
28
28
  "publishConfig": {
29
29
  "access": "public",