@mutmutco/cli 2.40.3 → 2.41.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/dist/main.cjs +203 -17
- package/package.json +1 -1
package/dist/main.cjs
CHANGED
|
@@ -5678,7 +5678,16 @@ async function secretsCapabilities(deps, opts) {
|
|
|
5678
5678
|
return;
|
|
5679
5679
|
}
|
|
5680
5680
|
if (!res.ok) {
|
|
5681
|
-
|
|
5681
|
+
const upgrade = await upgradeMessage(res);
|
|
5682
|
+
if (upgrade) {
|
|
5683
|
+
deps.err(upgrade);
|
|
5684
|
+
} else if (res.status === 404) {
|
|
5685
|
+
deps.err(
|
|
5686
|
+
"access capabilities: the Hub API did not recognize /secrets/capabilities (HTTP 404). This endpoint ships in the Hub, so a 404 means the deployed Hub predates this command and should answer once a Hub release carrying this command is deployed \u2014 it is not an authorization or missing-credential error."
|
|
5687
|
+
);
|
|
5688
|
+
} else {
|
|
5689
|
+
deps.err(`access capabilities failed: HTTP ${res.status}${await readErr(res)}`);
|
|
5690
|
+
}
|
|
5682
5691
|
return;
|
|
5683
5692
|
}
|
|
5684
5693
|
const report = await res.json();
|
|
@@ -6152,7 +6161,6 @@ function buildIngestPayload(args) {
|
|
|
6152
6161
|
}
|
|
6153
6162
|
|
|
6154
6163
|
// src/honcho-client.ts
|
|
6155
|
-
var HONCHO_ASSISTANT_PEER = "assistant";
|
|
6156
6164
|
function parseHonchoQueueStatus(json) {
|
|
6157
6165
|
const o = json && typeof json === "object" ? json : {};
|
|
6158
6166
|
const pendingRaw = o.pending_work_units ?? o.depth ?? o.queue_depth ?? o.pending;
|
|
@@ -6205,9 +6213,9 @@ async function ingestPost(cfg, body, fetchImpl = fetch, timeoutMs = 1e4) {
|
|
|
6205
6213
|
const session = String(body.session ?? "");
|
|
6206
6214
|
const meta = body.meta && typeof body.meta === "object" ? body.meta : {};
|
|
6207
6215
|
const rawMessages = Array.isArray(body.messages) ? body.messages : [];
|
|
6208
|
-
const messages = rawMessages.filter((m) => m && typeof m.content === "string" && m.content.trim().length > 0).map((m) => ({
|
|
6216
|
+
const messages = rawMessages.filter((m) => m && m.role === "user" && typeof m.content === "string" && m.content.trim().length > 0).map((m) => ({
|
|
6209
6217
|
content: m.content,
|
|
6210
|
-
peer_id:
|
|
6218
|
+
peer_id: peer,
|
|
6211
6219
|
metadata: { role: m.role, ...meta }
|
|
6212
6220
|
}));
|
|
6213
6221
|
if (!peer || !session || !messages.length) return { ok: true, threw: false, status: 204 };
|
|
@@ -6216,7 +6224,8 @@ async function ingestPost(cfg, body, fetchImpl = fetch, timeoutMs = 1e4) {
|
|
|
6216
6224
|
if (res.status === 404) {
|
|
6217
6225
|
const ensured = await request(cfg, fetchImpl, "POST", honchoRoutes.sessions(cfg.workspace), {
|
|
6218
6226
|
id: session,
|
|
6219
|
-
peers: { [peer]: {}
|
|
6227
|
+
peers: { [peer]: {} }
|
|
6228
|
+
// only the human peer — assistant turns are no longer ingested (#1775)
|
|
6220
6229
|
}, timeoutMs);
|
|
6221
6230
|
if (!ensured.ok && ensured.status !== 409) {
|
|
6222
6231
|
if (ensured.status >= 500) return { ok: false, threw: true, message: `honcho session-ensure ${ensured.status}` };
|
|
@@ -6366,7 +6375,7 @@ function spawnHonchoFlush() {
|
|
|
6366
6375
|
} catch {
|
|
6367
6376
|
}
|
|
6368
6377
|
}
|
|
6369
|
-
var DEFAULT_INGEST_MIN_INTERVAL_SEC =
|
|
6378
|
+
var DEFAULT_INGEST_MIN_INTERVAL_SEC = 300;
|
|
6370
6379
|
function honchoThrottlePath(key) {
|
|
6371
6380
|
const safe = (s) => s.replace(/[^A-Za-z0-9._-]/g, "_");
|
|
6372
6381
|
return `.mmi/head-ts/honcho/${safe(key.project)}/${safe(key.branch)}`;
|
|
@@ -6398,7 +6407,12 @@ async function resolveSummaryText(summary, messageFile) {
|
|
|
6398
6407
|
}
|
|
6399
6408
|
return "";
|
|
6400
6409
|
}
|
|
6410
|
+
function isAutomatedSession(env = process.env) {
|
|
6411
|
+
const v = env.GITHUB_ACTIONS;
|
|
6412
|
+
return !!v && v !== "false" && v !== "0";
|
|
6413
|
+
}
|
|
6401
6414
|
async function runHonchoIngest(opts) {
|
|
6415
|
+
if (isAutomatedSession()) return;
|
|
6402
6416
|
const cfg = await loadConfig();
|
|
6403
6417
|
const peer = honchoPeerId(await honchoLogin(cfg), cfg);
|
|
6404
6418
|
if (!peer) return;
|
|
@@ -9672,6 +9686,44 @@ function buildPrArgs({ title, body, base: base2, head, repo }) {
|
|
|
9672
9686
|
return args;
|
|
9673
9687
|
}
|
|
9674
9688
|
|
|
9689
|
+
// src/issue-check.ts
|
|
9690
|
+
var CHECKLIST_RE = /^([ \t]*[-*+] \[)([ xX])(\] )(.*)$/gm;
|
|
9691
|
+
function findChecklistItems(body) {
|
|
9692
|
+
const items = [];
|
|
9693
|
+
for (const m of body.matchAll(CHECKLIST_RE)) {
|
|
9694
|
+
const prefix = m[1];
|
|
9695
|
+
const marker = m[2];
|
|
9696
|
+
const text = m[4].replace(/\r$/, "");
|
|
9697
|
+
items.push({
|
|
9698
|
+
markerIndex: (m.index ?? 0) + prefix.length,
|
|
9699
|
+
checked: marker.toLowerCase() === "x",
|
|
9700
|
+
text
|
|
9701
|
+
});
|
|
9702
|
+
}
|
|
9703
|
+
return items;
|
|
9704
|
+
}
|
|
9705
|
+
function selectChecklistItem(items, query) {
|
|
9706
|
+
const q = query.trim();
|
|
9707
|
+
if (!q) return { ok: false, reason: "not-found" };
|
|
9708
|
+
const exact = items.filter((it) => it.text.trim() === q);
|
|
9709
|
+
if (exact.length === 1) return { ok: true, item: exact[0] };
|
|
9710
|
+
if (exact.length > 1) return { ok: false, reason: "ambiguous", matches: exact };
|
|
9711
|
+
const sub = items.filter((it) => it.text.includes(q));
|
|
9712
|
+
if (sub.length === 1) return { ok: true, item: sub[0] };
|
|
9713
|
+
if (sub.length === 0) return { ok: false, reason: "not-found" };
|
|
9714
|
+
return { ok: false, reason: "ambiguous", matches: sub };
|
|
9715
|
+
}
|
|
9716
|
+
function setChecklistMarker(body, item, checked) {
|
|
9717
|
+
if (item.checked === checked) return { body, changed: false };
|
|
9718
|
+
const target = checked ? "x" : " ";
|
|
9719
|
+
return { body: body.slice(0, item.markerIndex) + target + body.slice(item.markerIndex + 1), changed: true };
|
|
9720
|
+
}
|
|
9721
|
+
function applyChecklistCheck(body, query, checked) {
|
|
9722
|
+
const sel = selectChecklistItem(findChecklistItems(body), query);
|
|
9723
|
+
if (!sel.ok) return sel;
|
|
9724
|
+
return { ok: true, edit: setChecklistMarker(body, sel.item, checked), item: sel.item };
|
|
9725
|
+
}
|
|
9726
|
+
|
|
9675
9727
|
// src/command-manifest.ts
|
|
9676
9728
|
function buildArgument(arg) {
|
|
9677
9729
|
const out = { name: arg.name(), required: arg.required, variadic: arg.variadic };
|
|
@@ -11913,8 +11965,8 @@ function stageLiveUpSteps(t) {
|
|
|
11913
11965
|
command: `gh ${ghDispatchArgs("tenant-deploy.yml", { slug: t.slug, repo: t.repo, ref: t.ref ?? "<branch>", stage: "dev" }).join(" ")}`
|
|
11914
11966
|
},
|
|
11915
11967
|
{
|
|
11916
|
-
label:
|
|
11917
|
-
command: `gh ${ghDispatchArgs("tenant-control.yml", { slug: t.slug, stage: "dev", action: "allow
|
|
11968
|
+
label: `gate ${t.host} to your IP at the Cloudflare edge (ephemeral firewall_custom skip rule)`,
|
|
11969
|
+
command: `gh ${ghDispatchArgs("tenant-control.yml", { slug: t.slug, stage: "dev", action: "cf-gate-allow", host: t.host, ip: "<your ip>" }).join(" ")}`
|
|
11918
11970
|
},
|
|
11919
11971
|
{ label: "tear down when done", command: "mmi-cli stage --live --down --apply" }
|
|
11920
11972
|
];
|
|
@@ -11926,8 +11978,8 @@ function stageLiveDownSteps(t) {
|
|
|
11926
11978
|
command: `gh ${ghDispatchArgs("tenant-control.yml", { slug: t.slug, stage: "dev", action: "stop" }).join(" ")}`
|
|
11927
11979
|
},
|
|
11928
11980
|
{
|
|
11929
|
-
label: "
|
|
11930
|
-
command: `gh ${ghDispatchArgs("tenant-control.yml", { slug: t.slug, stage: "dev", action: "
|
|
11981
|
+
label: "remove the Cloudflare edge gate (the stage goes dark even if restarted)",
|
|
11982
|
+
command: `gh ${ghDispatchArgs("tenant-control.yml", { slug: t.slug, stage: "dev", action: "cf-gate-clear", host: t.host }).join(" ")}`
|
|
11931
11983
|
}
|
|
11932
11984
|
];
|
|
11933
11985
|
}
|
|
@@ -11935,8 +11987,9 @@ async function runStageLiveUp(deps, t) {
|
|
|
11935
11987
|
if (!t.ref?.trim()) throw new Error("stage --live: cannot resolve the current branch to deploy");
|
|
11936
11988
|
const ip = (await deps.detectIp()).trim();
|
|
11937
11989
|
if (!validStageLiveIp(ip)) throw new Error(`stage --live: detected public IP is not a literal IPv4/IPv6 address: "${ip.slice(0, 80)}"`);
|
|
11990
|
+
if (!t.host?.trim()) throw new Error("stage --live: cannot resolve the dev edge host (registry edgeDomains.dev)");
|
|
11938
11991
|
await deps.run("gh", ghDispatchArgs("tenant-deploy.yml", { slug: t.slug, repo: t.repo, ref: t.ref, stage: "dev" }));
|
|
11939
|
-
await deps.run("gh", ghDispatchArgs("tenant-control.yml", { slug: t.slug, stage: "dev", action: "allow
|
|
11992
|
+
await deps.run("gh", ghDispatchArgs("tenant-control.yml", { slug: t.slug, stage: "dev", action: "cf-gate-allow", host: t.host, ip }));
|
|
11940
11993
|
return {
|
|
11941
11994
|
command: "stage --live",
|
|
11942
11995
|
mode: "up",
|
|
@@ -11945,19 +11998,20 @@ async function runStageLiveUp(deps, t) {
|
|
|
11945
11998
|
ref: t.ref,
|
|
11946
11999
|
ip,
|
|
11947
12000
|
dispatched: ["tenant-deploy.yml", "tenant-control.yml"],
|
|
11948
|
-
message: `dispatched the dev deploy of ${t.ref} and the
|
|
12001
|
+
message: `dispatched the dev deploy of ${t.ref} and the Cloudflare edge gate for ${t.host} \u2192 ${ip}; watch the runs in ${STAGE_LIVE_HUB_REPO} Actions \u2014 tear down with: mmi-cli stage --live --down --apply`
|
|
11949
12002
|
};
|
|
11950
12003
|
}
|
|
11951
12004
|
async function runStageLiveDown(deps, t) {
|
|
12005
|
+
if (!t.host?.trim()) throw new Error("stage --live: cannot resolve the dev edge host (registry edgeDomains.dev)");
|
|
11952
12006
|
await deps.run("gh", ghDispatchArgs("tenant-control.yml", { slug: t.slug, stage: "dev", action: "stop" }));
|
|
11953
|
-
await deps.run("gh", ghDispatchArgs("tenant-control.yml", { slug: t.slug, stage: "dev", action: "
|
|
12007
|
+
await deps.run("gh", ghDispatchArgs("tenant-control.yml", { slug: t.slug, stage: "dev", action: "cf-gate-clear", host: t.host }));
|
|
11954
12008
|
return {
|
|
11955
12009
|
command: "stage --live",
|
|
11956
12010
|
mode: "down",
|
|
11957
12011
|
slug: t.slug,
|
|
11958
12012
|
repo: t.repo,
|
|
11959
12013
|
dispatched: ["tenant-control.yml", "tenant-control.yml"],
|
|
11960
|
-
message: `dispatched the dev stop and
|
|
12014
|
+
message: `dispatched the dev stop and the Cloudflare edge gate clear for ${t.host}; the dev stage is dark until the next mmi-cli stage --live --apply`
|
|
11961
12015
|
};
|
|
11962
12016
|
}
|
|
11963
12017
|
|
|
@@ -15240,6 +15294,9 @@ async function upsertProject(slug, patch, deps) {
|
|
|
15240
15294
|
async function attestAppGaps(slug, repo, deps) {
|
|
15241
15295
|
return postJson(`/projects/${encodeURIComponent(slug)}/attest-app`, { repo }, deps);
|
|
15242
15296
|
}
|
|
15297
|
+
async function setDeployCoords(slug, payload, deps) {
|
|
15298
|
+
return postJson(`/projects/${encodeURIComponent(slug)}/deploy`, payload, deps);
|
|
15299
|
+
}
|
|
15243
15300
|
async function tenantControl(payload, deps) {
|
|
15244
15301
|
return postJson("/tenant-control", payload, deps, "POST", { noRetry: true });
|
|
15245
15302
|
}
|
|
@@ -15634,7 +15691,7 @@ ${section}`.trim();
|
|
|
15634
15691
|
}
|
|
15635
15692
|
|
|
15636
15693
|
// src/project-set.ts
|
|
15637
|
-
var UNSET_KEYS = ["oauth", "requiredRuntimeSecrets", "edgeDomains", "requiredGcpApis", "publishRequired", "publishDir", "dashboard", "ci", "requiredChecks", "gate"];
|
|
15694
|
+
var UNSET_KEYS = ["oauth", "requiredRuntimeSecrets", "edgeDomains", "requiredGcpApis", "publishRequired", "publishDir", "dashboard", "fofuEnabled", "ci", "requiredChecks", "gate"];
|
|
15638
15695
|
var UNSET_KEY_SET = new Set(UNSET_KEYS);
|
|
15639
15696
|
var RUNTIME_SECRET_STAGES = ["dev", "rc", "main"];
|
|
15640
15697
|
function parseRuntimeSecretsVar(raw) {
|
|
@@ -15798,6 +15855,11 @@ function parseDashboardVar(raw) {
|
|
|
15798
15855
|
if (raw === "false") return false;
|
|
15799
15856
|
throw new Error("project set: dashboard must be true or false");
|
|
15800
15857
|
}
|
|
15858
|
+
function parseFofuEnabledVar(raw) {
|
|
15859
|
+
if (raw === "true") return true;
|
|
15860
|
+
if (raw === "false") return false;
|
|
15861
|
+
throw new Error("project set: fofuEnabled must be true or false");
|
|
15862
|
+
}
|
|
15801
15863
|
function parsePublishDirVar(raw) {
|
|
15802
15864
|
const v = raw.trim();
|
|
15803
15865
|
if (v === "" || v === ".") {
|
|
@@ -15860,6 +15922,7 @@ var SETTABLE_VAR_KEYS = [
|
|
|
15860
15922
|
"publishRequired",
|
|
15861
15923
|
"publishDir",
|
|
15862
15924
|
"dashboard",
|
|
15925
|
+
"fofuEnabled",
|
|
15863
15926
|
"requiredGcpApis",
|
|
15864
15927
|
"requiredRuntimeSecrets",
|
|
15865
15928
|
"edgeDomains",
|
|
@@ -15878,6 +15941,7 @@ var SETTABLE_VAR_HINTS = {
|
|
|
15878
15941
|
publishRequired: "true|false",
|
|
15879
15942
|
publishDir: "relative subpath, e.g. packages/ui",
|
|
15880
15943
|
dashboard: "true|false",
|
|
15944
|
+
fofuEnabled: "true|false",
|
|
15881
15945
|
repos: 'JSON array, e.g. ["mutmutco/mm-foo"]',
|
|
15882
15946
|
oauth: "JSON {subdomains,domains,callbackPath}",
|
|
15883
15947
|
requiredGcpApis: "comma-string",
|
|
@@ -15953,6 +16017,8 @@ function buildProjectSetPatch(input) {
|
|
|
15953
16017
|
patch[key] = parsePublishRequiredVar(raw);
|
|
15954
16018
|
} else if (key === "dashboard") {
|
|
15955
16019
|
patch[key] = parseDashboardVar(raw);
|
|
16020
|
+
} else if (key === "fofuEnabled") {
|
|
16021
|
+
patch[key] = parseFofuEnabledVar(raw);
|
|
15956
16022
|
} else if (key === "publishDir") {
|
|
15957
16023
|
patch[key] = parsePublishDirVar(raw);
|
|
15958
16024
|
} else if (key === "ci") {
|
|
@@ -15981,6 +16047,54 @@ function buildProjectSetPatch(input) {
|
|
|
15981
16047
|
}
|
|
15982
16048
|
return patch;
|
|
15983
16049
|
}
|
|
16050
|
+
var DEPLOY_SUBSTRATES = ["hetzner-ssh"];
|
|
16051
|
+
var DEPLOY_STAGES = ["dev", "rc", "main"];
|
|
16052
|
+
var DEPLOY_DOMAIN_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/;
|
|
16053
|
+
var DEPLOY_SHELL_SAFE_RE = /^[A-Za-z0-9./:_?&=%-]*$/;
|
|
16054
|
+
function buildSetDeployPatch(slug, input) {
|
|
16055
|
+
const clean4 = (v) => typeof v === "string" && v.trim() !== "" ? v.trim() : void 0;
|
|
16056
|
+
const stage2 = clean4(input.stage);
|
|
16057
|
+
if (!stage2 || !DEPLOY_STAGES.includes(stage2)) {
|
|
16058
|
+
throw new Error(`project set-deploy: --stage must be one of: ${DEPLOY_STAGES.join(", ")}`);
|
|
16059
|
+
}
|
|
16060
|
+
const substrate = clean4(input.substrate) ?? "hetzner-ssh";
|
|
16061
|
+
if (!DEPLOY_SUBSTRATES.includes(substrate)) {
|
|
16062
|
+
throw new Error(`project set-deploy: --substrate must be one of: ${DEPLOY_SUBSTRATES.join(", ")}`);
|
|
16063
|
+
}
|
|
16064
|
+
const sshHost = clean4(input.sshHost);
|
|
16065
|
+
if (substrate === "hetzner-ssh" && !sshHost) {
|
|
16066
|
+
throw new Error("project set-deploy: hetzner-ssh requires --ssh-host");
|
|
16067
|
+
}
|
|
16068
|
+
const sshUser = clean4(input.sshUser) ?? "root";
|
|
16069
|
+
const deployPath = clean4(input.deployPath) ?? `/opt/mmi/${slug}/${stage2}`;
|
|
16070
|
+
const serviceName = clean4(input.service) ?? slug;
|
|
16071
|
+
for (const [label, v] of [["--ssh-host", sshHost], ["--ssh-user", sshUser], ["--deploy-path", deployPath], ["--service", serviceName]]) {
|
|
16072
|
+
if (v !== void 0 && !DEPLOY_SHELL_SAFE_RE.test(v)) throw new Error(`project set-deploy: ${label} contains unsafe characters`);
|
|
16073
|
+
}
|
|
16074
|
+
let port;
|
|
16075
|
+
if (input.port !== void 0 && input.port !== null && `${input.port}`.trim() !== "") {
|
|
16076
|
+
port = Number(input.port);
|
|
16077
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error("project set-deploy: --port must be an integer 1..65535");
|
|
16078
|
+
}
|
|
16079
|
+
const domain = clean4(input.domain);
|
|
16080
|
+
if (domain !== void 0 && !DEPLOY_DOMAIN_RE.test(domain)) throw new Error(`project set-deploy: --domain must be a DNS hostname, got ${JSON.stringify(input.domain)}`);
|
|
16081
|
+
const aliases = (Array.isArray(input.aliases) ? input.aliases : []).map((a) => clean4(a)).filter((a) => a !== void 0);
|
|
16082
|
+
for (const a of aliases) {
|
|
16083
|
+
if (!DEPLOY_DOMAIN_RE.test(a)) throw new Error(`project set-deploy: --alias must be a DNS hostname, got ${JSON.stringify(a)}`);
|
|
16084
|
+
}
|
|
16085
|
+
const uniqueAliases = [...new Set(aliases)];
|
|
16086
|
+
return {
|
|
16087
|
+
stage: stage2,
|
|
16088
|
+
substrate,
|
|
16089
|
+
sshHost,
|
|
16090
|
+
sshUser,
|
|
16091
|
+
deployPath,
|
|
16092
|
+
serviceName,
|
|
16093
|
+
...domain !== void 0 ? { domain } : {},
|
|
16094
|
+
...port !== void 0 ? { port } : {},
|
|
16095
|
+
...uniqueAliases.length ? { aliases: uniqueAliases } : {}
|
|
16096
|
+
};
|
|
16097
|
+
}
|
|
15984
16098
|
function repoFromRemoteUrl(remoteUrl) {
|
|
15985
16099
|
const m = remoteUrl.trim().match(/^(?:[a-z][a-z0-9+.-]*:\/\/)?(?:[^@\s/]+@)?github\.com[:/]([^/\s:]+)\/([^/\s]+?)(?:\.git)?\/?$/i);
|
|
15986
16100
|
return m ? `${m[1]}/${m[2]}` : void 0;
|
|
@@ -17729,7 +17843,7 @@ deploys run centrally (tenant-deploy.yml); product repos carry no deploy files.
|
|
|
17729
17843
|
}
|
|
17730
17844
|
});
|
|
17731
17845
|
project.command("resolve <owner/repo>").description("deploy coords for a stage \u2014 for diagnosis. NOTE: /deploy-coords is OIDC-gated (a deploy job\u2019s id-token), so a gh-token CLI cannot read it from a dev machine").option("--stage <main|rc>", "deploy stage", "main").option("--json", "machine-readable output").action((_repoOrRepo, o) => {
|
|
17732
|
-
const msg = "project resolve: deploy coords are served only to a deploy workflow (GitHub OIDC id-token, repo-scoped). A gh-token CLI on a dev machine cannot read /deploy-coords; inspect the DEPLOY# item via
|
|
17846
|
+
const msg = "project resolve: deploy coords are served only to a deploy workflow (GitHub OIDC id-token, repo-scoped). A gh-token CLI on a dev machine cannot read /deploy-coords; inspect the DEPLOY# item via a master registry (DDB) read instead.";
|
|
17733
17847
|
if (o.json) {
|
|
17734
17848
|
console.log(JSON.stringify({ ok: false, stage: o.stage, error: msg }));
|
|
17735
17849
|
process.exitCode = 1;
|
|
@@ -17829,6 +17943,34 @@ project.command("set [owner/repo]").description("upsert project META (idempotent
|
|
|
17829
17943
|
const res = await upsertProject(slug, { ...patch, repo }, registryClientDeps(cfg));
|
|
17830
17944
|
return reportWrite("project set", res);
|
|
17831
17945
|
});
|
|
17946
|
+
project.command("set-deploy [owner/repo]").description("write the DEPLOY#<stage> Hetzner deploy coords for a tenant (master-only) \u2014 the explicit-coords path that seeds a freshly-bootstrapped tenant; defaults to the current repo").requiredOption("--stage <stage>", "dev | rc | main").option("--ssh-host <host>", "the box address the deploy ssh-es into (required for hetzner-ssh)").option("--ssh-user <user>", "ssh user (default root)").option("--port <port>", "loopback port the container binds / Caddy upstream (1..65535)").option("--substrate <substrate>", "hetzner-ssh (default)").option("--deploy-path <path>", "on-box per-stage release root (default /opt/mmi/<slug>/<stage>)").option("--service <name>", "systemd/compose service name (default the slug)").option("--domain <domain>", "canonical serving host (default the project edgeDomains[stage])").option("--alias <domain...>", "extra serving hostname the box Caddy answers (repeatable)").option("--json", "machine-readable output").action(async (repoOrSlug, o) => {
|
|
17947
|
+
const cfg = await loadConfig();
|
|
17948
|
+
let target;
|
|
17949
|
+
try {
|
|
17950
|
+
target = await projectTarget("project set-deploy", repoOrSlug);
|
|
17951
|
+
} catch (e) {
|
|
17952
|
+
return fail(e.message);
|
|
17953
|
+
}
|
|
17954
|
+
const slug = slugOf(target);
|
|
17955
|
+
let body;
|
|
17956
|
+
try {
|
|
17957
|
+
body = buildSetDeployPatch(slug, {
|
|
17958
|
+
stage: o.stage,
|
|
17959
|
+
sshHost: o.sshHost,
|
|
17960
|
+
sshUser: o.sshUser,
|
|
17961
|
+
port: o.port,
|
|
17962
|
+
substrate: o.substrate,
|
|
17963
|
+
deployPath: o.deployPath,
|
|
17964
|
+
service: o.service,
|
|
17965
|
+
domain: o.domain,
|
|
17966
|
+
aliases: o.alias
|
|
17967
|
+
});
|
|
17968
|
+
} catch (e) {
|
|
17969
|
+
return fail(e.message);
|
|
17970
|
+
}
|
|
17971
|
+
const res = await setDeployCoords(slug, body, registryClientDeps(cfg));
|
|
17972
|
+
return reportWrite("project set-deploy", res);
|
|
17973
|
+
});
|
|
17832
17974
|
var registry = program2.command("registry").description("the DDB org registry \u2014 org-level constants");
|
|
17833
17975
|
registry.command("org").description("the org config (account id, region, orgProjectId, sagaApiUrl)").option("--json", "machine-readable output").action(async (_o) => {
|
|
17834
17976
|
const cfg = await loadConfig();
|
|
@@ -18011,6 +18153,46 @@ issue.command("link-child <parent> <child>").description("link an existing issue
|
|
|
18011
18153
|
return fail(`issue link-child: ${(err.stderr || err.message || String(e)).trim()}${note ? ` (${note})` : ""}`);
|
|
18012
18154
|
}
|
|
18013
18155
|
});
|
|
18156
|
+
issue.command("check <ref>").description("tick (or with --off untick) a task-list checkbox in an issue/epic body by its item text and print {number,repo,item,checked,changed} JSON").requiredOption("--item <text>", "the checklist item to match \u2014 exact item text, else a unique substring").option("--off", "untick the item ([x] \u2192 [ ]) instead of ticking it").option("--repo <owner/repo>", "repo for a bare ref (defaults to the current repo)").action(async (ref, o) => {
|
|
18157
|
+
let parsed;
|
|
18158
|
+
try {
|
|
18159
|
+
parsed = parseIssueRef(ref);
|
|
18160
|
+
} catch (e) {
|
|
18161
|
+
return fail(`issue check: ${e.message}`);
|
|
18162
|
+
}
|
|
18163
|
+
const repo = await resolveRepo(parsed.repo ?? o.repo);
|
|
18164
|
+
if (!repo) return fail("issue check: could not resolve repo \u2014 pass --repo owner/repo");
|
|
18165
|
+
const checked = o.off !== true;
|
|
18166
|
+
let body;
|
|
18167
|
+
try {
|
|
18168
|
+
const viewed = await ghJson(["issue", "view", String(parsed.number), "--repo", repo, "--json", "body"]);
|
|
18169
|
+
body = viewed.body ?? "";
|
|
18170
|
+
} catch (e) {
|
|
18171
|
+
return fail(`issue check: could not read ${repo}#${parsed.number}: ${e.message}`);
|
|
18172
|
+
}
|
|
18173
|
+
const result = applyChecklistCheck(body, o.item, checked);
|
|
18174
|
+
if (!result.ok) {
|
|
18175
|
+
if (result.reason === "ambiguous") {
|
|
18176
|
+
const list = result.matches.map((m) => ` - ${m.text}`).join("\n");
|
|
18177
|
+
return fail(`issue check: "${o.item}" matches ${result.matches.length} checklist items in ${repo}#${parsed.number} \u2014 narrow the text:
|
|
18178
|
+
${list}`);
|
|
18179
|
+
}
|
|
18180
|
+
return fail(`issue check: no checklist item matching "${o.item}" in ${repo}#${parsed.number}`);
|
|
18181
|
+
}
|
|
18182
|
+
if (!result.edit.changed) {
|
|
18183
|
+
return console.log(JSON.stringify({ number: parsed.number, repo, item: result.item.text, checked, changed: false }));
|
|
18184
|
+
}
|
|
18185
|
+
const edit = await bodyArgsViaFile(["issue", "edit", String(parsed.number), "--repo", repo, "--body", result.edit.body]);
|
|
18186
|
+
try {
|
|
18187
|
+
await execFileP2("gh", edit.args, { timeout: GH_MUTATION_TIMEOUT_MS });
|
|
18188
|
+
} catch (e) {
|
|
18189
|
+
const note = timeoutKillNote(e, GH_MUTATION_TIMEOUT_MS);
|
|
18190
|
+
return fail(`issue check: edit failed for ${repo}#${parsed.number}: ${e.message}${note ? ` (${note})` : ""}`);
|
|
18191
|
+
} finally {
|
|
18192
|
+
await edit.cleanup();
|
|
18193
|
+
}
|
|
18194
|
+
console.log(JSON.stringify({ number: parsed.number, repo, item: result.item.text, checked, changed: true }));
|
|
18195
|
+
});
|
|
18014
18196
|
program2.command("report").description("file a friction report on the Hub board (GitHub auth, dedups open reports) and print {number,url} JSON").option("--title <title>", "one-line friction summary").option("--title-file <path|->", "read the friction summary from a UTF-8 file, or from stdin with -").option("--body <body>", "report body (markdown)").option("--body-file <path|->", "read report body from a UTF-8 file, or from stdin with -").option("--type <type>", "bug | feature | task (sets the matching label)", "task").option("--priority <priority>", "urgent | high | medium | low (sets the board Priority field only, #416)", "medium").option("--repo <owner/repo>", `target repo (defaults to the org Hub: ${HUB_REPO2})`).option("--force", "file a new issue even when an open report looks like a duplicate").option("--json", "machine-readable output (already the default \u2014 report always prints JSON; #682)").action(async (o) => {
|
|
18015
18197
|
let body;
|
|
18016
18198
|
let priority;
|
|
@@ -18831,7 +19013,11 @@ async function stageLiveTarget() {
|
|
|
18831
19013
|
const repo = await resolveRepo();
|
|
18832
19014
|
if (!repo) throw new Error("stage --live: cannot resolve the current repo (run inside a GitHub-remoted checkout)");
|
|
18833
19015
|
const ref = await gitOut(["rev-parse", "--abbrev-ref", "HEAD"]).catch(() => "") || void 0;
|
|
18834
|
-
|
|
19016
|
+
const slug = slugOf(repo);
|
|
19017
|
+
const meta = await fetchProjectBySlug(slug, registryClientDeps(await loadConfig())).catch(() => null);
|
|
19018
|
+
const host = edgeDomainsByStage(meta).dev ?? `dev.${defaultSubdomain(slug)}.mutatismutandis.co`;
|
|
19019
|
+
if (!host.trim()) throw new Error(`stage --live: no dev edge host for ${slug} (registry edgeDomains.dev)`);
|
|
19020
|
+
return { slug, repo, host, ref };
|
|
18835
19021
|
}
|
|
18836
19022
|
async function runStageLiveCommand(o) {
|
|
18837
19023
|
let target;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mutmutco/cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.41.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",
|