@mutmutco/claude-plugin 4.5.151 → 4.5.152

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mmi",
3
3
  "displayName": "MMI",
4
- "version": "4.5.151",
4
+ "version": "4.5.152",
5
5
  "mmiCompat": "4.x",
6
6
  "description": "MMI workflow skills and org gates delivery.",
7
7
  "author": {
package/cli/dist/main.cjs CHANGED
@@ -7152,7 +7152,7 @@ async function readAppVaultSecret(read) {
7152
7152
  if (/^(Hub GitHub App allowance used up|rate limited)/.test(reason)) {
7153
7153
  throw new AppActorError(`app actor: the vault could not read the App credentials because GitHub is rate-limited, not a denial: ${reason}`, "mint-failed");
7154
7154
  }
7155
- if (/^authority refused|\(HTTP 404\b/.test(reason)) return null;
7155
+ if (/^authority refused|\(HTTP 40[14]\b/.test(reason)) return null;
7156
7156
  throw new AppActorError(`app actor: the vault read of the App credentials failed, not a denial: ${reason}`, "mint-failed");
7157
7157
  }
7158
7158
  }
@@ -7203,7 +7203,8 @@ async function mintInstallationToken(deps) {
7203
7203
  }
7204
7204
  return { token: body.token, expiresAt: body.expires_at, quotaScope: appQuotaScope(appId, installationId, process.env.GITHUB_API_URL) };
7205
7205
  }
7206
- async function pollTokenOrUndefined(deps) {
7206
+ async function pollTokenOrUndefined(deps, env = process.env) {
7207
+ if (env[APP_ACTOR_ENV]?.trim() === "personal") return void 0;
7207
7208
  try {
7208
7209
  return (await mintInstallationToken(deps)).token;
7209
7210
  } catch (e) {
@@ -7212,25 +7213,42 @@ async function pollTokenOrUndefined(deps) {
7212
7213
  }
7213
7214
  }
7214
7215
  var notedIneligibleCommand = false;
7216
+ var notedVaultDenied = false;
7215
7217
  async function activateAppActor(commandPath3, env, mint, note = (line) => process.stderr.write(`${line}
7216
7218
  `)) {
7217
7219
  activeQuotaScope = void 0;
7218
7220
  activeToken = void 0;
7219
7221
  const requested = (env[APP_ACTOR_ENV] ?? "").trim();
7220
- if (!requested) return "personal";
7221
- if (requested !== "app") {
7222
- throw new AppActorError(`app actor: unknown ${APP_ACTOR_ENV} value \u2014 only 'app' is supported; refusing to guess an identity`);
7222
+ if (requested === "personal") return "personal";
7223
+ if (requested && requested !== "app") {
7224
+ throw new AppActorError(`app actor: unknown ${APP_ACTOR_ENV} value \u2014 only 'app' and 'personal' are supported; refusing to guess an identity`);
7223
7225
  }
7224
7226
  if (!APP_ELIGIBLE_COMMANDS.has(commandPath3)) {
7225
- if (!notedIneligibleCommand) {
7227
+ if (requested === "app" && !notedIneligibleCommand) {
7226
7228
  notedIneligibleCommand = true;
7227
7229
  note(`app actor: '${commandPath3}' is personal-only (viewer/@me/authorship) \u2014 running on the personal token despite ${APP_ACTOR_ENV}=app`);
7228
7230
  }
7229
7231
  return "personal";
7230
7232
  }
7233
+ const personalGh = env.GH_TOKEN;
7234
+ const personalGithub = env.GITHUB_TOKEN;
7231
7235
  delete env.GH_TOKEN;
7232
7236
  delete env.GITHUB_TOKEN;
7233
- const minted = await mint();
7237
+ let minted;
7238
+ try {
7239
+ minted = await mint();
7240
+ } catch (e) {
7241
+ if (!requested && e instanceof AppActorError && e.code === "vault-denied") {
7242
+ if (personalGh !== void 0) env.GH_TOKEN = personalGh;
7243
+ if (personalGithub !== void 0) env.GITHUB_TOKEN = personalGithub;
7244
+ if (!notedVaultDenied) {
7245
+ notedVaultDenied = true;
7246
+ note("app actor: vault denied App credentials \u2014 running on the personal token on this host");
7247
+ }
7248
+ return "personal";
7249
+ }
7250
+ throw e;
7251
+ }
7234
7252
  env.GH_TOKEN = minted.token;
7235
7253
  activeQuotaScope = minted.quotaScope;
7236
7254
  activeToken = minted.token;
@@ -7741,7 +7759,7 @@ function setPollTokenProvider(provider) {
7741
7759
  observedPool.clear();
7742
7760
  }
7743
7761
  async function pollToken() {
7744
- if (!pollTokenProvider) return void 0;
7762
+ if (process.env.MMI_ACTOR?.trim() === "personal" || !pollTokenProvider) return void 0;
7745
7763
  pollTokenOnce ??= pollTokenProvider().catch((e) => {
7746
7764
  if (e instanceof AppActorError && e.code === "mint-failed") throw e;
7747
7765
  return void 0;
@@ -18666,10 +18684,10 @@ var rollout_plan_default = {
18666
18684
  note: "The v4.0.0 stamp happens at cut time (D6e #4463); until then the candidate is the origin/development head artifacts (built cli/dist + npm pack), identity proven by dist content hash (D6a)."
18667
18685
  },
18668
18686
  baseline: {
18669
- version: "4.5.151",
18670
- tag: "v4.5.151",
18671
- commit: "c80d8f0f8b26",
18672
- npm: "@mutmutco/cli@4.5.151"
18687
+ version: "4.5.152",
18688
+ tag: "v4.5.152",
18689
+ commit: "8437cd11d3f1",
18690
+ npm: "@mutmutco/cli@4.5.152"
18673
18691
  },
18674
18692
  exitCriterion: "fleet-n-of-n",
18675
18693
  hubOnlyShortcut: "forbidden",
@@ -18686,14 +18704,14 @@ var rollout_plan_default = {
18686
18704
  repo: "mutmutco/mmi-hub",
18687
18705
  role: "canary",
18688
18706
  schedule: "train",
18689
- v3Target: "v4.5.151"
18707
+ v3Target: "v4.5.152"
18690
18708
  }
18691
18709
  ],
18692
18710
  rollbackTrigger: "Any red inside the post-contract soak window: `devops train gate` FAIL attributable to the v4 doors, Hub endpoint health probe failure, a pre-v4 client admitted instead of receiving actionable HTTP 426, or npm consumer install/doctor failure on the v4-only dist.",
18693
18711
  rollback: {
18694
18712
  independent: true,
18695
- mechanism: "npm dist-tag latest -> 4.5.151 and redeploy the Hub Lambda from tag v4.5.151 (c80d8f0f8b26); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
18696
- v3Target: "v4.5.151 (@mutmutco/cli@4.5.151, tag commit c80d8f0f8b26 \u2014 last known-good release carrying the repo-index v4-only contract)"
18713
+ mechanism: "npm dist-tag latest -> 4.5.152 and redeploy the Hub Lambda from tag v4.5.152 (8437cd11d3f1); installed clients repair via `mmi-cli doctor`. No other cohort is touched.",
18714
+ v3Target: "v4.5.152 (@mutmutco/cli@4.5.152, tag commit 8437cd11d3f1 \u2014 last known-good release carrying the repo-index v4-only contract)"
18697
18715
  }
18698
18716
  },
18699
18717
  {
@@ -22984,6 +23002,14 @@ async function runTenantControl(deps, options) {
22984
23002
  if (output) result.taskOutput = clampTaskOutput(output);
22985
23003
  }
22986
23004
  }
23005
+ if (watch && runId != null && action === "run-task" && conclusion === "success") {
23006
+ try {
23007
+ const pages = JSON.parse(await deps.run("gh", ["api", `repos/${HUB_REPO2}/actions/runs/${runId}/artifacts`, "--paginate", "--slurp"]));
23008
+ result.outputArtifacts = pages.flatMap((page) => page.artifacts ?? []).filter((a) => a.name.startsWith("task-output-") && Number.isSafeInteger(a.id) && a.id > 0).map((a) => ({ id: a.id, name: a.name, runId, downloadCommand: `MMI_ACTOR=app mmi-cli devops api get repos/${HUB_REPO2}/actions/artifacts/${a.id}/zip --out tmp/task-output-${runId}-${a.id}.zip` }));
23009
+ } catch {
23010
+ result.outputArtifactReadError = `Could not read output artifacts; retry with --resume ${runId} --watch (no re-dispatch)`;
23011
+ }
23012
+ }
22987
23013
  result.note = conclusion === "success" ? `tenant-control ${action} run succeeded` : conclusion === "failure" ? `tenant-control ${action} run failed \u2014 inspect the run` : runId == null ? `dispatched tenant-control.yml (${action}) \u2014 run not correlated; check the Actions tab` : `dispatched tenant-control.yml (${action}) \u2014 not watched`;
22988
23014
  if (conclusion === "pending" && runId != null && action === "run-task") {
22989
23015
  result.note += `; run ${runId} \u2014 resume without re-dispatching: mmi-cli devops runtime tenant control ${repo} ${stage} run-task --resume ${runId} --watch`;
@@ -22998,6 +23024,8 @@ function renderTenantControl(r) {
22998
23024
  if (r.secrets?.length) {
22999
23025
  for (const s of r.secrets) lines2.push(` ${s.key}: ${s.status}`);
23000
23026
  }
23027
+ for (const a of r.outputArtifacts ?? []) lines2.push(` output artifact: ${a.name} (id ${a.id}, run ${a.runId})`, ` ${a.downloadCommand}`);
23028
+ if (r.outputArtifactReadError) lines2.push(` ${r.outputArtifactReadError}`);
23001
23029
  if (r.taskOutput) {
23002
23030
  lines2.push(" task output:");
23003
23031
  for (const l of r.taskOutput.split("\n")) lines2.push(` ${l}`);
@@ -32898,7 +32926,7 @@ function parseTenantTasksVar(raw) {
32898
32926
  if (!nameRe.test(name)) throw new Error(`org project set: tenantTasks name ${JSON.stringify(name)} must be lowercase kebab-case`);
32899
32927
  if (!rawTask || typeof rawTask !== "object" || Array.isArray(rawTask)) throw new Error(`org project set: tenantTasks.${name} must be an object`);
32900
32928
  const task = rawTask;
32901
- if (Object.keys(task).some((key) => !["service", "command", "stages", "timeoutSeconds", "artifact", "secretOutput"].includes(key))) throw new Error(`org project set: tenantTasks.${name} has an unknown field`);
32929
+ if (Object.keys(task).some((key) => !["service", "command", "stages", "timeoutSeconds", "artifact", "output", "secretOutput"].includes(key))) throw new Error(`org project set: tenantTasks.${name} has an unknown field`);
32902
32930
  if (typeof task.service !== "string" || !serviceRe.test(task.service)) throw new Error(`org project set: tenantTasks.${name}.service has an unsafe shape`);
32903
32931
  if (!Array.isArray(task.command) || task.command.length < 1 || task.command.length > 32 || task.command.some((arg) => typeof arg !== "string" || arg.length < 1 || arg.length > 256 || /[\u0000\r\n]/.test(arg))) throw new Error(`org project set: tenantTasks.${name}.command must be 1-32 bounded argv strings`);
32904
32932
  if (!Array.isArray(task.stages) || task.stages.length < 1 || task.stages.length > 3 || task.stages.some((stage) => typeof stage !== "string" || !validStages.has(stage)) || new Set(task.stages).size !== task.stages.length) throw new Error(`org project set: tenantTasks.${name}.stages must be unique dev/rc/main values`);
@@ -32908,12 +32936,16 @@ function parseTenantTasksVar(raw) {
32908
32936
  if (artifact !== "none" && artifact !== "required") throw new Error(`org project set: tenantTasks.${name}.artifact must be none or required`);
32909
32937
  const carriesPlaceholder = task.command.some((arg) => arg.includes("{artifact}"));
32910
32938
  if (artifact === "required" !== carriesPlaceholder) throw new Error(`org project set: tenantTasks.${name} must use {artifact} exactly when artifact is required`);
32939
+ if (task.output !== void 0 && task.output !== "file") throw new Error(`org project set: tenantTasks.${name}.output must be file, use {output}, and cannot combine with secretOutput`);
32940
+ const outputPlaceholder = task.command.some((arg) => arg.includes("{output}"));
32941
+ if (task.output === "file" !== outputPlaceholder || task.output !== void 0 && task.secretOutput !== void 0) throw new Error(`org project set: tenantTasks.${name}.output must be file, use {output}, and cannot combine with secretOutput`);
32911
32942
  out[name] = {
32912
32943
  service: task.service,
32913
32944
  command: task.command,
32914
32945
  stages: task.stages,
32915
32946
  ...task.timeoutSeconds !== void 0 ? { timeoutSeconds: task.timeoutSeconds } : {},
32916
32947
  ...artifact !== "none" ? { artifact } : {},
32948
+ ...task.output === "file" ? { output: "file" } : {},
32917
32949
  ...task.secretOutput !== void 0 ? { secretOutput: parseTenantTaskSecretOutput(task.secretOutput, `tenantTasks.${name}.secretOutput`) } : {}
32918
32950
  };
32919
32951
  }
@@ -32976,7 +33008,7 @@ var SETTABLE_VAR_HINTS = {
32976
33008
  requiredGcpApis: "comma-string",
32977
33009
  requiredRuntimeSecrets: 'JSON stage map, e.g. {"dev":["KEY"],"rc":["KEY"],"main":["KEY"]}',
32978
33010
  requiredBuildSecrets: 'JSON flat array, e.g. ["NODE_AUTH_TOKEN=@github-packages-token"]',
32979
- tenantTasks: 'JSON map {name:{service,command[],stages[],timeoutSeconds?,artifact?:"required",secretOutput?:{path,vault:{repo,key},overwrite?}}} \u2014 merged per entry, at most 20 stored in total; retire ONE task with --unset-task NAME; use {artifact} in argv when required; a secretOutput is master-only',
33011
+ tenantTasks: 'JSON map {name:{service,command[],stages[],timeoutSeconds?,artifact?:"required",output?:"file",secretOutput?:{path,vault:{repo,key},overwrite?}}} \u2014 merged per entry, at most 20 stored in total; retire ONE task with --unset-task NAME; use {artifact} in argv when required; a secretOutput is master-only',
32980
33012
  internalPeers: "JSON {owner/repo:[dev|rc|main]} \u2014 same-box suite peers via the box Caddy; clear with --unset internalPeers (#7886)",
32981
33013
  secrets: "JSON catalog map keyed by KEY {key,purpose,group,owner,stages[],consumers[]} \u2014 merged per entry; drop ONE entry with --unset-secret KEY; clear ALL with --unset secrets --confirm-clear-secrets; prefer --secrets-file",
32982
33014
  edgeDomains: "JSON {dev,rc,main} domain map",
@@ -53256,8 +53288,9 @@ async function runBrokerRestChecks(deps, spec, resolveRepo2) {
53256
53288
  return { rows: read.rows, state: read.state, exitCode: checkRowsExitCode(read.state) };
53257
53289
  }
53258
53290
  function requireBrokerApp(label) {
53259
- if (process.env.MMI_ACTOR !== "app") {
53260
- fail(`${label}: App-only read \u2014 re-run with spawn-scoped MMI_ACTOR=app (reads never fall back to personal; interactive use keeps gh and the human commands)`);
53291
+ const actor = (process.env.MMI_ACTOR ?? "").trim();
53292
+ if (actor && actor !== "app") {
53293
+ fail(`${label}: App-only read \u2014 re-run with spawn-scoped MMI_ACTOR=app (explicit personal opt-out uses gh and the human commands instead)`);
53261
53294
  }
53262
53295
  }
53263
53296
  function registerPrChecksRequiredCommand(prRoot, resolveRepo2, overrides) {
@@ -53442,7 +53475,10 @@ var ACTIONS_JOBS_RE = new RegExp(`^repos/${NAME}/${NAME}/actions/runs/\\d+(?:/at
53442
53475
  var ACTIONS_ARTIFACTS_RE = new RegExp(`^repos/${NAME}/${NAME}/actions/runs/\\d+/artifacts$`);
53443
53476
  var ACTIONS_ARTIFACT_ZIP_RE = new RegExp(`^repos/${NAME}/${NAME}/actions/artifacts/\\d+/zip$`);
53444
53477
  var ACTIONS_JOB_LOGS_RE = new RegExp(`^repos/${NAME}/${NAME}/actions/jobs/\\d+/logs$`);
53445
- var API_GET_ALLOWED_SHAPES = "repos/{owner}/{repo}/pulls/{n}/files, repos/{owner}/{repo}/compare/{a}...{b}, repos/{owner}/{repo}/contents/{path}[?ref=<branch>], repos/{owner}/{repo}/actions/runs[?per_page=1-100&page=<n>&status=&branch=&event=&head_sha=<40-hex>&created=<date-range>&actor=, each key at most once], repos/{owner}/{repo}/actions/runs/{id}, repos/{owner}/{repo}/actions/runs/{id}/jobs, repos/{owner}/{repo}/actions/runs/{id}/attempts/{n}/jobs, repos/{owner}/{repo}/actions/runs/{id}/artifacts, repos/{owner}/{repo}/actions/artifacts/{id}/zip (needs --out <file>), repos/{owner}/{repo}/actions/jobs/{id}/logs (needs --out <file>)";
53478
+ var RELEASE_LATEST_RE = new RegExp(`^repos/${NAME}/${NAME}/releases/latest$`);
53479
+ var RELEASE_TAG_RE = new RegExp(`^repos/${NAME}/${NAME}/releases/tags/(${NAME})$`);
53480
+ var RELEASE_ASSET_RE = new RegExp(`^repos/${NAME}/${NAME}/releases/assets/\\d+$`);
53481
+ var API_GET_ALLOWED_SHAPES = "repos/{owner}/{repo}/pulls/{n}/files, repos/{owner}/{repo}/compare/{a}...{b}, repos/{owner}/{repo}/contents/{path}[?ref=<branch>], repos/{owner}/{repo}/actions/runs[?per_page=1-100&page=<n>&status=&branch=&event=&head_sha=<40-hex>&created=<date-range>&actor=, each key at most once], repos/{owner}/{repo}/actions/runs/{id}, repos/{owner}/{repo}/actions/runs/{id}/jobs, repos/{owner}/{repo}/actions/runs/{id}/attempts/{n}/jobs, repos/{owner}/{repo}/actions/runs/{id}/artifacts, repos/{owner}/{repo}/actions/artifacts/{id}/zip (needs --out <file>), repos/{owner}/{repo}/actions/jobs/{id}/logs (needs --out <file>), repos/{owner}/{repo}/releases/latest, repos/{owner}/{repo}/releases/tags/{tag}, repos/{owner}/{repo}/releases/assets/{id} (needs --out <file>)";
53446
53482
  function parseApiGetPath(rawPath) {
53447
53483
  const path2 = rawPath.trim();
53448
53484
  const refuse = () => new Error(`api get: refusing '${rawPath}' \u2014 only these GET reads are allowed (#7219): ${API_GET_ALLOWED_SHAPES}`);
@@ -53458,6 +53494,10 @@ function parseApiGetPath(rawPath) {
53458
53494
  if (ACTIONS_ARTIFACTS_RE.test(path2)) return { kind: "actions-artifacts", path: path2, paginate: true };
53459
53495
  if (ACTIONS_ARTIFACT_ZIP_RE.test(path2)) return { kind: "actions-artifact-zip", path: path2, paginate: false };
53460
53496
  if (ACTIONS_JOB_LOGS_RE.test(path2)) return { kind: "actions-job-logs", path: path2, paginate: false };
53497
+ if (RELEASE_LATEST_RE.test(path2)) return { kind: "release-latest", path: path2, paginate: false };
53498
+ const tag = RELEASE_TAG_RE.exec(path2)?.[1];
53499
+ if (tag && !tag.includes("..")) return { kind: "release-tag", path: path2, paginate: false };
53500
+ if (RELEASE_ASSET_RE.test(path2)) return { kind: "release-asset", path: path2, paginate: false };
53461
53501
  if (CONTENTS_BASE_RE.test(base) && (query === "" || CONTENTS_QUERY_RE.test(query))) {
53462
53502
  return { kind: "contents", path: path2, paginate: false };
53463
53503
  }
@@ -53469,7 +53509,8 @@ function buildApiGetArgs(path2, accept) {
53469
53509
  const req = parseApiGetPath(path2);
53470
53510
  const args = ["api", req.path];
53471
53511
  if (req.paginate) args.push("--paginate");
53472
- const header = acceptHeaderFor(accept);
53512
+ const requestedHeader = acceptHeaderFor(accept);
53513
+ const header = req.kind === "release-asset" ? "application/octet-stream" : requestedHeader;
53473
53514
  if (header !== API_ACCEPT_HEADERS.json) args.push("-H", `Accept: ${header}`);
53474
53515
  return args;
53475
53516
  }
@@ -53486,16 +53527,16 @@ async function readAppApi(path2, runner = apiReadRunner) {
53486
53527
  }
53487
53528
  function registerApiReadCommands(program3, overrides) {
53488
53529
  const spawn6 = overrides?.runner ?? apiReadRunner;
53489
- const api = program3.command("api").description("bounded GitHub REST reads for automation \u2014 GET only, allowlisted paths (pull files, compare, contents, actions runs/jobs/artifacts)");
53530
+ const api = program3.command("api").description("bounded GitHub REST reads for automation \u2014 GET only, allowlisted paths (pull files, compare, contents, actions runs/jobs/artifacts, releases)");
53490
53531
  api.command("get").description("GET one allowlisted REST path and print the response body to stdout (JSON, or raw file bytes with --accept raw); refuses everything outside the allowlist").option("--accept <type>", "response media type: json (default) or raw (file bytes)", (v) => {
53491
53532
  acceptHeaderFor(v);
53492
53533
  return v.trim().toLowerCase();
53493
- }, "json").option("--out <file>", "write the response bytes to this file instead of stdout (required for, and only valid with, actions/artifacts/{id}/zip)").argument("<path>", "allowlisted GET path (shapes named on refusal)", (v) => parseApiGetPath(v)).action(async (req, o) => {
53494
- const zip = req.kind === "actions-artifact-zip" || req.kind === "actions-job-logs";
53495
- if (zip !== (o.out !== void 0)) return fail(`api get: --out <file> is required for, and only valid with, repos/{owner}/{repo}/actions/artifacts/{id}/zip and repos/{owner}/{repo}/actions/jobs/{id}/logs`);
53496
- const actionsRoute = req.kind.startsWith("actions-");
53497
- if (actionsRoute) requireBrokerApp("api get");
53498
- const env = actionsRoute || process.env.MMI_ACTOR === "app" ? brokerChildEnv() : void 0;
53534
+ }, "json").option("--out <file>", "write response bytes to a file (required for, and only valid with, actions artifact zips, job logs, and release assets)").argument("<path>", "allowlisted GET path (shapes named on refusal)", (v) => parseApiGetPath(v)).action(async (req, o) => {
53535
+ const zip = req.kind === "actions-artifact-zip" || req.kind === "actions-job-logs" || req.kind === "release-asset";
53536
+ if (zip !== (o.out !== void 0)) return fail(`api get: --out <file> is required for, and only valid with, repos/{owner}/{repo}/actions/artifacts/{id}/zip and repos/{owner}/{repo}/actions/jobs/{id}/logs and repos/{owner}/{repo}/releases/assets/{id}`);
53537
+ const appRoute = req.kind.startsWith("actions-") || req.kind.startsWith("release-");
53538
+ if (appRoute) requireBrokerApp("api get");
53539
+ const env = appRoute || process.env.MMI_ACTOR?.trim() !== "personal" ? brokerChildEnv() : void 0;
53499
53540
  let body;
53500
53541
  try {
53501
53542
  body = await runApiGet(
@@ -57203,7 +57244,7 @@ ${filelessTransitionGuide(target, o.stage)}`
57203
57244
  return reportWrite("org project set-deploy", res);
57204
57245
  });
57205
57246
  var projectTenantTasks = project.command("tenant-tasks").description("project-admin self-declare of tenant tasks for every stage of your own repo \u2014 new-task-only; editing an existing task stays master-only (`org project set --var tenantTasks=...`)");
57206
- projectTenantTasks.command("declare <name>").description("register a NEW tenant task \u2014 becomes runnable via `runtime tenant control \u2026 run-task` on its declared dev/rc/main stages. Body shape {service,command[],stages[],timeoutSeconds?,artifact?}; required artifact tasks must carry {artifact} in the command argv.").option("--service <name>", "service in the tenant runtime the task runs in (required unless --var carries it)").option("--command <argv...>", `task command as argv (repeatable; required unless --var carries it). An argv token that starts with "-" (python -m mod) is read as an option and the whole call fails to parse: declare such a command with --var '{"service":"<svc>","command":["python","-m","mod"],"stages":["dev"]}' instead (#7668)`).option("--stages <list>", "comma-separated dev,rc,main stages this task may run on (required unless --var carries it)").option("--timeout-seconds <n>", "optional wall-clock ceiling 1..10800 (1..3600 with a secretOutput)").option("--artifact <none|required>", "artifact contract (default none; required tasks must contain {artifact} in argv)").option("--var <json>", "full task object {service,command[],stages[],timeoutSeconds?,artifact?} \u2014 use instead of the individual flags. A task carrying secretOutput:{path,vault:{repo,key},overwrite?} is master-only; declare that via `org project set --var tenantTasks=...`").option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action(async (name, o) => {
57247
+ projectTenantTasks.command("declare <name>").description('register a NEW tenant task \u2014 becomes runnable via `runtime tenant control \u2026 run-task` on its declared dev/rc/main stages. Body shape {service,command[],stages[],timeoutSeconds?,artifact?,output?:"file"}; required artifact tasks must carry {artifact} in the command argv.').option("--service <name>", "service in the tenant runtime the task runs in (required unless --var carries it)").option("--command <argv...>", `task command as argv (repeatable; required unless --var carries it). An argv token that starts with "-" (python -m mod) is read as an option and the whole call fails to parse: declare such a command with --var '{"service":"<svc>","command":["python","-m","mod"],"stages":["dev"]}' instead (#7668)`).option("--stages <list>", "comma-separated dev,rc,main stages this task may run on (required unless --var carries it)").option("--timeout-seconds <n>", "optional wall-clock ceiling 1..10800 (1..3600 with a secretOutput)").option("--output <file>", "return {output} as a private 1-day Actions artifact (cannot combine with secretOutput)").option("--artifact <none|required>", "artifact contract (default none; required tasks must contain {artifact} in argv)").option("--var <json>", 'full task object {service,command[],stages[],timeoutSeconds?,artifact?,output?:"file"} \u2014 use instead of the individual flags. A task carrying secretOutput:{path,vault:{repo,key},overwrite?} is master-only; declare that via `org project set --var tenantTasks=...`').option("--repo <owner/repo>", "target repo (defaults to the current repo)").option("--json", "machine-readable output").action(async (name, o) => {
57207
57248
  const cfg = await loadConfig();
57208
57249
  let target;
57209
57250
  try {
@@ -57228,7 +57269,8 @@ projectTenantTasks.command("declare <name>").description("register a NEW tenant
57228
57269
  command: o.command,
57229
57270
  stages: o.stages.split(",").map((s) => s.trim()).filter(Boolean),
57230
57271
  ...o.timeoutSeconds !== void 0 ? { timeoutSeconds: Number(o.timeoutSeconds) } : {},
57231
- ...o.artifact !== void 0 ? { artifact: o.artifact } : {}
57272
+ ...o.artifact !== void 0 ? { artifact: o.artifact } : {},
57273
+ ...o.output !== void 0 ? { output: o.output } : {}
57232
57274
  };
57233
57275
  }
57234
57276
  if (task && typeof task === "object" && task.secretOutput !== void 0) {
package/cli/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/cli",
3
- "version": "4.5.151",
3
+ "version": "4.5.152",
4
4
  "description": "MMI Future CLI — the org dev toolbox and shared cross-IDE engine for every registry-declared MMI coding surface.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mutmutco/claude-plugin",
3
- "version": "4.5.151",
3
+ "version": "4.5.152",
4
4
  "description": "MMI workflow skills and org gates delivery.",
5
5
  "author": {
6
6
  "name": "MMI Future",