@getmikie/cli 1.1.120 → 1.1.123

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
@@ -9,7 +9,7 @@ npx --yes --ignore-scripts --registry=https://registry.npmjs.org @getmikie/cli@1
9
9
  For a persistent command path on Windows PowerShell:
10
10
 
11
11
  ```powershell
12
- npm.cmd install --global @getmikie/cli@1.1.108 --ignore-scripts --registry=https://registry.npmjs.org
12
+ npm.cmd install --global @getmikie/cli@1.1.108 --allow-scripts=@getmikie/cli --registry=https://registry.npmjs.org
13
13
  ```
14
14
 
15
15
  Mikie does not change your PowerShell execution policy. Keep the exact package
@@ -19,11 +19,13 @@ incorrectly targets the current directory.
19
19
  For a persistent command path on macOS or Linux:
20
20
 
21
21
  ```sh
22
- npm install --global @getmikie/cli@1.1.108 --ignore-scripts --registry=https://registry.npmjs.org
22
+ npm install --global @getmikie/cli@1.1.108 --allow-scripts=@getmikie/cli --registry=https://registry.npmjs.org
23
23
  ```
24
24
 
25
- These portable npm commands disable every lifecycle script and pin the public
26
- npm registry. The public OS installers additionally authenticate exact wrapper
25
+ These persistent npm commands allow only `@getmikie/cli`'s reconciliation
26
+ script and pin the public npm registry. npm 11.5.1 or newer is required for
27
+ `--allow-scripts`; the temporary `npx` command above keeps scripts disabled.
28
+ The public OS installers additionally authenticate exact wrapper
27
29
  and native tarball bytes, install them offline, and explicitly run only Mikie's
28
30
  authenticated reconciliation script.
29
31
 
package/bin/mikie.mjs CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  import { createRequire } from "node:module";
4
4
  import { homedir } from "node:os";
@@ -48,6 +48,7 @@ if (["--version", "-v"].includes(singleFlag)) {
48
48
  console.log(`Mikie ${packageMetadata.version}
49
49
 
50
50
  Usage:
51
+ mikie activity --mode solo|ai_assisted|unknown --language <category>
51
52
  mikie savings history [options]
52
53
  mikie savings export-meter-history
53
54
  mikie mcp [--adapter codex|claude_code]
@@ -92,7 +93,7 @@ All commands execute the compiled Mikie native engine.`);
92
93
  const selected = selectPlatformPackage();
93
94
  const platformPackageJson = require.resolve(`${selected.packageName}/package.json`);
94
95
  const wrapperPackageJson = fileURLToPath(new URL("../package.json", import.meta.url));
95
- const binary = await resolveNativeBinary();
96
+ const binary = await resolveNativeBinary({ version: packageMetadata.version });
96
97
  const globalPrefix = npmPrefix.stdout.trim();
97
98
  const globalShims =
98
99
  process.platform === "win32"
@@ -150,7 +151,7 @@ All commands execute the compiled Mikie native engine.`);
150
151
  return {
151
152
  wrapperPackageJson,
152
153
  platformPackageJson: require.resolve(`${selected.packageName}/package.json`),
153
- binary: await resolveNativeBinary(),
154
+ binary: await resolveNativeBinary({ version: packageMetadata.version }),
154
155
  shims: globalShims,
155
156
  };
156
157
  },
@@ -160,16 +161,20 @@ All commands execute the compiled Mikie native engine.`);
160
161
  ownership: path.join(customerHome, ".claude", "mikie-evidence-hook-ownership.json"),
161
162
  },
162
163
  transactionRoot,
