@mutmutco/cli 2.63.0 → 2.65.0

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.
Files changed (3) hide show
  1. package/dist/main.cjs +125 -74
  2. package/package.json +1 -1
  3. package/dist/saga.cjs +0 -5245
package/dist/main.cjs CHANGED
@@ -3439,7 +3439,7 @@ function resolveRulesBase(orgRulesSource, defaultBase) {
3439
3439
  }
3440
3440
 
3441
3441
  // src/index.ts
3442
- var import_node_child_process10 = require("node:child_process");
3442
+ var import_node_child_process11 = require("node:child_process");
3443
3443
 
3444
3444
  // src/cli-shared.ts
3445
3445
  var import_promises = require("node:fs/promises");
@@ -13518,6 +13518,7 @@ function writeError(res) {
13518
13518
  }
13519
13519
 
13520
13520
  // src/secrets.ts
13521
+ var import_node_child_process8 = require("node:child_process");
13521
13522
  var OWNER2 = "mutmutco";
13522
13523
  var SSM_ROOT = "/mmi-future";
13523
13524
  var PROJECT_TIER_SEGMENT = "dev";
@@ -13568,7 +13569,8 @@ function formatVaultPointer(p) {
13568
13569
  ...Object.entries(p.wellKnown).map(([k, keys]) => ` ${k}: ${keys.join(", ")}`),
13569
13570
  ``,
13570
13571
  `enumerate actual keys: mmi-cli secrets list`,
13571
- `read one: mmi-cli secrets get <stage>/<KEY> (e.g. main/GOOGLE_CLIENT_ID)`,
13572
+ `use one keyless: mmi-cli secrets use <stage>/<KEY> -- <cmd> (inject, never printed; raw get is master-only, WS6 #2184)`,
13573
+ `validate one: mmi-cli secrets verify <KEY>`,
13572
13574
  `set a key: mmi-cli secrets set <stage>/<KEY> (value via stdin; project-admin self-serves own repo)`,
13573
13575
  `import Rails creds: mmi-cli secrets import-rails-credentials --stage main --map secret_key_base=SECRET_KEY_BASE`,
13574
13576
  `copy provider keys: mmi-cli secrets copy --from rc --to dev --keys RECALL_API_KEY,GEMINI_API_KEY`
@@ -13664,7 +13666,7 @@ async function fetchSecretValue(deps, key, opts) {
13664
13666
  const res = await deps.fetch(`${deps.apiUrl}/secrets/get`, {
13665
13667
  method: "POST",
13666
13668
  headers: await deps.headers({ "content-type": "application/json" }),
13667
- body: JSON.stringify({ repo, key, ...slug ? { slug } : {} }),
13669
+ body: JSON.stringify({ repo, key, use: true, ...slug ? { slug } : {} }),
13668
13670
  signal: AbortSignal.timeout(TIMEOUT_MS)
13669
13671
  });
13670
13672
  if (!res.ok) return null;
@@ -13718,7 +13720,7 @@ no vault credentials visible`;
13718
13720
  "",
13719
13721
  ...lines,
13720
13722
  "",
13721
- "+ = readable/usable now. Read one value: `mmi-cli secrets get <stage>/<KEY>` (only `get` prints a value).",
13723
+ "+ = usable now. Consume keyless: `mmi-cli secrets use <KEY> -- <cmd>` (inject, never printed); validate: `mmi-cli secrets verify <KEY>`. Raw `get` (prints the value) is master-only (WS6 #2184).",
13722
13724
  " Cross-slug (org-infra): `mmi-cli secrets get --slug cloudflare GLOBAL_API_KEY` (master-gated)."
13723
13725
  ].join("\n");
13724
13726
  }
@@ -14244,21 +14246,64 @@ async function secretsCopy(deps, opts) {
14244
14246
  }
14245
14247
  return true;
14246
14248
  }
14249
+ function defaultSpawn(command, args, env) {
14250
+ const r = (0, import_node_child_process8.spawnSync)(command, args, { stdio: "inherit", env });
14251
+ if (r.error) throw r.error;
14252
+ return r.status ?? 1;
14253
+ }
14247
14254
  async function secretsUse(deps, key, opts) {
14248
- const slug = await vaultSlug(deps, opts);
14249
- const tier = classifyTier(slug, key);
14250
- const path2 = secretParamName(slug, key);
14251
- deps.log(
14252
- [
14253
- `${key} \u2192 ${path2} (${tier} tier)`,
14254
- "",
14255
- "Consume it WITHOUT committing it:",
14256
- ` \u2022 Runtime / agents: read it keylessly at runtime via the box's OIDC role (it can read its own ${tier} tier). Never bake it into an image or commit it.`,
14257
- ` \u2022 CI (GitHub Actions): the workflow assumes its OIDC role and runs \`aws ssm get-parameter --with-decryption --name ${path2}\` \u2014 no GitHub secret.`,
14258
- " \u2022 Local dev: pull it into a gitignored .env from the vault. To confirm access without printing in PowerShell: `$null = mmi-cli secrets get " + key + "`; in POSIX shells: `mmi-cli secrets get " + key + " >/dev/null`. Never paste it into tracked files or chat.",
14259
- tier === "project" ? " \u2022 Bare keys default to dev/. Use an explicit rc/<KEY> or main/<KEY> when the stage needs its own value." : " \u2022 For your own product repo, project-admins self-serve this stage key. Org-infra/cross-slug keys remain master-gated."
14260
- ].join("\n")
14261
- );
14255
+ const command = opts.command ?? [];
14256
+ if (command.length === 0) {
14257
+ const slug2 = await vaultSlug(deps, opts);
14258
+ const tier = classifyTier(slug2, key);
14259
+ const path2 = secretParamName(slug2, key);
14260
+ deps.log(
14261
+ [
14262
+ `${key} \u2192 ${path2} (${tier} tier)`,
14263
+ "",
14264
+ "Consume it WITHOUT committing it:",
14265
+ ` \u2022 Keyless run: \`mmi-cli secrets use ${key}${opts.slug ? ` --slug ${opts.slug}` : ""} -- <command>\` injects it into the command's env (never printed). A granted non-master can USE an org secret this way.`,
14266
+ ` \u2022 Runtime / agents: read it keylessly at runtime via the box's OIDC role. Never bake it into an image or commit it.`,
14267
+ ` \u2022 CI (GitHub Actions): the workflow assumes its OIDC role and runs \`aws ssm get-parameter --with-decryption --name ${path2}\` \u2014 no GitHub secret.`,
14268
+ tier === "project" ? " \u2022 Bare keys default to dev/. Use an explicit rc/<KEY> or main/<KEY> when the stage needs its own value." : " \u2022 For your own product repo, project-admins self-serve this stage key. Org-infra/cross-slug keys remain master-gated unless granted."
14269
+ ].join("\n")
14270
+ );
14271
+ return;
14272
+ }
14273
+ if (!isValidSecretKey(key)) {
14274
+ deps.err(`invalid secret key ${JSON.stringify(key)}`);
14275
+ return false;
14276
+ }
14277
+ const repo = await targetRepo(deps, opts);
14278
+ const slug = opts.slug?.toLowerCase();
14279
+ const res = await deps.fetch(`${deps.apiUrl}/secrets/get`, {
14280
+ method: "POST",
14281
+ headers: await deps.headers({ "content-type": "application/json" }),
14282
+ body: JSON.stringify({ repo, key, use: true, ...slug ? { slug } : {} }),
14283
+ signal: AbortSignal.timeout(TIMEOUT_MS)
14284
+ });
14285
+ if (!res.ok) {
14286
+ const body = await readJsonBody(res);
14287
+ if (res.status === 404 && body.code === "secret_not_found") {
14288
+ deps.err(`Secret ${key} was not found.`);
14289
+ deps.err(`Approved escalation: mmi-cli secrets request ${key} --repo ${repo} --reason "<why it is needed>"`);
14290
+ return false;
14291
+ }
14292
+ deps.err(
14293
+ await upgradeMessage(res, body) ?? (res.status === 403 ? `secrets use: not authorized for ${key} (HTTP 403) \u2014 ask the master for a \`secrets grant\`${errorDetail(body)}` : `secrets use failed: HTTP ${res.status}${errorDetail(body)}`)
14294
+ );
14295
+ return false;
14296
+ }
14297
+ const { value } = await res.json();
14298
+ if (!value) {
14299
+ deps.err(`secrets use: no value returned for ${key}`);
14300
+ return false;
14301
+ }
14302
+ const envName = (opts.name ?? secretLeafName(key)).toUpperCase().replace(/[^A-Z0-9_]/g, "_");
14303
+ const [cmd, ...args] = command;
14304
+ const run = deps.spawn ?? defaultSpawn;
14305
+ const code = run(cmd, args, { ...process.env, [envName]: value });
14306
+ return code === 0;
14262
14307
  }
