@mutmutco/cli 2.50.0 → 2.51.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 (2) hide show
  1. package/dist/main.cjs +221 -13
  2. package/package.json +1 -1
package/dist/main.cjs CHANGED
@@ -15749,6 +15749,22 @@ function stageRequiredSecrets(stage2, meta) {
15749
15749
  function stageKey(stage2, key) {
15750
15750
  return key.includes("/") ? key : `${stage2}/${key}`;
15751
15751
  }
15752
+ function materializedRuntimeSecretName(entry) {
15753
+ return entry.includes(":") ? entry.split(":").pop() : entry;
15754
+ }
15755
+ function runtimeSecretStreamGap(stage2, meta, presentSecrets) {
15756
+ const streamed = new Set(
15757
+ (contractByStage(meta.requiredRuntimeSecrets)[stage2] ?? []).map(materializedRuntimeSecretName)
15758
+ );
15759
+ const seen = /* @__PURE__ */ new Set();
15760
+ const gap = [];
15761
+ for (const name of stageRequiredSecrets(stage2, meta).map(materializedRuntimeSecretName)) {
15762
+ if (seen.has(name)) continue;
15763
+ seen.add(name);
15764
+ if (!streamed.has(name) && presentSecrets.has(stageKey(stage2, name))) gap.push(name);
15765
+ }
15766
+ return gap;
15767
+ }
15752
15768
  function hasRuntimeSecretContract(contract) {
15753
15769
  if (!contract || typeof contract !== "object" || Array.isArray(contract)) return false;
15754
15770
  return ["dev", "rc", "main"].some((stage2) => Array.isArray(contract[stage2]));
@@ -15990,6 +16006,11 @@ async function buildV2Doctor(repoOrSlug, deps) {
15990
16006
  const missing = required.filter((key) => !presentSecrets.has(key));
15991
16007
  return [stage2, { required, present, missing }];
15992
16008
  }));
16009
+ const runtimeSecretStreamWarnings = Object.fromEntries(STAGES.map((stage2) => [
16010
+ stage2,
16011
+ stageInTrack(meta, stage2) ? runtimeSecretStreamGap(stage2, meta, presentSecrets) : []
16012
+ ]));
16013
+ const runtimeSecretStreamWarningRows = STAGES.map((stage2) => ({ stage: stage2, names: runtimeSecretStreamWarnings[stage2] })).filter((row) => row.names.length > 0);
15993
16014
  const metaMissing = ["class", "projectType", "deployModel", "vaultPath", "kbPointer"].filter((key) => meta[key] === void 0).concat(boardRegistryGaps(meta));
15994
16015
  const ok = !secretsError && metaMissing.length === 0 && Object.values(deployCoords).every((v) => v.ok) && Object.values(secrets).every((v) => v.missing.length === 0);
15995
16016
  const edgeDomainWarnings = deps.resolveDns ? await probeEdgeDomains(meta, deps.resolveDns) : [];
@@ -16005,6 +16026,7 @@ async function buildV2Doctor(repoOrSlug, deps) {
16005
16026
  autoHealAvailable: Object.keys(autoHeal.patch),
16006
16027
  appOwnedGaps: autoHeal.appOwnedGaps,
16007
16028
  ...edgeDomainWarnings.length ? { edgeDomainWarnings } : {},
16029
+ ...runtimeSecretStreamWarningRows.length ? { runtimeSecretStreamWarnings: runtimeSecretStreamWarningRows } : {},
16008
16030
  appAttested: appAttestationOf(meta) ?? void 0
16009
16031
  };
16010
16032
  }
