@mutmutco/cli 2.32.1 → 2.32.3
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/dist/main.cjs +287 -117
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -3392,7 +3392,7 @@ var program = new Command();
|
|
|
3392
3392
|
|
|
3393
3393
|
// src/index.ts
|
|
3394
3394
|
var import_promises5 = require("node:fs/promises");
|
|
3395
|
-
var
|
|
3395
|
+
var import_node_fs15 = require("node:fs");
|
|
3396
3396
|
|
|
3397
3397
|
// src/rules-sync.ts
|
|
3398
3398
|
function normalizeEol(s) {
|
|
@@ -6857,8 +6857,8 @@ async function runNorthstarContext(io, deps) {
|
|
|
6857
6857
|
}
|
|
6858
6858
|
|
|
6859
6859
|
// src/index.ts
|
|
6860
|
-
var
|
|
6861
|
-
var
|
|
6860
|
+
var import_node_path14 = require("node:path");
|
|
6861
|
+
var import_node_os5 = require("node:os");
|
|
6862
6862
|
|
|
6863
6863
|
// src/gh-create.ts
|
|
6864
6864
|
var import_promises4 = require("node:fs/promises");
|
|
@@ -8247,6 +8247,112 @@ function decideStage(inputs) {
|
|
|
8247
8247
|
return { source: "none", gap, staleIgnored: stale || void 0, staleFields: stale ? staleFields : void 0, registryError: registry2.error };
|
|
8248
8248
|
}
|
|
8249
8249
|
|
|
8250
|
+
// src/cursor-plugin-seed.ts
|
|
8251
|
+
var import_node_fs11 = require("node:fs");
|
|
8252
|
+
var import_node_os4 = require("node:os");
|
|
8253
|
+
var import_node_path11 = require("node:path");
|
|
8254
|
+
function isSemverVersion(v) {
|
|
8255
|
+
return typeof v === "string" && /^v?\d+\.\d+\.\d+/.test(v.trim());
|
|
8256
|
+
}
|
|
8257
|
+
var MMI_HUB_REPO = "mutmutco/MMI-Hub";
|
|
8258
|
+
var CURSOR_THIRD_PARTY_STATE_KEY = "cursor/thirdPartyExtensibilityEnabled";
|
|
8259
|
+
var PLUGIN_JSON_REL = ".cursor-plugin/plugin.json";
|
|
8260
|
+
function cursorUserGlobalStatePath() {
|
|
8261
|
+
if (process.platform === "win32") {
|
|
8262
|
+
const base2 = process.env.APPDATA || (0, import_node_path11.join)((0, import_node_os4.homedir)(), "AppData", "Roaming");
|
|
8263
|
+
return (0, import_node_path11.join)(base2, "Cursor", "User", "globalStorage", "state.vscdb");
|
|
8264
|
+
}
|
|
8265
|
+
if (process.platform === "darwin") {
|
|
8266
|
+
return (0, import_node_path11.join)((0, import_node_os4.homedir)(), "Library", "Application Support", "Cursor", "User", "globalStorage", "state.vscdb");
|
|
8267
|
+
}
|
|
8268
|
+
return (0, import_node_path11.join)((0, import_node_os4.homedir)(), ".config", "Cursor", "User", "globalStorage", "state.vscdb");
|
|
8269
|
+
}
|
|
8270
|
+
async function readCursorThirdPartyExtensibilityEnabled(execFileP5) {
|
|
8271
|
+
const dbPath = cursorUserGlobalStatePath();
|
|
8272
|
+
if (!(0, import_node_fs11.existsSync)(dbPath)) return void 0;
|
|
8273
|
+
try {
|
|
8274
|
+
const { stdout } = await execFileP5("sqlite3", [dbPath, `SELECT value FROM ItemTable WHERE key = '${CURSOR_THIRD_PARTY_STATE_KEY}';`], {
|
|
8275
|
+
timeout: 5e3
|
|
8276
|
+
});
|
|
8277
|
+
const value = stdout.trim().toLowerCase();
|
|
8278
|
+
if (value === "true") return true;
|
|
8279
|
+
if (value === "false") return false;
|
|
8280
|
+
return void 0;
|
|
8281
|
+
} catch {
|
|
8282
|
+
return void 0;
|
|
8283
|
+
}
|
|
8284
|
+
}
|
|
8285
|
+
function syncDirContents(src, dest) {
|
|
8286
|
+
(0, import_node_fs11.mkdirSync)(dest, { recursive: true });
|
|
8287
|
+
for (const name of (0, import_node_fs11.readdirSync)(dest)) {
|
|
8288
|
+
(0, import_node_fs11.rmSync)((0, import_node_path11.join)(dest, name), { recursive: true, force: true });
|
|
8289
|
+
}
|
|
8290
|
+
(0, import_node_fs11.cpSync)(src, dest, { recursive: true });
|
|
8291
|
+
}
|
|
8292
|
+
function releaseTag(releasedVersion) {
|
|
8293
|
+
return releasedVersion.startsWith("v") ? releasedVersion : `v${releasedVersion}`;
|
|
8294
|
+
}
|
|
8295
|
+
async function extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5) {
|
|
8296
|
+
const tarFile = (0, import_node_path11.join)(tmpRoot, "archive.tar");
|
|
8297
|
+
try {
|
|
8298
|
+
await execFileP5("git", ["-C", hubCheckout, "fetch", "origin", `tag ${tag}`, "--quiet"], { timeout: 6e4 });
|
|
8299
|
+
await execFileP5("git", ["-C", hubCheckout, "archive", "--format=tar", `--output=${tarFile}`, tag, "plugins/mmi"], {
|
|
8300
|
+
timeout: 6e4
|
|
8301
|
+
});
|
|
8302
|
+
await execFileP5("tar", ["-xf", tarFile, "-C", tmpRoot], { timeout: 6e4 });
|
|
8303
|
+
const pluginMmi = (0, import_node_path11.join)(tmpRoot, "plugins", "mmi");
|
|
8304
|
+
return (0, import_node_fs11.existsSync)((0, import_node_path11.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
|
|
8305
|
+
} catch {
|
|
8306
|
+
return void 0;
|
|
8307
|
+
}
|
|
8308
|
+
}
|
|
8309
|
+
async function downloadPluginMmiViaGh(tag, tmpRoot, execFileP5) {
|
|
8310
|
+
const tarPath = (0, import_node_path11.join)(tmpRoot, "repo.tgz");
|
|
8311
|
+
try {
|
|
8312
|
+
await execFileP5("gh", ["api", `repos/${MMI_HUB_REPO}/tarball/refs/tags/${tag}`, "--output", tarPath], {
|
|
8313
|
+
timeout: 12e4
|
|
8314
|
+
});
|
|
8315
|
+
await execFileP5("tar", ["-xzf", tarPath, "-C", tmpRoot], { timeout: 12e4 });
|
|
8316
|
+
const top = (0, import_node_fs11.readdirSync)(tmpRoot).find((entry) => entry !== "repo.tgz");
|
|
8317
|
+
if (!top) return void 0;
|
|
8318
|
+
const pluginMmi = (0, import_node_path11.join)(tmpRoot, top, "plugins", "mmi");
|
|
8319
|
+
return (0, import_node_fs11.existsSync)((0, import_node_path11.join)(pluginMmi, PLUGIN_JSON_REL)) ? pluginMmi : void 0;
|
|
8320
|
+
} catch {
|
|
8321
|
+
return void 0;
|
|
8322
|
+
}
|
|
8323
|
+
}
|
|
8324
|
+
async function resolvePluginMmiSource(releasedVersion, hubCheckout, tmpRoot, execFileP5) {
|
|
8325
|
+
(0, import_node_fs11.mkdirSync)(tmpRoot, { recursive: true });
|
|
8326
|
+
const tag = releaseTag(releasedVersion);
|
|
8327
|
+
if (hubCheckout) {
|
|
8328
|
+
const fromHub = await extractPluginMmiFromHubCheckout(hubCheckout, tag, tmpRoot, execFileP5);
|
|
8329
|
+
if (fromHub) return fromHub;
|
|
8330
|
+
}
|
|
8331
|
+
return downloadPluginMmiViaGh(tag, (0, import_node_path11.join)(tmpRoot, "gh"), execFileP5);
|
|
8332
|
+
}
|
|
8333
|
+
function cursorPluginPinsNeedingSeed(pins, releasedVersion) {
|
|
8334
|
+
if (!isSemverVersion(releasedVersion)) return pins.filter((pin) => !pin.hasPluginJson || !pin.hasHooksJson || pin.isEmpty);
|
|
8335
|
+
return pins.filter((pin) => {
|
|
8336
|
+
if (!pin.hasPluginJson || !pin.hasHooksJson || pin.isEmpty) return true;
|
|
8337
|
+
if (isSemverVersion(pin.version) && compareVersions(pin.version, releasedVersion) < 0) return true;
|
|
8338
|
+
return false;
|
|
8339
|
+
});
|
|
8340
|
+
}
|
|
8341
|
+
async function applyCursorPluginCacheSeed(input) {
|
|
8342
|
+
if (!isSemverVersion(input.releasedVersion)) return false;
|
|
8343
|
+
const pinsToSeed = cursorPluginPinsNeedingSeed(input.pins, input.releasedVersion);
|
|
8344
|
+
if (pinsToSeed.length === 0) return false;
|
|
8345
|
+
const tmpRoot = await input.mkdtemp("mmi-cursor-seed-");
|
|
8346
|
+
const source = await resolvePluginMmiSource(input.releasedVersion, input.hubCheckout, tmpRoot, input.execFileP);
|
|
8347
|
+
if (!source) return false;
|
|
8348
|
+
input.log(` \u21BB seeding Cursor MMI plugin cache \u2192 ${input.releasedVersion}\u2026`);
|
|
8349
|
+
for (const pin of pinsToSeed) {
|
|
8350
|
+
syncDirContents(source, pin.path);
|
|
8351
|
+
}
|
|
8352
|
+
(0, import_node_fs11.rmSync)(tmpRoot, { recursive: true, force: true });
|
|
8353
|
+
return true;
|
|
8354
|
+
}
|
|
8355
|
+
|
|
8250
8356
|
// src/bootstrap-seeds.ts
|
|
8251
8357
|
var PLACEHOLDER_RE = /\{\{([A-Z0-9_]+)\}\}/g;
|
|
8252
8358
|
function loadBootstrapSeeds(manifestJson) {
|
|
@@ -8567,7 +8673,7 @@ function detectSurface(env) {
|
|
|
8567
8673
|
if (env.MMI_AGENT_SURFACE === "codex" || has("CODEX_HOME") || (env.CLAUDE_PLUGIN_ROOT ?? "").includes(".codex")) {
|
|
8568
8674
|
return "codex";
|
|
8569
8675
|
}
|
|
8570
|
-
if (env.MMI_AGENT_SURFACE === "cursor" || has("CURSOR_TRACE_ID") || has("CURSOR_USER") || has("CURSOR_SESSION_ID")) {
|
|
8676
|
+
if (env.MMI_AGENT_SURFACE === "cursor" || has("CURSOR_TRACE_ID") || has("CURSOR_USER") || has("CURSOR_SESSION_ID") || env.CURSOR_AGENT === "1" || has("CURSOR_EXTENSION_HOST_ROLE")) {
|
|
8571
8677
|
return "cursor";
|
|
8572
8678
|
}
|
|
8573
8679
|
const isClaude = has("CLAUDECODE") || has("CLAUDE_CODE_ENTRYPOINT") || has("CLAUDE_PLUGIN_ROOT") || env.MMI_AGENT_SURFACE === "claude";
|
|
@@ -8622,7 +8728,7 @@ var PLUGIN_UPDATE_RECIPES = {
|
|
|
8622
8728
|
};
|
|
8623
8729
|
function highestSemver(versions) {
|
|
8624
8730
|
return versions.reduce((best, v) => {
|
|
8625
|
-
if (!
|
|
8731
|
+
if (!isSemverVersion2(v)) return best;
|
|
8626
8732
|
if (best === void 0) return v;
|
|
8627
8733
|
return compareVersions(v, best) > 0 ? v : best;
|
|
8628
8734
|
}, void 0);
|
|
@@ -8633,11 +8739,11 @@ function buildPluginUpdateReport(input) {
|
|
|
8633
8739
|
const codexActiveCache = highestSemver(input.codexCacheVersions ?? []);
|
|
8634
8740
|
return {
|
|
8635
8741
|
versions: {
|
|
8636
|
-
...
|
|
8742
|
+
...isSemverVersion2(input.cliVersion) ? { cli: input.cliVersion } : {},
|
|
8637
8743
|
...claudePlugin ? { claudePlugin } : {},
|
|
8638
8744
|
...codexMarketplace ? { codexMarketplace } : {},
|
|
8639
8745
|
...codexActiveCache ? { codexActiveCache } : {},
|
|
8640
|
-
...
|
|
8746
|
+
...isSemverVersion2(input.releasedVersion) ? { released: input.releasedVersion } : {}
|
|
8641
8747
|
},
|
|
8642
8748
|
recipes: PLUGIN_UPDATE_RECIPES
|
|
8643
8749
|
};
|
|
@@ -8667,7 +8773,7 @@ function buildDoctorJsonPayload(input) {
|
|
|
8667
8773
|
};
|
|
8668
8774
|
}
|
|
8669
8775
|
var INSTALLED_PLUGIN_VERSION_LABEL = "installed MMI plugin version (vs latest release)";
|
|
8670
|
-
function
|
|
8776
|
+
function isSemverVersion2(v) {
|
|
8671
8777
|
return typeof v === "string" && /^v?\d+\.\d+\.\d+/.test(v.trim());
|
|
8672
8778
|
}
|
|
8673
8779
|
function staleRecordCommand(surface) {
|
|
@@ -8688,7 +8794,8 @@ function buildInstalledPluginVersionCheck(input) {
|
|
|
8688
8794
|
fix: pluginRecoveryFix(input.surface),
|
|
8689
8795
|
pluginId
|
|
8690
8796
|
};
|
|
8691
|
-
if (!input.isOrgRepo || !
|
|
8797
|
+
if (!input.isOrgRepo || !isSemverVersion2(input.releasedVersion)) return base2;
|
|
8798
|
+
if (input.surface === "cursor") return base2;
|
|
8692
8799
|
const stale = [];
|
|
8693
8800
|
let sawRecord = false;
|
|
8694
8801
|
let currentVersion;
|
|
@@ -8697,7 +8804,7 @@ function buildInstalledPluginVersionCheck(input) {
|
|
|
8697
8804
|
if (!Array.isArray(records) || records.length === 0) continue;
|
|
8698
8805
|
sawRecord = true;
|
|
8699
8806
|
const installedVersion = bestRecord(records).version;
|
|
8700
|
-
if (!
|
|
8807
|
+
if (!isSemverVersion2(installedVersion)) continue;
|
|
8701
8808
|
if (compareVersions(installedVersion, input.releasedVersion) >= 0) {
|
|
8702
8809
|
currentVersion = currentVersion ?? installedVersion;
|
|
8703
8810
|
} else {
|
|
@@ -8729,15 +8836,16 @@ function joinCachePath(root, ...parts) {
|
|
|
8729
8836
|
function cursorPluginInstallFix(input) {
|
|
8730
8837
|
const logHint = "check %APPDATA%\\Cursor\\logs\\<session>\\window*\\exthost\\anysphere.cursor-agent-exec\\Cursor Plugins.*.log for `unable to get password from user`";
|
|
8731
8838
|
const authSteps = "ensure GitHub SSH auth works for Cursor's background git (start ssh-agent, add your GitHub key, verify `ssh -T git@github.com`) \u2014 Cursor disables credential.helper during marketplace clone";
|
|
8732
|
-
const marketplaceRefresh = "
|
|
8839
|
+
const marketplaceRefresh = "when Dashboard \u2192 Settings \u2192 Plugins shows Update next to the MMI Team Marketplace, refresh it there; otherwise run `mmi-cli doctor --apply` to seed the cache from the latest release";
|
|
8733
8840
|
const guide = `full recovery: ${CURSOR_MARKETPLACE_INSTALL_GUIDE}`;
|
|
8734
8841
|
if (input.reason === "missing-cache") {
|
|
8735
8842
|
return `${marketplaceRefresh}; ${authSteps}; ${guide}`;
|
|
8736
8843
|
}
|
|
8737
8844
|
const pin = input.pinName ?? "<commit-pin>";
|
|
8738
8845
|
const cacheDir = joinCachePath(input.cacheRoot, pin);
|
|
8846
|
+
const autoSeed = "run `mmi-cli doctor --apply` to seed plugins/mmi from the latest release into the active pin";
|
|
8739
8847
|
const localSeed = input.hubCheckout ? `temporary fallback: copy ${joinCachePath(input.hubCheckout, "plugins", "mmi")} to ${cacheDir}, then restart Cursor` : `temporary fallback: copy plugins/mmi from a local MMI-Hub checkout to ${cacheDir}, then restart Cursor`;
|
|
8740
|
-
return `Cursor plugin cache at ${cacheDir} is empty or missing ${CURSOR_PLUGIN_JSON_REL} or ${CURSOR_HOOKS_JSON_REL} \u2014 ${marketplaceRefresh}; ${authSteps}; ${localSeed}; ${logHint}; ${guide}`;
|
|
8848
|
+
return `Cursor plugin cache at ${cacheDir} is empty or missing ${CURSOR_PLUGIN_JSON_REL} or ${CURSOR_HOOKS_JSON_REL} \u2014 ${autoSeed}; ${marketplaceRefresh}; ${authSteps}; ${localSeed}; ${logHint}; ${guide}`;
|
|
8741
8849
|
}
|
|
8742
8850
|
function buildCursorPluginInstallCheck(input) {
|
|
8743
8851
|
const base2 = {
|
|
@@ -8775,8 +8883,29 @@ function buildCursorPluginInstallCheck(input) {
|
|
|
8775
8883
|
};
|
|
8776
8884
|
}
|
|
8777
8885
|
}
|
|
8886
|
+
if (input.surface === "cursor" && isSemverVersion2(input.releasedVersion) && input.pins.some((pin) => isSemverVersion2(pin.version) && compareVersions(pin.version, input.releasedVersion) < 0)) {
|
|
8887
|
+
const stale = input.pins.filter((pin) => isSemverVersion2(pin.version) && compareVersions(pin.version, input.releasedVersion) < 0).map((pin) => `${pin.version} at ${pin.name}`).join(", ");
|
|
8888
|
+
return {
|
|
8889
|
+
...base2,
|
|
8890
|
+
ok: false,
|
|
8891
|
+
cacheRoot: input.cacheRoot,
|
|
8892
|
+
pins: input.pins,
|
|
8893
|
+
fix: `Cursor MMI plugin cache is behind ${input.releasedVersion} (${stale}) \u2014 run \`mmi-cli doctor --apply\` to seed the cache from the latest release (effective after restart Cursor); when Dashboard \u2192 Settings \u2192 Plugins shows Update next to the MMI Team Marketplace, a master-admin can refresh the pin there instead; ${CURSOR_MARKETPLACE_INSTALL_GUIDE}`
|
|
8894
|
+
};
|
|
8895
|
+
}
|
|
8778
8896
|
return { ...base2, cacheRoot: input.cacheRoot, pins: input.pins };
|
|
8779
8897
|
}
|
|
8898
|
+
var CURSOR_THIRD_PARTY_EXTENSIBILITY_LABEL = "Cursor third-party plugin import";
|
|
8899
|
+
var CURSOR_THIRD_PARTY_EXTENSIBILITY_FIX = "disable Cursor Settings \u2192 Rules, Skills, Subagents \u2192 Include third-party Plugins, Skills, and other configs (cursor/thirdPartyExtensibilityEnabled) \u2014 it imports Claude/Codex hooks that break the MMI Team Marketplace plugin; use MMI from the Team Marketplace only, then restart Cursor";
|
|
8900
|
+
function buildCursorThirdPartyExtensibilityCheck(input) {
|
|
8901
|
+
const base2 = {
|
|
8902
|
+
ok: true,
|
|
8903
|
+
label: CURSOR_THIRD_PARTY_EXTENSIBILITY_LABEL,
|
|
8904
|
+
fix: CURSOR_THIRD_PARTY_EXTENSIBILITY_FIX
|
|
8905
|
+
};
|
|
8906
|
+
if (!input.isOrgRepo || input.surface !== "cursor" || input.enabled === void 0) return base2;
|
|
8907
|
+
return { ...base2, ok: !input.enabled };
|
|
8908
|
+
}
|
|
8780
8909
|
function buildCursorHookCliCheck(input) {
|
|
8781
8910
|
const fix = "update the MMI Team Marketplace plugin (releases ship cli/dist under plugins/mmi/cli/dist) or install mmi-cli on PATH \u2014 Cursor hooks fall back to PATH when the bundled CLI is missing";
|
|
8782
8911
|
const base2 = { ok: true, label: CURSOR_HOOK_CLI_LABEL, fix };
|
|
@@ -8926,8 +9055,8 @@ async function runStageLiveDown(deps, t) {
|
|
|
8926
9055
|
|
|
8927
9056
|
// src/stage-runner.ts
|
|
8928
9057
|
var import_node_child_process7 = require("node:child_process");
|
|
8929
|
-
var
|
|
8930
|
-
var
|
|
9058
|
+
var import_node_fs12 = require("node:fs");
|
|
9059
|
+
var import_node_path12 = require("node:path");
|
|
8931
9060
|
var import_node_net2 = require("node:net");
|
|
8932
9061
|
var import_node_util6 = require("node:util");
|
|
8933
9062
|
var execFileP4 = (0, import_node_util6.promisify)(import_node_child_process7.execFile);
|
|
@@ -8972,7 +9101,7 @@ function detectStaleEnvFile(exampleContent, targetContent, mtimes) {
|
|
|
8972
9101
|
return void 0;
|
|
8973
9102
|
}
|
|
8974
9103
|
function stageStatePath(cwd = process.cwd()) {
|
|
8975
|
-
return (0,
|
|
9104
|
+
return (0, import_node_path12.join)(cwd, "tmp", "stage", "state.json");
|
|
8976
9105
|
}
|
|
8977
9106
|
var POSIX_ONLY_VERBS = ["cp", "mv", "rm", "ln", "cat", "touch", "chmod", "export"];
|
|
8978
9107
|
function posixOnlyShellProblems(command, field, platform = process.platform) {
|
|
@@ -9034,9 +9163,9 @@ async function shell(command, cwd, timeoutMs) {
|
|
|
9034
9163
|
});
|
|
9035
9164
|
}
|
|
9036
9165
|
function readState(path2) {
|
|
9037
|
-
if (!(0,
|
|
9166
|
+
if (!(0, import_node_fs12.existsSync)(path2)) return null;
|
|
9038
9167
|
try {
|
|
9039
|
-
return JSON.parse((0,
|
|
9168
|
+
return JSON.parse((0, import_node_fs12.readFileSync)(path2, "utf8"));
|
|
9040
9169
|
} catch {
|
|
9041
9170
|
return null;
|
|
9042
9171
|
}
|
|
@@ -9088,7 +9217,7 @@ async function stopStage(opts = {}) {
|
|
|
9088
9217
|
return { ok: true, action: "stop", statePath, message: "no previous stage state found" };
|
|
9089
9218
|
}
|
|
9090
9219
|
await killTree(state.pid);
|
|
9091
|
-
(0,
|
|
9220
|
+
(0, import_node_fs12.rmSync)(statePath, { force: true });
|
|
9092
9221
|
return { ok: true, action: "stop", statePath, pid: state.pid, message: `stopped previous stage pid ${state.pid}` };
|
|
9093
9222
|
}
|
|
9094
9223
|
async function startStage(config = {}, opts = {}) {
|
|
@@ -9097,7 +9226,7 @@ async function startStage(config = {}, opts = {}) {
|
|
|
9097
9226
|
const cwd = opts.cwd ?? process.cwd();
|
|
9098
9227
|
const statePath = opts.statePath ?? stageStatePath(cwd);
|
|
9099
9228
|
const dir = statePath.slice(0, Math.max(statePath.lastIndexOf("/"), statePath.lastIndexOf("\\")));
|
|
9100
|
-
(0,
|
|
9229
|
+
(0, import_node_fs12.mkdirSync)(dir, { recursive: true });
|
|
9101
9230
|
let stagePort;
|
|
9102
9231
|
if (config.portRange) {
|
|
9103
9232
|
const [s, e] = config.portRange;
|
|
@@ -9107,14 +9236,14 @@ async function startStage(config = {}, opts = {}) {
|
|
|
9107
9236
|
}
|
|
9108
9237
|
const sub = (s) => s != null && stagePort != null ? s.replace(/\$\{?STAGE_PORT\}?/g, String(stagePort)) : s;
|
|
9109
9238
|
if (config.ensureEnv) {
|
|
9110
|
-
const target = (0,
|
|
9111
|
-
const example = (0,
|
|
9112
|
-
if (!(0,
|
|
9113
|
-
(0,
|
|
9114
|
-
} else if ((0,
|
|
9115
|
-
const stale = detectStaleEnvFile((0,
|
|
9116
|
-
exampleMtimeMs: (0,
|
|
9117
|
-
targetMtimeMs: (0,
|
|
9239
|
+
const target = (0, import_node_path12.join)(cwd, config.ensureEnv.target);
|
|
9240
|
+
const example = (0, import_node_path12.join)(cwd, config.ensureEnv.example);
|
|
9241
|
+
if (!(0, import_node_fs12.existsSync)(target) && (0, import_node_fs12.existsSync)(example)) {
|
|
9242
|
+
(0, import_node_fs12.copyFileSync)(example, target);
|
|
9243
|
+
} else if ((0, import_node_fs12.existsSync)(target) && (0, import_node_fs12.existsSync)(example)) {
|
|
9244
|
+
const stale = detectStaleEnvFile((0, import_node_fs12.readFileSync)(example, "utf8"), (0, import_node_fs12.readFileSync)(target, "utf8"), {
|
|
9245
|
+
exampleMtimeMs: (0, import_node_fs12.statSync)(example).mtimeMs,
|
|
9246
|
+
targetMtimeMs: (0, import_node_fs12.statSync)(target).mtimeMs
|
|
9118
9247
|
});
|
|
9119
9248
|
if (stale) {
|
|
9120
9249
|
const msg = `stale ${config.ensureEnv.target} (${stale}) \u2014 delete it or refresh from ${config.ensureEnv.example} before re-running /stage`;
|
|
@@ -9145,13 +9274,13 @@ async function startStage(config = {}, opts = {}) {
|
|
|
9145
9274
|
healthUrl: sub(config.healthUrl?.trim()) || void 0,
|
|
9146
9275
|
port: stagePort
|
|
9147
9276
|
};
|
|
9148
|
-
(0,
|
|
9277
|
+
(0, import_node_fs12.writeFileSync)(statePath, JSON.stringify(state, null, 2), "utf8");
|
|
9149
9278
|
try {
|
|
9150
9279
|
if (state.healthUrl) await waitForHealth(state.healthUrl, opts.timeoutMs ?? 6e4, config.healthAnyStatus);
|
|
9151
9280
|
else await waitForProcessStability(child);
|
|
9152
9281
|
} catch (e) {
|
|
9153
9282
|
await killTree(state.pid);
|
|
9154
|
-
(0,
|
|
9283
|
+
(0, import_node_fs12.rmSync)(statePath, { force: true });
|
|
9155
9284
|
throw e;
|
|
9156
9285
|
}
|
|
9157
9286
|
const result = { ok: true, action: "start", statePath, pid: state.pid, port: stagePort, message: `started stage pid ${state.pid}${stagePort != null ? ` on port ${stagePort}` : ""}` };
|
|
@@ -10891,7 +11020,7 @@ async function announceRelease(deps, args) {
|
|
|
10891
11020
|
}
|
|
10892
11021
|
|
|
10893
11022
|
// src/port-registry.ts
|
|
10894
|
-
var
|
|
11023
|
+
var import_node_fs13 = require("node:fs");
|
|
10895
11024
|
|
|
10896
11025
|
// ../infra/port-geometry.mjs
|
|
10897
11026
|
var PORT_BLOCK = 100;
|
|
@@ -10905,8 +11034,8 @@ function nextPortBlock(registry2) {
|
|
|
10905
11034
|
return [base2, base2 + PORT_SPAN];
|
|
10906
11035
|
}
|
|
10907
11036
|
function loadPortRegistry(path2) {
|
|
10908
|
-
if (!(0,
|
|
10909
|
-
const raw = JSON.parse((0,
|
|
11037
|
+
if (!(0, import_node_fs13.existsSync)(path2)) return {};
|
|
11038
|
+
const raw = JSON.parse((0, import_node_fs13.readFileSync)(path2, "utf8"));
|
|
10910
11039
|
const out = {};
|
|
10911
11040
|
for (const [key, value] of Object.entries(raw)) {
|
|
10912
11041
|
if (Array.isArray(value) && value.length === 2 && value.every((n) => typeof n === "number")) {
|
|
@@ -10920,9 +11049,9 @@ function ensurePortRange(repo, path2) {
|
|
|
10920
11049
|
const existing = registry2[repo];
|
|
10921
11050
|
if (existing) return existing;
|
|
10922
11051
|
const range = nextPortBlock(registry2);
|
|
10923
|
-
const raw = (0,
|
|
11052
|
+
const raw = (0, import_node_fs13.existsSync)(path2) ? JSON.parse((0, import_node_fs13.readFileSync)(path2, "utf8")) : {};
|
|
10924
11053
|
raw[repo] = range;
|
|
10925
|
-
(0,
|
|
11054
|
+
(0, import_node_fs13.writeFileSync)(path2, JSON.stringify(raw, null, 2) + "\n", "utf8");
|
|
10926
11055
|
return range;
|
|
10927
11056
|
}
|
|
10928
11057
|
function portCursorSeed(registry2) {
|
|
@@ -12708,10 +12837,10 @@ function parseKbTree(stdout, prefix) {
|
|
|
12708
12837
|
}
|
|
12709
12838
|
|
|
12710
12839
|
// src/plan.ts
|
|
12711
|
-
var
|
|
12840
|
+
var import_node_path13 = require("node:path");
|
|
12712
12841
|
var PLANS_DIR = "plans";
|
|
12713
|
-
var META_FILE = (0,
|
|
12714
|
-
var planPath = (slug) => (0,
|
|
12842
|
+
var META_FILE = (0, import_node_path13.join)(PLANS_DIR, ".plan-meta.json");
|
|
12843
|
+
var planPath = (slug) => (0, import_node_path13.join)(PLANS_DIR, `${slug}.md`);
|
|
12715
12844
|
var metaKey = (project2, slug) => `${project2}/${slug}`;
|
|
12716
12845
|
function parseMeta(raw) {
|
|
12717
12846
|
if (!raw) return {};
|
|
@@ -12736,7 +12865,7 @@ function hashContent(s) {
|
|
|
12736
12865
|
function staleHint(slug) {
|
|
12737
12866
|
return `remote "${slug}" is newer \u2014 run \`mmi-cli northstar pull ${slug}\` first (your local is based on an older version), or re-push with \`--force\` to overwrite`;
|
|
12738
12867
|
}
|
|
12739
|
-
var INDEX_FILE = (0,
|
|
12868
|
+
var INDEX_FILE = (0, import_node_path13.join)(PLANS_DIR, ".index.json");
|
|
12740
12869
|
var INDEX_TTL_MS = 6e4;
|
|
12741
12870
|
function parseIndex(raw) {
|
|
12742
12871
|
if (!raw) return null;
|
|
@@ -12765,7 +12894,7 @@ function mergeIndex(idx, scope, plans, now) {
|
|
|
12765
12894
|
const mergedScope = idx.scope === null ? null : [.../* @__PURE__ */ new Set([...idx.scope, ...scope])];
|
|
12766
12895
|
return { fetchedAt: now, scope: mergedScope, plans: [...kept, ...plans] };
|
|
12767
12896
|
}
|
|
12768
|
-
var QUEUE_FILE = (0,
|
|
12897
|
+
var QUEUE_FILE = (0, import_node_path13.join)(PLANS_DIR, ".sync-queue.json");
|
|
12769
12898
|
var QUEUE_MAX_ATTEMPTS = 10;
|
|
12770
12899
|
function isValidQueueEntry(e) {
|
|
12771
12900
|
if (!e || typeof e !== "object") return false;
|
|
@@ -13214,11 +13343,11 @@ async function planGraduate(deps, slug, opts = {}) {
|
|
|
13214
13343
|
}
|
|
13215
13344
|
|
|
13216
13345
|
// src/atomic-write.ts
|
|
13217
|
-
var
|
|
13346
|
+
var import_node_fs14 = require("node:fs");
|
|
13218
13347
|
function atomicWriteFileSync(path2, content) {
|
|
13219
13348
|
const tmp = `${path2}.${process.pid}.tmp`;
|
|
13220
|
-
(0,
|
|
13221
|
-
(0,
|
|
13349
|
+
(0, import_node_fs14.writeFileSync)(tmp, content, "utf8");
|
|
13350
|
+
(0, import_node_fs14.renameSync)(tmp, path2);
|
|
13222
13351
|
}
|
|
13223
13352
|
|
|
13224
13353
|
// src/oauth.ts
|
|
@@ -13449,7 +13578,7 @@ async function fetchHubVersionInfo(baseUrl) {
|
|
|
13449
13578
|
}
|
|
13450
13579
|
function readRepoVersion() {
|
|
13451
13580
|
try {
|
|
13452
|
-
return JSON.parse((0,
|
|
13581
|
+
return JSON.parse((0, import_node_fs15.readFileSync)((0, import_node_path14.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
|
|
13453
13582
|
} catch {
|
|
13454
13583
|
return void 0;
|
|
13455
13584
|
}
|
|
@@ -13559,10 +13688,10 @@ async function runRulesSync(opts, io = consoleIo) {
|
|
|
13559
13688
|
for (const entry of fetched) {
|
|
13560
13689
|
if ("error" in entry) continue;
|
|
13561
13690
|
const { file, source } = entry;
|
|
13562
|
-
const current = (0,
|
|
13691
|
+
const current = (0, import_node_fs15.existsSync)(file) ? await (0, import_promises5.readFile)(file, "utf8") : null;
|
|
13563
13692
|
if (needsUpdate(source, current)) {
|
|
13564
13693
|
const slash = file.lastIndexOf("/");
|
|
13565
|
-
if (slash > 0) (0,
|
|
13694
|
+
if (slash > 0) (0, import_node_fs15.mkdirSync)(file.slice(0, slash), { recursive: true });
|
|
13566
13695
|
await (0, import_promises5.writeFile)(file, normalizeEol(source), "utf8");
|
|
13567
13696
|
changed++;
|
|
13568
13697
|
if (!opts.quiet) io.log(`mmi-cli rules: updated ${file}`);
|
|
@@ -13588,7 +13717,7 @@ async function runDocsSync(opts, io = consoleIo) {
|
|
|
13588
13717
|
return null;
|
|
13589
13718
|
}
|
|
13590
13719
|
},
|
|
13591
|
-
localContent: async (f) => (0,
|
|
13720
|
+
localContent: async (f) => (0, import_node_fs15.existsSync)(f) ? await (0, import_promises5.readFile)(f, "utf8") : null,
|
|
13592
13721
|
writeDoc: async (f, c) => {
|
|
13593
13722
|
await (0, import_promises5.writeFile)(f, c, "utf8");
|
|
13594
13723
|
}
|
|
@@ -13761,7 +13890,7 @@ function detachPlanSync() {
|
|
|
13761
13890
|
}
|
|
13762
13891
|
}
|
|
13763
13892
|
function makePlanDeps(cfg, io = consoleIo) {
|
|
13764
|
-
const ensureDir = () => (0,
|
|
13893
|
+
const ensureDir = () => (0, import_node_fs15.mkdirSync)(PLANS_DIR, { recursive: true });
|
|
13765
13894
|
return {
|
|
13766
13895
|
apiUrl: cfg.sagaApiUrl,
|
|
13767
13896
|
fetch: (url, init = {}) => fetch(url, { ...init, signal: init.signal ?? AbortSignal.timeout(1e4) }),
|
|
@@ -13769,24 +13898,24 @@ function makePlanDeps(cfg, io = consoleIo) {
|
|
|
13769
13898
|
project: async () => (await sagaKey(cfg)).project,
|
|
13770
13899
|
readLocal: (slug) => {
|
|
13771
13900
|
try {
|
|
13772
|
-
return (0,
|
|
13901
|
+
return (0, import_node_fs15.readFileSync)(planPath(slug), "utf8");
|
|
13773
13902
|
} catch {
|
|
13774
13903
|
return null;
|
|
13775
13904
|
}
|
|
13776
13905
|
},
|
|
13777
13906
|
writeLocal: (slug, content) => {
|
|
13778
13907
|
ensureDir();
|
|
13779
|
-
(0,
|
|
13908
|
+
(0, import_node_fs15.writeFileSync)(planPath(slug), content, "utf8");
|
|
13780
13909
|
},
|
|
13781
13910
|
removeLocal: (slug) => {
|
|
13782
13911
|
try {
|
|
13783
|
-
(0,
|
|
13912
|
+
(0, import_node_fs15.rmSync)(planPath(slug));
|
|
13784
13913
|
} catch {
|
|
13785
13914
|
}
|
|
13786
13915
|
},
|
|
13787
13916
|
readMetaRaw: () => {
|
|
13788
13917
|
try {
|
|
13789
|
-
return (0,
|
|
13918
|
+
return (0, import_node_fs15.readFileSync)(META_FILE, "utf8");
|
|
13790
13919
|
} catch {
|
|
13791
13920
|
return null;
|
|
13792
13921
|
}
|
|
@@ -13797,7 +13926,7 @@ function makePlanDeps(cfg, io = consoleIo) {
|
|
|
13797
13926
|
},
|
|
13798
13927
|
readIndexRaw: () => {
|
|
13799
13928
|
try {
|
|
13800
|
-
return (0,
|
|
13929
|
+
return (0, import_node_fs15.readFileSync)(INDEX_FILE, "utf8");
|
|
13801
13930
|
} catch {
|
|
13802
13931
|
return null;
|
|
13803
13932
|
}
|
|
@@ -13808,7 +13937,7 @@ function makePlanDeps(cfg, io = consoleIo) {
|
|
|
13808
13937
|
},
|
|
13809
13938
|
readQueueRaw: () => {
|
|
13810
13939
|
try {
|
|
13811
|
-
return (0,
|
|
13940
|
+
return (0, import_node_fs15.readFileSync)(QUEUE_FILE, "utf8");
|
|
13812
13941
|
} catch {
|
|
13813
13942
|
return null;
|
|
13814
13943
|
}
|
|
@@ -14664,7 +14793,7 @@ async function createDeferredWorktreeStore() {
|
|
|
14664
14793
|
},
|
|
14665
14794
|
write: async (entries) => {
|
|
14666
14795
|
try {
|
|
14667
|
-
await (0, import_promises5.mkdir)((0,
|
|
14796
|
+
await (0, import_promises5.mkdir)((0, import_node_path14.dirname)(registryPath), { recursive: true });
|
|
14668
14797
|
await (0, import_promises5.writeFile)(registryPath, serializeDeferredWorktrees(entries), "utf8");
|
|
14669
14798
|
} catch {
|
|
14670
14799
|
}
|
|
@@ -14683,7 +14812,7 @@ function worktreeRemoveDeps(execGit) {
|
|
|
14683
14812
|
}
|
|
14684
14813
|
function teardownWorktreeStage(worktreePath) {
|
|
14685
14814
|
return runWorktreeStageTeardown(worktreePath, {
|
|
14686
|
-
hasStageState: (wt) => (0,
|
|
14815
|
+
hasStageState: (wt) => (0, import_node_fs15.existsSync)(stageStatePath(wt)),
|
|
14687
14816
|
stopRecordedStage: async (wt) => (await stopStage({ cwd: wt })).pid,
|
|
14688
14817
|
listComposeProjects: async () => {
|
|
14689
14818
|
const { stdout } = await execFileP2("docker", ["compose", "ls", "--all", "--format", "json"], { timeout: GC_GH_TIMEOUT_MS });
|
|
@@ -14746,7 +14875,7 @@ pr.command("merge <number>").description("merge a PR (squash by default) and cle
|
|
|
14746
14875
|
} : await cleanupPrMergeLocalBranch(headRef, {
|
|
14747
14876
|
beforeWorktrees,
|
|
14748
14877
|
startingPath,
|
|
14749
|
-
pathExists: (p) => (0,
|
|
14878
|
+
pathExists: (p) => (0, import_node_fs15.existsSync)(p),
|
|
14750
14879
|
execGit: async (args) => (await execFileP2("git", args, { timeout: GIT_TIMEOUT_MS })).stdout,
|
|
14751
14880
|
teardownWorktreeStage,
|
|
14752
14881
|
deferredStore,
|
|
@@ -14899,7 +15028,7 @@ function rawValues(flag) {
|
|
|
14899
15028
|
return out;
|
|
14900
15029
|
}
|
|
14901
15030
|
function printLine(value) {
|
|
14902
|
-
(0,
|
|
15031
|
+
(0, import_node_fs15.writeSync)(1, `${value}
|
|
14903
15032
|
`);
|
|
14904
15033
|
}
|
|
14905
15034
|
function stageKeepAlive() {
|
|
@@ -14916,8 +15045,8 @@ async function resolveStage() {
|
|
|
14916
15045
|
local,
|
|
14917
15046
|
shell: shellFor(),
|
|
14918
15047
|
registry: { deployModel: project2?.deployModel, portRange, error: read.ok ? void 0 : read.error },
|
|
14919
|
-
hasCompose: (0,
|
|
14920
|
-
hasEnvExample: (0,
|
|
15048
|
+
hasCompose: (0, import_node_fs15.existsSync)((0, import_node_path14.join)(process.cwd(), "docker-compose.yml")),
|
|
15049
|
+
hasEnvExample: (0, import_node_fs15.existsSync)((0, import_node_path14.join)(process.cwd(), ".env.example"))
|
|
14921
15050
|
});
|
|
14922
15051
|
}
|
|
14923
15052
|
function stageStepsFor(res, stops = true) {
|
|
@@ -14953,9 +15082,9 @@ program2.command("port-range <repo>").description("assign (idempotently) + print
|
|
|
14953
15082
|
printLine(o.json ? JSON.stringify({ repo, portRange: [start2, end2], source: "meta" }) : `${repo}: stage.portRange [${start2}, ${end2}]`);
|
|
14954
15083
|
return;
|
|
14955
15084
|
}
|
|
14956
|
-
const path2 = (0,
|
|
15085
|
+
const path2 = (0, import_node_path14.join)(process.cwd(), "infra", "port-ranges.json");
|
|
14957
15086
|
const allocate = async (seed) => {
|
|
14958
|
-
const { stdout } = await execFileP2("node", [(0,
|
|
15087
|
+
const { stdout } = await execFileP2("node", [(0, import_node_path14.join)(process.cwd(), "infra", "port-ddb.mjs"), String(seed)], { timeout: 15e3 });
|
|
14959
15088
|
const parsed = JSON.parse(stdout);
|
|
14960
15089
|
if (!Array.isArray(parsed.range) || parsed.range.length !== 2) throw new Error("port-ddb: no range in output");
|
|
14961
15090
|
return parsed.range;
|
|
@@ -15289,7 +15418,7 @@ bootstrap.command("verify <repo>").description("audit whether an existing repo i
|
|
|
15289
15418
|
const report = await verifyBootstrap(repo, o.class, {
|
|
15290
15419
|
client: defaultGitHubClient(),
|
|
15291
15420
|
projectMeta: meta,
|
|
15292
|
-
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0,
|
|
15421
|
+
readLocalFile: (path2) => path2 === "projects.json" && apiProjects != null ? apiProjects : (0, import_node_fs15.existsSync)(path2) ? (0, import_node_fs15.readFileSync)(path2, "utf8") : null,
|
|
15293
15422
|
// requiredGcpApis is stored as an array by a JSON write, but `project set --var KEY=VALUE` stores a raw
|
|
15294
15423
|
// comma-string — accept either so the seeded value verifies regardless of how it was written.
|
|
15295
15424
|
requiredGcpApis: (() => {
|
|
@@ -15332,12 +15461,12 @@ bootstrap.command("apply <repo>").description("idempotent seed apply from skills
|
|
|
15332
15461
|
return fail(`bootstrap apply: ${e.message}`);
|
|
15333
15462
|
}
|
|
15334
15463
|
const manifestPath = "skills/bootstrap/seeds/manifest.json";
|
|
15335
|
-
if (!(0,
|
|
15336
|
-
const manifest = loadBootstrapSeeds((0,
|
|
15464
|
+
if (!(0, import_node_fs15.existsSync)(manifestPath)) return fail(`bootstrap apply: ${manifestPath} not found; run from the MMI-Hub repo root`);
|
|
15465
|
+
const manifest = loadBootstrapSeeds((0, import_node_fs15.readFileSync)(manifestPath, "utf8"));
|
|
15337
15466
|
const baseBranch = o.class === "content" ? "main" : "development";
|
|
15338
15467
|
const slug = parsedRepo.slug;
|
|
15339
15468
|
const gh = async (args) => execFileP2("gh", args, { timeout: 2e4 });
|
|
15340
|
-
const readFile5 = (p) => (0,
|
|
15469
|
+
const readFile5 = (p) => (0, import_node_fs15.existsSync)(p) ? (0, import_node_fs15.readFileSync)(p, "utf8") : null;
|
|
15341
15470
|
const enc2 = (p) => p.split("/").map(encodeURIComponent).join("/");
|
|
15342
15471
|
const rawVars = {};
|
|
15343
15472
|
for (const value of rawValues("--var")) {
|
|
@@ -15546,16 +15675,16 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
15546
15675
|
if (o.class !== "deployable" && o.class !== "content") return failGraceful("access audit: --class must be deployable or content");
|
|
15547
15676
|
targets = [{ repo: o.repo, class: o.class }];
|
|
15548
15677
|
} else {
|
|
15549
|
-
const projectsJson = registryProjects ? JSON.stringify({ projects: registryProjects }) : (0,
|
|
15678
|
+
const projectsJson = registryProjects ? JSON.stringify({ projects: registryProjects }) : (0, import_node_fs15.existsSync)("projects.json") ? (0, import_node_fs15.readFileSync)("projects.json", "utf8") : null;
|
|
15550
15679
|
if (!projectsJson) return failGraceful("access audit: no project registry \u2014 Hub API unreachable and projects.json not found; run from the MMI-Hub repo root or pass --repo <owner/repo>");
|
|
15551
|
-
const fanoutJson = (0,
|
|
15680
|
+
const fanoutJson = (0, import_node_fs15.existsSync)(".github/fanout-targets.json") ? (0, import_node_fs15.readFileSync)(".github/fanout-targets.json", "utf8") : null;
|
|
15552
15681
|
targets = loadAccessTargets(projectsJson, fanoutJson);
|
|
15553
15682
|
}
|
|
15554
15683
|
const derivedMatrix = registryProjects ? accessMatrixFromProjects(registryProjects) : {};
|
|
15555
|
-
const fileMatrix = (0,
|
|
15684
|
+
const fileMatrix = (0, import_node_fs15.existsSync)("access-matrix.json") ? loadAccessMatrix((0, import_node_fs15.readFileSync)("access-matrix.json", "utf8")) : {};
|
|
15556
15685
|
const matrix = mergeAccessMatrix(fileMatrix, derivedMatrix);
|
|
15557
15686
|
const derivedContracts = registryProjects ? dataAccessContractsFromProjects(registryProjects) : { consumers: {} };
|
|
15558
|
-
const fileContracts = (0,
|
|
15687
|
+
const fileContracts = (0, import_node_fs15.existsSync)("data-access-contracts.json") ? loadDataAccessContracts((0, import_node_fs15.readFileSync)("data-access-contracts.json", "utf8")) : { consumers: {} };
|
|
15559
15688
|
const dataAccess = mergeDataAccessContracts(fileContracts, derivedContracts);
|
|
15560
15689
|
const report = await auditOrgAccess(targets, deps, matrix, dataAccess);
|
|
15561
15690
|
console.log(o.json ? JSON.stringify(report, null, 2) : renderAccessReport(report));
|
|
@@ -15564,20 +15693,20 @@ access.command("audit").description("audit collaborator roles + train-branch pus
|
|
|
15564
15693
|
var isWin = process.platform === "win32";
|
|
15565
15694
|
var installedPluginsPath = (surface = detectSurface(process.env)) => {
|
|
15566
15695
|
const homeDir = surface === "codex" ? ".codex" : ".claude";
|
|
15567
|
-
return (0,
|
|
15696
|
+
return (0, import_node_path14.join)((0, import_node_os5.homedir)(), homeDir, "plugins", "installed_plugins.json");
|
|
15568
15697
|
};
|
|
15569
15698
|
function readInstalledPlugins() {
|
|
15570
15699
|
try {
|
|
15571
|
-
return JSON.parse((0,
|
|
15700
|
+
return JSON.parse((0, import_node_fs15.readFileSync)(installedPluginsPath(), "utf8"));
|
|
15572
15701
|
} catch {
|
|
15573
15702
|
return null;
|
|
15574
15703
|
}
|
|
15575
15704
|
}
|
|
15576
15705
|
function installedPluginSources() {
|
|
15577
15706
|
return ["claude", "codex"].map((surface) => {
|
|
15578
|
-
const recordPath = (0,
|
|
15707
|
+
const recordPath = (0, import_node_path14.join)((0, import_node_os5.homedir)(), `.${surface}`, "plugins", "installed_plugins.json");
|
|
15579
15708
|
try {
|
|
15580
|
-
return { surface, installed: JSON.parse((0,
|
|
15709
|
+
return { surface, installed: JSON.parse((0, import_node_fs15.readFileSync)(recordPath, "utf8")), recordPath };
|
|
15581
15710
|
} catch {
|
|
15582
15711
|
return { surface, installed: null, recordPath };
|
|
15583
15712
|
}
|
|
@@ -15585,7 +15714,7 @@ function installedPluginSources() {
|
|
|
15585
15714
|
}
|
|
15586
15715
|
function readClaudeSettings() {
|
|
15587
15716
|
try {
|
|
15588
|
-
return JSON.parse((0,
|
|
15717
|
+
return JSON.parse((0, import_node_fs15.readFileSync)((0, import_node_path14.join)(process.cwd(), ".claude", "settings.json"), "utf8"));
|
|
15589
15718
|
} catch {
|
|
15590
15719
|
return null;
|
|
15591
15720
|
}
|
|
@@ -15607,7 +15736,7 @@ function writeProjectInstallRecord(record) {
|
|
|
15607
15736
|
const list = file.plugins[MMI_PLUGIN_ID] ?? [];
|
|
15608
15737
|
list.push(record);
|
|
15609
15738
|
file.plugins[MMI_PLUGIN_ID] = list;
|
|
15610
|
-
(0,
|
|
15739
|
+
(0, import_node_fs15.writeFileSync)(installedPluginsPath(), `${JSON.stringify(file, null, 2)}
|
|
15611
15740
|
`, "utf8");
|
|
15612
15741
|
return true;
|
|
15613
15742
|
} catch {
|
|
@@ -15620,9 +15749,9 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
|
|
|
15620
15749
|
if (!file) return false;
|
|
15621
15750
|
if (!file.plugins) file.plugins = {};
|
|
15622
15751
|
const path2 = installedPluginsPath();
|
|
15623
|
-
(0,
|
|
15752
|
+
(0, import_node_fs15.copyFileSync)(path2, `${path2}.bak`);
|
|
15624
15753
|
file.plugins[pluginId] = records;
|
|
15625
|
-
(0,
|
|
15754
|
+
(0, import_node_fs15.writeFileSync)(path2, `${JSON.stringify(file, null, 2)}
|
|
15626
15755
|
`, "utf8");
|
|
15627
15756
|
return true;
|
|
15628
15757
|
} catch {
|
|
@@ -15630,29 +15759,37 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
|
|
|
15630
15759
|
}
|
|
15631
15760
|
}
|
|
15632
15761
|
function cursorPluginCacheRoot() {
|
|
15633
|
-
return (0,
|
|
15762
|
+
return (0, import_node_path14.join)((0, import_node_os5.homedir)(), ".cursor", "plugins", "cache", "mmi", "mmi");
|
|
15634
15763
|
}
|
|
15635
15764
|
function cursorPluginCachePinSnapshots() {
|
|
15636
15765
|
const root = cursorPluginCacheRoot();
|
|
15637
15766
|
try {
|
|
15638
|
-
return (0,
|
|
15639
|
-
const path2 = (0,
|
|
15640
|
-
const pluginJson = (0,
|
|
15641
|
-
const hooksJson = (0,
|
|
15642
|
-
const cliBundle = (0,
|
|
15767
|
+
return (0, import_node_fs15.readdirSync)(root, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")).map((entry) => {
|
|
15768
|
+
const path2 = (0, import_node_path14.join)(root, entry.name);
|
|
15769
|
+
const pluginJson = (0, import_node_path14.join)(path2, ".cursor-plugin", "plugin.json");
|
|
15770
|
+
const hooksJson = (0, import_node_path14.join)(path2, "hooks", "hooks.json");
|
|
15771
|
+
const cliBundle = (0, import_node_path14.join)(path2, "cli", "dist", "index.cjs");
|
|
15772
|
+
let version;
|
|
15773
|
+
try {
|
|
15774
|
+
const raw = JSON.parse((0, import_node_fs15.readFileSync)(pluginJson, "utf8"));
|
|
15775
|
+
version = typeof raw.version === "string" ? raw.version : void 0;
|
|
15776
|
+
} catch {
|
|
15777
|
+
version = void 0;
|
|
15778
|
+
}
|
|
15643
15779
|
let isEmpty = true;
|
|
15644
15780
|
try {
|
|
15645
|
-
isEmpty = (0,
|
|
15781
|
+
isEmpty = (0, import_node_fs15.readdirSync)(path2).length === 0;
|
|
15646
15782
|
} catch {
|
|
15647
15783
|
isEmpty = true;
|
|
15648
15784
|
}
|
|
15649
15785
|
return {
|
|
15650
15786
|
name: entry.name,
|
|
15651
15787
|
path: path2,
|
|
15652
|
-
hasPluginJson: (0,
|
|
15653
|
-
hasHooksJson: (0,
|
|
15654
|
-
hasCliBundle: (0,
|
|
15655
|
-
isEmpty
|
|
15788
|
+
hasPluginJson: (0, import_node_fs15.existsSync)(pluginJson),
|
|
15789
|
+
hasHooksJson: (0, import_node_fs15.existsSync)(hooksJson),
|
|
15790
|
+
hasCliBundle: (0, import_node_fs15.existsSync)(cliBundle),
|
|
15791
|
+
isEmpty,
|
|
15792
|
+
version
|
|
15656
15793
|
};
|
|
15657
15794
|
});
|
|
15658
15795
|
} catch {
|
|
@@ -15660,19 +15797,19 @@ function cursorPluginCachePinSnapshots() {
|
|
|
15660
15797
|
}
|
|
15661
15798
|
}
|
|
15662
15799
|
function hubCheckoutForCursorSeed() {
|
|
15663
|
-
const manifest = (0,
|
|
15664
|
-
return (0,
|
|
15800
|
+
const manifest = (0, import_node_path14.join)(process.cwd(), "plugins", "mmi", ".cursor-plugin", "plugin.json");
|
|
15801
|
+
return (0, import_node_fs15.existsSync)(manifest) ? process.cwd() : void 0;
|
|
15665
15802
|
}
|
|
15666
15803
|
function mmiPluginCacheRootSnapshots() {
|
|
15667
15804
|
const roots = [
|
|
15668
|
-
{ surface: "claude", root: (0,
|
|
15669
|
-
{ surface: "codex", root: (0,
|
|
15805
|
+
{ surface: "claude", root: (0, import_node_path14.join)((0, import_node_os5.homedir)(), ".claude", "plugins", "cache", "mmi", "mmi") },
|
|
15806
|
+
{ surface: "codex", root: (0, import_node_path14.join)((0, import_node_os5.homedir)(), ".codex", "plugins", "cache", "mmi", "mmi") }
|
|
15670
15807
|
];
|
|
15671
15808
|
return roots.flatMap(({ surface, root }) => {
|
|
15672
15809
|
try {
|
|
15673
|
-
const entries = (0,
|
|
15810
|
+
const entries = (0, import_node_fs15.readdirSync)(root, { withFileTypes: true }).map((entry) => ({
|
|
15674
15811
|
name: entry.name,
|
|
15675
|
-
path: (0,
|
|
15812
|
+
path: (0, import_node_path14.join)(root, entry.name),
|
|
15676
15813
|
isDirectory: entry.isDirectory()
|
|
15677
15814
|
}));
|
|
15678
15815
|
return [{ surface, root, entries }];
|
|
@@ -15683,7 +15820,7 @@ function mmiPluginCacheRootSnapshots() {
|
|
|
15683
15820
|
}
|
|
15684
15821
|
function hasNestedMmiChild(versionDir) {
|
|
15685
15822
|
try {
|
|
15686
|
-
return (0,
|
|
15823
|
+
return (0, import_node_fs15.statSync)((0, import_node_path14.join)(versionDir, "mmi")).isDirectory();
|
|
15687
15824
|
} catch {
|
|
15688
15825
|
return false;
|
|
15689
15826
|
}
|
|
@@ -15694,10 +15831,10 @@ function nestedPluginTreeSnapshot() {
|
|
|
15694
15831
|
);
|
|
15695
15832
|
}
|
|
15696
15833
|
function uniqueQuarantineTarget(path2) {
|
|
15697
|
-
if (!(0,
|
|
15834
|
+
if (!(0, import_node_fs15.existsSync)(path2)) return path2;
|
|
15698
15835
|
for (let i = 1; i < 100; i += 1) {
|
|
15699
15836
|
const candidate = `${path2}-${i}`;
|
|
15700
|
-
if (!(0,
|
|
15837
|
+
if (!(0, import_node_fs15.existsSync)(candidate)) return candidate;
|
|
15701
15838
|
}
|
|
15702
15839
|
return `${path2}-${Date.now()}`;
|
|
15703
15840
|
}
|
|
@@ -15705,32 +15842,32 @@ function quarantinePluginCacheDirs(plan2) {
|
|
|
15705
15842
|
let moved = 0;
|
|
15706
15843
|
for (const move of plan2) {
|
|
15707
15844
|
try {
|
|
15708
|
-
if (!(0,
|
|
15845
|
+
if (!(0, import_node_fs15.existsSync)(move.from)) continue;
|
|
15709
15846
|
const target = uniqueQuarantineTarget(move.to);
|
|
15710
|
-
(0,
|
|
15711
|
-
(0,
|
|
15847
|
+
(0, import_node_fs15.mkdirSync)((0, import_node_path14.dirname)(target), { recursive: true });
|
|
15848
|
+
(0, import_node_fs15.renameSync)(move.from, target);
|
|
15712
15849
|
moved += 1;
|
|
15713
15850
|
} catch {
|
|
15714
15851
|
}
|
|
15715
15852
|
}
|
|
15716
15853
|
return moved;
|
|
15717
15854
|
}
|
|
15718
|
-
var gitignorePath = () => (0,
|
|
15855
|
+
var gitignorePath = () => (0, import_node_path14.join)(process.cwd(), ".gitignore");
|
|
15719
15856
|
function readTextFile(path2) {
|
|
15720
15857
|
try {
|
|
15721
|
-
if (!(0,
|
|
15722
|
-
return (0,
|
|
15858
|
+
if (!(0, import_node_fs15.existsSync)(path2)) return null;
|
|
15859
|
+
return (0, import_node_fs15.readFileSync)(path2, "utf8");
|
|
15723
15860
|
} catch {
|
|
15724
15861
|
return null;
|
|
15725
15862
|
}
|
|
15726
15863
|
}
|
|
15727
15864
|
function playwrightMcpConfigSnapshots() {
|
|
15728
15865
|
const cwd = process.cwd();
|
|
15729
|
-
const home = (0,
|
|
15866
|
+
const home = (0, import_node_os5.homedir)();
|
|
15730
15867
|
const candidates = [
|
|
15731
|
-
(0,
|
|
15732
|
-
(0,
|
|
15733
|
-
(0,
|
|
15868
|
+
(0, import_node_path14.join)(cwd, ".cursor", "mcp.json"),
|
|
15869
|
+
(0, import_node_path14.join)(home, ".cursor", "mcp.json"),
|
|
15870
|
+
(0, import_node_path14.join)(home, ".codex", "config.toml")
|
|
15734
15871
|
];
|
|
15735
15872
|
const out = [];
|
|
15736
15873
|
for (const path2 of candidates) {
|
|
@@ -15743,7 +15880,7 @@ function strayBrowserArtifactPaths() {
|
|
|
15743
15880
|
const cwd = process.cwd();
|
|
15744
15881
|
return STRAY_BROWSER_ARTIFACT_DIRS.filter((rel) => {
|
|
15745
15882
|
try {
|
|
15746
|
-
return (0,
|
|
15883
|
+
return (0, import_node_fs15.existsSync)((0, import_node_path14.join)(cwd, rel));
|
|
15747
15884
|
} catch {
|
|
15748
15885
|
return false;
|
|
15749
15886
|
}
|
|
@@ -15751,14 +15888,14 @@ function strayBrowserArtifactPaths() {
|
|
|
15751
15888
|
}
|
|
15752
15889
|
function readGitignore() {
|
|
15753
15890
|
try {
|
|
15754
|
-
return (0,
|
|
15891
|
+
return (0, import_node_fs15.readFileSync)(gitignorePath(), "utf8");
|
|
15755
15892
|
} catch {
|
|
15756
15893
|
return null;
|
|
15757
15894
|
}
|
|
15758
15895
|
}
|
|
15759
15896
|
function writeGitignore(content) {
|
|
15760
15897
|
try {
|
|
15761
|
-
(0,
|
|
15898
|
+
(0, import_node_fs15.writeFileSync)(gitignorePath(), content, "utf8");
|
|
15762
15899
|
return true;
|
|
15763
15900
|
} catch {
|
|
15764
15901
|
return false;
|
|
@@ -15797,7 +15934,7 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
15797
15934
|
let onPath = pathProbe;
|
|
15798
15935
|
if (!onPath) {
|
|
15799
15936
|
const root = process.env.CLAUDE_PLUGIN_ROOT;
|
|
15800
|
-
if (root && (0,
|
|
15937
|
+
if (root && (0, import_node_fs15.existsSync)(`${root}/bin/mmi-cli${isWin ? ".cmd" : ""}`)) onPath = true;
|
|
15801
15938
|
}
|
|
15802
15939
|
checks.push({ ok: onPath, label: "mmi-cli on PATH", fix: "auto-provisioned at session start \u2014 reopen the session, or install the MMI plugin" });
|
|
15803
15940
|
const surface = detectSurface(process.env);
|
|
@@ -15926,17 +16063,50 @@ async function runDoctor(opts, io = consoleIo) {
|
|
|
15926
16063
|
})
|
|
15927
16064
|
);
|
|
15928
16065
|
const cursorCacheRoot = cursorPluginCacheRoot();
|
|
16066
|
+
let cursorPins = cursorPluginCachePinSnapshots() ?? [];
|
|
16067
|
+
let cursorPluginCheck = buildCursorPluginInstallCheck({
|
|
16068
|
+
isOrgRepo: Boolean(cfg.sagaApiUrl),
|
|
16069
|
+
surface,
|
|
16070
|
+
cacheRoot: cursorCacheRoot,
|
|
16071
|
+
cacheRootExists: (0, import_node_fs15.existsSync)(cursorCacheRoot),
|
|
16072
|
+
pins: cursorPins,
|
|
16073
|
+
hubCheckout: hubCheckoutForCursorSeed(),
|
|
16074
|
+
releasedVersion
|
|
16075
|
+
});
|
|
16076
|
+
if (!cursorPluginCheck.ok && repairLocal) {
|
|
16077
|
+
const seeded = await applyCursorPluginCacheSeed({
|
|
16078
|
+
pins: cursorPins,
|
|
16079
|
+
releasedVersion,
|
|
16080
|
+
hubCheckout: hubCheckoutForCursorSeed(),
|
|
16081
|
+
execFileP: execFileP2,
|
|
16082
|
+
mkdtemp: (prefix) => (0, import_promises5.mkdtemp)((0, import_node_path14.join)((0, import_node_os5.tmpdir)(), prefix)),
|
|
16083
|
+
log: (m) => io.err(m)
|
|
16084
|
+
});
|
|
16085
|
+
if (seeded) {
|
|
16086
|
+
cursorPins = cursorPluginCachePinSnapshots() ?? [];
|
|
16087
|
+
cursorPluginCheck = buildCursorPluginInstallCheck({
|
|
16088
|
+
isOrgRepo: Boolean(cfg.sagaApiUrl),
|
|
16089
|
+
surface,
|
|
16090
|
+
cacheRoot: cursorCacheRoot,
|
|
16091
|
+
cacheRootExists: (0, import_node_fs15.existsSync)(cursorCacheRoot),
|
|
16092
|
+
pins: cursorPins,
|
|
16093
|
+
hubCheckout: hubCheckoutForCursorSeed(),
|
|
16094
|
+
releasedVersion
|
|
16095
|
+
});
|
|
16096
|
+
if (cursorPluginCheck.ok) {
|
|
16097
|
+
io.err(` \u21BB seeded Cursor MMI plugin cache \u2192 ${releasedVersion ?? "latest"} \u2014 ${reloadAction(surface)}`);
|
|
16098
|
+
}
|
|
16099
|
+
}
|
|
16100
|
+
}
|
|
16101
|
+
checks.push(cursorPluginCheck);
|
|
16102
|
+
const cursorThirdPartyEnabled = await readCursorThirdPartyExtensibilityEnabled(execFileP2);
|
|
15929
16103
|
checks.push(
|
|
15930
|
-
|
|
16104
|
+
buildCursorThirdPartyExtensibilityCheck({
|
|
15931
16105
|
isOrgRepo: Boolean(cfg.sagaApiUrl),
|
|
15932
16106
|
surface,
|
|
15933
|
-
|
|
15934
|
-
cacheRootExists: (0, import_node_fs14.existsSync)(cursorCacheRoot),
|
|
15935
|
-
pins: cursorPluginCachePinSnapshots() ?? [],
|
|
15936
|
-
hubCheckout: hubCheckoutForCursorSeed()
|
|
16107
|
+
enabled: cursorThirdPartyEnabled
|
|
15937
16108
|
})
|
|
15938
16109
|
);
|
|
15939
|
-
const cursorPins = cursorPluginCachePinSnapshots() ?? [];
|
|
15940
16110
|
checks.push(
|
|
15941
16111
|
buildCursorHookCliCheck({
|
|
15942
16112
|
isOrgRepo: Boolean(cfg.sagaApiUrl),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mutmutco/cli",
|
|
3
|
-
"version": "2.32.
|
|
3
|
+
"version": "2.32.3",
|
|
4
4
|
"description": "MMI Future CLI — delivers the org rules (whole-file), plus saga and KB access. The cross-IDE engine the plugin's SessionStart hook drives.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|