@getmikie/cli 1.1.108 → 1.1.114
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/README.md +14 -11
- package/bin/mikie.mjs +30 -20
- package/lib/launcher.mjs +12 -0
- package/lib/update.mjs +228 -19
- package/package.json +5 -5
- package/scripts/reconcile-install.mjs +20 -5
package/README.md
CHANGED
|
@@ -3,28 +3,29 @@
|
|
|
3
3
|
This package is a thin launcher for Mikie's compiled native relay.
|
|
4
4
|
|
|
5
5
|
```sh
|
|
6
|
-
npx -
|
|
6
|
+
npx --yes --ignore-scripts --registry=https://registry.npmjs.org @getmikie/cli@1.1.108 mcp
|
|
7
7
|
```
|
|
8
8
|
|
|
9
9
|
For a persistent command path on Windows PowerShell:
|
|
10
10
|
|
|
11
11
|
```powershell
|
|
12
|
-
npm.cmd install --global @getmikie/cli@
|
|
12
|
+
npm.cmd install --global @getmikie/cli@1.1.108 --ignore-scripts --registry=https://registry.npmjs.org
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
Mikie does not change your PowerShell execution policy.
|
|
16
|
-
command
|
|
17
|
-
|
|
18
|
-
directory.
|
|
15
|
+
Mikie does not change your PowerShell execution policy. Keep the exact package
|
|
16
|
+
name in the command: without naming the package as the install target, npm
|
|
17
|
+
incorrectly targets the current directory.
|
|
19
18
|
|
|
20
19
|
For a persistent command path on macOS or Linux:
|
|
21
20
|
|
|
22
21
|
```sh
|
|
23
|
-
npm install --global @getmikie/cli@
|
|
22
|
+
npm install --global @getmikie/cli@1.1.108 --ignore-scripts --registry=https://registry.npmjs.org
|
|
24
23
|
```
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
These portable npm commands disable every lifecycle script and pin the public
|
|
26
|
+
npm registry. The public OS installers additionally authenticate exact wrapper
|
|
27
|
+
and native tarball bytes, install them offline, and explicitly run only Mikie's
|
|
28
|
+
authenticated reconciliation script.
|
|
28
29
|
|
|
29
30
|
Then run:
|
|
30
31
|
|
|
@@ -46,7 +47,9 @@ mikie control run --request C:\absolute\path\control-request.json --json
|
|
|
46
47
|
```
|
|
47
48
|
|
|
48
49
|
The request must be an existing absolute JSON file. Mikie validates the frozen
|
|
49
|
-
repository, allowed paths,
|
|
50
|
-
|
|
50
|
+
repository, allowed paths, exact model and reasoning effort, bounded model-turn
|
|
51
|
+
timeout, and private temporary root before execution. The verifier argv is
|
|
52
|
+
restricted to `git diff --check`; interpreters, build tools, and test runners
|
|
53
|
+
that could execute repository-controlled code are rejected. The JSON result is
|
|
51
54
|
content-free: it reports terminal status and hashes, never prompts, context,
|
|
52
55
|
patch bodies, verifier output, or credentials.
|
package/bin/mikie.mjs
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
resolveDependencyPackageJson,
|
|
19
19
|
resolveNativeBinary,
|
|
20
20
|
selectPlatformPackage,
|
|
21
|
+
shouldReconcileInstalledClients,
|
|
21
22
|
translateArguments,
|
|
22
23
|
} from "../lib/launcher.mjs";
|
|
23
24
|
import {
|
|
@@ -26,9 +27,9 @@ import {
|
|
|
26
27
|
createFilesystemAdapter,
|
|
27
28
|
createRegistryAdapter,
|
|
28
29
|
detectUpdateTopology,
|
|
29
|
-
externalRecoveryInvocation,
|
|
30
30
|
parseUpdateHandoff,
|
|
31
31
|
parseTrajectoryReconciliationReceipt,
|
|
32
|
+
recoverPendingNpmUpdate,
|
|
32
33
|
runUpdateTransaction,
|
|
33
34
|
} from "../lib/update.mjs";
|
|
34
35
|
import {
|
|
@@ -47,12 +48,23 @@ if (["--version", "-v"].includes(singleFlag)) {
|
|
|
47
48
|
console.log(`Mikie ${packageMetadata.version}
|
|
48
49
|
|
|
49
50
|
Usage:
|
|
51
|
+
mikie savings history [options]
|
|
52
|
+
mikie savings export-meter-history
|
|
50
53
|
mikie mcp [--adapter codex|claude_code]
|
|
51
54
|
mikie login
|
|
52
|
-
mikie
|
|
55
|
+
mikie logout
|
|
56
|
+
mikie connect .|<absolute-directory>
|
|
57
|
+
mikie privacy review
|
|
58
|
+
mikie setup codex
|
|
59
|
+
mikie setup claude
|
|
60
|
+
mikie mode codex [active|shadow] [--json]
|
|
53
61
|
mikie control run --request <absolute-json> --json
|
|
54
|
-
mikie doctor
|
|
62
|
+
mikie doctor [--client codex]
|
|
55
63
|
mikie update
|
|
64
|
+
mikie device revoke
|
|
65
|
+
mikie uninstall
|
|
66
|
+
mikie --help
|
|
67
|
+
mikie --version
|
|
56
68
|
|
|
57
69
|
All commands execute the compiled Mikie native engine.`);
|
|
58
70
|
} else if (arguments_.length === 1 && arguments_[0].toLowerCase() === "update") {
|
|
@@ -77,33 +89,29 @@ All commands execute the compiled Mikie native engine.`);
|
|
|
77
89
|
throw new Error("global npm installation authority is unavailable");
|
|
78
90
|
}
|
|
79
91
|
const topology = detectUpdateTopology({ globalNodeModules: npmRoot.stdout.trim() });
|
|
92
|
+
const selected = selectPlatformPackage();
|
|
93
|
+
const platformPackageJson = require.resolve(`${selected.packageName}/package.json`);
|
|
94
|
+
const wrapperPackageJson = fileURLToPath(new URL("../package.json", import.meta.url));
|
|
95
|
+
const binary = await resolveNativeBinary();
|
|
96
|
+
const globalPrefix = npmPrefix.stdout.trim();
|
|
97
|
+
const globalShims =
|
|
98
|
+
process.platform === "win32"
|
|
99
|
+
? ["mikie", "mikie.cmd", "mikie.ps1"].map((name) => path.join(globalPrefix, name))
|
|
100
|
+
: [path.join(globalPrefix, "bin", "mikie")];
|
|
80
101
|
const customerHome = homedir();
|
|
81
102
|
const transactionRoot = path.join(customerHome, ".mikie", "npm-update-v1");
|
|
82
103
|
const recoveryJournal = path.join(transactionRoot, "journal.json");
|
|
83
104
|
if (topology === UpdateTopology.NpmGlobal) {
|
|
84
105
|
try {
|
|
85
106
|
await access(recoveryJournal);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
capture: true,
|
|
107
|
+
await recoverPendingNpmUpdate(transactionRoot, {
|
|
108
|
+
commands,
|
|
109
|
+
supersedingInstallation: { version: packageMetadata.version, binary },
|
|
90
110
|
});
|
|
91
|
-
if (recovered.code !== 0) {
|
|
92
|
-
throw new Error("pending Mikie update recovery failed");
|
|
93
|
-
}
|
|
94
111
|
} catch (error) {
|
|
95
112
|
if (error?.code !== "ENOENT") throw error;
|
|
96
113
|
}
|
|
97
114
|
}
|
|
98
|
-
const selected = selectPlatformPackage();
|
|
99
|
-
const platformPackageJson = require.resolve(`${selected.packageName}/package.json`);
|
|
100
|
-
const wrapperPackageJson = fileURLToPath(new URL("../package.json", import.meta.url));
|
|
101
|
-
const binary = await resolveNativeBinary();
|
|
102
|
-
const globalPrefix = npmPrefix.stdout.trim();
|
|
103
|
-
const globalShims =
|
|
104
|
-
process.platform === "win32"
|
|
105
|
-
? ["mikie", "mikie.cmd", "mikie.ps1"].map((name) => path.join(globalPrefix, name))
|
|
106
|
-
: [path.join(globalPrefix, "bin", "mikie")];
|
|
107
115
|
const handoffResult = await launchNativeCaptured(binary, ["update"], { topology });
|
|
108
116
|
if (handoffResult.code !== 0) {
|
|
109
117
|
if (handoffResult.stderr) process.stderr.write(handoffResult.stderr);
|
|
@@ -236,8 +244,10 @@ All commands execute the compiled Mikie native engine.`);
|
|
|
236
244
|
}
|
|
237
245
|
} else {
|
|
238
246
|
try {
|
|
239
|
-
await reconcileInstalledClients({ version: packageMetadata.version });
|
|
240
247
|
const nativeArguments = translateArguments(arguments_);
|
|
248
|
+
if (shouldReconcileInstalledClients(nativeArguments)) {
|
|
249
|
+
await reconcileInstalledClients({ version: packageMetadata.version });
|
|
250
|
+
}
|
|
241
251
|
const binary = await prepareNativeLaunch(await resolveNativeBinary(), nativeArguments, {
|
|
242
252
|
version: packageMetadata.version,
|
|
243
253
|
});
|
package/lib/launcher.mjs
CHANGED
|
@@ -424,6 +424,11 @@ export function shouldStageWindowsRuntime(arguments_) {
|
|
|
424
424
|
return arguments_[0] === "control" && arguments_[1] === "run";
|
|
425
425
|
}
|
|
426
426
|
|
|
427
|
+
/** Return whether a native runtime entrypoint needs installed client launchers reconciled first. */
|
|
428
|
+
export function shouldReconcileInstalledClients(arguments_) {
|
|
429
|
+
return ["serve", "run-relay", "run-codex-hook", "run-claude-hook"].includes(arguments_[0]);
|
|
430
|
+
}
|
|
431
|
+
|
|
427
432
|
async function materializeRuntime(
|
|
428
433
|
binary,
|
|
429
434
|
{ version, runtimeRoot, executableName, cleanupHistorical },
|
|
@@ -616,6 +621,13 @@ export function selectPlatformPackage({
|
|
|
616
621
|
}
|
|
617
622
|
|
|
618
623
|
export function translateArguments(arguments_) {
|
|
624
|
+
if (
|
|
625
|
+
arguments_.length === 2 &&
|
|
626
|
+
arguments_[0] === "setup" &&
|
|
627
|
+
arguments_[1] === "claude-code"
|
|
628
|
+
) {
|
|
629
|
+
return ["setup", "claude"];
|
|
630
|
+
}
|
|
619
631
|
if (arguments_[0] !== "mcp") {
|
|
620
632
|
return [...arguments_];
|
|
621
633
|
}
|
package/lib/update.mjs
CHANGED
|
@@ -19,11 +19,11 @@ import { isDeepStrictEqual } from "node:util";
|
|
|
19
19
|
|
|
20
20
|
const WRAPPER_PACKAGE = "@getmikie/cli";
|
|
21
21
|
const HANDOFF_SCHEMA = "mikie.update-handoff.v1";
|
|
22
|
-
const REPAIR_SCHEMA = "mikie.claude-hook-repair.
|
|
22
|
+
const REPAIR_SCHEMA = "mikie.claude-hook-repair.v2";
|
|
23
23
|
const TRAJECTORY_RECONCILIATION_SCHEMA = "mikie.trajectory-store-reconciliation.v1";
|
|
24
24
|
const RERUN_COMMAND = "npx -y @getmikie/cli@latest update";
|
|
25
25
|
const MAX_COMMAND_OUTPUT_BYTES = 1024 * 1024;
|
|
26
|
-
const DEFAULT_COMMAND_TIMEOUT_MS =
|
|
26
|
+
const DEFAULT_COMMAND_TIMEOUT_MS = 120_000;
|
|
27
27
|
const TRANSACTION_SCHEMA = "mikie.npm-update-transaction.v1";
|
|
28
28
|
const LOCK_SCHEMA = "mikie.npm-update-lock.v1";
|
|
29
29
|
const MAX_TRANSACTION_TREE_ENTRIES = 16_384;
|
|
@@ -331,11 +331,70 @@ export async function externalRecoveryInvocation(transactionRoot) {
|
|
|
331
331
|
"command",
|
|
332
332
|
]) ||
|
|
333
333
|
recovery.descriptor !== path.join(transactionRoot, "recovery.json") ||
|
|
334
|
-
|
|
334
|
+
recovery.launcher !== path.join(transactionRoot, "recover.mjs") ||
|
|
335
|
+
recovery.runtime !== path.join(transactionRoot, "recovery-runtime.mjs") ||
|
|
336
|
+
recovery.node !== process.execPath ||
|
|
337
|
+
![
|
|
338
|
+
recovery.descriptor_sha256,
|
|
339
|
+
recovery.launcher_sha256,
|
|
340
|
+
recovery.runtime_sha256,
|
|
341
|
+
].every((digest) => /^[a-f0-9]{64}$/.test(digest))
|
|
335
342
|
) {
|
|
336
343
|
throw new Error("invalid Mikie update recovery authority");
|
|
337
344
|
}
|
|
338
|
-
return
|
|
345
|
+
return {
|
|
346
|
+
...authenticatedRecoveryInvocation(transactionRoot, recovery.descriptor_sha256),
|
|
347
|
+
authority: {
|
|
348
|
+
descriptorSha256: recovery.descriptor_sha256,
|
|
349
|
+
launcherSha256: recovery.launcher_sha256,
|
|
350
|
+
runtimeSha256: recovery.runtime_sha256,
|
|
351
|
+
},
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** Recover with frozen transaction code first, then the authenticated current implementation. */
|
|
356
|
+
export async function recoverPendingNpmUpdate(
|
|
357
|
+
transactionRoot,
|
|
358
|
+
{
|
|
359
|
+
commands = createCommandAdapter(),
|
|
360
|
+
recoverCurrent = recoverExternalNpmUpdate,
|
|
361
|
+
supersedingInstallation,
|
|
362
|
+
} = {},
|
|
363
|
+
) {
|
|
364
|
+
const invocation = await externalRecoveryInvocation(transactionRoot);
|
|
365
|
+
const frozen = await commands.run(invocation.executable, invocation.args, {
|
|
366
|
+
shell: false,
|
|
367
|
+
capture: true,
|
|
368
|
+
});
|
|
369
|
+
if (frozen.code === 0) return { status: "recovered", method: "frozen" };
|
|
370
|
+
try {
|
|
371
|
+
let authenticatedSupersedingInstallation;
|
|
372
|
+
if (supersedingInstallation !== undefined) {
|
|
373
|
+
if (
|
|
374
|
+
!exactPublicVersion(supersedingInstallation?.version) ||
|
|
375
|
+
typeof supersedingInstallation?.binary !== "string" ||
|
|
376
|
+
!path.isAbsolute(supersedingInstallation.binary)
|
|
377
|
+
) {
|
|
378
|
+
throw new Error("invalid superseding Mikie installation authority");
|
|
379
|
+
}
|
|
380
|
+
authenticatedSupersedingInstallation = {
|
|
381
|
+
version: supersedingInstallation.version,
|
|
382
|
+
binarySha256: await executableDigest(supersedingInstallation.binary),
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
await recoverCurrent(transactionRoot, invocation.authority, {
|
|
386
|
+
supersedingInstallation: authenticatedSupersedingInstallation,
|
|
387
|
+
});
|
|
388
|
+
return { status: "recovered", method: "current" };
|
|
389
|
+
} catch (error) {
|
|
390
|
+
const message = error instanceof Error ? error.message : "";
|
|
391
|
+
const reason = /tampered|invalid|unsafe|authority|corrupt|changed/i.test(message)
|
|
392
|
+
? "recovery authority could not be authenticated"
|
|
393
|
+
: /settlement|configured client/i.test(message)
|
|
394
|
+
? "configured client state could not be settled"
|
|
395
|
+
: "authenticated recovery could not complete";
|
|
396
|
+
throw new Error(`pending Mikie update recovery failed: ${reason}; transaction preserved`);
|
|
397
|
+
}
|
|
339
398
|
}
|
|
340
399
|
|
|
341
400
|
function expectedHandoff(topology) {
|
|
@@ -632,12 +691,17 @@ function parseRepairReceipt(stdout) {
|
|
|
632
691
|
receipt.after === "current" &&
|
|
633
692
|
receipt.action === "already_current" &&
|
|
634
693
|
receipt.restart_required === false;
|
|
694
|
+
const absent =
|
|
695
|
+
receipt.before === "absent" &&
|
|
696
|
+
receipt.after === "absent" &&
|
|
697
|
+
receipt.action === "not_configured" &&
|
|
698
|
+
receipt.restart_required === false;
|
|
635
699
|
const skipped =
|
|
636
700
|
["foreign_conflict", "unavailable"].includes(receipt.before) &&
|
|
637
701
|
receipt.after === receipt.before &&
|
|
638
702
|
receipt.action === "refused" &&
|
|
639
703
|
receipt.restart_required === false;
|
|
640
|
-
if (!migrated && !current && !skipped) {
|
|
704
|
+
if (!migrated && !current && !absent && !skipped) {
|
|
641
705
|
throw new Error("candidate owned-adapter repair returned an invalid receipt");
|
|
642
706
|
}
|
|
643
707
|
return { receipt, skipped };
|
|
@@ -779,6 +843,7 @@ export async function runUpdateTransaction({
|
|
|
779
843
|
MIKIE_DISTRIBUTION_MODE: "npm",
|
|
780
844
|
MIKIE_NPM_UPDATE_TOPOLOGY: UpdateTopology.NpmGlobal,
|
|
781
845
|
MIKIE_NPM_UPDATE_REPAIR_PHASE: "prepare",
|
|
846
|
+
MIKIE_NPM_CLIENT_REPAIR_SCHEMA: REPAIR_SCHEMA,
|
|
782
847
|
MIKIE_UPDATE_REPAIR_LAUNCHER_STATE: "prior",
|
|
783
848
|
MIKIE_UPDATE_REPAIR_CANDIDATE: candidate.binary,
|
|
784
849
|
MIKIE_UPDATE_REPAIR_LAUNCHER: finalLauncher,
|
|
@@ -816,8 +881,10 @@ export async function runUpdateTransaction({
|
|
|
816
881
|
await files.activate(activatedCandidate);
|
|
817
882
|
await files.checkpointCandidate(snapshot, { provider: true });
|
|
818
883
|
const settlementRequired =
|
|
819
|
-
repairResult.
|
|
820
|
-
|
|
884
|
+
!repairResult.skipped &&
|
|
885
|
+
repairResult.receipt.action !== "not_configured" &&
|
|
886
|
+
(repairResult.receipt.action === "migrated" ||
|
|
887
|
+
files.requiresFinalLauncherSettlement?.() === true);
|
|
821
888
|
// A skipped receipt does not prove that no client still references a
|
|
822
889
|
// historical launcher. In particular, Claude can report unavailable
|
|
823
890
|
// when hooks/ownership are absent while an MCP-only registration remains.
|
|
@@ -840,6 +907,7 @@ export async function runUpdateTransaction({
|
|
|
840
907
|
MIKIE_DISTRIBUTION_MODE: "npm",
|
|
841
908
|
MIKIE_NPM_UPDATE_TOPOLOGY: UpdateTopology.NpmGlobal,
|
|
842
909
|
MIKIE_NPM_UPDATE_REPAIR_PHASE: "commit",
|
|
910
|
+
MIKIE_NPM_CLIENT_REPAIR_SCHEMA: REPAIR_SCHEMA,
|
|
843
911
|
MIKIE_UPDATE_REPAIR_LAUNCHER_STATE: "activated",
|
|
844
912
|
MIKIE_UPDATE_REPAIR_CANDIDATE: candidate.binary,
|
|
845
913
|
MIKIE_UPDATE_REPAIR_LAUNCHER: finalLauncher,
|
|
@@ -861,7 +929,7 @@ export async function runUpdateTransaction({
|
|
|
861
929
|
? "skipped"
|
|
862
930
|
: repairResult.receipt.action === "already_current"
|
|
863
931
|
? "verified"
|
|
864
|
-
:
|
|
932
|
+
: repairResult.receipt.action,
|
|
865
933
|
foreignSettings: verification.foreignSettings ?? [],
|
|
866
934
|
};
|
|
867
935
|
} catch (error) {
|
|
@@ -875,6 +943,7 @@ export async function runUpdateTransaction({
|
|
|
875
943
|
MIKIE_DISTRIBUTION_MODE: "npm",
|
|
876
944
|
MIKIE_NPM_UPDATE_TOPOLOGY: UpdateTopology.NpmGlobal,
|
|
877
945
|
MIKIE_NPM_UPDATE_REPAIR_PHASE: "rollback",
|
|
946
|
+
MIKIE_NPM_CLIENT_REPAIR_SCHEMA: REPAIR_SCHEMA,
|
|
878
947
|
MIKIE_UPDATE_REPAIR_LAUNCHER_STATE:
|
|
879
948
|
files.preparedLauncherState?.(snapshot) ?? "prior",
|
|
880
949
|
MIKIE_UPDATE_REPAIR_CANDIDATE: candidate.binary,
|
|
@@ -935,7 +1004,15 @@ export function createCommandAdapter({
|
|
|
935
1004
|
? environmentNpmCli
|
|
936
1005
|
: path.join(path.dirname(nodeExecutable), "node_modules", "npm", "bin", "npm-cli.js"));
|
|
937
1006
|
return {
|
|
938
|
-
run(
|
|
1007
|
+
run(
|
|
1008
|
+
executable,
|
|
1009
|
+
args,
|
|
1010
|
+
{
|
|
1011
|
+
shell = false,
|
|
1012
|
+
environment: commandEnvironment = {},
|
|
1013
|
+
timeoutMs = commandTimeoutMs,
|
|
1014
|
+
} = {},
|
|
1015
|
+
) {
|
|
939
1016
|
if (
|
|
940
1017
|
shell !== false ||
|
|
941
1018
|
!Array.isArray(args) ||
|
|
@@ -945,6 +1022,9 @@ export function createCommandAdapter({
|
|
|
945
1022
|
) {
|
|
946
1023
|
throw new Error("Mikie update commands require argument arrays with shell disabled");
|
|
947
1024
|
}
|
|
1025
|
+
if (!Number.isInteger(timeoutMs) || timeoutMs <= 0) {
|
|
1026
|
+
throw new Error("Mikie update command timeout is invalid");
|
|
1027
|
+
}
|
|
948
1028
|
return new Promise((resolve, reject) => {
|
|
949
1029
|
const stdout = [];
|
|
950
1030
|
const stderr = [];
|
|
@@ -970,7 +1050,7 @@ export function createCommandAdapter({
|
|
|
970
1050
|
// The child may already have exited.
|
|
971
1051
|
}
|
|
972
1052
|
reject(new Error("Mikie update command timed out"));
|
|
973
|
-
},
|
|
1053
|
+
}, timeoutMs);
|
|
974
1054
|
const finish = (callback) => {
|
|
975
1055
|
if (settled) return false;
|
|
976
1056
|
settled = true;
|
|
@@ -1041,11 +1121,19 @@ export function createRegistryAdapter({
|
|
|
1041
1121
|
return {
|
|
1042
1122
|
async resolveExactCompatibleVersion({ packageName, nodeVersion }) {
|
|
1043
1123
|
const deadline = now() + resolutionTimeoutMs;
|
|
1044
|
-
const
|
|
1045
|
-
|
|
1124
|
+
const runWithinDeadline = async (args) => {
|
|
1125
|
+
const timeoutMs = deadline - now();
|
|
1126
|
+
if (timeoutMs <= 0) return null;
|
|
1127
|
+
return commands.run(npmExecutable, args, {
|
|
1128
|
+
shell: false,
|
|
1129
|
+
capture: true,
|
|
1130
|
+
timeoutMs,
|
|
1131
|
+
});
|
|
1132
|
+
};
|
|
1133
|
+
const versionsResponse = await runWithinDeadline(
|
|
1046
1134
|
["view", packageName, "versions", "--json", "--registry", registryUrl],
|
|
1047
|
-
{ shell: false, capture: true },
|
|
1048
1135
|
);
|
|
1136
|
+
if (versionsResponse === null) return { public: false, compatible: false };
|
|
1049
1137
|
if (versionsResponse.code !== 0) return { public: false, compatible: false };
|
|
1050
1138
|
let published;
|
|
1051
1139
|
try {
|
|
@@ -1059,8 +1147,7 @@ export function createRegistryAdapter({
|
|
|
1059
1147
|
}
|
|
1060
1148
|
for (const version of published.slice(0, 64)) {
|
|
1061
1149
|
if (now() >= deadline) break;
|
|
1062
|
-
const response = await
|
|
1063
|
-
npmExecutable,
|
|
1150
|
+
const response = await runWithinDeadline(
|
|
1064
1151
|
[
|
|
1065
1152
|
"view",
|
|
1066
1153
|
`${packageName}@${version}`,
|
|
@@ -1070,8 +1157,8 @@ export function createRegistryAdapter({
|
|
|
1070
1157
|
"--registry",
|
|
1071
1158
|
registryUrl,
|
|
1072
1159
|
],
|
|
1073
|
-
{ shell: false, capture: true },
|
|
1074
1160
|
);
|
|
1161
|
+
if (response === null) break;
|
|
1075
1162
|
if (response.code !== 0) continue;
|
|
1076
1163
|
let metadata;
|
|
1077
1164
|
try {
|
|
@@ -1320,6 +1407,7 @@ export function createFilesystemAdapter({
|
|
|
1320
1407
|
prepareFinalLauncher,
|
|
1321
1408
|
retireFinalLauncher,
|
|
1322
1409
|
abandonFinalLauncher,
|
|
1410
|
+
supersedingInstallation,
|
|
1323
1411
|
}) {
|
|
1324
1412
|
const currentRoots = validateInstallationLayout(currentLayout);
|
|
1325
1413
|
const currentShims = currentRoots.shims;
|
|
@@ -1336,7 +1424,10 @@ export function createFilesystemAdapter({
|
|
|
1336
1424
|
(activationBoundary !== undefined && typeof activationBoundary !== "function") ||
|
|
1337
1425
|
(prepareFinalLauncher !== undefined && typeof prepareFinalLauncher !== "function") ||
|
|
1338
1426
|
(retireFinalLauncher !== undefined && typeof retireFinalLauncher !== "function") ||
|
|
1339
|
-
(abandonFinalLauncher !== undefined && typeof abandonFinalLauncher !== "function")
|
|
1427
|
+
(abandonFinalLauncher !== undefined && typeof abandonFinalLauncher !== "function") ||
|
|
1428
|
+
(supersedingInstallation !== undefined &&
|
|
1429
|
+
(!exactPublicVersion(supersedingInstallation?.version) ||
|
|
1430
|
+
!/^[a-f0-9]{64}$/.test(supersedingInstallation?.binarySha256 ?? "")))
|
|
1340
1431
|
) {
|
|
1341
1432
|
throw new Error("unsafe Mikie update filesystem path");
|
|
1342
1433
|
}
|
|
@@ -2341,6 +2432,86 @@ export function createFilesystemAdapter({
|
|
|
2341
2432
|
return owner?.activationState === "activated" ? "activated" : "prior";
|
|
2342
2433
|
};
|
|
2343
2434
|
|
|
2435
|
+
const committedActivationMatches = async (snapshot) => {
|
|
2436
|
+
for (const entry of snapshot.installation) {
|
|
2437
|
+
if (
|
|
2438
|
+
!entry.expectedFingerprint ||
|
|
2439
|
+
(await directoryFingerprint(entry.root)) !== entry.expectedFingerprint
|
|
2440
|
+
) {
|
|
2441
|
+
return false;
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
for (const entry of snapshot.shims) {
|
|
2445
|
+
if (
|
|
2446
|
+
!entry.expectedFingerprint ||
|
|
2447
|
+
(await readShimState(entry.path)).fingerprint !== entry.expectedFingerprint
|
|
2448
|
+
) {
|
|
2449
|
+
return false;
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
return true;
|
|
2453
|
+
};
|
|
2454
|
+
|
|
2455
|
+
const authenticateSupersedingInstallation = async () => {
|
|
2456
|
+
if (!supersedingInstallation) return false;
|
|
2457
|
+
try {
|
|
2458
|
+
const [wrapper, platform, binarySha256] = await Promise.all([
|
|
2459
|
+
readFile(currentLayout.wrapperPackageJson, "utf8"),
|
|
2460
|
+
readFile(currentLayout.platformPackageJson, "utf8"),
|
|
2461
|
+
executableDigest(currentLayout.binary),
|
|
2462
|
+
]);
|
|
2463
|
+
return (
|
|
2464
|
+
JSON.parse(wrapper)?.version === supersedingInstallation.version &&
|
|
2465
|
+
JSON.parse(platform)?.version === supersedingInstallation.version &&
|
|
2466
|
+
binarySha256 === supersedingInstallation.binarySha256
|
|
2467
|
+
);
|
|
2468
|
+
} catch {
|
|
2469
|
+
return false;
|
|
2470
|
+
}
|
|
2471
|
+
};
|
|
2472
|
+
|
|
2473
|
+
const cleanupSupersededCommittedActivation = async (snapshot) => {
|
|
2474
|
+
for (const entry of snapshot.installation) {
|
|
2475
|
+
const priorFingerprint = await directoryFingerprint(entry.priorQuarantine);
|
|
2476
|
+
const stagedFingerprint = await directoryFingerprint(entry.activationStaging);
|
|
2477
|
+
if (
|
|
2478
|
+
(priorFingerprint !== "absent" && priorFingerprint !== entry.sourceFingerprint) ||
|
|
2479
|
+
(stagedFingerprint !== "absent" && stagedFingerprint !== entry.expectedFingerprint)
|
|
2480
|
+
) {
|
|
2481
|
+
throw new Error("tampered Mikie superseded activation state");
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
for (const entry of snapshot.shims) {
|
|
2485
|
+
const prior = await readShimState(entry.priorQuarantine);
|
|
2486
|
+
const staged = await readShimState(entry.activationStaging);
|
|
2487
|
+
if (
|
|
2488
|
+
(prior.existed && prior.fingerprint !== entry.sourceFingerprint) ||
|
|
2489
|
+
(staged.existed && staged.fingerprint !== entry.expectedFingerprint)
|
|
2490
|
+
) {
|
|
2491
|
+
throw new Error("tampered Mikie superseded activation state");
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
for (const entry of snapshot.installation) {
|
|
2495
|
+
await rm(entry.priorQuarantine, { recursive: true, force: true });
|
|
2496
|
+
await rm(entry.activationStaging, { recursive: true, force: true });
|
|
2497
|
+
}
|
|
2498
|
+
for (const entry of snapshot.shims) {
|
|
2499
|
+
await rm(entry.priorQuarantine, { force: true });
|
|
2500
|
+
await rm(entry.activationStaging, { force: true });
|
|
2501
|
+
}
|
|
2502
|
+
};
|
|
2503
|
+
|
|
2504
|
+
const providerConfigurationMatchesSnapshot = async (snapshot) => {
|
|
2505
|
+
for (const entry of snapshot.providerConfig) {
|
|
2506
|
+
await assertNoLinkedAncestors(entry.path);
|
|
2507
|
+
const current = await readOptionalFile(entry.path);
|
|
2508
|
+
if (bytesFingerprint(current.existed, current.bytes) !== entry.sourceFingerprint) {
|
|
2509
|
+
return false;
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
return true;
|
|
2513
|
+
};
|
|
2514
|
+
|
|
2344
2515
|
const recoverInterrupted = async (lease, expectedJournalToken) => {
|
|
2345
2516
|
try {
|
|
2346
2517
|
const metadata = await lstat(transactionRoot);
|
|
@@ -2352,6 +2523,33 @@ export function createFilesystemAdapter({
|
|
|
2352
2523
|
throw error;
|
|
2353
2524
|
}
|
|
2354
2525
|
const snapshot = await hydrateJournal(lease, expectedJournalToken);
|
|
2526
|
+
if (snapshot.phase === "commit_decided_retain_runtime") {
|
|
2527
|
+
const activationMatches = await committedActivationMatches(snapshot);
|
|
2528
|
+
if (!activationMatches && (await authenticateSupersedingInstallation())) {
|
|
2529
|
+
await cleanupSupersededCommittedActivation(snapshot);
|
|
2530
|
+
await rm(transactionRoot, { recursive: true, force: true });
|
|
2531
|
+
return;
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
if (
|
|
2535
|
+
snapshot.phase === "commit_decided_retain_runtime" &&
|
|
2536
|
+
(await providerConfigurationMatchesSnapshot(snapshot))
|
|
2537
|
+
) {
|
|
2538
|
+
if (!reconcileTrajectoryStore) {
|
|
2539
|
+
throw new Error("trajectory-store reconciliation is unavailable");
|
|
2540
|
+
}
|
|
2541
|
+
await reconcileTrajectoryStore(snapshot.preparedCandidate.finalLauncher, snapshot);
|
|
2542
|
+
const activationMatches = await committedActivationMatches(snapshot);
|
|
2543
|
+
if (!activationMatches && !(await authenticateSupersedingInstallation())) {
|
|
2544
|
+
throw new Error("committed Mikie package changed before cleanup");
|
|
2545
|
+
}
|
|
2546
|
+
snapshot.phase = "committed_retain_runtime";
|
|
2547
|
+
await writeJournal(snapshot);
|
|
2548
|
+
if (activationMatches) await cleanupCommittedActivation(snapshot);
|
|
2549
|
+
else await cleanupSupersededCommittedActivation(snapshot);
|
|
2550
|
+
await rm(transactionRoot, { recursive: true, force: true });
|
|
2551
|
+
return;
|
|
2552
|
+
}
|
|
2355
2553
|
if (["commit_decided", "commit_decided_retain_runtime"].includes(snapshot.phase)) {
|
|
2356
2554
|
const retainHistoricalRuntimes = snapshot.phase === "commit_decided_retain_runtime";
|
|
2357
2555
|
if (!reconcileTrajectoryStore) {
|
|
@@ -2394,7 +2592,12 @@ export function createFilesystemAdapter({
|
|
|
2394
2592
|
return;
|
|
2395
2593
|
}
|
|
2396
2594
|
if (snapshot.phase === "committed_retain_runtime") {
|
|
2397
|
-
await
|
|
2595
|
+
const activationMatches = await committedActivationMatches(snapshot);
|
|
2596
|
+
if (!activationMatches && !(await authenticateSupersedingInstallation())) {
|
|
2597
|
+
throw new Error("committed Mikie package changed before cleanup");
|
|
2598
|
+
}
|
|
2599
|
+
if (activationMatches) await cleanupCommittedActivation(snapshot);
|
|
2600
|
+
else await cleanupSupersededCommittedActivation(snapshot);
|
|
2398
2601
|
await rm(transactionRoot, { recursive: true, force: true });
|
|
2399
2602
|
return;
|
|
2400
2603
|
}
|
|
@@ -3061,7 +3264,11 @@ export function createFilesystemAdapter({
|
|
|
3061
3264
|
return adapter;
|
|
3062
3265
|
}
|
|
3063
3266
|
|
|
3064
|
-
export async function recoverExternalNpmUpdate(
|
|
3267
|
+
export async function recoverExternalNpmUpdate(
|
|
3268
|
+
transactionRoot,
|
|
3269
|
+
expectedAuthority,
|
|
3270
|
+
{ supersedingInstallation } = {},
|
|
3271
|
+
) {
|
|
3065
3272
|
if (typeof transactionRoot !== "string" || !path.isAbsolute(transactionRoot)) {
|
|
3066
3273
|
throw new Error("unsafe Mikie update recovery root");
|
|
3067
3274
|
}
|
|
@@ -3154,6 +3361,7 @@ export async function recoverExternalNpmUpdate(transactionRoot, expectedAuthorit
|
|
|
3154
3361
|
},
|
|
3155
3362
|
providerPaths: descriptor.provider_paths,
|
|
3156
3363
|
transactionRoot,
|
|
3364
|
+
supersedingInstallation,
|
|
3157
3365
|
settleProviderRepair: async (phase, snapshot) => {
|
|
3158
3366
|
if (
|
|
3159
3367
|
snapshot?.preparedCandidate?.binary !== descriptor.candidate_binary ||
|
|
@@ -3171,6 +3379,7 @@ export async function recoverExternalNpmUpdate(transactionRoot, expectedAuthorit
|
|
|
3171
3379
|
MIKIE_DISTRIBUTION_MODE: "npm",
|
|
3172
3380
|
MIKIE_NPM_UPDATE_TOPOLOGY: UpdateTopology.NpmGlobal,
|
|
3173
3381
|
MIKIE_NPM_UPDATE_REPAIR_PHASE: phase,
|
|
3382
|
+
MIKIE_NPM_CLIENT_REPAIR_SCHEMA: REPAIR_SCHEMA,
|
|
3174
3383
|
MIKIE_UPDATE_REPAIR_CANDIDATE: descriptor.candidate_binary,
|
|
3175
3384
|
MIKIE_UPDATE_REPAIR_LAUNCHER: descriptor.final_launcher,
|
|
3176
3385
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getmikie/cli",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.114",
|
|
4
4
|
"description": "Native Mikie relay launcher for MCP clients",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -24,10 +24,10 @@
|
|
|
24
24
|
"node": ">=22"
|
|
25
25
|
},
|
|
26
26
|
"optionalDependencies": {
|
|
27
|
-
"@getmikie/darwin-arm64": "1.1.
|
|
28
|
-
"@getmikie/darwin-x64": "1.1.
|
|
29
|
-
"@getmikie/linux-x64-gnu": "1.1.
|
|
30
|
-
"@getmikie/win32-x64": "1.1.
|
|
27
|
+
"@getmikie/darwin-arm64": "1.1.114",
|
|
28
|
+
"@getmikie/darwin-x64": "1.1.114",
|
|
29
|
+
"@getmikie/linux-x64-gnu": "1.1.114",
|
|
30
|
+
"@getmikie/win32-x64": "1.1.114"
|
|
31
31
|
},
|
|
32
32
|
"publishConfig": {
|
|
33
33
|
"access": "public",
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
const RECONCILIATION_ARGUMENTS = ["reconcile-trajectory-store-v2", "--json"];
|
|
19
19
|
const CLIENT_REPAIR_ARGUMENTS = ["repair-claude-hooks-v1", "--json"];
|
|
20
20
|
const CLIENT_RECONCILIATION_SCHEMA = "mikie.npm-client-reconciliation.v2";
|
|
21
|
-
const CLIENT_REPAIR_SCHEMA = "mikie.claude-hook-repair.
|
|
21
|
+
const CLIENT_REPAIR_SCHEMA = "mikie.claude-hook-repair.v2";
|
|
22
22
|
const CLIENT_SETTLEMENT_SCHEMA = "mikie.claude-hook-repair-settlement.v1";
|
|
23
23
|
const TRANSACTION_TOKEN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
24
24
|
const PUBLIC_VERSION = /^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$/;
|
|
@@ -453,11 +453,12 @@ export async function reconcileInstalledClients({
|
|
|
453
453
|
preserveSha256,
|
|
454
454
|
}),
|
|
455
455
|
commands = createCommandAdapter({ environment, platform }),
|
|
456
|
+
allowDeferred = false,
|
|
456
457
|
} = {}) {
|
|
457
458
|
if (isTransactionStagedInstall(environment, platform)) {
|
|
458
459
|
return { status: "staged_candidate" };
|
|
459
460
|
}
|
|
460
|
-
if (!PUBLIC_VERSION.test(version ?? "")) {
|
|
461
|
+
if (!PUBLIC_VERSION.test(version ?? "") || typeof allowDeferred !== "boolean") {
|
|
461
462
|
throw new Error("installed client reconciliation authority is invalid");
|
|
462
463
|
}
|
|
463
464
|
|
|
@@ -513,7 +514,9 @@ export async function reconcileInstalledClients({
|
|
|
513
514
|
const preserved = explicitMarker
|
|
514
515
|
? [authority.runtime_sha256]
|
|
515
516
|
: await retainedRuntimeDigests(markerRoot, platform);
|
|
516
|
-
|
|
517
|
+
if (!allowDeferred) {
|
|
518
|
+
await retireLauncher(launcher, authority.runtime_sha256, preserved);
|
|
519
|
+
}
|
|
517
520
|
return { status: "current" };
|
|
518
521
|
}
|
|
519
522
|
const finalLauncher = await prepareLauncher(binary);
|
|
@@ -523,6 +526,7 @@ export async function reconcileInstalledClients({
|
|
|
523
526
|
const baseEnvironment = {
|
|
524
527
|
MIKIE_DISTRIBUTION_MODE: "npm",
|
|
525
528
|
MIKIE_NPM_UPDATE_TOPOLOGY: "npm_global",
|
|
529
|
+
MIKIE_NPM_CLIENT_REPAIR_SCHEMA: CLIENT_REPAIR_SCHEMA,
|
|
526
530
|
MIKIE_UPDATE_REPAIR_CANDIDATE: binary,
|
|
527
531
|
MIKIE_UPDATE_REPAIR_LAUNCHER: finalLauncher,
|
|
528
532
|
};
|
|
@@ -540,6 +544,9 @@ export async function reconcileInstalledClients({
|
|
|
540
544
|
});
|
|
541
545
|
parseClientSettlement(committed, settlementPhase);
|
|
542
546
|
if (preparedReceipt.action === "refused") {
|
|
547
|
+
if (allowDeferred) {
|
|
548
|
+
return { status: "deferred", providerState: preparedReceipt.before };
|
|
549
|
+
}
|
|
543
550
|
throw new Error(
|
|
544
551
|
"Mikie could not authenticate every configured client; close active clients and run `mikie update` before reinstalling",
|
|
545
552
|
);
|
|
@@ -554,7 +561,9 @@ export async function reconcileInstalledClients({
|
|
|
554
561
|
const preserved = explicitMarker
|
|
555
562
|
? [authority.runtime_sha256]
|
|
556
563
|
: await retainedRuntimeDigests(markerRoot, platform);
|
|
557
|
-
|
|
564
|
+
if (!allowDeferred) {
|
|
565
|
+
await retireLauncher(finalLauncher, authority.runtime_sha256, preserved);
|
|
566
|
+
}
|
|
558
567
|
return { status: "reconciled", finalLauncher };
|
|
559
568
|
}
|
|
560
569
|
|
|
@@ -566,7 +575,13 @@ if (scriptIdentity !== null && scriptIdentity === invokedIdentity) {
|
|
|
566
575
|
const packageMetadata = JSON.parse(
|
|
567
576
|
await readFile(fileURLToPath(new URL("../package.json", import.meta.url)), "utf8"),
|
|
568
577
|
);
|
|
569
|
-
await reconcileInstalledClients({
|
|
578
|
+
const clients = await reconcileInstalledClients({
|
|
579
|
+
version: packageMetadata.version,
|
|
580
|
+
allowDeferred: true,
|
|
581
|
+
});
|
|
582
|
+
if (clients.status === "deferred") {
|
|
583
|
+
console.warn("Mikie install client repair deferred; run `mikie setup` or `mikie doctor`.");
|
|
584
|
+
}
|
|
570
585
|
} catch (error) {
|
|
571
586
|
console.error(`Mikie install reconciliation failed: ${error.message}`);
|
|
572
587
|
process.exitCode = 1;
|