@mutmutco/cli 0.8.2 → 0.9.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.
- package/README.md +6 -0
- package/dist/index.cjs +383 -62
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -10,6 +10,12 @@ This package is published from [mutmutco/MMI-Hub](https://github.com/mutmutco/MM
|
|
|
10
10
|
npm install -g @mutmutco/cli
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
Authenticate GitHub once for saga and Project board operations:
|
|
14
|
+
|
|
15
|
+
```powershell
|
|
16
|
+
gh auth login --hostname github.com --git-protocol https --web --scopes "project"
|
|
17
|
+
```
|
|
18
|
+
|
|
13
19
|
Then verify the installed command:
|
|
14
20
|
|
|
15
21
|
```powershell
|
package/dist/index.cjs
CHANGED
|
@@ -3038,7 +3038,7 @@ var {
|
|
|
3038
3038
|
|
|
3039
3039
|
// src/index.ts
|
|
3040
3040
|
var import_promises = require("node:fs/promises");
|
|
3041
|
-
var
|
|
3041
|
+
var import_node_fs3 = require("node:fs");
|
|
3042
3042
|
var import_node_crypto = require("node:crypto");
|
|
3043
3043
|
|
|
3044
3044
|
// src/rules-sync.ts
|
|
@@ -3064,9 +3064,9 @@ function parseHookInput(stdin) {
|
|
|
3064
3064
|
}
|
|
3065
3065
|
|
|
3066
3066
|
// src/index.ts
|
|
3067
|
-
var
|
|
3068
|
-
var
|
|
3069
|
-
var
|
|
3067
|
+
var import_node_child_process4 = require("node:child_process");
|
|
3068
|
+
var import_node_util3 = require("node:util");
|
|
3069
|
+
var import_node_path3 = require("node:path");
|
|
3070
3070
|
|
|
3071
3071
|
// src/saga-head-maintainer.ts
|
|
3072
3072
|
var import_node_child_process = require("node:child_process");
|
|
@@ -3133,11 +3133,11 @@ function parseHeadUpdate(raw) {
|
|
|
3133
3133
|
return Object.keys(u).length ? u : null;
|
|
3134
3134
|
}
|
|
3135
3135
|
async function runHeadEngine(prompt, timeoutMs = HEAD_ENGINE_TIMEOUT_MS) {
|
|
3136
|
-
const { cmd, args, shell } = resolveEngine(process.platform, process.env.SAGA_HEAD_ENGINE);
|
|
3136
|
+
const { cmd, args, shell: shell2 } = resolveEngine(process.platform, process.env.SAGA_HEAD_ENGINE);
|
|
3137
3137
|
return await new Promise((resolve) => {
|
|
3138
3138
|
let child;
|
|
3139
3139
|
try {
|
|
3140
|
-
child = (0, import_node_child_process.spawn)(cmd, args, { shell, windowsHide: true });
|
|
3140
|
+
child = (0, import_node_child_process.spawn)(cmd, args, { shell: shell2, windowsHide: true });
|
|
3141
3141
|
} catch {
|
|
3142
3142
|
return resolve("");
|
|
3143
3143
|
}
|
|
@@ -3853,12 +3853,12 @@ function formatGcPlan(plan, apply) {
|
|
|
3853
3853
|
}
|
|
3854
3854
|
|
|
3855
3855
|
// src/command-plans.ts
|
|
3856
|
-
function stagePlan(
|
|
3856
|
+
function stagePlan(stage2 = {}) {
|
|
3857
3857
|
return [
|
|
3858
3858
|
{ label: "force-kill previous local stage", command: "mmi-cli stage stop --apply" },
|
|
3859
|
-
{ label: "run local build", command:
|
|
3860
|
-
{ label: "start local stage", command:
|
|
3861
|
-
{ label: "check health", command:
|
|
3859
|
+
{ label: "run local build", command: stage2.build || "(no stage.build configured)" },
|
|
3860
|
+
{ label: "start local stage", command: stage2.up || "(no stage.up configured)" },
|
|
3861
|
+
{ label: "check health", command: stage2.healthUrl ? `curl --fail ${stage2.healthUrl}` : "(no stage.healthUrl configured)" }
|
|
3862
3862
|
];
|
|
3863
3863
|
}
|
|
3864
3864
|
function trainPlan(command) {
|
|
@@ -3886,26 +3886,245 @@ function trainPlan(command) {
|
|
|
3886
3886
|
];
|
|
3887
3887
|
}
|
|
3888
3888
|
function bootstrapPlan(repo, repoClass) {
|
|
3889
|
-
const
|
|
3889
|
+
const branchModel = repoClass === "content" ? "content repo: main only" : "deployable repo: development, rc, main";
|
|
3890
|
+
const protectedBranches = repoClass === "content" ? "main" : "development, rc, main";
|
|
3890
3891
|
return [
|
|
3891
3892
|
{ label: `create or inspect ${repo}` },
|
|
3892
|
-
{ label: `provision
|
|
3893
|
-
{ label:
|
|
3894
|
-
{ label: "attach GitHub Project v2 and write .mmi/config.json", gated: true },
|
|
3895
|
-
{ label: "seed README.md and
|
|
3896
|
-
{ label: "
|
|
3893
|
+
{ label: `provision branch model: ${branchModel}`, gated: true },
|
|
3894
|
+
{ label: `apply branch protection / allowlist: ${protectedBranches}`, gated: true },
|
|
3895
|
+
{ label: "attach GitHub Project v2 and write saga-ready .mmi/config.json", gated: true },
|
|
3896
|
+
{ label: "seed README.md, architecture.md, AGENTS.md, and CLAUDE.md", gated: true },
|
|
3897
|
+
{ label: "commit .claude/settings.json and .cursor/rules/<repo>.mdc", gated: true },
|
|
3898
|
+
{ label: `register fanout target on ${repoClass === "content" ? "main" : "development"}`, gated: true }
|
|
3897
3899
|
];
|
|
3898
3900
|
}
|
|
3899
3901
|
|
|
3900
|
-
// src/
|
|
3902
|
+
// src/doctor.ts
|
|
3903
|
+
var GH_PROJECT_LOGIN_FIX = 'run: gh auth login --hostname github.com --git-protocol https --web --scopes "project"';
|
|
3904
|
+
function buildGithubAuthCheck(input) {
|
|
3905
|
+
const ok = Boolean(input.login?.trim());
|
|
3906
|
+
return {
|
|
3907
|
+
ok,
|
|
3908
|
+
label: "GitHub auth identity (saga + gh ops)",
|
|
3909
|
+
fix: input.ghInstalled ? GH_PROJECT_LOGIN_FIX : `install GitHub CLI (https://cli.github.com), then: ${GH_PROJECT_LOGIN_FIX.replace(/^run: /, "")}`
|
|
3910
|
+
};
|
|
3911
|
+
}
|
|
3912
|
+
|
|
3913
|
+
// src/stage-runner.ts
|
|
3914
|
+
var import_node_child_process3 = require("node:child_process");
|
|
3915
|
+
var import_node_fs2 = require("node:fs");
|
|
3916
|
+
var import_node_path2 = require("node:path");
|
|
3917
|
+
var import_node_util2 = require("node:util");
|
|
3901
3918
|
var execFileP2 = (0, import_node_util2.promisify)(import_node_child_process3.execFile);
|
|
3919
|
+
function stageStatePath(cwd = process.cwd()) {
|
|
3920
|
+
return (0, import_node_path2.join)(cwd, "tmp", "stage", "state.json");
|
|
3921
|
+
}
|
|
3922
|
+
function validateStageConfig(config = {}, action) {
|
|
3923
|
+
const problems = [];
|
|
3924
|
+
if (action === "run" && !config.build?.trim()) problems.push("stage.build is required for stage run");
|
|
3925
|
+
if (!config.up?.trim()) problems.push("stage.up is required to start the local stage");
|
|
3926
|
+
if (config.healthUrl != null && config.healthUrl.trim() && !/^https?:\/\//.test(config.healthUrl.trim())) {
|
|
3927
|
+
problems.push("stage.healthUrl must be an http(s) URL");
|
|
3928
|
+
}
|
|
3929
|
+
return problems;
|
|
3930
|
+
}
|
|
3931
|
+
async function shell(command, cwd, timeoutMs) {
|
|
3932
|
+
await execFileP2(command, [], {
|
|
3933
|
+
cwd,
|
|
3934
|
+
shell: true,
|
|
3935
|
+
timeout: timeoutMs,
|
|
3936
|
+
windowsHide: true,
|
|
3937
|
+
maxBuffer: 1024 * 1024 * 4
|
|
3938
|
+
});
|
|
3939
|
+
}
|
|
3940
|
+
function readState(path) {
|
|
3941
|
+
if (!(0, import_node_fs2.existsSync)(path)) return null;
|
|
3942
|
+
try {
|
|
3943
|
+
return JSON.parse((0, import_node_fs2.readFileSync)(path, "utf8"));
|
|
3944
|
+
} catch {
|
|
3945
|
+
return null;
|
|
3946
|
+
}
|
|
3947
|
+
}
|
|
3948
|
+
async function killTree(pid) {
|
|
3949
|
+
if (!Number.isInteger(pid) || pid <= 0) return;
|
|
3950
|
+
if (process.platform === "win32") {
|
|
3951
|
+
await execFileP2("taskkill", ["/PID", String(pid), "/T", "/F"], { windowsHide: true }).catch(() => void 0);
|
|
3952
|
+
return;
|
|
3953
|
+
}
|
|
3954
|
+
try {
|
|
3955
|
+
process.kill(-pid, "SIGTERM");
|
|
3956
|
+
} catch {
|
|
3957
|
+
try {
|
|
3958
|
+
process.kill(pid, "SIGTERM");
|
|
3959
|
+
} catch {
|
|
3960
|
+
}
|
|
3961
|
+
}
|
|
3962
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
3963
|
+
try {
|
|
3964
|
+
process.kill(-pid, "SIGKILL");
|
|
3965
|
+
} catch {
|
|
3966
|
+
try {
|
|
3967
|
+
process.kill(pid, "SIGKILL");
|
|
3968
|
+
} catch {
|
|
3969
|
+
}
|
|
3970
|
+
}
|
|
3971
|
+
}
|
|
3972
|
+
async function waitForHealth(url, timeoutMs) {
|
|
3973
|
+
const deadline = Date.now() + timeoutMs;
|
|
3974
|
+
let last = "";
|
|
3975
|
+
while (Date.now() < deadline) {
|
|
3976
|
+
try {
|
|
3977
|
+
const res = await fetch(url, { signal: AbortSignal.timeout(Math.min(5e3, timeoutMs)) });
|
|
3978
|
+
if (res.ok) return;
|
|
3979
|
+
last = `HTTP ${res.status}`;
|
|
3980
|
+
} catch (e) {
|
|
3981
|
+
last = e.message;
|
|
3982
|
+
}
|
|
3983
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
3984
|
+
}
|
|
3985
|
+
throw new Error(`stage health check timed out for ${url}${last ? ` (${last})` : ""}`);
|
|
3986
|
+
}
|
|
3987
|
+
async function stopStage(opts = {}) {
|
|
3988
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
3989
|
+
const statePath = opts.statePath ?? stageStatePath(cwd);
|
|
3990
|
+
const state = readState(statePath);
|
|
3991
|
+
if (!state) {
|
|
3992
|
+
return { ok: true, action: "stop", statePath, message: "no previous stage state found" };
|
|
3993
|
+
}
|
|
3994
|
+
await killTree(state.pid);
|
|
3995
|
+
(0, import_node_fs2.rmSync)(statePath, { force: true });
|
|
3996
|
+
return { ok: true, action: "stop", statePath, pid: state.pid, message: `stopped previous stage pid ${state.pid}` };
|
|
3997
|
+
}
|
|
3998
|
+
async function startStage(config = {}, opts = {}) {
|
|
3999
|
+
const problems = validateStageConfig(config, "start");
|
|
4000
|
+
if (problems.length) throw new Error(problems.join("; "));
|
|
4001
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
4002
|
+
const statePath = opts.statePath ?? stageStatePath(cwd);
|
|
4003
|
+
const dir = statePath.slice(0, Math.max(statePath.lastIndexOf("/"), statePath.lastIndexOf("\\")));
|
|
4004
|
+
(0, import_node_fs2.mkdirSync)(dir, { recursive: true });
|
|
4005
|
+
const up = config.up.trim();
|
|
4006
|
+
const child = (0, import_node_child_process3.spawn)(up, {
|
|
4007
|
+
cwd,
|
|
4008
|
+
shell: true,
|
|
4009
|
+
detached: true,
|
|
4010
|
+
windowsHide: true,
|
|
4011
|
+
stdio: "ignore"
|
|
4012
|
+
});
|
|
4013
|
+
const state = {
|
|
4014
|
+
pid: child.pid ?? 0,
|
|
4015
|
+
command: up,
|
|
4016
|
+
cwd,
|
|
4017
|
+
startedAt: (opts.now ?? (() => /* @__PURE__ */ new Date()))().toISOString(),
|
|
4018
|
+
healthUrl: config.healthUrl?.trim() || void 0
|
|
4019
|
+
};
|
|
4020
|
+
(0, import_node_fs2.writeFileSync)(statePath, JSON.stringify(state, null, 2), "utf8");
|
|
4021
|
+
if (state.healthUrl) await waitForHealth(state.healthUrl, opts.timeoutMs ?? 6e4);
|
|
4022
|
+
const result = { ok: true, action: "start", statePath, pid: state.pid, message: `started stage pid ${state.pid}` };
|
|
4023
|
+
opts.onReady?.(result);
|
|
4024
|
+
child.unref();
|
|
4025
|
+
return result;
|
|
4026
|
+
}
|
|
4027
|
+
async function runStage(config = {}, opts = {}) {
|
|
4028
|
+
const problems = validateStageConfig(config, "run");
|
|
4029
|
+
if (problems.length) throw new Error(problems.join("; "));
|
|
4030
|
+
const cwd = opts.cwd ?? process.cwd();
|
|
4031
|
+
const timeoutMs = opts.timeoutMs ?? 6e4;
|
|
4032
|
+
await stopStage({ ...opts, cwd });
|
|
4033
|
+
await shell(config.build.trim(), cwd, timeoutMs);
|
|
4034
|
+
const started = await startStage(config, { ...opts, cwd, timeoutMs });
|
|
4035
|
+
return { ...started, action: "run", message: `built and ${started.message}` };
|
|
4036
|
+
}
|
|
4037
|
+
|
|
4038
|
+
// src/bootstrap-verify.ts
|
|
4039
|
+
var requiredDocs = ["README.md", "architecture.md", "AGENTS.md", "CLAUDE.md", ".claude/settings.json", ".mmi/config.json"];
|
|
4040
|
+
var requiredLabels = ["bug", "feature", "task"];
|
|
4041
|
+
function expectedBranches(repoClass) {
|
|
4042
|
+
return repoClass === "content" ? ["main"] : ["development", "rc", "main"];
|
|
4043
|
+
}
|
|
4044
|
+
function safeJson(text, fallback) {
|
|
4045
|
+
try {
|
|
4046
|
+
return JSON.parse(text);
|
|
4047
|
+
} catch {
|
|
4048
|
+
return fallback;
|
|
4049
|
+
}
|
|
4050
|
+
}
|
|
4051
|
+
async function ghJson(deps, args, fallback) {
|
|
4052
|
+
try {
|
|
4053
|
+
return safeJson((await deps.gh(args)).stdout, fallback);
|
|
4054
|
+
} catch {
|
|
4055
|
+
return fallback;
|
|
4056
|
+
}
|
|
4057
|
+
}
|
|
4058
|
+
async function contentExists(deps, repo, branch, path) {
|
|
4059
|
+
try {
|
|
4060
|
+
const encodedPath = path.split("/").map(encodeURIComponent).join("/");
|
|
4061
|
+
await deps.gh(["api", `repos/${repo}/contents/${encodedPath}?ref=${encodeURIComponent(branch)}`]);
|
|
4062
|
+
return true;
|
|
4063
|
+
} catch {
|
|
4064
|
+
return false;
|
|
4065
|
+
}
|
|
4066
|
+
}
|
|
4067
|
+
async function protectedBranch(deps, repo, branch) {
|
|
4068
|
+
try {
|
|
4069
|
+
await deps.gh(["api", `repos/${repo}/branches/${branch}/protection`]);
|
|
4070
|
+
return true;
|
|
4071
|
+
} catch {
|
|
4072
|
+
return false;
|
|
4073
|
+
}
|
|
4074
|
+
}
|
|
4075
|
+
function localRegistryCheck(deps, path, predicate) {
|
|
4076
|
+
const text = deps.readLocalFile?.(path);
|
|
4077
|
+
if (text == null) return null;
|
|
4078
|
+
return predicate(safeJson(text, null));
|
|
4079
|
+
}
|
|
4080
|
+
async function verifyBootstrap(repo, repoClass, deps) {
|
|
4081
|
+
const branchesWanted = expectedBranches(repoClass);
|
|
4082
|
+
const baseBranch = repoClass === "content" ? "main" : "development";
|
|
4083
|
+
const checks = [];
|
|
4084
|
+
const repoInfo = await ghJson(deps, ["api", `repos/${repo}`], {});
|
|
4085
|
+
checks.push({ ok: Boolean(repoInfo.default_branch), label: "repo exists" });
|
|
4086
|
+
checks.push({ ok: repoInfo.default_branch === baseBranch, label: `default branch is ${baseBranch}`, detail: repoInfo.default_branch || "missing" });
|
|
4087
|
+
checks.push({ ok: repoInfo.has_wiki === true, label: "wiki enabled", detail: repoInfo.has_wiki === true ? void 0 : "has_wiki is false or unavailable" });
|
|
4088
|
+
const branchList = await ghJson(deps, ["api", `repos/${repo}/branches`, "--paginate"], []);
|
|
4089
|
+
const branchNames = new Set(branchList.map((b) => b.name));
|
|
4090
|
+
for (const branch of branchesWanted) {
|
|
4091
|
+
checks.push({ ok: branchNames.has(branch), label: `branch exists: ${branch}` });
|
|
4092
|
+
checks.push({ ok: await protectedBranch(deps, repo, branch), label: `branch protection exists: ${branch}` });
|
|
4093
|
+
}
|
|
4094
|
+
for (const path of requiredDocs) {
|
|
4095
|
+
checks.push({ ok: await contentExists(deps, repo, baseBranch, path), label: `bootstrap artifact exists: ${path}` });
|
|
4096
|
+
}
|
|
4097
|
+
checks.push({ ok: await contentExists(deps, repo, baseBranch, ".cursor/environment.json"), label: "Cursor environment committed" });
|
|
4098
|
+
const labels = await ghJson(deps, ["label", "list", "--repo", repo, "--json", "name"], []);
|
|
4099
|
+
const labelNames = new Set(labels.map((l) => l.name));
|
|
4100
|
+
for (const label of requiredLabels) {
|
|
4101
|
+
checks.push({ ok: labelNames.has(label), label: `label exists: ${label}` });
|
|
4102
|
+
}
|
|
4103
|
+
const actions = await ghJson(deps, ["api", `repos/${repo}/actions/permissions`], {});
|
|
4104
|
+
checks.push({ ok: actions.enabled === true, label: "GitHub Actions enabled" });
|
|
4105
|
+
const fanout = repo === "mutmutco/MMI-Hub" ? true : localRegistryCheck(deps, ".github/fanout-targets.json", (json) => Array.isArray(json?.repos) && json.repos.some((r) => r.repo === repo.split("/")[1] && r.branch === baseBranch));
|
|
4106
|
+
if (fanout != null) checks.push({ ok: fanout, label: `fanout target registered on ${baseBranch}` });
|
|
4107
|
+
const projectRegistry = localRegistryCheck(deps, "projects.json", (json) => Array.isArray(json?.projects) && json.projects.some((p) => (p.repos || []).includes(repo)));
|
|
4108
|
+
if (projectRegistry != null) checks.push({ ok: projectRegistry, label: "cloud-agent project registry includes repo" });
|
|
4109
|
+
return { ok: checks.every((c) => c.ok), repo, class: repoClass, baseBranch, checks };
|
|
4110
|
+
}
|
|
4111
|
+
function renderBootstrapVerifyReport(report) {
|
|
4112
|
+
const lines = [`mmi-cli bootstrap verify: ${report.ok ? "OK" : "CHECK"} ${report.repo} (${report.class}, ${report.baseBranch})`];
|
|
4113
|
+
for (const check of report.checks) {
|
|
4114
|
+
lines.push(`${check.ok ? "\u2713" : "\u2717"} ${check.label}${check.detail ? ` \u2014 ${check.detail}` : ""}`);
|
|
4115
|
+
}
|
|
4116
|
+
return lines.join("\n");
|
|
4117
|
+
}
|
|
4118
|
+
|
|
4119
|
+
// src/index.ts
|
|
4120
|
+
var execFileP3 = (0, import_node_util3.promisify)(import_node_child_process4.execFile);
|
|
3902
4121
|
var GIT_TIMEOUT_MS = 1e4;
|
|
3903
4122
|
var GC_GH_TIMEOUT_MS = 2e4;
|
|
3904
4123
|
async function githubToken() {
|
|
3905
4124
|
if (process.env.GH_TOKEN) return process.env.GH_TOKEN;
|
|
3906
4125
|
if (process.env.GITHUB_TOKEN) return process.env.GITHUB_TOKEN;
|
|
3907
4126
|
try {
|
|
3908
|
-
const { stdout } = await
|
|
4127
|
+
const { stdout } = await execFileP3("gh", ["auth", "token"]);
|
|
3909
4128
|
return stdout.trim() || void 0;
|
|
3910
4129
|
} catch {
|
|
3911
4130
|
return void 0;
|
|
@@ -3913,7 +4132,7 @@ async function githubToken() {
|
|
|
3913
4132
|
}
|
|
3914
4133
|
async function githubLogin() {
|
|
3915
4134
|
try {
|
|
3916
|
-
const { stdout } = await
|
|
4135
|
+
const { stdout } = await execFileP3("gh", ["api", "user", "--jq", ".login"]);
|
|
3917
4136
|
return stdout.trim() || void 0;
|
|
3918
4137
|
} catch {
|
|
3919
4138
|
return void 0;
|
|
@@ -3935,7 +4154,7 @@ var DEFAULT_KB_SOURCE = "https://raw.githubusercontent.com/mutmutco/MM-KB/main";
|
|
|
3935
4154
|
var SESSION_FILE = ".mmi/.session";
|
|
3936
4155
|
var gitOut = async (args) => {
|
|
3937
4156
|
try {
|
|
3938
|
-
return (await
|
|
4157
|
+
return (await execFileP3("git", [...args])).stdout.trim();
|
|
3939
4158
|
} catch {
|
|
3940
4159
|
return "";
|
|
3941
4160
|
}
|
|
@@ -3945,7 +4164,7 @@ function sessionDeps() {
|
|
|
3945
4164
|
env: process.env,
|
|
3946
4165
|
readPersisted: () => {
|
|
3947
4166
|
try {
|
|
3948
|
-
return (0,
|
|
4167
|
+
return (0, import_node_fs3.readFileSync)(SESSION_FILE, "utf8");
|
|
3949
4168
|
} catch {
|
|
3950
4169
|
return null;
|
|
3951
4170
|
}
|
|
@@ -3958,8 +4177,8 @@ function sessionDeps() {
|
|
|
3958
4177
|
var resolveSessionId = () => resolveSession(sessionDeps());
|
|
3959
4178
|
function persistSession(id) {
|
|
3960
4179
|
try {
|
|
3961
|
-
(0,
|
|
3962
|
-
(0,
|
|
4180
|
+
(0, import_node_fs3.mkdirSync)(".mmi", { recursive: true });
|
|
4181
|
+
(0, import_node_fs3.writeFileSync)(SESSION_FILE, id, "utf8");
|
|
3963
4182
|
} catch {
|
|
3964
4183
|
}
|
|
3965
4184
|
}
|
|
@@ -3995,18 +4214,18 @@ async function readStdin() {
|
|
|
3995
4214
|
async function ghPrs(limit) {
|
|
3996
4215
|
const args = (state) => ["pr", "list", "--state", state, "--limit", String(limit), "--json", "number,headRefName,state"];
|
|
3997
4216
|
const [open, closed] = await Promise.all([
|
|
3998
|
-
|
|
3999
|
-
|
|
4217
|
+
execFileP3("gh", args("open"), { timeout: GC_GH_TIMEOUT_MS }),
|
|
4218
|
+
execFileP3("gh", args("closed"), { timeout: GC_GH_TIMEOUT_MS })
|
|
4000
4219
|
]);
|
|
4001
4220
|
return [...JSON.parse(open.stdout || "[]"), ...JSON.parse(closed.stdout || "[]")];
|
|
4002
4221
|
}
|
|
4003
4222
|
async function worktreeBranches() {
|
|
4004
|
-
const { stdout } = await
|
|
4223
|
+
const { stdout } = await execFileP3("git", ["worktree", "list", "--porcelain"], { timeout: GIT_TIMEOUT_MS });
|
|
4005
4224
|
const parsed = parseWorktreePorcelain(stdout);
|
|
4006
4225
|
return await Promise.all(parsed.map(async (w) => {
|
|
4007
4226
|
let dirty = true;
|
|
4008
4227
|
try {
|
|
4009
|
-
const { stdout: status } = await
|
|
4228
|
+
const { stdout: status } = await execFileP3("git", ["-C", w.path, "status", "--porcelain"], { timeout: GIT_TIMEOUT_MS });
|
|
4010
4229
|
dirty = status.trim().length > 0;
|
|
4011
4230
|
} catch {
|
|
4012
4231
|
dirty = true;
|
|
@@ -4018,7 +4237,7 @@ async function gcPlan(remote, limit) {
|
|
|
4018
4237
|
const [branches, current, stale, prs, worktrees] = await Promise.all([
|
|
4019
4238
|
gitOut(["branch", "--format=%(refname:short)"]),
|
|
4020
4239
|
gitOut(["rev-parse", "--abbrev-ref", "HEAD"]),
|
|
4021
|
-
|
|
4240
|
+
execFileP3("git", ["remote", "prune", remote, "--dry-run"], { timeout: GIT_TIMEOUT_MS }).then((r) => parseRemotePruneDryRun(`${r.stdout}${r.stderr}`)).catch(() => []),
|
|
4022
4241
|
ghPrs(limit),
|
|
4023
4242
|
worktreeBranches()
|
|
4024
4243
|
]);
|
|
@@ -4033,24 +4252,24 @@ async function gcPlan(remote, limit) {
|
|
|
4033
4252
|
}
|
|
4034
4253
|
async function applyGcPlan(plan, remote) {
|
|
4035
4254
|
for (const branch of plan.branches) {
|
|
4036
|
-
if (branch.worktreePath) await
|
|
4037
|
-
await
|
|
4255
|
+
if (branch.worktreePath) await execFileP3("git", ["worktree", "remove", branch.worktreePath], { timeout: GIT_TIMEOUT_MS });
|
|
4256
|
+
await execFileP3("git", ["branch", "-D", branch.branch], { timeout: GIT_TIMEOUT_MS });
|
|
4038
4257
|
}
|
|
4039
4258
|
for (const ref of plan.trackingRefs) {
|
|
4040
|
-
await
|
|
4259
|
+
await execFileP3("git", ["update-ref", "-d", `refs/remotes/${remote}/${ref.branch}`], { timeout: GIT_TIMEOUT_MS });
|
|
4041
4260
|
}
|
|
4042
4261
|
if (plan.branches.some((b) => b.worktreePath)) {
|
|
4043
|
-
await
|
|
4262
|
+
await execFileP3("git", ["worktree", "prune"], { timeout: GIT_TIMEOUT_MS });
|
|
4044
4263
|
}
|
|
4045
4264
|
}
|
|
4046
4265
|
function resolveVersion() {
|
|
4047
4266
|
try {
|
|
4048
|
-
const manifest = (0,
|
|
4049
|
-
return JSON.parse((0,
|
|
4267
|
+
const manifest = (0, import_node_path3.join)(__dirname, "..", "..", ".claude-plugin", "plugin.json");
|
|
4268
|
+
return JSON.parse((0, import_node_fs3.readFileSync)(manifest, "utf8")).version || "0.0.0";
|
|
4050
4269
|
} catch {
|
|
4051
4270
|
try {
|
|
4052
|
-
const pkg = (0,
|
|
4053
|
-
return JSON.parse((0,
|
|
4271
|
+
const pkg = (0, import_node_path3.join)(__dirname, "..", "package.json");
|
|
4272
|
+
return JSON.parse((0, import_node_fs3.readFileSync)(pkg, "utf8")).version || "0.0.0";
|
|
4054
4273
|
} catch {
|
|
4055
4274
|
return "0.0.0";
|
|
4056
4275
|
}
|
|
@@ -4058,7 +4277,7 @@ function resolveVersion() {
|
|
|
4058
4277
|
}
|
|
4059
4278
|
function readRepoVersion() {
|
|
4060
4279
|
try {
|
|
4061
|
-
return JSON.parse((0,
|
|
4280
|
+
return JSON.parse((0, import_node_fs3.readFileSync)((0, import_node_path3.join)(process.cwd(), ".claude-plugin", "plugin.json"), "utf8")).version || void 0;
|
|
4062
4281
|
} catch {
|
|
4063
4282
|
return void 0;
|
|
4064
4283
|
}
|
|
@@ -4095,10 +4314,10 @@ rules.command("sync").option("--quiet", "stay silent unless something changed or
|
|
|
4095
4314
|
if (!opts.quiet) console.error(`mmi-cli rules: could not fetch ${file} (${e.message}); left it untouched`);
|
|
4096
4315
|
continue;
|
|
4097
4316
|
}
|
|
4098
|
-
const current = (0,
|
|
4317
|
+
const current = (0, import_node_fs3.existsSync)(file) ? await (0, import_promises.readFile)(file, "utf8") : null;
|
|
4099
4318
|
if (needsUpdate(source, current)) {
|
|
4100
4319
|
const slash = file.lastIndexOf("/");
|
|
4101
|
-
if (slash > 0) (0,
|
|
4320
|
+
if (slash > 0) (0, import_node_fs3.mkdirSync)(file.slice(0, slash), { recursive: true });
|
|
4102
4321
|
await (0, import_promises.writeFile)(file, normalizeEol(source), "utf8");
|
|
4103
4322
|
changed++;
|
|
4104
4323
|
if (!opts.quiet) console.log(`mmi-cli rules: updated ${file}`);
|
|
@@ -4146,7 +4365,7 @@ saga.command("head-update").option("--run", "detached worker: fetch state, run t
|
|
|
4146
4365
|
if (!headGateDue(tsPath)) return;
|
|
4147
4366
|
markHeadRun(tsPath);
|
|
4148
4367
|
try {
|
|
4149
|
-
(0,
|
|
4368
|
+
(0, import_node_child_process4.spawn)(process.execPath, [process.argv[1], "saga", "head-update", "--run"], {
|
|
4150
4369
|
detached: true,
|
|
4151
4370
|
stdio: "ignore",
|
|
4152
4371
|
windowsHide: true
|
|
@@ -4230,21 +4449,21 @@ program2.command("kb").description("org knowledgebase (read-only)").command("get
|
|
|
4230
4449
|
});
|
|
4231
4450
|
async function ghCreate(args) {
|
|
4232
4451
|
try {
|
|
4233
|
-
const { stdout } = await
|
|
4452
|
+
const { stdout } = await execFileP3("gh", args);
|
|
4234
4453
|
return parseCreatedUrl(stdout);
|
|
4235
4454
|
} catch (e) {
|
|
4236
4455
|
const err = e;
|
|
4237
4456
|
fail(`gh ${args[0]} create failed: ${(err.stderr || err.message || String(e)).trim()}`);
|
|
4238
4457
|
}
|
|
4239
4458
|
}
|
|
4240
|
-
async function
|
|
4241
|
-
const { stdout } = await
|
|
4459
|
+
async function ghJson2(args, timeout = 1e4) {
|
|
4460
|
+
const { stdout } = await execFileP3("gh", args, { timeout });
|
|
4242
4461
|
return JSON.parse(stdout);
|
|
4243
4462
|
}
|
|
4244
4463
|
async function resolveRepo(repo) {
|
|
4245
4464
|
if (repo) return repo;
|
|
4246
4465
|
try {
|
|
4247
|
-
const { stdout } = await
|
|
4466
|
+
const { stdout } = await execFileP3("gh", ["repo", "view", "--json", "nameWithOwner", "--jq", ".nameWithOwner"], { timeout: 5e3 });
|
|
4248
4467
|
return stdout.trim() || void 0;
|
|
4249
4468
|
} catch {
|
|
4250
4469
|
return void 0;
|
|
@@ -4254,7 +4473,7 @@ function scheduleRelatedDiscovery(o) {
|
|
|
4254
4473
|
try {
|
|
4255
4474
|
const args = ["issue", "discover-related", "--number", String(o.number), "--title", o.title, "--body", o.body];
|
|
4256
4475
|
if (o.repo) args.push("--repo", o.repo);
|
|
4257
|
-
(0,
|
|
4476
|
+
(0, import_node_child_process4.spawn)(process.execPath, [process.argv[1], ...args], {
|
|
4258
4477
|
detached: true,
|
|
4259
4478
|
stdio: "ignore",
|
|
4260
4479
|
windowsHide: true,
|
|
@@ -4281,7 +4500,7 @@ issue.command("discover-related").description("find related issues for an existi
|
|
|
4281
4500
|
const repo = await resolveRepo(o.repo);
|
|
4282
4501
|
if (!repo) return fail("issue discover-related: could not resolve repo");
|
|
4283
4502
|
try {
|
|
4284
|
-
const issues = await
|
|
4503
|
+
const issues = await ghJson2([
|
|
4285
4504
|
"issue",
|
|
4286
4505
|
"list",
|
|
4287
4506
|
"--repo",
|
|
@@ -4296,7 +4515,7 @@ issue.command("discover-related").description("find related issues for an existi
|
|
|
4296
4515
|
const candidates = findRelatedIssues({ number, title: o.title, body: o.body }, issues);
|
|
4297
4516
|
if (o.json) return console.log(JSON.stringify({ number, repo, candidates }, null, 2));
|
|
4298
4517
|
if (!candidates.length) return;
|
|
4299
|
-
const viewed = await
|
|
4518
|
+
const viewed = await ghJson2([
|
|
4300
4519
|
"issue",
|
|
4301
4520
|
"view",
|
|
4302
4521
|
String(number),
|
|
@@ -4306,7 +4525,7 @@ issue.command("discover-related").description("find related issues for an existi
|
|
|
4306
4525
|
"comments"
|
|
4307
4526
|
]);
|
|
4308
4527
|
if (viewed.comments.some((comment) => comment.body.includes(relatedMarker(number)))) return;
|
|
4309
|
-
await
|
|
4528
|
+
await execFileP3("gh", ["issue", "comment", String(number), "--repo", repo, "--body", buildRelatedComment(number, candidates)], { timeout: 1e4 });
|
|
4310
4529
|
} catch {
|
|
4311
4530
|
}
|
|
4312
4531
|
});
|
|
@@ -4345,11 +4564,102 @@ function renderSteps(title, steps) {
|
|
|
4345
4564
|
...steps.map((step, i) => `${i + 1}. ${step.gated ? "[gated] " : ""}${step.label}${step.command ? ` - ${step.command}` : ""}`)
|
|
4346
4565
|
].join("\n");
|
|
4347
4566
|
}
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4567
|
+
function rawFlag(flag) {
|
|
4568
|
+
return process.argv.includes(flag);
|
|
4569
|
+
}
|
|
4570
|
+
function rawValue(flag, fallback) {
|
|
4571
|
+
const index = process.argv.indexOf(flag);
|
|
4572
|
+
return index >= 0 && process.argv[index + 1] ? process.argv[index + 1] : fallback;
|
|
4573
|
+
}
|
|
4574
|
+
function printLine(value) {
|
|
4575
|
+
(0, import_node_fs3.writeSync)(1, `${value}
|
|
4576
|
+
`);
|
|
4577
|
+
}
|
|
4578
|
+
function stageKeepAlive() {
|
|
4579
|
+
return setTimeout(() => void 0, 5 * 60 * 1e3);
|
|
4580
|
+
}
|
|
4581
|
+
var stage = program2.command("stage").description("plan or run the repo local stage environment").option("--json", "machine-readable output").option("--apply", "run the full local stage: stop previous, build, start, health-check").option("--timeout-ms <ms>", "bounded build/health timeout", "60000").action(async (o) => {
|
|
4582
|
+
const cfg = (await loadConfig()).stage;
|
|
4583
|
+
if (o.apply) {
|
|
4584
|
+
const hold = stageKeepAlive();
|
|
4585
|
+
try {
|
|
4586
|
+
const result = await runStage(cfg, { timeoutMs: Number(o.timeoutMs || 6e4) });
|
|
4587
|
+
return printLine(o.json ? JSON.stringify(result) : `mmi-cli stage: ${result.message}`);
|
|
4588
|
+
} catch (e) {
|
|
4589
|
+
return fail(`stage: ${e.message}`);
|
|
4590
|
+
} finally {
|
|
4591
|
+
clearTimeout(hold);
|
|
4592
|
+
}
|
|
4593
|
+
}
|
|
4594
|
+
const steps = stagePlan(cfg);
|
|
4351
4595
|
console.log(o.json ? JSON.stringify({ command: "stage", steps }, null, 2) : renderSteps("mmi-cli stage: dry-run plan", steps));
|
|
4352
4596
|
});
|
|
4597
|
+
stage.command("stop").description("stop the previous local stage process recorded in tmp/stage/state.json").option("--json", "machine-readable output").option("--apply", "kill the recorded process tree and remove the state file").action(async () => {
|
|
4598
|
+
const o = { json: rawFlag("--json"), apply: rawFlag("--apply") };
|
|
4599
|
+
if (!o.apply) {
|
|
4600
|
+
const steps = [{ label: "stop recorded local stage", command: "mmi-cli stage stop --apply" }];
|
|
4601
|
+
return printLine(o.json ? JSON.stringify({ command: "stage stop", steps }, null, 2) : renderSteps("mmi-cli stage stop: dry-run plan", steps));
|
|
4602
|
+
}
|
|
4603
|
+
try {
|
|
4604
|
+
const result = await stopStage();
|
|
4605
|
+
printLine(o.json ? JSON.stringify(result) : `mmi-cli stage stop: ${result.message}`);
|
|
4606
|
+
} catch (e) {
|
|
4607
|
+
fail(`stage stop: ${e.message}`);
|
|
4608
|
+
}
|
|
4609
|
+
});
|
|
4610
|
+
stage.command("start").description("start the configured local stage process and optionally wait for health").option("--json", "machine-readable output").option("--apply", "start the configured stage.up process").option("--timeout-ms <ms>", "bounded health timeout", "60000").action(async () => {
|
|
4611
|
+
const o = { json: rawFlag("--json"), apply: rawFlag("--apply"), timeoutMs: rawValue("--timeout-ms", "60000") };
|
|
4612
|
+
const cfg = (await loadConfig()).stage;
|
|
4613
|
+
if (!o.apply) {
|
|
4614
|
+
const steps = [{ label: "start local stage", command: cfg?.up || "(no stage.up configured)" }];
|
|
4615
|
+
return printLine(o.json ? JSON.stringify({ command: "stage start", steps }, null, 2) : renderSteps("mmi-cli stage start: dry-run plan", steps));
|
|
4616
|
+
}
|
|
4617
|
+
try {
|
|
4618
|
+
const hold = stageKeepAlive();
|
|
4619
|
+
let printed = false;
|
|
4620
|
+
try {
|
|
4621
|
+
const result = await startStage(cfg, {
|
|
4622
|
+
timeoutMs: Number(o.timeoutMs || 6e4),
|
|
4623
|
+
onReady: (ready) => {
|
|
4624
|
+
printed = true;
|
|
4625
|
+
printLine(o.json ? JSON.stringify(ready) : `mmi-cli stage start: ${ready.message}`);
|
|
4626
|
+
}
|
|
4627
|
+
});
|
|
4628
|
+
if (!printed) printLine(o.json ? JSON.stringify(result) : `mmi-cli stage start: ${result.message}`);
|
|
4629
|
+
} finally {
|
|
4630
|
+
clearTimeout(hold);
|
|
4631
|
+
}
|
|
4632
|
+
} catch (e) {
|
|
4633
|
+
fail(`stage start: ${e.message}`);
|
|
4634
|
+
}
|
|
4635
|
+
});
|
|
4636
|
+
stage.command("run").description("force-stop previous stage, build, start, and health-check").option("--json", "machine-readable output").option("--apply", "run the configured stage sequence").option("--timeout-ms <ms>", "bounded build/health timeout", "60000").action(async () => {
|
|
4637
|
+
const o = { json: rawFlag("--json"), apply: rawFlag("--apply"), timeoutMs: rawValue("--timeout-ms", "60000") };
|
|
4638
|
+
const cfg = (await loadConfig()).stage;
|
|
4639
|
+
if (!o.apply) {
|
|
4640
|
+
const steps = stagePlan(cfg);
|
|
4641
|
+
return printLine(o.json ? JSON.stringify({ command: "stage run", steps }, null, 2) : renderSteps("mmi-cli stage run: dry-run plan", steps));
|
|
4642
|
+
}
|
|
4643
|
+
try {
|
|
4644
|
+
const hold = stageKeepAlive();
|
|
4645
|
+
let printed = false;
|
|
4646
|
+
try {
|
|
4647
|
+
const result = await runStage(cfg, {
|
|
4648
|
+
timeoutMs: Number(o.timeoutMs || 6e4),
|
|
4649
|
+
onReady: (ready) => {
|
|
4650
|
+
const runReady = { ...ready, action: "run", message: `built and ${ready.message}` };
|
|
4651
|
+
printed = true;
|
|
4652
|
+
printLine(o.json ? JSON.stringify(runReady) : `mmi-cli stage run: ${runReady.message}`);
|
|
4653
|
+
}
|
|
4654
|
+
});
|
|
4655
|
+
if (!printed) printLine(o.json ? JSON.stringify(result) : `mmi-cli stage run: ${result.message}`);
|
|
4656
|
+
} finally {
|
|
4657
|
+
clearTimeout(hold);
|
|
4658
|
+
}
|
|
4659
|
+
} catch (e) {
|
|
4660
|
+
fail(`stage run: ${e.message}`);
|
|
4661
|
+
}
|
|
4662
|
+
});
|
|
4353
4663
|
for (const commandName of ["rc", "release", "hotfix"]) {
|
|
4354
4664
|
program2.command(commandName).description(`plan ${commandName} train operations; mutations require explicit approval`).option("--json", "machine-readable output").option("--apply", "reserved for future train execution after explicit admin approval").action((o) => {
|
|
4355
4665
|
if (o.apply) return fail(`${commandName}: execution is not implemented yet; use the dry-run plan and the existing /${commandName} skill`);
|
|
@@ -4357,34 +4667,45 @@ for (const commandName of ["rc", "release", "hotfix"]) {
|
|
|
4357
4667
|
console.log(o.json ? JSON.stringify({ command: commandName, steps }, null, 2) : renderSteps(`mmi-cli ${commandName}: dry-run plan`, steps));
|
|
4358
4668
|
});
|
|
4359
4669
|
}
|
|
4360
|
-
program2.command("bootstrap").description("plan repo bootstrap operations; mutations require master-admin approval").
|
|
4670
|
+
var bootstrap = program2.command("bootstrap").description("plan repo bootstrap operations; mutations require master-admin approval").option("--repo <owner/repo>", "target repo").option("--class <class>", "deployable | content", "deployable").option("--json", "machine-readable output").option("--apply", "reserved for future bootstrap execution after explicit master-admin approval").action((o) => {
|
|
4671
|
+
if (!o.repo) return fail("bootstrap: required option --repo <owner/repo> not specified");
|
|
4361
4672
|
if (o.apply) return fail("bootstrap: execution is not implemented yet; use the dry-run plan and the existing /bootstrap skill");
|
|
4362
4673
|
if (o.class !== "deployable" && o.class !== "content") return fail("bootstrap: --class must be deployable or content");
|
|
4363
4674
|
const steps = bootstrapPlan(o.repo, o.class);
|
|
4364
4675
|
console.log(o.json ? JSON.stringify({ command: "bootstrap", repo: o.repo, class: o.class, steps }, null, 2) : renderSteps(`mmi-cli bootstrap: dry-run plan for ${o.repo}`, steps));
|
|
4365
4676
|
});
|
|
4677
|
+
bootstrap.command("verify <repo>").description("audit whether an existing repo is bootstrapped correctly; no mutations").option("--class <class>", "deployable | content", "deployable").option("--json", "machine-readable output").action(async (repo) => {
|
|
4678
|
+
const o = { class: rawValue("--class", "deployable"), json: rawFlag("--json") };
|
|
4679
|
+
if (o.class !== "deployable" && o.class !== "content") return fail("bootstrap verify: --class must be deployable or content");
|
|
4680
|
+
const report = await verifyBootstrap(repo, o.class, {
|
|
4681
|
+
gh: async (args) => execFileP3("gh", args, { timeout: 2e4 }),
|
|
4682
|
+
readLocalFile: (path) => (0, import_node_fs3.existsSync)(path) ? (0, import_node_fs3.readFileSync)(path, "utf8") : null
|
|
4683
|
+
});
|
|
4684
|
+
console.log(o.json ? JSON.stringify(report, null, 2) : renderBootstrapVerifyReport(report));
|
|
4685
|
+
if (!report.ok) process.exitCode = 1;
|
|
4686
|
+
});
|
|
4366
4687
|
var isWin = process.platform === "win32";
|
|
4367
4688
|
program2.command("doctor").description("check onboarding gates (GitHub auth, mmi-cli on PATH, repo config, plugin git clone) and print fixes").option("--banner", "one-line resume summary; silent when all gates pass").option("--json", "machine-readable output").action(async (opts) => {
|
|
4368
4689
|
const checks = [];
|
|
4369
|
-
const
|
|
4370
|
-
let
|
|
4371
|
-
if (!
|
|
4690
|
+
const login = await githubLogin();
|
|
4691
|
+
let ghInstalled = true;
|
|
4692
|
+
if (!login) {
|
|
4372
4693
|
try {
|
|
4373
|
-
await
|
|
4694
|
+
await execFileP3("gh", ["--version"]);
|
|
4374
4695
|
} catch {
|
|
4375
|
-
|
|
4696
|
+
ghInstalled = false;
|
|
4376
4697
|
}
|
|
4377
4698
|
}
|
|
4378
|
-
checks.push({
|
|
4699
|
+
checks.push(buildGithubAuthCheck({ login, ghInstalled }));
|
|
4379
4700
|
let onPath = false;
|
|
4380
4701
|
try {
|
|
4381
|
-
await
|
|
4702
|
+
await execFileP3(isWin ? "where" : "which", ["mmi-cli"]);
|
|
4382
4703
|
onPath = true;
|
|
4383
4704
|
} catch {
|
|
4384
4705
|
}
|
|
4385
4706
|
if (!onPath) {
|
|
4386
4707
|
const root = process.env.CLAUDE_PLUGIN_ROOT;
|
|
4387
|
-
if (root && (0,
|
|
4708
|
+
if (root && (0, import_node_fs3.existsSync)(`${root}/bin/mmi-cli${isWin ? ".cmd" : ""}`)) onPath = true;
|
|
4388
4709
|
}
|
|
4389
4710
|
checks.push({ ok: onPath, label: "mmi-cli on PATH", fix: "auto-provisioned at session start \u2014 reopen the session, or install the MMI plugin" });
|
|
4390
4711
|
checks.push(buildVersionLagReport({
|
|
@@ -4398,13 +4719,13 @@ program2.command("doctor").description("check onboarding gates (GitHub auth, mmi
|
|
|
4398
4719
|
const CLONE_FIX = 'run: git config --global url."https://github.com/".insteadOf "git@github.com:"';
|
|
4399
4720
|
let cloneOk = false;
|
|
4400
4721
|
try {
|
|
4401
|
-
const { stdout } = await
|
|
4722
|
+
const { stdout } = await execFileP3("git", ["config", "--global", "--get-all", REWRITE_KEY]);
|
|
4402
4723
|
cloneOk = stdout.split("\n").some((l) => l.trim() === "git@github.com:");
|
|
4403
4724
|
} catch {
|
|
4404
4725
|
}
|
|
4405
4726
|
if (!cloneOk) {
|
|
4406
4727
|
try {
|
|
4407
|
-
await
|
|
4728
|
+
await execFileP3("git", ["config", "--global", "--add", REWRITE_KEY, "git@github.com:"]);
|
|
4408
4729
|
cloneOk = true;
|
|
4409
4730
|
if (!opts.banner && !opts.json) console.error(" \u21BB repaired: git insteadOf git@github.com \u2192 https (plugin clone over HTTPS)");
|
|
4410
4731
|
} catch {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mutmutco/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.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",
|
|
@@ -31,7 +31,9 @@
|
|
|
31
31
|
"test": "vitest run",
|
|
32
32
|
"typecheck": "tsc --noEmit"
|
|
33
33
|
},
|
|
34
|
-
"dependencies": {
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"commander": "^12.1.0"
|
|
36
|
+
},
|
|
35
37
|
"devDependencies": {
|
|
36
38
|
"@types/node": "^22.0.0",
|
|
37
39
|
"esbuild": "^0.24.0",
|