@getmikie/cli 1.1.135 → 1.1.136
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/mikie.mjs +16 -0
- package/package.json +5 -5
- package/scripts/reconcile-install.mjs +105 -4
package/bin/mikie.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
3
4
|
import { createRequire } from "node:module";
|
|
4
5
|
import { homedir } from "node:os";
|
|
5
6
|
import { access, mkdir, writeFile } from "node:fs/promises";
|
|
@@ -37,6 +38,7 @@ import {
|
|
|
37
38
|
setAsideUpdateTransaction,
|
|
38
39
|
} from "../lib/update.mjs";
|
|
39
40
|
import {
|
|
41
|
+
clientReconciliationLine,
|
|
40
42
|
completeSkippedInstall,
|
|
41
43
|
installedClientRuntimeDigests,
|
|
42
44
|
reconcileInstalledClients,
|
|
@@ -335,6 +337,18 @@ Account:
|
|
|
335
337
|
} else if (result.status === "reconciled") {
|
|
336
338
|
console.log(`Mikie is up to date (${result.version})`);
|
|
337
339
|
}
|
|
340
|
+
if (result.status === "updated" || result.status === "reconciled") {
|
|
341
|
+
// Bring the clients already set up to the installed runtime now,
|
|
342
|
+
// through the installed package's own reconciliation (the files on
|
|
343
|
+
// disk are the new version's, unlike the modules this process loaded).
|
|
344
|
+
// Without it Codex and Claude Code stayed on the old runtime until
|
|
345
|
+
// some later command happened to finish the install.
|
|
346
|
+
spawnSync(
|
|
347
|
+
process.execPath,
|
|
348
|
+
[fileURLToPath(new URL("../scripts/reconcile-install.mjs", import.meta.url))],
|
|
349
|
+
{ stdio: "inherit", windowsHide: true },
|
|
350
|
+
);
|
|
351
|
+
}
|
|
338
352
|
} catch (error) {
|
|
339
353
|
const message = error instanceof Error ? error.message : "Mikie update failed";
|
|
340
354
|
for (const line of updateFailureLines(message, error?.recovery)) console.error(line);
|
|
@@ -358,6 +372,8 @@ Account:
|
|
|
358
372
|
const clients = await completeSkippedInstall({ version: packageMetadata.version });
|
|
359
373
|
if (clients.status === "deferred") {
|
|
360
374
|
await reportDeferredOnce(packageMetadata.version);
|
|
375
|
+
} else if (clients.status === "reconciled_by_setup") {
|
|
376
|
+
console.error(clientReconciliationLine(clients));
|
|
361
377
|
}
|
|
362
378
|
} catch (error) {
|
|
363
379
|
const reason = error instanceof Error ? error.message : "unavailable";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getmikie/cli",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.136",
|
|
4
4
|
"description": "Native Mikie relay launcher for MCP clients",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -21,13 +21,13 @@
|
|
|
21
21
|
"node": ">=22"
|
|
22
22
|
},
|
|
23
23
|
"optionalDependencies": {
|
|
24
|
-
"@getmikie/darwin-arm64": "1.1.
|
|
25
|
-
"@getmikie/linux-x64-gnu": "1.1.
|
|
26
|
-
"@getmikie/win32-x64": "1.1.
|
|
24
|
+
"@getmikie/darwin-arm64": "1.1.136",
|
|
25
|
+
"@getmikie/linux-x64-gnu": "1.1.136",
|
|
26
|
+
"@getmikie/win32-x64": "1.1.136"
|
|
27
27
|
},
|
|
28
28
|
"publishConfig": {
|
|
29
29
|
"access": "public",
|
|
30
30
|
"registry": "https://registry.npmjs.org"
|
|
31
31
|
},
|
|
32
|
-
"mikieReleaseNote": "
|
|
32
|
+
"mikieReleaseNote": "mikie update now moves Codex and Claude Code to the new version right away."
|
|
33
33
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
4
5
|
import { createReadStream } from "node:fs";
|
|
6
|
+
import { homedir } from "node:os";
|
|
5
7
|
import { lstat, mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
6
8
|
|
|
7
9
|
import {
|
|
@@ -449,6 +451,84 @@ export async function installedClientRuntimeDigests({
|
|
|
449
451
|
return retainedRuntimeDigests(defaultClientMarkerRoot(environment, platform), platform);
|
|
450
452
|
}
|
|
451
453
|
|
|
454
|
+
const SETUP_TIMEOUT_MS = 180_000;
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* The clients this person already set up Mikie in: Codex when its config
|
|
458
|
+
* has a `[mcp_servers.mikie]` table, Claude Code when `~/.claude.json` has a
|
|
459
|
+
* `mikie` MCP server. Clients never set up are never touched.
|
|
460
|
+
*/
|
|
461
|
+
export async function configuredClients({ environment = process.env } = {}) {
|
|
462
|
+
const home = environment.USERPROFILE || environment.HOME || homedir();
|
|
463
|
+
const clients = [];
|
|
464
|
+
try {
|
|
465
|
+
const codexHome = environment.CODEX_HOME || path.join(home, ".codex");
|
|
466
|
+
const codex = await readFile(path.join(codexHome, "config.toml"), "utf8");
|
|
467
|
+
if (/^\[mcp_servers\.mikie\]\s*$/m.test(codex)) clients.push("codex");
|
|
468
|
+
} catch {
|
|
469
|
+
// No Codex config: Codex was never set up.
|
|
470
|
+
}
|
|
471
|
+
try {
|
|
472
|
+
const claude = JSON.parse(await readFile(path.join(home, ".claude.json"), "utf8"));
|
|
473
|
+
if (claude?.mcpServers?.mikie) clients.push("claude");
|
|
474
|
+
} catch {
|
|
475
|
+
// No Claude config: Claude Code was never set up.
|
|
476
|
+
}
|
|
477
|
+
return clients;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Run `<launcher> setup <client>`, answering its confirmation with yes, the
|
|
482
|
+
* way the `mikie` command starts the runtime (as an npm install).
|
|
483
|
+
*/
|
|
484
|
+
export function runAnsweredSetup(
|
|
485
|
+
launcher,
|
|
486
|
+
client,
|
|
487
|
+
{ spawnProcess = spawn, environment = process.env } = {},
|
|
488
|
+
) {
|
|
489
|
+
return new Promise((resolve) => {
|
|
490
|
+
let settled = false;
|
|
491
|
+
const finish = (ok) => {
|
|
492
|
+
if (!settled) {
|
|
493
|
+
settled = true;
|
|
494
|
+
clearTimeout(deadline);
|
|
495
|
+
resolve(ok);
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
const child = spawnProcess(launcher, ["setup", client], {
|
|
499
|
+
shell: false,
|
|
500
|
+
stdio: ["pipe", "ignore", "ignore"],
|
|
501
|
+
windowsHide: true,
|
|
502
|
+
env: { ...environment, MIKIE_DISTRIBUTION_MODE: "npm" },
|
|
503
|
+
});
|
|
504
|
+
const deadline = setTimeout(() => {
|
|
505
|
+
child.kill?.();
|
|
506
|
+
finish(false);
|
|
507
|
+
}, SETUP_TIMEOUT_MS);
|
|
508
|
+
child.on("error", () => finish(false));
|
|
509
|
+
child.on("close", (code) => finish(code === 0));
|
|
510
|
+
child.stdin?.end("y\n");
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Bring every already-configured client to `launcher` through the same
|
|
516
|
+
* setup a person runs. The update's in-place repair is strict and refuses
|
|
517
|
+
* on states it cannot prove; left alone that kept clients on an old
|
|
518
|
+
* runtime release after release while the update reported success.
|
|
519
|
+
*/
|
|
520
|
+
export async function setupConfiguredClients({
|
|
521
|
+
launcher,
|
|
522
|
+
environment = process.env,
|
|
523
|
+
runSetup = runAnsweredSetup,
|
|
524
|
+
} = {}) {
|
|
525
|
+
const results = [];
|
|
526
|
+
for (const client of await configuredClients({ environment })) {
|
|
527
|
+
results.push({ client, ok: await runSetup(launcher, client, { environment }) });
|
|
528
|
+
}
|
|
529
|
+
return results;
|
|
530
|
+
}
|
|
531
|
+
|
|
452
532
|
/** Reconcile exact-owned client launchers once for this installed package. */
|
|
453
533
|
export async function reconcileInstalledClients({
|
|
454
534
|
environment = process.env,
|
|
@@ -464,6 +544,7 @@ export async function reconcileInstalledClients({
|
|
|
464
544
|
}),
|
|
465
545
|
commands = createCommandAdapter({ environment, platform }),
|
|
466
546
|
allowDeferred = false,
|
|
547
|
+
setupClients = setupConfiguredClients,
|
|
467
548
|
} = {}) {
|
|
468
549
|
if (isTransactionStagedInstall(environment, platform)) {
|
|
469
550
|
return { status: "staged_candidate" };
|
|
@@ -572,6 +653,15 @@ export async function reconcileInstalledClients({
|
|
|
572
653
|
parseClientSettlement(committed, settlementPhase);
|
|
573
654
|
if (preparedReceipt.action === "refused") {
|
|
574
655
|
if (allowDeferred) {
|
|
656
|
+
const setup = await setupClients({ launcher: finalLauncher, environment });
|
|
657
|
+
if (setup.length > 0 && setup.every((result) => result.ok)) {
|
|
658
|
+
await writeClientMarker(scopedMarker, authority);
|
|
659
|
+
return {
|
|
660
|
+
status: "reconciled_by_setup",
|
|
661
|
+
clients: setup.map((result) => result.client),
|
|
662
|
+
finalLauncher,
|
|
663
|
+
};
|
|
664
|
+
}
|
|
575
665
|
return { status: "deferred", providerState: preparedReceipt.before };
|
|
576
666
|
}
|
|
577
667
|
throw new Error(
|
|
@@ -605,12 +695,24 @@ export async function completeSkippedInstall({
|
|
|
605
695
|
reconcileTrajectory = reconcileInstalledTrajectory,
|
|
606
696
|
} = {}) {
|
|
607
697
|
const clients = await reconcileClients({ version, allowDeferred: true });
|
|
608
|
-
if (clients.status === "reconciled") {
|
|
698
|
+
if (clients.status === "reconciled" || clients.status === "reconciled_by_setup") {
|
|
609
699
|
await reconcileTrajectory();
|
|
610
700
|
}
|
|
611
701
|
return clients;
|
|
612
702
|
}
|
|
613
703
|
|
|
704
|
+
/** One line for the person: what an install did to their clients' setup. */
|
|
705
|
+
export function clientReconciliationLine(clients) {
|
|
706
|
+
if (clients?.status === "reconciled_by_setup") {
|
|
707
|
+
const names = clients.clients.map((client) => (client === "claude" ? "Claude Code" : "Codex"));
|
|
708
|
+
return `Mikie updated its setup in ${names.join(" and ")}. Restart ${names.length > 1 ? "them" : "it"} to use the new version.`;
|
|
709
|
+
}
|
|
710
|
+
if (clients?.status === "deferred") {
|
|
711
|
+
return "Mikie could not update its Codex and Claude Code setup. Run `mikie setup codex` or `mikie setup claude`.";
|
|
712
|
+
}
|
|
713
|
+
return null;
|
|
714
|
+
}
|
|
715
|
+
|
|
614
716
|
const scriptIdentity = pathIdentity(fileURLToPath(import.meta.url), process.platform);
|
|
615
717
|
const invokedIdentity = pathIdentity(process.argv[1], process.platform);
|
|
616
718
|
if (scriptIdentity !== null && scriptIdentity === invokedIdentity) {
|
|
@@ -623,9 +725,8 @@ if (scriptIdentity !== null && scriptIdentity === invokedIdentity) {
|
|
|
623
725
|
version: packageMetadata.version,
|
|
624
726
|
allowDeferred: true,
|
|
625
727
|
});
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
}
|
|
728
|
+
const line = clientReconciliationLine(clients);
|
|
729
|
+
if (line) console.warn(line);
|
|
629
730
|
} catch (error) {
|
|
630
731
|
console.error(`Mikie install reconciliation failed: ${error.message}`);
|
|
631
732
|
process.exitCode = 1;
|