@m8t-stack/cli 0.2.1 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +460 -40
- package/dist/cli.js.map +1 -1
- package/package.json +8 -7
package/dist/cli.js
CHANGED
|
@@ -1109,7 +1109,7 @@ var init_enable_hosted_brain = __esm({
|
|
|
1109
1109
|
import { Builtins, Cli } from "clipanion";
|
|
1110
1110
|
|
|
1111
1111
|
// src/lib/package-version.ts
|
|
1112
|
-
var CLI_VERSION = "0.2.
|
|
1112
|
+
var CLI_VERSION = "0.2.3";
|
|
1113
1113
|
|
|
1114
1114
|
// src/lib/render-error.ts
|
|
1115
1115
|
init_errors();
|
|
@@ -3135,7 +3135,7 @@ async function resolveFoundryProject(opts) {
|
|
|
3135
3135
|
throw new LocalCliError({
|
|
3136
3136
|
code: "FOUNDRY_PROJECT_ZERO",
|
|
3137
3137
|
message: `No AIServices Foundry project found in subscription ${opts.subscriptionId}.`,
|
|
3138
|
-
hint: "
|
|
3138
|
+
hint: "Create one with 'm8t foundry create --resource-group <rg> --location <region>', switch subscription with 'az account set --subscription <id>', or pass --endpoint."
|
|
3139
3139
|
});
|
|
3140
3140
|
}
|
|
3141
3141
|
if (candidates.length === 1) return candidates[0];
|
|
@@ -3367,7 +3367,7 @@ import * as fs6 from "fs";
|
|
|
3367
3367
|
// src/lib/persona-render.ts
|
|
3368
3368
|
init_errors();
|
|
3369
3369
|
import * as fs5 from "fs";
|
|
3370
|
-
function renderPersonaBody(personaPath, fillableFieldValues) {
|
|
3370
|
+
function renderPersonaBody(personaPath, fillableFieldValues, instanceName) {
|
|
3371
3371
|
let raw;
|
|
3372
3372
|
try {
|
|
3373
3373
|
raw = fs5.readFileSync(personaPath, "utf8");
|
|
@@ -3392,7 +3392,18 @@ function renderPersonaBody(personaPath, fillableFieldValues) {
|
|
|
3392
3392
|
hint: `Add these to fillableFieldValues in ~/.m8t-stack/foundry/<agent>.yaml, or re-deploy the agent via the architect to repopulate.`
|
|
3393
3393
|
});
|
|
3394
3394
|
}
|
|
3395
|
-
|
|
3395
|
+
const out = rendered.replace(/\s+$/, "");
|
|
3396
|
+
return instanceName ? injectInstanceIdentity(out, instanceName) : out;
|
|
3397
|
+
}
|
|
3398
|
+
function injectInstanceIdentity(body, instanceName) {
|
|
3399
|
+
const display = instanceName.charAt(0).toUpperCase() + instanceName.slice(1);
|
|
3400
|
+
const line = `**You are ${display}.** Introduce yourself as ${display} on first contact.`;
|
|
3401
|
+
const title = /^(#[^\n]*\n)/.exec(body);
|
|
3402
|
+
return title ? `${title[0]}
|
|
3403
|
+
${line}
|
|
3404
|
+
${body.slice(title[0].length)}` : `${line}
|
|
3405
|
+
|
|
3406
|
+
${body}`;
|
|
3396
3407
|
}
|
|
3397
3408
|
|
|
3398
3409
|
// src/lib/brain-loader-block.ts
|
|
@@ -3508,7 +3519,7 @@ async function linkBrain(args) {
|
|
|
3508
3519
|
if (personaPath) {
|
|
3509
3520
|
progress("Re-rendering persona body with brain loader\u2026");
|
|
3510
3521
|
const resolved = personaPath.startsWith("/") ? personaPath : path5.join(args.repoRoot, personaPath);
|
|
3511
|
-
base = renderPersonaBody(resolved, yaml?.fillableFieldValues ?? {});
|
|
3522
|
+
base = renderPersonaBody(resolved, yaml?.fillableFieldValues ?? {}, args.agentName);
|
|
3512
3523
|
} else {
|
|
3513
3524
|
progress("No local persona yaml \u2014 basing instructions on the deployed agent\u2026");
|
|
3514
3525
|
base = current.definition.instructions ?? "";
|
|
@@ -4230,7 +4241,7 @@ async function unlinkBrain(args) {
|
|
|
4230
4241
|
}
|
|
4231
4242
|
personaPath = path8.join(fs9.readFileSync(marker, "utf8").trim(), personaPath);
|
|
4232
4243
|
}
|
|
4233
|
-
strippedInstructions = renderPersonaBody(personaPath, yaml.fillableFieldValues ?? {});
|
|
4244
|
+
strippedInstructions = renderPersonaBody(personaPath, yaml.fillableFieldValues ?? {}, args.agentName);
|
|
4234
4245
|
} else {
|
|
4235
4246
|
progress("No persona yaml \u2014 stripping the loader from the deployed instructions\u2026");
|
|
4236
4247
|
strippedInstructions = stripBrainLoader(current.definition.instructions ?? "");
|
|
@@ -5319,7 +5330,10 @@ function parseImageRef(image) {
|
|
|
5319
5330
|
const host = hostAndPath.slice(0, slash);
|
|
5320
5331
|
const repo = hostAndPath.slice(slash + 1);
|
|
5321
5332
|
const acrName = host.replace(/\.azurecr\.io$/i, "");
|
|
5322
|
-
return { acrName, repo, tag: tag || "" };
|
|
5333
|
+
return { host, acrName, repo, tag: tag || "" };
|
|
5334
|
+
}
|
|
5335
|
+
function isAcrHost(host) {
|
|
5336
|
+
return /\.azurecr\.io$/i.test(host);
|
|
5323
5337
|
}
|
|
5324
5338
|
function acrScope(subscriptionId, projectAccountScope, acrName) {
|
|
5325
5339
|
const rg = /\/resourceGroups\/([^/]+)\//.exec(projectAccountScope)?.[1] ?? "";
|
|
@@ -5334,36 +5348,40 @@ async function checkPreconditions(args) {
|
|
|
5334
5348
|
hint: `Hosted agents need one of: ${HOSTED_AGENT_REGIONS.join(", ")}. Deploy against a project in a supported region.`
|
|
5335
5349
|
});
|
|
5336
5350
|
}
|
|
5337
|
-
const { acrName, repo, tag } = parseImageRef(args.image);
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
5346
|
-
|
|
5347
|
-
|
|
5348
|
-
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
acrScope: scope,
|
|
5354
|
-
principalId: args.project.projectPrincipalId
|
|
5355
|
-
});
|
|
5356
|
-
if (!hasPull) {
|
|
5357
|
-
await grantAcrPull({
|
|
5351
|
+
const { host, acrName, repo, tag } = parseImageRef(args.image);
|
|
5352
|
+
if (isAcrHost(host)) {
|
|
5353
|
+
const tagCheck = await imageTagExists(acrName, repo, tag);
|
|
5354
|
+
if (tagCheck === "absent") {
|
|
5355
|
+
throw new LocalCliError({
|
|
5356
|
+
code: "IMAGE_TAG_NOT_FOUND",
|
|
5357
|
+
message: `Image '${repo}:${tag}' was not found in ACR '${acrName}'.`,
|
|
5358
|
+
hint: `Build + push it first: cd agents/coding-agent && docker buildx build --platform linux/amd64 -t ${acrName}.azurecr.io/${repo}:${tag} --push . (see workers.md).`
|
|
5359
|
+
});
|
|
5360
|
+
}
|
|
5361
|
+
if (tagCheck === "unknown") {
|
|
5362
|
+
warnings.push(`could not verify image '${repo}:${tag}' in ACR '${acrName}' (az acr check failed) \u2014 proceeding; a bad image surfaces as a failed version.`);
|
|
5363
|
+
}
|
|
5364
|
+
if (args.project.projectPrincipalId) {
|
|
5365
|
+
const scope = acrScope(args.subscriptionId, args.project.accountScope, acrName);
|
|
5366
|
+
const hasPull = await principalHasAcrPull({
|
|
5358
5367
|
credential: args.credential,
|
|
5359
|
-
subscriptionId: args.subscriptionId,
|
|
5360
5368
|
acrScope: scope,
|
|
5361
5369
|
principalId: args.project.projectPrincipalId
|
|
5362
5370
|
});
|
|
5363
|
-
|
|
5371
|
+
if (!hasPull) {
|
|
5372
|
+
await grantAcrPull({
|
|
5373
|
+
credential: args.credential,
|
|
5374
|
+
subscriptionId: args.subscriptionId,
|
|
5375
|
+
acrScope: scope,
|
|
5376
|
+
principalId: args.project.projectPrincipalId
|
|
5377
|
+
});
|
|
5378
|
+
warnings.push(`granted AcrPull to the project managed identity on '${acrName}' (was missing).`);
|
|
5379
|
+
}
|
|
5380
|
+
} else {
|
|
5381
|
+
warnings.push("could not resolve the project managed identity to verify AcrPull \u2014 ensure it can pull from the ACR.");
|
|
5364
5382
|
}
|
|
5365
5383
|
} else {
|
|
5366
|
-
warnings.push(
|
|
5384
|
+
warnings.push(`public image host '${host}' \u2014 skipping the ACR tag check and AcrPull grant (anonymous pull, no registry credentials needed).`);
|
|
5367
5385
|
}
|
|
5368
5386
|
const canAssign = await callerCanAssignRoles({
|
|
5369
5387
|
credential: args.credential,
|
|
@@ -5522,6 +5540,9 @@ function usageModelName(usageName) {
|
|
|
5522
5540
|
return usageName.split(".").slice(2).join(".");
|
|
5523
5541
|
}
|
|
5524
5542
|
var REASONING_USAGE_RE = /gpt-5|o[1-9]/i;
|
|
5543
|
+
function isReasoningModel(modelDeployment) {
|
|
5544
|
+
return REASONING_USAGE_RE.test(modelDeployment);
|
|
5545
|
+
}
|
|
5525
5546
|
function modelQuotaVerdict(usages, modelDeployment) {
|
|
5526
5547
|
const token = modelDeployment.trim().toLowerCase();
|
|
5527
5548
|
const matches = usages.filter((u) => usageModelName(u.name).toLowerCase() === token);
|
|
@@ -5539,16 +5560,16 @@ var SIZE_PRESETS = {
|
|
|
5539
5560
|
medium: { cpu: "1", memory: "2Gi" },
|
|
5540
5561
|
large: { cpu: "2", memory: "4Gi" }
|
|
5541
5562
|
};
|
|
5542
|
-
var
|
|
5563
|
+
var DEFAULT_REGISTRY = "ghcr.io/m8t-run";
|
|
5543
5564
|
var DEFAULT_IMAGE = "m8t-coding-agent";
|
|
5544
|
-
var DEFAULT_TAG = "
|
|
5565
|
+
var DEFAULT_TAG = "v0.1.0";
|
|
5545
5566
|
var DEFAULT_MODEL = "gpt-4.1-mini";
|
|
5546
5567
|
var NAME_RE = /^[a-z0-9-]+$/;
|
|
5547
5568
|
var CoderDeployCommand = class extends M8tCommand {
|
|
5548
5569
|
static paths = [["coder", "deploy"]];
|
|
5549
5570
|
static usage = Command26.Usage({
|
|
5550
5571
|
description: "Deploy the curated coding agent as a hosted Foundry worker.",
|
|
5551
|
-
details: "Creates a hosted agent version from the
|
|
5572
|
+
details: "Creates a hosted agent version from a container image (defaults to the public GHCR image ghcr.io/m8t-run/m8t-coding-agent, pulled anonymously \u2014 no local build needed), tags it as an m8t worker (kind: hosted + persona), grants its identity the Foundry User role, and polls to active. Override with --image/--image-tag for a bring-your-own-registry image (e.g. a private ACR ref, which must be pushed first). Re-running creates a new version (idempotent).",
|
|
5552
5573
|
examples: [
|
|
5553
5574
|
["Deploy a coder with defaults", "$0 coder deploy my-coder"],
|
|
5554
5575
|
["Larger sandbox + a custom model", "$0 coder deploy my-coder --size large --model-deployment gpt-4.1"],
|
|
@@ -5603,7 +5624,7 @@ var CoderDeployCommand = class extends M8tCommand {
|
|
|
5603
5624
|
}
|
|
5604
5625
|
const persona = this.persona ?? "coding-agent";
|
|
5605
5626
|
const imageInput = this.image ?? DEFAULT_IMAGE;
|
|
5606
|
-
const repoRef = imageInput.includes("/") ? imageInput : `${
|
|
5627
|
+
const repoRef = imageInput.includes("/") ? imageInput : `${DEFAULT_REGISTRY}/${imageInput}`;
|
|
5607
5628
|
const image = `${repoRef}:${this.imageTag ?? DEFAULT_TAG}`;
|
|
5608
5629
|
const mode = resolveOutputMode(
|
|
5609
5630
|
this.output,
|
|
@@ -5843,21 +5864,66 @@ import { Command as Command28, Option as Option26 } from "clipanion";
|
|
|
5843
5864
|
import { DefaultAzureCredential as DefaultAzureCredential14 } from "@azure/identity";
|
|
5844
5865
|
init_errors();
|
|
5845
5866
|
init_rbac();
|
|
5867
|
+
|
|
5868
|
+
// src/lib/acs-provision.ts
|
|
5869
|
+
var MANAGED_DOMAIN = "AzureManagedDomain";
|
|
5870
|
+
async function provisionAcsEmail(args) {
|
|
5871
|
+
const { client, subscriptionId, resourceGroup, name, dataLocation, onProgress } = args;
|
|
5872
|
+
const acsName = `acs-${name}`;
|
|
5873
|
+
const emailName = `${name}-email`;
|
|
5874
|
+
onProgress?.(`creating ACS resource ${acsName}\u2026`);
|
|
5875
|
+
const acs = await client.communicationServices.beginCreateOrUpdateAndWait(
|
|
5876
|
+
resourceGroup,
|
|
5877
|
+
acsName,
|
|
5878
|
+
{ location: "global", dataLocation }
|
|
5879
|
+
);
|
|
5880
|
+
onProgress?.(`creating Email Communication Service ${emailName}\u2026`);
|
|
5881
|
+
await client.emailServices.beginCreateOrUpdateAndWait(resourceGroup, emailName, {
|
|
5882
|
+
location: "global",
|
|
5883
|
+
dataLocation
|
|
5884
|
+
});
|
|
5885
|
+
onProgress?.("provisioning the Azure-managed domain\u2026");
|
|
5886
|
+
const domain = await client.domains.beginCreateOrUpdateAndWait(
|
|
5887
|
+
resourceGroup,
|
|
5888
|
+
emailName,
|
|
5889
|
+
MANAGED_DOMAIN,
|
|
5890
|
+
{ location: "global", domainManagement: "AzureManaged" }
|
|
5891
|
+
);
|
|
5892
|
+
const domainResourceId = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroup}/providers/Microsoft.Communication/emailServices/${emailName}/domains/${MANAGED_DOMAIN}`;
|
|
5893
|
+
onProgress?.("linking the domain to the ACS resource\u2026");
|
|
5894
|
+
await client.communicationServices.beginCreateOrUpdateAndWait(resourceGroup, acsName, {
|
|
5895
|
+
location: "global",
|
|
5896
|
+
dataLocation,
|
|
5897
|
+
linkedDomains: [domainResourceId]
|
|
5898
|
+
});
|
|
5899
|
+
const hostName = acs.hostName ?? `${acsName}.communication.azure.com`;
|
|
5900
|
+
const fromSenderDomain = domain.fromSenderDomain;
|
|
5901
|
+
if (!fromSenderDomain) {
|
|
5902
|
+
throw new Error("ACS managed domain did not report a fromSenderDomain");
|
|
5903
|
+
}
|
|
5904
|
+
return {
|
|
5905
|
+
endpoint: `https://${hostName}/`,
|
|
5906
|
+
sender: `DoNotReply@${fromSenderDomain}`
|
|
5907
|
+
};
|
|
5908
|
+
}
|
|
5909
|
+
|
|
5910
|
+
// src/commands/azure-exec/deploy.ts
|
|
5911
|
+
import { CommunicationServiceManagementClient } from "@azure/arm-communication";
|
|
5846
5912
|
var SIZE_PRESETS2 = {
|
|
5847
5913
|
small: { cpu: "0.5", memory: "1Gi" },
|
|
5848
5914
|
medium: { cpu: "1", memory: "2Gi" },
|
|
5849
5915
|
large: { cpu: "2", memory: "4Gi" }
|
|
5850
5916
|
};
|
|
5851
|
-
var
|
|
5917
|
+
var DEFAULT_REGISTRY2 = "ghcr.io/m8t-run";
|
|
5852
5918
|
var DEFAULT_IMAGE2 = "m8t-azure-executor";
|
|
5853
|
-
var DEFAULT_TAG2 = "
|
|
5919
|
+
var DEFAULT_TAG2 = "v0.1.0";
|
|
5854
5920
|
var DEFAULT_MODEL2 = "gpt-5-mini";
|
|
5855
5921
|
var NAME_RE2 = /^[a-z0-9-]+$/;
|
|
5856
5922
|
var AzureExecDeployCommand = class extends M8tCommand {
|
|
5857
5923
|
static paths = [["azure-exec", "deploy"]];
|
|
5858
5924
|
static usage = Command28.Usage({
|
|
5859
5925
|
description: "Deploy the Azure executor as a hosted Foundry worker (az CLI + tiered ops).",
|
|
5860
|
-
details: "Creates a hosted agent version from the
|
|
5926
|
+
details: "Creates a hosted agent version from a container image (defaults to the public GHCR image ghcr.io/m8t-run/m8t-azure-executor, pulled anonymously \u2014 no local build needed), grants its identity Foundry User + Contributor (at --scope) + Key Vault Secrets User (brain KV), polls to active, and a2a-enables it as a target. Override with --image/--image-tag for a bring-your-own-registry image (e.g. a private ACR ref, which must be pushed first). Contributor scope is REQUIRED \u2014 pass --scope or --resource-group. Pass --grant-access-admin to additionally grant User Access Administrator (enables human-approved Tier-2 role/delete ops).",
|
|
5861
5927
|
examples: [
|
|
5862
5928
|
[
|
|
5863
5929
|
"Deploy scoped to a resource group",
|
|
@@ -5881,6 +5947,7 @@ var AzureExecDeployCommand = class extends M8tCommand {
|
|
|
5881
5947
|
output = Option26.String("--output");
|
|
5882
5948
|
skipQuotaCheck = Option26.Boolean("--skip-quota-check", false);
|
|
5883
5949
|
grantAccessAdmin = Option26.Boolean("--grant-access-admin", false);
|
|
5950
|
+
enableEmail = Option26.Boolean("--enable-email", false);
|
|
5884
5951
|
async executeCommand() {
|
|
5885
5952
|
if (!NAME_RE2.test(this.name)) {
|
|
5886
5953
|
throw new LocalCliError({
|
|
@@ -5925,8 +5992,15 @@ var AzureExecDeployCommand = class extends M8tCommand {
|
|
|
5925
5992
|
});
|
|
5926
5993
|
}
|
|
5927
5994
|
}
|
|
5995
|
+
if (this.enableEmail === true && (typeof this.resourceGroup !== "string" || this.resourceGroup.length === 0)) {
|
|
5996
|
+
throw new LocalCliError({
|
|
5997
|
+
code: "USAGE",
|
|
5998
|
+
message: "--enable-email requires --resource-group (the ACS resource lands in the Contributor-granted RG so the executor's identity can send).",
|
|
5999
|
+
hint: "Pass --resource-group <rg> (the same scope the executor is granted Contributor on)."
|
|
6000
|
+
});
|
|
6001
|
+
}
|
|
5928
6002
|
const imageInput = this.image ?? DEFAULT_IMAGE2;
|
|
5929
|
-
const repoRef = imageInput.includes("/") ? imageInput : `${
|
|
6003
|
+
const repoRef = imageInput.includes("/") ? imageInput : `${DEFAULT_REGISTRY2}/${imageInput}`;
|
|
5930
6004
|
const image = `${repoRef}:${this.imageTag ?? DEFAULT_TAG2}`;
|
|
5931
6005
|
const project = await resolveFoundryProject({
|
|
5932
6006
|
credential: credential2,
|
|
@@ -5955,11 +6029,27 @@ var AzureExecDeployCommand = class extends M8tCommand {
|
|
|
5955
6029
|
privateKeyPem,
|
|
5956
6030
|
write: (s) => this.context.stdout.write(s)
|
|
5957
6031
|
});
|
|
6032
|
+
let acsEnv = {};
|
|
6033
|
+
if (this.enableEmail === true && typeof this.resourceGroup === "string") {
|
|
6034
|
+
const resourceGroup = this.resourceGroup;
|
|
6035
|
+
onProgress?.("provisioning ACS Email (managed domain)\u2026");
|
|
6036
|
+
const acsClient = new CommunicationServiceManagementClient(credential2, subscriptionId);
|
|
6037
|
+
const acs = await provisionAcsEmail({
|
|
6038
|
+
client: acsClient,
|
|
6039
|
+
subscriptionId,
|
|
6040
|
+
resourceGroup,
|
|
6041
|
+
name: this.name,
|
|
6042
|
+
dataLocation: "United States",
|
|
6043
|
+
onProgress
|
|
6044
|
+
});
|
|
6045
|
+
acsEnv = { M8T_ACS_ENDPOINT: acs.endpoint, M8T_ACS_SENDER: acs.sender };
|
|
6046
|
+
}
|
|
5958
6047
|
const env = {
|
|
5959
6048
|
MODEL_DEPLOYMENT_NAME: this.modelDeployment ?? DEFAULT_MODEL2,
|
|
5960
6049
|
REASONING_EFFORT: "low",
|
|
5961
6050
|
GITHUB_APP_INSTALLATION_ID: installationId,
|
|
5962
|
-
AZURE_KEYVAULT_URI: kvUri
|
|
6051
|
+
AZURE_KEYVAULT_URI: kvUri,
|
|
6052
|
+
...acsEnv
|
|
5963
6053
|
};
|
|
5964
6054
|
for (const pair of this.env ?? []) {
|
|
5965
6055
|
const eq = pair.indexOf("=");
|
|
@@ -9570,6 +9660,13 @@ function renderDigestMarkdown(digest, opts) {
|
|
|
9570
9660
|
case "codified":
|
|
9571
9661
|
push("## Codified", `- \`${e.slug}\`${e.pr ? ` \u2014 PR ${e.pr}` : ""}${e.rejected ? ` \u2014 rejected: ${e.rejected}` : ""}`);
|
|
9572
9662
|
break;
|
|
9663
|
+
case "promotion": {
|
|
9664
|
+
const icon = e.outcome === "auto-merged" ? "\u2705" : e.outcome === "rejected" ? "\u274C" : "\u{1F6D1}";
|
|
9665
|
+
const scoreStr = typeof e.score === "number" ? `, score ${String(e.score)}` : "";
|
|
9666
|
+
const tags = e.defaulted ? ` [SAFETY explicit: ${e.defaulted.safety ? "no" : "yes"}, COLLISION explicit: ${e.defaulted.collision ? "no" : "yes"}]` : "";
|
|
9667
|
+
push("## Promotion", `- ${icon} \`${e.slug}\` \u2014 **${e.outcome.toUpperCase()}** (${e.decision}/${e.judgeStatus}${scoreStr}, origin: ${e.origin})${tags}${e.pr ? ` \u2014 PR ${e.pr}` : ""} \u2014 ${e.reason}`);
|
|
9668
|
+
break;
|
|
9669
|
+
}
|
|
9573
9670
|
default: {
|
|
9574
9671
|
const unknown = e;
|
|
9575
9672
|
const detail = unknown.detail ?? unknown.path ?? "";
|
|
@@ -9591,6 +9688,7 @@ function renderDigestMarkdown(digest, opts) {
|
|
|
9591
9688
|
"## Health",
|
|
9592
9689
|
"## Archived",
|
|
9593
9690
|
"## Codified",
|
|
9691
|
+
"## Promotion",
|
|
9594
9692
|
"## Other"
|
|
9595
9693
|
];
|
|
9596
9694
|
for (const header of order) {
|
|
@@ -10389,6 +10487,9 @@ var ZERO_WIDTH_RE = new RegExp("\u200B|\u200C|\u200D|\uFEFF", "g");
|
|
|
10389
10487
|
// ../../packages/brain/engine/dist/esm/librarian/janitor/frontmatter.js
|
|
10390
10488
|
import { parse as parseYaml9, stringify as stringifyYaml5 } from "yaml";
|
|
10391
10489
|
|
|
10490
|
+
// ../../packages/brain/engine/dist/esm/librarian/config.js
|
|
10491
|
+
import { parse as parseYaml10 } from "yaml";
|
|
10492
|
+
|
|
10392
10493
|
// src/lib/dream-discovery.ts
|
|
10393
10494
|
init_http();
|
|
10394
10495
|
init_errors();
|
|
@@ -10864,6 +10965,324 @@ function defaultDeps(overrides) {
|
|
|
10864
10965
|
return deps;
|
|
10865
10966
|
}
|
|
10866
10967
|
|
|
10968
|
+
// src/commands/foundry/create.ts
|
|
10969
|
+
import { Command as Command41, Option as Option39 } from "clipanion";
|
|
10970
|
+
|
|
10971
|
+
// src/lib/foundry-create.ts
|
|
10972
|
+
init_errors();
|
|
10973
|
+
import { createHash as createHash3 } from "crypto";
|
|
10974
|
+
function rethrowIfAzUnavailable(e) {
|
|
10975
|
+
if (e instanceof LocalCliError && (e.code === "AUTH_NOT_SIGNED_IN" || e.code === "AZ_NOT_INSTALLED")) {
|
|
10976
|
+
throw e;
|
|
10977
|
+
}
|
|
10978
|
+
}
|
|
10979
|
+
function deriveAccountName(subscriptionId) {
|
|
10980
|
+
const h = createHash3("sha256").update(subscriptionId).digest("hex").slice(0, 12);
|
|
10981
|
+
return `m8t${h}`;
|
|
10982
|
+
}
|
|
10983
|
+
function assertHostedRegion(location) {
|
|
10984
|
+
if (isHostedRegionSupported(location)) return;
|
|
10985
|
+
throw new LocalCliError({
|
|
10986
|
+
code: "FOUNDRY_REGION_NOT_ELIGIBLE",
|
|
10987
|
+
message: `Region '${location}' is not eligible for hosted Foundry agents.`,
|
|
10988
|
+
hint: `Use a hosted-eligible region, e.g. ${HOSTED_AGENT_REGIONS.slice(0, 4).join(", ")}.`
|
|
10989
|
+
});
|
|
10990
|
+
}
|
|
10991
|
+
async function checkModelQuota2(region, model) {
|
|
10992
|
+
const usages = await listModelQuota(region);
|
|
10993
|
+
const { verdict, quotad } = modelQuotaVerdict(usages, model);
|
|
10994
|
+
if (verdict === "no_quota") {
|
|
10995
|
+
throw new LocalCliError({
|
|
10996
|
+
code: "FOUNDRY_MODEL_NO_QUOTA",
|
|
10997
|
+
message: `Model '${model}' has 0 quota in ${region}.`,
|
|
10998
|
+
hint: `Pick a quota'd model (--model ${quotad[0] ?? "gpt-4.1-mini"}) or request quota in ${region}.`
|
|
10999
|
+
});
|
|
11000
|
+
}
|
|
11001
|
+
return verdict === "ok" ? "ok" : "unknown";
|
|
11002
|
+
}
|
|
11003
|
+
async function ensureAccount(args) {
|
|
11004
|
+
let existing = null;
|
|
11005
|
+
try {
|
|
11006
|
+
existing = JSON.parse(
|
|
11007
|
+
await runAz(["cognitiveservices", "account", "show", "--name", args.account, "--resource-group", args.resourceGroup, "--output", "json"])
|
|
11008
|
+
);
|
|
11009
|
+
} catch (e) {
|
|
11010
|
+
rethrowIfAzUnavailable(e);
|
|
11011
|
+
existing = null;
|
|
11012
|
+
}
|
|
11013
|
+
if (existing?.properties?.customSubDomainName && existing.properties.allowProjectManagement) {
|
|
11014
|
+
return { created: false, accountScope: existing.id ?? "", subdomain: existing.properties.customSubDomainName };
|
|
11015
|
+
}
|
|
11016
|
+
let createdOut;
|
|
11017
|
+
try {
|
|
11018
|
+
createdOut = JSON.parse(
|
|
11019
|
+
await runAz([
|
|
11020
|
+
"cognitiveservices",
|
|
11021
|
+
"account",
|
|
11022
|
+
"create",
|
|
11023
|
+
"--name",
|
|
11024
|
+
args.account,
|
|
11025
|
+
"--resource-group",
|
|
11026
|
+
args.resourceGroup,
|
|
11027
|
+
"--location",
|
|
11028
|
+
args.location,
|
|
11029
|
+
"--kind",
|
|
11030
|
+
"AIServices",
|
|
11031
|
+
"--sku",
|
|
11032
|
+
"S0",
|
|
11033
|
+
"--custom-domain",
|
|
11034
|
+
args.account,
|
|
11035
|
+
"--allow-project-management",
|
|
11036
|
+
"--yes",
|
|
11037
|
+
"--output",
|
|
11038
|
+
"json"
|
|
11039
|
+
])
|
|
11040
|
+
);
|
|
11041
|
+
} catch (e) {
|
|
11042
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
11043
|
+
if (/subdomain|custom.?domain|already in use|already exists/i.test(msg)) {
|
|
11044
|
+
throw new LocalCliError({
|
|
11045
|
+
code: "FOUNDRY_ACCOUNT_SUBDOMAIN_CONFLICT",
|
|
11046
|
+
message: `The custom subdomain '${args.account}' is unavailable (globally taken).`,
|
|
11047
|
+
hint: "Pass --account <globally-unique-name>."
|
|
11048
|
+
});
|
|
11049
|
+
}
|
|
11050
|
+
throw e;
|
|
11051
|
+
}
|
|
11052
|
+
if (!createdOut.properties?.customSubDomainName || !createdOut.properties.allowProjectManagement) {
|
|
11053
|
+
throw new LocalCliError({
|
|
11054
|
+
code: "FOUNDRY_ACCOUNT_NOT_CONFORMING",
|
|
11055
|
+
message: `Account '${args.account}' exists but lacks a custom subdomain or project management, and could not be converged.`,
|
|
11056
|
+
hint: "Use a fresh --account name or resource group."
|
|
11057
|
+
});
|
|
11058
|
+
}
|
|
11059
|
+
return { created: !existing, accountScope: createdOut.id ?? "", subdomain: createdOut.properties.customSubDomainName };
|
|
11060
|
+
}
|
|
11061
|
+
function readProjectEndpoint(show) {
|
|
11062
|
+
const ep = show.properties?.endpoints?.["AI Foundry API"];
|
|
11063
|
+
if (!ep) {
|
|
11064
|
+
throw new LocalCliError({
|
|
11065
|
+
code: "FOUNDRY_PROJECT_NO_ENDPOINT",
|
|
11066
|
+
message: "Project created but no 'AI Foundry API' endpoint was returned.",
|
|
11067
|
+
hint: "Re-run after a moment, or check 'az cognitiveservices account project show'."
|
|
11068
|
+
});
|
|
11069
|
+
}
|
|
11070
|
+
return ep;
|
|
11071
|
+
}
|
|
11072
|
+
var projectShowArgs = (a) => [
|
|
11073
|
+
"cognitiveservices",
|
|
11074
|
+
"account",
|
|
11075
|
+
"project",
|
|
11076
|
+
"show",
|
|
11077
|
+
"--name",
|
|
11078
|
+
a.account,
|
|
11079
|
+
"--resource-group",
|
|
11080
|
+
a.resourceGroup,
|
|
11081
|
+
"--project-name",
|
|
11082
|
+
a.project,
|
|
11083
|
+
"--output",
|
|
11084
|
+
"json"
|
|
11085
|
+
];
|
|
11086
|
+
async function ensureProject(args) {
|
|
11087
|
+
let show = null;
|
|
11088
|
+
try {
|
|
11089
|
+
show = JSON.parse(await runAz(projectShowArgs(args)));
|
|
11090
|
+
} catch (e) {
|
|
11091
|
+
rethrowIfAzUnavailable(e);
|
|
11092
|
+
show = null;
|
|
11093
|
+
}
|
|
11094
|
+
if (show) {
|
|
11095
|
+
return { created: false, endpoint: readProjectEndpoint(show), projectPrincipalId: show.identity?.principalId ?? null };
|
|
11096
|
+
}
|
|
11097
|
+
await runAz([
|
|
11098
|
+
"cognitiveservices",
|
|
11099
|
+
"account",
|
|
11100
|
+
"project",
|
|
11101
|
+
"create",
|
|
11102
|
+
"--name",
|
|
11103
|
+
args.account,
|
|
11104
|
+
"--resource-group",
|
|
11105
|
+
args.resourceGroup,
|
|
11106
|
+
"--project-name",
|
|
11107
|
+
args.project,
|
|
11108
|
+
"--location",
|
|
11109
|
+
args.location,
|
|
11110
|
+
"--output",
|
|
11111
|
+
"json"
|
|
11112
|
+
]);
|
|
11113
|
+
const created = JSON.parse(await runAz(projectShowArgs(args)));
|
|
11114
|
+
return { created: true, endpoint: readProjectEndpoint(created), projectPrincipalId: created.identity?.principalId ?? null };
|
|
11115
|
+
}
|
|
11116
|
+
async function ensureDeployment(args) {
|
|
11117
|
+
let current = null;
|
|
11118
|
+
try {
|
|
11119
|
+
const show = JSON.parse(
|
|
11120
|
+
await runAz(["cognitiveservices", "account", "deployment", "show", "--name", args.account, "--resource-group", args.resourceGroup, "--deployment-name", args.model, "--output", "json"])
|
|
11121
|
+
);
|
|
11122
|
+
current = show.sku?.capacity ?? null;
|
|
11123
|
+
} catch (e) {
|
|
11124
|
+
rethrowIfAzUnavailable(e);
|
|
11125
|
+
current = null;
|
|
11126
|
+
}
|
|
11127
|
+
if (current !== null && current >= args.capacity) return { created: false };
|
|
11128
|
+
if (current !== null && current < args.capacity) args.onProgress?.(`scaling ${args.model} ${String(current)}\u2192${String(args.capacity)}`);
|
|
11129
|
+
await runAz([
|
|
11130
|
+
"cognitiveservices",
|
|
11131
|
+
"account",
|
|
11132
|
+
"deployment",
|
|
11133
|
+
"create",
|
|
11134
|
+
"--name",
|
|
11135
|
+
args.account,
|
|
11136
|
+
"--resource-group",
|
|
11137
|
+
args.resourceGroup,
|
|
11138
|
+
"--deployment-name",
|
|
11139
|
+
args.model,
|
|
11140
|
+
"--model-name",
|
|
11141
|
+
args.model,
|
|
11142
|
+
"--model-version",
|
|
11143
|
+
args.modelVersion,
|
|
11144
|
+
"--model-format",
|
|
11145
|
+
"OpenAI",
|
|
11146
|
+
"--sku-name",
|
|
11147
|
+
"GlobalStandard",
|
|
11148
|
+
"--sku-capacity",
|
|
11149
|
+
String(args.capacity),
|
|
11150
|
+
"--output",
|
|
11151
|
+
"json"
|
|
11152
|
+
]);
|
|
11153
|
+
return { created: current === null };
|
|
11154
|
+
}
|
|
11155
|
+
var REASONING_MIN_CAPACITY = 250;
|
|
11156
|
+
async function createFoundryProject(args) {
|
|
11157
|
+
assertHostedRegion(args.location);
|
|
11158
|
+
if (!args.skipQuotaCheck) {
|
|
11159
|
+
const verdict = await checkModelQuota2(args.location, args.model);
|
|
11160
|
+
if (verdict === "unknown") {
|
|
11161
|
+
args.onProgress?.(`could not confirm quota for '${args.model}' in ${args.location}; proceeding`);
|
|
11162
|
+
}
|
|
11163
|
+
}
|
|
11164
|
+
if (isReasoningModel(args.model) && args.capacity < REASONING_MIN_CAPACITY) {
|
|
11165
|
+
args.onProgress?.(`note: reasoning model '${args.model}' at capacity ${String(args.capacity)} may 429; consider --capacity ${String(REASONING_MIN_CAPACITY)}`);
|
|
11166
|
+
}
|
|
11167
|
+
args.onProgress?.(`ensuring Foundry account ${args.account}\u2026`);
|
|
11168
|
+
const acct = await ensureAccount({ account: args.account, resourceGroup: args.resourceGroup, location: args.location });
|
|
11169
|
+
args.onProgress?.(`ensuring project ${args.project}\u2026`);
|
|
11170
|
+
const proj = await ensureProject({ account: args.account, resourceGroup: args.resourceGroup, project: args.project, location: args.location });
|
|
11171
|
+
args.onProgress?.(`ensuring model deployment ${args.model} (capacity ${String(args.capacity)})\u2026`);
|
|
11172
|
+
const dep = await ensureDeployment({
|
|
11173
|
+
account: args.account,
|
|
11174
|
+
resourceGroup: args.resourceGroup,
|
|
11175
|
+
model: args.model,
|
|
11176
|
+
modelVersion: args.modelVersion,
|
|
11177
|
+
capacity: args.capacity,
|
|
11178
|
+
onProgress: args.onProgress
|
|
11179
|
+
});
|
|
11180
|
+
return {
|
|
11181
|
+
endpoint: proj.endpoint,
|
|
11182
|
+
accountName: args.account,
|
|
11183
|
+
accountScope: acct.accountScope,
|
|
11184
|
+
projectName: args.project,
|
|
11185
|
+
region: args.location,
|
|
11186
|
+
projectPrincipalId: proj.projectPrincipalId,
|
|
11187
|
+
model: args.model,
|
|
11188
|
+
capacity: args.capacity,
|
|
11189
|
+
created: { account: acct.created, project: proj.created, deployment: dep.created }
|
|
11190
|
+
};
|
|
11191
|
+
}
|
|
11192
|
+
|
|
11193
|
+
// src/commands/foundry/create.ts
|
|
11194
|
+
init_errors();
|
|
11195
|
+
var FoundryCreateCommand = class extends M8tCommand {
|
|
11196
|
+
static paths = [["foundry", "create"]];
|
|
11197
|
+
static usage = Command41.Usage({
|
|
11198
|
+
description: "Create an AI Foundry (AIServices) account + project + model deployment from scratch.",
|
|
11199
|
+
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).",
|
|
11200
|
+
examples: [
|
|
11201
|
+
["Create from zero in a resource group", "$0 foundry create --resource-group rg-m8t-stack --location eastus2"],
|
|
11202
|
+
["Pick names + model", "$0 foundry create --resource-group rg-m8t-stack --location eastus2 --account myfoundry --project m8t --model gpt-4.1-mini"],
|
|
11203
|
+
["Higher capacity for a reasoning model", "$0 foundry create --resource-group rg-m8t-stack --location eastus2 --model gpt-5-mini --capacity 250"]
|
|
11204
|
+
]
|
|
11205
|
+
});
|
|
11206
|
+
resourceGroup = Option39.String("--resource-group");
|
|
11207
|
+
location = Option39.String("--location");
|
|
11208
|
+
account = Option39.String("--account");
|
|
11209
|
+
project = Option39.String("--project", "m8t");
|
|
11210
|
+
model = Option39.String("--model", "gpt-4.1-mini");
|
|
11211
|
+
modelVersion = Option39.String("--model-version", "2025-04-14");
|
|
11212
|
+
capacity = Option39.String("--capacity", "50");
|
|
11213
|
+
subscription = Option39.String("--subscription");
|
|
11214
|
+
skipQuotaCheck = Option39.Boolean("--skip-quota-check", false);
|
|
11215
|
+
output = Option39.String("--output");
|
|
11216
|
+
async executeCommand() {
|
|
11217
|
+
const mode = resolveOutputMode(
|
|
11218
|
+
this.output,
|
|
11219
|
+
this.context.stdout
|
|
11220
|
+
);
|
|
11221
|
+
const resourceGroup = typeof this.resourceGroup === "string" ? this.resourceGroup : void 0;
|
|
11222
|
+
const location = typeof this.location === "string" ? this.location : void 0;
|
|
11223
|
+
if (!resourceGroup) {
|
|
11224
|
+
throw new LocalCliError({ code: "USAGE", message: "--resource-group is required.", hint: "Example: m8t foundry create --resource-group rg-m8t-stack --location eastus2" });
|
|
11225
|
+
}
|
|
11226
|
+
if (!location) {
|
|
11227
|
+
throw new LocalCliError({ code: "USAGE", message: "--location is required.", hint: "Example: m8t foundry create --resource-group rg-m8t-stack --location eastus2" });
|
|
11228
|
+
}
|
|
11229
|
+
const subscriptionOpt = typeof this.subscription === "string" ? this.subscription : void 0;
|
|
11230
|
+
if (subscriptionOpt) await runAz(["account", "set", "--subscription", subscriptionOpt]);
|
|
11231
|
+
const account = await getAzAccount();
|
|
11232
|
+
const subscriptionId = subscriptionOpt ?? account.subscriptionId;
|
|
11233
|
+
const accountName = typeof this.account === "string" ? this.account : deriveAccountName(subscriptionId);
|
|
11234
|
+
const project = typeof this.project === "string" ? this.project : "m8t";
|
|
11235
|
+
const model = typeof this.model === "string" ? this.model : "gpt-4.1-mini";
|
|
11236
|
+
const modelVersion = typeof this.modelVersion === "string" ? this.modelVersion : "2025-04-14";
|
|
11237
|
+
const capacity = typeof this.capacity === "string" ? Number(this.capacity) : 50;
|
|
11238
|
+
const onProgress = mode === "pretty" ? (m) => this.context.stderr.write(` ${colors.dim(m)}
|
|
11239
|
+
`) : void 0;
|
|
11240
|
+
const result = await createFoundryProject({
|
|
11241
|
+
resourceGroup,
|
|
11242
|
+
location,
|
|
11243
|
+
account: accountName,
|
|
11244
|
+
project,
|
|
11245
|
+
model,
|
|
11246
|
+
modelVersion,
|
|
11247
|
+
capacity,
|
|
11248
|
+
// `=== true`: clipanion's Option descriptor is truthy until argv parse, so a
|
|
11249
|
+
// bare check would force skipQuotaCheck on under test construction.
|
|
11250
|
+
skipQuotaCheck: this.skipQuotaCheck === true,
|
|
11251
|
+
onProgress
|
|
11252
|
+
});
|
|
11253
|
+
if (mode === "json") {
|
|
11254
|
+
this.context.stdout.write(
|
|
11255
|
+
renderJson({
|
|
11256
|
+
endpoint: result.endpoint,
|
|
11257
|
+
accountName: result.accountName,
|
|
11258
|
+
accountResourceId: result.accountScope,
|
|
11259
|
+
projectName: result.projectName,
|
|
11260
|
+
region: result.region,
|
|
11261
|
+
model: result.model,
|
|
11262
|
+
capacity: result.capacity,
|
|
11263
|
+
created: result.created
|
|
11264
|
+
}) + "\n"
|
|
11265
|
+
);
|
|
11266
|
+
return 0;
|
|
11267
|
+
}
|
|
11268
|
+
this.context.stdout.write(
|
|
11269
|
+
renderKeyValueBlock([
|
|
11270
|
+
{ key: "endpoint", value: result.endpoint },
|
|
11271
|
+
{ key: "account", value: result.accountName },
|
|
11272
|
+
{ key: "project", value: result.projectName },
|
|
11273
|
+
{ key: "region", value: result.region },
|
|
11274
|
+
{ key: "model", value: `${result.model} (capacity ${String(result.capacity)})` }
|
|
11275
|
+
]) + "\n"
|
|
11276
|
+
);
|
|
11277
|
+
const made = Object.entries(result.created).filter(([, v]) => v).map(([k]) => k);
|
|
11278
|
+
this.context.stdout.write(colors.dim(`created: ${made.length ? made.join(", ") : "nothing (all present)"}
|
|
11279
|
+
`));
|
|
11280
|
+
this.context.stdout.write(colors.dim(`next: 'm8t deploy --foundry-endpoint ${result.endpoint}' to deploy the gateway.
|
|
11281
|
+
`));
|
|
11282
|
+
return 0;
|
|
11283
|
+
}
|
|
11284
|
+
};
|
|
11285
|
+
|
|
10867
11286
|
// src/cli.ts
|
|
10868
11287
|
var cli = new Cli({
|
|
10869
11288
|
binaryName: "m8t",
|
|
@@ -10910,6 +11329,7 @@ cli.register(A2aDisableCommand);
|
|
|
10910
11329
|
cli.register(EvalSkillCommand);
|
|
10911
11330
|
cli.register(EvalExamCommand);
|
|
10912
11331
|
cli.register(DreamRunCommand);
|
|
11332
|
+
cli.register(FoundryCreateCommand);
|
|
10913
11333
|
async function main() {
|
|
10914
11334
|
try {
|
|
10915
11335
|
return await cli.run(process.argv.slice(2));
|