163
- prepareFinalLauncher: (candidate, version) =>
164
- process.platform === "win32"
164
+ prepareFinalLauncher: async (candidate, version) => {
165
+ const preserveSha256 = await installedClientRuntimeDigests();
166
+ return process.platform === "win32"
165
167
  ? materializeWindowsRuntime(candidate.binary, {
166
168
  version,
167
169
  cleanupHistorical: false,
170
+ preserveSha256,
168
171
  })
169
172
  : materializePortableRuntime(candidate.binary, {
170
173
  version,
171
174
  cleanupHistorical: false,
172
- }),
175
+ preserveSha256,
176
+ });
177
+ },
173
178
  retireFinalLauncher: async ({ path: launcher, sha256 }) => {
174
179
  const preserveSha256 = await installedClientRuntimeDigests();
175
180
  return process.platform === "win32"
@@ -239,19 +244,31 @@ All commands execute the compiled Mikie native engine.`);
239
244
  }
240
245
  } catch (error) {
241
246
  const message = error instanceof Error ? error.message : "Mikie update failed";
242
- console.error(`mikie: ${message}`);
247
+ const guidance = message === "invalid Mikie update recovery authority"
248
+ ? "; keep the recovery files and contact support before retrying"
249
+ : "";
250
+ console.error(`mikie: ${message}${guidance}`);
243
251
  process.exitCode = 1;
244
252
  }
245
253
  } else {
246
254
  try {
247
255
  const nativeArguments = translateArguments(arguments_);
248
256
  if (shouldReconcileInstalledClients(nativeArguments)) {
249
- await reconcileInstalledClients({ version: packageMetadata.version });
257
+ const clients = await reconcileInstalledClients({
258
+ version: packageMetadata.version,
259
+ allowDeferred: nativeArguments[0] === "serve",
260
+ });
261
+ if (clients.status === "deferred") {
262
+ console.error("mikie: client repair deferred; relay startup continues. Run `mikie doctor` to inspect client registration.");
263
+ }
250
264
  }
251
265
  const binary = await prepareNativeLaunch(await resolveNativeBinary(), nativeArguments, {
252
266
  version: packageMetadata.version,
253
267
  });
254
268
  process.exitCode = await launchNative(binary, nativeArguments);
269
+ if (nativeArguments[0] === "serve" && process.exitCode !== 0) {
270
+ console.error(`mikie: Mikie relay exited with status ${process.exitCode}; run \`mikie doctor\`, then retry the MCP session`);
271
+ }
255
272
  } catch (error) {
256
273
  const message = error instanceof Error ? error.message : "Mikie is unavailable";
257
274
  console.error(`mikie: ${message}`);
package/lib/launcher.mjs CHANGED
@@ -20,7 +20,6 @@ import { spawn } from "node:child_process";
20
20
  const PLATFORM_PACKAGES = new Map([
21
21
  ["win32-x64", { packageName: "@getmikie/win32-x64", binaryPath: "bin/mikie.exe" }],
22
22
  ["darwin-arm64", { packageName: "@getmikie/darwin-arm64", binaryPath: "bin/mikie" }],
23
- ["darwin-x64", { packageName: "@getmikie/darwin-x64", binaryPath: "bin/mikie" }],
24
23
  ["linux-x64-gnu", { packageName: "@getmikie/linux-x64-gnu", binaryPath: "bin/mikie" }],
25
24
  ]);
26
25
 
@@ -68,13 +67,7 @@ async function cleanupHistoricalWindowsRuntimes(
68
67
  selectedDirectory,
69
68
  { graceMs = HISTORICAL_RUNTIME_GRACE_MS, preserveSha256 = [] } = {},
70
69
  ) {
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
- }
70
+ validateRuntimePreservation(preserveSha256);
78
71
  const preserved = new Set(preserveSha256);
79
72
  let entries;
80
73
  try {
@@ -104,6 +97,68 @@ async function cleanupHistoricalWindowsRuntimes(
104
97
  );
105
98
  }
106
99
 
100
+ function validateRuntimePreservation(preserveSha256) {
101
+ if (
102
+ !Array.isArray(preserveSha256) ||
103
+ preserveSha256.length > MAX_MATERIALIZED_WINDOWS_RUNTIMES ||
104
+ preserveSha256.some((digest) => !/^[0-9a-f]{64}$/.test(digest))
105
+ ) {
106
+ throw new Error("Mikie cached runtime preservation authority is invalid");
107
+ }
108
+ }
109
+
110
+ async function evictStaleUnreferencedWindowsRuntimes(
111
+ runtimeRoot,
112
+ selectedDirectory,
113
+ preserveSha256,
114
+ ) {
115
+ validateRuntimePreservation(preserveSha256);
116
+ let entries;
117
+ try {
118
+ entries = await readdir(runtimeRoot, { withFileTypes: true });
119
+ } catch {
120
+ return;
121
+ }
122
+ const materialized = entries.filter(
123
+ (entry) => entry.isDirectory() && SAFE_RUNTIME_DIRECTORY.test(entry.name),
124
+ );
125
+ const preserved = new Set(preserveSha256);
126
+ const cutoff = Date.now() - HISTORICAL_RUNTIME_GRACE_MS;
127
+ const candidates = [];
128
+ for (const entry of materialized) {
129
+ if (
130
+ entry.name === selectedDirectory ||
131
+ [...preserved].some((digest) => entry.name.endsWith(`-${digest}`))
132
+ ) {
133
+ continue;
134
+ }
135
+ const candidate = path.join(runtimeRoot, entry.name);
136
+ try {
137
+ const metadata = await lstat(candidate);
138
+ if (metadata.mtimeMs <= cutoff) {
139
+ candidates.push({ candidate, mtimeMs: metadata.mtimeMs });
140
+ }
141
+ } catch {
142
+ // A concurrent removal is already freeing capacity; re-count below.
143
+ }
144
+ }
145
+ candidates.sort((left, right) => left.mtimeMs - right.mtimeMs);
146
+ const required = Math.max(
147
+ 0,
148
+ materialized.length - (MAX_MATERIALIZED_WINDOWS_RUNTIMES - 1),
149
+ );
150
+ let removed = 0;
151
+ for (const { candidate } of candidates) {
152
+ if (removed >= required) break;
153
+ try {
154
+ await rm(candidate, { recursive: true, force: true, maxRetries: 0 });
155
+ removed += 1;
156
+ } catch {
157
+ // Active Windows executables remain locked and are retained safely.
158
+ }
159
+ }
160
+ }
161
+
107
162
  function abandonmentMarkerForExecutable(
108
163
  runtimeRoot,
109
164
  selectedDirectory,
@@ -246,7 +301,12 @@ async function scavengeAbandonedWindowsRuntimes(runtimeRoot) {
246
301
  }
247
302
  }
248
303
 
249
- async function enforceMaterializedRuntimeCapacity(runtimeRoot, selectedDirectory) {
304
+ async function enforceMaterializedRuntimeCapacity(
305
+ runtimeRoot,
306
+ selectedDirectory,
307
+ { preserveSha256 = [] } = {},
308
+ ) {
309
+ validateRuntimePreservation(preserveSha256);
250
310
  await scavengeAbandonedWindowsRuntimes(runtimeRoot);
251
311
  let entries;
252
312
  try {
@@ -264,6 +324,25 @@ async function enforceMaterializedRuntimeCapacity(runtimeRoot, selectedDirectory
264
324
  ) {
265
325
  return;
266
326
  }
327
+ await evictStaleUnreferencedWindowsRuntimes(
328
+ runtimeRoot,
329
+ selectedDirectory,
330
+ preserveSha256,
331
+ );
332
+ let remaining;
333
+ try {
334
+ remaining = (await readdir(runtimeRoot, { withFileTypes: true })).filter(
335
+ (entry) => entry.isDirectory() && SAFE_RUNTIME_DIRECTORY.test(entry.name),
336
+ );
337
+ } catch {
338
+ remaining = materialized;
339
+ }
340
+ if (
341
+ remaining.some((entry) => entry.name === selectedDirectory) ||
342
+ remaining.length < MAX_MATERIALIZED_WINDOWS_RUNTIMES
343
+ ) {
344
+ return;
345
+ }
267
346
  throw new Error(
268
347
  "Mikie cached Windows runtime retention capacity is full; reconcile exact-owned clients before installing another runtime",
269
348
  );
@@ -412,7 +491,7 @@ export async function retireHistoricalRuntimes(
412
491
 
413
492
  export function shouldStageWindowsRuntime(arguments_) {
414
493
  if (arguments_.length === 0) return true;
415
- if (["mcp", "serve", "run-relay", "doctor"].includes(arguments_[0])) return true;
494
+ if (["mcp", "serve", "run-relay"].includes(arguments_[0])) return true;
416
495
  if (arguments_.length === 1 && arguments_[0] === "uninstall") return true;
417
496
  if (
418
497
  arguments_.length === 2 &&
@@ -421,6 +500,17 @@ export function shouldStageWindowsRuntime(arguments_) {
421
500
  ) {
422
501
  return true;
423
502
  }
503
+ if (arguments_[0] === "mode" && arguments_[1] === "codex") {
504
+ const modeArguments = arguments_.slice(2);
505
+ return (
506
+ modeArguments.length === 0 ||
507
+ (modeArguments.length === 1 &&
508
+ ["active", "shadow", "--json"].includes(modeArguments[0])) ||
509
+ (modeArguments.length === 2 &&
510
+ ["active", "shadow"].includes(modeArguments[0]) &&
511
+ modeArguments[1] === "--json")
512
+ );
513
+ }
424
514
  return arguments_[0] === "control" && arguments_[1] === "run";
425
515
  }
426
516
 
@@ -431,7 +521,7 @@ export function shouldReconcileInstalledClients(arguments_) {
431
521
 
432
522
  async function materializeRuntime(
433
523
  binary,
434
- { version, runtimeRoot, executableName, cleanupHistorical },
524
+ { version, runtimeRoot, executableName, cleanupHistorical, preserveSha256 },
435
525
  ) {
436
526
  const sourceMetadata = await lstat(binary);
437
527
  if (!sourceMetadata.isFile() || sourceMetadata.isSymbolicLink() || sourceMetadata.size === 0) {
@@ -441,11 +531,15 @@ async function materializeRuntime(
441
531
  const selectedDirectory = `${version}-${sourceSha256}`;
442
532
  const runtimeDirectory = path.join(runtimeRoot, selectedDirectory);
443
533
  const materialized = path.join(runtimeDirectory, executableName);
444
- await enforceMaterializedRuntimeCapacity(runtimeRoot, selectedDirectory);
534
+ await enforceMaterializedRuntimeCapacity(runtimeRoot, selectedDirectory, {
535
+ preserveSha256,
536
+ });
445
537
  await mkdir(runtimeDirectory, { recursive: true });
446
538
  if (await verifyMaterializedRuntime(materialized, sourceSha256, { allowMissing: true })) {
447
539
  if (cleanupHistorical) {
448
- await cleanupHistoricalWindowsRuntimes(runtimeRoot, selectedDirectory);
540
+ await cleanupHistoricalWindowsRuntimes(runtimeRoot, selectedDirectory, {
541
+ preserveSha256,
542
+ });
449
543
  }
450
544
  return materialized;
451
545
  }
@@ -475,7 +569,9 @@ async function materializeRuntime(
475
569
  }
476
570
  await verifyMaterializedRuntime(materialized, sourceSha256);
477
571
  if (cleanupHistorical) {
478
- await cleanupHistoricalWindowsRuntimes(runtimeRoot, selectedDirectory);
572
+ await cleanupHistoricalWindowsRuntimes(runtimeRoot, selectedDirectory, {
573
+ preserveSha256,
574
+ });
479
575
  }
480
576
  return materialized;
481
577
  } finally {
@@ -489,6 +585,7 @@ export async function materializeWindowsRuntime(
489
585
  version,
490
586
  localAppData = process.env.LOCALAPPDATA,
491
587
  cleanupHistorical = true,
588
+ preserveSha256 = [],
492
589
  } = {},
493
590
  ) {
494
591
  if (
@@ -503,6 +600,7 @@ export async function materializeWindowsRuntime(
503
600
  runtimeRoot: path.join(localAppData, "Mikie", "runtime-v1"),
504
601
  executableName: "mikie.exe",
505
602
  cleanupHistorical,
603
+ preserveSha256,
506
604
  });
507
605
  }
508
606
 
@@ -553,6 +651,7 @@ export async function materializePortableRuntime(
553
651
  platform = process.platform,
554
652
  environment = process.env,
555
653
  cleanupHistorical = true,
654
+ preserveSha256 = [],
556
655
  } = {},
557
656
  ) {
558
657
  const runtimeRoot = portableRuntimeRoot(platform, environment);
@@ -568,6 +667,7 @@ export async function materializePortableRuntime(
568
667
  version,
569
668
  localAppData: environment.LOCALAPPDATA,
570
669
  cleanupHistorical,
670
+ preserveSha256,
571
671
  });
572
672
  }
573
673
  return materializeRuntime(binary, {
@@ -575,9 +675,46 @@ export async function materializePortableRuntime(
575
675
  runtimeRoot,
576
676
  executableName: "mikie",
577
677
  cleanupHistorical,
678
+ preserveSha256,
578
679
  });
579
680
  }
580
681
 
682
+ /** Inspect the existing pinned runtime without creating or reconciling any state. */
683
+ async function inspectMaterializedRuntime(binary, { platform, version, environment }) {
684
+ if (!path.isAbsolute(binary)) {
685
+ throw new Error("Mikie packaged runtime authority is invalid");
686
+ }
687
+ const sourceMetadata = await lstat(binary);
688
+ if (!sourceMetadata.isFile() || sourceMetadata.isSymbolicLink() || sourceMetadata.size === 0) {
689
+ throw new Error("Mikie packaged runtime is invalid; reinstall the CLI before retrying doctor");
690
+ }
691
+ const sourceSha256 = await sha256File(binary);
692
+ const selected = materializedRuntimePath({ version, sha256: sourceSha256, platform, environment });
693
+ const repair = "run `mikie update`; if it cannot restore the runtime, contact support";
694
+ try {
695
+ for (const directory of [path.dirname(path.dirname(selected)), path.dirname(selected)]) {
696
+ const metadata = await lstat(directory);
697
+ if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
698
+ throw new Error("invalid runtime directory");
699
+ }
700
+ }
701
+ await verifyMaterializedRuntime(selected, sourceSha256);
702
+ if (platform !== "win32") await access(selected, fsConstants.X_OK);
703
+ } catch (error) {
704
+ if (error?.code === "ENOENT") {
705
+ throw new Error("Mikie installed runtime is missing; run `mikie setup codex` or `mikie setup claude` for your configured client, then retry `mikie doctor`");
706
+ }
707
+ if (error?.message === "Mikie cached Windows runtime hash does not match") {
708
+ throw new Error(`Mikie installed runtime does not match the packaged engine; ${repair}`);
709
+ }
710
+ throw new Error(`Mikie installed runtime is invalid or unreadable; ${repair}`);
711
+ }
712
+ if ((await sha256File(binary)) !== sourceSha256) {
713
+ throw new Error("Mikie packaged runtime changed during doctor inspection; retry after installation finishes");
714
+ }
715
+ return selected;
716
+ }
717
+
581
718
  export async function prepareNativeLaunch(
582
719
  binary,
583
720
  arguments_,
@@ -585,8 +722,12 @@ export async function prepareNativeLaunch(
585
722
  platform = process.platform,
586
723
  version,
587
724
  environment = process.env,
725
+ preserveSha256 = [],
588
726
  } = {},
589
727
  ) {
728
+ if (arguments_[0] === "doctor") {
729
+ return inspectMaterializedRuntime(binary, { platform, version, environment });
730
+ }
590
731
  if (!shouldStageWindowsRuntime(arguments_)) {
591
732
  return binary;
592
733
  }
@@ -595,6 +736,7 @@ export async function prepareNativeLaunch(
595
736
  platform,
596
737
  environment,
597
738
  cleanupHistorical: false,
739
+ preserveSha256,
598
740
  });
599
741
  }
600
742
 
@@ -614,6 +756,12 @@ export function selectPlatformPackage({
614
756
  } = {}) {
615
757
  const key = platform === "linux" && glibc ? `${platform}-${arch}-gnu` : `${platform}-${arch}`;
616
758
  const selected = PLATFORM_PACKAGES.get(key);
759
+ if (!selected && key === "darwin-x64") {
760
+ throw new Error(
761
+ "Mikie no longer supports Intel Macs. The last version for this Mac is 1.1.121: " +
762
+ "npm install --global @getmikie/cli@1.1.121",
763
+ );
764
+ }
617
765
  if (!selected) {
618
766
  throw new Error("Mikie does not publish a native npm engine for this platform");
619
767
  }
@@ -645,6 +793,7 @@ export async function resolveNativeBinary({
645
793
  arch = process.arch,
646
794
  glibc,
647
795
  resolvePackage,
796
+ version,
648
797
  } = {}) {
649
798
  const selected = selectPlatformPackage({ platform, arch, glibc });
650
799
  const require = createRequire(import.meta.url);
@@ -657,6 +806,27 @@ export async function resolveNativeBinary({
657
806
  throw new Error(`Required Mikie native package ${selected.packageName} is unavailable`);
658
807
  }
659
808
 
809
+ let metadata;
810
+ try {
811
+ const handle = await open(packageJson, "r");
812
+ try {
813
+ const bytes = Buffer.alloc(65537);
814
+ const { bytesRead } = await handle.read(bytes, 0, bytes.length, 0);
815
+ if (bytesRead > 65536) throw new Error("oversized metadata");
816
+ metadata = JSON.parse(bytes.subarray(0, bytesRead).toString("utf8"));
817
+ } finally {
818
+ await handle.close();
819
+ }
820
+ if (metadata?.name !== selected.packageName) throw new Error("wrong package identity");
821
+ } catch {
822
+ throw new Error(`Required Mikie native package ${selected.packageName} metadata is invalid; reinstall the CLI`);
823
+ }
824
+ const wrapperVersion = version ?? require("../package.json").version;
825
+ if (typeof wrapperVersion !== "string" || !SAFE_RUNTIME_VERSION.test(wrapperVersion)
826
+ || metadata.version !== wrapperVersion) {
827
+ throw new Error("Mikie wrapper and native package versions do not match; reinstall the CLI before retrying");
828
+ }
829
+
660
830
  const binary = path.join(path.dirname(packageJson), ...selected.binaryPath.split("/"));
661
831
  try {
662
832
  const metadata = await stat(binary);
package/lib/update.mjs CHANGED
@@ -611,6 +611,38 @@ function reportedForeignSettings(registration, settings, candidateBinary) {
611
611
  return reported;
612
612
  }
613
613
 
614
+ function snapshotHasNoClaudeOwnedState(snapshot) {
615
+ const providers = new Map(snapshot.providerConfig.map((entry) => [entry.name, entry]));
616
+ const registration = parseOptionalJson(providers.get("registration"));
617
+ const settings = parseOptionalJson(providers.get("settings"));
618
+ if (providers.get("ownership")?.existed) return false;
619
+ if (
620
+ registration?.mcpServers &&
621
+ typeof registration.mcpServers === "object" &&
622
+ !Array.isArray(registration.mcpServers) &&
623
+ Object.hasOwn(registration.mcpServers, "mikie")
624
+ ) {
625
+ return false;
626
+ }
627
+ const hooks = settings?.hooks;
628
+ if (!hooks || typeof hooks !== "object" || Array.isArray(hooks)) return true;
629
+ return !Object.entries(hooks).some(([wireEvent, groups]) =>
630
+ Array.isArray(groups) &&
631
+ groups.some((group) =>
632
+ Array.isArray(group?.hooks) &&
633
+ group.hooks.some((handler) => {
634
+ if (handler?.type !== "command" || !Array.isArray(handler.args)) return false;
635
+ const commandEvent = CLAUDE_HOOK_EVENTS.find(([event]) => event === wireEvent)?.[1];
636
+ return (
637
+ (commandEvent && isDeepStrictEqual(handler.args, ["run-claude-hook", commandEvent])) ||
638
+ (["Stop", "StopFailure"].includes(wireEvent) &&
639
+ isDeepStrictEqual(handler.args, ["evidence", "ingest", "--client", "claude"]))
640
+ );
641
+ }),
642
+ ),
643
+ );
644
+ }
645
+
614
646
  export function verifyClaudeV2Contract({
615
647
  candidateBinary,
616
648
  registration,
@@ -826,13 +858,16 @@ export async function runUpdateTransaction({
826
858
  throw new Error("candidate package version verification failed");
827
859
  }
828
860
  finalLauncher = await files.finalLauncher(candidate, resolved.version);
829
- const externalRecovery = await files.recordPreparedCandidate(
861
+ await files.recordPreparedCandidate(
830
862
  snapshot,
831
863
  candidate,
832
864
  finalLauncher,
833
865
  );
834
- recovery = externalRecovery;
835
- output.writeLine(`Recovery: ${externalRecovery}`);
866
+ // The authenticated external recovery program remains in the transaction
867
+ // journal. The installed entrypoint loads it before resolving a runtime;
868
+ // its internal Node/bootstrap payload is not a customer-facing command.
869
+ recovery = "mikie update";
870
+ output.writeLine(`Recovery: ${recovery}`);
836
871
  await files.markProviderPreparing(snapshot);
837
872
  providerPrepared = true;
838
873
 
@@ -886,7 +921,7 @@ export async function runUpdateTransaction({
886
921
  (repairResult.receipt.action === "migrated" ||
887
922
  files.requiresFinalLauncherSettlement?.() === true);
888
923
  // A skipped receipt does not prove that no client still references a
889
- // historical launcher. In particular, Claude can report unavailable
924
+ // historical launcher. In particular, Claude reports foreign_conflict
890
925
  // when hooks/ownership are absent while an MCP-only registration remains.
891
926
  // Retain until a later authenticated reconciliation proves both surfaces
892
927
  // absent/current instead of deleting a possibly referenced runtime.
@@ -3211,10 +3246,14 @@ export function createFilesystemAdapter({
3211
3246
  }
3212
3247
  },
3213
3248
  async verifyClaudeV2({ candidate, snapshot, repair }) {
3214
- if (
3249
+ const refused =
3215
3250
  repair?.action === "refused" &&
3216
- ["foreign_conflict", "unavailable"].includes(repair.before)
3217
- ) {
3251
+ ["foreign_conflict", "unavailable"].includes(repair.before);
3252
+ const provenNoClaudeState =
3253
+ snapshotHasNoClaudeOwnedState(snapshot) &&
3254
+ ((repair?.action === "not_configured" && repair.before === "absent") ||
3255
+ (repair?.action === "already_current" && repair.before === "current"));
3256
+ if (refused || provenNoClaudeState) {
3218
3257
  const after = await Promise.all(
3219
3258
  snapshot.providerConfig.map(async (entry) => ({
3220
3259
  ...entry,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getmikie/cli",
3
- "version": "1.1.120",
3
+ "version": "1.1.123",
4
4
  "description": "Native Mikie relay launcher for MCP clients",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -24,10 +24,9 @@
24
24
  "node": ">=22"
25
25
  },
26
26
  "optionalDependencies": {
27
- "@getmikie/darwin-arm64": "1.1.120",
28
- "@getmikie/darwin-x64": "1.1.120",
29
- "@getmikie/linux-x64-gnu": "1.1.120",
30
- "@getmikie/win32-x64": "1.1.120"
27
+ "@getmikie/darwin-arm64": "1.1.123",
28
+ "@getmikie/linux-x64-gnu": "1.1.123",
29
+ "@getmikie/win32-x64": "1.1.123"
31
30
  },
32
31
  "publishConfig": {
33
32
  "access": "public",
@@ -456,8 +456,7 @@ export async function reconcileInstalledClients({
456
456
  version,
457
457
  markerPath,
458
458
  resolveBinary = resolveNativeBinary,
459
- prepareLauncher = (binary) =>
460
- prepareNativeLaunch(binary, ["setup", "codex"], { platform, version, environment }),
459
+ prepareLauncher,
461
460
  retireLauncher = (launcher, runtimeSha256, preserveSha256) =>
462
461
  retireHistoricalRuntimes(launcher, {
463
462
  expectedSha256: runtimeSha256,
@@ -487,11 +486,21 @@ export async function reconcileInstalledClients({
487
486
  throw new Error("installed client reconciliation authority is invalid");
488
487
  }
489
488
  const markerRoot = path.dirname(scopedMarker);
489
+ const preservedRuntimeSha256 = explicitMarker
490
+ ? []
491
+ : await retainedRuntimeDigests(markerRoot, platform);
492
+ const prepareLauncherForInstall = prepareLauncher ?? ((candidate) =>
493
+ prepareNativeLaunch(candidate, ["setup", "codex"], {
494
+ platform,
495
+ version,
496
+ environment,
497
+ preserveSha256: preservedRuntimeSha256,
498
+ }));
490
499
  if (!explicitMarker) {
491
500
  await retainedRuntimeDigests(markerRoot, platform, {
492
- replacementName: path.basename(scopedMarker),
493
- replacementSha256: authority.runtime_sha256,
494
- });
501
+ replacementName: path.basename(scopedMarker),
502
+ replacementSha256: authority.runtime_sha256,
503
+ });
495
504
  }
496
505
  if (await readClientMarker(scopedMarker, authority)) {
497
506
  const expectedLauncher = materializedRuntimePath({
@@ -510,7 +519,7 @@ export async function reconcileInstalledClients({
510
519
  if ((await sha256File(binary)) !== initialRuntimeSha256) {
511
520
  throw new Error("installed Mikie runtime changed during client reconciliation");
512
521
  }
513
- launcher = await prepareLauncher(binary);
522
+ launcher = await prepareLauncherForInstall(binary);
514
523
  if (
515
524
  pathIdentity(launcher, platform) !== pathIdentity(expectedLauncher, platform) ||
516
525
  (await sha256File(binary)) !== initialRuntimeSha256 ||
@@ -530,7 +539,7 @@ export async function reconcileInstalledClients({
530
539
  }
531
540
  return { status: "current" };
532
541
  }
533
- const finalLauncher = await prepareLauncher(binary);
542
+ const finalLauncher = await prepareLauncherForInstall(binary);
534
543
  if (![binary, finalLauncher].every((value) => typeof value === "string" && path.isAbsolute(value))) {
535
544
  throw new Error("installed client reconciliation authority is invalid");
536
545
  }