@@ -16035,6 +16057,9 @@ function renderReadinessIssueBody(existingBody, report, opts = {}) {
16035
16057
  ...(report.edgeDomainWarnings ?? []).map(
16036
16058
  (w) => `- \u26A0 edge domain does not resolve in DNS (advisory): ${w.stage} \u2192 ${w.host}; verify the registry edgeDomains value against the live public host`
16037
16059
  ),
16060
+ ...(report.runtimeSecretStreamWarnings ?? []).map(
16061
+ (w) => `- \u26A0 required secrets provisioned but not in requiredRuntimeSecrets (advisory): ${w.stage} \u2192 ${w.names.join(", ")}; add them to the registry stream list or they will not be materialized into tenant.env`
16062
+ ),
16038
16063
  "",
16039
16064
  "### Auto-heal applied / available",
16040
16065
  ...opts.healed?.length ? opts.healed.map((x) => `- ${x}`) : report.autoHealAvailable.map((x) => `- ${x}`),
@@ -18044,8 +18069,45 @@ var CLAUDE_RECOVERY = `claude plugin marketplace remove ${LEGACY_MMI_MARKETPLACE
18044
18069
  var CODEX_RECOVERY = `codex plugin marketplace remove ${LEGACY_MMI_MARKETPLACE} && codex plugin marketplace remove mutmutco && codex plugin marketplace add mutmutco/MMI-Hub --ref main && codex plugin add mmi@mutmutco`;
18045
18070
  var OPENCODE_PLUGIN_PACKAGE = "@mutmutco/opencode-mmi";
18046
18071
  var OPENCODE_PLUGIN_SPEC = `${OPENCODE_PLUGIN_PACKAGE}@latest`;
18047
- var OPENCODE_PLUGIN_INSTALL_COMMAND = `opencode plugin ${OPENCODE_PLUGIN_SPEC} --global --force`;
18072
+ var OPENCODE_PLUGIN_INSTALL_COMMAND = `mmi-cli doctor --apply`;
18048
18073
  var OPENCODE_RECOVERY = `${OPENCODE_PLUGIN_INSTALL_COMMAND} # then restart OpenCode to load MMI commands`;
18074
+ var OPENCODE_WORKFLOW_COMMANDS = [
18075
+ "mmi",
18076
+ "secrets",
18077
+ "stage",
18078
+ "rcand",
18079
+ "release",
18080
+ "hotfix",
18081
+ "bootstrap",
18082
+ "grind",
18083
+ "build",
18084
+ "handoff",
18085
+ "coop",
18086
+ "browser-automation"
18087
+ ];
18088
+ function opencodeCommandDescription(command) {
18089
+ if (command === "mmi") return "Run the MMI work-board workflow.";
18090
+ if (command === "browser-automation") return "Run the MMI browser automation workflow.";
18091
+ return `Run the MMI ${command} workflow.`;
18092
+ }
18093
+ function opencodeCommandTemplate(command) {
18094
+ return [
18095
+ `Use the \`${command}\` skill and follow it exactly.`,
18096
+ "",
18097
+ "$ARGUMENTS"
18098
+ ].join("\n");
18099
+ }
18100
+ function opencodeCommandMarkdown(command) {
18101
+ return [
18102
+ "---",
18103
+ `description: ${opencodeCommandDescription(command)}`,
18104
+ "agent: build",
18105
+ "---",
18106
+ "",
18107
+ opencodeCommandTemplate(command),
18108
+ ""
18109
+ ].join("\n");
18110
+ }
18049
18111
  var PLUGIN_SURFACE_HEAL = {
18050
18112
  claude: {
18051
18113
  delivery: "plugin-cli",
@@ -18239,6 +18301,7 @@ function buildOpencodeVersionCheck(input) {
18239
18301
  return { ...base, ok: false, installedVersion: input.installedVersion, releasedVersion: input.releasedVersion };
18240
18302
  }
18241
18303
  var OPENCODE_CONFIG_PLUGIN_LABEL = "OpenCode MMI adapter config wiring";
18304
+ var OPENCODE_SURFACE_ASSETS_LABEL = "OpenCode MMI commands and skills";
18242
18305
  function opencodePluginEntryMatches(entry) {
18243
18306
  return entry === OPENCODE_PLUGIN_PACKAGE || entry === OPENCODE_PLUGIN_SPEC || Array.isArray(entry) && (entry[0] === OPENCODE_PLUGIN_PACKAGE || entry[0] === OPENCODE_PLUGIN_SPEC);
18244
18307
  }
@@ -18326,6 +18389,23 @@ function buildOpencodeConfigPluginCheck(input) {
18326
18389
  }
18327
18390
  return { ...base, configPath: input.configPath };
18328
18391
  }
18392
+ function buildOpencodeSurfaceAssetsCheck(input) {
18393
+ const base = {
18394
+ ok: true,
18395
+ label: OPENCODE_SURFACE_ASSETS_LABEL,
18396
+ fix: OPENCODE_RECOVERY,
18397
+ configPath: input.configPath,
18398
+ commandsDir: input.commandsDir,
18399
+ skillsPath: input.skillsPath
18400
+ };
18401
+ if (!input.isOrgRepo) return base;
18402
+ const existing = new Set(input.existingCommands.map((c) => c.toLowerCase()));
18403
+ const missingCommands = OPENCODE_WORKFLOW_COMMANDS.filter((c) => !existing.has(c));
18404
+ const normalizedSkillsPath = input.skillsPath?.replace(/\\/g, "/");
18405
+ const hasSkillsPath = Boolean(normalizedSkillsPath && input.configuredSkillsPaths?.some((p) => p.replace(/\\/g, "/") === normalizedSkillsPath));
18406
+ if (!missingCommands.length && hasSkillsPath) return { ...base, hasSkillsPath };
18407
+ return { ...base, ok: false, missingCommands, hasSkillsPath };
18408
+ }
18329
18409
  var OPENCODE_DESKTOP_BOOTSTRAP_LABEL = "OpenCode Desktop stale project bootstrap";
18330
18410
  var OPENCODE_DESKTOP_BOOTSTRAP_FIX = "OpenCode Desktop is bootstrapping a deleted MMI worktree; open an existing checkout in OpenCode, remove/select away from the stale project entry, then restart OpenCode";
18331
18411
  function decodeLogUrlDirectory(value) {
@@ -19084,8 +19164,17 @@ function backupAndWriteInstalledPlugins(records, pluginId) {
19084
19164
  return false;
19085
19165
  }
19086
19166
  }
19167
+ function opencodeConfigDir() {
19168
+ return (0, import_node_path22.join)((0, import_node_os5.homedir)(), ".config", "opencode");
19169
+ }
19087
19170
  function opencodeConfigPath() {
19088
- return (0, import_node_path22.join)((0, import_node_os5.homedir)(), ".config", "opencode", "opencode.jsonc");
19171
+ return (0, import_node_path22.join)(opencodeConfigDir(), "opencode.jsonc");
19172
+ }
19173
+ function opencodeCommandsDir() {
19174
+ return (0, import_node_path22.join)(opencodeConfigDir(), "commands");
19175
+ }
19176
+ function opencodeSkillsPath() {
19177
+ return (0, import_node_path22.join)(opencodeConfigDir(), "node_modules", "@mutmutco", "opencode-mmi", "skills");
19089
19178
  }
19090
19179
  function opencodeConfigSnapshot() {
19091
19180
  const path2 = opencodeConfigPath();
@@ -19094,12 +19183,14 @@ function opencodeConfigSnapshot() {
19094
19183
  const raw = (0, import_node_fs26.readFileSync)(path2, "utf8");
19095
19184
  const parsed = JSON.parse(stripJsonc(raw));
19096
19185
  const hasPluginField = Object.prototype.hasOwnProperty.call(parsed, "plugin");
19186
+ const skillsPaths = Array.isArray(parsed.skills?.paths) ? parsed.skills.paths.filter((p) => typeof p === "string") : void 0;
19097
19187
  return {
19098
19188
  path: path2,
19099
19189
  hasConfig: true,
19100
19190
  hasPluginField,
19101
19191
  parseOk: true,
19102
19192
  raw,
19193
+ ...skillsPaths ? { skillsPaths } : {},
19103
19194
  ...Array.isArray(parsed.plugin) ? { pluginEntries: parsed.plugin } : parsed.plugin === void 0 ? {} : { pluginEntries: void 0 }
19104
19195
  };
19105
19196
  } catch {
@@ -19120,6 +19211,79 @@ function writeOpencodeConfigPlugin(snapshot) {
19120
19211
  return false;
19121
19212
  }
19122
19213
  }
19214
+ function writeOpencodeSkillsPath(snapshot, skillsPath) {
19215
+ try {
19216
+ const raw = snapshot.hasConfig ? snapshot.raw : void 0;
19217
+ const parsed = raw ? JSON.parse(stripJsonc(raw)) : { $schema: "https://opencode.ai/config.json" };
19218
+ const skills = parsed.skills && typeof parsed.skills === "object" && !Array.isArray(parsed.skills) ? parsed.skills : {};
19219
+ const paths = Array.isArray(skills.paths) ? skills.paths.filter((p) => typeof p === "string") : [];
19220
+ const normalized = skillsPath.replace(/\\/g, "/");
19221
+ if (!paths.some((p) => p.replace(/\\/g, "/") === normalized)) paths.push(skillsPath.replace(/\\/g, "/"));
19222
+ parsed.skills = { ...skills, paths };
19223
+ (0, import_node_fs26.mkdirSync)((0, import_node_path22.dirname)(snapshot.path), { recursive: true });
19224
+ if (snapshot.hasConfig && (0, import_node_fs26.existsSync)(snapshot.path)) (0, import_node_fs26.copyFileSync)(snapshot.path, `${snapshot.path}.bak`);
19225
+ (0, import_node_fs26.writeFileSync)(snapshot.path, `${JSON.stringify(parsed, null, 2)}
19226
+ `, "utf8");
19227
+ return true;
19228
+ } catch {
19229
+ return false;
19230
+ }
19231
+ }
19232
+ function opencodeExistingCommands() {
19233
+ try {
19234
+ return (0, import_node_fs26.readdirSync)(opencodeCommandsDir(), { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => entry.name.slice(0, -3).toLowerCase());
19235
+ } catch {
19236
+ return [];
19237
+ }
19238
+ }
19239
+ function writeOpencodeCommandFiles() {
19240
+ try {
19241
+ const dir = opencodeCommandsDir();
19242
+ (0, import_node_fs26.mkdirSync)(dir, { recursive: true });
19243
+ for (const command of OPENCODE_WORKFLOW_COMMANDS) {
19244
+ (0, import_node_fs26.writeFileSync)((0, import_node_path22.join)(dir, `${command}.md`), opencodeCommandMarkdown(command), "utf8");
19245
+ }
19246
+ return true;
19247
+ } catch {
19248
+ return false;
19249
+ }
19250
+ }
19251
+ function readOpencodeAdapterDiskVersion() {
19252
+ const candidates = [
19253
+ (0, import_node_path22.join)(opencodeConfigDir(), "node_modules", "@mutmutco", "opencode-mmi", "package.json"),
19254
+ (0, import_node_path22.join)((0, import_node_os5.homedir)(), ".cache", "opencode", "node_modules", "@mutmutco", "opencode-mmi", "package.json")
19255
+ ];
19256
+ for (const path2 of candidates) {
19257
+ try {
19258
+ const parsed = JSON.parse((0, import_node_fs26.readFileSync)(path2, "utf8"));
19259
+ if (typeof parsed.version === "string" && parsed.version.trim()) return parsed.version.trim();
19260
+ } catch {
19261
+ continue;
19262
+ }
19263
+ }
19264
+ return void 0;
19265
+ }
19266
+ function opencodeMmiPluginSpecs(snapshot) {
19267
+ const specs = (snapshot.pluginEntries ?? []).map((entry) => Array.isArray(entry) ? entry[0] : entry).filter((entry) => typeof entry === "string").filter((entry) => entry === OPENCODE_PLUGIN_PACKAGE || entry === OPENCODE_PLUGIN_SPEC || entry.startsWith("@mutmutco/"));
19268
+ return Array.from(new Set(specs.length ? specs : [OPENCODE_PLUGIN_SPEC]));
19269
+ }
19270
+ async function forceInstallOpencodeMmiPlugins(snapshot, log) {
19271
+ try {
19272
+ const specs = opencodeMmiPluginSpecs(snapshot);
19273
+ log(` \u21BB force-refreshing OpenCode MMI npm plugin(s): ${specs.join(", ")}\u2026`);
19274
+ (0, import_node_fs26.mkdirSync)(opencodeConfigDir(), { recursive: true });
19275
+ await runHostBin("npm", ["install", "--prefix", opencodeConfigDir(), "--force", ...specs], { timeout: NPM_UPDATE_TIMEOUT_MS });
19276
+ return true;
19277
+ } catch {
19278
+ return false;
19279
+ }
19280
+ }
19281
+ function opencodeInstalledVersionForDoctor() {
19282
+ return process.env.MMI_OPENCODE_PLUGIN_VERSION || readOpencodeAdapterDiskVersion();
19283
+ }
19284
+ function opencodePluginVersionsForReport() {
19285
+ return [process.env.MMI_OPENCODE_PLUGIN_VERSION, readOpencodeAdapterDiskVersion()].filter((v) => Boolean(v));
19286
+ }
19123
19287
  function opencodeDesktopLogsRoot() {
19124
19288
  if (process.platform === "win32") {
19125
19289
  const base = process.env.APPDATA || (0, import_node_path22.join)((0, import_node_os5.homedir)(), "AppData", "Roaming");
@@ -19598,7 +19762,7 @@ async function runDoctor(opts, io = consoleIo) {
19598
19762
  }
19599
19763
  }
19600
19764
  checks.push(installedVersionCheck);
19601
- const openCodeConfigSnapshot = opencodeConfigSnapshot();
19765
+ let openCodeConfigSnapshot = opencodeConfigSnapshot();
19602
19766
  const inspectOpenCode = surface === "opencode" || openCodeConfigSnapshot.hasConfig || runExtended;
19603
19767
  if (inspectOpenCode) {
19604
19768
  let opencodeConfigCheck = buildOpencodeConfigPluginCheck({
@@ -19611,14 +19775,14 @@ async function runDoctor(opts, io = consoleIo) {
19611
19775
  });
19612
19776
  if (!opencodeConfigCheck.ok && opencodeConfigCheck.reason !== "unreadable-config" && opencodeConfigCheck.reason !== "invalid-plugin-shape" && repairLocal) {
19613
19777
  if (writeOpencodeConfigPlugin(openCodeConfigSnapshot)) {
19614
- const refreshed = opencodeConfigSnapshot();
19778
+ openCodeConfigSnapshot = opencodeConfigSnapshot();
19615
19779
  opencodeConfigCheck = buildOpencodeConfigPluginCheck({
19616
19780
  isOrgRepo: Boolean(cfg.sagaApiUrl),
19617
- configPath: refreshed.path,
19618
- hasConfig: refreshed.hasConfig,
19619
- hasPluginField: refreshed.hasPluginField,
19620
- pluginEntries: refreshed.pluginEntries,
19621
- parseOk: refreshed.parseOk
19781
+ configPath: openCodeConfigSnapshot.path,
19782
+ hasConfig: openCodeConfigSnapshot.hasConfig,
19783
+ hasPluginField: openCodeConfigSnapshot.hasPluginField,
19784
+ pluginEntries: openCodeConfigSnapshot.pluginEntries,
19785
+ parseOk: openCodeConfigSnapshot.parseOk
19622
19786
  });
19623
19787
  if (opencodeConfigCheck.ok) {
19624
19788
  markPluginReloadRequired();
@@ -19627,11 +19791,55 @@ async function runDoctor(opts, io = consoleIo) {
19627
19791
  }
19628
19792
  }
19629
19793
  checks.push(opencodeConfigCheck);
19630
- checks.push(buildOpencodeVersionCheck({
19794
+ let opencodeInstalledVersion = opencodeInstalledVersionForDoctor();
19795
+ let opencodeVersionCheck = buildOpencodeVersionCheck({
19631
19796
  isOrgRepo: Boolean(cfg.sagaApiUrl),
19632
- installedVersion: process.env.MMI_OPENCODE_PLUGIN_VERSION,
19797
+ installedVersion: opencodeInstalledVersion,
19633
19798
  releasedVersion
19634
- }));
19799
+ });
19800
+ if (!opencodeVersionCheck.ok && repairFull) {
19801
+ if (await forceInstallOpencodeMmiPlugins(openCodeConfigSnapshot, (m) => io.err(m))) {
19802
+ opencodeInstalledVersion = readOpencodeAdapterDiskVersion() ?? opencodeInstalledVersion;
19803
+ opencodeVersionCheck = buildOpencodeVersionCheck({
19804
+ isOrgRepo: Boolean(cfg.sagaApiUrl),
19805
+ installedVersion: opencodeInstalledVersion,
19806
+ releasedVersion
19807
+ });
19808
+ if (opencodeVersionCheck.ok) {
19809
+ markPluginReloadRequired();
19810
+ io.err(` \u21BB force-refreshed OpenCode MMI plugin \u2192 ${opencodeInstalledVersion ?? releasedVersion ?? "latest"} \u2014 ${reloadAction("opencode")} to load it`);
19811
+ }
19812
+ }
19813
+ }
19814
+ checks.push(opencodeVersionCheck);
19815
+ let surfaceAssetsCheck = buildOpencodeSurfaceAssetsCheck({
19816
+ isOrgRepo: Boolean(cfg.sagaApiUrl),
19817
+ configPath: openCodeConfigSnapshot.path,
19818
+ commandsDir: opencodeCommandsDir(),
19819
+ existingCommands: opencodeExistingCommands(),
19820
+ skillsPath: opencodeSkillsPath(),
19821
+ configuredSkillsPaths: openCodeConfigSnapshot.skillsPaths
19822
+ });
19823
+ if (!surfaceAssetsCheck.ok && repairLocal) {
19824
+ const wroteCommands = surfaceAssetsCheck.missingCommands?.length ? writeOpencodeCommandFiles() : true;
19825
+ const wroteSkills = surfaceAssetsCheck.hasSkillsPath ? true : writeOpencodeSkillsPath(openCodeConfigSnapshot, opencodeSkillsPath());
19826
+ if (wroteCommands || wroteSkills) {
19827
+ openCodeConfigSnapshot = opencodeConfigSnapshot();
19828
+ surfaceAssetsCheck = buildOpencodeSurfaceAssetsCheck({
19829
+ isOrgRepo: Boolean(cfg.sagaApiUrl),
19830
+ configPath: openCodeConfigSnapshot.path,
19831
+ commandsDir: opencodeCommandsDir(),
19832
+ existingCommands: opencodeExistingCommands(),
19833
+ skillsPath: opencodeSkillsPath(),
19834
+ configuredSkillsPaths: openCodeConfigSnapshot.skillsPaths
19835
+ });
19836
+ if (surfaceAssetsCheck.ok) {
19837
+ markPluginReloadRequired();
19838
+ io.err(` \u21BB materialized OpenCode MMI commands + skills path \u2014 ${reloadAction("opencode")} to load them`);
19839
+ }
19840
+ }
19841
+ }
19842
+ checks.push(surfaceAssetsCheck);
19635
19843
  checks.push(buildOpencodeDesktopBootstrapCheck({
19636
19844
  isOrgRepo: Boolean(cfg.sagaApiUrl),
19637
19845
  surface,
@@ -19885,7 +20093,7 @@ async function runDoctor(opts, io = consoleIo) {
19885
20093
  claudePluginVersions: sourceVersions("claude"),
19886
20094
  codexPluginVersions: sourceVersions("codex"),
19887
20095
  codexCacheVersions: cacheVersionsFor("codex"),
19888
- opencodePluginVersions: [process.env.MMI_OPENCODE_PLUGIN_VERSION],
20096
+ opencodePluginVersions: opencodePluginVersionsForReport(),
19889
20097
  releasedVersion
19890
20098
  });
19891
20099
  const resources = doctorResourcesForGaps(gaps);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "2.50.0",
3
+ "version": "2.51.0",
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",