@m8t-stack/cli 0.2.18 → 0.2.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/cli.js +1186 -316
  2. package/dist/cli.js.map +1 -1
  3. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1227,7 +1227,7 @@ var init_enable_hosted_brain = __esm({
1227
1227
  import { Builtins, Cli } from "clipanion";
1228
1228
 
1229
1229
  // src/lib/package-version.ts
1230
- var CLI_VERSION = "0.2.18";
1230
+ var CLI_VERSION = "0.2.19";
1231
1231
 
1232
1232
  // src/lib/render-error.ts
1233
1233
  init_errors();
@@ -3326,12 +3326,12 @@ function encodeURIPath(str4) {
3326
3326
  return str4.replace(/[^A-Za-z0-9\-._~!$&'()*+,;=:@]+/g, encodeURIComponent);
3327
3327
  }
3328
3328
  var EMPTY = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null));
3329
- var createPathTagFunction = (pathEncoder = encodeURIPath) => function path32(statics, ...params) {
3329
+ var createPathTagFunction = (pathEncoder = encodeURIPath) => function path33(statics, ...params) {
3330
3330
  if (statics.length === 1)
3331
3331
  return statics[0];
3332
3332
  let postPath = false;
3333
3333
  const invalidSegments = [];
3334
- const path33 = statics.reduce((previousValue, currentValue, index) => {
3334
+ const path34 = statics.reduce((previousValue, currentValue, index) => {
3335
3335
  if (/[?#]/.test(currentValue)) {
3336
3336
  postPath = true;
3337
3337
  }
@@ -3348,7 +3348,7 @@ var createPathTagFunction = (pathEncoder = encodeURIPath) => function path32(sta
3348
3348
  }
3349
3349
  return previousValue + currentValue + (index === params.length ? "" : encoded);
3350
3350
  }, "");
3351
- const pathOnly = path33.split(/[?#]/, 1)[0];
3351
+ const pathOnly = path34.split(/[?#]/, 1)[0];
3352
3352
  const invalidSegmentPattern = /(?<=^|\/)(?:\.|%2e){1,2}(?=\/|$)/gi;
3353
3353
  let match;
3354
3354
  while ((match = invalidSegmentPattern.exec(pathOnly)) !== null) {
@@ -3369,10 +3369,10 @@ var createPathTagFunction = (pathEncoder = encodeURIPath) => function path32(sta
3369
3369
  }, "");
3370
3370
  throw new OpenAIError(`Path parameters result in path with invalid segments:
3371
3371
  ${invalidSegments.map((e) => e.error).join("\n")}
3372
- ${path33}
3372
+ ${path34}
3373
3373
  ${underline}`);
3374
3374
  }
3375
- return path33;
3375
+ return path34;
3376
3376
  };
3377
3377
  var path = /* @__PURE__ */ createPathTagFunction(encodeURIPath);
3378
3378
 
@@ -10889,9 +10889,9 @@ var OpenAI = class {
10889
10889
  this.apiKey = token;
10890
10890
  return true;
10891
10891
  }
10892
- buildURL(path32, query, defaultBaseURL) {
10892
+ buildURL(path33, query, defaultBaseURL) {
10893
10893
  const baseURL = !__classPrivateFieldGet(this, _OpenAI_instances, "m", _OpenAI_baseURLOverridden).call(this) && defaultBaseURL || this.baseURL;
10894
- const url = isAbsoluteURL(path32) ? new URL(path32) : new URL(baseURL + (baseURL.endsWith("/") && path32.startsWith("/") ? path32.slice(1) : path32));
10894
+ const url = isAbsoluteURL(path33) ? new URL(path33) : new URL(baseURL + (baseURL.endsWith("/") && path33.startsWith("/") ? path33.slice(1) : path33));
10895
10895
  const defaultQuery = this.defaultQuery();
10896
10896
  const pathQuery = Object.fromEntries(url.searchParams);
10897
10897
  if (!isEmptyObj(defaultQuery) || !isEmptyObj(pathQuery)) {
@@ -10919,24 +10919,24 @@ var OpenAI = class {
10919
10919
  */
10920
10920
  async prepareRequest(request, { url, options }) {
10921
10921
  }
10922
- get(path32, opts) {
10923
- return this.methodRequest("get", path32, opts);
10922
+ get(path33, opts) {
10923
+ return this.methodRequest("get", path33, opts);
10924
10924
  }
10925
- post(path32, opts) {
10926
- return this.methodRequest("post", path32, opts);
10925
+ post(path33, opts) {
10926
+ return this.methodRequest("post", path33, opts);
10927
10927
  }
10928
- patch(path32, opts) {
10929
- return this.methodRequest("patch", path32, opts);
10928
+ patch(path33, opts) {
10929
+ return this.methodRequest("patch", path33, opts);
10930
10930
  }
10931
- put(path32, opts) {
10932
- return this.methodRequest("put", path32, opts);
10931
+ put(path33, opts) {
10932
+ return this.methodRequest("put", path33, opts);
10933
10933
  }
10934
- delete(path32, opts) {
10935
- return this.methodRequest("delete", path32, opts);
10934
+ delete(path33, opts) {
10935
+ return this.methodRequest("delete", path33, opts);
10936
10936
  }
10937
- methodRequest(method, path32, opts) {
10937
+ methodRequest(method, path33, opts) {
10938
10938
  return this.request(Promise.resolve(opts).then((opts2) => {
10939
- return { method, path: path32, ...opts2 };
10939
+ return { method, path: path33, ...opts2 };
10940
10940
  }));
10941
10941
  }
10942
10942
  request(options, remainingRetries = null) {
@@ -11058,8 +11058,8 @@ var OpenAI = class {
11058
11058
  }));
11059
11059
  return { response, options, controller, requestLogID, retryOfRequestLogID, startTime };
11060
11060
  }
11061
- getAPIList(path32, Page2, opts) {
11062
- return this.requestAPIList(Page2, opts && "then" in opts ? opts.then((opts2) => ({ method: "get", path: path32, ...opts2 })) : { method: "get", path: path32, ...opts });
11061
+ getAPIList(path33, Page2, opts) {
11062
+ return this.requestAPIList(Page2, opts && "then" in opts ? opts.then((opts2) => ({ method: "get", path: path33, ...opts2 })) : { method: "get", path: path33, ...opts });
11063
11063
  }
11064
11064
  requestAPIList(Page2, options) {
11065
11065
  const request = this.makeRequest(options, null, void 0);
@@ -11153,8 +11153,8 @@ var OpenAI = class {
11153
11153
  }
11154
11154
  async buildRequest(inputOptions, { retryCount = 0 } = {}) {
11155
11155
  const options = { ...inputOptions };
11156
- const { method, path: path32, query, defaultBaseURL } = options;
11157
- const url = this.buildURL(path32, query, defaultBaseURL);
11156
+ const { method, path: path33, query, defaultBaseURL } = options;
11157
+ const url = this.buildURL(path33, query, defaultBaseURL);
11158
11158
  if ("timeout" in options)
11159
11159
  validatePositiveInteger("timeout", options.timeout);
11160
11160
  options.timeout = options.timeout ?? this.timeout;
@@ -12156,10 +12156,10 @@ var BindListCommand = class extends M8tCommand {
12156
12156
  const qs = new URLSearchParams();
12157
12157
  if (this.channel) qs.set("channel", this.channel);
12158
12158
  if (this.status) qs.set("status", this.status);
12159
- const path32 = qs.toString().length > 0 ? `/api/bindings?${qs.toString()}` : "/api/bindings";
12159
+ const path33 = qs.toString().length > 0 ? `/api/bindings?${qs.toString()}` : "/api/bindings";
12160
12160
  const data = await apiCall(
12161
12161
  { gatewayUrl: ctx.gatewayUrl, gatewayClientId: ctx.gatewayClientId },
12162
- { method: "GET", path: path32 },
12162
+ { method: "GET", path: path33 },
12163
12163
  (msg) => this.context.stderr.write(msg + "\n")
12164
12164
  );
12165
12165
  if (mode === "json") {
@@ -13197,6 +13197,13 @@ var defaultGhExec = (cmd, args) => new Promise((resolve3) => {
13197
13197
  const child = spawn2(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
13198
13198
  let stdout = "";
13199
13199
  let stderr = "";
13200
+ let settled = false;
13201
+ const done = (r) => {
13202
+ if (!settled) {
13203
+ settled = true;
13204
+ resolve3(r);
13205
+ }
13206
+ };
13200
13207
  child.stdout.on("data", (d) => {
13201
13208
  stdout += d.toString();
13202
13209
  });
@@ -13204,7 +13211,10 @@ var defaultGhExec = (cmd, args) => new Promise((resolve3) => {
13204
13211
  stderr += d.toString();
13205
13212
  });
13206
13213
  child.on("close", (code) => {
13207
- resolve3({ stdout, stderr, exitCode: code ?? -1 });
13214
+ done({ stdout, stderr, exitCode: code ?? -1 });
13215
+ });
13216
+ child.on("error", (err) => {
13217
+ done({ stdout, stderr: stderr || err.message, exitCode: -1 });
13208
13218
  });
13209
13219
  });
13210
13220
  async function isGhAuthed(exec = defaultGhExec) {
@@ -13360,8 +13370,8 @@ ${r.stderr}`;
13360
13370
  message: `gh api /repos/${slug} failed (exit ${r.exitCode.toString()}): ${(r.stderr || r.stdout).slice(0, 200)}`
13361
13371
  });
13362
13372
  },
13363
- getFile: async (path32) => {
13364
- const r = await exec("gh", ["api", "-H", "Accept: application/vnd.github.raw+json", `/repos/${slug}/contents/${path32}`]);
13373
+ getFile: async (path33) => {
13374
+ const r = await exec("gh", ["api", "-H", "Accept: application/vnd.github.raw+json", `/repos/${slug}/contents/${path33}`]);
13365
13375
  return r.exitCode === 0 ? r.stdout : null;
13366
13376
  },
13367
13377
  isEmpty: async () => {
@@ -13535,15 +13545,21 @@ function discoverKvUri(env, override) {
13535
13545
  hint: "Pass --kv-uri or set AZURE_KEYVAULT_URI / KEYVAULT_URI."
13536
13546
  });
13537
13547
  }
13538
- function resolveRepoRoot() {
13548
+ function tryResolveRepoRoot() {
13539
13549
  const marker = path5.join(os2.homedir(), ".m8t-stack", "repo-root");
13540
- if (!fs5.existsSync(marker)) {
13550
+ if (!fs5.existsSync(marker)) return null;
13551
+ const root = fs5.readFileSync(marker, "utf8").trim();
13552
+ return root.length > 0 ? root : null;
13553
+ }
13554
+ function resolveRepoRoot() {
13555
+ const root = tryResolveRepoRoot();
13556
+ if (root === null) {
13541
13557
  throw new LocalCliError({
13542
13558
  code: "M8T_REPO_ROOT_MISSING",
13543
13559
  message: "~/.m8t-stack/repo-root marker missing \u2014 m8t install never completed."
13544
13560
  });
13545
13561
  }
13546
- return fs5.readFileSync(marker, "utf8").trim();
13562
+ return root;
13547
13563
  }
13548
13564
 
13549
13565
  // src/commands/brain/check-app.ts
@@ -14309,7 +14325,7 @@ function appRepoProbe(args) {
14309
14325
  const res = await doFetch(`${GH_API}/repos/${args.repo}`, { headers: H, signal: AbortSignal.timeout(GH_REQUEST_TIMEOUT_MS) });
14310
14326
  return res.status;
14311
14327
  },
14312
- getFile: (path32) => readRepoFileViaApp({ token: args.token, repo: args.repo, path: path32, fetchImpl: args.fetchImpl }),
14328
+ getFile: (path33) => readRepoFileViaApp({ token: args.token, repo: args.repo, path: path33, fetchImpl: args.fetchImpl }),
14313
14329
  isEmpty: async () => {
14314
14330
  const res = await doFetch(`${GH_API}/repos/${args.repo}/contents`, { headers: H, signal: AbortSignal.timeout(GH_REQUEST_TIMEOUT_MS) });
14315
14331
  return res.status === 404;
@@ -17447,8 +17463,8 @@ async function fetchPublicTags(repo, fetchImpl = fetch) {
17447
17463
  hint: "Pass a ghcr.io/<owner>/<name> repo, or use the BYOC private-ACR deploy flow."
17448
17464
  });
17449
17465
  }
17450
- const path32 = repo.replace(/^ghcr\.io\//, "");
17451
- const tokenRes = await fetchImpl(`https://ghcr.io/token?scope=repository:${path32}:pull`);
17466
+ const path33 = repo.replace(/^ghcr\.io\//, "");
17467
+ const tokenRes = await fetchImpl(`https://ghcr.io/token?scope=repository:${path33}:pull`);
17452
17468
  if (!tokenRes.ok) {
17453
17469
  throw new LocalCliError({
17454
17470
  code: "PLATFORM_GHCR_TOKEN_FAILED",
@@ -17464,7 +17480,7 @@ async function fetchPublicTags(repo, fetchImpl = fetch) {
17464
17480
  hint: "Make the package public (deploy/ghcr-package-visibility.md), then retry."
17465
17481
  });
17466
17482
  }
17467
- const tagsRes = await fetchImpl(`https://ghcr.io/v2/${path32}/tags/list`, {
17483
+ const tagsRes = await fetchImpl(`https://ghcr.io/v2/${path33}/tags/list`, {
17468
17484
  headers: { Authorization: `Bearer ${token}` }
17469
17485
  });
17470
17486
  if (!tagsRes.ok) {
@@ -17601,12 +17617,49 @@ function entityToStamp(e) {
17601
17617
 
17602
17618
  // ../../packages/platform-release/dist/esm/channel-url.js
17603
17619
  var CHANNEL_LATEST_URL = "https://github.com/m8t-run/m8t/releases/latest/download/manifest.json";
17604
- function channelUrlForVersion(tag) {
17605
- return `https://github.com/m8t-run/m8t/releases/download/${tag}/manifest.json`;
17620
+ function platformTag(version) {
17621
+ const bare = version.trim().replace(/^platform-/, "").replace(/^v/, "");
17622
+ return `platform-v${bare}`;
17623
+ }
17624
+ function channelUrlForVersion(version) {
17625
+ return `https://github.com/m8t-run/m8t/releases/download/${platformTag(version)}/manifest.json`;
17626
+ }
17627
+
17628
+ // ../../packages/platform-release/dist/esm/apply-request.js
17629
+ var APPLY_REQUEST_PK = "system";
17630
+ var APPLY_REQUEST_RK = "apply-request";
17631
+ var IN_FLIGHT = ["claimed", "applying"];
17632
+ function isInFlight(status) {
17633
+ return IN_FLIGHT.includes(status);
17634
+ }
17635
+ function applyRequestToEntity(r) {
17636
+ return { partitionKey: APPLY_REQUEST_PK, rowKey: APPLY_REQUEST_RK, value: JSON.stringify(r), updatedAt: r.updatedAt };
17637
+ }
17638
+ function entityToApplyRequest(e) {
17639
+ return JSON.parse(e.value);
17640
+ }
17641
+
17642
+ // ../../packages/platform-release/dist/esm/infra-params.js
17643
+ var INFRA_PARAMS_PK = "system";
17644
+ var INFRA_PARAMS_RK = "infra-params";
17645
+ function infraParamsToEntity(p) {
17646
+ return { partitionKey: INFRA_PARAMS_PK, rowKey: INFRA_PARAMS_RK, value: JSON.stringify(p) };
17647
+ }
17648
+ function entityToInfraParams(e) {
17649
+ return JSON.parse(e.value);
17606
17650
  }
17607
17651
 
17608
17652
  // src/lib/release-channel.ts
17609
17653
  init_errors();
17654
+ function manifestSourceForVersionTag(channelUrl, version) {
17655
+ const tag = platformTag(version);
17656
+ const custom = channelUrl.trim();
17657
+ if (!custom || custom === CHANNEL_LATEST_URL) return { channel: true, version: tag };
17658
+ if (custom.includes("/releases/latest/download/")) {
17659
+ return { url: custom.replace("/releases/latest/download/", `/releases/download/${tag}/`) };
17660
+ }
17661
+ return { url: custom };
17662
+ }
17610
17663
  async function fetchManifest(source, deps = {}) {
17611
17664
  const readFile8 = deps.readFile ?? ((p) => fs18.readFileSync(p, "utf8"));
17612
17665
  const fetchImpl = deps.fetchImpl ?? fetch;
@@ -17899,6 +17952,24 @@ function buildBicepParams(p) {
17899
17952
  `acrResourceId=${p.acrResourceId}`
17900
17953
  ];
17901
17954
  if (p.foundryTracingMode) params.push(`foundryTracingMode=${p.foundryTracingMode}`);
17955
+ if (p.assignSubscriptionRoles !== void 0) params.push(`assignSubscriptionRoles=${String(p.assignSubscriptionRoles)}`);
17956
+ if (p.provisionUpdater !== void 0) params.push(`provisionUpdater=${String(p.provisionUpdater)}`);
17957
+ if (p.installerImage !== void 0) params.push(`installerImage=${p.installerImage}`);
17958
+ if (p.updateCron !== void 0) params.push(`updateCron=${p.updateCron}`);
17959
+ if (p.channelUrl !== void 0) params.push(`channelUrl=${p.channelUrl}`);
17960
+ if (p.gatewayCpu) params.push(`gatewayCpu=${p.gatewayCpu}`);
17961
+ if (p.gatewayMemory) params.push(`gatewayMemory=${p.gatewayMemory}`);
17962
+ if (p.refereeEnabled) params.push(`refereeEnabled=${p.refereeEnabled}`);
17963
+ if (p.refereeBrainRepos) params.push(`refereeBrainRepos=${p.refereeBrainRepos}`);
17964
+ if (p.refereeFeedRepo) params.push(`refereeFeedRepo=${p.refereeFeedRepo}`);
17965
+ if (p.refereeInstallationId) params.push(`refereeInstallationId=${p.refereeInstallationId}`);
17966
+ if (p.refereeWebhookHmacKvUri) params.push(`refereeWebhookHmacKvUri=${p.refereeWebhookHmacKvUri}`);
17967
+ if (p.examKvUri) params.push(`examKvUri=${p.examKvUri}`);
17968
+ if (p.examLaWorkspaceId) params.push(`examLaWorkspaceId=${p.examLaWorkspaceId}`);
17969
+ if (p.brainEvalDeployment) params.push(`brainEvalDeployment=${p.brainEvalDeployment}`);
17970
+ if (p.brainAppLogin) params.push(`brainAppLogin=${p.brainAppLogin}`);
17971
+ if (p.refereeCheckpointDir) params.push(`refereeCheckpointDir=${p.refereeCheckpointDir}`);
17972
+ if (p.examApiBase) params.push(`examApiBase=${p.examApiBase}`);
17902
17973
  return params;
17903
17974
  }
17904
17975
  async function resolveRepoRoot2(home = os9.homedir()) {
@@ -18154,22 +18225,22 @@ async function applyPlan(plan, deps, ctx) {
18154
18225
  stamp.components.gateway = await deps.applyGateway(a, ctx);
18155
18226
  } else if (a.component === "codingAgent" || a.component === "azureExecutor") {
18156
18227
  const r = await deps.applyAgent(a, ctx);
18157
- if (r !== "absent") {
18158
- stamp.components[a.component] = { ...r, state: "managed" };
18159
- } else {
18160
- ctx.onProgress?.(`${a.component}: not deployed on this install \u2014 stamp left unchanged.`);
18228
+ if (r === "absent") {
18229
+ ctx.onProgress?.(`${a.component}: not deployed on this install \u2014 skipped (not gated).`);
18230
+ continue;
18161
18231
  }
18232
+ stamp.components[a.component] = { ...r, state: "managed" };
18162
18233
  } else {
18163
18234
  if (!a.personaName) {
18164
18235
  throw new LocalCliError({ code: "PLATFORM_PERSONA_NO_NAME", message: "persona action missing personaName" });
18165
18236
  }
18166
18237
  const r = await deps.applyPersona(a, ctx);
18167
18238
  if (r === "absent") {
18168
- ctx.onProgress?.(`persona '${a.personaName}': not deployed on this install \u2014 stamp left unchanged.`);
18169
- } else {
18170
- const prev = Object.hasOwn(stamp.components.personas, a.personaName) ? stamp.components.personas[a.personaName] : void 0;
18171
- stamp.components.personas[a.personaName] = r === "noop" ? { treeSha: a.to, foundryVersion: prev?.foundryVersion ?? "" } : r;
18239
+ ctx.onProgress?.(`persona '${a.personaName}': not deployed on this install \u2014 skipped (not gated).`);
18240
+ continue;
18172
18241
  }
18242
+ const prev = Object.hasOwn(stamp.components.personas, a.personaName) ? stamp.components.personas[a.personaName] : void 0;
18243
+ stamp.components.personas[a.personaName] = r === "noop" ? { treeSha: a.to, foundryVersion: prev?.foundryVersion ?? "" } : r;
18173
18244
  }
18174
18245
  applied.push(a);
18175
18246
  stamp.lastResult = "partial";
@@ -18186,6 +18257,9 @@ async function applyPlan(plan, deps, ctx) {
18186
18257
  }
18187
18258
  stamp.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
18188
18259
  await deps.writeStamp(stamp);
18260
+ if (!isTargeted && ctx.suffix && deps.writeInfraParams) {
18261
+ await deps.writeInfraParams(ctx, ctx.suffix);
18262
+ }
18189
18263
  return { stamp, applied };
18190
18264
  } catch (e) {
18191
18265
  stamp.lastResult = "failed";
@@ -18277,7 +18351,8 @@ async function resolveBicepParamsForConverge(ctx, opts = {}) {
18277
18351
  foundryProjectEndpoint: opts.foundryEndpoint ?? "",
18278
18352
  acrPullIdentityResourceId,
18279
18353
  acrResourceId,
18280
- foundryTracingMode: opts.foundryTracingMode
18354
+ foundryTracingMode: opts.foundryTracingMode,
18355
+ ...opts.assignSubscriptionRoles !== void 0 ? { assignSubscriptionRoles: opts.assignSubscriptionRoles } : {}
18281
18356
  };
18282
18357
  }
18283
18358
  function swapHostedImage(def, newImage) {
@@ -18544,6 +18619,59 @@ import { DefaultAzureCredential as DefaultAzureCredential17 } from "@azure/ident
18544
18619
 
18545
18620
  // src/lib/platform-converge-cli.ts
18546
18621
  import * as path21 from "path";
18622
+
18623
+ // src/lib/platform-infra-params.ts
18624
+ import { TableClient as TableClient2 } from "@azure/data-tables";
18625
+ async function openInfraParamsTable(opts) {
18626
+ const { tableEndpoint } = await discoverStampStorage(opts);
18627
+ return new TableClient2(tableEndpoint, "Metadata", opts.credential);
18628
+ }
18629
+ async function readInfraParams(client) {
18630
+ try {
18631
+ return entityToInfraParams(await client.getEntity(INFRA_PARAMS_PK, INFRA_PARAMS_RK));
18632
+ } catch {
18633
+ return null;
18634
+ }
18635
+ }
18636
+ async function writeInfraParams(client, p) {
18637
+ await client.upsertEntity(infraParamsToEntity(p), "Replace");
18638
+ }
18639
+ async function deriveSuffixVerified(opts) {
18640
+ const list = JSON.parse(
18641
+ await runAz([
18642
+ "containerapp",
18643
+ "list",
18644
+ "-g",
18645
+ opts.resourceGroup,
18646
+ "--subscription",
18647
+ opts.subscriptionId,
18648
+ "--query",
18649
+ "[?starts_with(name, 'm8t-gateway-')].name",
18650
+ "-o",
18651
+ "json"
18652
+ ])
18653
+ );
18654
+ if (list.length !== 1) return null;
18655
+ const suffix = list[0].replace(/^m8t-gateway-/, "");
18656
+ const cae = JSON.parse(
18657
+ await runAz([
18658
+ "containerapp",
18659
+ "env",
18660
+ "list",
18661
+ "-g",
18662
+ opts.resourceGroup,
18663
+ "--subscription",
18664
+ opts.subscriptionId,
18665
+ "--query",
18666
+ `[?name=='m8t-cae-${suffix}'].name`,
18667
+ "-o",
18668
+ "json"
18669
+ ])
18670
+ );
18671
+ return cae.length === 1 ? suffix : null;
18672
+ }
18673
+
18674
+ // src/lib/platform-converge-cli.ts
18547
18675
  init_errors();
18548
18676
  var HOSTED_AGENT_PERSONA_DIR = {
18549
18677
  codingAgent: "coding-agent",
@@ -18603,7 +18731,23 @@ async function buildConvergeDeps(args) {
18603
18731
  },
18604
18732
  async applyInfra(a, ctx) {
18605
18733
  const target = infraTargetSha(ctx.manifest, args.tree);
18606
- return applyInfraDeploy(a, ctx, target, { suffix: args.suffix });
18734
+ const infraTable = await openInfraParamsTable({ credential: args.credential, subscriptionId: args.subscriptionId, resourceGroup: args.resourceGroup });
18735
+ const stamped = (await readInfraParams(infraTable))?.bicep;
18736
+ args.onProgress?.("subscription-scoped modules skipped: updater is RG-scoped; sub-level changes require a founder-run CLI converge.");
18737
+ const opts = {
18738
+ suffix: args.suffix,
18739
+ assignSubscriptionRoles: false,
18740
+ ...stamped ? {
18741
+ tenantId: stamped.tenantId,
18742
+ clientId: stamped.clientId,
18743
+ foundryEndpoint: stamped.foundryProjectEndpoint,
18744
+ foundryResourceId: stamped.foundryResourceId,
18745
+ acrPullIdentity: stamped.acrPullIdentityResourceId,
18746
+ acrResourceId: stamped.acrResourceId,
18747
+ ...stamped.foundryTracingMode ? { foundryTracingMode: stamped.foundryTracingMode } : {}
18748
+ } : {}
18749
+ };
18750
+ return applyInfraDeploy(a, ctx, target, opts);
18607
18751
  },
18608
18752
  async healthGate(_ctx, applied) {
18609
18753
  for (const a of applied) {
@@ -18644,6 +18788,22 @@ async function buildConvergeDeps(args) {
18644
18788
  stamp: { ...s, cli: CLI_VERSION },
18645
18789
  onProgress: args.onProgress
18646
18790
  });
18791
+ },
18792
+ async writeInfraParams(ctx, _suffix) {
18793
+ const table = await openInfraParamsTable({ credential: args.credential, subscriptionId: args.subscriptionId, resourceGroup: args.resourceGroup });
18794
+ const existing = await readInfraParams(table);
18795
+ if (!existing) {
18796
+ warn("infra-params re-stamp skipped: no existing row to refresh (deploy/enable-auto-update writes the authoritative row).");
18797
+ return;
18798
+ }
18799
+ await writeInfraParams(table, {
18800
+ ...existing,
18801
+ bicep: {
18802
+ ...existing.bicep,
18803
+ imageRef: `${ctx.manifest.components.gateway.ref}:${ctx.manifest.components.gateway.tag}`
18804
+ },
18805
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
18806
+ });
18647
18807
  }
18648
18808
  };
18649
18809
  }
@@ -18799,7 +18959,7 @@ var PlatformUpdateCommand = class extends M8tCommand {
18799
18959
  onProgress,
18800
18960
  onWarn
18801
18961
  });
18802
- await applyPlan(plan, deps, { credential: credential2, subscriptionId, resourceGroup, contentDir, manifest, onProgress });
18962
+ await applyPlan(plan, deps, { credential: credential2, subscriptionId, resourceGroup, contentDir, manifest, onProgress, suffix });
18803
18963
  if (mode === "json") this.context.stdout.write(renderJson({ ...plan, applied: true }) + "\n");
18804
18964
  else log(colors.success(`converged \u2192 ${plan.targetVersion} \u2713`));
18805
18965
  return 0;
@@ -18831,8 +18991,497 @@ var PlatformUpdateCommand = class extends M8tCommand {
18831
18991
  }
18832
18992
  };
18833
18993
 
18994
+ // src/commands/platform/converge.ts
18995
+ import { Command as Command33 } from "clipanion";
18996
+
18997
+ // src/lib/platform-converge-mi.ts
18998
+ import { ManagedIdentityCredential } from "@azure/identity";
18999
+ init_errors();
19000
+ function resolveHeadlessContextFromEnv(env) {
19001
+ const need = (k) => {
19002
+ const v = (env[k] ?? "").trim();
19003
+ if (!v) throw new LocalCliError({ code: "HEADLESS_ENV_MISSING", message: `converge: required env ${k} is unset` });
19004
+ return v;
19005
+ };
19006
+ const miClientId = need("MI_CLIENT_ID");
19007
+ return {
19008
+ credential: new ManagedIdentityCredential({ clientId: miClientId }),
19009
+ miClientId,
19010
+ subscriptionId: need("SUBSCRIPTION_ID"),
19011
+ resourceGroup: need("RESOURCE_GROUP"),
19012
+ endpoint: need("FOUNDRY_ENDPOINT"),
19013
+ channelUrl: (env.M8T_UPDATE_CHANNEL_URL ?? "").trim() || CHANNEL_LATEST_URL
19014
+ };
19015
+ }
19016
+
19017
+ // src/lib/apply-request-store.ts
19018
+ import { TableClient as TableClient3 } from "@azure/data-tables";
19019
+ async function openApplyTable(opts) {
19020
+ const { tableEndpoint } = await discoverStampStorage(opts);
19021
+ return new TableClient3(tableEndpoint, "Metadata", opts.credential);
19022
+ }
19023
+ function hasStatus(e, n) {
19024
+ return e.statusCode === n;
19025
+ }
19026
+ async function readApplyRequest(client) {
19027
+ try {
19028
+ const row = await client.getEntity(APPLY_REQUEST_PK, APPLY_REQUEST_RK);
19029
+ return { ...entityToApplyRequest(row), etag: row.etag };
19030
+ } catch (e) {
19031
+ if (hasStatus(e, 404)) return null;
19032
+ throw e;
19033
+ }
19034
+ }
19035
+ async function claimApplyRequest(client, executionId, nowIso, leaseMs) {
19036
+ const cur = await readApplyRequest(client);
19037
+ if (!cur) return null;
19038
+ const leaseExpired = cur.status === "claimed" && cur.leaseUntil !== null && Date.parse(cur.leaseUntil) < Date.parse(nowIso);
19039
+ const claimable = cur.status === "pending" || cur.status === "awaiting-engine-update" || leaseExpired;
19040
+ if (!claimable) return null;
19041
+ const nowMs = Date.parse(nowIso);
19042
+ const leaseUntil = new Date((Number.isNaN(nowMs) ? Date.now() : nowMs) + leaseMs).toISOString();
19043
+ const claimed = { ...cur, status: "claimed", claimedBy: executionId, leaseUntil, updatedAt: nowIso };
19044
+ try {
19045
+ await client.updateEntity({ ...applyRequestToEntity(claimed), etag: cur.etag }, "Merge", { etag: cur.etag });
19046
+ return claimed;
19047
+ } catch (e) {
19048
+ if (hasStatus(e, 412)) return null;
19049
+ throw e;
19050
+ }
19051
+ }
19052
+ async function patchApplyRequest(client, mutate) {
19053
+ const cur = await readApplyRequest(client);
19054
+ if (!cur) return;
19055
+ const next = mutate(cur);
19056
+ await client.updateEntity({ ...applyRequestToEntity(next), etag: cur.etag }, "Merge", { etag: cur.etag });
19057
+ }
19058
+
19059
+ // src/lib/platform-self-update.ts
19060
+ import * as fs22 from "fs";
19061
+ import * as path22 from "path";
19062
+ function toVTag2(v) {
19063
+ return v.startsWith("v") ? v : `v${v}`;
19064
+ }
19065
+ function readBakedReleaseVersion(repoRoot) {
19066
+ if (!repoRoot) return null;
19067
+ try {
19068
+ const raw = fs22.readFileSync(path22.join(repoRoot, "installer", "version.json"), "utf8");
19069
+ const v = JSON.parse(raw).version;
19070
+ return typeof v === "string" && v.length > 0 ? v : null;
19071
+ } catch {
19072
+ return null;
19073
+ }
19074
+ }
19075
+ function needsEngineUpdate(manifest, runningVersion, bakedRelease) {
19076
+ if (runningVersion === "0.0.0-dev") return false;
19077
+ if (bakedRelease != null && bakedRelease.length > 0 && bakedRelease !== manifest.components.installer.version) return true;
19078
+ const running = toVTag2(runningVersion);
19079
+ const min = toVTag2(manifest.components.cli.min);
19080
+ return compareSemver(running, min) < 0;
19081
+ }
19082
+ async function updateOwnJobImage(opts) {
19083
+ const inst = opts.manifest.components.installer;
19084
+ const image = `${inst.ref}:${inst.tag}`;
19085
+ const job = opts.jobName ?? process.env.UPDATER_JOB_NAME;
19086
+ if (!job) throw new Error("UPDATER_JOB_NAME unset \u2014 cannot self-update the updater job image");
19087
+ await runAz([
19088
+ "containerapp",
19089
+ "job",
19090
+ "update",
19091
+ "-n",
19092
+ job,
19093
+ "-g",
19094
+ opts.resourceGroup,
19095
+ "--subscription",
19096
+ opts.subscriptionId,
19097
+ "--image",
19098
+ image,
19099
+ "--only-show-errors"
19100
+ ]);
19101
+ }
19102
+
19103
+ // src/lib/platform-health-gate.ts
19104
+ function nextBreaker(cur) {
19105
+ return cur === "none" ? "rolled-back" : "held";
19106
+ }
19107
+ function gateTargetsFor(applied) {
19108
+ const targets = [];
19109
+ for (const a of applied) {
19110
+ if (a.component === "gateway") {
19111
+ targets.push({ kind: "gateway-version", expected: a.to });
19112
+ } else if (a.component === "codingAgent" || a.component === "azureExecutor") {
19113
+ targets.push({ kind: "agent-queryable", component: a.component });
19114
+ } else if (a.component === "personas" && a.personaName) {
19115
+ targets.push({ kind: "agent-queryable", persona: a.personaName });
19116
+ }
19117
+ }
19118
+ return targets;
19119
+ }
19120
+ function gatePassed(probes) {
19121
+ return probes.every((p) => p.ok);
19122
+ }
19123
+ async function runHealthGateWithRollback(args) {
19124
+ const notify = args.notify ?? (async () => {
19125
+ });
19126
+ const budgetMs = args.gateBudgetMs ?? 5 * 6e4;
19127
+ const target = args.plan.targetVersion;
19128
+ let applied = [];
19129
+ try {
19130
+ const result = await applyPlan(args.plan, args.deps, args.ctx);
19131
+ applied = result.applied;
19132
+ const outcomes = await runGate(args, applied, budgetMs);
19133
+ if (!gatePassed(outcomes)) {
19134
+ const detail = outcomes.find((o) => !o.ok)?.detail ?? "a post-apply health probe failed";
19135
+ return await rollbackOrHold(args, notify, detail);
19136
+ }
19137
+ await patchApplyRequest(args.client, (r) => ({
19138
+ ...r,
19139
+ status: "success",
19140
+ breaker: r.breaker,
19141
+ // unchanged — a clean apply never trips the breaker
19142
+ result: { appliedVersion: target, error: null },
19143
+ updatedAt: args.now().toISOString()
19144
+ }));
19145
+ await notify({ outcome: "success", target });
19146
+ return "success";
19147
+ } catch (e) {
19148
+ return await rollbackOrHold(args, notify, e.message);
19149
+ }
19150
+ }
19151
+ async function runGate(args, applied, budgetMs) {
19152
+ const deadline = args.now().getTime() + budgetMs;
19153
+ const probeGateway = args.probeGatewayVersion ?? defaultGatewayProbe;
19154
+ const probeAgent = args.probeAgentQueryable ?? defaultAgentProbe;
19155
+ const outcomes = [];
19156
+ for (const t of gateTargetsFor(applied)) {
19157
+ if (args.now().getTime() > deadline) {
19158
+ outcomes.push({ ok: false, detail: "health gate exceeded its time budget" });
19159
+ break;
19160
+ }
19161
+ if (t.kind === "gateway-version") {
19162
+ if (!args.gatewayUrl) continue;
19163
+ outcomes.push(await probeGateway(args.gatewayUrl, t.expected));
19164
+ } else {
19165
+ const agentName = "persona" in t ? t.persona : hostedAgentName(t.component);
19166
+ outcomes.push(await probeAgent({ credential: args.credential, endpoint: args.endpoint, agentName }));
19167
+ }
19168
+ }
19169
+ return outcomes;
19170
+ }
19171
+ async function rollbackOrHold(args, notify, error) {
19172
+ const target = args.plan.targetVersion;
19173
+ const rollbackTarget = args.stamp.previousPlatformVersion;
19174
+ const current = await readBreaker(args.client);
19175
+ const next = nextBreaker(current);
19176
+ if (next === "held") {
19177
+ await patchApplyRequest(args.client, (r) => ({
19178
+ ...r,
19179
+ status: "held",
19180
+ breaker: "held",
19181
+ result: { appliedVersion: r.result?.appliedVersion ?? null, error },
19182
+ updatedAt: args.now().toISOString()
19183
+ }));
19184
+ await notify({ outcome: "held", target, error });
19185
+ return "held";
19186
+ }
19187
+ if (rollbackTarget) {
19188
+ await reconvergeToPrevious(args, rollbackTarget);
19189
+ }
19190
+ await patchApplyRequest(args.client, (r) => ({
19191
+ ...r,
19192
+ status: "rolled-back",
19193
+ breaker: "rolled-back",
19194
+ result: { appliedVersion: rollbackTarget, error },
19195
+ updatedAt: args.now().toISOString()
19196
+ }));
19197
+ await notify({ outcome: "rolled-back", target, error });
19198
+ return "rolled-back";
19199
+ }
19200
+ async function reconvergeToPrevious(args, previousVersion) {
19201
+ const fetchManifestImpl = args.fetchManifestImpl ?? fetchManifest;
19202
+ const resolveContentImpl = args.resolveReleaseContentImpl ?? resolveReleaseContent;
19203
+ const fetchTreeImpl = args.fetchRepoTreeImpl ?? fetchRepoTree;
19204
+ const prevManifest = await fetchManifestImpl(manifestSourceForVersionTag(args.channelUrl ?? "", previousVersion));
19205
+ const contentDir = await resolveContentImpl(prevManifest, args.repoRoot ? { contentDir: args.repoRoot } : {});
19206
+ const tree = prevManifest.components.infra?.treeSha ? { treeShaOf: () => void 0 } : await fetchTreeImpl(prevManifest.platform.commit);
19207
+ const liveStamp = await args.deps.readStamp();
19208
+ const rollbackPlan = diffPlan(prevManifest, liveStamp, tree);
19209
+ const rollbackCtx = { ...args.ctx, manifest: prevManifest, contentDir };
19210
+ await applyPlan(rollbackPlan, args.deps, rollbackCtx);
19211
+ }
19212
+ async function defaultGatewayProbe(gatewayUrl, expected) {
19213
+ try {
19214
+ const res = await fetch(`${gatewayUrl}/api/version`, { method: "GET" });
19215
+ if (res.status === 401 || res.status === 403) return { ok: true };
19216
+ if (!res.ok) return { ok: false, detail: `gateway /api/version returned HTTP ${res.status.toString()}` };
19217
+ const body = await res.json().catch(() => ({}));
19218
+ if (body.version && !versionMatches(body.version, expected)) {
19219
+ return { ok: false, detail: `gateway reports version '${body.version}', expected '${expected}'` };
19220
+ }
19221
+ return { ok: true };
19222
+ } catch (e) {
19223
+ return { ok: false, detail: `gateway unreachable: ${e.message}` };
19224
+ }
19225
+ }
19226
+ function versionMatches(running, expected) {
19227
+ const norm = (s) => s.replace(/^v/, "").trim();
19228
+ const r = norm(running);
19229
+ const e = norm(expected);
19230
+ return r === e || r.startsWith(e) || e.startsWith(r);
19231
+ }
19232
+ async function defaultAgentProbe(args) {
19233
+ try {
19234
+ await awaitAgentQueryable({ credential: args.credential, projectEndpoint: args.endpoint, agentName: args.agentName });
19235
+ return { ok: true };
19236
+ } catch (e) {
19237
+ return { ok: false, detail: `agent '${args.agentName}' not queryable: ${e.message}` };
19238
+ }
19239
+ }
19240
+ function hostedAgentName(component) {
19241
+ return component === "codingAgent" ? "coder" : "azure-executor";
19242
+ }
19243
+ async function readBreaker(client) {
19244
+ const row = await readApplyRequest(client);
19245
+ return row?.breaker ?? "none";
19246
+ }
19247
+
19248
+ // src/commands/platform/converge.ts
19249
+ init_errors();
19250
+ function canonicalVersion(v) {
19251
+ const bare = v.replace(/^platform-/, "");
19252
+ return bare.startsWith("v") ? bare : `v${bare}`;
19253
+ }
19254
+ function isForwardTarget(target, installed) {
19255
+ if (!installed) return true;
19256
+ return compareSemver(canonicalVersion(target), canonicalVersion(installed)) >= 0;
19257
+ }
19258
+ async function failIfStillInFlight(client, now, error) {
19259
+ const cur = await readApplyRequest(client);
19260
+ if (!cur || !isInFlight(cur.status)) return;
19261
+ await patchApplyRequest(client, (r) => ({
19262
+ ...r,
19263
+ status: "failed",
19264
+ result: { appliedVersion: null, error },
19265
+ updatedAt: now()
19266
+ }));
19267
+ }
19268
+ var PlatformConvergeCommand = class extends M8tCommand {
19269
+ static paths = [["platform", "converge"]];
19270
+ static usage = Command33.Usage({
19271
+ category: "Platform",
19272
+ description: "Headless updater-job driver: claim a pending apply-request and converge the platform to it.",
19273
+ details: "The Managed-Identity-authenticated entry point run by the updater Container Apps job. One invocation is one tick: it claims the pending apply-request row (if any), refuses a downgrade against the installed stamp, self-fetches and validates the target manifest, self-updates its own engine image when the manifest requires a newer CLI, then drives the F02 converge engine behind the T15 health-gate + auto-rollback. Reads its subscription/RG/endpoint/channel from env (MI_CLIENT_ID, SUBSCRIPTION_ID, RESOURCE_GROUP, FOUNDRY_ENDPOINT, M8T_UPDATE_CHANNEL_URL). Not intended for interactive use \u2014 the founder-facing path is 'm8t platform update'."
19274
+ });
19275
+ async executeCommand() {
19276
+ const ctx = resolveHeadlessContextFromEnv(process.env);
19277
+ const executionId = process.env.CONTAINER_APP_JOB_EXECUTION_NAME ?? `exec-${process.pid.toString()}`;
19278
+ const now = () => (/* @__PURE__ */ new Date()).toISOString();
19279
+ const log = (m) => {
19280
+ this.context.stdout.write(m + "\n");
19281
+ };
19282
+ const client = await openApplyTable({
19283
+ credential: ctx.credential,
19284
+ subscriptionId: ctx.subscriptionId,
19285
+ resourceGroup: ctx.resourceGroup
19286
+ });
19287
+ const claimed = await claimApplyRequest(client, executionId, now(), 30 * 60 * 1e3);
19288
+ if (!claimed) {
19289
+ log("no pending apply-request \u2014 nothing to converge.");
19290
+ return 0;
19291
+ }
19292
+ log(`claimed apply-request \u2192 target ${claimed.target} (execution ${executionId}).`);
19293
+ const stamp = await readStamp({
19294
+ credential: ctx.credential,
19295
+ subscriptionId: ctx.subscriptionId,
19296
+ resourceGroup: ctx.resourceGroup
19297
+ });
19298
+ const installed = stamp ? stamp.platformVersion : null;
19299
+ if (!isForwardTarget(claimed.target, installed)) {
19300
+ await patchApplyRequest(client, (r) => ({
19301
+ ...r,
19302
+ status: "failed",
19303
+ result: { appliedVersion: null, error: `refused downgrade ${installed ?? "(none)"} \u2192 ${claimed.target}` },
19304
+ updatedAt: now()
19305
+ }));
19306
+ log(`refused: target ${claimed.target} is a downgrade from installed ${installed ?? "(none)"}.`);
19307
+ return 1;
19308
+ }
19309
+ const manifest = await fetchManifest(targetManifestSource(ctx, claimed.target));
19310
+ if (manifest.platform.version !== claimed.target) {
19311
+ await patchApplyRequest(client, (r) => ({
19312
+ ...r,
19313
+ status: "failed",
19314
+ result: { appliedVersion: null, error: `channel manifest is version ${manifest.platform.version}, expected the claimed target ${claimed.target}` },
19315
+ updatedAt: now()
19316
+ }));
19317
+ log(`refused: fetched manifest version ${manifest.platform.version} \u2260 claimed target ${claimed.target}.`);
19318
+ return 1;
19319
+ }
19320
+ const repoRoot = tryResolveRepoRoot();
19321
+ const bakedRelease = readBakedReleaseVersion(repoRoot);
19322
+ if (needsEngineUpdate(manifest, CLI_VERSION, bakedRelease)) {
19323
+ log(
19324
+ `self-updating the updater image (baked release ${bakedRelease ?? "(none)"} / engine ${CLI_VERSION} \u2192 target installer ${manifest.components.installer.version}, cli.min ${manifest.components.cli.min}).`
19325
+ );
19326
+ await updateOwnJobImage({
19327
+ subscriptionId: ctx.subscriptionId,
19328
+ resourceGroup: ctx.resourceGroup,
19329
+ manifest
19330
+ });
19331
+ await patchApplyRequest(client, (r) => ({
19332
+ ...r,
19333
+ status: "awaiting-engine-update",
19334
+ phase: "updating the updater engine image",
19335
+ updatedAt: now()
19336
+ }));
19337
+ log("updater image bumped \u2014 awaiting-engine-update. The next tick re-claims with the new engine.");
19338
+ return 0;
19339
+ }
19340
+ assertCliVersionOk(manifest, CLI_VERSION, (w) => this.context.stderr.write(w + "\n"));
19341
+ await patchApplyRequest(client, (r) => ({ ...r, status: "applying", phase: "starting", updatedAt: now() }));
19342
+ let plan;
19343
+ let applyCtx;
19344
+ let deps;
19345
+ try {
19346
+ const project = await resolveFoundryProject({
19347
+ credential: ctx.credential,
19348
+ subscriptionId: ctx.subscriptionId,
19349
+ interactive: false,
19350
+ endpoint: ctx.endpoint
19351
+ });
19352
+ const contentDir = await resolveReleaseContent(manifest, repoRoot ? { contentDir: repoRoot } : {});
19353
+ const tree = manifest.components.infra?.treeSha ? { treeShaOf: () => void 0 } : await fetchRepoTree(manifest.platform.commit);
19354
+ const suffix = await this.recoverSuffix(ctx);
19355
+ const gatewayResourceId = await this.resolveGatewayResourceId(ctx, suffix);
19356
+ deps = await buildConvergeDeps({
19357
+ credential: ctx.credential,
19358
+ subscriptionId: ctx.subscriptionId,
19359
+ resourceGroup: ctx.resourceGroup,
19360
+ contentDir,
19361
+ manifest,
19362
+ tree,
19363
+ project,
19364
+ gatewayResourceId,
19365
+ suffix,
19366
+ onProgress: (m) => {
19367
+ void patchApplyRequest(client, (r) => ({ ...r, phase: m, updatedAt: now() })).catch(() => void 0);
19368
+ log(m);
19369
+ },
19370
+ onWarn: (m) => this.context.stderr.write(m + "\n")
19371
+ });
19372
+ plan = diffPlan(manifest, stamp, tree, { forceInfra: true });
19373
+ applyCtx = {
19374
+ credential: ctx.credential,
19375
+ subscriptionId: ctx.subscriptionId,
19376
+ resourceGroup: ctx.resourceGroup,
19377
+ contentDir,
19378
+ manifest,
19379
+ suffix,
19380
+ onProgress: (m) => {
19381
+ log(m);
19382
+ }
19383
+ };
19384
+ } catch (e) {
19385
+ await failIfStillInFlight(client, now, e.message);
19386
+ throw e;
19387
+ }
19388
+ const outcome = await runHealthGateWithRollback({
19389
+ client,
19390
+ deps,
19391
+ plan,
19392
+ ctx: applyCtx,
19393
+ stamp: stamp ?? seedStampFor(plan),
19394
+ manifest,
19395
+ credential: ctx.credential,
19396
+ endpoint: ctx.endpoint,
19397
+ // The rollback re-converge needs the SAME zero-repo-auth inputs as the
19398
+ // forward path: baked content (repoRoot) + the channel to resolve the
19399
+ // PREVIOUS version's manifest (never GitHub repo auth). Without these the
19400
+ // rollback would 404 on the private pre-OSS repo just like the forward
19401
+ // path did before this fix.
19402
+ channelUrl: ctx.channelUrl,
19403
+ repoRoot,
19404
+ now: () => /* @__PURE__ */ new Date()
19405
+ });
19406
+ log(`converge outcome: ${outcome}.`);
19407
+ return outcome === "success" ? 0 : 1;
19408
+ }
19409
+ /**
19410
+ * Recover the real bicep suffix WITHOUT guessing: prefer the stamped
19411
+ * `system/infra-params` row (written by deploy + every full converge), and
19412
+ * fall back to deriving it from the live gateway container-app name — but only
19413
+ * when that derivation VERIFIES (exactly one gateway + a matching CAE). If
19414
+ * both fail, throw rather than provision a duplicate stack.
19415
+ */
19416
+ async recoverSuffix(ctx) {
19417
+ const infraTable = await openInfraParamsTable({
19418
+ credential: ctx.credential,
19419
+ subscriptionId: ctx.subscriptionId,
19420
+ resourceGroup: ctx.resourceGroup
19421
+ });
19422
+ const stamped = (await readInfraParams(infraTable))?.suffix;
19423
+ const suffix = stamped ?? await deriveSuffixVerified({
19424
+ credential: ctx.credential,
19425
+ subscriptionId: ctx.subscriptionId,
19426
+ resourceGroup: ctx.resourceGroup
19427
+ });
19428
+ if (!suffix) {
19429
+ throw new LocalCliError({
19430
+ code: "PLATFORM_INFRA_SUFFIX_UNRECOVERABLE",
19431
+ message: "Could not recover the deployment's resource-name suffix: no system/infra-params row is stamped, and deriving it from the live gateway did not verify (0 or >1 gateway, or no matching CAE).",
19432
+ hint: "Run a full 'm8t platform update --force-infra --suffix <suffix>' once to stamp system/infra-params, then re-run the updater."
19433
+ });
19434
+ }
19435
+ return suffix;
19436
+ }
19437
+ /** Resolve the gateway container-app ARM resource id for the recovered suffix. */
19438
+ async resolveGatewayResourceId(ctx, suffix) {
19439
+ const id = (await runAz([
19440
+ "containerapp",
19441
+ "show",
19442
+ "-n",
19443
+ `m8t-gateway-${suffix}`,
19444
+ "-g",
19445
+ ctx.resourceGroup,
19446
+ "--subscription",
19447
+ ctx.subscriptionId,
19448
+ "--query",
19449
+ "id",
19450
+ "-o",
19451
+ "tsv"
19452
+ ])).trim();
19453
+ if (!id) {
19454
+ throw new LocalCliError({
19455
+ code: "PLATFORM_GATEWAY_NOT_FOUND",
19456
+ message: `Could not resolve the gateway container app 'm8t-gateway-${suffix}' in resource group ${ctx.resourceGroup}.`
19457
+ });
19458
+ }
19459
+ return id;
19460
+ }
19461
+ };
19462
+ function seedStampFor(plan) {
19463
+ return {
19464
+ schemaVersion: 1,
19465
+ platformVersion: plan.targetVersion,
19466
+ previousPlatformVersion: null,
19467
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
19468
+ lastResult: "success",
19469
+ cli: CLI_VERSION,
19470
+ components: {
19471
+ gateway: { tag: "", digest: "", state: "managed" },
19472
+ codingAgent: { tag: "", digest: "", state: "managed" },
19473
+ azureExecutor: { tag: "", digest: "", state: "managed" },
19474
+ personas: {},
19475
+ infra: { treeSha: "" }
19476
+ }
19477
+ };
19478
+ }
19479
+ function targetManifestSource(ctx, target) {
19480
+ return manifestSourceForVersionTag(ctx.channelUrl, target);
19481
+ }
19482
+
18834
19483
  // src/commands/platform/enable-cost-report.ts
18835
- import { Command as Command33, Option as Option31 } from "clipanion";
19484
+ import { Command as Command34, Option as Option31 } from "clipanion";
18836
19485
 
18837
19486
  // src/lib/wire-gateway-acs.ts
18838
19487
  init_rbac();
@@ -18872,7 +19521,7 @@ async function wireGatewayForAcs(args) {
18872
19521
  init_errors();
18873
19522
  var PlatformEnableCostReportCommand = class extends M8tCommand {
18874
19523
  static paths = [["platform", "enable-cost-report"]];
18875
- static usage = Command33.Usage({
19524
+ static usage = Command34.Usage({
18876
19525
  description: "Wire the deployed gateway to send the bi-weekly cost report via ACS Email.",
18877
19526
  details: "Discovers the live gateway Container App, grants its managed identity Contributor at the ACS resource scope (authorising the ACS Email send), and sets M8T_ACS_ENDPOINT / M8T_ACS_SENDER / M8T_ENABLE_COST_REPORTER=1 on the gateway. Idempotent \u2014 safely re-runnable. ACS is created during the executor deploy; pass its endpoint, sender, and resource id (from that deploy or 'az communication list')."
18878
19527
  });
@@ -18956,8 +19605,161 @@ var PlatformEnableCostReportCommand = class extends M8tCommand {
18956
19605
  }
18957
19606
  };
18958
19607
 
19608
+ // src/commands/platform/enable-auto-update.ts
19609
+ import { Command as Command35, Option as Option32 } from "clipanion";
19610
+ import { confirm as confirm6 } from "@inquirer/prompts";
19611
+ import { DefaultAzureCredential as DefaultAzureCredential18 } from "@azure/identity";
19612
+ init_errors();
19613
+ var PlatformEnableAutoUpdateCommand = class extends M8tCommand {
19614
+ static paths = [["platform", "enable-auto-update"]];
19615
+ static usage = Command35.Usage({
19616
+ category: "Platform",
19617
+ description: "Retrofit the auto-updater (MI + cron job + role assignments) onto an existing install.",
19618
+ details: "For an install that predates the Platform Update Framework: recovers the deployment's resource-name suffix (from the stamped system/infra-params row, or by verified derivation from the live gateway), recovers the other bicep params from live state (WITHOUT changing the deployed gateway image), and re-runs deploy/main.bicep with provisionUpdater=true \u2014 which provisions the updater managed identity, the cron Container Apps Job, and every role assignment (Owner@RG + Foundry User@account + Storage Table + Storage Blob + AcrPull), all scoped to the resource group. On success, backfills system/infra-params so future converges never have to re-derive the suffix. Idempotent \u2014 safe to re-run."
19619
+ });
19620
+ subscription = Option32.String("--subscription");
19621
+ resourceGroup = Option32.String("--resource-group", {
19622
+ description: "m8t-stack resource group to disambiguate the gateway (multi-deployment subscriptions)."
19623
+ });
19624
+ suffix = Option32.String("--suffix", {
19625
+ description: "The existing deployment's resource-name suffix. Required when it cannot be recovered from system/infra-params or the live gateway."
19626
+ });
19627
+ installerImage = Option32.String("--installer-image", {
19628
+ required: true,
19629
+ description: "Updater CA-Job image ref (the converge engine at the current release). Required \u2014 an empty image would skip provisioning."
19630
+ });
19631
+ updateCron = Option32.String("--update-cron", {
19632
+ description: "Cron schedule for the updater job (bicep default applies when omitted)."
19633
+ });
19634
+ channelUrl = Option32.String("--channel-url", {
19635
+ description: "Release-channel URL the updater job polls (bicep default applies when omitted)."
19636
+ });
19637
+ endpoint = Option32.String("--endpoint", {
19638
+ description: "Foundry project endpoint URL. Disambiguates the project in a multi-project subscription."
19639
+ });
19640
+ yes = Option32.Boolean("--yes", false);
19641
+ output = Option32.String("--output");
19642
+ async executeCommand() {
19643
+ const mode = resolveOutputMode(
19644
+ this.output,
19645
+ this.context.stdout
19646
+ );
19647
+ const interactive = this.context.stdout.isTTY === true;
19648
+ const log = (msg) => {
19649
+ if (mode !== "json") this.context.stdout.write(msg + "\n");
19650
+ };
19651
+ const onProgress = (m) => {
19652
+ log(colors.dim(m));
19653
+ };
19654
+ const gw = await resolveGatewayContext({
19655
+ interactive: mode !== "json",
19656
+ subscriptionId: this.subscription,
19657
+ resourceGroup: this.resourceGroup
19658
+ });
19659
+ const { resourceGroup, name: gatewayName } = parseContainerAppResourceId(gw.containerAppResourceId);
19660
+ const credential2 = new DefaultAzureCredential18();
19661
+ const account = await getAzAccount();
19662
+ const subscriptionId = this.subscription ?? account.subscriptionId;
19663
+ const explicitSuffix = typeof this.suffix === "string" && this.suffix.length > 0 ? this.suffix : void 0;
19664
+ onProgress("recovering the deployment's resource-name suffix\u2026");
19665
+ const infraTable = await openInfraParamsTable({ credential: credential2, subscriptionId, resourceGroup });
19666
+ const stampedParams = await readInfraParams(infraTable);
19667
+ const derivedSuffix = explicitSuffix ? void 0 : stampedParams?.suffix ?? await deriveSuffixVerified({ credential: credential2, subscriptionId, resourceGroup }) ?? void 0;
19668
+ const suffix = explicitSuffix ?? derivedSuffix;
19669
+ if (!suffix) {
19670
+ throw new LocalCliError({
19671
+ code: "PLATFORM_ENABLE_AUTO_UPDATE_SUFFIX_UNRECOVERABLE",
19672
+ message: "Could not recover the deployment's resource-name suffix: no --suffix was given, no system/infra-params row is stamped, and deriving it from the live gateway did not verify (0 or >1 gateway, or no matching CAE).",
19673
+ hint: "Pass --suffix <existing-suffix> explicitly (from the deployed resource names, e.g. m8t-gateway-<suffix>)."
19674
+ });
19675
+ }
19676
+ if (!explicitSuffix && !stampedParams && !this.yes && interactive) {
19677
+ const ok = await confirm6({
19678
+ message: `Derived suffix '${suffix}' from the live gateway \u2014 proceed?`,
19679
+ default: true
19680
+ });
19681
+ if (!ok) {
19682
+ log(colors.dim("aborted \u2014 no change. Re-run with --suffix <suffix> to specify it explicitly."));
19683
+ return 0;
19684
+ }
19685
+ }
19686
+ onProgress("resolving Foundry project\u2026");
19687
+ const project = await resolveFoundryProject({
19688
+ credential: credential2,
19689
+ subscriptionId,
19690
+ interactive,
19691
+ endpoint: typeof this.endpoint === "string" ? this.endpoint : void 0
19692
+ });
19693
+ onProgress("reading the live gateway image (left unchanged)\u2026");
19694
+ const imageRef = (await runAz(["containerapp", "show", "-n", gatewayName, "-g", resourceGroup, "--query", "properties.template.containers[0].image", "-o", "tsv"])).trim();
19695
+ if (!imageRef) {
19696
+ throw new LocalCliError({
19697
+ code: "PLATFORM_ENABLE_AUTO_UPDATE_NO_IMAGE",
19698
+ message: `Could not read the live image for gateway '${gatewayName}' in resource group ${resourceGroup}.`
19699
+ });
19700
+ }
19701
+ const location = (await runAz(["resource", "list", "-g", resourceGroup, "--subscription", subscriptionId, "--query", "[0].location", "-o", "tsv"])).trim();
19702
+ if (!location) {
19703
+ throw new LocalCliError({
19704
+ code: "PLATFORM_ENABLE_AUTO_UPDATE_NO_LOCATION",
19705
+ message: `Could not resolve a location for resource group ${resourceGroup} \u2014 it may be empty.`
19706
+ });
19707
+ }
19708
+ const foundryResourceId = await resolveFoundryResourceId(project.endpoint);
19709
+ const acrPullIdentityResourceId = resolveAcrPullIdentity({});
19710
+ const acrResourceId = acrPullIdentityResourceId ? "" : await resolveAcrResourceId({ imageRef });
19711
+ const recoveredParams = {
19712
+ location,
19713
+ suffix,
19714
+ imageRef,
19715
+ tenantId: gw.gatewayTenantId,
19716
+ clientId: gw.gatewayClientId,
19717
+ foundryResourceId,
19718
+ foundryProjectEndpoint: project.endpoint,
19719
+ acrPullIdentityResourceId,
19720
+ acrResourceId
19721
+ };
19722
+ onProgress("running Bicep deployment to provision the updater (~2-4 min)\u2026");
19723
+ const repoRoot = await resolveRepoRoot2();
19724
+ await runBicepDeployment({
19725
+ resourceGroup,
19726
+ repoRoot,
19727
+ params: buildBicepParams({
19728
+ ...recoveredParams,
19729
+ provisionUpdater: true,
19730
+ installerImage: this.installerImage,
19731
+ ...typeof this.updateCron === "string" ? { updateCron: this.updateCron } : {},
19732
+ ...typeof this.channelUrl === "string" ? { channelUrl: this.channelUrl } : {}
19733
+ }),
19734
+ deploymentName: `m8t-enable-auto-update-${suffix.slice(0, 12)}`
19735
+ });
19736
+ onProgress("backfilling system/infra-params\u2026");
19737
+ await writeInfraParams(infraTable, {
19738
+ schemaVersion: 1,
19739
+ suffix,
19740
+ bicep: recoveredParams,
19741
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
19742
+ });
19743
+ if (mode === "json") {
19744
+ this.context.stdout.write(renderJson({ suffix, resourceGroup, installerImage: this.installerImage, enabled: true }) + "\n");
19745
+ return 0;
19746
+ }
19747
+ this.context.stdout.write(
19748
+ renderKeyValueBlock([
19749
+ { key: "suffix", value: suffix },
19750
+ { key: "resource group", value: resourceGroup },
19751
+ { key: "installer image", value: this.installerImage },
19752
+ { key: "infra-params", value: "backfilled" }
19753
+ ]) + "\n"
19754
+ );
19755
+ log(colors.success("auto-update enabled \u2014 the updater job will start converging on its next cron tick \u2713"));
19756
+ log(colors.dim("next: 'm8t platform status' to check drift, or 'm8t platform update' to converge interactively at any time."));
19757
+ return 0;
19758
+ }
19759
+ };
19760
+
18959
19761
  // src/commands/deploy.ts
18960
- import { Command as Command34, Option as Option32 } from "clipanion";
19762
+ import { Command as Command36, Option as Option33 } from "clipanion";
18961
19763
 
18962
19764
  // src/lib/app-reg.ts
18963
19765
  init_errors();
@@ -19218,7 +20020,7 @@ async function patchRedirectUris(appObjectId, fqdn) {
19218
20020
  init_errors();
19219
20021
 
19220
20022
  // src/lib/whatif.ts
19221
- import * as path22 from "path";
20023
+ import * as path23 from "path";
19222
20024
  function deriveResourceType(resourceId) {
19223
20025
  if (resourceId.startsWith("[")) return "";
19224
20026
  const afterProviders = resourceId.split("/providers/")[1];
@@ -19229,7 +20031,7 @@ function deriveResourceType(resourceId) {
19229
20031
  return parts.join("/");
19230
20032
  }
19231
20033
  async function runWhatIf(opts) {
19232
- const bicepPath = path22.join(opts.repoRoot, "deploy", "main.bicep");
20034
+ const bicepPath = path23.join(opts.repoRoot, "deploy", "main.bicep");
19233
20035
  const out = await runAz([
19234
20036
  "deployment",
19235
20037
  "group",
@@ -19294,9 +20096,9 @@ function flattenDelta(delta, prefix = "") {
19294
20096
  const out = [];
19295
20097
  for (const d of delta) {
19296
20098
  const segment = /^[0-9]+$/.test(d.path) ? `[${d.path}]` : prefix ? `.${d.path}` : d.path;
19297
- const path32 = prefix ? `${prefix}${segment}` : d.path;
19298
- if (d.children && d.children.length > 0) out.push(...flattenDelta(d.children, path32));
19299
- else out.push({ ...d, path: path32 });
20099
+ const path33 = prefix ? `${prefix}${segment}` : d.path;
20100
+ if (d.children && d.children.length > 0) out.push(...flattenDelta(d.children, path33));
20101
+ else out.push({ ...d, path: path33 });
19300
20102
  }
19301
20103
  return out;
19302
20104
  }
@@ -19346,24 +20148,40 @@ function classifyWhatIf(changes) {
19346
20148
  var DEFAULT_IMAGE_REF = "ghcr.io/m8t-run/m8t:latest";
19347
20149
  var DeployCommand = class extends M8tCommand {
19348
20150
  static paths = [["deploy"]];
19349
- static usage = Command34.Usage({
20151
+ static usage = Command36.Usage({
19350
20152
  description: "Deploy (or update) the m8t gateway/webapp stack via Bicep.",
19351
20153
  details: "Ensures the Entra app reg (or pass --client-id to reuse an existing one \u2014 required if you can't create app regs), writes ~/.m8t-stack/config.yaml, ensures the resource group, and runs deploy/main.bicep. The repo is located via ~/.m8t-stack/repo-root."
19352
20154
  });
19353
- subscription = Option32.String("--subscription");
19354
- resourceGroup = Option32.String("--resource-group", "rg-m8t-stack");
19355
- location = Option32.String("--location", "eastus");
19356
- suffix = Option32.String("--suffix", "");
19357
- imageRef = Option32.String("--image-ref", DEFAULT_IMAGE_REF);
19358
- acrPullIdentity = Option32.String("--acrpull-identity");
19359
- acrResourceId = Option32.String("--acr-resource-id");
19360
- foundryEndpoint = Option32.String("--foundry-endpoint");
19361
- foundryResourceId = Option32.String("--foundry-resource-id");
19362
- foundryTracing = Option32.String("--foundry-tracing");
20155
+ subscription = Option33.String("--subscription");
20156
+ resourceGroup = Option33.String("--resource-group", "rg-m8t-stack");
20157
+ location = Option33.String("--location", "eastus");
20158
+ suffix = Option33.String("--suffix", "");
20159
+ imageRef = Option33.String("--image-ref", DEFAULT_IMAGE_REF);
20160
+ acrPullIdentity = Option33.String("--acrpull-identity");
20161
+ acrResourceId = Option33.String("--acr-resource-id");
20162
+ foundryEndpoint = Option33.String("--foundry-endpoint");
20163
+ foundryResourceId = Option33.String("--foundry-resource-id");
20164
+ foundryTracing = Option33.String("--foundry-tracing");
19363
20165
  // project | account | skip (bicep default: project)
19364
- clientId = Option32.String("--client-id");
19365
- whatIf = Option32.Boolean("--what-if", false);
19366
- output = Option32.String("--output");
20166
+ clientId = Option33.String("--client-id");
20167
+ whatIf = Option33.Boolean("--what-if", false);
20168
+ output = Option33.String("--output");
20169
+ // Referee (E2-F5) — all optional, undefined by default ⇒ the bicep defaults
20170
+ // apply (dormant: gatewayCpu=0.25, gatewayMemory=0.5Gi, every referee/exam var
20171
+ // empty). Only pass these when explicitly enabling the referee exam stack.
20172
+ gatewayCpu = Option33.String("--gateway-cpu");
20173
+ gatewayMemory = Option33.String("--gateway-memory");
20174
+ refereeEnabled = Option33.String("--referee-enabled");
20175
+ refereeBrainRepos = Option33.String("--referee-brain-repos");
20176
+ refereeFeedRepo = Option33.String("--referee-feed-repo");
20177
+ refereeInstallationId = Option33.String("--referee-installation-id");
20178
+ refereeWebhookHmacKvUri = Option33.String("--referee-webhook-hmac-kv-uri");
20179
+ examKvUri = Option33.String("--exam-kv-uri");
20180
+ examLaWorkspaceId = Option33.String("--exam-la-workspace-id");
20181
+ brainEvalDeployment = Option33.String("--brain-eval-deployment");
20182
+ brainAppLogin = Option33.String("--brain-app-login");
20183
+ refereeCheckpointDir = Option33.String("--referee-checkpoint-dir");
20184
+ examApiBase = Option33.String("--exam-api-base");
19367
20185
  async executeCommand() {
19368
20186
  const mode = resolveOutputMode(
19369
20187
  this.output,
@@ -19407,7 +20225,20 @@ var DeployCommand = class extends M8tCommand {
19407
20225
  foundryProjectEndpoint: foundryEndpoint,
19408
20226
  acrPullIdentityResourceId: acrPullIdentityResourceId2,
19409
20227
  acrResourceId: acrResourceId2,
19410
- foundryTracingMode: parseFoundryTracingMode(this.foundryTracing)
20228
+ foundryTracingMode: parseFoundryTracingMode(this.foundryTracing),
20229
+ gatewayCpu: this.gatewayCpu,
20230
+ gatewayMemory: this.gatewayMemory,
20231
+ refereeEnabled: this.refereeEnabled,
20232
+ refereeBrainRepos: this.refereeBrainRepos,
20233
+ refereeFeedRepo: this.refereeFeedRepo,
20234
+ refereeInstallationId: this.refereeInstallationId,
20235
+ refereeWebhookHmacKvUri: this.refereeWebhookHmacKvUri,
20236
+ examKvUri: this.examKvUri,
20237
+ examLaWorkspaceId: this.examLaWorkspaceId,
20238
+ brainEvalDeployment: this.brainEvalDeployment,
20239
+ brainAppLogin: this.brainAppLogin,
20240
+ refereeCheckpointDir: this.refereeCheckpointDir,
20241
+ examApiBase: this.examApiBase
19411
20242
  });
19412
20243
  const changes = await runWhatIf({ resourceGroup: this.resourceGroup, repoRoot: repoRoot2, params: params2 });
19413
20244
  const classified = classifyWhatIf(changes);
@@ -19442,7 +20273,20 @@ var DeployCommand = class extends M8tCommand {
19442
20273
  foundryProjectEndpoint: foundryEndpoint,
19443
20274
  acrPullIdentityResourceId,
19444
20275
  acrResourceId,
19445
- foundryTracingMode: parseFoundryTracingMode(this.foundryTracing)
20276
+ foundryTracingMode: parseFoundryTracingMode(this.foundryTracing),
20277
+ gatewayCpu: this.gatewayCpu,
20278
+ gatewayMemory: this.gatewayMemory,
20279
+ refereeEnabled: this.refereeEnabled,
20280
+ refereeBrainRepos: this.refereeBrainRepos,
20281
+ refereeFeedRepo: this.refereeFeedRepo,
20282
+ refereeInstallationId: this.refereeInstallationId,
20283
+ refereeWebhookHmacKvUri: this.refereeWebhookHmacKvUri,
20284
+ examKvUri: this.examKvUri,
20285
+ examLaWorkspaceId: this.examLaWorkspaceId,
20286
+ brainEvalDeployment: this.brainEvalDeployment,
20287
+ brainAppLogin: this.brainAppLogin,
20288
+ refereeCheckpointDir: this.refereeCheckpointDir,
20289
+ examApiBase: this.examApiBase
19446
20290
  });
19447
20291
  log("running Bicep deployment (~3-5 min)\u2026");
19448
20292
  const outputs = await runBicepDeployment({
@@ -19477,7 +20321,7 @@ var DeployCommand = class extends M8tCommand {
19477
20321
 
19478
20322
  // src/commands/eval/skill.ts
19479
20323
  import { spawnSync as spawnSync3 } from "child_process";
19480
- import { Command as Command35, Option as Option33 } from "clipanion";
20324
+ import { Command as Command37, Option as Option34 } from "clipanion";
19481
20325
  init_errors();
19482
20326
  var DECISIONS = /* @__PURE__ */ new Set(["promote", "reject", "needs_review"]);
19483
20327
  var JUDGE_STATUSES = /* @__PURE__ */ new Set(["ok", "skipped", "unavailable"]);
@@ -19502,14 +20346,14 @@ function parseVerdict(stdout) {
19502
20346
  }
19503
20347
  var EvalSkillCommand = class extends M8tCommand {
19504
20348
  static paths = [["eval", "skill"]];
19505
- static usage = Command35.Usage({
20349
+ static usage = Command37.Usage({
19506
20350
  description: "Vet one inbox skill candidate (Brain Faculties Eval F1): promote / reject / needs_review. Shells out to the Python `brain-eval` core (override its path with $BRAIN_EVAL_BIN)."
19507
20351
  });
19508
- candidate = Option33.String();
19509
- skillsDir = Option33.String("--skills-dir");
19510
- noJudge = Option33.Boolean("--no-judge", false);
19511
- deployment = Option33.String("--deployment");
19512
- output = Option33.String("--output");
20352
+ candidate = Option34.String();
20353
+ skillsDir = Option34.String("--skills-dir");
20354
+ noJudge = Option34.Boolean("--no-judge", false);
20355
+ deployment = Option34.String("--deployment");
20356
+ output = Option34.String("--output");
19513
20357
  executeCommand() {
19514
20358
  return Promise.resolve(this._runCommand());
19515
20359
  }
@@ -19566,9 +20410,9 @@ var EvalSkillCommand = class extends M8tCommand {
19566
20410
 
19567
20411
  // src/commands/eval/exam.ts
19568
20412
  import { spawnSync as spawnSync4 } from "child_process";
19569
- import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync15, existsSync as existsSync13, readdirSync } from "fs";
19570
- import { join as join22 } from "path";
19571
- import { Command as Command36, Option as Option34 } from "clipanion";
20413
+ import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync16, existsSync as existsSync13, readdirSync } from "fs";
20414
+ import { join as join23 } from "path";
20415
+ import { Command as Command38, Option as Option35 } from "clipanion";
19572
20416
  init_errors();
19573
20417
  init_esm();
19574
20418
  function parseArmToken(tok, opts) {
@@ -19656,7 +20500,7 @@ function parseExamVerdict(stdout) {
19656
20500
  }
19657
20501
  return v;
19658
20502
  }
19659
- var DEFAULT_REPS = { impact: 3, dream_delta: 5 };
20503
+ var DEFAULT_REPS = { impact: 3, dream_delta: 5, skill_abstraction: 5 };
19660
20504
  function inferExamType(arms, explicit) {
19661
20505
  if (explicit !== void 0) {
19662
20506
  if (!EXAM_TYPES.has(explicit)) {
@@ -19677,6 +20521,22 @@ function inferExamType(arms, explicit) {
19677
20521
  message: "could not infer --exam-type from arms \u2014 pass --exam-type impact|dream_delta|tournament"
19678
20522
  });
19679
20523
  }
20524
+ function assertAbstractionArmsExplicit(examType, arms, skill) {
20525
+ if (examType !== "skill_abstraction") return;
20526
+ if (typeof skill !== "string" || skill.length === 0) {
20527
+ throw new LocalCliError({
20528
+ code: "USAGE",
20529
+ message: "--exam-type skill_abstraction requires --skill <slug> (the promoted skill's slug)"
20530
+ });
20531
+ }
20532
+ const bothPinned = arms.length === 2 && arms.every((a) => typeof a.state === "object" && "pinned" in a.state);
20533
+ if (!bothPinned) {
20534
+ throw new LocalCliError({
20535
+ code: "USAGE",
20536
+ message: "--exam-type skill_abstraction requires exactly 2 explicit pinned arms (the promotion event's base/merge shas), e.g. --arms 'pinned:<base>+pinned:<merge>'"
20537
+ });
20538
+ }
20539
+ }
19680
20540
  function resolveJudgeDeployment(flag, env) {
19681
20541
  if (typeof flag === "string" && flag.length > 0) return { deployment: flag };
19682
20542
  const fromEnv = env.EXAM_JUDGE_DEPLOYMENT;
@@ -19733,9 +20593,9 @@ function resolveRefereeHome(refereeHomeOut, env) {
19733
20593
  return null;
19734
20594
  }
19735
20595
  function resolveTaskSetDir(base, worker, version) {
19736
- if (existsSync13(join22(base, version))) return version;
20596
+ if (existsSync13(join23(base, version))) return version;
19737
20597
  const prefixed = `${worker}-${version}`;
19738
- if (existsSync13(join22(base, prefixed))) return prefixed;
20598
+ if (existsSync13(join23(base, prefixed))) return prefixed;
19739
20599
  if (version === "latest" && existsSync13(base)) {
19740
20600
  const dirs = readdirSync(base, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.startsWith(`${worker}-`)).map((d) => d.name).sort();
19741
20601
  if (dirs.length > 0) return dirs[dirs.length - 1];
@@ -19751,11 +20611,11 @@ function readSealedTaskIds(taskSetVersion, refereeHomeOut, env = process.env) {
19751
20611
  message: "cannot resolve the referee home to read the sealed-task manifest \u2014 set $REFEREE_HOME (mirroring the Python runner) so the feed can be redacted; refusing to emit a possibly-unredacted feed (Law-1)"
19752
20612
  });
19753
20613
  }
19754
- const versionDir = resolveTaskSetDir(join22(home, "tasksets", worker), worker, version);
19755
- const manifestPath = join22(home, "tasksets", worker, versionDir, "manifest.yaml");
20614
+ const versionDir = resolveTaskSetDir(join23(home, "tasksets", worker), worker, version);
20615
+ const manifestPath = join23(home, "tasksets", worker, versionDir, "manifest.yaml");
19756
20616
  let text;
19757
20617
  try {
19758
- text = readFileSync15(manifestPath, "utf-8");
20618
+ text = readFileSync16(manifestPath, "utf-8");
19759
20619
  } catch (e) {
19760
20620
  throw new LocalCliError({
19761
20621
  code: "EXAM_REDACTION_UNSAFE",
@@ -19782,23 +20642,24 @@ function buildPlan(args) {
19782
20642
  }
19783
20643
  var EvalExamCommand = class extends M8tCommand {
19784
20644
  static paths = [["eval", "exam"]];
19785
- static usage = Command36.Usage({
20645
+ static usage = Command38.Usage({
19786
20646
  description: "Run a brain exam (Brain Referee E2-F2): impact A/B + dream-delta. Resolves the plan, then shells out to the Python `brain-exam` orchestrator (override its path with $BRAIN_EXAM_BIN). Renders an ExamVerdict: three-valued verdict + power note + per-task flips."
19787
20647
  });
19788
- worker = Option34.String();
19789
- arms = Option34.String("--arms");
19790
- taskSet = Option34.String("--task-set");
19791
- examType = Option34.String("--exam-type");
19792
- reps = Option34.String("-n,--reps");
19793
- probes = Option34.String("--probes");
19794
- pool = Option34.String("--pool");
19795
- out = Option34.String("--out");
19796
- dryRun = Option34.Boolean("--dry-run", false);
19797
- keepArms = Option34.Boolean("--keep-arms", false);
19798
- allowStub = Option34.Boolean("--allow-stub", false);
19799
- deployment = Option34.String("--deployment");
19800
- output = Option34.String("--output");
19801
- observeWaitS = Option34.String("--observe-wait-s");
20648
+ worker = Option35.String();
20649
+ arms = Option35.String("--arms");
20650
+ taskSet = Option35.String("--task-set");
20651
+ examType = Option35.String("--exam-type");
20652
+ skill = Option35.String("--skill");
20653
+ reps = Option35.String("-n,--reps");
20654
+ probes = Option35.String("--probes");
20655
+ pool = Option35.String("--pool");
20656
+ out = Option35.String("--out");
20657
+ dryRun = Option35.Boolean("--dry-run", false);
20658
+ keepArms = Option35.Boolean("--keep-arms", false);
20659
+ allowStub = Option35.Boolean("--allow-stub", false);
20660
+ deployment = Option35.String("--deployment");
20661
+ output = Option35.String("--output");
20662
+ observeWaitS = Option35.String("--observe-wait-s");
19802
20663
  async executeCommand() {
19803
20664
  await Promise.resolve();
19804
20665
  const worker = typeof this.worker === "string" ? this.worker : void 0;
@@ -19807,6 +20668,7 @@ var EvalExamCommand = class extends M8tCommand {
19807
20668
  if (!armsRaw) throw new LocalCliError({ code: "USAGE", message: "--arms <spec> is required" });
19808
20669
  const arms = parseArmSpec(armsRaw, "git", { allowStub: this.allowStub === true });
19809
20670
  const examType = inferExamType(arms, typeof this.examType === "string" ? this.examType : void 0);
20671
+ assertAbstractionArmsExplicit(examType, arms, typeof this.skill === "string" ? this.skill : void 0);
19810
20672
  let reps = DEFAULT_REPS[examType] ?? 3;
19811
20673
  if (typeof this.reps === "string") {
19812
20674
  const parsed = Number.parseInt(this.reps, 10);
@@ -19874,9 +20736,9 @@ var EvalExamCommand = class extends M8tCommand {
19874
20736
  if (out !== void 0) {
19875
20737
  const sealedTaskIds = taskSetVersion ? readSealedTaskIds(taskSetVersion, out) : /* @__PURE__ */ new Set();
19876
20738
  mkdirSync4(out, { recursive: true });
19877
- writeFileSync4(join22(out, "verdict.json"), JSON.stringify(verdict, null, 2));
20739
+ writeFileSync4(join23(out, "verdict.json"), JSON.stringify(verdict, null, 2));
19878
20740
  const feed = redactForFeed(verdict, sealedTaskIds);
19879
- writeFileSync4(join22(out, "feed.json"), JSON.stringify(feed, null, 2));
20741
+ writeFileSync4(join23(out, "feed.json"), JSON.stringify(feed, null, 2));
19880
20742
  }
19881
20743
  if (mode === "json") {
19882
20744
  this.context.stdout.write(renderJson(verdict) + "\n");
@@ -19912,10 +20774,10 @@ var EvalExamCommand = class extends M8tCommand {
19912
20774
  };
19913
20775
 
19914
20776
  // src/commands/version.ts
19915
- import { Command as Command37, Option as Option35 } from "clipanion";
20777
+ import { Command as Command39, Option as Option36 } from "clipanion";
19916
20778
  var VersionCommand = class extends M8tCommand {
19917
20779
  static paths = [["version"], ["--version"], ["-v"]];
19918
- static usage = Command37.Usage({
20780
+ static usage = Command39.Usage({
19919
20781
  description: "Print the CLI version.",
19920
20782
  details: "Prints the m8t CLI version. With --verbose, also prints Node version and platform.",
19921
20783
  examples: [
@@ -19923,8 +20785,8 @@ var VersionCommand = class extends M8tCommand {
19923
20785
  ["Print as JSON", "$0 version --output json"]
19924
20786
  ]
19925
20787
  });
19926
- output = Option35.String("--output", { description: "pretty | json | auto (default)" });
19927
- verbose = Option35.Boolean("--verbose", false);
20788
+ output = Option36.String("--output", { description: "pretty | json | auto (default)" });
20789
+ verbose = Option36.Boolean("--verbose", false);
19928
20790
  executeCommand() {
19929
20791
  const mode = resolveOutputMode(
19930
20792
  this.output ?? "auto",
@@ -19955,18 +20817,18 @@ var VersionCommand = class extends M8tCommand {
19955
20817
  };
19956
20818
 
19957
20819
  // src/commands/whoami.ts
19958
- import { Command as Command38, Option as Option36 } from "clipanion";
20820
+ import { Command as Command40, Option as Option37 } from "clipanion";
19959
20821
  var WhoamiCommand = class extends M8tCommand {
19960
20822
  static paths = [["whoami"]];
19961
- static usage = Command38.Usage({
20823
+ static usage = Command40.Usage({
19962
20824
  description: "Show your identity + the gateway you'll talk to. Probes the backend."
19963
20825
  });
19964
- output = Option36.String("--output");
19965
- verbose = Option36.Boolean("--verbose", false);
19966
- subscription = Option36.String("--subscription", {
20826
+ output = Option37.String("--output");
20827
+ verbose = Option37.Boolean("--verbose", false);
20828
+ subscription = Option37.String("--subscription", {
19967
20829
  description: "Azure subscription ID to discover gateway in (defaults to active az subscription)."
19968
20830
  });
19969
- resourceGroup = Option36.String("--resource-group", {
20831
+ resourceGroup = Option37.String("--resource-group", {
19970
20832
  description: "m8t-stack resource group to disambiguate the gateway (multi-deployment subscriptions)."
19971
20833
  });
19972
20834
  async executeCommand() {
@@ -20031,7 +20893,7 @@ var WhoamiCommand = class extends M8tCommand {
20031
20893
  };
20032
20894
 
20033
20895
  // src/commands/status.ts
20034
- import { Command as Command39, Option as Option37 } from "clipanion";
20896
+ import { Command as Command41, Option as Option38 } from "clipanion";
20035
20897
 
20036
20898
  // src/lib/azd.ts
20037
20899
  init_errors();
@@ -20096,10 +20958,10 @@ async function resolveLocalContext() {
20096
20958
  // src/commands/status.ts
20097
20959
  var StatusCommand = class extends M8tCommand {
20098
20960
  static paths = [["status"]];
20099
- static usage = Command39.Usage({
20961
+ static usage = Command41.Usage({
20100
20962
  description: "Show the local m8t context: identity, config.yaml, gateway cache, azd mode."
20101
20963
  });
20102
- output = Option37.String("--output");
20964
+ output = Option38.String("--output");
20103
20965
  async executeCommand() {
20104
20966
  const mode = resolveOutputMode(
20105
20967
  this.output,
@@ -20137,11 +20999,11 @@ var StatusCommand = class extends M8tCommand {
20137
20999
  };
20138
21000
 
20139
21001
  // src/commands/doctor.ts
20140
- import { Command as Command40, Option as Option38 } from "clipanion";
20141
- import { DefaultAzureCredential as DefaultAzureCredential18 } from "@azure/identity";
20142
- import * as fs22 from "fs";
21002
+ import { Command as Command42, Option as Option39 } from "clipanion";
21003
+ import { DefaultAzureCredential as DefaultAzureCredential19 } from "@azure/identity";
21004
+ import * as fs23 from "fs";
20143
21005
  import * as os10 from "os";
20144
- import * as path23 from "path";
21006
+ import * as path24 from "path";
20145
21007
 
20146
21008
  // src/lib/doctor-checks.ts
20147
21009
  function checkAzSignedIn(az) {
@@ -20366,34 +21228,34 @@ async function resolveAccountLocation(accountId) {
20366
21228
  }
20367
21229
  }
20368
21230
  function probeRepoRoot() {
20369
- const marker = path23.join(os10.homedir(), ".m8t-stack", "repo-root");
20370
- if (!fs22.existsSync(marker)) {
21231
+ const marker = path24.join(os10.homedir(), ".m8t-stack", "repo-root");
21232
+ if (!fs23.existsSync(marker)) {
20371
21233
  return { markerExists: false, path: null, isDir: false, isGitCheckout: false };
20372
21234
  }
20373
21235
  let repoPath;
20374
21236
  try {
20375
- repoPath = fs22.readFileSync(marker, "utf8").trim();
21237
+ repoPath = fs23.readFileSync(marker, "utf8").trim();
20376
21238
  } catch {
20377
21239
  return { markerExists: true, path: null, isDir: false, isGitCheckout: false };
20378
21240
  }
20379
21241
  if (!repoPath) return { markerExists: true, path: null, isDir: false, isGitCheckout: false };
20380
21242
  let isDir = false;
20381
21243
  try {
20382
- isDir = fs22.statSync(repoPath).isDirectory();
21244
+ isDir = fs23.statSync(repoPath).isDirectory();
20383
21245
  } catch {
20384
21246
  isDir = false;
20385
21247
  }
20386
- const isGitCheckout = isDir && fs22.existsSync(path23.join(repoPath, ".git"));
21248
+ const isGitCheckout = isDir && fs23.existsSync(path24.join(repoPath, ".git"));
20387
21249
  return { markerExists: true, path: repoPath, isDir, isGitCheckout };
20388
21250
  }
20389
21251
  var DoctorCommand = class extends M8tCommand {
20390
21252
  static paths = [["doctor"]];
20391
- static usage = Command40.Usage({
21253
+ static usage = Command42.Usage({
20392
21254
  description: "Diagnose the local m8t setup: az login, config.yaml, gateway, Foundry data-plane."
20393
21255
  });
20394
- output = Option38.String("--output");
20395
- agent = Option38.String("--agent");
20396
- resourceGroup = Option38.String("--resource-group", {
21256
+ output = Option39.String("--output");
21257
+ agent = Option39.String("--agent");
21258
+ resourceGroup = Option39.String("--resource-group", {
20397
21259
  description: "m8t-stack resource group to disambiguate the gateway (multi-deployment subscriptions)."
20398
21260
  });
20399
21261
  async executeCommand() {
@@ -20466,7 +21328,7 @@ var DoctorCommand = class extends M8tCommand {
20466
21328
  if (typeof this.agent === "string" && this.agent) {
20467
21329
  checking(`delivery grant for ${this.agent}`);
20468
21330
  try {
20469
- const credential2 = new DefaultAzureCredential18();
21331
+ const credential2 = new DefaultAzureCredential19();
20470
21332
  const cur = await getAgentVersion({
20471
21333
  credential: credential2,
20472
21334
  projectEndpoint: foundry.projectEndpoint,
@@ -20509,18 +21371,18 @@ var DoctorCommand = class extends M8tCommand {
20509
21371
  };
20510
21372
 
20511
21373
  // src/commands/switch.ts
20512
- import { Command as Command41, Option as Option39 } from "clipanion";
21374
+ import { Command as Command43, Option as Option40 } from "clipanion";
20513
21375
 
20514
21376
  // src/lib/profiles.ts
20515
- import * as fs23 from "fs/promises";
20516
- import * as path24 from "path";
21377
+ import * as fs24 from "fs/promises";
21378
+ import * as path25 from "path";
20517
21379
  import { parse as parseYaml11, stringify as stringifyYaml4 } from "yaml";
20518
21380
  function profilesDir() {
20519
21381
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
20520
- return path24.join(home, ".m8t-stack", "profiles");
21382
+ return path25.join(home, ".m8t-stack", "profiles");
20521
21383
  }
20522
21384
  function getProfilePath(name) {
20523
- return path24.join(profilesDir(), `${path24.basename(name)}.yaml`);
21385
+ return path25.join(profilesDir(), `${path25.basename(name)}.yaml`);
20524
21386
  }
20525
21387
  function slugifyTenant(domainOrId) {
20526
21388
  const base = domainOrId.split(".")[0].toLowerCase();
@@ -20529,7 +21391,7 @@ function slugifyTenant(domainOrId) {
20529
21391
  }
20530
21392
  async function listProfiles() {
20531
21393
  try {
20532
- const entries = await fs23.readdir(profilesDir());
21394
+ const entries = await fs24.readdir(profilesDir());
20533
21395
  return entries.filter((e) => e.endsWith(".yaml")).map((e) => e.replace(/\.yaml$/, "")).sort();
20534
21396
  } catch (e) {
20535
21397
  if (e.code === "ENOENT") return [];
@@ -20538,7 +21400,7 @@ async function listProfiles() {
20538
21400
  }
20539
21401
  async function readProfile(name) {
20540
21402
  try {
20541
- const raw = await fs23.readFile(getProfilePath(name), "utf8");
21403
+ const raw = await fs24.readFile(getProfilePath(name), "utf8");
20542
21404
  const parsed = parseYaml11(raw);
20543
21405
  if (!parsed || typeof parsed !== "object") return null;
20544
21406
  return parsed;
@@ -20549,14 +21411,14 @@ async function readProfile(name) {
20549
21411
  }
20550
21412
  async function exists(p) {
20551
21413
  try {
20552
- await fs23.stat(p);
21414
+ await fs24.stat(p);
20553
21415
  return true;
20554
21416
  } catch {
20555
21417
  return false;
20556
21418
  }
20557
21419
  }
20558
21420
  async function saveProfile(p) {
20559
- await fs23.mkdir(profilesDir(), { recursive: true });
21421
+ await fs24.mkdir(profilesDir(), { recursive: true });
20560
21422
  let name = p.name;
20561
21423
  let n = 2;
20562
21424
  while (await exists(getProfilePath(name))) {
@@ -20565,8 +21427,8 @@ async function saveProfile(p) {
20565
21427
  }
20566
21428
  const target = getProfilePath(name);
20567
21429
  const tmp = `${target}.tmp`;
20568
- await fs23.writeFile(tmp, stringifyYaml4({ ...p, name }, { indent: 2 }), "utf8");
20569
- await fs23.rename(tmp, target);
21430
+ await fs24.writeFile(tmp, stringifyYaml4({ ...p, name }, { indent: 2 }), "utf8");
21431
+ await fs24.rename(tmp, target);
20570
21432
  return name;
20571
21433
  }
20572
21434
 
@@ -20649,14 +21511,14 @@ async function profileSwitch(name, asName) {
20649
21511
  init_errors();
20650
21512
  var SwitchCommand = class extends M8tCommand {
20651
21513
  static paths = [["switch"]];
20652
- static usage = Command41.Usage({
21514
+ static usage = Command43.Usage({
20653
21515
  description: "Re-point local config at another deployment: --subscription <id|name> (discovery) or <profile>."
20654
21516
  });
20655
- profile = Option39.String({ required: false });
20656
- subscription = Option39.String("--subscription");
20657
- list = Option39.Boolean("--list", false);
20658
- as = Option39.String("--as");
20659
- output = Option39.String("--output");
21517
+ profile = Option40.String({ required: false });
21518
+ subscription = Option40.String("--subscription");
21519
+ list = Option40.Boolean("--list", false);
21520
+ as = Option40.String("--as");
21521
+ output = Option40.String("--output");
20660
21522
  async executeCommand() {
20661
21523
  const mode = resolveOutputMode(
20662
21524
  this.output,
@@ -20713,7 +21575,7 @@ var SwitchCommand = class extends M8tCommand {
20713
21575
 
20714
21576
  // src/commands/open.ts
20715
21577
  import { spawn as spawn5 } from "child_process";
20716
- import { Command as Command42, Option as Option40 } from "clipanion";
21578
+ import { Command as Command44, Option as Option41 } from "clipanion";
20717
21579
 
20718
21580
  // src/lib/open-targets.ts
20719
21581
  init_errors();
@@ -20759,14 +21621,14 @@ function openUrl(url) {
20759
21621
  }
20760
21622
  var OpenCommand = class extends M8tCommand {
20761
21623
  static paths = [["open"]];
20762
- static usage = Command42.Usage({
21624
+ static usage = Command44.Usage({
20763
21625
  description: "Open the deployed webapp (default), the Foundry portal, or the resource group.",
20764
21626
  details: "Targets: webapp (deployed app, default) | foundry (ai.azure.com) | portal (resource group in the Azure portal). Pass --print to emit the URL instead of launching a browser (also the default when stdout isn't a TTY)."
20765
21627
  });
20766
- target = Option40.String({ required: false });
20767
- print = Option40.Boolean("--print", false);
20768
- output = Option40.String("--output");
20769
- resourceGroup = Option40.String("--resource-group", {
21628
+ target = Option41.String({ required: false });
21629
+ print = Option41.Boolean("--print", false);
21630
+ output = Option41.String("--output");
21631
+ resourceGroup = Option41.String("--resource-group", {
20770
21632
  description: "m8t-stack resource group to disambiguate the gateway (multi-deployment subscriptions)."
20771
21633
  });
20772
21634
  async executeCommand() {
@@ -20810,9 +21672,9 @@ var OpenCommand = class extends M8tCommand {
20810
21672
  };
20811
21673
 
20812
21674
  // src/commands/dream/run.ts
20813
- import { Command as Command43, Option as Option41 } from "clipanion";
21675
+ import { Command as Command45, Option as Option42 } from "clipanion";
20814
21676
  import { AzureCliCredential } from "@azure/identity";
20815
- import { TableClient as TableClient3 } from "@azure/data-tables";
21677
+ import { TableClient as TableClient5 } from "@azure/data-tables";
20816
21678
  import { AIProjectClient as AIProjectClient3 } from "@azure/ai-projects";
20817
21679
  import { LogsQueryClient as LogsQueryClient3, LogsQueryResultStatus as LogsQueryResultStatus3 } from "@azure/monitor-query";
20818
21680
 
@@ -21119,7 +21981,7 @@ var ConvFetchError = class extends Error {
21119
21981
  };
21120
21982
 
21121
21983
  // ../../packages/brain/engine/dist/esm/cursor.js
21122
- import { TableClient as TableClient2 } from "@azure/data-tables";
21984
+ import { TableClient as TableClient4 } from "@azure/data-tables";
21123
21985
 
21124
21986
  // ../../packages/agent-ledger/dist/esm/row-key.js
21125
21987
  var MAX_MS = 864e13;
@@ -21241,7 +22103,7 @@ async function commitCursorAdvance(plan, cursor) {
21241
22103
  }
21242
22104
  var CURSOR_TABLE_NAME = "BrainDreamCursor";
21243
22105
  function makeTableCursor(credential2, tableEndpoint) {
21244
- const client = new TableClient2(tableEndpoint, CURSOR_TABLE_NAME, credential2);
22106
+ const client = new TableClient4(tableEndpoint, CURSOR_TABLE_NAME, credential2);
21245
22107
  return new TableCursor(client);
21246
22108
  }
21247
22109
  function formatCursorPreview(cursor) {
@@ -21735,7 +22597,7 @@ function buildAdvancePlan(worker, cursor, physicalPks, consumedRowsByPk, failedT
21735
22597
  // ../../packages/brain/engine/dist/esm/dream/writer.js
21736
22598
  var API2 = "https://api.github.com";
21737
22599
  var WRITER_ALLOWED_PREFIX = /^(memory|inbox|quarantine|artifacts)\//;
21738
- var isAllowedWritePath = (path32) => WRITER_ALLOWED_PREFIX.test(path32) && !path32.split("/").includes("..");
22600
+ var isAllowedWritePath = (path33, allowedPrefix) => allowedPrefix.test(path33) && !path33.split("/").includes("..");
21739
22601
  var unquote = (s) => s.trim().replace(/^["']|["']$/g, "");
21740
22602
  function parseFrontmatter(content) {
21741
22603
  const m = /^---\n([\s\S]*?)\n---/.exec(content);
@@ -21774,8 +22636,12 @@ function parseFrontmatter(content) {
21774
22636
  function makeGitDataWriter(args) {
21775
22637
  const doFetch = args.fetchImpl ?? fetch;
21776
22638
  const repoPath = args.repository;
21777
- async function gh(token, method, path32, body) {
21778
- const res = await doFetch(`${API2}/repos/${repoPath}${path32}`, {
22639
+ const allowedPathPrefix = args.allowedPathPrefix ?? WRITER_ALLOWED_PREFIX;
22640
+ if (args.allowedPathPrefix && !args.allowedPathPrefix.source.startsWith("^")) {
22641
+ throw new Error(`makeGitDataWriter: allowedPathPrefix must be anchored with "^" (got /${args.allowedPathPrefix.source}/) \u2014 an unanchored regex would widen the B1 write-path allowlist`);
22642
+ }
22643
+ async function gh(token, method, path33, body) {
22644
+ const res = await doFetch(`${API2}/repos/${repoPath}${path33}`, {
21779
22645
  method,
21780
22646
  headers: {
21781
22647
  Authorization: `Bearer ${token}`,
@@ -21788,8 +22654,8 @@ function makeGitDataWriter(args) {
21788
22654
  const text = await res.text();
21789
22655
  return { status: res.status, ok: res.ok, json: text ? JSON.parse(text) : {} };
21790
22656
  }
21791
- async function readFileWith(token, path32) {
21792
- const encodedPath = path32.split("/").map(encodeURIComponent).join("/");
22657
+ async function readFileWith(token, path33) {
22658
+ const encodedPath = path33.split("/").map(encodeURIComponent).join("/");
21793
22659
  const r = await gh(token, "GET", `/contents/${encodedPath}?ref=${args.branch}`);
21794
22660
  if (r.status === 404)
21795
22661
  return null;
@@ -21805,9 +22671,9 @@ function makeGitDataWriter(args) {
21805
22671
  throw new Error(`fetchTip: HTTP ${String(r.status)}`);
21806
22672
  return r.json.object.sha;
21807
22673
  },
21808
- async readFile(path32) {
22674
+ async readFile(path33) {
21809
22675
  const token = await args.mintToken();
21810
- return readFileWith(token, path32);
22676
+ return readFileWith(token, path33);
21811
22677
  },
21812
22678
  async listMemory() {
21813
22679
  const token = await args.mintToken();
@@ -21816,15 +22682,15 @@ function makeGitDataWriter(args) {
21816
22682
  return [];
21817
22683
  const paths = r.json.tree.filter((e) => e.type === "blob" && e.path.startsWith("memory/") && e.path.endsWith(".md")).map((e) => e.path);
21818
22684
  const out = [];
21819
- for (const path32 of paths) {
21820
- const content = await readFileWith(token, path32);
21821
- out.push({ path: path32, frontmatter: content ? parseFrontmatter(content) : {} });
22685
+ for (const path33 of paths) {
22686
+ const content = await readFileWith(token, path33);
22687
+ out.push({ path: path33, frontmatter: content ? parseFrontmatter(content) : {} });
21822
22688
  }
21823
22689
  return out;
21824
22690
  },
21825
22691
  async commitBatch(a) {
21826
22692
  try {
21827
- const escaping = a.changes.filter((c) => !isAllowedWritePath(c.path));
22693
+ const escaping = a.changes.filter((c) => !isAllowedWritePath(c.path, allowedPathPrefix));
21828
22694
  if (escaping.length > 0) {
21829
22695
  return {
21830
22696
  ok: false,
@@ -21876,9 +22742,11 @@ function makeFoundryModelClient(openai, model) {
21876
22742
  const create = openai.responses.create;
21877
22743
  const stream = await create({
21878
22744
  model,
22745
+ // Explicit `type: "message"` — the Responses API rejects untyped input
22746
+ // items (400 `Invalid value: ''`).
21879
22747
  input: [
21880
- { role: "system", content: system },
21881
- { role: "user", content: user }
22748
+ { type: "message", role: "system", content: system },
22749
+ { type: "message", role: "user", content: user }
21882
22750
  ],
21883
22751
  stream: true
21884
22752
  }, signal ? { signal } : void 0);
@@ -22255,7 +23123,7 @@ async function loadMemoryContext(brain) {
22255
23123
  return {
22256
23124
  files,
22257
23125
  indexEntries,
22258
- originOf: (path32) => originByPath.get(path32) ?? "worker"
23126
+ originOf: (path33) => originByPath.get(path33) ?? "worker"
22259
23127
  };
22260
23128
  }
22261
23129
 
@@ -22276,14 +23144,14 @@ function checkEvidenceMembership(delta, harvested) {
22276
23144
  }
22277
23145
  var BatchAbort = class extends Error {
22278
23146
  path;
22279
- constructor(path32, message) {
23147
+ constructor(path33, message) {
22280
23148
  super(message);
22281
- this.path = path32;
23149
+ this.path = path33;
22282
23150
  this.name = "BatchAbort";
22283
23151
  }
22284
23152
  };
22285
- var isIndexFile = (path32) => /(^|\/)[^/]*_index\.md$/.test(path32);
22286
- var isMemoryIndex = (path32) => path32 === "memory/MEMORY.md";
23153
+ var isIndexFile = (path33) => /(^|\/)[^/]*_index\.md$/.test(path33);
23154
+ var isMemoryIndex = (path33) => path33 === "memory/MEMORY.md";
22287
23155
  function targetPath(delta) {
22288
23156
  switch (delta.verb) {
22289
23157
  case "new":
@@ -22300,29 +23168,29 @@ function targetPath(delta) {
22300
23168
  }
22301
23169
  async function checkOriginTier(delta, lookup) {
22302
23170
  const evidence = evidenceOf(delta) ?? [];
22303
- const path32 = targetPath(delta);
22304
- if (path32 === null)
23171
+ const path33 = targetPath(delta);
23172
+ if (path33 === null)
22305
23173
  return { ok: true };
22306
- if (isIndexFile(path32) && !isMemoryIndex(path32)) {
22307
- return { ok: false, flagged: { kind: "flagged", path: path32, tension: `refused: ${path32} is an index file (only memory/MEMORY.md is editable)`, evidence } };
23174
+ if (isIndexFile(path33) && !isMemoryIndex(path33)) {
23175
+ return { ok: false, flagged: { kind: "flagged", path: path33, tension: `refused: ${path33} is an index file (only memory/MEMORY.md is editable)`, evidence } };
22308
23176
  }
22309
- const target = await lookup(path32);
22310
- const isStructural = path32.startsWith("references/");
23177
+ const target = await lookup(path33);
23178
+ const isStructural = path33.startsWith("references/");
22311
23179
  const origin = target?.frontmatter.origin ?? (isStructural ? "operator" : "worker");
22312
23180
  const editable = origin === "worker" || origin === "dream";
22313
23181
  if (editable)
22314
23182
  return { ok: true };
22315
23183
  const removesOperatorTarget = delta.verb === "retract" || delta.verb === "supersede" && delta.oldPath !== delta.newPath;
22316
23184
  if (removesOperatorTarget) {
22317
- throw new BatchAbort(path32, `supersede/retract OLD target ${path32} is origin: ${origin} \u2014 aborting batch (never half-apply)`);
23185
+ throw new BatchAbort(path33, `supersede/retract OLD target ${path33} is origin: ${origin} \u2014 aborting batch (never half-apply)`);
22318
23186
  }
22319
- return { ok: false, flagged: { kind: "flagged", path: path32, tension: `operator-origin (${origin}); flag-only`, evidence } };
23187
+ return { ok: false, flagged: { kind: "flagged", path: path33, tension: `operator-origin (${origin}); flag-only`, evidence } };
22320
23188
  }
22321
23189
  var ALLOWED_MEMORY = /^memory\/.+\.md$/;
22322
23190
  var ALLOWED_INBOX = /^inbox\//;
22323
23191
  var ALLOWED_QUARANTINE = /^quarantine\//;
22324
23192
  var ALLOWED_FLAG = /^memory\//;
22325
- var hasTraversal = (path32) => path32.split("/").includes("..");
23193
+ var hasTraversal = (path33) => path33.split("/").includes("..");
22326
23194
  function checkPathAllowed(delta) {
22327
23195
  const evidence = evidenceOf(delta) ?? [];
22328
23196
  const reject = (detail) => ({ ok: false, rejected: { kind: "rejected", detail, evidence } });
@@ -22429,7 +23297,7 @@ ${inject}
22429
23297
  ---
22430
23298
  `);
22431
23299
  }
22432
- var indexLine = (path32, title, date, tags) => `- \`${path32}\` \u2014 **${title}**: ${title}. (${date} \xB7 ${tags.join(", ")})`;
23300
+ var indexLine = (path33, title, date, tags) => `- \`${path33}\` \u2014 **${title}**: ${title}. (${date} \xB7 ${tags.join(", ")})`;
22433
23301
  function splitIndex(index) {
22434
23302
  const all = index.split("\n");
22435
23303
  const firstLineIdx = all.findIndex((l) => l.startsWith("- `memory/"));
@@ -22602,16 +23470,16 @@ function defaultDreamSeams(opts = {}) {
22602
23470
  const mem = await memory(deps.brain);
22603
23471
  const harvested = harvestedIds(input);
22604
23472
  const ctx = { readFile: (p) => deps.brain.readFile(p), at: deps.clock() };
22605
- const lookup = (path32) => Promise.resolve(mem.files.find((f) => f.path === path32) ?? null);
23473
+ const lookup = (path33) => Promise.resolve(mem.files.find((f) => f.path === path33) ?? null);
22606
23474
  const digest = [...pendingRejected];
22607
23475
  let changes = [];
22608
23476
  let indexEdit;
22609
23477
  try {
22610
23478
  for (const raw of deltas) {
22611
23479
  const delta = forcePolicyQuarantine(raw);
22612
- const path32 = checkPathAllowed(delta);
22613
- if (!path32.ok) {
22614
- digest.push(path32.rejected);
23480
+ const path33 = checkPathAllowed(delta);
23481
+ if (!path33.ok) {
23482
+ digest.push(path33.rejected);
22615
23483
  continue;
22616
23484
  }
22617
23485
  const ev = checkEvidenceMembership(delta, harvested);
@@ -22954,20 +23822,20 @@ function redactTranscripts(input) {
22954
23822
  }
22955
23823
  var DreamRunCommand = class extends M8tCommand {
22956
23824
  static paths = [["dream", "run"]];
22957
- static usage = Command43.Usage({
23825
+ static usage = Command45.Usage({
22958
23826
  description: "Dry-run the brain consumption pipeline for one worker (no model call, no writes).",
22959
23827
  details: "Builds AzureCliCredential + resolves the Foundry project, ledger table, and Log Analytics workspace, runs the consumption pipeline, and prints the skip-ledger, the partition invariant, and harvest stats. Transcripts are metadata-only unless --show-transcripts is passed."
22960
23828
  });
22961
- worker = Option41.String("--worker", { description: "Worker (canonical name) to harvest. Required." });
22962
- dryRun = Option41.Boolean("--dry-run", false, { description: "Read-only harvest; no model call, no writes." });
22963
- since = Option41.String("--since", { description: "ISO-8601 start override (rejected if malformed or future)." });
22964
- reset = Option41.Boolean("--reset", false, { description: "Ignore the stored cursor; read from the beginning." });
22965
- showTranscripts = Option41.Boolean("--show-transcripts", false, {
23829
+ worker = Option42.String("--worker", { description: "Worker (canonical name) to harvest. Required." });
23830
+ dryRun = Option42.Boolean("--dry-run", false, { description: "Read-only harvest; no model call, no writes." });
23831
+ since = Option42.String("--since", { description: "ISO-8601 start override (rejected if malformed or future)." });
23832
+ reset = Option42.Boolean("--reset", false, { description: "Ignore the stored cursor; read from the beginning." });
23833
+ showTranscripts = Option42.Boolean("--show-transcripts", false, {
22966
23834
  description: "Print transcript bodies (default: metadata only)."
22967
23835
  });
22968
- subscription = Option41.String("--subscription");
22969
- endpoint = Option41.String("--endpoint");
22970
- output = Option41.String("--output");
23836
+ subscription = Option42.String("--subscription");
23837
+ endpoint = Option42.String("--endpoint");
23838
+ output = Option42.String("--output");
22971
23839
  // Resolution seam — overridden by tests; built lazily at runtime otherwise.
22972
23840
  deps;
22973
23841
  async executeCommand() {
@@ -23162,7 +24030,7 @@ AppDependencies
23162
24030
  };
23163
24031
  }
23164
24032
  function buildLiveSources(context, credential2) {
23165
- const ledgerClient = new TableClient3(context.ledgerTableEndpoint, LEDGER_TABLE_NAME, credential2);
24033
+ const ledgerClient = new TableClient5(context.ledgerTableEndpoint, LEDGER_TABLE_NAME, credential2);
23166
24034
  const ledger = makeLedgerSource(
23167
24035
  async (pk, sinceTs) => fetchLedgerRows(
23168
24036
  { workers: [pk], source: "all", from: sinceTs, to: "9999-12-31T23:59:59.999Z" },
@@ -23315,7 +24183,7 @@ function defaultDeps(overrides) {
23315
24183
  }
23316
24184
 
23317
24185
  // src/commands/foundry/create.ts
23318
- import { Command as Command44, Option as Option42 } from "clipanion";
24186
+ import { Command as Command46, Option as Option43 } from "clipanion";
23319
24187
 
23320
24188
  // src/lib/foundry-create.ts
23321
24189
  init_errors();
@@ -23553,7 +24421,7 @@ async function createFoundryProject(args) {
23553
24421
  init_errors();
23554
24422
  var FoundryCreateCommand = class extends M8tCommand {
23555
24423
  static paths = [["foundry", "create"]];
23556
- static usage = Command44.Usage({
24424
+ static usage = Command46.Usage({
23557
24425
  description: "Create an AI Foundry (AIServices) account + project + model deployment from scratch.",
23558
24426
  details: "Non-interactive and idempotent. Creates the AIServices account (custom subdomain + project management), a project, and a model deployment (default gpt-4.1-mini @ capacity 50). Region must be hosted-agent-eligible. Emits the project endpoint as structured output. Re-run is a clean no-op (account/project skipped if present; deployment capacity converges UP, never down).",
23559
24427
  examples: [
@@ -23562,16 +24430,16 @@ var FoundryCreateCommand = class extends M8tCommand {
23562
24430
  ["Higher capacity for a reasoning model", "$0 foundry create --resource-group rg-m8t-stack --location eastus2 --model gpt-5-mini --capacity 250"]
23563
24431
  ]
23564
24432
  });
23565
- resourceGroup = Option42.String("--resource-group");
23566
- location = Option42.String("--location");
23567
- account = Option42.String("--account");
23568
- project = Option42.String("--project", "m8t");
23569
- model = Option42.String("--model", "gpt-4.1-mini");
23570
- modelVersion = Option42.String("--model-version", "2025-04-14");
23571
- capacity = Option42.String("--capacity", "50");
23572
- subscription = Option42.String("--subscription");
23573
- skipQuotaCheck = Option42.Boolean("--skip-quota-check", false);
23574
- output = Option42.String("--output");
24433
+ resourceGroup = Option43.String("--resource-group");
24434
+ location = Option43.String("--location");
24435
+ account = Option43.String("--account");
24436
+ project = Option43.String("--project", "m8t");
24437
+ model = Option43.String("--model", "gpt-4.1-mini");
24438
+ modelVersion = Option43.String("--model-version", "2025-04-14");
24439
+ capacity = Option43.String("--capacity", "50");
24440
+ subscription = Option43.String("--subscription");
24441
+ skipQuotaCheck = Option43.Boolean("--skip-quota-check", false);
24442
+ output = Option43.String("--output");
23575
24443
  async executeCommand() {
23576
24444
  const mode = resolveOutputMode(
23577
24445
  this.output,
@@ -23643,22 +24511,22 @@ var FoundryCreateCommand = class extends M8tCommand {
23643
24511
  };
23644
24512
 
23645
24513
  // src/commands/foundry/await-ready.ts
23646
- import { Command as Command45, Option as Option43 } from "clipanion";
24514
+ import { Command as Command47, Option as Option44 } from "clipanion";
23647
24515
  import { AzureCliCredential as AzureCliCredential2 } from "@azure/identity";
23648
24516
  init_errors();
23649
24517
  var FoundryAwaitReadyCommand = class extends M8tCommand {
23650
24518
  static paths = [["foundry", "await-ready"]];
23651
- static usage = Command45.Usage({
24519
+ static usage = Command47.Usage({
23652
24520
  description: "Wait until a freshly-created Foundry project's data plane reliably serves it.",
23653
24521
  details: "Probes the project (GET /agents) until it returns 200 on a few consecutive tries, or fails clearly after a bounded budget. A newly-created account can serve intermittent 404 'Project not found' for minutes; run this after 'foundry create' and before deploying agents so the worker phase doesn't catch the unstable window.",
23654
24522
  examples: [["Wait for a project to be ready", "$0 foundry await-ready --endpoint https://acc.services.ai.azure.com/api/projects/m8t"]]
23655
24523
  });
23656
- endpoint = Option43.String("--endpoint");
23657
- consecutive = Option43.String("--consecutive", "3");
23658
- attempts = Option43.String("--attempts", "60");
23659
- interval = Option43.String("--interval", "5");
23660
- subscription = Option43.String("--subscription");
23661
- output = Option43.String("--output");
24524
+ endpoint = Option44.String("--endpoint");
24525
+ consecutive = Option44.String("--consecutive", "3");
24526
+ attempts = Option44.String("--attempts", "60");
24527
+ interval = Option44.String("--interval", "5");
24528
+ subscription = Option44.String("--subscription");
24529
+ output = Option44.String("--output");
23662
24530
  async executeCommand() {
23663
24531
  const mode = resolveOutputMode(this.output, this.context.stdout);
23664
24532
  const endpoint = typeof this.endpoint === "string" ? this.endpoint : void 0;
@@ -23692,7 +24560,7 @@ var FoundryAwaitReadyCommand = class extends M8tCommand {
23692
24560
  };
23693
24561
 
23694
24562
  // src/commands/bootstrap/preflight.ts
23695
- import { Command as Command46, Option as Option44 } from "clipanion";
24563
+ import { Command as Command48, Option as Option45 } from "clipanion";
23696
24564
  init_errors();
23697
24565
 
23698
24566
  // src/lib/bootstrap-preflight.ts
@@ -23766,7 +24634,7 @@ function buildPreflightBanner(who) {
23766
24634
  // src/commands/bootstrap/preflight.ts
23767
24635
  var BootstrapPreflightCommand = class extends M8tCommand {
23768
24636
  static paths = [["bootstrap", "preflight"]];
23769
- static usage = Command46.Usage({
24637
+ static usage = Command48.Usage({
23770
24638
  description: "Loudly verify you can install m8t-stack (Owner/UAA + directory admin) and hard-stop if not.",
23771
24639
  details: "Step 1 of `m8t bootstrap`. Prints an unmissable admin-credentials notice, then checks: Owner or User Access Administrator at subscription scope (hard requirement), directory-admin capability to register the app (or pass --client-id), and registers Microsoft.ContainerInstance. Exits non-zero with the exact failing check + remedy.",
23772
24640
  examples: [
@@ -23774,8 +24642,8 @@ var BootstrapPreflightCommand = class extends M8tCommand {
23774
24642
  ["BYO app registration (directory guests)", "$0 bootstrap preflight --client-id <appId>"]
23775
24643
  ]
23776
24644
  });
23777
- clientId = Option44.String("--client-id");
23778
- subscription = Option44.String("--subscription");
24645
+ clientId = Option45.String("--client-id");
24646
+ subscription = Option45.String("--subscription");
23779
24647
  async executeCommand() {
23780
24648
  const clientId = typeof this.clientId === "string" ? this.clientId : void 0;
23781
24649
  const account = await getAzAccount();
@@ -23831,10 +24699,10 @@ ${colors.error(" " + why)}
23831
24699
  }
23832
24700
 
23833
24701
  // src/commands/bootstrap/launch.ts
23834
- import * as fs26 from "fs";
24702
+ import * as fs27 from "fs";
23835
24703
  import * as os13 from "os";
23836
- import * as path27 from "path";
23837
- import { Command as Command47, Option as Option45 } from "clipanion";
24704
+ import * as path28 from "path";
24705
+ import { Command as Command49, Option as Option46 } from "clipanion";
23838
24706
  init_errors();
23839
24707
 
23840
24708
  // src/lib/bootstrap-mi.ts
@@ -23979,9 +24847,9 @@ async function kickInstaller(s) {
23979
24847
 
23980
24848
  // src/lib/bootstrap-status.ts
23981
24849
  import { createHash as createHash4, randomUUID as randomUUID2 } from "crypto";
23982
- import * as fs24 from "fs/promises";
24850
+ import * as fs25 from "fs/promises";
23983
24851
  import * as os11 from "os";
23984
- import * as path25 from "path";
24852
+ import * as path26 from "path";
23985
24853
  init_errors();
23986
24854
  var STATUS_CONTAINER = "status";
23987
24855
  var STATUS_BLOB = "status.json";
@@ -24020,7 +24888,7 @@ async function readStatusBlob(opts) {
24020
24888
  cause: e
24021
24889
  });
24022
24890
  }
24023
- const tmpFile = path25.join(os11.tmpdir(), `m8t-status-${randomUUID2()}.json`);
24891
+ const tmpFile = path26.join(os11.tmpdir(), `m8t-status-${randomUUID2()}.json`);
24024
24892
  try {
24025
24893
  await runAz([
24026
24894
  "storage",
@@ -24039,7 +24907,7 @@ async function readStatusBlob(opts) {
24039
24907
  "--no-progress",
24040
24908
  "--only-show-errors"
24041
24909
  ]);
24042
- const raw = await fs24.readFile(tmpFile, "utf8");
24910
+ const raw = await fs25.readFile(tmpFile, "utf8");
24043
24911
  return JSON.parse(raw.trim());
24044
24912
  } catch (e) {
24045
24913
  throw new LocalCliError({
@@ -24049,23 +24917,23 @@ async function readStatusBlob(opts) {
24049
24917
  cause: e
24050
24918
  });
24051
24919
  } finally {
24052
- await fs24.rm(tmpFile, { force: true });
24920
+ await fs25.rm(tmpFile, { force: true });
24053
24921
  }
24054
24922
  }
24055
24923
 
24056
24924
  // src/lib/bootstrap-state.ts
24057
- import * as fs25 from "fs/promises";
24925
+ import * as fs26 from "fs/promises";
24058
24926
  import * as os12 from "os";
24059
- import * as path26 from "path";
24927
+ import * as path27 from "path";
24060
24928
  function stateDir(home) {
24061
- return path26.join(home, ".m8t-stack");
24929
+ return path27.join(home, ".m8t-stack");
24062
24930
  }
24063
24931
  function statePath(home) {
24064
- return path26.join(stateDir(home), "bootstrap.json");
24932
+ return path27.join(stateDir(home), "bootstrap.json");
24065
24933
  }
24066
24934
  async function readBootstrapState(home = os12.homedir()) {
24067
24935
  try {
24068
- const raw = await fs25.readFile(statePath(home), "utf8");
24936
+ const raw = await fs26.readFile(statePath(home), "utf8");
24069
24937
  return JSON.parse(raw);
24070
24938
  } catch (e) {
24071
24939
  if (e.code === "ENOENT") return null;
@@ -24073,10 +24941,10 @@ async function readBootstrapState(home = os12.homedir()) {
24073
24941
  }
24074
24942
  }
24075
24943
  async function writeBootstrapState(state, home = os12.homedir()) {
24076
- await fs25.mkdir(stateDir(home), { recursive: true });
24944
+ await fs26.mkdir(stateDir(home), { recursive: true });
24077
24945
  const tmp = `${statePath(home)}.tmp`;
24078
- await fs25.writeFile(tmp, JSON.stringify(state, null, 2), "utf8");
24079
- await fs25.rename(tmp, statePath(home));
24946
+ await fs26.writeFile(tmp, JSON.stringify(state, null, 2), "utf8");
24947
+ await fs26.rename(tmp, statePath(home));
24080
24948
  }
24081
24949
 
24082
24950
  // src/commands/bootstrap/launch.ts
@@ -24087,7 +24955,7 @@ var ACI_NAME = "m8t-installer";
24087
24955
  var MI_NAME = "m8t-installer-mi";
24088
24956
  var BootstrapLaunchCommand = class extends M8tCommand {
24089
24957
  static paths = [["bootstrap", "launch"]];
24090
- static usage = Command47.Usage({
24958
+ static usage = Command49.Usage({
24091
24959
  description: "Create + authorize the installer managed identity, then kick the cloud installer.",
24092
24960
  details: "Step 2 of `m8t bootstrap` (run after `preflight`). Ensures the resource group, creates the m8t-stack app registration (or uses --client-id), creates a user-assigned managed identity granted Owner at subscription scope, and launches the published m8t-installer image as an ACI run-to-completion job under that identity. Writes ~/.m8t-stack/bootstrap.json for `status`/`reap`/`finish`.",
24093
24961
  examples: [
@@ -24096,13 +24964,13 @@ var BootstrapLaunchCommand = class extends M8tCommand {
24096
24964
  ["Pin a specific installer tag", "$0 bootstrap launch --location eastus2 --installer-tag v0.1.0"]
24097
24965
  ]
24098
24966
  });
24099
- location = Option45.String("--location");
24100
- resourceGroup = Option45.String("--resource-group");
24101
- clientId = Option45.String("--client-id");
24102
- subscription = Option45.String("--subscription");
24103
- installerTag = Option45.String("--installer-tag");
24104
- gatewayImageRef = Option45.String("--gateway-image-ref");
24105
- githubAppCreds = Option45.String("--github-app-creds");
24967
+ location = Option46.String("--location");
24968
+ resourceGroup = Option46.String("--resource-group");
24969
+ clientId = Option46.String("--client-id");
24970
+ subscription = Option46.String("--subscription");
24971
+ installerTag = Option46.String("--installer-tag");
24972
+ gatewayImageRef = Option46.String("--gateway-image-ref");
24973
+ githubAppCreds = Option46.String("--github-app-creds");
24106
24974
  async executeCommand() {
24107
24975
  const location = typeof this.location === "string" ? this.location : void 0;
24108
24976
  if (!location) {
@@ -24116,12 +24984,12 @@ var BootstrapLaunchCommand = class extends M8tCommand {
24116
24984
  const subscriptionId = (typeof this.subscription === "string" ? this.subscription : void 0) ?? account.subscriptionId;
24117
24985
  const out = (m) => this.context.stderr.write(` ${colors.dim(m)}
24118
24986
  `);
24119
- const credsPath = typeof this.githubAppCreds === "string" ? this.githubAppCreds : path27.join(os13.homedir(), ".m8t-stack", "github-app.json");
24987
+ const credsPath = typeof this.githubAppCreds === "string" ? this.githubAppCreds : path28.join(os13.homedir(), ".m8t-stack", "github-app.json");
24120
24988
  let githubApp;
24121
- if (fs26.existsSync(credsPath)) {
24989
+ if (fs27.existsSync(credsPath)) {
24122
24990
  let parsed;
24123
24991
  try {
24124
- parsed = JSON.parse(fs26.readFileSync(credsPath, "utf8"));
24992
+ parsed = JSON.parse(fs27.readFileSync(credsPath, "utf8"));
24125
24993
  } catch {
24126
24994
  throw new LocalCliError({
24127
24995
  code: "GITHUB_APP_CREDS_INVALID",
@@ -24131,7 +24999,7 @@ var BootstrapLaunchCommand = class extends M8tCommand {
24131
24999
  }
24132
25000
  let pem;
24133
25001
  try {
24134
- pem = fs26.readFileSync(parsed.pemPath, "utf8");
25002
+ pem = fs27.readFileSync(parsed.pemPath, "utf8");
24135
25003
  } catch {
24136
25004
  throw new LocalCliError({
24137
25005
  code: "GITHUB_APP_PEM_MISSING",
@@ -24203,7 +25071,7 @@ var BootstrapLaunchCommand = class extends M8tCommand {
24203
25071
  };
24204
25072
 
24205
25073
  // src/commands/bootstrap/status.ts
24206
- import { Command as Command48, Option as Option46 } from "clipanion";
25074
+ import { Command as Command50, Option as Option47 } from "clipanion";
24207
25075
  init_errors();
24208
25076
 
24209
25077
  // src/lib/bootstrap-aci-state.ts
@@ -24231,13 +25099,13 @@ async function getAciState(opts) {
24231
25099
  // src/commands/bootstrap/status.ts
24232
25100
  var BootstrapStatusCommand = class extends M8tCommand {
24233
25101
  static paths = [["bootstrap", "status"]];
24234
- static usage = Command48.Usage({
25102
+ static usage = Command50.Usage({
24235
25103
  description: "Show the cloud installer's live status (phase, progress, result).",
24236
25104
  details: "Reads the durable status blob written by the installer. --watch polls until the install reaches done or failed.",
24237
25105
  examples: [["One read", "$0 bootstrap status"], ["Watch to completion", "$0 bootstrap status --watch"]]
24238
25106
  });
24239
- watch = Option46.Boolean("--watch", false);
24240
- output = Option46.String("--output");
25107
+ watch = Option47.Boolean("--watch", false);
25108
+ output = Option47.String("--output");
24241
25109
  async executeCommand() {
24242
25110
  const state = await readBootstrapState();
24243
25111
  if (!state) {
@@ -24308,7 +25176,7 @@ function formatStatus(d) {
24308
25176
  }
24309
25177
 
24310
25178
  // src/commands/bootstrap/reap.ts
24311
- import { Command as Command49, Option as Option47 } from "clipanion";
25179
+ import { Command as Command51, Option as Option48 } from "clipanion";
24312
25180
  init_errors();
24313
25181
 
24314
25182
  // src/lib/bootstrap-reap.ts
@@ -24384,14 +25252,14 @@ async function reapInstaller(opts) {
24384
25252
  // src/commands/bootstrap/reap.ts
24385
25253
  var BootstrapReapCommand = class extends M8tCommand {
24386
25254
  static paths = [["bootstrap", "reap"]];
24387
- static usage = Command49.Usage({
25255
+ static usage = Command51.Usage({
24388
25256
  description: "Tear down the installer scaffolding (ACI \u2192 MI \u2192 its role assignments) after a successful install.",
24389
25257
  details: "Runs locally on the 'done' signal (the installer can't delete its own identity). The platform RG and the gateway's own assignments persist. A failed install is left intact for diagnosis unless --force.",
24390
25258
  examples: [["Reap after done", "$0 bootstrap reap"]]
24391
25259
  });
24392
- force = Option47.Boolean("--force", false);
24393
- sweepOrphans = Option47.Boolean("--sweep-orphans", false);
24394
- yes = Option47.Boolean("--yes", false);
25260
+ force = Option48.Boolean("--force", false);
25261
+ sweepOrphans = Option48.Boolean("--sweep-orphans", false);
25262
+ yes = Option48.Boolean("--yes", false);
24395
25263
  async executeCommand() {
24396
25264
  if (this.sweepOrphans === true) {
24397
25265
  const { subscriptionId: sub } = await getAzAccount();
@@ -24456,17 +25324,17 @@ Found ${String(found.length)} orphaned Owner@sub assignment(s) (dry-run). ${colo
24456
25324
  };
24457
25325
 
24458
25326
  // src/commands/bootstrap/finish.ts
24459
- import * as fs27 from "fs/promises";
25327
+ import * as fs28 from "fs/promises";
24460
25328
  import * as os15 from "os";
24461
- import * as path29 from "path";
24462
- import { Command as Command50, Option as Option48 } from "clipanion";
25329
+ import * as path30 from "path";
25330
+ import { Command as Command52, Option as Option49 } from "clipanion";
24463
25331
  init_errors();
24464
25332
 
24465
25333
  // src/lib/company-profile-seed.ts
24466
25334
  import { spawn as spawn6 } from "child_process";
24467
- import { closeSync, openSync, readFileSync as readFileSync18 } from "fs";
25335
+ import { closeSync, openSync, readFileSync as readFileSync19 } from "fs";
24468
25336
  import * as os14 from "os";
24469
- import * as path28 from "path";
25337
+ import * as path29 from "path";
24470
25338
  init_errors();
24471
25339
 
24472
25340
  // src/lib/onboarding-profile.ts
@@ -24647,9 +25515,9 @@ function composeFounderIdentityNote(id) {
24647
25515
 
24648
25516
  // src/lib/company-profile-seed.ts
24649
25517
  function readGithubAppCreds(credsPath) {
24650
- const p = credsPath ?? path28.join(os14.homedir(), ".m8t-stack", "github-app.json");
25518
+ const p = credsPath ?? path29.join(os14.homedir(), ".m8t-stack", "github-app.json");
24651
25519
  try {
24652
- return JSON.parse(readFileSync18(p, "utf8"));
25520
+ return JSON.parse(readFileSync19(p, "utf8"));
24653
25521
  } catch {
24654
25522
  return null;
24655
25523
  }
@@ -24677,7 +25545,7 @@ async function resolveSeedContext(opts) {
24677
25545
  async function applyProfileToBrain(args) {
24678
25546
  const token = await mintInstallationTokenFromPem({
24679
25547
  appId: args.appCreds.appId,
24680
- privateKeyPem: readFileSync18(args.appCreds.pemPath, "utf8"),
25548
+ privateKeyPem: readFileSync19(args.appCreds.pemPath, "utf8"),
24681
25549
  installationId: args.appCreds.installationId,
24682
25550
  fetchImpl: args.fetchImpl
24683
25551
  });
@@ -24712,7 +25580,7 @@ async function applyProfileToBrain(args) {
24712
25580
  });
24713
25581
  }
24714
25582
  function spawnDetachedSeedWatch() {
24715
- const logPath = path28.join(os14.homedir(), ".m8t-stack", "seed-profile.log");
25583
+ const logPath = path29.join(os14.homedir(), ".m8t-stack", "seed-profile.log");
24716
25584
  const fd = openSync(logPath, "a");
24717
25585
  try {
24718
25586
  const child = spawn6(process.execPath, [process.argv[1] ?? "", "bootstrap", "seed-profile", "--watch"], {
@@ -24789,14 +25657,14 @@ function renderInstallSummary(args) {
24789
25657
  // src/commands/bootstrap/finish.ts
24790
25658
  var BootstrapFinishCommand = class extends M8tCommand {
24791
25659
  static paths = [["bootstrap", "finish"]];
24792
- static usage = Command50.Usage({
25660
+ static usage = Command52.Usage({
24793
25661
  description: "Point your local tools at the now-live platform (repo-root marker, discovery cache, next steps).",
24794
25662
  details: "Final step of `m8t bootstrap` (after the install reaches done). Writes ~/.m8t-stack/repo-root, points your local tools at the live gateway, and \u2014 if you completed the onboarding questionnaire \u2014 seeds your brain-backed advisor's brain with your company profile (best-effort; never blocks finish). Run `m8t bootstrap seed-profile` to seed manually later.",
24795
25663
  examples: [["Finish local", "$0 bootstrap finish --repo-root /path/to/m8t"]]
24796
25664
  });
24797
- repoRoot = Option48.String("--repo-root");
24798
- subscription = Option48.String("--subscription");
24799
- resourceGroup = Option48.String("--resource-group");
25665
+ repoRoot = Option49.String("--repo-root");
25666
+ subscription = Option49.String("--subscription");
25667
+ resourceGroup = Option49.String("--resource-group");
24800
25668
  async executeCommand() {
24801
25669
  const state = await readBootstrapState();
24802
25670
  if (!state) {
@@ -24811,9 +25679,9 @@ var BootstrapFinishCommand = class extends M8tCommand {
24811
25679
  });
24812
25680
  }
24813
25681
  const repoRoot = (typeof this.repoRoot === "string" ? this.repoRoot : void 0) ?? process.cwd();
24814
- const markerDir = path29.join(os15.homedir(), ".m8t-stack");
24815
- await fs27.mkdir(markerDir, { recursive: true });
24816
- await fs27.writeFile(path29.join(markerDir, "repo-root"), `${repoRoot}
25682
+ const markerDir = path30.join(os15.homedir(), ".m8t-stack");
25683
+ await fs28.mkdir(markerDir, { recursive: true });
25684
+ await fs28.writeFile(path30.join(markerDir, "repo-root"), `${repoRoot}
24817
25685
  `, "utf8");
24818
25686
  const subscriptionId = (typeof this.subscription === "string" ? this.subscription : void 0) ?? state.subscriptionId;
24819
25687
  const resourceGroup = (typeof this.resourceGroup === "string" ? this.resourceGroup : void 0) ?? state.resourceGroup;
@@ -24849,7 +25717,7 @@ var BootstrapFinishCommand = class extends M8tCommand {
24849
25717
  }
24850
25718
  let brainOrg = null;
24851
25719
  try {
24852
- const credsRaw = await fs27.readFile(path29.join(markerDir, "github-app.json"), "utf8");
25720
+ const credsRaw = await fs28.readFile(path30.join(markerDir, "github-app.json"), "utf8");
24853
25721
  const creds = JSON.parse(credsRaw);
24854
25722
  brainOrg = typeof creds.org === "string" ? creds.org : null;
24855
25723
  } catch {
@@ -24880,18 +25748,18 @@ ${colors.field("Then open a brand new chat/session")} \u2014 new skills + MCP se
24880
25748
  };
24881
25749
 
24882
25750
  // src/commands/bootstrap/ui.ts
24883
- import * as fs29 from "fs";
25751
+ import * as fs30 from "fs";
24884
25752
  import * as os17 from "os";
24885
- import * as path31 from "path";
24886
- import { Command as Command51, Option as Option49 } from "clipanion";
24887
- import { DefaultAzureCredential as DefaultAzureCredential19 } from "@azure/identity";
25753
+ import * as path32 from "path";
25754
+ import { Command as Command53, Option as Option50 } from "clipanion";
25755
+ import { DefaultAzureCredential as DefaultAzureCredential20 } from "@azure/identity";
24888
25756
  init_errors();
24889
25757
 
24890
25758
  // src/lib/bootstrap-ui.ts
24891
- import * as fs28 from "fs";
25759
+ import * as fs29 from "fs";
24892
25760
  import * as net from "net";
24893
25761
  import * as os16 from "os";
24894
- import * as path30 from "path";
25762
+ import * as path31 from "path";
24895
25763
  import { spawn as spawn7 } from "child_process";
24896
25764
  init_errors();
24897
25765
  init_rbac();
@@ -24958,9 +25826,9 @@ async function ensureFounderFoundryRole(args) {
24958
25826
  });
24959
25827
  }
24960
25828
  function writeWebEnvLocal(args) {
24961
- const envPath = path30.join(args.repoRoot, "apps", "web", ".env.local");
24962
- if (fs28.existsSync(envPath)) {
24963
- fs28.copyFileSync(envPath, envPath + ".bak");
25829
+ const envPath = path31.join(args.repoRoot, "apps", "web", ".env.local");
25830
+ if (fs29.existsSync(envPath)) {
25831
+ fs29.copyFileSync(envPath, envPath + ".bak");
24964
25832
  }
24965
25833
  const body = [
24966
25834
  "# Written by `m8t bootstrap ui` \u2014 onboarding chat-only run (Simple Stacey).",
@@ -24974,7 +25842,7 @@ function writeWebEnvLocal(args) {
24974
25842
  "NEXT_PUBLIC_M8T_ONBOARDING=1",
24975
25843
  ""
24976
25844
  ].join("\n");
24977
- fs28.writeFileSync(envPath, body, "utf8");
25845
+ fs29.writeFileSync(envPath, body, "utf8");
24978
25846
  return envPath;
24979
25847
  }
24980
25848
  function assertNodeVersion(versionString = process.version) {
@@ -25016,10 +25884,10 @@ async function installWebDeps(repoRoot) {
25016
25884
  await runInherit("pnpm", ["install"], repoRoot, "BOOTSTRAP_UI_INSTALL_FAILED");
25017
25885
  }
25018
25886
  function onboardingUiPaths(home = os16.homedir()) {
25019
- const dir = path30.join(home, ".m8t-stack");
25887
+ const dir = path31.join(home, ".m8t-stack");
25020
25888
  return {
25021
- logPath: path30.join(dir, "onboarding-ui.log"),
25022
- pidPath: path30.join(dir, "onboarding-ui.pid")
25889
+ logPath: path31.join(dir, "onboarding-ui.log"),
25890
+ pidPath: path31.join(dir, "onboarding-ui.pid")
25023
25891
  };
25024
25892
  }
25025
25893
  async function serveOnboardingUiDetached(args) {
@@ -25043,8 +25911,8 @@ async function serveOnboardingUiDetached(args) {
25043
25911
  if (await isPortOpen()) {
25044
25912
  return { alreadyRunning: true, logPath };
25045
25913
  }
25046
- fs28.mkdirSync(path30.dirname(logPath), { recursive: true });
25047
- const fd = fs28.openSync(logPath, "a");
25914
+ fs29.mkdirSync(path31.dirname(logPath), { recursive: true });
25915
+ const fd = fs29.openSync(logPath, "a");
25048
25916
  const child = spawn7("pnpm", ["--filter", "web", "dev"], {
25049
25917
  cwd: args.repoRoot,
25050
25918
  detached: true,
@@ -25052,7 +25920,7 @@ async function serveOnboardingUiDetached(args) {
25052
25920
  env: { ...process.env, PORT: args.port },
25053
25921
  shell: false
25054
25922
  });
25055
- fs28.closeSync(fd);
25923
+ fs29.closeSync(fd);
25056
25924
  if (child.pid == null) {
25057
25925
  throw new LocalCliError({
25058
25926
  code: "BOOTSTRAP_UI_DEV_SPAWN_FAILED",
@@ -25061,7 +25929,7 @@ async function serveOnboardingUiDetached(args) {
25061
25929
  });
25062
25930
  }
25063
25931
  child.unref();
25064
- fs28.writeFileSync(pidPath, String(child.pid), "utf8");
25932
+ fs29.writeFileSync(pidPath, String(child.pid), "utf8");
25065
25933
  const deadline = Date.now() + 45e3;
25066
25934
  const sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
25067
25935
  while (Date.now() < deadline) {
@@ -25084,7 +25952,7 @@ function stopOnboardingUi(home = os16.homedir()) {
25084
25952
  const { pidPath } = onboardingUiPaths(home);
25085
25953
  let pidStr;
25086
25954
  try {
25087
- pidStr = fs28.readFileSync(pidPath, "utf8").trim();
25955
+ pidStr = fs29.readFileSync(pidPath, "utf8").trim();
25088
25956
  } catch {
25089
25957
  return false;
25090
25958
  }
@@ -25097,7 +25965,7 @@ function stopOnboardingUi(home = os16.homedir()) {
25097
25965
  }
25098
25966
  }
25099
25967
  try {
25100
- fs28.unlinkSync(pidPath);
25968
+ fs29.unlinkSync(pidPath);
25101
25969
  } catch {
25102
25970
  }
25103
25971
  return true;
@@ -25154,7 +26022,7 @@ async function deploySimpleStacey(args) {
25154
26022
  // src/commands/bootstrap/ui.ts
25155
26023
  var BootstrapUiCommand = class extends M8tCommand {
25156
26024
  static paths = [["bootstrap", "ui"]];
25157
- static usage = Command51.Usage({
26025
+ static usage = Command53.Usage({
25158
26026
  description: "Deploy Simple Stacey + start the local onboarding chat UI in the background (returns immediately).",
25159
26027
  details: [
25160
26028
  "Run after `m8t bootstrap launch`, in parallel with `status --watch`. Waits for the cloud",
@@ -25172,15 +26040,15 @@ var BootstrapUiCommand = class extends M8tCommand {
25172
26040
  ["Foreground (blocking, old behaviour)", "$0 bootstrap ui --repo-root /path/to/m8t --foreground"]
25173
26041
  ]
25174
26042
  });
25175
- repoRoot = Option49.String("--repo-root");
25176
- port = Option49.String("--port", "3000");
25177
- endpoint = Option49.String("--endpoint", {
26043
+ repoRoot = Option50.String("--repo-root");
26044
+ port = Option50.String("--port", "3000");
26045
+ endpoint = Option50.String("--endpoint", {
25178
26046
  description: "Foundry project endpoint to target \u2014 disambiguates when the subscription has multiple projects."
25179
26047
  });
25180
- prepOnly = Option49.Boolean("--prep-only", false);
25181
- skipInstall = Option49.Boolean("--skip-install", false);
25182
- stop = Option49.Boolean("--stop", false);
25183
- foreground = Option49.Boolean("--foreground", false);
26048
+ prepOnly = Option50.Boolean("--prep-only", false);
26049
+ skipInstall = Option50.Boolean("--skip-install", false);
26050
+ stop = Option50.Boolean("--stop", false);
26051
+ foreground = Option50.Boolean("--foreground", false);
25184
26052
  async executeCommand() {
25185
26053
  if (this.stop === true) {
25186
26054
  const stopped = stopOnboardingUi();
@@ -25213,10 +26081,10 @@ var BootstrapUiCommand = class extends M8tCommand {
25213
26081
  this.context.stderr.write(` ${colors.dim(m)}
25214
26082
  `);
25215
26083
  };
25216
- if (fs29.existsSync(path31.join(os17.homedir(), ".m8t-stack", "config.yaml"))) {
26084
+ if (fs30.existsSync(path32.join(os17.homedir(), ".m8t-stack", "config.yaml"))) {
25217
26085
  out(colors.error("\u26A0 an existing ~/.m8t-stack/config.yaml will override the onboarding app registration in apps/web \u2014 if Microsoft sign-in fails, move it aside and retry."));
25218
26086
  }
25219
- const credential2 = new DefaultAzureCredential19();
26087
+ const credential2 = new DefaultAzureCredential20();
25220
26088
  const account = await getAzAccount();
25221
26089
  assertNodeVersion();
25222
26090
  out("waiting for Foundry (the installer's foundry-create phase)\u2026");
@@ -25287,10 +26155,10 @@ var BootstrapUiCommand = class extends M8tCommand {
25287
26155
  };
25288
26156
 
25289
26157
  // src/commands/bootstrap/seed-profile.ts
25290
- import { Command as Command52, Option as Option50 } from "clipanion";
26158
+ import { Command as Command54, Option as Option51 } from "clipanion";
25291
26159
  var BootstrapSeedProfileCommand = class extends M8tCommand {
25292
26160
  static paths = [["bootstrap", "seed-profile"]];
25293
- static usage = Command52.Usage({
26161
+ static usage = Command54.Usage({
25294
26162
  description: "Seed your advisors' brains with the founder + company profile from the onboarding questionnaire.",
25295
26163
  details: "Reads the latest stacey-intake conversation, renders memory/founder.md + memory/company-profile.md (+ their MEMORY.md index lines), and commits them to both <org>/stacey-brain and <org>/azzy-brain via the GitHub App. Idempotent. --watch polls until the founder completes the questionnaire.",
25296
26164
  examples: [
@@ -25298,11 +26166,11 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
25298
26166
  ["Wait for the founder to finish", "$0 bootstrap seed-profile --watch"]
25299
26167
  ]
25300
26168
  });
25301
- endpoint = Option50.String("--endpoint", { description: "Override the Foundry endpoint (else read from the install status)." });
25302
- brain = Option50.String("--brain", { description: "Override the brain repo (default <org>/stacey-brain)." });
25303
- watch = Option50.Boolean("--watch", false, { description: "Poll until the questionnaire completes (or --timeout)." });
25304
- timeout = Option50.String("--timeout", { description: "Watch timeout in minutes (default 20)." });
25305
- githubAppCreds = Option50.String("--github-app-creds");
26169
+ endpoint = Option51.String("--endpoint", { description: "Override the Foundry endpoint (else read from the install status)." });
26170
+ brain = Option51.String("--brain", { description: "Override the brain repo (default <org>/stacey-brain)." });
26171
+ watch = Option51.Boolean("--watch", false, { description: "Poll until the questionnaire completes (or --timeout)." });
26172
+ timeout = Option51.String("--timeout", { description: "Watch timeout in minutes (default 20)." });
26173
+ githubAppCreds = Option51.String("--github-app-creds");
25306
26174
  async executeCommand() {
25307
26175
  const ctx = await resolveSeedContext({
25308
26176
  endpointOverride: typeof this.endpoint === "string" ? this.endpoint : void 0,
@@ -25387,7 +26255,9 @@ cli.register(CoderTeardownCommand);
25387
26255
  cli.register(AzureExecDeployCommand);
25388
26256
  cli.register(PlatformStatusCommand);
25389
26257
  cli.register(PlatformUpdateCommand);
26258
+ cli.register(PlatformConvergeCommand);
25390
26259
  cli.register(PlatformEnableCostReportCommand);
26260
+ cli.register(PlatformEnableAutoUpdateCommand);
25391
26261
  cli.register(DeployCommand);
25392
26262
  cli.register(AgentDeployAdvisorCommand);
25393
26263
  cli.register(AgentRemoveCommand);