@getmikie/cli 1.1.124 → 1.1.126
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 +82 -11
- package/lib/launcher.mjs +27 -0
- package/lib/update.mjs +87 -10
- package/package.json +4 -4
package/bin/mikie.mjs
CHANGED
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
retireHistoricalWindowsRuntimes,
|
|
17
17
|
retireHistoricalRuntimes,
|
|
18
18
|
resolveDependencyPackageJson,
|
|
19
|
+
resolveInstalledPackages,
|
|
19
20
|
resolveNativeBinary,
|
|
20
21
|
selectPlatformPackage,
|
|
21
22
|
shouldCompleteSkippedInstall,
|
|
@@ -30,8 +31,10 @@ import {
|
|
|
30
31
|
detectUpdateTopology,
|
|
31
32
|
parseUpdateHandoff,
|
|
32
33
|
parseTrajectoryReconciliationReceipt,
|
|
34
|
+
pendingUpdateTransaction,
|
|
33
35
|
recoverPendingNpmUpdate,
|
|
34
36
|
runUpdateTransaction,
|
|
37
|
+
setAsideUpdateTransaction,
|
|
35
38
|
} from "../lib/update.mjs";
|
|
36
39
|
import {
|
|
37
40
|
completeSkippedInstall,
|
|
@@ -40,6 +43,20 @@ import {
|
|
|
40
43
|
} from "../scripts/reconcile-install.mjs";
|
|
41
44
|
|
|
42
45
|
const require = createRequire(import.meta.url);
|
|
46
|
+
|
|
47
|
+
/** One next step for the update failures a customer can act on. */
|
|
48
|
+
function updateGuidance(message) {
|
|
49
|
+
if (/EBUSY|EPERM|resource busy|operation not permitted|locked/i.test(message)) {
|
|
50
|
+
return "; a running Mikie process holds a file: close Codex and Claude Code, then run `mikie update` again";
|
|
51
|
+
}
|
|
52
|
+
if (/registry did not resolve|registry/i.test(message)) {
|
|
53
|
+
return "; the npm registry could not be reached or returned no compatible version: check the connection and run `mikie update` again";
|
|
54
|
+
}
|
|
55
|
+
if (/another Mikie update transaction is active/.test(message)) {
|
|
56
|
+
return "; wait for the other `mikie update` to finish";
|
|
57
|
+
}
|
|
58
|
+
return "";
|
|
59
|
+
}
|
|
43
60
|
const packageMetadata = require("../package.json");
|
|
44
61
|
const arguments_ = process.argv.slice(2);
|
|
45
62
|
const singleFlag = arguments_.length === 1 ? arguments_[0].toLowerCase() : null;
|
|
@@ -95,7 +112,36 @@ All commands execute the compiled Mikie native engine.`);
|
|
|
95
112
|
const selected = selectPlatformPackage();
|
|
96
113
|
const platformPackageJson = require.resolve(`${selected.packageName}/package.json`);
|
|
97
114
|
const wrapperPackageJson = fileURLToPath(new URL("../package.json", import.meta.url));
|
|
98
|
-
|
|
115
|
+
let binary;
|
|
116
|
+
try {
|
|
117
|
+
binary = await resolveNativeBinary({ version: packageMetadata.version });
|
|
118
|
+
} catch (error) {
|
|
119
|
+
// A half-finished install (wrapper and native package at different
|
|
120
|
+
// versions, or a missing native package) cannot run its own updater.
|
|
121
|
+
// Reinstall the whole CLI from npm, the documented recovery.
|
|
122
|
+
const reason = error instanceof Error ? error.message : "the native package is unavailable";
|
|
123
|
+
if (topology !== UpdateTopology.NpmGlobal || !/native package|versions do not match/.test(reason)) {
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
console.error(`mikie: the installed CLI is incomplete (${reason}); reinstalling it from npm.`);
|
|
127
|
+
const registry = createRegistryAdapter({ commands });
|
|
128
|
+
const resolved = await registry.resolveExactCompatibleVersion({
|
|
129
|
+
packageName: "@getmikie/cli",
|
|
130
|
+
currentVersion: packageMetadata.version,
|
|
131
|
+
nodeVersion: process.versions.node,
|
|
132
|
+
});
|
|
133
|
+
const spec = `@getmikie/cli@${resolved.version}`;
|
|
134
|
+
const reinstall = await commands.run(
|
|
135
|
+
"npm",
|
|
136
|
+
["install", "--global", spec, "--allow-scripts=@getmikie/cli"],
|
|
137
|
+
{ shell: false, capture: true },
|
|
138
|
+
);
|
|
139
|
+
if (reinstall.code !== 0) {
|
|
140
|
+
throw new Error(`reinstalling the CLI failed; run: npm install --global ${spec} --allow-scripts=@getmikie/cli`);
|
|
141
|
+
}
|
|
142
|
+
console.log(`Reinstalled Mikie ${resolved.version}. Run \`mikie doctor\` to check client setup.`);
|
|
143
|
+
process.exit(0);
|
|
144
|
+
}
|
|
99
145
|
const globalPrefix = npmPrefix.stdout.trim();
|
|
100
146
|
const globalShims =
|
|
101
147
|
process.platform === "win32"
|
|
@@ -105,14 +151,29 @@ All commands execute the compiled Mikie native engine.`);
|
|
|
105
151
|
const transactionRoot = path.join(customerHome, ".mikie", "npm-update-v1");
|
|
106
152
|
const recoveryJournal = path.join(transactionRoot, "journal.json");
|
|
107
153
|
if (topology === UpdateTopology.NpmGlobal) {
|
|
154
|
+
let pending = true;
|
|
108
155
|
try {
|
|
109
156
|
await access(recoveryJournal);
|
|
110
|
-
await recoverPendingNpmUpdate(transactionRoot, {
|
|
111
|
-
commands,
|
|
112
|
-
supersedingInstallation: { version: packageMetadata.version, binary },
|
|
113
|
-
});
|
|
114
157
|
} catch (error) {
|
|
115
158
|
if (error?.code !== "ENOENT") throw error;
|
|
159
|
+
pending = false;
|
|
160
|
+
}
|
|
161
|
+
if (pending) {
|
|
162
|
+
try {
|
|
163
|
+
await recoverPendingNpmUpdate(transactionRoot, {
|
|
164
|
+
commands,
|
|
165
|
+
supersedingInstallation: { version: packageMetadata.version, binary },
|
|
166
|
+
});
|
|
167
|
+
} catch (error) {
|
|
168
|
+
// The installed packages were verified above. An earlier transaction
|
|
169
|
+
// that cannot be finished (superseded, tampered or from an older
|
|
170
|
+
// updater) is kept byte for byte but must not block this update.
|
|
171
|
+
const reason = error instanceof Error ? error.message : "recovery failed";
|
|
172
|
+
const aside = await setAsideUpdateTransaction(transactionRoot);
|
|
173
|
+
console.error(
|
|
174
|
+
`mikie: an earlier unfinished update could not be completed (${reason}); its files were kept at ${aside} and this update continues from ${packageMetadata.version}.`,
|
|
175
|
+
);
|
|
176
|
+
}
|
|
116
177
|
}
|
|
117
178
|
}
|
|
118
179
|
const handoffResult = await launchNativeCaptured(binary, ["update"], { topology });
|
|
@@ -150,10 +211,13 @@ All commands execute the compiled Mikie native engine.`);
|
|
|
150
211
|
: [path.join(stagingPrefix, "bin", "mikie")],
|
|
151
212
|
};
|
|
152
213
|
}
|
|
214
|
+
// The activated packages on disk: this process is still the
|
|
215
|
+
// previous wrapper, so its own version is not the one to verify.
|
|
216
|
+
const installed = await resolveInstalledPackages(wrapperPackageJson);
|
|
153
217
|
return {
|
|
154
218
|
wrapperPackageJson,
|
|
155
|
-
platformPackageJson:
|
|
156
|
-
binary:
|
|
219
|
+
platformPackageJson: installed.platformPackageJson,
|
|
220
|
+
binary: installed.binary,
|
|
157
221
|
shims: globalShims,
|
|
158
222
|
};
|
|
159
223
|
},
|
|
@@ -246,10 +310,7 @@ All commands execute the compiled Mikie native engine.`);
|
|
|
246
310
|
}
|
|
247
311
|
} catch (error) {
|
|
248
312
|
const message = error instanceof Error ? error.message : "Mikie update failed";
|
|
249
|
-
|
|
250
|
-
? "; keep the recovery files and contact support before retrying"
|
|
251
|
-
: "";
|
|
252
|
-
console.error(`mikie: ${message}${guidance}`);
|
|
313
|
+
console.error(`mikie: ${message}${updateGuidance(message)}`);
|
|
253
314
|
process.exitCode = 1;
|
|
254
315
|
}
|
|
255
316
|
} else {
|
|
@@ -276,6 +337,16 @@ All commands execute the compiled Mikie native engine.`);
|
|
|
276
337
|
console.error(`mikie: install reconciliation is incomplete (${reason}); the command continues.`);
|
|
277
338
|
}
|
|
278
339
|
}
|
|
340
|
+
if (nativeArguments[0] === "doctor") {
|
|
341
|
+
const pendingUpdate = await pendingUpdateTransaction(
|
|
342
|
+
path.join(homedir(), ".mikie", "npm-update-v1"),
|
|
343
|
+
);
|
|
344
|
+
if (pendingUpdate) {
|
|
345
|
+
console.log(
|
|
346
|
+
`update transaction: pending (${pendingUpdate.phase}); run \`mikie update\` to finish it or set it aside`,
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
279
350
|
const binary = await prepareNativeLaunch(await resolveNativeBinary(), nativeArguments, {
|
|
280
351
|
version: packageMetadata.version,
|
|
281
352
|
});
|
package/lib/launcher.mjs
CHANGED
|
@@ -809,6 +809,33 @@ export function resolveDependencyPackageJson(wrapperPackageJson, dependencyPacka
|
|
|
809
809
|
return wrapperRequire.resolve(`${dependencyPackageName}/package.json`);
|
|
810
810
|
}
|
|
811
811
|
|
|
812
|
+
/**
|
|
813
|
+
* The wrapper and native package installed on disk now, checked against each
|
|
814
|
+
* other. After `mikie update` activates a new version, the running process is
|
|
815
|
+
* still the previous wrapper, so its own version must not be the reference.
|
|
816
|
+
*/
|
|
817
|
+
export async function resolveInstalledPackages(
|
|
818
|
+
wrapperPackageJson,
|
|
819
|
+
{ platform = process.platform, arch = process.arch, glibc } = {},
|
|
820
|
+
) {
|
|
821
|
+
const selected = selectPlatformPackage({ platform, arch, glibc });
|
|
822
|
+
let version;
|
|
823
|
+
try {
|
|
824
|
+
version = JSON.parse(await readFile(wrapperPackageJson, "utf8"))?.version;
|
|
825
|
+
} catch {
|
|
826
|
+
throw new Error("Mikie wrapper package metadata is invalid; reinstall the CLI");
|
|
827
|
+
}
|
|
828
|
+
const platformPackageJson = resolveDependencyPackageJson(wrapperPackageJson, selected.packageName);
|
|
829
|
+
const binary = await resolveNativeBinary({
|
|
830
|
+
platform,
|
|
831
|
+
arch,
|
|
832
|
+
glibc,
|
|
833
|
+
version,
|
|
834
|
+
resolvePackage: () => platformPackageJson,
|
|
835
|
+
});
|
|
836
|
+
return { wrapperPackageJson, platformPackageJson, binary, version };
|
|
837
|
+
}
|
|
838
|
+
|
|
812
839
|
export async function resolveNativeBinary({
|
|
813
840
|
platform = process.platform,
|
|
814
841
|
arch = process.arch,
|
package/lib/update.mjs
CHANGED
|
@@ -428,6 +428,55 @@ function validateHandoff(value, topology = value?.topology) {
|
|
|
428
428
|
return value;
|
|
429
429
|
}
|
|
430
430
|
|
|
431
|
+
/**
|
|
432
|
+
* Move an earlier update transaction that cannot be finished out of the way,
|
|
433
|
+
* byte for byte, so it cannot block every later `mikie update`. Refuses while
|
|
434
|
+
* another update process holds the transaction lock. Returns the new path.
|
|
435
|
+
*/
|
|
436
|
+
export async function setAsideUpdateTransaction(
|
|
437
|
+
transactionRoot,
|
|
438
|
+
{ now = new Date(), isProcessAlive = processIsAlive } = {},
|
|
439
|
+
) {
|
|
440
|
+
if (typeof transactionRoot !== "string" || !path.isAbsolute(transactionRoot)) {
|
|
441
|
+
throw new Error("unsafe Mikie update transaction root");
|
|
442
|
+
}
|
|
443
|
+
await assertNoLinkedAncestors(transactionRoot);
|
|
444
|
+
try {
|
|
445
|
+
const lock = parseJsonNoDuplicateKeys(await readFile(`${transactionRoot}.lock`));
|
|
446
|
+
if (isProcessAlive(lock?.process_id)) {
|
|
447
|
+
throw new Error("another Mikie update transaction is active");
|
|
448
|
+
}
|
|
449
|
+
} catch (error) {
|
|
450
|
+
if (error?.message === "another Mikie update transaction is active") throw error;
|
|
451
|
+
}
|
|
452
|
+
const stamp = now.toISOString().replace(/[-:]/g, "").replace(/\.\d+Z$/, "Z");
|
|
453
|
+
for (let attempt = 0; attempt < 100; attempt += 1) {
|
|
454
|
+
const target = `${transactionRoot}.set-aside-${stamp}${attempt ? `-${attempt}` : ""}`;
|
|
455
|
+
try {
|
|
456
|
+
await lstat(target);
|
|
457
|
+
continue;
|
|
458
|
+
} catch (error) {
|
|
459
|
+
if (error?.code !== "ENOENT") throw error;
|
|
460
|
+
}
|
|
461
|
+
await rename(transactionRoot, target);
|
|
462
|
+
return target;
|
|
463
|
+
}
|
|
464
|
+
throw new Error("no free name to set aside the Mikie update transaction");
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/** The phase of a pending update transaction, or null when there is none. */
|
|
468
|
+
export async function pendingUpdateTransaction(transactionRoot) {
|
|
469
|
+
try {
|
|
470
|
+
const journal = parseJsonNoDuplicateKeys(
|
|
471
|
+
await readFile(path.join(transactionRoot, "journal.json")),
|
|
472
|
+
);
|
|
473
|
+
return { phase: typeof journal?.phase === "string" ? journal.phase : "unknown" };
|
|
474
|
+
} catch (error) {
|
|
475
|
+
if (error?.code === "ENOENT") return null;
|
|
476
|
+
return { phase: "unreadable" };
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
431
480
|
export function parseUpdateHandoff(stdout, expectedTopology) {
|
|
432
481
|
if (typeof stdout !== "string" || stdout.trim().split(/\r?\n/).length !== 1) {
|
|
433
482
|
throw new Error("invalid Mikie update handoff");
|
|
@@ -2487,22 +2536,50 @@ export function createFilesystemAdapter({
|
|
|
2487
2536
|
return true;
|
|
2488
2537
|
};
|
|
2489
2538
|
|
|
2539
|
+
// The recorded layout, and the same wrapper with its platform package
|
|
2540
|
+
// nested under it: a later npm install can nest the platform package and
|
|
2541
|
+
// leave the journal's hoisted copy behind at the older version.
|
|
2542
|
+
const supersedingLayouts = () => {
|
|
2543
|
+
const platformRoot = path.dirname(currentLayout.platformPackageJson);
|
|
2544
|
+
const nestedRoot = path.join(
|
|
2545
|
+
path.dirname(currentLayout.wrapperPackageJson),
|
|
2546
|
+
"node_modules",
|
|
2547
|
+
path.basename(path.dirname(platformRoot)),
|
|
2548
|
+
path.basename(platformRoot),
|
|
2549
|
+
);
|
|
2550
|
+
const nested = {
|
|
2551
|
+
platformPackageJson: path.join(nestedRoot, "package.json"),
|
|
2552
|
+
binary: path.join(nestedRoot, path.relative(platformRoot, currentLayout.binary)),
|
|
2553
|
+
};
|
|
2554
|
+
return sameExecutablePath(nestedRoot, platformRoot) ? [currentLayout] : [currentLayout, nested];
|
|
2555
|
+
};
|
|
2556
|
+
|
|
2490
2557
|
const authenticateSupersedingInstallation = async () => {
|
|
2491
2558
|
if (!supersedingInstallation) return false;
|
|
2559
|
+
let wrapperVersion;
|
|
2492
2560
|
try {
|
|
2493
|
-
|
|
2494
|
-
readFile(currentLayout.wrapperPackageJson, "utf8"),
|
|
2495
|
-
readFile(currentLayout.platformPackageJson, "utf8"),
|
|
2496
|
-
executableDigest(currentLayout.binary),
|
|
2497
|
-
]);
|
|
2498
|
-
return (
|
|
2499
|
-
JSON.parse(wrapper)?.version === supersedingInstallation.version &&
|
|
2500
|
-
JSON.parse(platform)?.version === supersedingInstallation.version &&
|
|
2501
|
-
binarySha256 === supersedingInstallation.binarySha256
|
|
2502
|
-
);
|
|
2561
|
+
wrapperVersion = JSON.parse(await readFile(currentLayout.wrapperPackageJson, "utf8"))?.version;
|
|
2503
2562
|
} catch {
|
|
2504
2563
|
return false;
|
|
2505
2564
|
}
|
|
2565
|
+
if (wrapperVersion !== supersedingInstallation.version) return false;
|
|
2566
|
+
for (const layout of supersedingLayouts()) {
|
|
2567
|
+
try {
|
|
2568
|
+
const [platform, binarySha256] = await Promise.all([
|
|
2569
|
+
readFile(layout.platformPackageJson, "utf8"),
|
|
2570
|
+
executableDigest(layout.binary),
|
|
2571
|
+
]);
|
|
2572
|
+
if (
|
|
2573
|
+
JSON.parse(platform)?.version === supersedingInstallation.version &&
|
|
2574
|
+
binarySha256 === supersedingInstallation.binarySha256
|
|
2575
|
+
) {
|
|
2576
|
+
return true;
|
|
2577
|
+
}
|
|
2578
|
+
} catch {
|
|
2579
|
+
// Try the next layout.
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
return false;
|
|
2506
2583
|
};
|
|
2507
2584
|
|
|
2508
2585
|
const cleanupSupersededCommittedActivation = async (snapshot) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getmikie/cli",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.126",
|
|
4
4
|
"description": "Native Mikie relay launcher for MCP clients",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
"node": ">=22"
|
|
25
25
|
},
|
|
26
26
|
"optionalDependencies": {
|
|
27
|
-
"@getmikie/darwin-arm64": "1.1.
|
|
28
|
-
"@getmikie/linux-x64-gnu": "1.1.
|
|
29
|
-
"@getmikie/win32-x64": "1.1.
|
|
27
|
+
"@getmikie/darwin-arm64": "1.1.126",
|
|
28
|
+
"@getmikie/linux-x64-gnu": "1.1.126",
|
|
29
|
+
"@getmikie/win32-x64": "1.1.126"
|
|
30
30
|
},
|
|
31
31
|
"publishConfig": {
|
|
32
32
|
"access": "public",
|