@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/README.md +1 -1
- package/dist/cli.js +1299 -358
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
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 {
|
|
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.
|
|
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
|
|
2058
|
-
if (
|
|
2092
|
+
const result2 = await reader.read();
|
|
2093
|
+
if (result2?.done)
|
|
2059
2094
|
reader.releaseLock();
|
|
2060
|
-
return
|
|
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
|
|
2803
|
-
left.push(
|
|
2804
|
-
right.push(
|
|
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
|
|
4388
|
-
if (
|
|
4389
|
-
this._addMessage(
|
|
4390
|
-
if (singleFunctionToCall &&
|
|
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
|
|
4398
|
-
if (
|
|
4399
|
-
throw
|
|
4432
|
+
for (const result2 of results) {
|
|
4433
|
+
if (result2.status === "rejected")
|
|
4434
|
+
throw result2.reason;
|
|
4400
4435
|
}
|
|
4401
|
-
for (const
|
|
4402
|
-
if (
|
|
4403
|
-
this._addMessage(
|
|
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((
|
|
11251
|
+
const rejected = results.filter((result2) => result2.status === "rejected");
|
|
11217
11252
|
if (rejected.length) {
|
|
11218
|
-
for (const
|
|
11219
|
-
console.error(
|
|
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
|
|
11225
|
-
if (
|
|
11226
|
-
values.push(
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
14755
|
+
const result2 = await checkAppHealth({ credential: credential2, kvUri });
|
|
14721
14756
|
if (mode === "json") {
|
|
14722
|
-
this.context.stdout.write(renderJson(
|
|
14723
|
-
return
|
|
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",
|
|
14735
|
-
row("App ID numeric",
|
|
14736
|
-
row("Private key valid",
|
|
14737
|
-
row("App JWT accepted",
|
|
14738
|
-
row("Installations listable",
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
15979
|
-
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 ${
|
|
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
|
-
|
|
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
|
|
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:
|
|
16144
|
-
connectionName:
|
|
16145
|
-
foundryVersion:
|
|
16146
|
-
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 (
|
|
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 ${
|
|
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
|
|
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
|
-
|
|
16213
|
+
result2.push({ name, metadata: md });
|
|
16181
16214
|
}
|
|
16182
16215
|
}
|
|
16183
|
-
return
|
|
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(
|
|
16234
|
-
if (!
|
|
16235
|
-
const 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
|
|
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:
|
|
16653
|
-
connectionDeleted:
|
|
16654
|
-
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 &&
|
|
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
|
|
17067
|
-
const bearerHash = createHash("sha256").update(
|
|
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
|
|
17317
|
-
if (
|
|
17318
|
-
return Promise.reject(new Error(
|
|
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
|
|
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:
|
|
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 (
|
|
17554
|
-
this.context.stdout.write(`${colors.success("\u2713")} ${colors.field(this.worker)} is a discoverable a2a target (agent version ${
|
|
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(
|
|
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
|
|
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, ...
|
|
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(
|
|
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
|
|
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
|
-
|
|
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:
|
|
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:
|
|
18451
|
-
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:
|
|
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 ${
|
|
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
|
-
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
18860
|
-
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:
|
|
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 ${
|
|
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
|
|
19479
|
-
return
|
|
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
|
|
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 =
|
|
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
|
|
21186
|
-
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
|
|
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(
|
|
21730
|
+
this.context.stdout.write(renderJson(result2) + "\n");
|
|
21704
21731
|
} else {
|
|
21705
|
-
this.context.stdout.write(`${
|
|
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
|
|
21826
|
+
const result2 = { platformVersion: stamp.platformVersion, previous: previousPlatformVersion, storageAccount: accountName };
|
|
21800
21827
|
this.context.stdout.write(
|
|
21801
|
-
mode === "json" ? renderJson(
|
|
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
|
|
21897
|
-
this.context.stdout.write(mode === "json" ? renderJson(
|
|
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
|
|
21930
|
+
const result3 = { outcome: "unreadable" };
|
|
21904
21931
|
this.context.stdout.write(
|
|
21905
|
-
mode === "json" ? renderJson(
|
|
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
|
|
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(
|
|
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
|
|
22577
|
-
return
|
|
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
|
-
|
|
23670
|
-
|
|
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
|
-
|
|
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
|
|
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/
|
|
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 =
|
|
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 =
|
|
24155
|
-
subscription =
|
|
24156
|
-
list =
|
|
24157
|
-
as =
|
|
24158
|
-
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
|
|
25129
|
+
let result2;
|
|
24182
25130
|
if (this.subscription) {
|
|
24183
|
-
|
|
25131
|
+
result2 = await discoverySwitch(this.subscription, this.as);
|
|
24184
25132
|
} else if (this.profile) {
|
|
24185
|
-
|
|
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(
|
|
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:
|
|
24200
|
-
{ key: "clientId", value:
|
|
24201
|
-
{ key: "endpoint", value:
|
|
24202
|
-
{ key: "subscription", value:
|
|
24203
|
-
{ key: "saved profile", value:
|
|
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
|
|
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 =
|
|
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 =
|
|
24266
|
-
print =
|
|
24267
|
-
output =
|
|
24268
|
-
resourceGroup =
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
24593
|
-
if (
|
|
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(
|
|
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
|
|
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 (
|
|
26364
|
-
deps.logger.error({ at: "runDream", worker: input.worker, terminal:
|
|
26365
|
-
digest.push({ kind: "failure", detail: `write ${
|
|
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:
|
|
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 =
|
|
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 =
|
|
26544
|
-
dryRun =
|
|
26545
|
-
since =
|
|
26546
|
-
reset =
|
|
26547
|
-
showTranscripts =
|
|
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 =
|
|
26551
|
-
endpoint =
|
|
26552
|
-
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
|
|
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=${
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
27680
|
+
const result2 = await client.queryWorkspace(workspaceId, kql, {
|
|
26733
27681
|
startTime: new Date(from),
|
|
26734
27682
|
endTime: new Date(to)
|
|
26735
27683
|
});
|
|
26736
|
-
if (
|
|
26737
|
-
const table =
|
|
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
|
|
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 =
|
|
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 =
|
|
27148
|
-
location =
|
|
27149
|
-
account =
|
|
27150
|
-
project =
|
|
27151
|
-
model =
|
|
27152
|
-
modelVersion =
|
|
27153
|
-
capacity =
|
|
27154
|
-
subscription =
|
|
27155
|
-
skipQuotaCheck =
|
|
27156
|
-
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
|
|
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:
|
|
27198
|
-
accountName:
|
|
27199
|
-
accountResourceId:
|
|
27200
|
-
projectName:
|
|
27201
|
-
region:
|
|
27202
|
-
model:
|
|
27203
|
-
capacity:
|
|
27204
|
-
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:
|
|
27212
|
-
{ key: "account", value:
|
|
27213
|
-
{ key: "project", value:
|
|
27214
|
-
{ key: "region", value:
|
|
27215
|
-
{ key: "model", value: `${
|
|
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(
|
|
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 ${
|
|
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
|
|
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 =
|
|
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 =
|
|
27239
|
-
consecutive =
|
|
27240
|
-
attempts =
|
|
27241
|
-
interval =
|
|
27242
|
-
subscription =
|
|
27243
|
-
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
|
|
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 =
|
|
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
|
|
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 =
|
|
27401
|
-
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
|
-
|
|
27433
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
27845
|
-
resourceGroup =
|
|
27846
|
-
clientId =
|
|
27847
|
-
subscription =
|
|
27848
|
-
installerTag =
|
|
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 =
|
|
27853
|
-
gatewayImageRef =
|
|
27854
|
-
githubAppCreds =
|
|
27855
|
-
contactEmail =
|
|
27856
|
-
company =
|
|
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 =
|
|
27862
|
-
org =
|
|
27863
|
-
noBrains =
|
|
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
|
|
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 =
|
|
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 =
|
|
28051
|
-
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
|
|
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 =
|
|
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 =
|
|
28236
|
-
sweepOrphans =
|
|
28237
|
-
yes =
|
|
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
|
|
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
|
|
28739
|
-
if (
|
|
28740
|
-
else if (machineText.includes("m8t_onboarding")) rejection ??=
|
|
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,
|
|
28968
|
-
const chosenIdx =
|
|
28969
|
-
const better =
|
|
28970
|
-
const runningModel =
|
|
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((
|
|
30115
|
+
settled.forEach((result2, index) => {
|
|
29206
30116
|
const brainRepo = brainRepos[index];
|
|
29207
|
-
if (
|
|
30117
|
+
if (result2.status === "fulfilled") {
|
|
29208
30118
|
verified.push(brainRepo);
|
|
29209
30119
|
} else {
|
|
29210
30120
|
failed.push(brainRepo);
|
|
29211
|
-
causes.push(
|
|
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 =
|
|
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 =
|
|
29305
|
-
subscription =
|
|
29306
|
-
resourceGroup =
|
|
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
|
|
29410
|
-
import { DefaultAzureCredential as
|
|
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
|
|
29863
|
-
if (
|
|
29864
|
-
|
|
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
|
|
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:
|
|
30003
|
-
note: renderChosenModelNote(decideNote(WHITELIST,
|
|
30004
|
-
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 =
|
|
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 =
|
|
30239
|
-
port =
|
|
30240
|
-
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 =
|
|
30244
|
-
skipInstall =
|
|
30245
|
-
stop =
|
|
30246
|
-
foreground =
|
|
30247
|
-
voice =
|
|
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
|
|
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
|
|
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 =
|
|
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 =
|
|
30421
|
-
brain =
|
|
30422
|
-
watch =
|
|
30423
|
-
timeout =
|
|
30424
|
-
githubAppCreds =
|
|
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
|
|
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 =
|
|
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 =
|
|
30578
|
-
contactEmail =
|
|
30579
|
-
subscription =
|
|
30580
|
-
resourceGroup =
|
|
30581
|
-
force =
|
|
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);
|