14263
14308
 
14264
14309
  // src/secrets-commands.ts
@@ -14378,7 +14423,7 @@ function registerSecretsCommands(program3) {
14378
14423
  const ok = await secretsPreflight(d, { repo: o.repo, stage: o.stage, required });
14379
14424
  if (!ok) process.exitCode = 1;
14380
14425
  });
14381
- secrets.command("get <key>").description("print one secret value over TLS (prints once, raw \u2014 do not log/paste it)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--slug <slug>", "vault slug override for org-infra namespaces (cloudflare, shared, \u2026) \u2014 master-gated").action((key, o) => withSecrets(async (d) => {
14426
+ secrets.command("get <key>").description("print one secret value, RAW \u2014 MASTER-ONLY (WS6 #2184); non-master: `secrets use` to consume keyless or `secrets verify` to validate").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--slug <slug>", "vault slug override for org-infra namespaces (cloudflare, shared, \u2026) \u2014 master-gated").action((key, o) => withSecrets(async (d) => {
14382
14427
  const ok = await secretsGet(d, key, o);
14383
14428
  if (!ok) process.exitCode = 1;
14384
14429
  }));
@@ -14439,7 +14484,10 @@ function registerSecretsCommands(program3) {
14439
14484
  if (!ok) process.exitCode = 1;
14440
14485
  }));
14441
14486
  secrets.command("rm <key>").description("remove a secret (project tier self-serve; org tier needs a grant)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").action((key, o) => withSecrets((d) => secretsRemove(d, key, o)));
14442
- secrets.command("use <key>").description("print guidance on consuming a secret without committing it (no value)").option("--repo <owner/repo>", "target repo (defaults to the current repo)").action((key, o) => withSecrets((d) => secretsUse(d, key, o)));
14487
+ secrets.command("use <key> [command...]").description("consume a secret KEYLESS: `use <key> -- <cmd>` injects it into the command env (never printed); a granted non-master can use an org secret. No command prints guidance only.").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--slug <slug>", "org-infra namespace (e.g. _org) for a granted org secret").option("--name <ENVVAR>", "env var name to inject under (default: the key leaf, UPPER_SNAKE)").action((key, command, o) => withSecrets(async (d) => {
14488
+ const ok = await secretsUse(d, key, { ...o, command });
14489
+ if (ok === false) process.exitCode = 1;
14490
+ }));
14443
14491
  secrets.command("grant <repo> <login> <key>").description("MASTER-ONLY: grant a project-admin standing access to a specific org-tier secret").action((repo, login, key) => withSecrets((d) => secretsGrant(d, repo, login, key, {})));
