@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 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 -y @getmikie/cli mcp
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@latest --allow-scripts=@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. Do not shorten the
16
- command to `npm install -g --allow-scripts=@getmikie/cli`: without naming the
17
- package as the install target, that command incorrectly targets the current
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@latest --allow-scripts=@getmikie/cli
22
+ npm install --global @getmikie/cli@1.1.108 --ignore-scripts --registry=https://registry.npmjs.org
24
23
  ```
25
24
 
26
- On every platform, `--allow-scripts=@getmikie/cli` approves only Mikie's
27
- packaged install script; it does not approve unrelated package scripts.
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, deterministic verifier, exact model and reasoning
50
- effort, bounded model-turn timeout, and private temporary root before execution. The JSON result is
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 connect .
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
- const invocation = await externalRecoveryInvocation(transactionRoot);
87
- const recovered = await commands.run(invocation.executable, invocation.args, {
88
- shell: false,
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.v1";
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 = 30_000;
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
- !/^[a-f0-9]{64}$/.test(recovery.descriptor_sha256)
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 authenticatedRecoveryInvocation(transactionRoot, recovery.descriptor_sha256);
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.receipt.action === "migrated" ||
820
- files.requiresFinalLauncherSettlement?.() === true;
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
- : "migrated",
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(executable, args, { shell = false, environment: commandEnvironment = {} } = {}) {
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
- }, commandTimeoutMs);
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 versionsResponse = await commands.run(
1045
- npmExecutable,
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 commands.run(
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 cleanupCommittedActivation(snapshot);
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(transactionRoot, expectedAuthority) {
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.108",
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.108",
28
- "@getmikie/darwin-x64": "1.1.108",
29
- "@getmikie/linux-x64-gnu": "1.1.108",
30
- "@getmikie/win32-x64": "1.1.108"
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.v1";
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
- await retireLauncher(launcher, authority.runtime_sha256, preserved);
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
- await retireLauncher(finalLauncher, authority.runtime_sha256, preserved);
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({ version: packageMetadata.version });
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;