@m8t-stack/cli 0.2.49 → 0.2.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -453,7 +453,13 @@ function emptyChecks() {
453
453
  installationsListable: { ok: false }
454
454
  };
455
455
  }
456
+ function isPermanentKvRbacDenial(e) {
457
+ const msg = e.message ?? "";
458
+ return /assignment:\s*\(not found\)/i.test(msg);
459
+ }
456
460
  function isTransientErr(e) {
461
+ if (isPermanentKvRbacDenial(e))
462
+ return false;
457
463
  const any = e;
458
464
  const code = any.statusCode ?? any.status;
459
465
  if (typeof code === "number" && (code === 429 || code === 403 || code >= 500))
@@ -474,7 +480,12 @@ async function runChecksOnce(args) {
474
480
  checks.kvSecretsReadable = { ok: true };
475
481
  } catch (e) {
476
482
  checks.kvSecretsReadable = { ok: false, error: e.message };
477
- return { ok: false, transient: isTransientErr(e), checks };
483
+ return {
484
+ ok: false,
485
+ transient: isTransientErr(e),
486
+ ...isPermanentKvRbacDenial(e) ? { permanentReason: "kv_rbac_denied" } : {},
487
+ checks
488
+ };
478
489
  }
479
490
  const appIdNum = Number(appId);
480
491
  if (Number.isInteger(appIdNum) && appIdNum > 0) {
@@ -556,6 +567,30 @@ async function checkAppHealth(args) {
556
567
  }
557
568
  return last;
558
569
  }
570
+ function appHealthFailureCopy(health) {
571
+ if (health.permanentReason === "kv_rbac_denied") {
572
+ return {
573
+ message: "You do not have access to this platform's Key Vault, so the GitHub App secrets could not be read.",
574
+ hint: "This is a permanent permission gap, not a blip \u2014 retrying will not fix it. Run `m8t prereqs --fix` to grant yourself Key Vault secrets access (or ask whoever administers the subscription to run it for you), then re-run this command.",
575
+ category: "unknown",
576
+ retryable: false
577
+ };
578
+ }
579
+ if (health.transient === true) {
580
+ return {
581
+ message: "GitHub App health check failed transiently (Key Vault / GitHub API blip).",
582
+ hint: "This is usually a transient Azure/GitHub blip \u2014 retrying. If it persists, run `m8t brain check-app`.",
583
+ category: "transient",
584
+ retryable: true
585
+ };
586
+ }
587
+ return {
588
+ message: "GitHub App is not configured correctly.",
589
+ hint: "Run `m8t brain check-app` for details.",
590
+ category: "unknown",
591
+ retryable: false
592
+ };
593
+ }
559
594
  var init_check = __esm({
560
595
  "../../packages/github-app-auth/dist/esm/check.js"() {
561
596
  "use strict";
@@ -1110,7 +1145,7 @@ async function grantKeyVaultSecretsUser(args) {
1110
1145
  properties: {
1111
1146
  roleDefinitionId: `/subscriptions/${args.subscriptionId}/providers/Microsoft.Authorization/roleDefinitions/${KV_SECRETS_USER_ROLE_ID}`,
1112
1147
  principalId: args.principalId,
1113
- principalType: "ServicePrincipal"
1148
+ principalType: args.principalType ?? "ServicePrincipal"
1114
1149
  }
1115
1150
  },
1116
1151
  okStatuses: [409]
@@ -1329,7 +1364,7 @@ var init_enable_hosted_brain = __esm({
1329
1364
  import { Builtins, Cli } from "clipanion";
1330
1365
 
1331
1366
  // src/lib/package-version.ts
1332
- var CLI_VERSION = "0.2.49";
1367
+ var CLI_VERSION = "0.2.52";
1333
1368
 
1334
1369
  // src/lib/render-error.ts
1335
1370
  init_errors();
@@ -2054,10 +2089,10 @@ function ReadableStreamToAsyncIterable(stream) {
2054
2089
  return {
2055
2090
  async next() {
2056
2091
  try {
2057
- const result = await reader.read();
2058
- if (result?.done)
2092
+ const result2 = await reader.read();
2093
+ if (result2?.done)
2059
2094
  reader.releaseLock();
2060
- return result;
2095
+ return result2;
2061
2096
  } catch (e) {
2062
2097
  reader.releaseLock();
2063
2098
  throw e;
@@ -2799,9 +2834,9 @@ var Stream = class _Stream {
2799
2834
  return {
2800
2835
  next: () => {
2801
2836
  if (queue.length === 0) {
2802
- const result = iterator.next();
2803
- left.push(result);
2804
- right.push(result);
2837
+ const result2 = iterator.next();
2838
+ left.push(result2);
2839
+ right.push(result2);
2805
2840
  }
2806
2841
  return queue.shift();
2807
2842
  }
@@ -4384,23 +4419,23 @@ var AbstractChatCompletionRunner = class extends EventStream {
4384
4419
  }
4385
4420
  if (singleFunctionToCall || params.parallel_tool_calls === false) {
4386
4421
  for (const toolCall of message.tool_calls) {
4387
- const result = await runToolCall(toolCall);
4388
- if (result.message)
4389
- this._addMessage(result.message);
4390
- if (singleFunctionToCall && result.functionCalled) {
4422
+ const result2 = await runToolCall(toolCall);
4423
+ if (result2.message)
4424
+ this._addMessage(result2.message);
4425
+ if (singleFunctionToCall && result2.functionCalled) {
4391
4426
  await afterCompletion?.(chatCompletion, runner);
4392
4427
  return;
4393
4428
  }
4394
4429
  }
4395
4430
  } else {
4396
4431
  const results = await Promise.allSettled(message.tool_calls.map(runToolCall));
4397
- for (const result of results) {
4398
- if (result.status === "rejected")
4399
- throw result.reason;
4432
+ for (const result2 of results) {
4433
+ if (result2.status === "rejected")
4434
+ throw result2.reason;
4400
4435
  }
4401
- for (const result of results) {
4402
- if (result.status === "fulfilled" && result.value.message) {
4403
- this._addMessage(result.value.message);
4436
+ for (const result2 of results) {
4437
+ if (result2.status === "fulfilled" && result2.value.message) {
4438
+ this._addMessage(result2.value.message);
4404
4439
  }
4405
4440
  }
4406
4441
  }
@@ -11213,17 +11248,17 @@ Uploads.Parts = Parts;
11213
11248
  // ../../node_modules/.pnpm/openai@6.48.0_ws@8.21.1_zod@4.4.3/node_modules/openai/lib/Util.mjs
11214
11249
  var allSettledWithThrow = async (promises) => {
11215
11250
  const results = await Promise.allSettled(promises);
11216
- const rejected = results.filter((result) => result.status === "rejected");
11251
+ const rejected = results.filter((result2) => result2.status === "rejected");
11217
11252
  if (rejected.length) {
11218
- for (const result of rejected) {
11219
- console.error(result.reason);
11253
+ for (const result2 of rejected) {
11254
+ console.error(result2.reason);
11220
11255
  }
11221
11256
  throw new Error(`${rejected.length} promise(s) failed - see the above errors`);
11222
11257
  }
11223
11258
  const values = [];
11224
- for (const result of results) {
11225
- if (result.status === "fulfilled") {
11226
- values.push(result.value);
11259
+ for (const result2 of results) {
11260
+ if (result2.status === "fulfilled") {
11261
+ values.push(result2.value);
11227
11262
  }
11228
11263
  }
11229
11264
  return values;
@@ -13985,17 +14020,17 @@ var TeamRemoveCommand = class extends M8tCommand {
13985
14020
  }
13986
14021
  }
13987
14022
  const ctx = await resolveGatewayContext({ interactive, subscriptionId: this.subscription, resourceGroup: this.resourceGroup });
13988
- const result = await apiCall(
14023
+ const result2 = await apiCall(
13989
14024
  { gatewayUrl: ctx.gatewayUrl, gatewayClientId: ctx.gatewayClientId },
13990
14025
  { method: "DELETE", path: `/api/team/${encodeURIComponent(this.handle)}` },
13991
14026
  (msg) => this.context.stderr.write(msg + "\n")
13992
14027
  );
13993
14028
  if (mode === "json") {
13994
- this.context.stdout.write(renderJson(result) + "\n");
14029
+ this.context.stdout.write(renderJson(result2) + "\n");
13995
14030
  return 0;
13996
14031
  }
13997
14032
  this.context.stdout.write(
13998
- `${colors.success("\u2713")} removed ${colors.field(result.deleted.handle)} (${result.deleted.rows.toString()} row${result.deleted.rows === 1 ? "" : "s"}).
14033
+ `${colors.success("\u2713")} removed ${colors.field(result2.deleted.handle)} (${result2.deleted.rows.toString()} row${result2.deleted.rows === 1 ? "" : "s"}).
13999
14034
  `
14000
14035
  );
14001
14036
  return 0;
@@ -14717,10 +14752,10 @@ var BrainCheckAppCommand = class extends M8tCommand {
14717
14752
  );
14718
14753
  const kvUri = discoverKvUri(process.env, typeof this.kvUri === "string" ? this.kvUri : void 0);
14719
14754
  const credential2 = new DefaultAzureCredential3();
14720
- const result = await checkAppHealth({ credential: credential2, kvUri });
14755
+ const result2 = await checkAppHealth({ credential: credential2, kvUri });
14721
14756
  if (mode === "json") {
14722
- this.context.stdout.write(renderJson(result) + "\n");
14723
- return result.ok ? 0 : 1;
14757
+ this.context.stdout.write(renderJson(result2) + "\n");
14758
+ return result2.ok ? 0 : 1;
14724
14759
  }
14725
14760
  this.context.stdout.write(`${colors.field("GitHub App health check")} (${kvUri})
14726
14761
 
@@ -14731,18 +14766,18 @@ var BrainCheckAppCommand = class extends M8tCommand {
14731
14766
  this.context.stdout.write(` ${status} ${label.padEnd(28)} ${colors.dim(detail)}
14732
14767
  `);
14733
14768
  };
14734
- row("KV secrets readable", result.checks.kvSecretsReadable);
14735
- row("App ID numeric", result.checks.appIdNumeric);
14736
- row("Private key valid", result.checks.privateKeyValid);
14737
- row("App JWT accepted", result.checks.appJwtAccepted);
14738
- row("Installations listable", result.checks.installationsListable);
14769
+ row("KV secrets readable", result2.checks.kvSecretsReadable);
14770
+ row("App ID numeric", result2.checks.appIdNumeric);
14771
+ row("Private key valid", result2.checks.privateKeyValid);
14772
+ row("App JWT accepted", result2.checks.appJwtAccepted);
14773
+ row("Installations listable", result2.checks.installationsListable);
14739
14774
  this.context.stdout.write("\n");
14740
14775
  this.context.stdout.write(
14741
- result.ok ? `${colors.success("\u2713")} all checks passed.
14776
+ result2.ok ? `${colors.success("\u2713")} all checks passed.
14742
14777
  ` : `${colors.error("\u2717")} one or more checks failed \u2014 see above.
14743
14778
  `
14744
14779
  );
14745
- return result.ok ? 0 : 1;
14780
+ return result2.ok ? 0 : 1;
14746
14781
  }
14747
14782
  };
14748
14783
 
@@ -15810,8 +15845,7 @@ var BrainCreateCommand = class extends M8tCommand {
15810
15845
  if (!health.ok) {
15811
15846
  throw new LocalCliError({
15812
15847
  code: "APP_HEALTH_FAILED",
15813
- message: "GitHub App is not configured correctly.",
15814
- hint: "Run `m8t brain check-app` for details, then re-run this command."
15848
+ ...appHealthFailureCopy(health)
15815
15849
  });
15816
15850
  }
15817
15851
  }
@@ -15835,7 +15869,7 @@ var BrainCreateCommand = class extends M8tCommand {
15835
15869
  });
15836
15870
  }
15837
15871
  const tmpDir = fs11.mkdtempSync(path10.join(os4.tmpdir(), `m8t-brain-create-${worker}-`));
15838
- let result;
15872
+ let result2;
15839
15873
  let resolvedInstallationId = null;
15840
15874
  const force = this.reuse === true ? "reuse" : this.newName === true ? "new" : void 0;
15841
15875
  const interactive = !useAppPath && this.context.stdout.isTTY === true;
@@ -15950,7 +15984,7 @@ var BrainCreateCommand = class extends M8tCommand {
15950
15984
  `);
15951
15985
  }
15952
15986
  }
15953
- result = await linkBrain({
15987
+ result2 = await linkBrain({
15954
15988
  credential: credential2,
15955
15989
  kvUri,
15956
15990
  projectEndpoint: project.endpoint,
@@ -15975,14 +16009,14 @@ var BrainCreateCommand = class extends M8tCommand {
15975
16009
  repo: `${owner}/${finalName}`,
15976
16010
  branch,
15977
16011
  installationId: resolvedInstallationId,
15978
- connectionName: result.connectionName,
15979
- foundryVersion: result.foundryVersion
16012
+ connectionName: result2.connectionName,
16013
+ foundryVersion: result2.foundryVersion
15980
16014
  }) + "\n"
15981
16015
  );
15982
16016
  return 0;
15983
16017
  }
15984
16018
  this.context.stdout.write(
15985
- `${colors.success("\u2713")} created + linked ${colors.field(worker)} \u2194 ${colors.field(`${owner}/${finalName}`)} (agent version ${result.foundryVersion ?? ""}).
16019
+ `${colors.success("\u2713")} created + linked ${colors.field(worker)} \u2194 ${colors.field(`${owner}/${finalName}`)} (agent version ${result2.foundryVersion ?? ""}).
15986
16020
  `
15987
16021
  );
15988
16022
  this.context.stdout.write(` ${colors.hint("brain repo:")} https://github.com/${owner}/${finalName}
@@ -16070,8 +16104,7 @@ var BrainLinkCommand = class extends M8tCommand {
16070
16104
  if (!health.ok) {
16071
16105
  throw new LocalCliError({
16072
16106
  code: "APP_HEALTH_FAILED",
16073
- message: "GitHub App is not configured correctly.",
16074
- hint: "Run `m8t brain check-app` for details, then re-run this command."
16107
+ ...appHealthFailureCopy(health)
16075
16108
  });
16076
16109
  }
16077
16110
  const account = await getAzAccount();
@@ -16112,7 +16145,7 @@ var BrainLinkCommand = class extends M8tCommand {
16112
16145
  this.context.stdout.write(` ${colors.success("\u2713")} installation detected (id: ${installationId})
16113
16146
  `);
16114
16147
  }
16115
- const result = await linkBrain({
16148
+ const result2 = await linkBrain({
16116
16149
  credential: credential2,
16117
16150
  kvUri,
16118
16151
  projectEndpoint: project.endpoint,
@@ -16140,18 +16173,18 @@ var BrainLinkCommand = class extends M8tCommand {
16140
16173
  worker: this.worker,
16141
16174
  repo,
16142
16175
  branch,
16143
- installationId: result.installationId,
16144
- connectionName: result.connectionName,
16145
- foundryVersion: result.foundryVersion,
16146
- alreadyLinked: result.alreadyLinked
16176
+ installationId: result2.installationId,
16177
+ connectionName: result2.connectionName,
16178
+ foundryVersion: result2.foundryVersion,
16179
+ alreadyLinked: result2.alreadyLinked
16147
16180
  }) + "\n");
16148
16181
  return 0;
16149
16182
  }
16150
- if (result.alreadyLinked) {
16183
+ if (result2.alreadyLinked) {
16151
16184
  this.context.stdout.write(`${colors.success("\u2713")} ${colors.field(this.worker)} already linked to ${colors.field(repo)} (token rotated). No-op.
16152
16185
  `);
16153
16186
  } else {
16154
- this.context.stdout.write(`${colors.success("\u2713")} linked ${colors.field(this.worker)} \u2194 ${colors.field(repo)} via App-mode (install ${installationId}, agent version ${result.foundryVersion ?? "unknown"}).
16187
+ this.context.stdout.write(`${colors.success("\u2713")} linked ${colors.field(this.worker)} \u2194 ${colors.field(repo)} via App-mode (install ${installationId}, agent version ${result2.foundryVersion ?? "unknown"}).
16155
16188
  `);
16156
16189
  this.context.stdout.write(` ${colors.hint("brain doctrine:")} https://github.com/${repo}/blob/${branch}/AGENTS.md
16157
16190
  `);
@@ -16171,16 +16204,16 @@ import { AIProjectClient as AIProjectClient2 } from "@azure/ai-projects";
16171
16204
  async function listM8tAgents(args) {
16172
16205
  const project = new AIProjectClient2(args.projectEndpoint, args.credential);
16173
16206
  const iter = project.agents.list();
16174
- const result = [];
16207
+ const result2 = [];
16175
16208
  for await (const raw of iter) {
16176
16209
  if (!raw.name && !raw.id) continue;
16177
16210
  const name = String(raw.name ?? raw.id);
16178
16211
  const md = raw.metadata ?? raw.versions?.latest?.metadata ?? {};
16179
16212
  if (md.source === "m8t") {
16180
- result.push({ name, metadata: md });
16213
+ result2.push({ name, metadata: md });
16181
16214
  }
16182
16215
  }
16183
- return result;
16216
+ return result2;
16184
16217
  }
16185
16218
 
16186
16219
  // src/commands/brain/list.ts
@@ -16230,9 +16263,9 @@ import { Command as Command21, Option as Option20 } from "clipanion";
16230
16263
 
16231
16264
  // src/lib/github-orgs.ts
16232
16265
  var ORG_QUERY = "{ viewer { organizations(first:50) { nodes { login viewerCanAdminister enterpriseOwners(first:1) { totalCount } } } } }";
16233
- function classifyOrgs(result) {
16234
- if (!result.ok) return { verdict: "undetermined", candidates: [], orgs: [] };
16235
- const orgs = result.orgs;
16266
+ function classifyOrgs(result2) {
16267
+ if (!result2.ok) return { verdict: "undetermined", candidates: [], orgs: [] };
16268
+ const orgs = result2.orgs;
16236
16269
  if (orgs.length === 0) return { verdict: "no-orgs", candidates: [], orgs };
16237
16270
  const enterprise = orgs.filter((o) => o.enterprise);
16238
16271
  if (enterprise.length === 1) {
@@ -16635,7 +16668,7 @@ var BrainUnlinkCommand = class extends M8tCommand {
16635
16668
  endpoint: typeof this.endpoint === "string" ? this.endpoint : void 0,
16636
16669
  agentEndpoint
16637
16670
  });
16638
- const result = await unlinkBrain({
16671
+ const result2 = await unlinkBrain({
16639
16672
  credential: credential2,
16640
16673
  kvUri,
16641
16674
  projectEndpoint: project.endpoint,
@@ -16649,9 +16682,9 @@ var BrainUnlinkCommand = class extends M8tCommand {
16649
16682
  renderJson({
16650
16683
  worker,
16651
16684
  repo,
16652
- foundryVersion: result.foundryVersion,
16653
- connectionDeleted: result.connectionDeleted,
16654
- appUninstalled: result.appUninstalled
16685
+ foundryVersion: result2.foundryVersion,
16686
+ connectionDeleted: result2.connectionDeleted,
16687
+ appUninstalled: result2.appUninstalled
16655
16688
  }) + "\n"
16656
16689
  );
16657
16690
  return 0;
@@ -16660,7 +16693,7 @@ var BrainUnlinkCommand = class extends M8tCommand {
16660
16693
  `${colors.success("\u2713")} unlinked ${colors.field(worker)} from ${colors.field(repo)}. Repo intact \u2014 run ${colors.field(`gh repo delete ${repo}`)} to remove it.
16661
16694
  `
16662
16695
  );
16663
- if (!keepAppInstall && result.appUninstalled) {
16696
+ if (!keepAppInstall && result2.appUninstalled) {
16664
16697
  this.context.stdout.write(
16665
16698
  ` ${colors.hint("app install removed:")} re-link anytime with \`m8t brain link ${worker} --repo ${repo}\` \u2014 no re-registration needed.
16666
16699
  `
@@ -17063,10 +17096,10 @@ async function enableA2a(args) {
17063
17096
  message: `Agent '${args.agentName}' is kind '${current.definition.kind}'. Agent-to-agent enablement supports prompt agents (callers) and hosted agents (callees); '${current.definition.kind}' is neither.`
17064
17097
  });
17065
17098
  }
17066
- const bearer = `a2a_${randomBytes2(32).toString("base64url")}`;
17067
- const bearerHash = createHash("sha256").update(bearer).digest("hex");
17099
+ const bearer2 = `a2a_${randomBytes2(32).toString("base64url")}`;
17100
+ const bearerHash = createHash("sha256").update(bearer2).digest("hex");
17068
17101
  progress("Provisioning the A2A connection\u2026");
17069
- await putA2aConnection({ credential: args.credential, projectArmId: args.projectArmId, connectionName, target: args.bridgeUrl, bearer });
17102
+ await putA2aConnection({ credential: args.credential, projectArmId: args.projectArmId, connectionName, target: args.bridgeUrl, bearer: bearer2 });
17070
17103
  progress("Deploying the a2a-enabled agent version\u2026");
17071
17104
  const definition = {
17072
17105
  ...current.definition,
@@ -17313,9 +17346,9 @@ function defaultRemoveAgentDeps(args) {
17313
17346
  await deleteAgent({ credential: credential2, endpoint: projectEndpoint, name: agentName });
17314
17347
  },
17315
17348
  deleteBrainRepo(repo) {
17316
- const result = spawnSync2("gh", ["repo", "delete", repo, "--yes"], { encoding: "utf8" });
17317
- if (result.status !== 0) {
17318
- return Promise.reject(new Error(result.stderr?.trim() || `gh repo delete exited with code ${String(result.status)}`));
17349
+ const result2 = spawnSync2("gh", ["repo", "delete", repo, "--yes"], { encoding: "utf8" });
17350
+ if (result2.status !== 0) {
17351
+ return Promise.reject(new Error(result2.stderr?.trim() || `gh repo delete exited with code ${String(result2.status)}`));
17319
17352
  }
17320
17353
  return Promise.resolve();
17321
17354
  },
@@ -17537,7 +17570,7 @@ var A2aEnableCommand = class extends M8tCommand {
17537
17570
  });
17538
17571
  const personaPath = this.resolvePersonaPath();
17539
17572
  const bridgeUrl = resolveBridgeUrl({ flag: typeof this.gatewayUrl === "string" ? this.gatewayUrl : void 0, env });
17540
- const result = await enableA2a({
17573
+ const result2 = await enableA2a({
17541
17574
  credential: credential2,
17542
17575
  projectEndpoint: project.endpoint,
17543
17576
  projectArmId: `${project.accountScope}/projects/${project.projectName}`,
@@ -17547,17 +17580,17 @@ var A2aEnableCommand = class extends M8tCommand {
17547
17580
  onProgress: progress
17548
17581
  });
17549
17582
  if (mode === "json") {
17550
- this.context.stdout.write(renderJson({ worker: this.worker, mode: result.mode, connectionName: result.connectionName, foundryVersion: result.foundryVersion, bridgeUrl }) + "\n");
17583
+ this.context.stdout.write(renderJson({ worker: this.worker, mode: result2.mode, connectionName: result2.connectionName, foundryVersion: result2.foundryVersion, bridgeUrl }) + "\n");
17551
17584
  return 0;
17552
17585
  }
17553
- if (result.mode === "target") {
17554
- this.context.stdout.write(`${colors.success("\u2713")} ${colors.field(this.worker)} is a discoverable a2a target (agent version ${result.foundryVersion}).
17586
+ if (result2.mode === "target") {
17587
+ this.context.stdout.write(`${colors.success("\u2713")} ${colors.field(this.worker)} is a discoverable a2a target (agent version ${result2.foundryVersion}).
17555
17588
  `);
17556
17589
  this.context.stdout.write(` ${colors.hint("directory:")} it now appears in every a2a caller's discover_workers as a callee. Hosted agents are callees only \u2014 no caller connection.
17557
17590
  `);
17558
17591
  return 0;
17559
17592
  }
17560
- this.context.stdout.write(`${colors.success("\u2713")} ${colors.field(this.worker)} is a2a-enabled (connection ${colors.field(result.connectionName ?? "")}, agent version ${result.foundryVersion}).
17593
+ this.context.stdout.write(`${colors.success("\u2713")} ${colors.field(this.worker)} is a2a-enabled (connection ${colors.field(result2.connectionName ?? "")}, agent version ${result2.foundryVersion}).
17561
17594
  `);
17562
17595
  this.context.stdout.write(` ${colors.hint("directory:")} it now appears in every a2a worker's discover_workers, and can delegate via invoke_worker.
17563
17596
  `);
@@ -17604,7 +17637,7 @@ var A2aDisableCommand = class extends M8tCommand {
17604
17637
  endpoint: typeof this.endpoint === "string" ? this.endpoint : void 0,
17605
17638
  agentEndpoint: readAgentYaml(this.worker)?.projectEndpoint
17606
17639
  });
17607
- const result = await disableA2a({
17640
+ const result2 = await disableA2a({
17608
17641
  credential: credential2,
17609
17642
  projectEndpoint: project.endpoint,
17610
17643
  projectArmId: `${project.accountScope}/projects/${project.projectName}`,
@@ -17612,10 +17645,10 @@ var A2aDisableCommand = class extends M8tCommand {
17612
17645
  onProgress: progress
17613
17646
  });
17614
17647
  if (mode === "json") {
17615
- this.context.stdout.write(renderJson({ worker: this.worker, ...result }) + "\n");
17648
+ this.context.stdout.write(renderJson({ worker: this.worker, ...result2 }) + "\n");
17616
17649
  return 0;
17617
17650
  }
17618
- this.context.stdout.write(`${colors.success("\u2713")} ${colors.field(this.worker)} a2a-disabled (connection ${colors.field(result.connectionName)} removed, agent version ${result.foundryVersion ?? ""}).
17651
+ this.context.stdout.write(`${colors.success("\u2713")} ${colors.field(this.worker)} a2a-disabled (connection ${colors.field(result2.connectionName)} removed, agent version ${result2.foundryVersion ?? ""}).
17619
17652
  `);
17620
17653
  return 0;
17621
17654
  }
@@ -18338,7 +18371,7 @@ var CoderDeployCommand = class extends M8tCommand {
18338
18371
  const { persona: personaName, personaVersion } = resolvePersona(persona);
18339
18372
  const onProgress = mode === "pretty" ? (m) => this.context.stderr.write(` ${colors.dim(m)}
18340
18373
  `) : void 0;
18341
- const result = await deployHostedWorker({
18374
+ const result2 = await deployHostedWorker({
18342
18375
  credential: credential2,
18343
18376
  subscriptionId,
18344
18377
  project,
@@ -18369,10 +18402,7 @@ var CoderDeployCommand = class extends M8tCommand {
18369
18402
  if (!health.ok) {
18370
18403
  throw new LocalCliError({
18371
18404
  code: "APP_HEALTH_FAILED",
18372
- message: health.transient ? "GitHub App health check failed transiently (Key Vault / GitHub API blip)." : "GitHub App is not configured correctly.",
18373
- hint: health.transient ? "This is usually a transient Azure/GitHub blip \u2014 retrying. If it persists, run `m8t brain check-app`." : "Run `m8t brain check-app` for details.",
18374
- category: health.transient ? "transient" : "unknown",
18375
- retryable: health.transient === true
18405
+ ...appHealthFailureCopy(health)
18376
18406
  });
18377
18407
  }
18378
18408
  const { slug, appId, privateKeyPem } = await readAppSecrets({ credential: credential2, kvUri });
@@ -18412,7 +18442,7 @@ var CoderDeployCommand = class extends M8tCommand {
18412
18442
  await ensureDeliveryGrant({
18413
18443
  credential: credential2,
18414
18444
  subscriptionId,
18415
- principalId: result.principalId,
18445
+ principalId: result2.principalId,
18416
18446
  kvUri
18417
18447
  });
18418
18448
  this.context.stdout.write(
@@ -18447,20 +18477,20 @@ var CoderDeployCommand = class extends M8tCommand {
18447
18477
  this.context.stdout.write(
18448
18478
  renderJson({
18449
18479
  name: this.name,
18450
- version: result.version,
18451
- status: result.status,
18480
+ version: result2.version,
18481
+ status: result2.status,
18452
18482
  persona: personaName,
18453
18483
  image,
18454
18484
  size,
18455
18485
  endpoint: project.endpoint,
18456
- agentPrincipalId: result.principalId,
18486
+ agentPrincipalId: result2.principalId,
18457
18487
  warnings: warnings.list()
18458
18488
  }) + "\n"
18459
18489
  );
18460
18490
  return 0;
18461
18491
  }
18462
18492
  this.context.stdout.write(
18463
- `${colors.success("\u2713")} deployed hosted coder ${colors.field(this.name)} (version ${result.version}, ${result.status}, ${size}, persona ${personaName}).
18493
+ `${colors.success("\u2713")} deployed hosted coder ${colors.field(this.name)} (version ${result2.version}, ${result2.status}, ${size}, persona ${personaName}).
18464
18494
  `
18465
18495
  );
18466
18496
  this.context.stdout.write(` image: ${image}
@@ -18732,10 +18762,7 @@ var AzureExecDeployCommand = class extends M8tCommand {
18732
18762
  if (!health.ok) {
18733
18763
  throw new LocalCliError({
18734
18764
  code: "APP_HEALTH_FAILED",
18735
- message: health.transient ? "GitHub App health check failed transiently (Key Vault / GitHub API blip)." : "GitHub App is not configured correctly.",
18736
- hint: health.transient ? "This is usually a transient Azure/GitHub blip \u2014 retrying. If it persists, run `m8t brain check-app`." : "Run `m8t brain check-app` for details.",
18737
- category: health.transient ? "transient" : "unknown",
18738
- retryable: health.transient === true
18765
+ ...appHealthFailureCopy(health)
18739
18766
  });
18740
18767
  }
18741
18768
  const { slug, appId, privateKeyPem } = await readAppSecrets({ credential: credential2, kvUri });
@@ -18802,7 +18829,7 @@ var AzureExecDeployCommand = class extends M8tCommand {
18802
18829
  }
18803
18830
  }
18804
18831
  const { persona: personaName, personaVersion } = resolvePersona("azure-executor");
18805
- const result = await deployHostedWorker({
18832
+ const result2 = await deployHostedWorker({
18806
18833
  credential: credential2,
18807
18834
  subscriptionId,
18808
18835
  project,
@@ -18819,7 +18846,7 @@ var AzureExecDeployCommand = class extends M8tCommand {
18819
18846
  credential: credential2,
18820
18847
  subscriptionId,
18821
18848
  scope: grantScope,
18822
- principalId: result.principalId
18849
+ principalId: result2.principalId
18823
18850
  });
18824
18851
  if (this.grantAccessAdmin === true) {
18825
18852
  onProgress?.(`granting User Access Administrator at ${grantScope}\u2026`);
@@ -18827,14 +18854,14 @@ var AzureExecDeployCommand = class extends M8tCommand {
18827
18854
  credential: credential2,
18828
18855
  subscriptionId,
18829
18856
  scope: grantScope,
18830
- principalId: result.principalId
18857
+ principalId: result2.principalId
18831
18858
  });
18832
18859
  }
18833
18860
  onProgress?.("granting Key Vault Secrets User for delivery\u2026");
18834
18861
  await ensureDeliveryGrant({
18835
18862
  credential: credential2,
18836
18863
  subscriptionId,
18837
- principalId: result.principalId,
18864
+ principalId: result2.principalId,
18838
18865
  kvUri
18839
18866
  });
18840
18867
  onProgress?.("a2a-enabling as a target\u2026");
@@ -18856,20 +18883,20 @@ var AzureExecDeployCommand = class extends M8tCommand {
18856
18883
  this.context.stdout.write(
18857
18884
  renderJson({
18858
18885
  name: this.name,
18859
- version: result.version,
18860
- status: result.status,
18886
+ version: result2.version,
18887
+ status: result2.status,
18861
18888
  persona: personaName,
18862
18889
  image,
18863
18890
  scope: grantScope,
18864
18891
  endpoint: project.endpoint,
18865
- agentPrincipalId: result.principalId,
18892
+ agentPrincipalId: result2.principalId,
18866
18893
  warnings: warnings.list()
18867
18894
  }) + "\n"
18868
18895
  );
18869
18896
  return 0;
18870
18897
  }
18871
18898
  this.context.stdout.write(
18872
- `${colors.success("\u2713")} deployed Azure executor ${colors.field(this.name)} (version ${result.version}, ${result.status}, ${size}).
18899
+ `${colors.success("\u2713")} deployed Azure executor ${colors.field(this.name)} (version ${result2.version}, ${result2.status}, ${size}).
18873
18900
  `
18874
18901
  );
18875
18902
  this.context.stdout.write(` scope: ${grantScope}
@@ -19475,8 +19502,8 @@ async function readStampOutcome(opts) {
19475
19502
  }
19476
19503
  }
19477
19504
  async function readStamp(opts) {
19478
- const result = await readStampOutcome(opts);
19479
- return result.source === "explicit" ? result.stamp : null;
19505
+ const result2 = await readStampOutcome(opts);
19506
+ return result2.source === "explicit" ? result2.stamp : null;
19480
19507
  }
19481
19508
 
19482
19509
  // src/lib/platform-converge.ts
@@ -19636,7 +19663,7 @@ function buildDeployResult(args) {
19636
19663
  }
19637
19664
  async function runBicepDeployment(opts) {
19638
19665
  const bicepPath = path20.join(opts.repoRoot, "deploy", "main.bicep");
19639
- const result = JSON.parse(
19666
+ const result2 = JSON.parse(
19640
19667
  await runAz([
19641
19668
  "deployment",
19642
19669
  "group",
@@ -19653,7 +19680,7 @@ async function runBicepDeployment(opts) {
19653
19680
  "json"
19654
19681
  ])
19655
19682
  );
19656
- const outputs = result.properties?.outputs ?? {};
19683
+ const outputs = result2.properties?.outputs ?? {};
19657
19684
  const fqdn = outputs.containerAppFqdn?.value;
19658
19685
  if (typeof fqdn !== "string" || !fqdn) {
19659
19686
  throw new LocalCliError({
@@ -21182,8 +21209,8 @@ async function runHealthGateWithRollback(args) {
21182
21209
  const target = args.plan.targetVersion;
21183
21210
  let applied = [];
21184
21211
  try {
21185
- const result = await applyPlan(args.plan, args.deps, args.ctx);
21186
- applied = result.applied;
21212
+ const result2 = await applyPlan(args.plan, args.deps, args.ctx);
21213
+ applied = result2.applied;
21187
21214
  const outcomes = await runGate(args, applied, budgetMs);
21188
21215
  if (!gatePassed(outcomes)) {
21189
21216
  const detail = outcomes.find((o) => !o.ok)?.detail ?? "a post-apply health probe failed";
@@ -21692,7 +21719,7 @@ var PlatformSeedStampCommand = class extends M8tCommand {
21692
21719
  `);
21693
21720
  };
21694
21721
  const credential2 = typeof this.miClientId === "string" && this.miClientId.trim().length > 0 ? new ManagedIdentityCredential2({ clientId: this.miClientId.trim() }) : new DefaultAzureCredential18();
21695
- const result = await seedStamp2({
21722
+ const result2 = await seedStamp2({
21696
21723
  descriptorPath: need(this.descriptor, "--descriptor"),
21697
21724
  credential: credential2,
21698
21725
  subscriptionId: need(this.subscription, "--subscription"),
@@ -21700,9 +21727,9 @@ var PlatformSeedStampCommand = class extends M8tCommand {
21700
21727
  onProgress
21701
21728
  });
21702
21729
  if (mode === "json") {
21703
- this.context.stdout.write(renderJson(result) + "\n");
21730
+ this.context.stdout.write(renderJson(result2) + "\n");
21704
21731
  } else {
21705
- this.context.stdout.write(`${result.outcome}: ${result.platformVersion} (storage ${result.storageAccount})
21732
+ this.context.stdout.write(`${result2.outcome}: ${result2.platformVersion} (storage ${result2.storageAccount})
21706
21733
  `);
21707
21734
  }
21708
21735
  return 0;
@@ -21796,9 +21823,9 @@ var PlatformStampBuildCommand = class extends M8tCommand {
21796
21823
  ...priorComponents?.brainSeeds ? { brainSeeds: priorComponents.brainSeeds } : {}
21797
21824
  });
21798
21825
  await writeStamp({ ...ctx, stamp, onProgress });
21799
- const result = { platformVersion: stamp.platformVersion, previous: previousPlatformVersion, storageAccount: accountName };
21826
+ const result2 = { platformVersion: stamp.platformVersion, previous: previousPlatformVersion, storageAccount: accountName };
21800
21827
  this.context.stdout.write(
21801
- mode === "json" ? renderJson(result) + "\n" : `platform stamp: ${result.platformVersion} (previous ${result.previous ?? "none"}, storage ${result.storageAccount})
21828
+ mode === "json" ? renderJson(result2) + "\n" : `platform stamp: ${result2.platformVersion} (previous ${result2.previous ?? "none"}, storage ${result2.storageAccount})
21802
21829
  `
21803
21830
  );
21804
21831
  return 0;
@@ -21893,23 +21920,23 @@ var PlatformPolicyCommand = class extends M8tCommand {
21893
21920
  if (typeof this.set === "string" && this.set.length > 0) {
21894
21921
  const policy = buildPolicy(this.set, (/* @__PURE__ */ new Date()).toISOString(), "cli");
21895
21922
  await writePolicy({ ...ctx, policy, onProgress });
21896
- const result2 = { mode: policy.mode, updatedAt: policy.updatedAt };
21897
- this.context.stdout.write(mode === "json" ? renderJson(result2) + "\n" : `update policy: ${policy.mode}
21923
+ const result3 = { mode: policy.mode, updatedAt: policy.updatedAt };
21924
+ this.context.stdout.write(mode === "json" ? renderJson(result3) + "\n" : `update policy: ${policy.mode}
21898
21925
  `);
21899
21926
  return 0;
21900
21927
  }
21901
21928
  const current = await readPolicy(ctx);
21902
21929
  if (current.source === "unreadable") {
21903
- const result2 = { outcome: "unreadable" };
21930
+ const result3 = { outcome: "unreadable" };
21904
21931
  this.context.stdout.write(
21905
- mode === "json" ? renderJson(result2) + "\n" : `update policy: could not be read \u2014 the current setting is unknown; do not assume it is on the default (${DEFAULT_POLICY_MODE}).
21932
+ mode === "json" ? renderJson(result3) + "\n" : `update policy: could not be read \u2014 the current setting is unknown; do not assume it is on the default (${DEFAULT_POLICY_MODE}).
21906
21933
  `
21907
21934
  );
21908
21935
  return 0;
21909
21936
  }
21910
- const result = current.source === "explicit" ? { mode: current.policy.mode, outcome: "explicit" } : { mode: DEFAULT_POLICY_MODE, outcome: "absent" };
21937
+ const result2 = current.source === "explicit" ? { mode: current.policy.mode, outcome: "explicit" } : { mode: DEFAULT_POLICY_MODE, outcome: "absent" };
21911
21938
  this.context.stdout.write(
21912
- mode === "json" ? renderJson(result) + "\n" : `update policy: ${result.mode}${result.outcome === "absent" ? " (default \u2014 never set)" : ""}
21939
+ mode === "json" ? renderJson(result2) + "\n" : `update policy: ${result2.mode}${result2.outcome === "absent" ? " (default \u2014 never set)" : ""}
21913
21940
  `
21914
21941
  );
21915
21942
  return 0;
@@ -22573,8 +22600,8 @@ async function runWhatIf(opts) {
22573
22600
  "--output",
22574
22601
  "json"
22575
22602
  ]);
22576
- const result = JSON.parse(out);
22577
- return result.changes ?? [];
22603
+ const result2 = JSON.parse(out);
22604
+ return result2.changes ?? [];
22578
22605
  }
22579
22606
  var fmt = (c) => ` \u2022 ${c.resourceType} ${c.path}: ${JSON.stringify(c.before ?? null)} \u2192 ${JSON.stringify(c.after ?? null)}${c.reason ? ` [${c.reason}]` : ""}`;
22580
22607
  function reportWhatIf(r, opts) {
@@ -23666,8 +23693,12 @@ function checkDataPlane(probe) {
23666
23693
  return {
23667
23694
  name: "foundry data-plane",
23668
23695
  status: "FAIL",
23669
- detail: `HTTP ${probe.status.toString()} \u2014 your identity lacks a data-plane role on the Foundry account`,
23670
- remediation: `az role assignment create --assignee-object-id ${oid} --assignee-principal-type User --role "Cognitive Services User" --scope ${scope}`
23696
+ // Subscription Owner is the natural assumption and it is wrong: reading and
23697
+ // invoking agents are dataActions, and control-plane roles carry none.
23698
+ detail: `HTTP ${probe.status.toString()} \u2014 your identity lacks a data-plane role on the Foundry account (subscription Owner does not grant one)`,
23699
+ // "Foundry User", not a Cognitive Services role: Microsoft's guidance is that
23700
+ // Cognitive Services roles are for AI Services resources, not Foundry projects.
23701
+ remediation: `m8t prereqs --fix (or by hand: az role assignment create --assignee-object-id ${oid} --assignee-principal-type User --role "Foundry User" --scope ${scope})`
23671
23702
  };
23672
23703
  }
23673
23704
  return {
@@ -23676,6 +23707,24 @@ function checkDataPlane(probe) {
23676
23707
  detail: `HTTP ${probe.status !== 0 ? probe.status.toString() : "(unreachable)"} \u2014 unexpected; re-run with --verbose`
23677
23708
  };
23678
23709
  }
23710
+ function checkKeyVaultSecrets(probe) {
23711
+ if (probe.status >= 200 && probe.status < 300) {
23712
+ return { name: "key vault secrets", status: "PASS", detail: `HTTP ${probe.status.toString()}` };
23713
+ }
23714
+ if (probe.status === 401 || probe.status === 403) {
23715
+ return {
23716
+ name: "key vault secrets",
23717
+ status: "FAIL",
23718
+ detail: `HTTP ${probe.status.toString()} \u2014 you cannot read the platform Key Vault, so worker deploys will fail. This is a permanent permission gap, not a transient blip.`,
23719
+ remediation: `m8t prereqs --fix (or by hand: az role assignment create --assignee-object-id ${probe.oid ?? "<your-object-id>"} --assignee-principal-type User --role "Key Vault Secrets User" --scope ${probe.kvScope ?? "<key-vault-resource-id>"})`
23720
+ };
23721
+ }
23722
+ return {
23723
+ name: "key vault secrets",
23724
+ status: "INFO",
23725
+ detail: probe.status === 0 ? "skipped \u2014 could not reach the platform Key Vault" : `HTTP ${probe.status.toString()} \u2014 unexpected`
23726
+ };
23727
+ }
23679
23728
  function checkDeliveryGrant(probes) {
23680
23729
  const withDelivery = probes.filter((p) => p.hasDeliveryEnv);
23681
23730
  if (withDelivery.length === 0) {
@@ -23798,7 +23847,180 @@ function checkLegacyStateDir(probe) {
23798
23847
  init_foundry_agent_get();
23799
23848
  init_foundry_agents();
23800
23849
  init_rbac();
23801
- async function resolveFoundryAccountId(endpoint) {
23850
+
23851
+ // src/lib/prereq-probes.ts
23852
+ init_rbac();
23853
+ var FOUNDRY_SCOPE4 = "https://ai.azure.com/.default";
23854
+ var KV_SCOPE = "https://vault.azure.net/.default";
23855
+ var AGENTS_API = "v1";
23856
+ var COGNITIVE_SERVICES_USER_ROLE_ID = "a97b65f3-24c7-4388-baec-2e87135dc908";
23857
+ var FOUNDRY_DATA_PLANE_ROLE_IDS = [FOUNDRY_USER_ROLE_ID, COGNITIVE_SERVICES_USER_ROLE_ID];
23858
+ async function probeProviders(namespaces, subscriptionId) {
23859
+ return Promise.all(
23860
+ namespaces.map(async (namespace) => {
23861
+ try {
23862
+ const args = ["provider", "show", "--namespace", namespace, "--query", "registrationState", "-o", "tsv"];
23863
+ if (subscriptionId) args.push("--subscription", subscriptionId);
23864
+ const out = (await runAz(args)).trim();
23865
+ return { namespace, state: out && out !== "None" ? out : null };
23866
+ } catch {
23867
+ return { namespace, state: null };
23868
+ }
23869
+ })
23870
+ );
23871
+ }
23872
+ async function registerProviders(namespaces, opts = {}) {
23873
+ const attempts = opts.attempts ?? 20;
23874
+ const sleepMs = opts.sleepMs ?? 5e3;
23875
+ for (const namespace of namespaces) {
23876
+ try {
23877
+ const args = ["provider", "register", "--namespace", namespace, "--only-show-errors"];
23878
+ if (opts.subscriptionId) args.push("--subscription", opts.subscriptionId);
23879
+ await runAz(args);
23880
+ } catch {
23881
+ continue;
23882
+ }
23883
+ for (let i = 1; i <= attempts; i++) {
23884
+ const [state] = await probeProviders([namespace], opts.subscriptionId);
23885
+ if (state.state?.toLowerCase() === "registered") break;
23886
+ opts.onProgress?.(`registering ${namespace} (${i.toString()}/${attempts.toString()})\u2026`);
23887
+ if (sleepMs > 0) await new Promise((r) => setTimeout(r, sleepMs));
23888
+ }
23889
+ }
23890
+ }
23891
+ async function probeModelQuota(region, model) {
23892
+ const usages = await listModelQuota(region);
23893
+ const { verdict, quotad } = modelQuotaVerdict(usages, model);
23894
+ return { verdict, quotad, model, region, usagesReadable: usages.length > 0 };
23895
+ }
23896
+ async function bearer(credential2, scope) {
23897
+ try {
23898
+ const t = await credential2.getToken(scope);
23899
+ return t?.token ?? null;
23900
+ } catch {
23901
+ return null;
23902
+ }
23903
+ }
23904
+ async function hasRoleAtScope(credential2, scope, principalId, roleIds) {
23905
+ try {
23906
+ const token = await bearer(credential2, "https://management.azure.com/.default");
23907
+ if (!token) return null;
23908
+ const url = `https://management.azure.com${scope}/providers/Microsoft.Authorization/roleAssignments?api-version=2022-04-01&$filter=${encodeURIComponent(`principalId eq '${principalId}'`)}`;
23909
+ const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
23910
+ if (!res.ok) return null;
23911
+ const body = await res.json();
23912
+ return (body.value ?? []).some(
23913
+ (a) => roleIds.some((id) => (a.properties?.roleDefinitionId ?? "").toLowerCase().endsWith(id.toLowerCase()))
23914
+ );
23915
+ } catch {
23916
+ return null;
23917
+ }
23918
+ }
23919
+ async function probeFoundryAccess(args) {
23920
+ const base = {
23921
+ scope: args.accountId,
23922
+ principalId: args.principalId,
23923
+ isSelf: args.isSelf
23924
+ };
23925
+ if (args.isSelf) {
23926
+ const token = await bearer(args.credential, FOUNDRY_SCOPE4);
23927
+ if (!token) return { ...base, evidence: "unavailable" };
23928
+ try {
23929
+ const res = await fetch(`${args.projectEndpoint}/agents?api-version=${AGENTS_API}`, {
23930
+ headers: { Authorization: `Bearer ${token}` }
23931
+ });
23932
+ return { ...base, evidence: "probe", status: res.status };
23933
+ } catch {
23934
+ return { ...base, evidence: "unavailable" };
23935
+ }
23936
+ }
23937
+ if (!args.accountId || !args.principalId) return { ...base, evidence: "unavailable" };
23938
+ const granted = await hasRoleAtScope(args.credential, args.accountId, args.principalId, FOUNDRY_DATA_PLANE_ROLE_IDS);
23939
+ if (granted === null) return { ...base, evidence: "unavailable" };
23940
+ return { ...base, evidence: "assignment", granted };
23941
+ }
23942
+ async function probeKeyVaultAccess(args) {
23943
+ const base = {
23944
+ scope: args.kvResourceId ?? args.kvUri,
23945
+ principalId: args.principalId,
23946
+ isSelf: args.isSelf
23947
+ };
23948
+ if (args.isSelf) {
23949
+ if (!args.kvUri) return { ...base, evidence: "unavailable" };
23950
+ const token = await bearer(args.credential, KV_SCOPE);
23951
+ if (!token) return { ...base, evidence: "unavailable" };
23952
+ try {
23953
+ const url = `${args.kvUri.replace(/\/$/, "")}/secrets?api-version=7.4&maxresults=1`;
23954
+ const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } });
23955
+ return { ...base, evidence: "probe", status: res.status };
23956
+ } catch {
23957
+ return { ...base, evidence: "unavailable" };
23958
+ }
23959
+ }
23960
+ if (!args.kvResourceId || !args.principalId) return { ...base, evidence: "unavailable" };
23961
+ const granted = await hasRoleAtScope(args.credential, args.kvResourceId, args.principalId, [KV_SECRETS_USER_ROLE_ID]);
23962
+ if (granted === null) return { ...base, evidence: "unavailable" };
23963
+ return { ...base, evidence: "assignment", granted };
23964
+ }
23965
+ async function probeRedirectUri(clientId, gatewayUrl) {
23966
+ if (!clientId) return { registeredUris: null, gatewayUrl, clientId };
23967
+ try {
23968
+ const out = await runAz(["ad", "app", "show", "--id", clientId, "--query", "spa.redirectUris", "-o", "json"]);
23969
+ return { registeredUris: JSON.parse(out), gatewayUrl, clientId };
23970
+ } catch {
23971
+ return { registeredUris: null, gatewayUrl, clientId };
23972
+ }
23973
+ }
23974
+ async function resolveFoundryAccountId(projectEndpoint) {
23975
+ const m = /^https:\/\/([^.]+)\./.exec(projectEndpoint);
23976
+ if (!m) return null;
23977
+ try {
23978
+ const out = await runAz(["cognitiveservices", "account", "list", "--query", `[?name=='${m[1]}'].id`, "-o", "json"]);
23979
+ return JSON.parse(out)[0] ?? null;
23980
+ } catch {
23981
+ return null;
23982
+ }
23983
+ }
23984
+ async function resolvePlatformKeyVault(resourceGroup, subscriptionId) {
23985
+ try {
23986
+ const args = ["keyvault", "list", "-g", resourceGroup, "--query", "[0].{uri:properties.vaultUri,id:id}", "-o", "json"];
23987
+ if (subscriptionId) args.push("--subscription", subscriptionId);
23988
+ const parsed = JSON.parse(await runAz(args));
23989
+ return parsed?.uri && parsed.id ? { uri: parsed.uri, id: parsed.id } : null;
23990
+ } catch {
23991
+ return null;
23992
+ }
23993
+ }
23994
+ async function resolvePrincipalObjectId(who) {
23995
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(who)) {
23996
+ const asUser = await runAz(["ad", "user", "show", "--id", who, "--query", "id", "-o", "tsv"]).catch(() => "");
23997
+ if (asUser.trim()) return asUser.trim();
23998
+ return who;
23999
+ }
24000
+ const out = await runAz(["ad", "user", "show", "--id", who, "--query", "id", "-o", "tsv"]).catch(() => "");
24001
+ return out.trim() || null;
24002
+ }
24003
+ async function fixFoundryAccess(args) {
24004
+ await grantFoundryUser({
24005
+ credential: args.credential,
24006
+ subscriptionId: args.subscriptionId,
24007
+ accountScope: args.accountId,
24008
+ principalId: args.principalId,
24009
+ principalType: "User"
24010
+ });
24011
+ }
24012
+ async function fixKeyVaultAccess(args) {
24013
+ await grantKeyVaultSecretsUser({
24014
+ credential: args.credential,
24015
+ subscriptionId: args.subscriptionId,
24016
+ kvScope: args.kvResourceId,
24017
+ principalId: args.principalId,
24018
+ principalType: "User"
24019
+ });
24020
+ }
24021
+
24022
+ // src/commands/doctor.ts
24023
+ async function resolveFoundryAccountId2(endpoint) {
23802
24024
  const m = /^https:\/\/([^.]+)\.services\.ai\.azure\.com/.exec(endpoint);
23803
24025
  if (!m) return null;
23804
24026
  try {
@@ -23929,8 +24151,12 @@ var DoctorCommand = class extends M8tCommand {
23929
24151
  emit(checkLegacyStateDir(probeLegacyStateDir()));
23930
24152
  checking("gateway");
23931
24153
  let gw = { ok: false, detail: "not probed" };
24154
+ let platformRg = null;
24155
+ let platformSub = null;
23932
24156
  try {
23933
24157
  const ctx = await resolveGatewayContext({ interactive: false, resourceGroup: this.resourceGroup });
24158
+ platformRg = ctx.containerAppResourceId.split("/resourceGroups/")[1]?.split("/")[0] ?? null;
24159
+ platformSub = ctx.subscriptionId;
23934
24160
  const token = await getBearerToken(ctx.gatewayClientId);
23935
24161
  const res = await fetch(`${ctx.gatewayUrl}/api/me`, {
23936
24162
  headers: { Authorization: `Bearer ${token}` }
@@ -23952,9 +24178,24 @@ var DoctorCommand = class extends M8tCommand {
23952
24178
  } catch {
23953
24179
  status = 0;
23954
24180
  }
23955
- const foundryAccountId = await resolveFoundryAccountId(foundry.projectEndpoint);
24181
+ const foundryAccountId = await resolveFoundryAccountId2(foundry.projectEndpoint);
23956
24182
  const oid = await getCallerObjectId().catch(() => null);
23957
24183
  emit(checkDataPlane({ status, foundryAccountId, oid }));
24184
+ checking("key vault secrets");
24185
+ const kv = platformRg ? await resolvePlatformKeyVault(platformRg, platformSub ?? void 0) : null;
24186
+ let kvStatus = 0;
24187
+ if (kv) {
24188
+ try {
24189
+ const token = await new DefaultAzureCredential22().getToken("https://vault.azure.net/.default");
24190
+ const res = await fetch(`${kv.uri.replace(/\/$/, "")}/secrets?api-version=7.4&maxresults=1`, {
24191
+ headers: { Authorization: `Bearer ${token.token}` }
24192
+ });
24193
+ kvStatus = res.status;
24194
+ } catch {
24195
+ kvStatus = 0;
24196
+ }
24197
+ }
24198
+ emit(checkKeyVaultSecrets({ status: kvStatus, kvScope: kv?.id ?? null, oid }));
23958
24199
  checking("model capacity");
23959
24200
  const deployments = foundryAccountId ? await listModelDeployments(foundryAccountId) : [];
23960
24201
  emit(checkReasoningCapacity(deployments));
@@ -24007,8 +24248,715 @@ var DoctorCommand = class extends M8tCommand {
24007
24248
  }
24008
24249
  };
24009
24250
 
24010
- // src/commands/switch.ts
24251
+ // src/commands/prereqs.ts
24011
24252
  import { Command as Command47, Option as Option44 } from "clipanion";
24253
+ init_errors();
24254
+
24255
+ // src/lib/prereq-deps.ts
24256
+ import { DefaultAzureCredential as DefaultAzureCredential23 } from "@azure/identity";
24257
+
24258
+ // src/lib/bootstrap-preflight.ts
24259
+ var ADMIN_DIRECTORY_ROLES = ["Global Administrator", "Application Administrator", "Cloud Application Administrator"];
24260
+ async function checkSubScopeAdmin(callerObjectId, subscriptionId) {
24261
+ const rows = JSON.parse(await runAz([
24262
+ "role",
24263
+ "assignment",
24264
+ "list",
24265
+ "--assignee",
24266
+ callerObjectId,
24267
+ "--scope",
24268
+ `/subscriptions/${subscriptionId}`,
24269
+ "--include-inherited",
24270
+ "--query",
24271
+ "[].{roleDefinitionName: roleDefinitionName}",
24272
+ "-o",
24273
+ "json"
24274
+ ]));
24275
+ const hit = rows.find((r) => r.roleDefinitionName === "Owner" || r.roleDefinitionName === "User Access Administrator");
24276
+ return { ok: Boolean(hit), role: hit?.roleDefinitionName };
24277
+ }
24278
+ async function checkAppRegCapability(callerObjectId) {
24279
+ let roles;
24280
+ try {
24281
+ roles = JSON.parse(await runAz([
24282
+ "rest",
24283
+ "--method",
24284
+ "GET",
24285
+ "--url",
24286
+ `https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments?$filter=principalId eq '${callerObjectId}'&$expand=roleDefinition`,
24287
+ "--query",
24288
+ "value[].roleDefinition.displayName",
24289
+ "-o",
24290
+ "json"
24291
+ ]));
24292
+ } catch {
24293
+ roles = null;
24294
+ }
24295
+ if (roles === null) return { ok: false, inconclusive: true };
24296
+ return { ok: roles.some((r) => ADMIN_DIRECTORY_ROLES.includes(r)), inconclusive: false };
24297
+ }
24298
+ async function registerContainerInstance(subscriptionId) {
24299
+ await runAz(["provider", "register", "--namespace", "Microsoft.ContainerInstance", "--subscription", subscriptionId, "--only-show-errors"]);
24300
+ }
24301
+ function buildPreflightBanner(who) {
24302
+ return [
24303
+ "\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557",
24304
+ "\u2551 \u26D4 STOP \u2014 READ THIS BEFORE CONTINUING \u2551",
24305
+ "\u2551 \u2551",
24306
+ "\u2551 Installing m8t creates real Azure resources in YOUR \u2551",
24307
+ "\u2551 subscription and assigns roles. To do that, the account you just \u2551",
24308
+ "\u2551 signed in with MUST be: \u2551",
24309
+ "\u2551 \u2551",
24310
+ "\u2551 \u2022 OWNER or USER ACCESS ADMINISTRATOR at the subscription scope \u2551",
24311
+ "\u2551 (it has to create a managed identity and assign it roles), AND \u2551",
24312
+ "\u2551 \u2022 a directory admin able to register one Entra app \u2551",
24313
+ "\u2551 (Application / Cloud Application / Global Administrator) \u2551",
24314
+ "\u2551 \u2014 OR you supply a ready app registration with --client-id. \u2551",
24315
+ "\u2551 \u2551",
24316
+ "\u2551 If you are NOT this, this install CANNOT proceed and will STOP NOW. \u2551",
24317
+ "\u2551 Nothing has been created yet. Get an admin to run this, or ask one \u2551",
24318
+ "\u2551 for an app registration id (--client-id <appId>) and try again. \u2551",
24319
+ "\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D",
24320
+ "",
24321
+ `Signed in as: ${who.upn} (tenant ${who.tenantId}, sub ${who.subscriptionId})`,
24322
+ ""
24323
+ ].join("\n");
24324
+ }
24325
+
24326
+ // src/lib/prereq-deps.ts
24327
+ var INSTALL_REASONING_MODEL = "gpt-5.4";
24328
+ function buildPrereqDeps(opts = {}) {
24329
+ let credentialSingleton2 = null;
24330
+ return {
24331
+ getAzAccount: () => getAzAccount().catch(() => null),
24332
+ getCallerObjectId: () => getCallerObjectId().catch(() => null),
24333
+ checkSubScopeAdmin,
24334
+ checkAppRegCapability,
24335
+ probeProviders,
24336
+ registerProviders,
24337
+ probeModelQuota,
24338
+ discoverPlatform: async () => {
24339
+ const cfg = await readFoundryConfig();
24340
+ const d = await discoverGateway({
24341
+ interactive: false,
24342
+ ...opts.resourceGroup ? { resourceGroup: opts.resourceGroup } : {},
24343
+ ...opts.subscription ? { subscriptionId: opts.subscription } : {}
24344
+ });
24345
+ return {
24346
+ gatewayUrl: d.gatewayUrl,
24347
+ gatewayClientId: d.gatewayClientId,
24348
+ subscriptionId: d.subscriptionId,
24349
+ resourceGroup: d.containerAppResourceId.split("/resourceGroups/")[1]?.split("/")[0] ?? "",
24350
+ // The DEPLOYMENT is the authority, local config only a fallback. A
24351
+ // teammate joining a platform someone else installed has no
24352
+ // ~/.m8t/config.yaml at all — reading it first left the single most
24353
+ // important check reporting "could not determine" for exactly the person
24354
+ // this feature exists to serve.
24355
+ projectEndpoint: d.projectEndpoint ?? cfg?.projectEndpoint ?? null
24356
+ };
24357
+ },
24358
+ isNoPlatformError: (e) => e.code === "GATEWAY_DISCOVERY_ZERO",
24359
+ resolveFoundryAccountId,
24360
+ resolvePlatformKeyVault,
24361
+ resolvePrincipalObjectId,
24362
+ probeFoundryAccess,
24363
+ probeKeyVaultAccess,
24364
+ probeRedirectUri,
24365
+ fixFoundryAccess,
24366
+ fixKeyVaultAccess,
24367
+ credential: () => credentialSingleton2 ??= new DefaultAzureCredential23()
24368
+ };
24369
+ }
24370
+
24371
+ // src/lib/prereqs.ts
24372
+ var PREREQS = [
24373
+ // ── install phase ────────────────────────────────────────────────────────
24374
+ {
24375
+ slug: "local-tooling",
24376
+ phase: "install",
24377
+ title: "git, Azure CLI, Node 20+, and the m8t CLI on your machine",
24378
+ breaks: "Nothing can run at all.",
24379
+ coverage: "auto",
24380
+ checkedBy: "runbook",
24381
+ fixable: false
24382
+ },
24383
+ {
24384
+ slug: "az-signed-in",
24385
+ phase: "install",
24386
+ title: "Signed in to Azure with an active subscription",
24387
+ breaks: "Every Azure call fails immediately.",
24388
+ coverage: "refuses",
24389
+ checkedBy: "prereqs",
24390
+ fixable: false
24391
+ },
24392
+ {
24393
+ slug: "subscription-admin",
24394
+ phase: "install",
24395
+ title: "Owner or User Access Administrator at subscription scope",
24396
+ breaks: "The install cannot create its managed identity or assign it roles.",
24397
+ coverage: "refuses",
24398
+ checkedBy: "prereqs",
24399
+ fixable: false
24400
+ },
24401
+ {
24402
+ slug: "app-registration",
24403
+ phase: "install",
24404
+ title: "Directory rights to register one Entra app (or a ready --client-id)",
24405
+ breaks: "There is no app registration, so nobody can ever sign in to the webapp.",
24406
+ coverage: "refuses",
24407
+ checkedBy: "prereqs",
24408
+ fixable: false
24409
+ },
24410
+ {
24411
+ slug: "resource-providers",
24412
+ phase: "install",
24413
+ title: "Every Azure resource provider the install uses is registered",
24414
+ breaks: "The installer dies partway through creating resources, with an error that names a provider rather than anything actionable.",
24415
+ coverage: "refuses",
24416
+ checkedBy: "prereqs",
24417
+ fixable: true
24418
+ },
24419
+ {
24420
+ slug: "model-quota",
24421
+ phase: "install",
24422
+ title: "Model quota for the reasoning model in the target region",
24423
+ breaks: "The install spends money creating resources and then cannot deploy the model the workers need.",
24424
+ coverage: "refuses",
24425
+ checkedBy: "prereqs",
24426
+ fixable: false
24427
+ },
24428
+ {
24429
+ slug: "github-org",
24430
+ phase: "install",
24431
+ title: "A GitHub organization and the rights to install an App on it",
24432
+ breaks: "The brain-backed workers have nowhere to keep their memory.",
24433
+ coverage: "refuses",
24434
+ checkedBy: "runbook",
24435
+ fixable: false
24436
+ },
24437
+ {
24438
+ slug: "azure-policy",
24439
+ phase: "install",
24440
+ title: "No Azure Policy or deny assignment blocking resource creation",
24441
+ breaks: "The installer is refused by governance partway through, with a policy error.",
24442
+ coverage: "unhandled",
24443
+ checkedBy: "none",
24444
+ fixable: false
24445
+ },
24446
+ {
24447
+ slug: "region-eligibility",
24448
+ phase: "install",
24449
+ title: "The target region can host every service the platform uses",
24450
+ breaks: "A resource fails to create in that region partway through the install.",
24451
+ coverage: "unhandled",
24452
+ checkedBy: "none",
24453
+ fixable: false
24454
+ },
24455
+ {
24456
+ slug: "soft-deleted-account",
24457
+ phase: "install",
24458
+ title: "No soft-deleted Cognitive Services account still holding the model quota",
24459
+ breaks: "Quota looks free but the model deployment fails; the quota is held by an account you already deleted.",
24460
+ coverage: "unhandled",
24461
+ checkedBy: "none",
24462
+ fixable: false
24463
+ },
24464
+ {
24465
+ slug: "subscription-billing",
24466
+ phase: "install",
24467
+ title: "The subscription is active and can be billed",
24468
+ breaks: "Resource creation is refused for billing reasons.",
24469
+ coverage: "unhandled",
24470
+ checkedBy: "none",
24471
+ fixable: false
24472
+ },
24473
+ // ── usage phase ──────────────────────────────────────────────────────────
24474
+ {
24475
+ slug: "platform-discoverable",
24476
+ phase: "usage",
24477
+ title: "The m8t CLI can find the platform you are pointed at",
24478
+ breaks: "Every CLI command that talks to the platform fails to find it.",
24479
+ coverage: "refuses",
24480
+ checkedBy: "prereqs",
24481
+ fixable: false
24482
+ },
24483
+ {
24484
+ slug: "signin-redirect-uri",
24485
+ phase: "usage",
24486
+ title: "The webapp's sign-in redirect URI is registered on the app",
24487
+ breaks: "Sign-in fails with AADSTS50011 on a platform that is otherwise perfectly healthy.",
24488
+ coverage: "auto",
24489
+ checkedBy: "prereqs",
24490
+ // Reported, not repaired. The app registration is shared across every
24491
+ // deployment in the tenant; writing it belongs to the install.
24492
+ fixable: false
24493
+ },
24494
+ {
24495
+ slug: "foundry-data-plane",
24496
+ phase: "usage",
24497
+ title: "Your account can reach the Foundry data plane",
24498
+ breaks: "Your AI team will not load, chat does not work, and the CLI and coding-agent plugin cannot reach your workers. Subscription Owner does NOT cover this \u2014 it is a data action, and control-plane roles carry none.",
24499
+ coverage: "auto",
24500
+ checkedBy: "prereqs",
24501
+ fixable: true
24502
+ },
24503
+ {
24504
+ slug: "keyvault-secrets",
24505
+ phase: "usage",
24506
+ title: "Your account can read the platform's Key Vault secrets",
24507
+ breaks: "Deploying a worker fails \u2014 the coding agent and the Azure executor both read the vault as you.",
24508
+ coverage: "auto",
24509
+ checkedBy: "prereqs",
24510
+ fixable: true
24511
+ },
24512
+ {
24513
+ slug: "local-state",
24514
+ phase: "usage",
24515
+ title: "Local state the coding-agent plugin needs (~/.m8t/repo-root, config)",
24516
+ breaks: "The m8t plugin cannot list or reach your workers from an agent session.",
24517
+ coverage: "warns",
24518
+ checkedBy: "doctor",
24519
+ fixable: false
24520
+ }
24521
+ ];
24522
+ function prereqsForPhase(phase) {
24523
+ return PREREQS.filter((p) => p.phase === phase);
24524
+ }
24525
+ function prereqBySlug(slug) {
24526
+ return PREREQS.find((p) => p.slug === slug);
24527
+ }
24528
+ function spec(slug) {
24529
+ const s = prereqBySlug(slug);
24530
+ if (!s) throw new Error(`unknown prereq slug: ${slug}`);
24531
+ return s;
24532
+ }
24533
+ function result(slug, status, detail, remedy) {
24534
+ const s = spec(slug);
24535
+ return { slug: s.slug, phase: s.phase, title: s.title, status, detail, ...remedy ? { remedy } : {} };
24536
+ }
24537
+ var REQUIRED_PROVIDERS = [
24538
+ "Microsoft.ContainerInstance",
24539
+ // the installer job itself
24540
+ "Microsoft.ManagedIdentity",
24541
+ // installer MI + gateway user-assigned identity
24542
+ "Microsoft.CognitiveServices",
24543
+ // Foundry account, project, model deployments
24544
+ "Microsoft.App",
24545
+ // Container Apps environment, gateway, updater job
24546
+ "Microsoft.OperationalInsights",
24547
+ // Log Analytics workspace
24548
+ "Microsoft.Storage",
24549
+ // status blob + platform tables/blobs
24550
+ "Microsoft.KeyVault",
24551
+ // platform secrets
24552
+ "Microsoft.ServiceBus",
24553
+ // gateway queues
24554
+ "Microsoft.Insights",
24555
+ // Application Insights
24556
+ "Microsoft.ContainerRegistry"
24557
+ // hosted-agent image staging
24558
+ ];
24559
+ function evaluateProviders(states) {
24560
+ if (states.length === 0) {
24561
+ return result("resource-providers", "skipped", "no provider registration state could be read \u2014 proceeding unverified");
24562
+ }
24563
+ const unreadable = states.filter((s) => s.state === null).map((s) => s.namespace);
24564
+ const unregistered = states.filter((s) => s.state !== null && s.state.toLowerCase() !== "registered" && s.state.toLowerCase() !== "registering").map((s) => s.namespace);
24565
+ const registering = states.filter((s) => s.state?.toLowerCase() === "registering").map((s) => s.namespace);
24566
+ if (unregistered.length > 0) {
24567
+ return result(
24568
+ "resource-providers",
24569
+ "fail",
24570
+ `not registered on this subscription: ${unregistered.join(", ")}`,
24571
+ unregistered.map((ns) => `az provider register --namespace ${ns}`).join("\n ")
24572
+ );
24573
+ }
24574
+ if (registering.length > 0) {
24575
+ return result("resource-providers", "warn", `still registering: ${registering.join(", ")} \u2014 this resolves on its own, usually within a minute`);
24576
+ }
24577
+ if (unreadable.length > 0) {
24578
+ return result("resource-providers", "skipped", `could not read registration state for: ${unreadable.join(", ")} \u2014 proceeding unverified`);
24579
+ }
24580
+ return result("resource-providers", "pass", `all ${states.length.toString()} required providers registered`);
24581
+ }
24582
+ function evaluateModelQuota(e) {
24583
+ if (!e.usagesReadable) {
24584
+ return result("model-quota", "skipped", `could not read model quota in ${e.region} \u2014 proceeding unverified`);
24585
+ }
24586
+ if (e.verdict === "no_quota") {
24587
+ const alt = e.quotad.length > 0 ? ` Models with quota there: ${e.quotad.join(", ")}.` : "";
24588
+ return result(
24589
+ "model-quota",
24590
+ "fail",
24591
+ `${e.model} has zero quota in ${e.region}.${alt}`,
24592
+ `Request quota for ${e.model} in ${e.region} in the Azure portal (Foundry \u2192 Quotas), or install into a region that already has it.`
24593
+ );
24594
+ }
24595
+ if (e.verdict === "unknown") {
24596
+ return result("model-quota", "warn", `no quota entry for ${e.model} in ${e.region} \u2014 cannot confirm; the install will report honestly if it is missing`);
24597
+ }
24598
+ return result("model-quota", "pass", `${e.model} has quota in ${e.region}`);
24599
+ }
24600
+ function evaluateAzSignedIn(account) {
24601
+ if (!account) return result("az-signed-in", "fail", "not signed in to Azure", "az login");
24602
+ return result("az-signed-in", "pass", `${account.upn} (tenant ${account.tenantId}, subscription ${account.subscriptionId})`);
24603
+ }
24604
+ function evaluateSubscriptionAdmin(probe) {
24605
+ if (!probe.ok) {
24606
+ return result(
24607
+ "subscription-admin",
24608
+ "fail",
24609
+ "you are not Owner or User Access Administrator at subscription scope",
24610
+ "Ask a subscription Owner to run the install \u2014 creating and granting the installer's managed identity is unavoidable."
24611
+ );
24612
+ }
24613
+ return result("subscription-admin", "pass", `${probe.role ?? "Owner/User Access Administrator"} at subscription scope`);
24614
+ }
24615
+ function evaluateAppRegistration(probe, clientId) {
24616
+ if (clientId) return result("app-registration", "pass", `using your app registration ${clientId}`);
24617
+ if (probe.inconclusive) {
24618
+ return result(
24619
+ "app-registration",
24620
+ "fail",
24621
+ "could not verify your directory admin role (Microsoft Graph denied the read \u2014 common for guest accounts)",
24622
+ "Re-run with --client-id <appId> using an app registration an admin prepared for you."
24623
+ );
24624
+ }
24625
+ if (!probe.ok) {
24626
+ return result(
24627
+ "app-registration",
24628
+ "fail",
24629
+ "you do not hold a directory admin role (Application / Cloud Application / Global Administrator)",
24630
+ "Re-run with --client-id <appId> using an app registration an admin prepared for you."
24631
+ );
24632
+ }
24633
+ return result("app-registration", "pass", "you can register the m8t app in this directory");
24634
+ }
24635
+ function grantRemedy(slug, p) {
24636
+ const target = p.isSelf ? "m8t prereqs --fix" : `m8t prereqs --fix --for ${p.principalId ?? "<user>"}`;
24637
+ const role = slug === "foundry-data-plane" ? "Foundry User" : "Key Vault Secrets User";
24638
+ const scope = p.scope ?? `<${slug === "foundry-data-plane" ? "foundry-account" : "key-vault"}-resource-id>`;
24639
+ return `${target}
24640
+ or by hand: az role assignment create --assignee-object-id ${p.principalId ?? "<object-id>"} --assignee-principal-type User --role "${role}" --scope ${scope}`;
24641
+ }
24642
+ function evaluateFoundryAccess(p) {
24643
+ const who = p.isSelf ? "your account" : "that account";
24644
+ if (p.evidence === "unavailable") {
24645
+ return result("foundry-data-plane", "skipped", `could not determine whether ${who} can reach the Foundry data plane`);
24646
+ }
24647
+ if (p.evidence === "probe") {
24648
+ if (p.status !== void 0 && p.status >= 200 && p.status < 300) {
24649
+ return result("foundry-data-plane", "pass", `${who} can reach the Foundry data plane (HTTP ${p.status.toString()})`);
24650
+ }
24651
+ if (p.status === 401 || p.status === 403) {
24652
+ return result(
24653
+ "foundry-data-plane",
24654
+ "fail",
24655
+ `${who} is missing a Foundry data-plane role on ${p.scope ?? "the Foundry account"} (HTTP ${p.status.toString()}). Subscription Owner does not cover this \u2014 reading and invoking agents are data actions, and control-plane roles carry none.`,
24656
+ grantRemedy("foundry-data-plane", p)
24657
+ );
24658
+ }
24659
+ return result("foundry-data-plane", "warn", `unexpected response from the Foundry data plane (HTTP ${(p.status ?? 0).toString()})`);
24660
+ }
24661
+ if (p.granted === true) {
24662
+ return result("foundry-data-plane", "pass", `${who} holds a Foundry data-plane role on ${p.scope ?? "the Foundry account"}`);
24663
+ }
24664
+ return result(
24665
+ "foundry-data-plane",
24666
+ "fail",
24667
+ `${who} holds no known Foundry data-plane role on ${p.scope ?? "the Foundry account"}. (Checked by role assignment, not by calling as them \u2014 a custom role granting the same data action would not be recognised here.)`,
24668
+ grantRemedy("foundry-data-plane", p)
24669
+ );
24670
+ }
24671
+ function evaluateKeyVaultAccess(p) {
24672
+ const who = p.isSelf ? "your account" : "that account";
24673
+ if (p.evidence === "unavailable") {
24674
+ return result("keyvault-secrets", "skipped", `could not determine whether ${who} can read the platform Key Vault`);
24675
+ }
24676
+ if (p.evidence === "probe") {
24677
+ if (p.status !== void 0 && p.status >= 200 && p.status < 300) {
24678
+ return result("keyvault-secrets", "pass", `${who} can read secrets from ${p.scope ?? "the platform Key Vault"}`);
24679
+ }
24680
+ if (p.status === 401 || p.status === 403) {
24681
+ return result(
24682
+ "keyvault-secrets",
24683
+ "fail",
24684
+ `${who} cannot read secrets from ${p.scope ?? "the platform Key Vault"} (HTTP ${p.status.toString()}). Deploying a worker will fail \u2014 this is a permanent permission gap, not a transient blip.`,
24685
+ grantRemedy("keyvault-secrets", p)
24686
+ );
24687
+ }
24688
+ return result("keyvault-secrets", "warn", `unexpected response from the platform Key Vault (HTTP ${(p.status ?? 0).toString()})`);
24689
+ }
24690
+ if (p.granted === true) {
24691
+ return result("keyvault-secrets", "pass", `${who} holds Key Vault Secrets User on ${p.scope ?? "the platform Key Vault"}`);
24692
+ }
24693
+ return result(
24694
+ "keyvault-secrets",
24695
+ "fail",
24696
+ `${who} holds no secrets-read role on ${p.scope ?? "the platform Key Vault"}. (Checked by role assignment, not by calling as them.)`,
24697
+ grantRemedy("keyvault-secrets", p)
24698
+ );
24699
+ }
24700
+ function evaluateRedirectUri(p) {
24701
+ if (!p.gatewayUrl) return result("signin-redirect-uri", "skipped", "no gateway URL resolved");
24702
+ if (p.registeredUris === null) {
24703
+ return result("signin-redirect-uri", "skipped", `could not read app registration ${p.clientId ?? "(unknown)"} \u2014 needs directory rights`);
24704
+ }
24705
+ const want = p.gatewayUrl.replace(/\/$/, "");
24706
+ if (p.registeredUris.some((u) => u.replace(/\/$/, "") === want)) {
24707
+ return result("signin-redirect-uri", "pass", `${want} is registered on the sign-in app`);
24708
+ }
24709
+ return result(
24710
+ "signin-redirect-uri",
24711
+ "fail",
24712
+ `${want} is not a registered redirect URI \u2014 sign-in will fail with AADSTS50011`,
24713
+ // Deliberately NOT "m8t prereqs --fix": this command reports it and does not
24714
+ // write it. The app registration is shared across every deployment in the
24715
+ // tenant, so the write belongs to the install, not to whoever is diagnosing.
24716
+ `Re-run 'm8t bootstrap finish' (it registers this), or ask a directory admin to add it \u2014 MERGE, never overwrite:
24717
+ az ad app update --id ${p.clientId ?? "<clientId>"} --set spa.redirectUris="['${want}']"`
24718
+ );
24719
+ }
24720
+ function evaluatePlatformDiscoverable(p) {
24721
+ if (p.gatewayUrl) return result("platform-discoverable", "pass", p.gatewayUrl);
24722
+ return result(
24723
+ "platform-discoverable",
24724
+ "fail",
24725
+ p.detail,
24726
+ "m8t switch (point this machine at the right tenant/subscription), or pass --resource-group if the subscription holds several deployments"
24727
+ );
24728
+ }
24729
+ function summarize(phase, results) {
24730
+ return {
24731
+ phase,
24732
+ ok: !results.some((r) => r.status === "fail"),
24733
+ results,
24734
+ unchecked: prereqsForPhase(phase).filter((p) => p.checkedBy === "none").map((p) => p.slug)
24735
+ };
24736
+ }
24737
+
24738
+ // src/lib/prereq-run.ts
24739
+ async function detectPhase(deps) {
24740
+ try {
24741
+ return await deps.discoverPlatform() ? "usage" : "install";
24742
+ } catch (e) {
24743
+ if (deps.isNoPlatformError(e)) return "install";
24744
+ throw e;
24745
+ }
24746
+ }
24747
+ async function runInstallPhase(deps, opts) {
24748
+ const results = [];
24749
+ const account = await deps.getAzAccount().catch(() => null);
24750
+ results.push(evaluateAzSignedIn(account));
24751
+ if (!account) return summarize("install", results);
24752
+ const subscriptionId = opts.subscription ?? account.subscriptionId;
24753
+ const oid = await deps.getCallerObjectId().catch(() => null);
24754
+ if (oid) {
24755
+ results.push(evaluateSubscriptionAdmin(await deps.checkSubScopeAdmin(oid, subscriptionId).catch(() => ({ ok: false }))));
24756
+ results.push(
24757
+ opts.clientId ? evaluateAppRegistration({ ok: true, inconclusive: false }, opts.clientId) : evaluateAppRegistration(await deps.checkAppRegCapability(oid).catch(() => ({ ok: false, inconclusive: true })))
24758
+ );
24759
+ }
24760
+ if (opts.fix) {
24761
+ const before = await deps.probeProviders(REQUIRED_PROVIDERS, subscriptionId);
24762
+ const missing = before.filter((s) => s.state !== null && s.state.toLowerCase() !== "registered").map((s) => s.namespace);
24763
+ if (missing.length > 0) {
24764
+ opts.onProgress?.(`registering ${missing.length.toString()} resource provider(s)\u2026`);
24765
+ await deps.registerProviders(missing, { subscriptionId, onProgress: opts.onProgress });
24766
+ }
24767
+ }
24768
+ results.push(evaluateProviders(await deps.probeProviders(REQUIRED_PROVIDERS, subscriptionId)));
24769
+ if (opts.region && opts.model) {
24770
+ results.push(evaluateModelQuota(await deps.probeModelQuota(opts.region, opts.model)));
24771
+ }
24772
+ return summarize("install", results);
24773
+ }
24774
+ async function runUsagePhase(deps, opts) {
24775
+ const results = [];
24776
+ let platform = null;
24777
+ try {
24778
+ platform = await deps.discoverPlatform();
24779
+ } catch (e) {
24780
+ if (!deps.isNoPlatformError(e)) throw e;
24781
+ }
24782
+ results.push(
24783
+ evaluatePlatformDiscoverable({
24784
+ gatewayUrl: platform?.gatewayUrl ?? null,
24785
+ detail: platform ? "" : "no m8t deployment found for this Azure session"
24786
+ })
24787
+ );
24788
+ if (!platform) return summarize("usage", results);
24789
+ const credential2 = deps.credential();
24790
+ const selfOid = await deps.getCallerObjectId().catch(() => null);
24791
+ const isSelf = !opts.forPrincipal;
24792
+ const principalId = isSelf ? selfOid : await deps.resolvePrincipalObjectId(opts.forPrincipal ?? "").catch(() => null);
24793
+ if (!isSelf && !principalId) {
24794
+ throw new UnknownPrincipalError(opts.forPrincipal ?? "");
24795
+ }
24796
+ if (isSelf) {
24797
+ results.push(evaluateRedirectUri(await deps.probeRedirectUri(platform.gatewayClientId, platform.gatewayUrl)));
24798
+ }
24799
+ const accountId = platform.projectEndpoint ? await deps.resolveFoundryAccountId(platform.projectEndpoint).catch(() => null) : null;
24800
+ const kv = await deps.resolvePlatformKeyVault(platform.resourceGroup, platform.subscriptionId).catch(() => null);
24801
+ const foundryProbe = async () => platform.projectEndpoint ? deps.probeFoundryAccess({ credential: credential2, projectEndpoint: platform.projectEndpoint, accountId, principalId, isSelf }) : null;
24802
+ if (!platform.projectEndpoint) {
24803
+ results.push(
24804
+ evaluateFoundryAccess({ evidence: "unavailable", scope: null, principalId, isSelf })
24805
+ );
24806
+ }
24807
+ let foundry = await foundryProbe();
24808
+ if (opts.fix && foundry && needsGrant(foundry) && accountId && principalId) {
24809
+ opts.onProgress?.("granting Foundry data-plane access\u2026");
24810
+ await deps.fixFoundryAccess({ credential: credential2, subscriptionId: platform.subscriptionId, accountId, principalId });
24811
+ results.push({ ...evaluateFoundryAccess(foundry), status: "fixed", detail: "granted Foundry User \u2014 allow up to a minute to take effect" });
24812
+ foundry = null;
24813
+ }
24814
+ if (foundry) results.push(evaluateFoundryAccess(foundry));
24815
+ let keyvault = await deps.probeKeyVaultAccess({
24816
+ credential: credential2,
24817
+ kvUri: kv?.uri ?? null,
24818
+ kvResourceId: kv?.id ?? null,
24819
+ principalId,
24820
+ isSelf
24821
+ });
24822
+ if (opts.fix && needsGrant(keyvault) && kv && principalId) {
24823
+ opts.onProgress?.("granting Key Vault secrets access\u2026");
24824
+ await deps.fixKeyVaultAccess({ credential: credential2, subscriptionId: platform.subscriptionId, kvResourceId: kv.id, principalId });
24825
+ results.push({ ...evaluateKeyVaultAccess(keyvault), status: "fixed", detail: "granted Key Vault Secrets User \u2014 allow up to a minute to take effect" });
24826
+ keyvault = null;
24827
+ }
24828
+ if (keyvault) results.push(evaluateKeyVaultAccess(keyvault));
24829
+ return summarize("usage", results);
24830
+ }
24831
+ var UnknownPrincipalError = class extends Error {
24832
+ constructor(who) {
24833
+ super(
24834
+ `Could not find '${who}' in this directory. Pass the exact user principal name (e.g. alice@contoso.com) or their object id.`
24835
+ );
24836
+ this.who = who;
24837
+ this.name = "UnknownPrincipalError";
24838
+ }
24839
+ who;
24840
+ };
24841
+ function needsGrant(p) {
24842
+ if (p.evidence === "probe") return p.status === 401 || p.status === 403;
24843
+ if (p.evidence === "assignment") return p.granted === false;
24844
+ return false;
24845
+ }
24846
+ async function runPrereqs(deps, opts) {
24847
+ const phase = opts.phase ?? await detectPhase(deps);
24848
+ return phase === "install" ? runInstallPhase(deps, opts) : runUsagePhase(deps, opts);
24849
+ }
24850
+
24851
+ // src/commands/prereqs.ts
24852
+ var GLYPH = {
24853
+ pass: colors.success("PASS"),
24854
+ fixed: colors.success("FIXED"),
24855
+ warn: colors.hint("WARN"),
24856
+ fail: colors.error("FAIL"),
24857
+ skipped: colors.dim("SKIP")
24858
+ };
24859
+ function renderVerdict(v) {
24860
+ const lines = [];
24861
+ lines.push(
24862
+ v.phase === "install" ? colors.field("Checking whether this subscription can install m8t.") : colors.field("Checking whether you can use this m8t platform.")
24863
+ );
24864
+ lines.push("");
24865
+ for (const r of v.results) {
24866
+ lines.push(`[${GLYPH[r.status]}] ${r.title}`);
24867
+ lines.push(` ${colors.dim(r.detail)}`);
24868
+ if (r.remedy) lines.push(colors.hint(` fix: ${r.remedy}`));
24869
+ }
24870
+ if (v.unchecked.length > 0) {
24871
+ lines.push("");
24872
+ lines.push(
24873
+ colors.dim(
24874
+ `Not checked by this command (you may still hit them): ${v.unchecked.join(", ")}. See guides/prerequisites.md.`
24875
+ )
24876
+ );
24877
+ }
24878
+ lines.push("");
24879
+ lines.push(v.ok ? colors.success("\u2705 Nothing is blocking you.") : colors.error("\u26D4 Blocked \u2014 fix the FAIL rows above, then re-run."));
24880
+ return lines.join("\n");
24881
+ }
24882
+ var PrereqsCommand = class extends M8tCommand {
24883
+ static paths = [["prereqs"]];
24884
+ static usage = Command47.Usage({
24885
+ description: "Check (and optionally fix) everything m8t needs \u2014 to install, or for you to use it.",
24886
+ details: "Runs one of two phases. With no platform discoverable it checks INSTALL prerequisites: your Azure sign-in, subscription and directory rights, resource-provider registrations, and model quota. With a platform live it checks USAGE prerequisites for the signed-in account: the sign-in redirect URI, Foundry data-plane access, and Key Vault secrets access. Pass --fix to repair what is repairable, and --fix --for <upn> to set a teammate up (usage phase only). Read-only without --fix.",
24887
+ examples: [
24888
+ ["Before installing", "$0 prereqs"],
24889
+ ["Register anything missing, then install", "$0 prereqs --fix"],
24890
+ ["After joining a platform someone else installed", "$0 prereqs"],
24891
+ ["Grant yourself what you are missing", "$0 prereqs --fix"],
24892
+ ["Set a teammate up (needs role-assignment rights)", "$0 prereqs --fix --for alice@contoso.com"]
24893
+ ]
24894
+ });
24895
+ fix = Option44.Boolean("--fix", false, { description: "Repair what is repairable. Without it the command only reports." });
24896
+ for_ = Option44.String("--for", { description: "UPN or object id of another person. Usage phase only." });
24897
+ phase = Option44.String("--phase", { description: "Force 'install' or 'usage' instead of auto-detecting." });
24898
+ region = Option44.String("--region", { description: "Target region for the install-phase quota check." });
24899
+ model = Option44.String("--model", { description: `Model whose quota the install needs (default ${INSTALL_REASONING_MODEL}).` });
24900
+ clientId = Option44.String("--client-id", { description: "A ready app registration, in place of directory-admin rights." });
24901
+ subscription = Option44.String("--subscription");
24902
+ resourceGroup = Option44.String("--resource-group");
24903
+ output = Option44.String("--output");
24904
+ async executeCommand() {
24905
+ const mode = resolveOutputMode(this.output, this.context.stdout);
24906
+ const json = mode === "json";
24907
+ const phase = this.resolvePhase();
24908
+ const forPrincipal = typeof this.for_ === "string" ? this.for_ : void 0;
24909
+ if (forPrincipal && phase === "install") {
24910
+ throw new LocalCliError({
24911
+ code: "PREREQS_FOR_INSTALL_PHASE",
24912
+ message: "--for applies to the usage phase only.",
24913
+ hint: "Install prerequisites belong to the subscription and the person running the install, not to a named teammate. Run 'm8t prereqs --fix --for <upn>' once the platform is live."
24914
+ });
24915
+ }
24916
+ if (forPrincipal && !this.fix) {
24917
+ this.context.stderr.write(
24918
+ colors.dim(" (checking by role assignment \u2014 we cannot call the data plane as someone else)\n")
24919
+ );
24920
+ }
24921
+ const deps = buildPrereqDeps({
24922
+ resourceGroup: typeof this.resourceGroup === "string" ? this.resourceGroup : void 0,
24923
+ subscription: typeof this.subscription === "string" ? this.subscription : void 0
24924
+ });
24925
+ const opts = {
24926
+ fix: this.fix === true,
24927
+ ...forPrincipal ? { forPrincipal } : {},
24928
+ ...phase ? { phase } : {},
24929
+ region: typeof this.region === "string" ? this.region : void 0,
24930
+ model: typeof this.model === "string" ? this.model : INSTALL_REASONING_MODEL,
24931
+ clientId: typeof this.clientId === "string" ? this.clientId : void 0,
24932
+ subscription: typeof this.subscription === "string" ? this.subscription : void 0,
24933
+ resourceGroup: typeof this.resourceGroup === "string" ? this.resourceGroup : void 0,
24934
+ onProgress: json ? void 0 : (m) => this.context.stderr.write(colors.dim(` ${m}
24935
+ `))
24936
+ };
24937
+ const verdict = await runPrereqs(deps, opts);
24938
+ if (json) {
24939
+ this.context.stdout.write(renderJson(verdict) + "\n");
24940
+ } else {
24941
+ this.context.stdout.write(renderVerdict(verdict) + "\n");
24942
+ }
24943
+ return verdict.ok ? 0 : 1;
24944
+ }
24945
+ resolvePhase() {
24946
+ if (typeof this.phase !== "string") return void 0;
24947
+ if (this.phase !== "install" && this.phase !== "usage") {
24948
+ throw new LocalCliError({
24949
+ code: "PREREQS_BAD_PHASE",
24950
+ message: `Unknown phase '${this.phase}'.`,
24951
+ hint: `Valid phases: install, usage. Omit --phase to auto-detect (install prerequisites are ${prereqsForPhase("install").length.toString()} checks; usage are ${prereqsForPhase("usage").length.toString()}).`
24952
+ });
24953
+ }
24954
+ return this.phase;
24955
+ }
24956
+ };
24957
+
24958
+ // src/commands/switch.ts
24959
+ import { Command as Command48, Option as Option45 } from "clipanion";
24012
24960
 
24013
24961
  // src/lib/profiles.ts
24014
24962
  import * as fs27 from "fs/promises";
@@ -24148,14 +25096,14 @@ async function profileSwitch(name, asName) {
24148
25096
  init_errors();
24149
25097
  var SwitchCommand = class extends M8tCommand {
24150
25098
  static paths = [["switch"]];
24151
- static usage = Command47.Usage({
25099
+ static usage = Command48.Usage({
24152
25100
  description: "Re-point local config at another deployment: --subscription <id|name> (discovery) or <profile>."
24153
25101
  });
24154
- profile = Option44.String({ required: false });
24155
- subscription = Option44.String("--subscription");
24156
- list = Option44.Boolean("--list", false);
24157
- as = Option44.String("--as");
24158
- output = Option44.String("--output");
25102
+ profile = Option45.String({ required: false });
25103
+ subscription = Option45.String("--subscription");
25104
+ list = Option45.Boolean("--list", false);
25105
+ as = Option45.String("--as");
25106
+ output = Option45.String("--output");
24159
25107
  async executeCommand() {
24160
25108
  const mode = resolveOutputMode(
24161
25109
  this.output,
@@ -24178,11 +25126,11 @@ var SwitchCommand = class extends M8tCommand {
24178
25126
  }
24179
25127
  return 0;
24180
25128
  }
24181
- let result;
25129
+ let result2;
24182
25130
  if (this.subscription) {
24183
- result = await discoverySwitch(this.subscription, this.as);
25131
+ result2 = await discoverySwitch(this.subscription, this.as);
24184
25132
  } else if (this.profile) {
24185
- result = await profileSwitch(this.profile, this.as);
25133
+ result2 = await profileSwitch(this.profile, this.as);
24186
25134
  } else {
24187
25135
  throw new LocalCliError({
24188
25136
  code: "SWITCH_NO_TARGET",
@@ -24191,16 +25139,16 @@ var SwitchCommand = class extends M8tCommand {
24191
25139
  });
24192
25140
  }
24193
25141
  if (mode === "json") {
24194
- this.context.stdout.write(renderJson(result) + "\n");
25142
+ this.context.stdout.write(renderJson(result2) + "\n");
24195
25143
  return 0;
24196
25144
  }
24197
25145
  this.context.stdout.write(
24198
25146
  renderKeyValueBlock([
24199
- { key: "tenant", value: result.tenantId },
24200
- { key: "clientId", value: result.clientId },
24201
- { key: "endpoint", value: result.projectEndpoint },
24202
- { key: "subscription", value: result.subscriptionId },
24203
- { key: "saved profile", value: result.snapshot ?? colors.dim("(nothing to snapshot)") }
25147
+ { key: "tenant", value: result2.tenantId },
25148
+ { key: "clientId", value: result2.clientId },
25149
+ { key: "endpoint", value: result2.projectEndpoint },
25150
+ { key: "subscription", value: result2.subscriptionId },
25151
+ { key: "saved profile", value: result2.snapshot ?? colors.dim("(nothing to snapshot)") }
24204
25152
  ]) + "\n"
24205
25153
  );
24206
25154
  this.context.stdout.write(
@@ -24212,7 +25160,7 @@ var SwitchCommand = class extends M8tCommand {
24212
25160
 
24213
25161
  // src/commands/open.ts
24214
25162
  import { spawn as spawn5 } from "child_process";
24215
- import { Command as Command48, Option as Option45 } from "clipanion";
25163
+ import { Command as Command49, Option as Option46 } from "clipanion";
24216
25164
 
24217
25165
  // src/lib/open-targets.ts
24218
25166
  init_errors();
@@ -24258,14 +25206,14 @@ function openUrl(url) {
24258
25206
  }
24259
25207
  var OpenCommand = class extends M8tCommand {
24260
25208
  static paths = [["open"]];
24261
- static usage = Command48.Usage({
25209
+ static usage = Command49.Usage({
24262
25210
  description: "Open the deployed webapp (default), the Foundry portal, or the resource group.",
24263
25211
  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)."
24264
25212
  });
24265
- target = Option45.String({ required: false });
24266
- print = Option45.Boolean("--print", false);
24267
- output = Option45.String("--output");
24268
- resourceGroup = Option45.String("--resource-group", {
25213
+ target = Option46.String({ required: false });
25214
+ print = Option46.Boolean("--print", false);
25215
+ output = Option46.String("--output");
25216
+ resourceGroup = Option46.String("--resource-group", {
24269
25217
  description: "m8t resource group to disambiguate the gateway (multi-deployment subscriptions)."
24270
25218
  });
24271
25219
  async executeCommand() {
@@ -24309,7 +25257,7 @@ var OpenCommand = class extends M8tCommand {
24309
25257
  };
24310
25258
 
24311
25259
  // src/commands/dream/run.ts
24312
- import { Command as Command49, Option as Option46 } from "clipanion";
25260
+ import { Command as Command50, Option as Option47 } from "clipanion";
24313
25261
  import { AzureCliCredential } from "@azure/identity";
24314
25262
  import { TableClient as TableClient7 } from "@azure/data-tables";
24315
25263
  import { AIProjectClient as AIProjectClient3 } from "@azure/ai-projects";
@@ -24514,11 +25462,11 @@ function extractQuery(args) {
24514
25462
  return null;
24515
25463
  }
24516
25464
  }
24517
- function extractSources(result) {
25465
+ function extractSources(result2) {
24518
25466
  const out = [];
24519
25467
  const re = /\[\d+\]\s*([^\n[]+)/g;
24520
25468
  let m;
24521
- while ((m = re.exec(result)) !== null) {
25469
+ while ((m = re.exec(result2)) !== null) {
24522
25470
  const t = m[1].trim();
24523
25471
  if (t)
24524
25472
  out.push(t);
@@ -24589,10 +25537,10 @@ async function fetchEnrichment(credential2, workspaceId, respIds, from, to) {
24589
25537
  if (ids.length === 0)
24590
25538
  return /* @__PURE__ */ new Map();
24591
25539
  const client = new LogsQueryClient(credential2);
24592
- const result = await client.queryWorkspace(workspaceId, buildEnrichKql(ids), { startTime: new Date(from), endTime: new Date(to) });
24593
- if (result.status !== LogsQueryResultStatus.Success || result.tables.length === 0)
25540
+ const result2 = await client.queryWorkspace(workspaceId, buildEnrichKql(ids), { startTime: new Date(from), endTime: new Date(to) });
25541
+ if (result2.status !== LogsQueryResultStatus.Success || result2.tables.length === 0)
24594
25542
  return /* @__PURE__ */ new Map();
24595
- return parseEnrichment(tableToSpans(result.tables[0]));
25543
+ return parseEnrichment(tableToSpans(result2.tables[0]));
24596
25544
  }
24597
25545
 
24598
25546
  // ../../packages/agent-ledger/dist/esm/read/brain-reads-client.js
@@ -26352,7 +27300,7 @@ async function runDream(input, deps, seams = defaultDreamSeams()) {
26352
27300
  return { worker: input.worker, applied: [], digest, advanced: true };
26353
27301
  }
26354
27302
  const message = applied.digest.length ? `dream: ${input.worker} consolidation` : `dream: ${input.worker}`;
26355
- const result = await commitWithRebase({
27303
+ const result2 = await commitWithRebase({
26356
27304
  writer: deps.brain,
26357
27305
  message,
26358
27306
  changes: applied.changes,
@@ -26360,13 +27308,13 @@ async function runDream(input, deps, seams = defaultDreamSeams()) {
26360
27308
  maxAttempts: MAX_WRITE_ATTEMPTS,
26361
27309
  verify: async () => verifyEndState(deps.brain, applied.changes)
26362
27310
  });
26363
- if (result.outcome !== "committed") {
26364
- deps.logger.error({ at: "runDream", worker: input.worker, terminal: result.outcome, attempts: result.attempts });
26365
- digest.push({ kind: "failure", detail: `write ${result.outcome} after ${String(result.attempts)} attempt(s)`, evidence: [] });
27311
+ if (result2.outcome !== "committed") {
27312
+ deps.logger.error({ at: "runDream", worker: input.worker, terminal: result2.outcome, attempts: result2.attempts });
27313
+ digest.push({ kind: "failure", detail: `write ${result2.outcome} after ${String(result2.attempts)} attempt(s)`, evidence: [] });
26366
27314
  return { worker: input.worker, applied: [], digest, advanced: false };
26367
27315
  }
26368
27316
  await commitCursorAdvance(input.advance, deps.cursor);
26369
- deps.logger.info({ at: "runDream", worker: input.worker, committed: result.newSha, advanced: true });
27317
+ deps.logger.info({ at: "runDream", worker: input.worker, committed: result2.newSha, advanced: true });
26370
27318
  await emitDigest(input, deps, digest);
26371
27319
  const appliedChanges = applied.digest.filter((e) => e.kind === "changed");
26372
27320
  return { worker: input.worker, applied: appliedChanges, digest, advanced: true };
@@ -26536,20 +27484,20 @@ function redactTranscripts(input) {
26536
27484
  }
26537
27485
  var DreamRunCommand = class extends M8tCommand {
26538
27486
  static paths = [["dream", "run"]];
26539
- static usage = Command49.Usage({
27487
+ static usage = Command50.Usage({
26540
27488
  description: "Dry-run the brain consumption pipeline for one worker (no model call, no writes).",
26541
27489
  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."
26542
27490
  });
26543
- worker = Option46.String("--worker", { description: "Worker (canonical name) to harvest. Required." });
26544
- dryRun = Option46.Boolean("--dry-run", false, { description: "Read-only harvest; no model call, no writes." });
26545
- since = Option46.String("--since", { description: "ISO-8601 start override (rejected if malformed or future)." });
26546
- reset = Option46.Boolean("--reset", false, { description: "Ignore the stored cursor; read from the beginning." });
26547
- showTranscripts = Option46.Boolean("--show-transcripts", false, {
27491
+ worker = Option47.String("--worker", { description: "Worker (canonical name) to harvest. Required." });
27492
+ dryRun = Option47.Boolean("--dry-run", false, { description: "Read-only harvest; no model call, no writes." });
27493
+ since = Option47.String("--since", { description: "ISO-8601 start override (rejected if malformed or future)." });
27494
+ reset = Option47.Boolean("--reset", false, { description: "Ignore the stored cursor; read from the beginning." });
27495
+ showTranscripts = Option47.Boolean("--show-transcripts", false, {
26548
27496
  description: "Print transcript bodies (default: metadata only)."
26549
27497
  });
26550
- subscription = Option46.String("--subscription");
26551
- endpoint = Option46.String("--endpoint");
26552
- output = Option46.String("--output");
27498
+ subscription = Option47.String("--subscription");
27499
+ endpoint = Option47.String("--endpoint");
27500
+ output = Option47.String("--output");
26553
27501
  // Resolution seam — overridden by tests; built lazily at runtime otherwise.
26554
27502
  deps;
26555
27503
  async executeCommand() {
@@ -26569,7 +27517,7 @@ var DreamRunCommand = class extends M8tCommand {
26569
27517
  endpoint: typeof this.endpoint === "string" ? this.endpoint : void 0
26570
27518
  });
26571
27519
  const liveCursor = await liveDeps.readCursor(liveContext.worker);
26572
- const result = await liveDeps.runDream({
27520
+ const result2 = await liveDeps.runDream({
26573
27521
  worker: liveContext.worker,
26574
27522
  physicalPks: liveContext.physicalPks,
26575
27523
  since,
@@ -26579,10 +27527,10 @@ var DreamRunCommand = class extends M8tCommand {
26579
27527
  ...buildSources(liveContext, liveDeps)
26580
27528
  });
26581
27529
  this.context.stdout.write(
26582
- `${colors.field("dream run (live)")} worker=${result.worker} applied ${String(result.applied.length)} ${result.advanced ? colors.success("advanced") : colors.dim("not advanced")}
27530
+ `${colors.field("dream run (live)")} worker=${result2.worker} applied ${String(result2.applied.length)} ${result2.advanced ? colors.success("advanced") : colors.dim("not advanced")}
26583
27531
  `
26584
27532
  );
26585
- const changed = result.applied.filter(
27533
+ const changed = result2.applied.filter(
26586
27534
  (e) => e.kind === "changed"
26587
27535
  );
26588
27536
  for (const e of changed) {
@@ -26591,7 +27539,7 @@ var DreamRunCommand = class extends M8tCommand {
26591
27539
  `
26592
27540
  );
26593
27541
  }
26594
- const cost = result.digest.find(
27542
+ const cost = result2.digest.find(
26595
27543
  (d) => d.kind === "cost"
26596
27544
  );
26597
27545
  if (cost) {
@@ -26600,7 +27548,7 @@ var DreamRunCommand = class extends M8tCommand {
26600
27548
  `
26601
27549
  );
26602
27550
  }
26603
- const failures = result.digest.filter(
27551
+ const failures = result2.digest.filter(
26604
27552
  (d) => d.kind === "failure"
26605
27553
  );
26606
27554
  for (const f of failures) {
@@ -26729,12 +27677,12 @@ AppDependencies
26729
27677
  | where isnotempty(conv)
26730
27678
  | project conv
26731
27679
  | take 1`;
26732
- const result = await client.queryWorkspace(workspaceId, kql, {
27680
+ const result2 = await client.queryWorkspace(workspaceId, kql, {
26733
27681
  startTime: new Date(from),
26734
27682
  endTime: new Date(to)
26735
27683
  });
26736
- if (result.status !== LogsQueryResultStatus3.Success || result.tables.length === 0) return null;
26737
- const table = result.tables[0];
27684
+ if (result2.status !== LogsQueryResultStatus3.Success || result2.tables.length === 0) return null;
27685
+ const table = result2.tables[0];
26738
27686
  if (table.rows.length === 0) return null;
26739
27687
  const row = table.rows[0];
26740
27688
  const idx = table.columnDescriptors.findIndex((c) => c.name === "conv");
@@ -26897,7 +27845,7 @@ function defaultDeps(overrides) {
26897
27845
  }
26898
27846
 
26899
27847
  // src/commands/foundry/create.ts
26900
- import { Command as Command50, Option as Option47 } from "clipanion";
27848
+ import { Command as Command51, Option as Option48 } from "clipanion";
26901
27849
 
26902
27850
  // src/lib/foundry-create.ts
26903
27851
  init_errors();
@@ -27135,7 +28083,7 @@ async function createFoundryProject(args) {
27135
28083
  init_errors();
27136
28084
  var FoundryCreateCommand = class extends M8tCommand {
27137
28085
  static paths = [["foundry", "create"]];
27138
- static usage = Command50.Usage({
28086
+ static usage = Command51.Usage({
27139
28087
  description: "Create an AI Foundry (AIServices) account + project + model deployment from scratch.",
27140
28088
  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).",
27141
28089
  examples: [
@@ -27144,16 +28092,16 @@ var FoundryCreateCommand = class extends M8tCommand {
27144
28092
  ["Higher capacity for a reasoning model", "$0 foundry create --resource-group rg-m8t-stack --location eastus2 --model gpt-5-mini --capacity 250"]
27145
28093
  ]
27146
28094
  });
27147
- resourceGroup = Option47.String("--resource-group");
27148
- location = Option47.String("--location");
27149
- account = Option47.String("--account");
27150
- project = Option47.String("--project", "m8t");
27151
- model = Option47.String("--model", "gpt-4.1-mini");
27152
- modelVersion = Option47.String("--model-version", "2025-04-14");
27153
- capacity = Option47.String("--capacity", "50");
27154
- subscription = Option47.String("--subscription");
27155
- skipQuotaCheck = Option47.Boolean("--skip-quota-check", false);
27156
- output = Option47.String("--output");
28095
+ resourceGroup = Option48.String("--resource-group");
28096
+ location = Option48.String("--location");
28097
+ account = Option48.String("--account");
28098
+ project = Option48.String("--project", "m8t");
28099
+ model = Option48.String("--model", "gpt-4.1-mini");
28100
+ modelVersion = Option48.String("--model-version", "2025-04-14");
28101
+ capacity = Option48.String("--capacity", "50");
28102
+ subscription = Option48.String("--subscription");
28103
+ skipQuotaCheck = Option48.Boolean("--skip-quota-check", false);
28104
+ output = Option48.String("--output");
27157
28105
  async executeCommand() {
27158
28106
  const mode = resolveOutputMode(
27159
28107
  this.output,
@@ -27178,7 +28126,7 @@ var FoundryCreateCommand = class extends M8tCommand {
27178
28126
  const capacity = typeof this.capacity === "string" ? Number(this.capacity) : 50;
27179
28127
  const onProgress = mode === "pretty" ? (m) => this.context.stderr.write(` ${colors.dim(m)}
27180
28128
  `) : void 0;
27181
- const result = await createFoundryProject({
28129
+ const result2 = await createFoundryProject({
27182
28130
  resourceGroup,
27183
28131
  location,
27184
28132
  account: accountName,
@@ -27194,53 +28142,53 @@ var FoundryCreateCommand = class extends M8tCommand {
27194
28142
  if (mode === "json") {
27195
28143
  this.context.stdout.write(
27196
28144
  renderJson({
27197
- endpoint: result.endpoint,
27198
- accountName: result.accountName,
27199
- accountResourceId: result.accountScope,
27200
- projectName: result.projectName,
27201
- region: result.region,
27202
- model: result.model,
27203
- capacity: result.capacity,
27204
- created: result.created
28145
+ endpoint: result2.endpoint,
28146
+ accountName: result2.accountName,
28147
+ accountResourceId: result2.accountScope,
28148
+ projectName: result2.projectName,
28149
+ region: result2.region,
28150
+ model: result2.model,
28151
+ capacity: result2.capacity,
28152
+ created: result2.created
27205
28153
  }) + "\n"
27206
28154
  );
27207
28155
  return 0;
27208
28156
  }
27209
28157
  this.context.stdout.write(
27210
28158
  renderKeyValueBlock([
27211
- { key: "endpoint", value: result.endpoint },
27212
- { key: "account", value: result.accountName },
27213
- { key: "project", value: result.projectName },
27214
- { key: "region", value: result.region },
27215
- { key: "model", value: `${result.model} (capacity ${String(result.capacity)})` }
28159
+ { key: "endpoint", value: result2.endpoint },
28160
+ { key: "account", value: result2.accountName },
28161
+ { key: "project", value: result2.projectName },
28162
+ { key: "region", value: result2.region },
28163
+ { key: "model", value: `${result2.model} (capacity ${String(result2.capacity)})` }
27216
28164
  ]) + "\n"
27217
28165
  );
27218
- const made = Object.entries(result.created).filter(([, v]) => v).map(([k]) => k);
28166
+ const made = Object.entries(result2.created).filter(([, v]) => v).map(([k]) => k);
27219
28167
  this.context.stdout.write(colors.dim(`created: ${made.length ? made.join(", ") : "nothing (all present)"}
27220
28168
  `));
27221
- this.context.stdout.write(colors.dim(`next: 'm8t deploy --foundry-endpoint ${result.endpoint}' to deploy the gateway.
28169
+ this.context.stdout.write(colors.dim(`next: 'm8t deploy --foundry-endpoint ${result2.endpoint}' to deploy the gateway.
27222
28170
  `));
27223
28171
  return 0;
27224
28172
  }
27225
28173
  };
27226
28174
 
27227
28175
  // src/commands/foundry/await-ready.ts
27228
- import { Command as Command51, Option as Option48 } from "clipanion";
28176
+ import { Command as Command52, Option as Option49 } from "clipanion";
27229
28177
  import { AzureCliCredential as AzureCliCredential2 } from "@azure/identity";
27230
28178
  init_errors();
27231
28179
  var FoundryAwaitReadyCommand = class extends M8tCommand {
27232
28180
  static paths = [["foundry", "await-ready"]];
27233
- static usage = Command51.Usage({
28181
+ static usage = Command52.Usage({
27234
28182
  description: "Wait until a freshly-created Foundry project's data plane reliably serves it.",
27235
28183
  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.",
27236
28184
  examples: [["Wait for a project to be ready", "$0 foundry await-ready --endpoint https://acc.services.ai.azure.com/api/projects/m8t"]]
27237
28185
  });
27238
- endpoint = Option48.String("--endpoint");
27239
- consecutive = Option48.String("--consecutive", "3");
27240
- attempts = Option48.String("--attempts", "60");
27241
- interval = Option48.String("--interval", "5");
27242
- subscription = Option48.String("--subscription");
27243
- output = Option48.String("--output");
28186
+ endpoint = Option49.String("--endpoint");
28187
+ consecutive = Option49.String("--consecutive", "3");
28188
+ attempts = Option49.String("--attempts", "60");
28189
+ interval = Option49.String("--interval", "5");
28190
+ subscription = Option49.String("--subscription");
28191
+ output = Option49.String("--output");
27244
28192
  async executeCommand() {
27245
28193
  const mode = resolveOutputMode(this.output, this.context.stdout);
27246
28194
  const endpoint = typeof this.endpoint === "string" ? this.endpoint : void 0;
@@ -27274,7 +28222,7 @@ var FoundryAwaitReadyCommand = class extends M8tCommand {
27274
28222
  };
27275
28223
 
27276
28224
  // src/commands/bootstrap/preflight.ts
27277
- import { Command as Command52, Option as Option49 } from "clipanion";
28225
+ import { Command as Command53, Option as Option50 } from "clipanion";
27278
28226
 
27279
28227
  // ../../packages/telemetry-contract/artifact/tier-map.ts
27280
28228
  var EVENT_TIERS = {
@@ -27285,7 +28233,8 @@ var EVENT_TIERS = {
27285
28233
  "heartbeat": 1,
27286
28234
  "update-applied": 1,
27287
28235
  "update-failed": 1,
27288
- "consent-changed": 1
28236
+ "consent-changed": 1,
28237
+ "identity-mismatch": 1
27289
28238
  // extended additively for tier-2 usage events: "usage-rollup": 2
27290
28239
  };
27291
28240
  var EVENT_ENUM = Object.keys(EVENT_TIERS);
@@ -27318,87 +28267,23 @@ var SPEND_DISCLOSURE = [
27318
28267
  " - You can remove it later - see guides/uninstall.md."
27319
28268
  ].join("\n");
27320
28269
 
27321
- // src/lib/bootstrap-preflight.ts
27322
- var ADMIN_DIRECTORY_ROLES = ["Global Administrator", "Application Administrator", "Cloud Application Administrator"];
27323
- async function checkSubScopeAdmin(callerObjectId, subscriptionId) {
27324
- const rows = JSON.parse(await runAz([
27325
- "role",
27326
- "assignment",
27327
- "list",
27328
- "--assignee",
27329
- callerObjectId,
27330
- "--scope",
27331
- `/subscriptions/${subscriptionId}`,
27332
- "--include-inherited",
27333
- "--query",
27334
- "[].{roleDefinitionName: roleDefinitionName}",
27335
- "-o",
27336
- "json"
27337
- ]));
27338
- const hit = rows.find((r) => r.roleDefinitionName === "Owner" || r.roleDefinitionName === "User Access Administrator");
27339
- return { ok: Boolean(hit), role: hit?.roleDefinitionName };
27340
- }
27341
- async function checkAppRegCapability(callerObjectId) {
27342
- let roles;
27343
- try {
27344
- roles = JSON.parse(await runAz([
27345
- "rest",
27346
- "--method",
27347
- "GET",
27348
- "--url",
27349
- `https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments?$filter=principalId eq '${callerObjectId}'&$expand=roleDefinition`,
27350
- "--query",
27351
- "value[].roleDefinition.displayName",
27352
- "-o",
27353
- "json"
27354
- ]));
27355
- } catch {
27356
- roles = null;
27357
- }
27358
- if (roles === null) return { ok: false, inconclusive: true };
27359
- return { ok: roles.some((r) => ADMIN_DIRECTORY_ROLES.includes(r)), inconclusive: false };
27360
- }
27361
- async function registerContainerInstance(subscriptionId) {
27362
- await runAz(["provider", "register", "--namespace", "Microsoft.ContainerInstance", "--subscription", subscriptionId, "--only-show-errors"]);
27363
- }
27364
- function buildPreflightBanner(who) {
27365
- return [
27366
- "\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557",
27367
- "\u2551 \u26D4 STOP \u2014 READ THIS BEFORE CONTINUING \u2551",
27368
- "\u2551 \u2551",
27369
- "\u2551 Installing m8t creates real Azure resources in YOUR \u2551",
27370
- "\u2551 subscription and assigns roles. To do that, the account you just \u2551",
27371
- "\u2551 signed in with MUST be: \u2551",
27372
- "\u2551 \u2551",
27373
- "\u2551 \u2022 OWNER or USER ACCESS ADMINISTRATOR at the subscription scope \u2551",
27374
- "\u2551 (it has to create a managed identity and assign it roles), AND \u2551",
27375
- "\u2551 \u2022 a directory admin able to register one Entra app \u2551",
27376
- "\u2551 (Application / Cloud Application / Global Administrator) \u2551",
27377
- "\u2551 \u2014 OR you supply a ready app registration with --client-id. \u2551",
27378
- "\u2551 \u2551",
27379
- "\u2551 If you are NOT this, this install CANNOT proceed and will STOP NOW. \u2551",
27380
- "\u2551 Nothing has been created yet. Get an admin to run this, or ask one \u2551",
27381
- "\u2551 for an app registration id (--client-id <appId>) and try again. \u2551",
27382
- "\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D",
27383
- "",
27384
- `Signed in as: ${who.upn} (tenant ${who.tenantId}, sub ${who.subscriptionId})`,
27385
- ""
27386
- ].join("\n");
27387
- }
27388
-
27389
28270
  // src/commands/bootstrap/preflight.ts
27390
28271
  var BootstrapPreflightCommand = class extends M8tCommand {
27391
28272
  static paths = [["bootstrap", "preflight"]];
27392
- static usage = Command52.Usage({
28273
+ static usage = Command53.Usage({
27393
28274
  description: "Loudly verify you can install m8t (Owner/UAA + directory admin) and hard-stop if not.",
27394
- 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.",
28275
+ 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), every Azure resource provider the install uses (registering any that are missing), and \u2014 with --location \u2014 model quota in the target region. Exits non-zero with the exact failing check + remedy. `m8t prereqs` runs the same substrate checks on their own.",
27395
28276
  examples: [
27396
28277
  ["Run the preflight", "$0 bootstrap preflight"],
28278
+ ["Check quota for the region you will install into", "$0 bootstrap preflight --location eastus2"],
27397
28279
  ["BYO app registration (directory guests)", "$0 bootstrap preflight --client-id <appId>"]
27398
28280
  ]
27399
28281
  });
27400
- clientId = Option49.String("--client-id");
27401
- subscription = Option49.String("--subscription");
28282
+ clientId = Option50.String("--client-id");
28283
+ subscription = Option50.String("--subscription");
28284
+ location = Option50.String("--location", {
28285
+ description: "Target region. Enables the model-quota check \u2014 without it, quota is not verified."
28286
+ });
27402
28287
  async executeCommand() {
27403
28288
  const clientId = typeof this.clientId === "string" ? this.clientId : void 0;
27404
28289
  const account = await getAzAccount();
@@ -27429,8 +28314,33 @@ ${colors.dim(DISCLOSURE_TIER1)}
27429
28314
  } else {
27430
28315
  this.context.stdout.write(line(true, `Using your app registration ${clientId}`, "--client-id supplied; skipping the directory-admin check"));
27431
28316
  }
27432
- await registerContainerInstance(subscriptionId);
27433
- this.context.stdout.write(line(true, "Microsoft.ContainerInstance registered", "installer substrate"));
28317
+ const region = typeof this.location === "string" ? this.location : void 0;
28318
+ const verdict = await runInstallPhase(buildPrereqDeps({ subscription: subscriptionId }), {
28319
+ fix: true,
28320
+ subscription: subscriptionId,
28321
+ // BOTH region and model are required for the quota check to run at all.
28322
+ // Passing only the region silently skipped it while --location implied it
28323
+ // had happened.
28324
+ ...region ? { region, model: INSTALL_REASONING_MODEL } : {},
28325
+ ...clientId ? { clientId } : {},
28326
+ onProgress: (m) => {
28327
+ this.context.stderr.write(colors.dim(` ${m}
28328
+ `));
28329
+ }
28330
+ });
28331
+ for (const r of verdict.results.filter((x) => x.slug === "resource-providers" || x.slug === "model-quota")) {
28332
+ this.context.stdout.write(line(r.status !== "fail", r.title, r.detail));
28333
+ if (r.status === "fail") {
28334
+ this.context.stderr.write(failBlock(r.detail, r.remedy ?? "See guides/prerequisites.md."));
28335
+ throw new LocalCliError({ code: `BOOTSTRAP_PREREQ_${r.slug.toUpperCase().replace(/-/g, "_")}`, message: r.detail, hint: r.remedy });
28336
+ }
28337
+ }
28338
+ if (verdict.unchecked.length > 0) {
28339
+ this.context.stdout.write(
28340
+ colors.dim(` Not checked (you may still hit them): ${verdict.unchecked.join(", ")} \u2014 see guides/prerequisites.md
28341
+ `)
28342
+ );
28343
+ }
27434
28344
  this.context.stdout.write(
27435
28345
  `
27436
28346
  ${colors.success("\u2705 Preflight passed.")} Here is the ONE consent step you'll clear, then walk away:
@@ -27461,7 +28371,7 @@ ${colors.error(" " + why)}
27461
28371
  import * as fs30 from "fs";
27462
28372
  import * as os14 from "os";
27463
28373
  import * as path32 from "path";
27464
- import { Command as Command53, Option as Option50 } from "clipanion";
28374
+ import { Command as Command54, Option as Option51 } from "clipanion";
27465
28375
  init_errors();
27466
28376
 
27467
28377
  // src/lib/bootstrap-mi.ts
@@ -27830,7 +28740,7 @@ var ACI_NAME = "m8t-installer";
27830
28740
  var MI_NAME = "m8t-installer-mi";
27831
28741
  var BootstrapLaunchCommand = class extends M8tCommand {
27832
28742
  static paths = [["bootstrap", "launch"]];
27833
- static usage = Command53.Usage({
28743
+ static usage = Command54.Usage({
27834
28744
  description: "Create + authorize the installer managed identity, then kick the cloud installer.",
27835
28745
  details: "Step 2 of `m8t bootstrap` (run after `preflight`). Ensures the resource group, creates the m8t 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/bootstrap.json for `status`/`reap`/`finish`.",
27836
28746
  examples: [
@@ -27841,26 +28751,26 @@ var BootstrapLaunchCommand = class extends M8tCommand {
27841
28751
  ["Share a support contact", "$0 bootstrap launch --location eastus2 --contact-email you@example.com --company 'Acme'"]
27842
28752
  ]
27843
28753
  });
27844
- location = Option50.String("--location");
27845
- resourceGroup = Option50.String("--resource-group");
27846
- clientId = Option50.String("--client-id");
27847
- subscription = Option50.String("--subscription");
27848
- installerTag = Option50.String("--installer-tag");
28754
+ location = Option51.String("--location");
28755
+ resourceGroup = Option51.String("--resource-group");
28756
+ clientId = Option51.String("--client-id");
28757
+ subscription = Option51.String("--subscription");
28758
+ installerTag = Option51.String("--installer-tag");
27849
28759
  // Full image ref override (registry + repo + tag) — an escape hatch when the
27850
28760
  // default org/tag is wrong for the current CLI (e.g. a stale published build).
27851
28761
  // Wins over --installer-tag / the pinned default.
27852
- installerImage = Option50.String("--installer-image");
27853
- gatewayImageRef = Option50.String("--gateway-image-ref");
27854
- githubAppCreds = Option50.String("--github-app-creds");
27855
- contactEmail = Option50.String("--contact-email", { description: "Share a contact email with m8t support. Not sent unless you pass it." });
27856
- company = Option50.String("--company", { description: "Share your company name with m8t support. Not sent unless you pass it." });
28762
+ installerImage = Option51.String("--installer-image");
28763
+ gatewayImageRef = Option51.String("--gateway-image-ref");
28764
+ githubAppCreds = Option51.String("--github-app-creds");
28765
+ contactEmail = Option51.String("--contact-email", { description: "Share a contact email with m8t support. Not sent unless you pass it." });
28766
+ company = Option51.String("--company", { description: "Share your company name with m8t support. Not sent unless you pass it." });
27857
28767
  // Value-carrying on purpose: a bare --force would be cargo-culted into
27858
28768
  // runbooks and harness prompts and erode the protection, whereas a faithful
27859
28769
  // paste can never accidentally carry the victim group's name. It AUTHORIZES
27860
28770
  // the target; --resource-group is what CHOOSES it.
27861
- reinstallInto = Option50.String("--reinstall-into", { description: "Consent to installing into --resource-group even though it already holds resources. Must match the target group's name." });
27862
- org = Option50.String("--org", { description: "Assert the GitHub App on disk is installed on this org; refuses on a mismatch." });
27863
- noBrains = Option50.Boolean("--no-brains", false, { description: "Install without brain-backed workers. For test and CI rigs; the supported install creates brains." });
28771
+ reinstallInto = Option51.String("--reinstall-into", { description: "Consent to installing into --resource-group even though it already holds resources. Must match the target group's name." });
28772
+ org = Option51.String("--org", { description: "Assert the GitHub App on disk is installed on this org; refuses on a mismatch." });
28773
+ noBrains = Option51.Boolean("--no-brains", false, { description: "Install without brain-backed workers. For test and CI rigs; the supported install creates brains." });
27864
28774
  async executeCommand() {
27865
28775
  const location = typeof this.location === "string" ? this.location : void 0;
27866
28776
  if (!location) {
@@ -28014,7 +28924,7 @@ var BootstrapLaunchCommand = class extends M8tCommand {
28014
28924
  };
28015
28925
 
28016
28926
  // src/commands/bootstrap/status.ts
28017
- import { Command as Command54, Option as Option51 } from "clipanion";
28927
+ import { Command as Command55, Option as Option52 } from "clipanion";
28018
28928
  init_errors();
28019
28929
 
28020
28930
  // src/lib/bootstrap-aci-state.ts
@@ -28042,13 +28952,13 @@ async function getAciState(opts) {
28042
28952
  // src/commands/bootstrap/status.ts
28043
28953
  var BootstrapStatusCommand = class extends M8tCommand {
28044
28954
  static paths = [["bootstrap", "status"]];
28045
- static usage = Command54.Usage({
28955
+ static usage = Command55.Usage({
28046
28956
  description: "Show the cloud installer's live status (phase, progress, result).",
28047
28957
  details: "Reads the durable status blob written by the installer. --watch polls until the install reaches done or failed.",
28048
28958
  examples: [["One read", "$0 bootstrap status"], ["Watch to completion", "$0 bootstrap status --watch"]]
28049
28959
  });
28050
- watch = Option51.Boolean("--watch", false);
28051
- output = Option51.String("--output");
28960
+ watch = Option52.Boolean("--watch", false);
28961
+ output = Option52.String("--output");
28052
28962
  async executeCommand() {
28053
28963
  const state = await readBootstrapState();
28054
28964
  if (!state) {
@@ -28133,7 +29043,7 @@ function formatStatus(d) {
28133
29043
  }
28134
29044
 
28135
29045
  // src/commands/bootstrap/reap.ts
28136
- import { Command as Command55, Option as Option52 } from "clipanion";
29046
+ import { Command as Command56, Option as Option53 } from "clipanion";
28137
29047
  init_errors();
28138
29048
 
28139
29049
  // src/lib/bootstrap-reap.ts
@@ -28227,14 +29137,14 @@ async function reapInstaller(opts) {
28227
29137
  // src/commands/bootstrap/reap.ts
28228
29138
  var BootstrapReapCommand = class extends M8tCommand {
28229
29139
  static paths = [["bootstrap", "reap"]];
28230
- static usage = Command55.Usage({
29140
+ static usage = Command56.Usage({
28231
29141
  description: "Tear down the installer scaffolding (ACI \u2192 MI \u2192 its role assignments) after a successful install.",
28232
29142
  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.",
28233
29143
  examples: [["Reap after done", "$0 bootstrap reap"]]
28234
29144
  });
28235
- force = Option52.Boolean("--force", false);
28236
- sweepOrphans = Option52.Boolean("--sweep-orphans", false);
28237
- yes = Option52.Boolean("--yes", false);
29145
+ force = Option53.Boolean("--force", false);
29146
+ sweepOrphans = Option53.Boolean("--sweep-orphans", false);
29147
+ yes = Option53.Boolean("--yes", false);
28238
29148
  async executeCommand() {
28239
29149
  if (this.sweepOrphans === true) {
28240
29150
  const { subscriptionId: sub } = await getAzAccount();
@@ -28331,7 +29241,7 @@ Found ${String(total)} orphaned assignment(s) (${breakdown}) (dry-run). ${colors
28331
29241
  import * as fs31 from "fs/promises";
28332
29242
  import * as os16 from "os";
28333
29243
  import * as path34 from "path";
28334
- import { Command as Command56, Option as Option53 } from "clipanion";
29244
+ import { Command as Command57, Option as Option54 } from "clipanion";
28335
29245
  init_errors();
28336
29246
 
28337
29247
  // src/lib/company-profile-seed.ts
@@ -28735,9 +29645,9 @@ async function findOnboardingProfile(args) {
28735
29645
  if (typeof part.transcript === "string") return [part.transcript];
28736
29646
  return [];
28737
29647
  }).join("\n");
28738
- const result = parseOnboardingArtifactResult(machineText);
28739
- if (result.ok) artifacts.push(result.artifact);
28740
- else if (machineText.includes("m8t_onboarding")) rejection ??= result.reason;
29648
+ const result2 = parseOnboardingArtifactResult(machineText);
29649
+ if (result2.ok) artifacts.push(result2.artifact);
29650
+ else if (machineText.includes("m8t_onboarding")) rejection ??= result2.reason;
28741
29651
  }
28742
29652
  if (artifacts.length === 0) {
28743
29653
  return {
@@ -28964,10 +29874,10 @@ var CERTAIN_UNAVAILABLE = /* @__PURE__ */ new Set([
28964
29874
  "deploy-rejected-region"
28965
29875
  ]);
28966
29876
  var QUOTA_BLOCKED = /* @__PURE__ */ new Set(["no-quota", "deploy-rejected-quota"]);
28967
- function decideNote(whitelist, result) {
28968
- const chosenIdx = result.chosen ? whitelist.findIndex((r) => r.model === result.chosen?.model) : whitelist.length;
28969
- const better = result.trace.slice(0, Math.max(chosenIdx, 0));
28970
- const runningModel = result.chosen?.model ?? null;
29877
+ function decideNote(whitelist, result2) {
29878
+ const chosenIdx = result2.chosen ? whitelist.findIndex((r) => r.model === result2.chosen?.model) : whitelist.length;
29879
+ const better = result2.trace.slice(0, Math.max(chosenIdx, 0));
29880
+ const runningModel = result2.chosen?.model ?? null;
28971
29881
  if (chosenIdx === 0) {
28972
29882
  return { status: "top", runningModel, pitchModel: null, unavailableAbovePitch: [] };
28973
29883
  }
@@ -29202,13 +30112,13 @@ async function applyProfileToBrains(args) {
29202
30112
  const verified = [];
29203
30113
  const failed = [];
29204
30114
  const causes = [];
29205
- settled.forEach((result, index) => {
30115
+ settled.forEach((result2, index) => {
29206
30116
  const brainRepo = brainRepos[index];
29207
- if (result.status === "fulfilled") {
30117
+ if (result2.status === "fulfilled") {
29208
30118
  verified.push(brainRepo);
29209
30119
  } else {
29210
30120
  failed.push(brainRepo);
29211
- causes.push(result.reason);
30121
+ causes.push(result2.reason);
29212
30122
  }
29213
30123
  });
29214
30124
  if (failed.length > 0 || verified.length !== brainRepos.length) {
@@ -29296,14 +30206,14 @@ function renderInstallSummary(args) {
29296
30206
  // src/commands/bootstrap/finish.ts
29297
30207
  var BootstrapFinishCommand = class extends M8tCommand {
29298
30208
  static paths = [["bootstrap", "finish"]];
29299
- static usage = Command56.Usage({
30209
+ static usage = Command57.Usage({
29300
30210
  description: "Point your local tools at the now-live platform (repo-root marker, discovery cache, next steps).",
29301
30211
  details: "Final step of `m8t bootstrap` (after the install reaches done). Writes ~/.m8t/repo-root, points your local tools at the live gateway, and \u2014 if you completed the onboarding intake \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.",
29302
30212
  examples: [["Finish local", "$0 bootstrap finish --repo-root /path/to/m8t"]]
29303
30213
  });
29304
- repoRoot = Option53.String("--repo-root");
29305
- subscription = Option53.String("--subscription");
29306
- resourceGroup = Option53.String("--resource-group");
30214
+ repoRoot = Option54.String("--repo-root");
30215
+ subscription = Option54.String("--subscription");
30216
+ resourceGroup = Option54.String("--resource-group");
29307
30217
  async executeCommand() {
29308
30218
  const state = await readBootstrapState();
29309
30219
  if (!state) {
@@ -29354,6 +30264,36 @@ var BootstrapFinishCommand = class extends M8tCommand {
29354
30264
  ${colors.hint(` m8t bootstrap finish --repo-root ${repoRoot} # (as an admin), or by hand:`)}
29355
30265
  ${colors.hint(` az ad app update --id ${d.gatewayClientId} --set spa.redirectUris="['${d.gatewayUrl}']" # (merge, do not overwrite)`)}
29356
30266
 
30267
+ `
30268
+ );
30269
+ }
30270
+ try {
30271
+ const verdict = await runUsagePhase(
30272
+ buildPrereqDeps({ subscription: subscriptionId, resourceGroup }),
30273
+ { fix: true, onProgress: (m) => {
30274
+ this.context.stdout.write(colors.dim(` ${m}
30275
+ `));
30276
+ } }
30277
+ );
30278
+ const rows = verdict.results.filter((r) => r.slug !== "signin-redirect-uri");
30279
+ const unresolved = rows.filter((r) => r.status === "fail");
30280
+ if (unresolved.length === 0) {
30281
+ this.context.stdout.write(` ${colors.success("\u2713 your account can reach Foundry and read the platform Key Vault")}
30282
+
30283
+ `);
30284
+ }
30285
+ for (const r of unresolved) {
30286
+ this.context.stderr.write(
30287
+ ` ${colors.error("\u26A0 could not grant you access:")} ${r.detail}
30288
+ ` + (r.remedy ? ` ${colors.hint(`Fix it with: ${r.remedy}`)}
30289
+ ` : "") + "\n"
30290
+ );
30291
+ }
30292
+ } catch (e) {
30293
+ this.context.stderr.write(
30294
+ ` ${colors.error("\u26A0 could not check or grant your platform access:")} ${e.message}
30295
+ ${colors.hint("Run 'm8t prereqs --fix' when you can \u2014 until then your AI team will not load and worker deploys will fail.")}
30296
+
29357
30297
  `
29358
30298
  );
29359
30299
  }
@@ -29406,8 +30346,8 @@ ${colors.field("Then open a brand new chat/session")} \u2014 new skills + MCP se
29406
30346
  import * as fs33 from "fs";
29407
30347
  import * as os18 from "os";
29408
30348
  import * as path36 from "path";
29409
- import { Command as Command57, Option as Option54 } from "clipanion";
29410
- import { DefaultAzureCredential as DefaultAzureCredential23 } from "@azure/identity";
30349
+ import { Command as Command58, Option as Option55 } from "clipanion";
30350
+ import { DefaultAzureCredential as DefaultAzureCredential24 } from "@azure/identity";
29411
30351
  init_errors();
29412
30352
 
29413
30353
  // src/lib/bootstrap-ui.ts
@@ -29859,9 +30799,9 @@ async function serveOnboardingRelayDetached(args) {
29859
30799
  }
29860
30800
  function autoOpenOnboardingUi(url, open = tryOpenUrl) {
29861
30801
  try {
29862
- const result = open(url);
29863
- if (result instanceof Promise) {
29864
- result.catch(() => {
30802
+ const result2 = open(url);
30803
+ if (result2 instanceof Promise) {
30804
+ result2.catch(() => {
29865
30805
  });
29866
30806
  }
29867
30807
  } catch {
@@ -29993,15 +30933,15 @@ async function resolveIntakeModel(args) {
29993
30933
  });
29994
30934
  };
29995
30935
  const start = Date.now();
29996
- const result = await walkCascade(WHITELIST, plan, deploy, {
30936
+ const result2 = await walkCascade(WHITELIST, plan, deploy, {
29997
30937
  now: () => Date.now(),
29998
30938
  deadlineAt: start + (args.deadlineMs ?? DEFAULT_DEADLINE_MS),
29999
30939
  onRung: (model, outcome) => args.onNarrate?.(narrate(model, outcome, region))
30000
30940
  });
30001
30941
  return {
30002
- model: result.chosen?.model,
30003
- note: renderChosenModelNote(decideNote(WHITELIST, result)),
30004
- trace: result.trace
30942
+ model: result2.chosen?.model,
30943
+ note: renderChosenModelNote(decideNote(WHITELIST, result2)),
30944
+ trace: result2.trace
30005
30945
  };
30006
30946
  } catch (e) {
30007
30947
  return degradeToFloor(e instanceof Error ? e.message : String(e));
@@ -30214,7 +31154,7 @@ function renderDeployFailure(error) {
30214
31154
  }
30215
31155
  var BootstrapUiCommand = class extends M8tCommand {
30216
31156
  static paths = [["bootstrap", "ui"]];
30217
- static usage = Command57.Usage({
31157
+ static usage = Command58.Usage({
30218
31158
  description: "Deploy Azzy + start the local onboarding chat UI in the background (returns immediately).",
30219
31159
  details: [
30220
31160
  "Run after `m8t bootstrap launch`, in parallel with `status --watch`. Waits for the cloud",
@@ -30235,16 +31175,16 @@ var BootstrapUiCommand = class extends M8tCommand {
30235
31175
  ["Experimental: start the voice relay (no effect on the text-only intake)", "$0 bootstrap ui --repo-root /path/to/m8t --voice"]
30236
31176
  ]
30237
31177
  });
30238
- repoRoot = Option54.String("--repo-root");
30239
- port = Option54.String("--port", "3000");
30240
- endpoint = Option54.String("--endpoint", {
31178
+ repoRoot = Option55.String("--repo-root");
31179
+ port = Option55.String("--port", "3000");
31180
+ endpoint = Option55.String("--endpoint", {
30241
31181
  description: "Foundry project endpoint to target \u2014 disambiguates when the subscription has multiple projects."
30242
31182
  });
30243
- prepOnly = Option54.Boolean("--prep-only", false);
30244
- skipInstall = Option54.Boolean("--skip-install", false);
30245
- stop = Option54.Boolean("--stop", false);
30246
- foreground = Option54.Boolean("--foreground", false);
30247
- voice = Option54.Boolean("--voice", false, {
31183
+ prepOnly = Option55.Boolean("--prep-only", false);
31184
+ skipInstall = Option55.Boolean("--skip-install", false);
31185
+ stop = Option55.Boolean("--stop", false);
31186
+ foreground = Option55.Boolean("--foreground", false);
31187
+ voice = Option55.Boolean("--voice", false, {
30248
31188
  description: "Experimental: when serving, starts the voice relay and writes the intake voice env var. The onboarding intake is text-only and is unaffected by this flag \u2014 no voice worker is registered for it."
30249
31189
  });
30250
31190
  async executeCommand() {
@@ -30282,7 +31222,7 @@ var BootstrapUiCommand = class extends M8tCommand {
30282
31222
  if (fs33.existsSync(path36.join(os18.homedir(), ".m8t", "config.yaml"))) {
30283
31223
  out(colors.error("\u26A0 an existing ~/.m8t/config.yaml will override the onboarding app registration in apps/web \u2014 if Microsoft sign-in fails, move it aside and retry."));
30284
31224
  }
30285
- const credential2 = new DefaultAzureCredential23();
31225
+ const credential2 = new DefaultAzureCredential24();
30286
31226
  const account = await getAzAccount();
30287
31227
  assertNodeVersion();
30288
31228
  out("waiting for Foundry (the installer's foundry-create phase)\u2026");
@@ -30406,10 +31346,10 @@ var BootstrapUiCommand = class extends M8tCommand {
30406
31346
  };
30407
31347
 
30408
31348
  // src/commands/bootstrap/seed-profile.ts
30409
- import { Command as Command58, Option as Option55 } from "clipanion";
31349
+ import { Command as Command59, Option as Option56 } from "clipanion";
30410
31350
  var BootstrapSeedProfileCommand = class extends M8tCommand {
30411
31351
  static paths = [["bootstrap", "seed-profile"]];
30412
- static usage = Command58.Usage({
31352
+ static usage = Command59.Usage({
30413
31353
  description: "Seed your advisors' brains with the founder + company profile from the onboarding intake.",
30414
31354
  details: "Reads the latest onboarding 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 finishes the intake.",
30415
31355
  examples: [
@@ -30417,11 +31357,11 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
30417
31357
  ["Wait for the founder to finish", "$0 bootstrap seed-profile --watch"]
30418
31358
  ]
30419
31359
  });
30420
- endpoint = Option55.String("--endpoint", { description: "Override the Foundry endpoint (else read from the install status)." });
30421
- brain = Option55.String("--brain", { description: "Seed only this one brain repo, instead of both <org>/stacey-brain and <org>/azzy-brain." });
30422
- watch = Option55.Boolean("--watch", false, { description: "Poll until the intake completes (or --timeout)." });
30423
- timeout = Option55.String("--timeout", { description: "Watch timeout in minutes (default 20)." });
30424
- githubAppCreds = Option55.String("--github-app-creds");
31360
+ endpoint = Option56.String("--endpoint", { description: "Override the Foundry endpoint (else read from the install status)." });
31361
+ brain = Option56.String("--brain", { description: "Seed only this one brain repo, instead of both <org>/stacey-brain and <org>/azzy-brain." });
31362
+ watch = Option56.Boolean("--watch", false, { description: "Poll until the intake completes (or --timeout)." });
31363
+ timeout = Option56.String("--timeout", { description: "Watch timeout in minutes (default 20)." });
31364
+ githubAppCreds = Option56.String("--github-app-creds");
30425
31365
  async executeCommand() {
30426
31366
  const ctx = await resolveSeedContext({
30427
31367
  endpointOverride: typeof this.endpoint === "string" ? this.endpoint : void 0,
@@ -30484,7 +31424,7 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
30484
31424
  import * as fs34 from "fs";
30485
31425
  import * as os19 from "os";
30486
31426
  import * as path37 from "path";
30487
- import { Command as Command59, Option as Option56 } from "clipanion";
31427
+ import { Command as Command60, Option as Option57 } from "clipanion";
30488
31428
  init_errors();
30489
31429
 
30490
31430
  // src/lib/telemetry-enroll.ts
@@ -30566,7 +31506,7 @@ async function resolveKeyVaultName(containerAppResourceId) {
30566
31506
  }
30567
31507
  var TelemetryEnrollCommand = class extends M8tCommand {
30568
31508
  static paths = [["telemetry", "enroll"]];
30569
- static usage = Command59.Usage({
31509
+ static usage = Command60.Usage({
30570
31510
  description: "Enroll this installation for operational telemetry (pre-existing installs).",
30571
31511
  details: "Generates an instance id + ingest key from the m8t telemetry ingest and stores the key in your platform Key Vault, the same place the installer writes it on a fresh install. Your installation is identified only by that random instance id \u2014 no contact, company, or subscription details are sent unless you pass them explicitly. Idempotent: refuses if a key already exists.",
30572
31512
  examples: [
@@ -30574,11 +31514,11 @@ var TelemetryEnrollCommand = class extends M8tCommand {
30574
31514
  ["Enroll and share a support contact", "$0 telemetry enroll --contact-email you@example.com --company 'Acme'"]
30575
31515
  ]
30576
31516
  });
30577
- company = Option56.String("--company", { description: "Share your company name with m8t support. Not sent unless you pass it." });
30578
- contactEmail = Option56.String("--contact-email", { description: "Share a contact email with m8t support. Not sent unless you pass it." });
30579
- subscription = Option56.String("--subscription", { description: "Azure subscription id used to find your deployment. Never sent to m8t." });
30580
- resourceGroup = Option56.String("--resource-group", { description: "Resource group to disambiguate discovery, if you have more than one m8t deployment." });
30581
- force = Option56.Boolean("--force", false, { description: "Enroll even when a key is already stored. Use only when m8t support asks you to." });
31517
+ company = Option57.String("--company", { description: "Share your company name with m8t support. Not sent unless you pass it." });
31518
+ contactEmail = Option57.String("--contact-email", { description: "Share a contact email with m8t support. Not sent unless you pass it." });
31519
+ subscription = Option57.String("--subscription", { description: "Azure subscription id used to find your deployment. Never sent to m8t." });
31520
+ resourceGroup = Option57.String("--resource-group", { description: "Resource group to disambiguate discovery, if you have more than one m8t deployment." });
31521
+ force = Option57.Boolean("--force", false, { description: "Enroll even when a key is already stored. Use only when m8t support asks you to." });
30582
31522
  async executeCommand() {
30583
31523
  const account = await getAzAccount();
30584
31524
  const subscriptionId = (typeof this.subscription === "string" ? this.subscription : void 0) ?? account.subscriptionId;
@@ -30638,6 +31578,7 @@ cli.register(VersionCommand);
30638
31578
  cli.register(WhoamiCommand);
30639
31579
  cli.register(StatusCommand);
30640
31580
  cli.register(DoctorCommand);
31581
+ cli.register(PrereqsCommand);
30641
31582
  cli.register(SwitchCommand);
30642
31583
  cli.register(OpenCommand);
30643
31584
  cli.register(ConfigShowCommand);