14444
14492
  secrets.command("revoke <repo> <login> <key>").description("MASTER-ONLY: withdraw a previously granted org-tier secret access").action((repo, login, key) => withSecrets((d) => secretsRevoke(d, repo, login, key, {})));
14445
14493
  }
@@ -14510,7 +14558,7 @@ function registerEdgeCommands(program3) {
14510
14558
 
14511
14559
  // src/doctor-run.ts
14512
14560
  var import_node_fs16 = require("node:fs");
14513
- var import_node_child_process9 = require("node:child_process");
14561
+ var import_node_child_process10 = require("node:child_process");
14514
14562
  var import_promises3 = require("node:fs/promises");
14515
14563
  var import_node_path16 = require("node:path");
14516
14564
  var import_node_os5 = require("node:os");
@@ -14533,7 +14581,7 @@ function buildGuardSessionStartLine(state, opts = {}) {
14533
14581
  }
14534
14582
 
14535
14583
  // src/cursor-plugin-seed.ts
14536
- var import_node_child_process8 = require("node:child_process");
14584
+ var import_node_child_process9 = require("node:child_process");
14537
14585
  var import_node_fs13 = require("node:fs");
14538
14586
  var import_node_os4 = require("node:os");
14539
14587
  var import_node_path14 = require("node:path");
@@ -14544,7 +14592,7 @@ function isSemverVersion(v) {
14544
14592
  var MMI_HUB_REPO = "mutmutco/MMI-Hub";
14545
14593
  var CURSOR_THIRD_PARTY_STATE_KEY = "cursor/thirdPartyExtensibilityEnabled";
14546
14594
  var PLUGIN_JSON_REL = ".cursor-plugin/plugin.json";
14547
- var execFileBuffer = (0, import_node_util7.promisify)(import_node_child_process8.execFile);
14595
+ var execFileBuffer = (0, import_node_util7.promisify)(import_node_child_process9.execFile);
14548
14596
  function gitFetchReleaseTagArgs(hubCheckout, tag) {
14549
14597
  return ["-C", hubCheckout, "fetch", "origin", "tag", tag, "--quiet"];
14550
14598
  }
@@ -15398,7 +15446,7 @@ function buildOpencodeDesktopBootstrapCheck(input) {
15398
15446
  return { ...base, ok: false, fix: `${OPENCODE_DESKTOP_BOOTSTRAP_FIX} (stale: ${dirs})`, issues: [...input.issues] };
15399
15447
  }
15400
15448
  var OPENCODE_LEGACY_CONFIG_LABEL = "OpenCode legacy ~/.opencode config (stale plugin entries)";
15401
- var OPENCODE_LEGACY_CONFIG_FIX = 'remove or rename ~/.opencode/opencode.json (legacy path); configure OpenCode at ~/.config/opencode/opencode.jsonc with "plugin": ["@mutmutco/opencode-mmi"] \u2014 see docs/Guides/opencode-saga.md';
15449
+ var OPENCODE_LEGACY_CONFIG_FIX = 'remove or rename ~/.opencode/opencode.json (legacy path); configure OpenCode at ~/.config/opencode/opencode.jsonc with "plugin": ["@mutmutco/opencode-mmi"]';
15402
15450
  function parseOpencodeLegacyConfigPlugins(content) {
15403
15451
  try {
15404
15452
  const parsed = JSON.parse(content);
@@ -16019,7 +16067,7 @@ function reexecMmiCli(args) {
16019
16067
  }
16020
16068
  };
16021
16069
  const env = { ...process.env, [DOCTOR_POST_SELF_UPDATE_ENV]: "1" };
16022
- const child = isWin ? (0, import_node_child_process9.spawn)("cmd.exe", ["/c", "mmi-cli", ...args], { stdio: "inherit", env }) : (0, import_node_child_process9.spawn)("mmi-cli", args, { stdio: "inherit", env });
16070
+ const child = isWin ? (0, import_node_child_process10.spawn)("cmd.exe", ["/c", "mmi-cli", ...args], { stdio: "inherit", env }) : (0, import_node_child_process10.spawn)("mmi-cli", args, { stdio: "inherit", env });
16023
16071
  child.on("error", () => done(-1));
16024
16072
  child.on("exit", (code) => done(code ?? 0));
16025
16073
  });
@@ -16503,7 +16551,7 @@ function writeGitignore(content) {
16503
16551
  return false;
16504
16552
  }
16505
16553
  }
16506
- async function runDoctor(opts, io = consoleIo) {
16554
+ async function runDoctor(opts, io = consoleIo, readOrigin) {
16507
16555
  if (opts.guide) {
16508
16556
  if (opts.json) io.log(JSON.stringify({ resources: [MMI_AGENTIC_ONBOARDING_GUIDE] }, null, 2));
16509
16557
  else io.log(MMI_AGENTIC_ONBOARDING_GUIDE.url);
@@ -16516,14 +16564,17 @@ async function runDoctor(opts, io = consoleIo) {
16516
16564
  const checks = [];
16517
16565
  const REWRITE_KEY = "url.https://github.com/.insteadOf";
16518
16566
  const CLONE_FIX = 'run: git config --global url."https://github.com/".insteadOf "git@github.com:"';
16519
- const [login, pathProbe, releasedVersion, cfg, callerArn, cloneProbe] = await Promise.all([
16567
+ const [login, pathProbe, releasedVersion, cfg, callerArn, cloneProbe, isOrgRepo] = await Promise.all([
16520
16568
  githubLogin(),
16521
16569
  execFileP2(isWin ? "where" : "which", ["mmi-cli"]).then(() => true).catch(() => false),
16522
16570
  fetchReleasedVersion(),
16523
16571
  loadConfig(),
16524
16572
  awsCallerArn(),
16525
- execFileP2("git", ["config", "--global", "--get-all", REWRITE_KEY]).then(({ stdout }) => stdout.split("\n").some((l) => l.trim() === "git@github.com:")).catch(() => false)
16573
+ execFileP2("git", ["config", "--global", "--get-all", REWRITE_KEY]).then(({ stdout }) => stdout.split("\n").some((l) => l.trim() === "git@github.com:")).catch(() => false),
16526
16574
  // unset → repair below
16575
+ // hub-v3 step-back: org-membership gates the repo-local checks/repairs by the git `origin` remote,
16576
+ // not the now-always-defaulted `cfg.sagaApiUrl`. Independent git read — resolved concurrently here.
16577
+ readOrigin ? isOrgRepoRoot(readOrigin) : isOrgRepoRoot()
16527
16578
  ]);
16528
16579
  const surface = detectSurface(process.env);
16529
16580
  const versionReportProbe = buildVersionLagReport({
@@ -16538,18 +16589,18 @@ async function runDoctor(opts, io = consoleIo) {
16538
16589
  const opencodeAdapterStale = isBehind(opencodeInstalledVersionForDoctor(), releasedVersion);
16539
16590
  const cursorCacheStale = (0, import_node_fs16.existsSync)(cursorPluginCacheRoot()) && (cursorPluginCachePinSnapshots() ?? []).some((p) => isBehind(p.version, releasedVersion));
16540
16591
  const healPlan = doctorHealPlan({
16541
- isOrgRepo: Boolean(cfg.sagaApiUrl),
16592
+ isOrgRepo,
16542
16593
  surface,
16543
16594
  versionReport: versionReportProbe,
16544
16595
  hasPluginRoot: Boolean(process.env.CLAUDE_PLUGIN_ROOT),
16545
16596
  installedVersionCheck: buildInstalledPluginVersionCheck({
16546
- isOrgRepo: Boolean(cfg.sagaApiUrl),
16597
+ isOrgRepo,
16547
16598
  sources: sourcesForHealPlan,
16548
16599
  releasedVersion,
16549
16600
  surface
16550
16601
  }),
16551
16602
  legacyPluginCheck: buildLegacyPluginInstallCheck({
16552
- isOrgRepo: Boolean(cfg.sagaApiUrl),
16603
+ isOrgRepo,
16553
16604
  sources: sourcesForHealPlan
16554
16605
  }),
16555
16606
  opencodeAdapterStale,
@@ -16608,7 +16659,7 @@ async function runDoctor(opts, io = consoleIo) {
16608
16659
  const installedVersion = resolveClientVersion();
16609
16660
  checks.push(
16610
16661
  buildHubCompatCheck({
16611
- isOrgRepo: Boolean(cfg.sagaApiUrl),
16662
+ isOrgRepo,
16612
16663
  versionInfo: hubVersionInfo,
16613
16664
  installedVersion
16614
16665
  })
@@ -16616,7 +16667,7 @@ async function runDoctor(opts, io = consoleIo) {
16616
16667
  if (runExtended) {
16617
16668
  checks.push(
16618
16669
  buildHubDeployFreshnessCheck({
16619
- isOrgRepo: Boolean(cfg.sagaApiUrl),
16670
+ isOrgRepo,
16620
16671
  deployedHubVersion: hubVersionInfo?.hubVersion,
16621
16672
  installedVersion,
16622
16673
  releasedVersion
@@ -16639,7 +16690,7 @@ async function runDoctor(opts, io = consoleIo) {
16639
16690
  const installed = readInstalledPlugins();
16640
16691
  const claudeSettings = readClaudeSettings();
16641
16692
  let pluginCheck = buildPluginInstallRecordCheck({
16642
- isOrgRepo: Boolean(cfg.sagaApiUrl),
16693
+ isOrgRepo,
16643
16694
  settings: claudeSettings,
16644
16695
  installed,
16645
16696
  projectPath: process.cwd(),
@@ -16654,7 +16705,7 @@ async function runDoctor(opts, io = consoleIo) {
16654
16705
  }
16655
16706
  checks.push(pluginCheck);
16656
16707
  let legacyPluginCheck = buildLegacyPluginInstallCheck({
16657
- isOrgRepo: Boolean(cfg.sagaApiUrl),
16708
+ isOrgRepo,
16658
16709
  sources: installedPluginSources(),
16659
16710
  surface
16660
16711
  });
@@ -16663,7 +16714,7 @@ async function runDoctor(opts, io = consoleIo) {
16663
16714
  const codexLegacy = legacyPluginCheck.staleSurfaces?.includes("codex") ?? false;
16664
16715
  if (claudeLegacy && await applyPluginHeal("claude", surface, (m) => io.err(m), { force: true })) {
16665
16716
  legacyPluginCheck = buildLegacyPluginInstallCheck({
16666
- isOrgRepo: Boolean(cfg.sagaApiUrl),
16717
+ isOrgRepo,
16667
16718
  sources: installedPluginSources(),
16668
16719
  surface
16669
16720
  });
@@ -16673,7 +16724,7 @@ async function runDoctor(opts, io = consoleIo) {
16673
16724
  }
16674
16725
  if (!legacyPluginCheck.ok && codexLegacy && await applyPluginHeal("codex", surface, (m) => io.err(m), { force: true })) {
16675
16726
  legacyPluginCheck = buildLegacyPluginInstallCheck({
16676
- isOrgRepo: Boolean(cfg.sagaApiUrl),
16727
+ isOrgRepo,
16677
16728
  sources: installedPluginSources(),
16678
16729
  surface
16679
16730
  });
@@ -16683,7 +16734,7 @@ async function runDoctor(opts, io = consoleIo) {
16683
16734
  }
16684
16735
  }
16685
16736
  checks.push(legacyPluginCheck);
16686
- let gitignoreCheck = buildGitignoreManagedBlockCheck({ isOrgRepo: Boolean(cfg.sagaApiUrl), content: readGitignore() });
16737
+ let gitignoreCheck = buildGitignoreManagedBlockCheck({ isOrgRepo, content: readGitignore() });
16687
16738
  const gitignoreDecision = decideGitignoreRepair(gitignoreCheck, { repoWritesAllowed, repairFull });
16688
16739
  gitignoreCheck = gitignoreDecision.check;
16689
16740
  if (gitignoreDecision.action === "suppress") {
@@ -16701,7 +16752,7 @@ async function runDoctor(opts, io = consoleIo) {
16701
16752
  }
16702
16753
  }
16703
16754
  checks.push(gitignoreCheck);
16704
- let driftCheck = buildPluginConfigDriftCheck({ isOrgRepo: Boolean(cfg.sagaApiUrl), installed, surface });
16755
+ let driftCheck = buildPluginConfigDriftCheck({ isOrgRepo, installed, surface });
16705
16756
  if (!driftCheck.ok && driftCheck.recordsToWrite && repairLocal) {
16706
16757
  if (backupAndWriteInstalledPlugins(driftCheck.recordsToWrite, driftCheck.pluginId)) {
16707
16758
  driftCheck = { ...driftCheck, ok: true };
@@ -16711,18 +16762,18 @@ async function runDoctor(opts, io = consoleIo) {
16711
16762
  checks.push(driftCheck);
16712
16763
  checks.push(
16713
16764
  buildPluginResolvabilityCheck({
16714
- ...snapshotPluginGuardInput(surface, Boolean(cfg.sagaApiUrl)),
16765
+ ...snapshotPluginGuardInput(surface, isOrgRepo),
16715
16766
  surface
16716
16767
  })
16717
16768
  );
16718
- if (repairFull && Boolean(cfg.sagaApiUrl) && (surfaceToken(surface) === "claude" || surfaceToken(surface) === "codex")) {
16769
+ if (repairFull && isOrgRepo && (surfaceToken(surface) === "claude" || surfaceToken(surface) === "codex")) {
16719
16770
  const guardResult = ensureUserScopeGuardHook();
16720
16771
  if (guardResult === "written") {
16721
16772
  io.err(` \u21BB installed user-scope MMI guard hook (${surface === "codex" ? "~/.codex" : "~/.claude"}/settings.json) \u2014 survives a plugin prune`);
16722
16773
  }
16723
16774
  }
16724
16775
  let installedVersionCheck = buildInstalledPluginVersionCheck({
16725
- isOrgRepo: Boolean(cfg.sagaApiUrl),
16776
+ isOrgRepo,
16726
16777
  sources: installedPluginSources(),
16727
16778
  releasedVersion,
16728
16779
  surface
@@ -16730,7 +16781,7 @@ async function runDoctor(opts, io = consoleIo) {
16730
16781
  if (!installedVersionCheck.ok && (repairFull || repairLocal)) {
16731
16782
  const crossSurfaceRepair = Boolean(opts.apply) || !opts.json && !opts.banner && !opts.preflight;
16732
16783
  const canDriveSurface = async (token) => surfaceToken(surface) === token || crossSurfaceRepair && await hostBinAvailable(token);
16733
- const rereadInstalled = () => buildInstalledPluginVersionCheck({ isOrgRepo: Boolean(cfg.sagaApiUrl), sources: installedPluginSources(), releasedVersion, surface });
16784
+ const rereadInstalled = () => buildInstalledPluginVersionCheck({ isOrgRepo, sources: installedPluginSources(), releasedVersion, surface });
16734
16785
  const claudeStale = installedVersionCheck.staleSurfaces?.some((s) => s.surface === "claude") ?? false;
16735
16786
  if (claudeStale && await canDriveSurface("claude") && await applyPluginHeal("claude", surface, (m) => io.err(m), { force: true })) {
16736
16787
  const healed = rereadInstalled();
@@ -16755,7 +16806,7 @@ async function runDoctor(opts, io = consoleIo) {
16755
16806
  const inspectOpenCode = surface === "opencode" || openCodeConfigSnapshot.hasConfig || runExtended;
16756
16807
  if (inspectOpenCode) {
16757
16808
  let opencodeConfigCheck = buildOpencodeConfigPluginCheck({
16758
- isOrgRepo: Boolean(cfg.sagaApiUrl),
16809
+ isOrgRepo,
16759
16810
  configPath: openCodeConfigSnapshot.path,
16760
16811
  hasConfig: openCodeConfigSnapshot.hasConfig,
16761
16812
  hasPluginField: openCodeConfigSnapshot.hasPluginField,
@@ -16766,7 +16817,7 @@ async function runDoctor(opts, io = consoleIo) {
16766
16817
  if (writeOpencodeConfigPlugin(openCodeConfigSnapshot)) {
16767
16818
  openCodeConfigSnapshot = opencodeConfigSnapshot();
16768
16819
  opencodeConfigCheck = buildOpencodeConfigPluginCheck({
16769
- isOrgRepo: Boolean(cfg.sagaApiUrl),
16820
+ isOrgRepo,
16770
16821
  configPath: openCodeConfigSnapshot.path,
16771
16822
  hasConfig: openCodeConfigSnapshot.hasConfig,
16772
16823
  hasPluginField: openCodeConfigSnapshot.hasPluginField,
@@ -16782,7 +16833,7 @@ async function runDoctor(opts, io = consoleIo) {
16782
16833
  checks.push(opencodeConfigCheck);
16783
16834
  let opencodeInstalledVersion = opencodeInstalledVersionForDoctor();
16784
16835
  let opencodeVersionCheck = buildOpencodeVersionCheck({
16785
- isOrgRepo: Boolean(cfg.sagaApiUrl),
16836
+ isOrgRepo,
16786
16837
  installedVersion: opencodeInstalledVersion,
16787
16838
  releasedVersion
16788
16839
  });
@@ -16790,7 +16841,7 @@ async function runDoctor(opts, io = consoleIo) {
16790
16841
  if (await forceInstallOpencodeMmiPlugins(openCodeConfigSnapshot, (m) => io.err(m))) {
16791
16842
  opencodeInstalledVersion = readOpencodeAdapterDiskVersion() ?? opencodeInstalledVersion;
16792
16843
  opencodeVersionCheck = buildOpencodeVersionCheck({
16793
- isOrgRepo: Boolean(cfg.sagaApiUrl),
16844
+ isOrgRepo,
16794
16845
  installedVersion: opencodeInstalledVersion,
16795
16846
  releasedVersion
16796
16847
  });
@@ -16802,7 +16853,7 @@ async function runDoctor(opts, io = consoleIo) {
16802
16853
  }
16803
16854
  checks.push(opencodeVersionCheck);
16804
16855
  let surfaceAssetsCheck = buildOpencodeSurfaceAssetsCheck({
16805
- isOrgRepo: Boolean(cfg.sagaApiUrl),
16856
+ isOrgRepo,
16806
16857
  configPath: openCodeConfigSnapshot.path,
16807
16858
  commandsDir: opencodeCommandsDir(),
16808
16859
  existingCommands: opencodeExistingCommands(),
@@ -16815,7 +16866,7 @@ async function runDoctor(opts, io = consoleIo) {
16815
16866
  if (wroteCommands || wroteSkills) {
16816
16867
  openCodeConfigSnapshot = opencodeConfigSnapshot();
16817
16868
  surfaceAssetsCheck = buildOpencodeSurfaceAssetsCheck({
16818
- isOrgRepo: Boolean(cfg.sagaApiUrl),
16869
+ isOrgRepo,
16819
16870
  configPath: openCodeConfigSnapshot.path,
16820
16871
  commandsDir: opencodeCommandsDir(),
16821
16872
  existingCommands: opencodeExistingCommands(),
@@ -16830,13 +16881,13 @@ async function runDoctor(opts, io = consoleIo) {
16830
16881
  }
16831
16882
  checks.push(surfaceAssetsCheck);
16832
16883
  checks.push(buildOpencodeDesktopBootstrapCheck({
16833
- isOrgRepo: Boolean(cfg.sagaApiUrl),
16884
+ isOrgRepo,
16834
16885
  surface,
16835
16886
  issues: opencodeDesktopBootstrapSnapshot()
16836
16887
  }));
16837
16888
  }
16838
16889
  const legacyOpenCodeConfig = opencodeLegacyConfigSnapshot();
16839
- if (Boolean(cfg.sagaApiUrl) && legacyOpenCodeConfig.stalePlugins?.length) {
16890
+ if (isOrgRepo && legacyOpenCodeConfig.stalePlugins?.length) {
16840
16891
  let legacyOpenCodeCheck = buildOpencodeLegacyConfigCheck({
16841
16892
  isOrgRepo: true,
16842
16893
  legacyPath: legacyOpenCodeConfig.legacyPath,
@@ -16856,7 +16907,7 @@ async function runDoctor(opts, io = consoleIo) {
16856
16907
  };
16857
16908
  const codexCacheVersions = () => mmiPluginCacheRootSnapshots().filter((r) => r.surface === "codex").flatMap((r) => r.entries.filter((e) => e.isDirectory).map((e) => e.name));
16858
16909
  let cacheCleanupCheck = buildMmiPluginCacheCleanupCheck({
16859
- isOrgRepo: Boolean(cfg.sagaApiUrl),
16910
+ isOrgRepo,
16860
16911
  roots: mmiPluginCacheRootSnapshots(),
16861
16912
  activeVersion: resolveClientVersion(),
16862
16913
  releasedVersion,
@@ -16873,7 +16924,7 @@ async function runDoctor(opts, io = consoleIo) {
16873
16924
  }
16874
16925
  cacheCleanupCheck = {
16875
16926
  ...buildMmiPluginCacheCleanupCheck({
16876
- isOrgRepo: Boolean(cfg.sagaApiUrl),
16927
+ isOrgRepo,
16877
16928
  roots: mmiPluginCacheRootSnapshots(),
16878
16929
  activeVersion: resolveClientVersion(),
16879
16930
  releasedVersion,
@@ -16887,7 +16938,7 @@ async function runDoctor(opts, io = consoleIo) {
16887
16938
  }
16888
16939
  checks.push(cacheCleanupCheck);
16889
16940
  let codexActiveCacheCheck = buildCodexActiveCacheCheck({
16890
- isOrgRepo: Boolean(cfg.sagaApiUrl),
16941
+ isOrgRepo,
16891
16942
  releasedVersion,
16892
16943
  codexCacheVersions: codexCacheVersions(),
16893
16944
  codexRecordVersion: codexRecordVersion()
@@ -16898,7 +16949,7 @@ async function runDoctor(opts, io = consoleIo) {
16898
16949
  markPluginReloadRequired();
16899
16950
  io.err(` \u21BB restored Codex MMI plugin cache \u2192 ${releasedVersion ?? "latest"} via codex plugin \u2014 ${reloadAction("codex")} to load the new commands`);
16900
16951
  codexActiveCacheCheck = buildCodexActiveCacheCheck({
16901
- isOrgRepo: Boolean(cfg.sagaApiUrl),
16952
+ isOrgRepo,
16902
16953
  releasedVersion,
16903
16954
  codexCacheVersions: codexCacheVersions(),
16904
16955
  codexRecordVersion: codexRecordVersion()
@@ -16907,7 +16958,7 @@ async function runDoctor(opts, io = consoleIo) {
16907
16958
  }
16908
16959
  checks.push(codexActiveCacheCheck);
16909
16960
  let nestedPluginTreeCheck = buildNestedPluginTreeCheck({
16910
- isOrgRepo: Boolean(cfg.sagaApiUrl),
16961
+ isOrgRepo,
16911
16962
  isWindows: isWin,
16912
16963
  entries: nestedPluginTreeSnapshot()
16913
16964
  });
@@ -16915,7 +16966,7 @@ async function runDoctor(opts, io = consoleIo) {
16915
16966
  const nestedPaths = nestedPluginTreeCheck.nested.map((n) => n.path);
16916
16967
  if (await applyNestedPluginTreeCleanup(nestedPaths, (m) => io.err(m))) {
16917
16968
  nestedPluginTreeCheck = buildNestedPluginTreeCheck({
16918
- isOrgRepo: Boolean(cfg.sagaApiUrl),
16969
+ isOrgRepo,
16919
16970
  isWindows: isWin,
16920
16971
  entries: nestedPluginTreeSnapshot()
16921
16972
  });
@@ -16926,7 +16977,7 @@ async function runDoctor(opts, io = consoleIo) {
16926
16977
  markPluginReloadRequired();
16927
16978
  io.err(` \u21BB reinstalled MMI plugin after nested-cache cleanup \u2014 ${reloadAction(surface)} to load MMI commands`);
16928
16979
  nestedPluginTreeCheck = buildNestedPluginTreeCheck({
16929
- isOrgRepo: Boolean(cfg.sagaApiUrl),
16980
+ isOrgRepo,
16930
16981
  isWindows: isWin,
16931
16982
  entries: nestedPluginTreeSnapshot()
16932
16983
  });
@@ -16937,7 +16988,7 @@ async function runDoctor(opts, io = consoleIo) {
16937
16988
  const cursorCacheRoot = cursorPluginCacheRoot();
16938
16989
  let cursorPins = cursorPluginCachePinSnapshots() ?? [];
16939
16990
  let cursorPluginCheck = buildCursorPluginInstallCheck({
16940
- isOrgRepo: Boolean(cfg.sagaApiUrl),
16991
+ isOrgRepo,
16941
16992
  surface,
16942
16993
  cacheRoot: cursorCacheRoot,
16943
16994
  cacheRootExists: (0, import_node_fs16.existsSync)(cursorCacheRoot),
@@ -16957,7 +17008,7 @@ async function runDoctor(opts, io = consoleIo) {
16957
17008
  if (seeded) {
16958
17009
  cursorPins = cursorPluginCachePinSnapshots() ?? [];
16959
17010
  cursorPluginCheck = buildCursorPluginInstallCheck({
16960
- isOrgRepo: Boolean(cfg.sagaApiUrl),
17011
+ isOrgRepo,
16961
17012
  surface,
16962
17013
  cacheRoot: cursorCacheRoot,
16963
17014
  cacheRootExists: (0, import_node_fs16.existsSync)(cursorCacheRoot),
@@ -16974,14 +17025,14 @@ async function runDoctor(opts, io = consoleIo) {
16974
17025
  const cursorThirdPartyEnabled = await readCursorThirdPartyExtensibilityEnabled(execFileP2);
16975
17026
  checks.push(
16976
17027
  buildCursorThirdPartyExtensibilityCheck({
16977
- isOrgRepo: Boolean(cfg.sagaApiUrl),
17028
+ isOrgRepo,
16978
17029
  surface,
16979
17030
  enabled: cursorThirdPartyEnabled
16980
17031
  })
16981
17032
  );
16982
17033
  checks.push(
16983
17034
  buildCursorHookCliCheck({
16984
- isOrgRepo: Boolean(cfg.sagaApiUrl),
17035
+ isOrgRepo,
16985
17036
  surface,
16986
17037
  pins: cursorPins,
16987
17038
  mmiCliOnPath: onPath
@@ -16991,19 +17042,19 @@ async function runDoctor(opts, io = consoleIo) {
16991
17042
  const playwrightMcpConfigs = playwrightMcpConfigSnapshots();
16992
17043
  checks.push(
16993
17044
  buildPlaywrightMcpVisionCapCheck({
16994
- isOrgRepo: Boolean(cfg.sagaApiUrl),
17045
+ isOrgRepo,
16995
17046
  configs: playwrightMcpConfigs
16996
17047
  })
16997
17048
  );
16998
17049
  checks.push(
16999
17050
  buildPlaywrightMcpOutputDirCheck({
17000
- isOrgRepo: Boolean(cfg.sagaApiUrl),
17051
+ isOrgRepo,
17001
17052
  configs: playwrightMcpConfigs
17002
17053
  })
17003
17054
  );
17004
17055
  checks.push(
17005
17056
  buildBrowserArtifactsCheck({
17006
- isOrgRepo: Boolean(cfg.sagaApiUrl),
17057
+ isOrgRepo,
17007
17058
  strayPaths: strayBrowserArtifactPaths()
17008
17059
  })
17009
17060
  );
@@ -17122,12 +17173,12 @@ function ensureUserScopeGuardHook(opts = {}) {
17122
17173
  return "failed";
17123
17174
  }
17124
17175
  }
17125
- async function runGuard(opts = {}) {
17176
+ async function runGuard(opts = {}, readOrigin) {
17126
17177
  void opts;
17127
17178
  try {
17128
17179
  const surface = detectSurface(process.env);
17129
- const cfg = await loadConfig();
17130
- const input = snapshotPluginGuardInput(surface, Boolean(cfg.sagaApiUrl));
17180
+ const isOrgRepo = readOrigin ? await isOrgRepoRoot(readOrigin) : await isOrgRepoRoot();
17181
+ const input = snapshotPluginGuardInput(surface, isOrgRepo);
17131
17182
  const { state } = buildPluginGuardDecision(input);
17132
17183
  const { line, exitCode } = buildGuardSessionStartLine(state);
17133
17184
  if (line) console.error(line);
@@ -17551,7 +17602,7 @@ function runWorktreeInstall(command, cwd, quiet) {
17551
17602
  const file = isWin2 ? "cmd.exe" : bin;
17552
17603
  const spawnArgs = isWin2 ? ["/c", bin, ...args] : args;
17553
17604
  return new Promise((resolve5, reject) => {
17554
- const child = (0, import_node_child_process10.spawn)(file, spawnArgs, { cwd, stdio: quiet ? "ignore" : "inherit", windowsHide: true });
17605
+ const child = (0, import_node_child_process11.spawn)(file, spawnArgs, { cwd, stdio: quiet ? "ignore" : "inherit", windowsHide: true });
17555
17606
  const timer = setTimeout(() => {
17556
17607
  try {
17557
17608
  child.kill();
@@ -17746,7 +17797,7 @@ function scheduleRelatedDiscovery(o) {
17746
17797
  try {
17747
17798
  const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body];
17748
17799
  if (o.repo) args.push("--repo", o.repo);
17749
- (0, import_node_child_process10.spawn)(process.execPath, [process.argv[1], ...args], {
17800
+ (0, import_node_child_process11.spawn)(process.execPath, [process.argv[1], ...args], {
17750
17801
  detached: true,
17751
17802
  stdio: "ignore",
17752
17803
  windowsHide: true,
@@ -18800,7 +18851,7 @@ async function remoteBranchExists2(branch, options = {}) {
18800
18851
  }
18801
18852
  var COMPOSE_TIMEOUT_MS = 12e4;
18802
18853
  function spawnDeferredGcSweep() {
18803
- spawnDetachedSelf(["gc", "sweep-deferred", "--quiet"], { spawn: import_node_child_process10.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
18854
+ spawnDetachedSelf(["gc", "sweep-deferred", "--quiet"], { spawn: import_node_child_process11.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
18804
18855
  }
18805
18856
  async function createDeferredWorktreeStore() {
18806
18857
  try {
@@ -19963,7 +20014,7 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
19963
20014
  return;
19964
20015
  }
19965
20016
  if (!await isOrgRepoRoot()) return;
19966
- spawnDetachedSelf(["docs", "sync", "--quiet"], { spawn: import_node_child_process10.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
20017
+ spawnDetachedSelf(["docs", "sync", "--quiet"], { spawn: import_node_child_process11.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
19967
20018
  spawnDeferredGcSweep();
19968
20019
  const { parallel, sequential } = buildSessionStartPlan({
19969
20020
  // whoami (#879): surface the resolved human so agents act --for them without asking. Silent
@@ -19981,7 +20032,7 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
19981
20032
  readBoard,
19982
20033
  // #1813: warm the slice cache out-of-band (detached, like docs sync) so the ~20s live read
19983
20034
  // never costs banner time and next session's glance renders instantly within budget.
19984
- scheduleRefresh: () => spawnDetachedSelf(["board", "slice-refresh", "--quiet"], { spawn: import_node_child_process10.spawn, execPath: process.execPath, scriptPath: process.argv[1] })
20035
+ scheduleRefresh: () => spawnDetachedSelf(["board", "slice-refresh", "--quiet"], { spawn: import_node_child_process11.spawn, execPath: process.execPath, scriptPath: process.argv[1] })
19985
20036
  }),
19986
20037
  doctor: (io) => runDoctor({ banner: true }, io)
19987
20038
  });
@@ -19989,7 +20040,7 @@ program2.command("session-start").description("run the SessionStart verbs (whoam
19989
20040
  for (const line of scratchGcLines(process.cwd())) consoleIo.log(line);
19990
20041
  const worktreeBanner = worktreeAutoProvisionBanner(process.cwd());
19991
20042
  if (worktreeBanner) {
19992
- spawnDetachedSelf(["worktree", "setup", "--quiet"], { spawn: import_node_child_process10.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
20043
+ spawnDetachedSelf(["worktree", "setup", "--quiet"], { spawn: import_node_child_process11.spawn, execPath: process.execPath, scriptPath: process.argv[1] });
19993
20044
  consoleIo.log(worktreeBanner);
19994
20045
  }
19995
20046
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "2.63.0",
3
+ "version": "2.65.0",
4
4
  "description": "MMI Future CLI — delivers the org rules (whole-file), Jervaise-only continuity, and KB access. The cross-IDE engine the plugin's SessionStart hook drives.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",