@m8t-stack/cli 0.2.121 → 0.2.123
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 +711 -460
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1454,7 +1454,7 @@ function installFoundryDnsShim() {
|
|
|
1454
1454
|
}
|
|
1455
1455
|
|
|
1456
1456
|
// src/lib/package-version.ts
|
|
1457
|
-
var CLI_VERSION = "0.2.
|
|
1457
|
+
var CLI_VERSION = "0.2.123";
|
|
1458
1458
|
|
|
1459
1459
|
// src/lib/render-error.ts
|
|
1460
1460
|
init_errors();
|
|
@@ -16353,8 +16353,8 @@ function isM8tBrainMarker(yamlText) {
|
|
|
16353
16353
|
}
|
|
16354
16354
|
if (!parsed || typeof parsed !== "object") return false;
|
|
16355
16355
|
const root = parsed;
|
|
16356
|
-
const
|
|
16357
|
-
return
|
|
16356
|
+
const isObj3 = (v) => typeof v === "object" && v !== null;
|
|
16357
|
+
return isObj3(root.engine) || isObj3(root.processes) || isObj3(root.link);
|
|
16358
16358
|
}
|
|
16359
16359
|
async function createBlankRepo(args) {
|
|
16360
16360
|
const res = await fetch(`https://api.github.com/orgs/${args.org}/repos`, {
|
|
@@ -19485,15 +19485,16 @@ async function stagePublicImageIfNeeded(args) {
|
|
|
19485
19485
|
const notes = [];
|
|
19486
19486
|
const { host, repo, tag } = parseImageRef(args.image);
|
|
19487
19487
|
if (isAcrHost(host)) return { image: args.image, staged: false, notes };
|
|
19488
|
+
const sourceRef = args.digest ? `${host}/${repo}@${args.digest}` : args.image;
|
|
19488
19489
|
const acrName = deriveAcrName(args.project.accountName);
|
|
19489
19490
|
const resourceGroup = resourceGroupFromScope(args.project.accountScope);
|
|
19490
|
-
args.onProgress?.(`staging ${
|
|
19491
|
+
args.onProgress?.(`staging ${sourceRef} into ${acrName} (server-side import)\u2026`);
|
|
19491
19492
|
const acr = await ensureAcr({ acrName, resourceGroup, location: args.project.region });
|
|
19492
19493
|
if (acr.created) notes.push(`created ACR '${acrName}' in ${resourceGroup} for hosted-agent images.`);
|
|
19493
|
-
const { imported } = await ensureImageInAcr({ acrName, sourceRef
|
|
19494
|
+
const { imported } = await ensureImageInAcr({ acrName, sourceRef, repo, tag });
|
|
19494
19495
|
const acrRef = `${acr.loginServer}/${repo}:${tag}`;
|
|
19495
19496
|
notes.push(
|
|
19496
|
-
imported ? `imported ${
|
|
19497
|
+
imported ? `imported ${sourceRef} \u2192 ${acrRef} (server-side, no local build).` : `image already present in ${acrName}; using ${acrRef}.`
|
|
19497
19498
|
);
|
|
19498
19499
|
return { image: acrRef, staged: true, notes };
|
|
19499
19500
|
}
|
|
@@ -20536,6 +20537,12 @@ var AzureExecDeployCommand = class extends M8tCommand {
|
|
|
20536
20537
|
name = Option31.String();
|
|
20537
20538
|
image = Option31.String("--image");
|
|
20538
20539
|
imageTag = Option31.String("--image-tag");
|
|
20540
|
+
// `sha256:<hex>` to import FROM, when the caller knows exactly which bytes it
|
|
20541
|
+
// wants. The cloud installer passes the release manifest's digest so the
|
|
20542
|
+
// executor an install deploys is the one the RELEASE names, not whatever the
|
|
20543
|
+
// engine's own baked pin happened to be — the same correction the gateway got.
|
|
20544
|
+
// The destination in the customer's ACR stays tagged; only the source is pinned.
|
|
20545
|
+
imageDigest = Option31.String("--image-digest", { description: "Import the image from this digest instead of the tag. Must be sha256:<64 hex>." });
|
|
20539
20546
|
size = Option31.String("--size");
|
|
20540
20547
|
scope = Option31.String("--scope");
|
|
20541
20548
|
resourceGroup = Option31.String("--resource-group");
|
|
@@ -20624,7 +20631,18 @@ var AzureExecDeployCommand = class extends M8tCommand {
|
|
|
20624
20631
|
currentMetadata = { ...current.metadata ?? {} };
|
|
20625
20632
|
} catch {
|
|
20626
20633
|
}
|
|
20627
|
-
const
|
|
20634
|
+
const imageDigest = typeof this.imageDigest === "string" ? this.imageDigest.trim() : "";
|
|
20635
|
+
if (imageDigest && !/^sha256:[0-9a-f]{64}$/.test(imageDigest)) {
|
|
20636
|
+
throw new LocalCliError({
|
|
20637
|
+
code: "AZURE_EXEC_IMAGE_DIGEST_INVALID",
|
|
20638
|
+
message: `--image-digest must be sha256:<64 hex>, got ${JSON.stringify(imageDigest)}.`
|
|
20639
|
+
});
|
|
20640
|
+
}
|
|
20641
|
+
const staged = await stagePublicImageIfNeeded({
|
|
20642
|
+
image: requestedImage,
|
|
20643
|
+
project,
|
|
20644
|
+
...imageDigest ? { digest: imageDigest } : {}
|
|
20645
|
+
});
|
|
20628
20646
|
for (const n of staged.notes) {
|
|
20629
20647
|
warnings.add(n);
|
|
20630
20648
|
}
|
|
@@ -22659,6 +22677,13 @@ var PlatformStatusCommand = class extends M8tCommand {
|
|
|
22659
22677
|
kind: "platform-status",
|
|
22660
22678
|
gateway: resourceGroup,
|
|
22661
22679
|
targetVersion: manifest.platform.tag,
|
|
22680
|
+
// What the install RECORDS it is running, as opposed to what the channel
|
|
22681
|
+
// currently offers (targetVersion). `managed` already says whether a stamp
|
|
22682
|
+
// exists; this says what it holds — the row the updater diffs against and
|
|
22683
|
+
// fleet telemetry aggregates, which was previously unreadable from this
|
|
22684
|
+
// command at all. null when unstamped, never absent, so a consumer can
|
|
22685
|
+
// tell "no stamp" from "field not emitted by an older CLI".
|
|
22686
|
+
installedVersion: stamp?.platformVersion ?? null,
|
|
22662
22687
|
managed: stamp !== null,
|
|
22663
22688
|
rows,
|
|
22664
22689
|
...verify ? { verify } : {}
|
|
@@ -22666,7 +22691,9 @@ var PlatformStatusCommand = class extends M8tCommand {
|
|
|
22666
22691
|
);
|
|
22667
22692
|
return 0;
|
|
22668
22693
|
}
|
|
22669
|
-
log(colors.field(
|
|
22694
|
+
log(colors.field(
|
|
22695
|
+
`installed: ${stamp?.platformVersion ?? "(none)"} target: ${manifest.platform.tag}` + (stamp === null ? " (unmanaged \u2014 no stamp found)" : "")
|
|
22696
|
+
));
|
|
22670
22697
|
this.context.stdout.write(this.renderRows(rows) + "\n");
|
|
22671
22698
|
if (verify) {
|
|
22672
22699
|
log("");
|
|
@@ -24533,74 +24560,26 @@ import { DefaultAzureCredential as DefaultAzureCredential20, ManagedIdentityCred
|
|
|
24533
24560
|
init_errors();
|
|
24534
24561
|
|
|
24535
24562
|
// src/lib/platform-stamp-seed.ts
|
|
24536
|
-
var
|
|
24537
|
-
var BARE_SEMVER = /^\d+\.\d+\.\d+$/;
|
|
24563
|
+
var BARE_SEMVER = /^\d+\.\d+\.\d+(?:-canary\.(?:0|[1-9]\d*))?$/;
|
|
24538
24564
|
function isBarePlatformVersion(v) {
|
|
24539
24565
|
return typeof v === "string" && BARE_SEMVER.test(v);
|
|
24540
24566
|
}
|
|
24541
|
-
function
|
|
24542
|
-
|
|
24543
|
-
}
|
|
24544
|
-
function validateDescriptor(raw) {
|
|
24545
|
-
const errors = [];
|
|
24546
|
-
if (!isObj3(raw)) return ["descriptor is not an object"];
|
|
24547
|
-
if (raw.schemaVersion !== 1) errors.push("schemaVersion must be the integer 1");
|
|
24548
|
-
if (typeof raw.platformVersion !== "string" || !BARE_SEMVER.test(raw.platformVersion)) {
|
|
24549
|
-
errors.push(`platformVersion must be a bare X.Y.Z release version, got ${JSON.stringify(raw.platformVersion)}`);
|
|
24550
|
-
}
|
|
24551
|
-
if (!isObj3(raw.generatedFrom)) {
|
|
24552
|
-
errors.push("generatedFrom is required");
|
|
24553
|
-
} else {
|
|
24554
|
-
if (typeof raw.generatedFrom.commit !== "string") {
|
|
24555
|
-
errors.push("generatedFrom.commit is required");
|
|
24556
|
-
}
|
|
24557
|
-
if (typeof raw.generatedFrom.generatedAt !== "string") {
|
|
24558
|
-
errors.push("generatedFrom.generatedAt is required");
|
|
24559
|
-
}
|
|
24560
|
-
}
|
|
24561
|
-
const images = raw.images;
|
|
24562
|
-
if (!isObj3(images)) {
|
|
24563
|
-
errors.push("images is required");
|
|
24564
|
-
} else {
|
|
24565
|
-
for (const key2 of IMAGE_COMPONENTS) {
|
|
24566
|
-
const img = images[key2];
|
|
24567
|
-
if (!isObj3(img)) {
|
|
24568
|
-
errors.push(`images.${key2} is required`);
|
|
24569
|
-
continue;
|
|
24570
|
-
}
|
|
24571
|
-
if (typeof img.ref !== "string" || img.ref.length === 0) errors.push(`images.${key2}.ref is required`);
|
|
24572
|
-
if (typeof img.tag !== "string" || img.tag.length === 0) errors.push(`images.${key2}.tag is required`);
|
|
24573
|
-
if (typeof img.digest !== "string") errors.push(`images.${key2}.digest must be a string (may be empty)`);
|
|
24574
|
-
}
|
|
24575
|
-
}
|
|
24576
|
-
const content = raw.content;
|
|
24577
|
-
if (content !== void 0) {
|
|
24578
|
-
if (!isObj3(content)) {
|
|
24579
|
-
errors.push("content must be an object when present");
|
|
24580
|
-
} else {
|
|
24581
|
-
if (!isObj3(content.personas)) errors.push("content.personas must be an object");
|
|
24582
|
-
if (typeof content.infra !== "string") errors.push("content.infra must be a string");
|
|
24583
|
-
if (typeof content.brainSeedsRev !== "string") errors.push("content.brainSeedsRev must be a string");
|
|
24584
|
-
}
|
|
24585
|
-
}
|
|
24586
|
-
return errors;
|
|
24587
|
-
}
|
|
24588
|
-
function buildSeedStamp(d, cliVersion, nowIso, platformVersionOverride) {
|
|
24589
|
-
if (platformVersionOverride !== void 0 && !isBarePlatformVersion(platformVersionOverride)) {
|
|
24590
|
-
throw new Error(`platformVersionOverride must be a bare X.Y.Z release version, got ${JSON.stringify(platformVersionOverride)}`);
|
|
24567
|
+
function buildSeedStamp(m, cliVersion, nowIso) {
|
|
24568
|
+
if (!isBarePlatformVersion(m.platform.version)) {
|
|
24569
|
+
throw new Error(`manifest platform.version must be a bare X.Y.Z release version, got ${JSON.stringify(m.platform.version)}`);
|
|
24591
24570
|
}
|
|
24592
24571
|
const img = (k) => ({
|
|
24593
|
-
tag:
|
|
24594
|
-
digest:
|
|
24572
|
+
tag: m.components[k].tag,
|
|
24573
|
+
digest: m.components[k].digest,
|
|
24595
24574
|
state: "managed"
|
|
24596
24575
|
});
|
|
24597
24576
|
const personas = {};
|
|
24598
|
-
for (const [name,
|
|
24599
|
-
personas[name] = { treeSha, foundryVersion: "" };
|
|
24577
|
+
for (const [name, item] of Object.entries(m.components.personas.items)) {
|
|
24578
|
+
personas[name] = { treeSha: item.treeSha, foundryVersion: "" };
|
|
24600
24579
|
}
|
|
24601
24580
|
const stamp = {
|
|
24602
24581
|
schemaVersion: 1,
|
|
24603
|
-
platformVersion: platformTag(
|
|
24582
|
+
platformVersion: platformTag(m.platform.version),
|
|
24604
24583
|
previousPlatformVersion: null,
|
|
24605
24584
|
updatedAt: nowIso,
|
|
24606
24585
|
lastResult: "success",
|
|
@@ -24611,22 +24590,16 @@ function buildSeedStamp(d, cliVersion, nowIso, platformVersionOverride) {
|
|
|
24611
24590
|
codingAgent: img("codingAgent"),
|
|
24612
24591
|
azureExecutor: img("azureExecutor"),
|
|
24613
24592
|
personas,
|
|
24614
|
-
infra: { treeSha:
|
|
24593
|
+
infra: { treeSha: m.components.infra?.treeSha ?? "" }
|
|
24615
24594
|
}
|
|
24616
24595
|
};
|
|
24617
|
-
|
|
24596
|
+
const seedRev = seedSetRev(m);
|
|
24597
|
+
if (seedRev) stamp.components.brainSeeds = { seedRev };
|
|
24618
24598
|
return stamp;
|
|
24619
24599
|
}
|
|
24620
24600
|
|
|
24621
24601
|
// src/commands/platform/seed-stamp.ts
|
|
24622
24602
|
async function seedStamp2(args) {
|
|
24623
|
-
if (args.platformVersion !== void 0 && !isBarePlatformVersion(args.platformVersion)) {
|
|
24624
|
-
throw new LocalCliError({
|
|
24625
|
-
code: "PLATFORM_SEED_VERSION_INVALID",
|
|
24626
|
-
message: `--platform-version must be a bare X.Y.Z release version, got ${JSON.stringify(args.platformVersion)}.`,
|
|
24627
|
-
hint: "Pass the platform release this install is deploying, e.g. --platform-version 0.7.8. Omit the flag to stamp the version baked into the installer image."
|
|
24628
|
-
});
|
|
24629
|
-
}
|
|
24630
24603
|
const readFileImpl = args.readFileImpl ?? ((p) => readFileSync20(p, "utf8"));
|
|
24631
24604
|
const readStampImpl = args.readStampImpl ?? readStamp;
|
|
24632
24605
|
const writeStampImpl = args.writeStampImpl ?? ((stamp2, o) => writeStamp({ ...o, stamp: stamp2 }));
|
|
@@ -24634,34 +24607,34 @@ async function seedStamp2(args) {
|
|
|
24634
24607
|
const ctx = { credential: args.credential, subscriptionId: args.subscriptionId, resourceGroup: args.resourceGroup };
|
|
24635
24608
|
let raw;
|
|
24636
24609
|
try {
|
|
24637
|
-
raw = JSON.parse(readFileImpl(args.
|
|
24610
|
+
raw = JSON.parse(readFileImpl(args.manifestPath));
|
|
24638
24611
|
} catch (e) {
|
|
24639
24612
|
throw new LocalCliError({
|
|
24640
|
-
code: "
|
|
24641
|
-
message: `Cannot read the
|
|
24642
|
-
hint: "The
|
|
24613
|
+
code: "PLATFORM_SEED_MANIFEST_UNREADABLE",
|
|
24614
|
+
message: `Cannot read the release manifest at ${args.manifestPath}.`,
|
|
24615
|
+
hint: "The installer writes it at preflight after verifying its digest. Reaching this without one means the manifest gate was bypassed.",
|
|
24643
24616
|
cause: e
|
|
24644
24617
|
});
|
|
24645
24618
|
}
|
|
24646
|
-
const errors =
|
|
24619
|
+
const errors = validateManifest(raw);
|
|
24647
24620
|
if (errors.length > 0) {
|
|
24648
24621
|
throw new LocalCliError({
|
|
24649
|
-
code: "
|
|
24650
|
-
message: `The
|
|
24651
|
-
hint: "
|
|
24622
|
+
code: "PLATFORM_SEED_MANIFEST_INVALID",
|
|
24623
|
+
message: `The release manifest at ${args.manifestPath} is not valid: ${errors.join("; ")}.`,
|
|
24624
|
+
hint: "This install was launched against a manifest the platform cannot read. Nothing was stamped."
|
|
24652
24625
|
});
|
|
24653
24626
|
}
|
|
24654
|
-
const
|
|
24627
|
+
const manifest = raw;
|
|
24655
24628
|
const { accountName } = await discoverImpl(ctx);
|
|
24656
24629
|
const existing = await readStampImpl(ctx);
|
|
24657
24630
|
if (existing) {
|
|
24658
24631
|
args.onProgress?.(`platform stamp already present (${existing.platformVersion}) \u2014 leaving it untouched.`);
|
|
24659
24632
|
return { outcome: "already-stamped", platformVersion: existing.platformVersion, storageAccount: accountName };
|
|
24660
24633
|
}
|
|
24661
|
-
const stamp = buildSeedStamp(
|
|
24634
|
+
const stamp = buildSeedStamp(manifest, CLI_VERSION, args.nowIso ?? (/* @__PURE__ */ new Date()).toISOString());
|
|
24662
24635
|
await writeStampImpl(stamp, { ...ctx, onProgress: args.onProgress });
|
|
24663
24636
|
return {
|
|
24664
|
-
outcome:
|
|
24637
|
+
outcome: "seeded",
|
|
24665
24638
|
platformVersion: stamp.platformVersion,
|
|
24666
24639
|
storageAccount: accountName,
|
|
24667
24640
|
components: {
|
|
@@ -24674,11 +24647,10 @@ async function seedStamp2(args) {
|
|
|
24674
24647
|
var PlatformSeedStampCommand = class extends M8tCommand {
|
|
24675
24648
|
static paths = [["platform", "seed-stamp"]];
|
|
24676
24649
|
static usage = Command39.Usage({
|
|
24677
|
-
description: "INTERNAL: write the initial installed-state stamp from
|
|
24678
|
-
details: "Used by the cloud installer at the end of a from-zero install so the platform records which release it is running. Writes the stamp ONLY IF ABSENT \u2014 it never overwrites a stamp written by the converge engine, so a re-run or a container restart is harmless. Not a founder-facing command: to change what is installed, use 'm8t platform update'."
|
|
24650
|
+
description: "INTERNAL: write the initial installed-state stamp from the release manifest this install deployed.",
|
|
24651
|
+
details: "Used by the cloud installer at the end of a from-zero install so the platform records which release it is running. Seeds from the SAME document every later converge stamps from, so the first update diffs against a row that describes the same release. Writes the stamp ONLY IF ABSENT \u2014 it never overwrites a stamp written by the converge engine, so a re-run or a container restart is harmless. Not a founder-facing command: to change what is installed, use 'm8t platform update'."
|
|
24679
24652
|
});
|
|
24680
|
-
|
|
24681
|
-
platformVersion = Option36.String("--platform-version", { description: "Bare X.Y.Z release this install is deploying. Overrides the descriptor's baked version, which lags by one declaration under publish->select. Omit to use the baked value." });
|
|
24653
|
+
manifest = Option36.String("--manifest", { description: "Path to the fetched, digest-verified release manifest for the release this install deployed." });
|
|
24682
24654
|
subscription = Option36.String("--subscription");
|
|
24683
24655
|
resourceGroup = Option36.String("--resource-group");
|
|
24684
24656
|
miClientId = Option36.String("--mi-client-id", { description: "Authenticate as this user-assigned managed identity instead of the ambient credential." });
|
|
@@ -24697,13 +24669,7 @@ var PlatformSeedStampCommand = class extends M8tCommand {
|
|
|
24697
24669
|
};
|
|
24698
24670
|
const credential2 = typeof this.miClientId === "string" && this.miClientId.trim().length > 0 ? new ManagedIdentityCredential4({ clientId: this.miClientId.trim() }) : new DefaultAzureCredential20();
|
|
24699
24671
|
const result2 = await seedStamp2({
|
|
24700
|
-
|
|
24701
|
-
// Absent flag ⇒ undefined ⇒ the descriptor's baked value. A flag PASSED but
|
|
24702
|
-
// empty is not the same thing and is refused below, deliberately: the
|
|
24703
|
-
// installer omits the flag entirely when it has nothing to say
|
|
24704
|
-
// (installer/lib.sh), so an empty one means a launcher computed a version and
|
|
24705
|
-
// got nothing — which should be loud, not silently absorbed by the fallback.
|
|
24706
|
-
platformVersion: typeof this.platformVersion === "string" ? this.platformVersion.trim() : void 0,
|
|
24672
|
+
manifestPath: need(this.manifest, "--manifest"),
|
|
24707
24673
|
credential: credential2,
|
|
24708
24674
|
subscriptionId: need(this.subscription, "--subscription"),
|
|
24709
24675
|
resourceGroup: need(this.resourceGroup, "--resource-group"),
|
|
@@ -24719,23 +24685,141 @@ var PlatformSeedStampCommand = class extends M8tCommand {
|
|
|
24719
24685
|
}
|
|
24720
24686
|
};
|
|
24721
24687
|
|
|
24722
|
-
// src/commands/platform/
|
|
24688
|
+
// src/commands/platform/verify-content.ts
|
|
24689
|
+
import { readFileSync as readFileSync21 } from "fs";
|
|
24723
24690
|
import { Command as Command40, Option as Option37 } from "clipanion";
|
|
24691
|
+
init_errors();
|
|
24692
|
+
|
|
24693
|
+
// src/lib/platform-content-crosscheck.ts
|
|
24694
|
+
function crossCheckContent(manifest, content) {
|
|
24695
|
+
const problems = [];
|
|
24696
|
+
const manifestInfra = manifest.components.infra?.treeSha ?? "";
|
|
24697
|
+
if (content.infra !== manifestInfra) {
|
|
24698
|
+
problems.push({ field: "infra", baked: content.infra, manifest: manifestInfra });
|
|
24699
|
+
}
|
|
24700
|
+
for (const [name, item] of Object.entries(manifest.components.personas.items)) {
|
|
24701
|
+
if (!Object.hasOwn(content.personas, name)) continue;
|
|
24702
|
+
if (content.personas[name] !== item.treeSha) {
|
|
24703
|
+
problems.push({ field: `personas.${name}`, baked: content.personas[name], manifest: item.treeSha });
|
|
24704
|
+
}
|
|
24705
|
+
}
|
|
24706
|
+
const manifestSeeds = manifest.components.brainSeeds.items;
|
|
24707
|
+
for (const entry of content.brainSeedsRev.split("|")) {
|
|
24708
|
+
if (!entry) continue;
|
|
24709
|
+
const first = entry.indexOf(":");
|
|
24710
|
+
const second = entry.indexOf(":", first + 1);
|
|
24711
|
+
if (first < 0 || second < 0) continue;
|
|
24712
|
+
const name = entry.slice(0, first);
|
|
24713
|
+
const sub = entry.slice(first + 1, second);
|
|
24714
|
+
const baked = entry.slice(second + 1);
|
|
24715
|
+
if (!Object.hasOwn(manifestSeeds, name)) continue;
|
|
24716
|
+
const subtrees = manifestSeeds[name].subtrees;
|
|
24717
|
+
if (!Object.hasOwn(subtrees, sub)) continue;
|
|
24718
|
+
const want = subtrees[sub] ?? "-";
|
|
24719
|
+
if (baked !== want) {
|
|
24720
|
+
problems.push({ field: `brainSeeds.${name}.${sub}`, baked, manifest: want });
|
|
24721
|
+
}
|
|
24722
|
+
}
|
|
24723
|
+
return problems;
|
|
24724
|
+
}
|
|
24725
|
+
function describeCrossCheckProblems(problems, version) {
|
|
24726
|
+
if (problems.length === 0) return "";
|
|
24727
|
+
const lines = problems.map((p) => ` ${p.field}
|
|
24728
|
+
this image carries: ${p.baked}
|
|
24729
|
+
${version} names: ${p.manifest}`);
|
|
24730
|
+
return `This installer image does not carry the content that platform ${version} names.
|
|
24731
|
+
${lines.join("\n")}
|
|
24732
|
+
|
|
24733
|
+
The install was stopped before anything was deployed. This is a fault in the release, not in your setup: the engine and the manifest were built from different trees. Get the newest CLI with \`npm install -g @m8t-stack/cli\` and start the install again.`;
|
|
24734
|
+
}
|
|
24735
|
+
|
|
24736
|
+
// src/commands/platform/verify-content.ts
|
|
24737
|
+
function readJson(path47, what) {
|
|
24738
|
+
let raw;
|
|
24739
|
+
try {
|
|
24740
|
+
raw = readFileSync21(path47, "utf8");
|
|
24741
|
+
} catch (e) {
|
|
24742
|
+
throw new LocalCliError({ code: "PLATFORM_VERIFY_READ_FAILED", message: `Could not read the ${what} at '${path47}': ${e.message}` });
|
|
24743
|
+
}
|
|
24744
|
+
try {
|
|
24745
|
+
return JSON.parse(raw);
|
|
24746
|
+
} catch (e) {
|
|
24747
|
+
throw new LocalCliError({ code: "PLATFORM_VERIFY_PARSE_FAILED", message: `The ${what} at '${path47}' is not valid JSON: ${e.message}` });
|
|
24748
|
+
}
|
|
24749
|
+
}
|
|
24750
|
+
var PlatformVerifyContentCommand = class extends M8tCommand {
|
|
24751
|
+
static paths = [["platform", "verify-content"]];
|
|
24752
|
+
static usage = Command40.Usage({
|
|
24753
|
+
category: "Platform",
|
|
24754
|
+
description: "INTERNAL: check that this installer image's baked content is what the release manifest names.",
|
|
24755
|
+
details: "Run by the cloud installer at preflight, before anything is deployed. Compares the image's install-descriptor.json content block (persona tree SHAs, the deploy/ tree SHA, the brain-seed rev \u2014 all computed from the tree the image actually baked) against the same facts in the release manifest the install was handed. Exits non-zero, naming every field that disagrees and both values, when they do not match. Not a founder-facing command."
|
|
24756
|
+
});
|
|
24757
|
+
manifest = Option37.String("--manifest", { description: "Path to the fetched, digest-verified release manifest." });
|
|
24758
|
+
descriptor = Option37.String("--descriptor", { description: "Path to install-descriptor.json (baked into this image)." });
|
|
24759
|
+
// Not `async`: this command is pure file reads and a comparison, with no
|
|
24760
|
+
// awaited work at all. The base class wants a Promise, so it gets one.
|
|
24761
|
+
executeCommand() {
|
|
24762
|
+
const need = (v, flag) => {
|
|
24763
|
+
if (typeof v !== "string" || v.trim().length === 0) {
|
|
24764
|
+
throw new LocalCliError({ code: "PLATFORM_VERIFY_ARG_MISSING", message: `${flag} is required.` });
|
|
24765
|
+
}
|
|
24766
|
+
return v.trim();
|
|
24767
|
+
};
|
|
24768
|
+
const manifestPath = need(this.manifest, "--manifest");
|
|
24769
|
+
const rawManifest = readJson(manifestPath, "release manifest");
|
|
24770
|
+
const manifestErrors = validateManifest(rawManifest);
|
|
24771
|
+
if (manifestErrors.length > 0) {
|
|
24772
|
+
throw new LocalCliError({
|
|
24773
|
+
code: "PLATFORM_VERIFY_MANIFEST_INVALID",
|
|
24774
|
+
message: `The release manifest at '${manifestPath}' is not a manifest this platform can read:
|
|
24775
|
+
- ${manifestErrors.join("\n - ")}`,
|
|
24776
|
+
hint: "Nothing was compared and nothing was deployed. This is a fault in the release, not in your setup."
|
|
24777
|
+
});
|
|
24778
|
+
}
|
|
24779
|
+
const manifest = rawManifest;
|
|
24780
|
+
const descriptor = readJson(need(this.descriptor, "--descriptor"), "install descriptor");
|
|
24781
|
+
const content = descriptor.content;
|
|
24782
|
+
if (!content || typeof content.brainSeedsRev !== "string" || typeof content.infra !== "string" || typeof content.personas !== "object") {
|
|
24783
|
+
throw new LocalCliError({
|
|
24784
|
+
code: "PLATFORM_VERIFY_DESCRIPTOR_INCOMPLETE",
|
|
24785
|
+
message: "This installer image's descriptor carries no usable content block, so there is nothing to check the release manifest against.",
|
|
24786
|
+
hint: "The image was built without scripts/install-descriptor-gen.mjs, or built where it could not read its own tree. It is malformed \u2014 reinstall with the current CLI (`npm install -g @m8t-stack/cli`)."
|
|
24787
|
+
});
|
|
24788
|
+
}
|
|
24789
|
+
const problems = crossCheckContent(manifest, {
|
|
24790
|
+
personas: content.personas,
|
|
24791
|
+
infra: content.infra,
|
|
24792
|
+
brainSeedsRev: content.brainSeedsRev
|
|
24793
|
+
});
|
|
24794
|
+
if (problems.length > 0) {
|
|
24795
|
+
throw new LocalCliError({
|
|
24796
|
+
code: "PLATFORM_VERIFY_CONTENT_MISMATCH",
|
|
24797
|
+
message: describeCrossCheckProblems(problems, manifest.platform.version)
|
|
24798
|
+
});
|
|
24799
|
+
}
|
|
24800
|
+
this.context.stderr.write(`content verified against platform ${manifest.platform.version}
|
|
24801
|
+
`);
|
|
24802
|
+
return Promise.resolve(0);
|
|
24803
|
+
}
|
|
24804
|
+
};
|
|
24805
|
+
|
|
24806
|
+
// src/commands/platform/gateway-adopt.ts
|
|
24807
|
+
import { Command as Command41, Option as Option38 } from "clipanion";
|
|
24724
24808
|
import { DefaultAzureCredential as DefaultAzureCredential21 } from "@azure/identity";
|
|
24725
24809
|
init_errors();
|
|
24726
24810
|
var PlatformGatewayAdoptCommand = class extends M8tCommand {
|
|
24727
24811
|
static paths = [["platform", "gateway", "adopt"]];
|
|
24728
|
-
static usage =
|
|
24812
|
+
static usage = Command41.Usage({
|
|
24729
24813
|
description: "Explicitly authorize one legacy m8t ACR gateway for managed, digest-pinned updates.",
|
|
24730
24814
|
details: "Fail-closed ownership transition. Reads the live gateway and ACR digest, requires the operator to repeat both exactly, verifies the generated legacy topology, and atomically writes the adoption marker with a pending platform update. Unadopted BYOC remains external."
|
|
24731
24815
|
});
|
|
24732
|
-
subscription =
|
|
24733
|
-
resourceGroup =
|
|
24734
|
-
target =
|
|
24735
|
-
expectedImage =
|
|
24736
|
-
expectedDigest =
|
|
24737
|
-
expectedAcrResourceId =
|
|
24738
|
-
output =
|
|
24816
|
+
subscription = Option38.String("--subscription");
|
|
24817
|
+
resourceGroup = Option38.String("--resource-group");
|
|
24818
|
+
target = Option38.String("--target", { description: "Published platform target whose installer contains adoption support." });
|
|
24819
|
+
expectedImage = Option38.String("--expected-image", { description: "Exact live legacy ACR image ref." });
|
|
24820
|
+
expectedDigest = Option38.String("--expected-digest", { description: "Exact sha256 digest resolved from that ACR image." });
|
|
24821
|
+
expectedAcrResourceId = Option38.String("--expected-acr-resource-id", { description: "Exact ARM id of the legacy ACR." });
|
|
24822
|
+
output = Option38.String("--output");
|
|
24739
24823
|
async executeCommand() {
|
|
24740
24824
|
const need = (v, flag) => {
|
|
24741
24825
|
if (typeof v !== "string" || v.trim() === "") throw new LocalCliError({ code: "PLATFORM_GATEWAY_ADOPTION_ARG_MISSING", message: `${flag} is required.` });
|
|
@@ -24802,7 +24886,7 @@ var PlatformGatewayAdoptCommand = class extends M8tCommand {
|
|
|
24802
24886
|
};
|
|
24803
24887
|
|
|
24804
24888
|
// src/commands/platform/policy.ts
|
|
24805
|
-
import { Command as
|
|
24889
|
+
import { Command as Command42, Option as Option39 } from "clipanion";
|
|
24806
24890
|
import { DefaultAzureCredential as DefaultAzureCredential22, ManagedIdentityCredential as ManagedIdentityCredential5 } from "@azure/identity";
|
|
24807
24891
|
init_errors();
|
|
24808
24892
|
|
|
@@ -24858,16 +24942,16 @@ async function readPolicy(opts) {
|
|
|
24858
24942
|
// src/commands/platform/policy.ts
|
|
24859
24943
|
var PlatformPolicyCommand = class extends M8tCommand {
|
|
24860
24944
|
static paths = [["platform", "policy"]];
|
|
24861
|
-
static usage =
|
|
24945
|
+
static usage = Command42.Usage({
|
|
24862
24946
|
description: "Show or set how this install handles available platform updates.",
|
|
24863
24947
|
details: "Modes: 'notify-only' shows updates but never applies them; 'auto-critical' (the default when unset) applies critical releases without asking; 'auto-all' applies every release. Use 'notify-only' where something else already controls what is deployed.",
|
|
24864
24948
|
examples: [["Show the current mode", "m8t platform policy"], ["Never apply automatically", "m8t platform policy --set notify-only"]]
|
|
24865
24949
|
});
|
|
24866
|
-
set =
|
|
24867
|
-
subscription =
|
|
24868
|
-
resourceGroup =
|
|
24869
|
-
miClientId =
|
|
24870
|
-
output =
|
|
24950
|
+
set = Option39.String("--set", { description: "notify-only | auto-critical | auto-all" });
|
|
24951
|
+
subscription = Option39.String("--subscription");
|
|
24952
|
+
resourceGroup = Option39.String("--resource-group");
|
|
24953
|
+
miClientId = Option39.String("--mi-client-id");
|
|
24954
|
+
output = Option39.String("--output");
|
|
24871
24955
|
async executeCommand() {
|
|
24872
24956
|
const mode = resolveOutputMode(this.output, this.context.stdout);
|
|
24873
24957
|
const need = (v, flag) => {
|
|
@@ -24913,7 +24997,7 @@ var PlatformPolicyCommand = class extends M8tCommand {
|
|
|
24913
24997
|
};
|
|
24914
24998
|
|
|
24915
24999
|
// src/commands/platform/enable-cost-report.ts
|
|
24916
|
-
import { Command as
|
|
25000
|
+
import { Command as Command43, Option as Option40 } from "clipanion";
|
|
24917
25001
|
|
|
24918
25002
|
// src/lib/wire-gateway-acs.ts
|
|
24919
25003
|
init_rbac();
|
|
@@ -24953,18 +25037,18 @@ async function wireGatewayForAcs(args) {
|
|
|
24953
25037
|
init_errors();
|
|
24954
25038
|
var PlatformEnableCostReportCommand = class extends M8tCommand {
|
|
24955
25039
|
static paths = [["platform", "enable-cost-report"]];
|
|
24956
|
-
static usage =
|
|
25040
|
+
static usage = Command43.Usage({
|
|
24957
25041
|
description: "Wire the deployed gateway to send the bi-weekly cost report via ACS Email.",
|
|
24958
25042
|
details: "Discovers the live gateway Container App, grants its managed identity Contributor at the ACS resource scope (authorising the ACS Email send), and sets M8T_ACS_ENDPOINT / M8T_ACS_SENDER / M8T_ENABLE_COST_REPORTER=1 on the gateway. Idempotent \u2014 safely re-runnable. ACS is created during the executor deploy; pass its endpoint, sender, and resource id (from that deploy or 'az communication list')."
|
|
24959
25043
|
});
|
|
24960
|
-
subscription =
|
|
24961
|
-
resourceGroup =
|
|
25044
|
+
subscription = Option40.String("--subscription");
|
|
25045
|
+
resourceGroup = Option40.String("--resource-group", {
|
|
24962
25046
|
description: "m8t resource group to disambiguate the gateway (multi-deployment subscriptions)."
|
|
24963
25047
|
});
|
|
24964
|
-
acsEndpoint =
|
|
24965
|
-
acsSender =
|
|
24966
|
-
acsResourceId =
|
|
24967
|
-
output =
|
|
25048
|
+
acsEndpoint = Option40.String("--acs-endpoint", { required: true, description: "ACS data-plane endpoint, e.g. https://acs-<name>.communication.azure.com/" });
|
|
25049
|
+
acsSender = Option40.String("--acs-sender", { required: true, description: "Managed-domain sender address, e.g. DoNotReply@<guid>.azurecomm.net" });
|
|
25050
|
+
acsResourceId = Option40.String("--acs-resource-id", { required: true, description: "ARM resource id of the acs-<name> Communication Service \u2014 the role-grant scope." });
|
|
25051
|
+
output = Option40.String("--output");
|
|
24968
25052
|
async executeCommand() {
|
|
24969
25053
|
const mode = resolveOutputMode(
|
|
24970
25054
|
this.output,
|
|
@@ -25038,13 +25122,13 @@ var PlatformEnableCostReportCommand = class extends M8tCommand {
|
|
|
25038
25122
|
};
|
|
25039
25123
|
|
|
25040
25124
|
// src/commands/platform/email.ts
|
|
25041
|
-
import { Command as
|
|
25125
|
+
import { Command as Command44, Option as Option41 } from "clipanion";
|
|
25042
25126
|
import { DefaultAzureCredential as DefaultAzureCredential23 } from "@azure/identity";
|
|
25043
25127
|
import { CommunicationServiceManagementClient as CommunicationServiceManagementClient2 } from "@azure/arm-communication";
|
|
25044
25128
|
init_errors();
|
|
25045
25129
|
var PlatformEmailCommand = class extends M8tCommand {
|
|
25046
25130
|
static paths = [["platform", "email"]];
|
|
25047
|
-
static usage =
|
|
25131
|
+
static usage = Command44.Usage({
|
|
25048
25132
|
description: "Turn outbound email (advisor handoffs) on or off for this install.",
|
|
25049
25133
|
details: "`on` resolves the ACS sender (reusing an existing one, provisioning only if there is none), records the decision in the platform stamp, and reconciles the executor's environment. `off` records the decision and strips the executor's email wiring, leaving the ACS resource in place so turning it back on is a flag flip. Idempotent \u2014 re-running either direction is safe, and no new agent version is posted when nothing changes.",
|
|
25050
25134
|
examples: [
|
|
@@ -25052,19 +25136,19 @@ var PlatformEmailCommand = class extends M8tCommand {
|
|
|
25052
25136
|
["Turn it off (a shared, public-facing deployment should stay off)", "m8t platform email off"]
|
|
25053
25137
|
]
|
|
25054
25138
|
});
|
|
25055
|
-
state =
|
|
25056
|
-
subscription =
|
|
25057
|
-
resourceGroup =
|
|
25139
|
+
state = Option41.String({ required: true, name: "on|off" });
|
|
25140
|
+
subscription = Option41.String("--subscription");
|
|
25141
|
+
resourceGroup = Option41.String("--resource-group", {
|
|
25058
25142
|
description: "m8t resource group, to disambiguate in a multi-deployment subscription."
|
|
25059
25143
|
});
|
|
25060
|
-
agent =
|
|
25061
|
-
kvUri =
|
|
25144
|
+
agent = Option41.String("--agent", { description: `Executor agent name. Resolved by persona when omitted (usually ${EXECUTOR_AGENT_FALLBACK}).` });
|
|
25145
|
+
kvUri = Option41.String("--kv-uri", {
|
|
25062
25146
|
description: "Install's Key Vault URI. Only needed if no ACS is on record yet and one must be provisioned."
|
|
25063
25147
|
});
|
|
25064
|
-
endpoint =
|
|
25148
|
+
endpoint = Option41.String("--endpoint", {
|
|
25065
25149
|
description: "Foundry project endpoint, to disambiguate a subscription holding several."
|
|
25066
25150
|
});
|
|
25067
|
-
output =
|
|
25151
|
+
output = Option41.String("--output");
|
|
25068
25152
|
async executeCommand() {
|
|
25069
25153
|
const wanted = this.state.trim().toLowerCase();
|
|
25070
25154
|
if (wanted !== "on" && wanted !== "off") {
|
|
@@ -25214,45 +25298,45 @@ var PlatformEmailCommand = class extends M8tCommand {
|
|
|
25214
25298
|
};
|
|
25215
25299
|
|
|
25216
25300
|
// src/commands/platform/enable-auto-update.ts
|
|
25217
|
-
import { Command as
|
|
25301
|
+
import { Command as Command45, Option as Option42 } from "clipanion";
|
|
25218
25302
|
import { confirm as confirm6 } from "@inquirer/prompts";
|
|
25219
25303
|
import { DefaultAzureCredential as DefaultAzureCredential24 } from "@azure/identity";
|
|
25220
25304
|
init_errors();
|
|
25221
25305
|
var PlatformEnableAutoUpdateCommand = class extends M8tCommand {
|
|
25222
25306
|
static paths = [["platform", "enable-auto-update"]];
|
|
25223
|
-
static usage =
|
|
25307
|
+
static usage = Command45.Usage({
|
|
25224
25308
|
category: "Platform",
|
|
25225
25309
|
description: "Retrofit the auto-updater (MI + cron job + role assignments) onto an existing install.",
|
|
25226
25310
|
details: "For an install that predates the Platform Update Framework: recovers the deployment's resource-name suffix (from the stamped system/infra-params row, or by verified derivation from the live gateway), recovers the other bicep params from live state (WITHOUT changing the deployed gateway image), and re-runs deploy/main.bicep with provisionUpdater=true \u2014 which provisions the updater managed identity, the cron Container Apps Job, and every role assignment (Owner@RG + Foundry User@account + Storage Table + Storage Blob + AcrPull), all scoped to the resource group. On success, backfills system/infra-params so future converges never have to re-derive the suffix. Idempotent \u2014 safe to re-run."
|
|
25227
25311
|
});
|
|
25228
|
-
subscription =
|
|
25229
|
-
resourceGroup =
|
|
25312
|
+
subscription = Option42.String("--subscription");
|
|
25313
|
+
resourceGroup = Option42.String("--resource-group", {
|
|
25230
25314
|
description: "m8t resource group to disambiguate the gateway (multi-deployment subscriptions)."
|
|
25231
25315
|
});
|
|
25232
|
-
suffix =
|
|
25316
|
+
suffix = Option42.String("--suffix", {
|
|
25233
25317
|
description: "The existing deployment's resource-name suffix. Required when it cannot be recovered from system/infra-params or the live gateway."
|
|
25234
25318
|
});
|
|
25235
|
-
installerImage =
|
|
25319
|
+
installerImage = Option42.String("--installer-image", {
|
|
25236
25320
|
required: true,
|
|
25237
25321
|
description: "Updater CA-Job image ref (the converge engine at the current release). Required \u2014 an empty image would skip provisioning."
|
|
25238
25322
|
});
|
|
25239
|
-
updateCron =
|
|
25323
|
+
updateCron = Option42.String("--update-cron", {
|
|
25240
25324
|
description: "Cron schedule for the updater job (bicep default applies when omitted)."
|
|
25241
25325
|
});
|
|
25242
|
-
channelUrl =
|
|
25326
|
+
channelUrl = Option42.String("--channel-url", {
|
|
25243
25327
|
description: "Release-channel URL the updater job polls (bicep default applies when omitted)."
|
|
25244
25328
|
});
|
|
25245
|
-
location =
|
|
25329
|
+
location = Option42.String("--location", {
|
|
25246
25330
|
description: "Region for the updater identity + job. Defaults to this install's stamped region, then to the resource group's existing resources."
|
|
25247
25331
|
});
|
|
25248
|
-
foundryTracing =
|
|
25332
|
+
foundryTracing = Option42.String("--foundry-tracing", {
|
|
25249
25333
|
description: "project | account | skip. Omitting it keeps the value already stamped on this install; only an install with nothing stamped falls through to the default (project)."
|
|
25250
25334
|
});
|
|
25251
|
-
endpoint =
|
|
25335
|
+
endpoint = Option42.String("--endpoint", {
|
|
25252
25336
|
description: "Foundry project endpoint URL. Disambiguates the project in a multi-project subscription."
|
|
25253
25337
|
});
|
|
25254
|
-
yes =
|
|
25255
|
-
output =
|
|
25338
|
+
yes = Option42.Boolean("--yes", false);
|
|
25339
|
+
output = Option42.String("--output");
|
|
25256
25340
|
async executeCommand() {
|
|
25257
25341
|
const mode = resolveOutputMode(
|
|
25258
25342
|
this.output,
|
|
@@ -25433,7 +25517,7 @@ var PlatformEnableAutoUpdateCommand = class extends M8tCommand {
|
|
|
25433
25517
|
};
|
|
25434
25518
|
|
|
25435
25519
|
// src/commands/deploy.ts
|
|
25436
|
-
import { Command as
|
|
25520
|
+
import { Command as Command46, Option as Option43 } from "clipanion";
|
|
25437
25521
|
|
|
25438
25522
|
// src/lib/app-reg.ts
|
|
25439
25523
|
init_esm();
|
|
@@ -25997,15 +26081,15 @@ function classifyWhatIf(changes) {
|
|
|
25997
26081
|
var DEFAULT_IMAGE_REF = "ghcr.io/m8t-labs/m8t:latest";
|
|
25998
26082
|
var DeployCommand = class extends M8tCommand {
|
|
25999
26083
|
static paths = [["deploy"]];
|
|
26000
|
-
static usage =
|
|
26084
|
+
static usage = Command46.Usage({
|
|
26001
26085
|
description: "Deploy (or update) the m8t gateway/webapp stack via Bicep.",
|
|
26002
26086
|
details: "Ensures the Entra app reg (or pass --client-id to reuse an existing one \u2014 required if you can't create app regs), writes ~/.m8t/config.yaml, ensures the resource group, and runs deploy/main.bicep. The repo is located via ~/.m8t/repo-root."
|
|
26003
26087
|
});
|
|
26004
|
-
subscription =
|
|
26005
|
-
resourceGroup =
|
|
26006
|
-
location =
|
|
26007
|
-
suffix =
|
|
26008
|
-
imageRef =
|
|
26088
|
+
subscription = Option43.String("--subscription");
|
|
26089
|
+
resourceGroup = Option43.String("--resource-group", "rg-m8t-stack");
|
|
26090
|
+
location = Option43.String("--location", "eastus");
|
|
26091
|
+
suffix = Option43.String("--suffix", "");
|
|
26092
|
+
imageRef = Option43.String("--image-ref", DEFAULT_IMAGE_REF);
|
|
26009
26093
|
// Gateway-only override. Empty ⇒ the gateway uses --image-ref, which is the
|
|
26010
26094
|
// from-zero case. It exists because the gateway and the voice relay do NOT
|
|
26011
26095
|
// always run the same image: a converge preserves a BYOC gateway on its own
|
|
@@ -26013,28 +26097,28 @@ var DeployCommand = class extends M8tCommand {
|
|
|
26013
26097
|
// (`--what-if`) that can only express one image therefore reports the other
|
|
26014
26098
|
// app as drift on every single run, forever — which is exactly what the
|
|
26015
26099
|
// infra-drift gate did from 2026-08-11.
|
|
26016
|
-
gatewayImageRef =
|
|
26017
|
-
acrPullIdentity =
|
|
26018
|
-
acrResourceId =
|
|
26019
|
-
foundryEndpoint =
|
|
26020
|
-
foundryResourceId =
|
|
26021
|
-
foundryTracing =
|
|
26100
|
+
gatewayImageRef = Option43.String("--gateway-image-ref", "");
|
|
26101
|
+
acrPullIdentity = Option43.String("--acrpull-identity");
|
|
26102
|
+
acrResourceId = Option43.String("--acr-resource-id");
|
|
26103
|
+
foundryEndpoint = Option43.String("--foundry-endpoint");
|
|
26104
|
+
foundryResourceId = Option43.String("--foundry-resource-id");
|
|
26105
|
+
foundryTracing = Option43.String("--foundry-tracing");
|
|
26022
26106
|
// project | account | skip (bicep default: project)
|
|
26023
|
-
clientId =
|
|
26024
|
-
whatIf =
|
|
26107
|
+
clientId = Option43.String("--client-id");
|
|
26108
|
+
whatIf = Option43.Boolean("--what-if", false);
|
|
26025
26109
|
// Only meaningful with --what-if. Routes the comparison through the
|
|
26026
26110
|
// value-free renderers (see ./lib/whatif-redact.js) instead of the default
|
|
26027
26111
|
// before/after renderer. Defaults false so a local, interactive run keeps
|
|
26028
26112
|
// showing values — that is the whole diagnostic point of --what-if.
|
|
26029
26113
|
// Automation that forwards this output anywhere non-private (a CI log, an
|
|
26030
26114
|
// issue) MUST pass --redact.
|
|
26031
|
-
redact =
|
|
26032
|
-
output =
|
|
26115
|
+
redact = Option43.Boolean("--redact", false);
|
|
26116
|
+
output = Option43.String("--output");
|
|
26033
26117
|
// Subscription-scoped role assignments. Omitted ⇒ the template default (true).
|
|
26034
26118
|
// Pass false when deploying as a principal scoped to the resource group only:
|
|
26035
26119
|
// it cannot deploy at subscription scope, and those assignments persist
|
|
26036
26120
|
// idempotently from the initial deployment anyway.
|
|
26037
|
-
assignSubscriptionRoles =
|
|
26121
|
+
assignSubscriptionRoles = Option43.String("--assign-subscription-roles");
|
|
26038
26122
|
/**
|
|
26039
26123
|
* The installer image the updater Container-Apps Job runs.
|
|
26040
26124
|
*
|
|
@@ -26045,23 +26129,23 @@ var DeployCommand = class extends M8tCommand {
|
|
|
26045
26129
|
* than that — the comparison proposes REMOVING an updater job that exists and
|
|
26046
26130
|
* should, and reports it as drift on every single run.
|
|
26047
26131
|
*/
|
|
26048
|
-
installerImage =
|
|
26132
|
+
installerImage = Option43.String("--installer-image");
|
|
26049
26133
|
// Referee — all optional, undefined by default ⇒ the bicep defaults
|
|
26050
26134
|
// apply (dormant: gatewayCpu=0.25, gatewayMemory=0.5Gi, every referee/exam var
|
|
26051
26135
|
// empty). Only pass these when explicitly enabling the referee exam stack.
|
|
26052
|
-
gatewayCpu =
|
|
26053
|
-
gatewayMemory =
|
|
26054
|
-
refereeEnabled =
|
|
26055
|
-
refereeBrainRepos =
|
|
26056
|
-
refereeFeedRepo =
|
|
26057
|
-
refereeInstallationId =
|
|
26058
|
-
refereeWebhookHmacKvUri =
|
|
26059
|
-
examKvUri =
|
|
26060
|
-
examLaWorkspaceId =
|
|
26061
|
-
brainEvalDeployment =
|
|
26062
|
-
brainAppLogin =
|
|
26063
|
-
refereeCheckpointDir =
|
|
26064
|
-
examApiBase =
|
|
26136
|
+
gatewayCpu = Option43.String("--gateway-cpu");
|
|
26137
|
+
gatewayMemory = Option43.String("--gateway-memory");
|
|
26138
|
+
refereeEnabled = Option43.String("--referee-enabled");
|
|
26139
|
+
refereeBrainRepos = Option43.String("--referee-brain-repos");
|
|
26140
|
+
refereeFeedRepo = Option43.String("--referee-feed-repo");
|
|
26141
|
+
refereeInstallationId = Option43.String("--referee-installation-id");
|
|
26142
|
+
refereeWebhookHmacKvUri = Option43.String("--referee-webhook-hmac-kv-uri");
|
|
26143
|
+
examKvUri = Option43.String("--exam-kv-uri");
|
|
26144
|
+
examLaWorkspaceId = Option43.String("--exam-la-workspace-id");
|
|
26145
|
+
brainEvalDeployment = Option43.String("--brain-eval-deployment");
|
|
26146
|
+
brainAppLogin = Option43.String("--brain-app-login");
|
|
26147
|
+
refereeCheckpointDir = Option43.String("--referee-checkpoint-dir");
|
|
26148
|
+
examApiBase = Option43.String("--exam-api-base");
|
|
26065
26149
|
async executeCommand() {
|
|
26066
26150
|
const mode = resolveOutputMode(
|
|
26067
26151
|
this.output,
|
|
@@ -26245,7 +26329,7 @@ var DeployCommand = class extends M8tCommand {
|
|
|
26245
26329
|
|
|
26246
26330
|
// src/commands/eval/skill.ts
|
|
26247
26331
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
26248
|
-
import { Command as
|
|
26332
|
+
import { Command as Command47, Option as Option44 } from "clipanion";
|
|
26249
26333
|
init_errors();
|
|
26250
26334
|
var DECISIONS = /* @__PURE__ */ new Set(["promote", "reject", "needs_review"]);
|
|
26251
26335
|
var JUDGE_STATUSES = /* @__PURE__ */ new Set(["ok", "skipped", "unavailable"]);
|
|
@@ -26270,14 +26354,14 @@ function parseVerdict(stdout) {
|
|
|
26270
26354
|
}
|
|
26271
26355
|
var EvalSkillCommand = class extends M8tCommand {
|
|
26272
26356
|
static paths = [["eval", "skill"]];
|
|
26273
|
-
static usage =
|
|
26357
|
+
static usage = Command47.Usage({
|
|
26274
26358
|
description: "Vet one inbox skill candidate: promote / reject / needs_review. Shells out to the Python `brain-eval` core (override its path with $BRAIN_EVAL_BIN)."
|
|
26275
26359
|
});
|
|
26276
|
-
candidate =
|
|
26277
|
-
skillsDir =
|
|
26278
|
-
noJudge =
|
|
26279
|
-
deployment =
|
|
26280
|
-
output =
|
|
26360
|
+
candidate = Option44.String();
|
|
26361
|
+
skillsDir = Option44.String("--skills-dir");
|
|
26362
|
+
noJudge = Option44.Boolean("--no-judge", false);
|
|
26363
|
+
deployment = Option44.String("--deployment");
|
|
26364
|
+
output = Option44.String("--output");
|
|
26281
26365
|
executeCommand() {
|
|
26282
26366
|
return Promise.resolve(this._runCommand());
|
|
26283
26367
|
}
|
|
@@ -26334,9 +26418,9 @@ var EvalSkillCommand = class extends M8tCommand {
|
|
|
26334
26418
|
|
|
26335
26419
|
// src/commands/eval/exam.ts
|
|
26336
26420
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
26337
|
-
import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, readFileSync as
|
|
26421
|
+
import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync22, existsSync as existsSync20, readdirSync as readdirSync3 } from "fs";
|
|
26338
26422
|
import { join as join30 } from "path";
|
|
26339
|
-
import { Command as
|
|
26423
|
+
import { Command as Command48, Option as Option45 } from "clipanion";
|
|
26340
26424
|
init_errors();
|
|
26341
26425
|
init_esm();
|
|
26342
26426
|
function parseArmToken(tok, opts) {
|
|
@@ -26539,7 +26623,7 @@ function readSealedTaskIds(taskSetVersion, refereeHomeOut, env = process.env) {
|
|
|
26539
26623
|
const manifestPath = join30(home, "tasksets", worker, versionDir, "manifest.yaml");
|
|
26540
26624
|
let text;
|
|
26541
26625
|
try {
|
|
26542
|
-
text =
|
|
26626
|
+
text = readFileSync22(manifestPath, "utf-8");
|
|
26543
26627
|
} catch (e) {
|
|
26544
26628
|
throw new LocalCliError({
|
|
26545
26629
|
code: "EXAM_REDACTION_UNSAFE",
|
|
@@ -26566,24 +26650,24 @@ function buildPlan(args) {
|
|
|
26566
26650
|
}
|
|
26567
26651
|
var EvalExamCommand = class extends M8tCommand {
|
|
26568
26652
|
static paths = [["eval", "exam"]];
|
|
26569
|
-
static usage =
|
|
26653
|
+
static usage = Command48.Usage({
|
|
26570
26654
|
description: "Run a brain exam: impact A/B + dream-delta. Resolves the plan, then shells out to the Python `brain-exam` orchestrator (override its path with $BRAIN_EXAM_BIN). Renders an ExamVerdict: three-valued verdict + power note + per-task flips."
|
|
26571
26655
|
});
|
|
26572
|
-
worker =
|
|
26573
|
-
arms =
|
|
26574
|
-
taskSet =
|
|
26575
|
-
examType =
|
|
26576
|
-
skill =
|
|
26577
|
-
reps =
|
|
26578
|
-
probes =
|
|
26579
|
-
pool =
|
|
26580
|
-
out =
|
|
26581
|
-
dryRun =
|
|
26582
|
-
keepArms =
|
|
26583
|
-
allowStub =
|
|
26584
|
-
deployment =
|
|
26585
|
-
output =
|
|
26586
|
-
observeWaitS =
|
|
26656
|
+
worker = Option45.String();
|
|
26657
|
+
arms = Option45.String("--arms");
|
|
26658
|
+
taskSet = Option45.String("--task-set");
|
|
26659
|
+
examType = Option45.String("--exam-type");
|
|
26660
|
+
skill = Option45.String("--skill");
|
|
26661
|
+
reps = Option45.String("-n,--reps");
|
|
26662
|
+
probes = Option45.String("--probes");
|
|
26663
|
+
pool = Option45.String("--pool");
|
|
26664
|
+
out = Option45.String("--out");
|
|
26665
|
+
dryRun = Option45.Boolean("--dry-run", false);
|
|
26666
|
+
keepArms = Option45.Boolean("--keep-arms", false);
|
|
26667
|
+
allowStub = Option45.Boolean("--allow-stub", false);
|
|
26668
|
+
deployment = Option45.String("--deployment");
|
|
26669
|
+
output = Option45.String("--output");
|
|
26670
|
+
observeWaitS = Option45.String("--observe-wait-s");
|
|
26587
26671
|
async executeCommand() {
|
|
26588
26672
|
await Promise.resolve();
|
|
26589
26673
|
const worker = typeof this.worker === "string" ? this.worker : void 0;
|
|
@@ -26698,10 +26782,10 @@ var EvalExamCommand = class extends M8tCommand {
|
|
|
26698
26782
|
};
|
|
26699
26783
|
|
|
26700
26784
|
// src/commands/version.ts
|
|
26701
|
-
import { Command as
|
|
26785
|
+
import { Command as Command49, Option as Option46 } from "clipanion";
|
|
26702
26786
|
var VersionCommand = class extends M8tCommand {
|
|
26703
26787
|
static paths = [["version"], ["--version"], ["-v"]];
|
|
26704
|
-
static usage =
|
|
26788
|
+
static usage = Command49.Usage({
|
|
26705
26789
|
description: "Print the CLI version.",
|
|
26706
26790
|
details: "Prints the m8t CLI version. With --verbose, also prints Node version and platform.",
|
|
26707
26791
|
examples: [
|
|
@@ -26709,8 +26793,8 @@ var VersionCommand = class extends M8tCommand {
|
|
|
26709
26793
|
["Print as JSON", "$0 version --output json"]
|
|
26710
26794
|
]
|
|
26711
26795
|
});
|
|
26712
|
-
output =
|
|
26713
|
-
verbose =
|
|
26796
|
+
output = Option46.String("--output", { description: "pretty | json | auto (default)" });
|
|
26797
|
+
verbose = Option46.Boolean("--verbose", false);
|
|
26714
26798
|
executeCommand() {
|
|
26715
26799
|
const mode = resolveOutputMode(
|
|
26716
26800
|
this.output ?? "auto",
|
|
@@ -26741,18 +26825,18 @@ var VersionCommand = class extends M8tCommand {
|
|
|
26741
26825
|
};
|
|
26742
26826
|
|
|
26743
26827
|
// src/commands/whoami.ts
|
|
26744
|
-
import { Command as
|
|
26828
|
+
import { Command as Command50, Option as Option47 } from "clipanion";
|
|
26745
26829
|
var WhoamiCommand = class extends M8tCommand {
|
|
26746
26830
|
static paths = [["whoami"]];
|
|
26747
|
-
static usage =
|
|
26831
|
+
static usage = Command50.Usage({
|
|
26748
26832
|
description: "Show your identity + the gateway you'll talk to. Probes the backend."
|
|
26749
26833
|
});
|
|
26750
|
-
output =
|
|
26751
|
-
verbose =
|
|
26752
|
-
subscription =
|
|
26834
|
+
output = Option47.String("--output");
|
|
26835
|
+
verbose = Option47.Boolean("--verbose", false);
|
|
26836
|
+
subscription = Option47.String("--subscription", {
|
|
26753
26837
|
description: "Azure subscription ID to discover gateway in (defaults to active az subscription)."
|
|
26754
26838
|
});
|
|
26755
|
-
resourceGroup =
|
|
26839
|
+
resourceGroup = Option47.String("--resource-group", {
|
|
26756
26840
|
description: "m8t resource group to disambiguate the gateway (multi-deployment subscriptions)."
|
|
26757
26841
|
});
|
|
26758
26842
|
async executeCommand() {
|
|
@@ -26817,7 +26901,7 @@ var WhoamiCommand = class extends M8tCommand {
|
|
|
26817
26901
|
};
|
|
26818
26902
|
|
|
26819
26903
|
// src/commands/status.ts
|
|
26820
|
-
import { Command as
|
|
26904
|
+
import { Command as Command51, Option as Option48 } from "clipanion";
|
|
26821
26905
|
|
|
26822
26906
|
// src/lib/azd.ts
|
|
26823
26907
|
init_errors();
|
|
@@ -26882,10 +26966,10 @@ async function resolveLocalContext() {
|
|
|
26882
26966
|
// src/commands/status.ts
|
|
26883
26967
|
var StatusCommand = class extends M8tCommand {
|
|
26884
26968
|
static paths = [["status"]];
|
|
26885
|
-
static usage =
|
|
26969
|
+
static usage = Command51.Usage({
|
|
26886
26970
|
description: "Show the local m8t context: identity, config.yaml, gateway cache, azd mode."
|
|
26887
26971
|
});
|
|
26888
|
-
output =
|
|
26972
|
+
output = Option48.String("--output");
|
|
26889
26973
|
async executeCommand() {
|
|
26890
26974
|
const mode = resolveOutputMode(
|
|
26891
26975
|
this.output,
|
|
@@ -26923,7 +27007,7 @@ var StatusCommand = class extends M8tCommand {
|
|
|
26923
27007
|
};
|
|
26924
27008
|
|
|
26925
27009
|
// src/commands/doctor.ts
|
|
26926
|
-
import { Command as
|
|
27010
|
+
import { Command as Command52, Option as Option49 } from "clipanion";
|
|
26927
27011
|
import { DefaultAzureCredential as DefaultAzureCredential25 } from "@azure/identity";
|
|
26928
27012
|
import * as fs30 from "fs";
|
|
26929
27013
|
import * as os12 from "os";
|
|
@@ -27471,12 +27555,12 @@ function probeLegacyStateDir() {
|
|
|
27471
27555
|
}
|
|
27472
27556
|
var DoctorCommand = class extends M8tCommand {
|
|
27473
27557
|
static paths = [["doctor"]];
|
|
27474
|
-
static usage =
|
|
27558
|
+
static usage = Command52.Usage({
|
|
27475
27559
|
description: "Diagnose the local m8t setup: az login, config.yaml, gateway, Foundry data-plane."
|
|
27476
27560
|
});
|
|
27477
|
-
output =
|
|
27478
|
-
agent =
|
|
27479
|
-
resourceGroup =
|
|
27561
|
+
output = Option49.String("--output");
|
|
27562
|
+
agent = Option49.String("--agent");
|
|
27563
|
+
resourceGroup = Option49.String("--resource-group", {
|
|
27480
27564
|
description: "m8t resource group to disambiguate the gateway (multi-deployment subscriptions)."
|
|
27481
27565
|
});
|
|
27482
27566
|
async executeCommand() {
|
|
@@ -27666,7 +27750,7 @@ var DoctorCommand = class extends M8tCommand {
|
|
|
27666
27750
|
};
|
|
27667
27751
|
|
|
27668
27752
|
// src/commands/prereqs.ts
|
|
27669
|
-
import { Command as
|
|
27753
|
+
import { Command as Command53, Option as Option50 } from "clipanion";
|
|
27670
27754
|
init_errors();
|
|
27671
27755
|
|
|
27672
27756
|
// src/lib/prereq-deps.ts
|
|
@@ -28372,7 +28456,7 @@ function renderVerdict(v) {
|
|
|
28372
28456
|
}
|
|
28373
28457
|
var PrereqsCommand = class extends M8tCommand {
|
|
28374
28458
|
static paths = [["prereqs"]];
|
|
28375
|
-
static usage =
|
|
28459
|
+
static usage = Command53.Usage({
|
|
28376
28460
|
description: "Check (and optionally fix) everything m8t needs \u2014 to install, or for you to use it.",
|
|
28377
28461
|
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, soft-deleted Cognitive Services accounts (they hold their model quota until purged), 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.",
|
|
28378
28462
|
examples: [
|
|
@@ -28383,15 +28467,15 @@ var PrereqsCommand = class extends M8tCommand {
|
|
|
28383
28467
|
["Set a teammate up (needs role-assignment rights)", "$0 prereqs --fix --for alice@contoso.com"]
|
|
28384
28468
|
]
|
|
28385
28469
|
});
|
|
28386
|
-
fix =
|
|
28387
|
-
for_ =
|
|
28388
|
-
phase =
|
|
28389
|
-
region =
|
|
28390
|
-
model =
|
|
28391
|
-
clientId =
|
|
28392
|
-
subscription =
|
|
28393
|
-
resourceGroup =
|
|
28394
|
-
output =
|
|
28470
|
+
fix = Option50.Boolean("--fix", false, { description: "Repair what is repairable. Without it the command only reports." });
|
|
28471
|
+
for_ = Option50.String("--for", { description: "UPN or object id of another person. Usage phase only." });
|
|
28472
|
+
phase = Option50.String("--phase", { description: "Force 'install' or 'usage' instead of auto-detecting." });
|
|
28473
|
+
region = Option50.String("--region", { description: "Target region. Enables the install-phase quota check, and judges soft-deleted accounts against the region you will install into (without it, they only warn)." });
|
|
28474
|
+
model = Option50.String("--model", { description: `Model whose quota the install needs (default ${INSTALL_REASONING_MODEL}).` });
|
|
28475
|
+
clientId = Option50.String("--client-id", { description: "A ready app registration, in place of directory-admin rights." });
|
|
28476
|
+
subscription = Option50.String("--subscription");
|
|
28477
|
+
resourceGroup = Option50.String("--resource-group");
|
|
28478
|
+
output = Option50.String("--output");
|
|
28395
28479
|
async executeCommand() {
|
|
28396
28480
|
const mode = resolveOutputMode(this.output, this.context.stdout);
|
|
28397
28481
|
const json = mode === "json";
|
|
@@ -28447,7 +28531,7 @@ var PrereqsCommand = class extends M8tCommand {
|
|
|
28447
28531
|
};
|
|
28448
28532
|
|
|
28449
28533
|
// src/commands/switch.ts
|
|
28450
|
-
import { Command as
|
|
28534
|
+
import { Command as Command54, Option as Option51 } from "clipanion";
|
|
28451
28535
|
|
|
28452
28536
|
// src/lib/profiles.ts
|
|
28453
28537
|
import * as fs31 from "fs/promises";
|
|
@@ -28587,14 +28671,14 @@ async function profileSwitch(name, asName) {
|
|
|
28587
28671
|
init_errors();
|
|
28588
28672
|
var SwitchCommand = class extends M8tCommand {
|
|
28589
28673
|
static paths = [["switch"]];
|
|
28590
|
-
static usage =
|
|
28674
|
+
static usage = Command54.Usage({
|
|
28591
28675
|
description: "Re-point local config at another deployment: --subscription <id|name> (discovery) or <profile>."
|
|
28592
28676
|
});
|
|
28593
|
-
profile =
|
|
28594
|
-
subscription =
|
|
28595
|
-
list =
|
|
28596
|
-
as =
|
|
28597
|
-
output =
|
|
28677
|
+
profile = Option51.String({ required: false });
|
|
28678
|
+
subscription = Option51.String("--subscription");
|
|
28679
|
+
list = Option51.Boolean("--list", false);
|
|
28680
|
+
as = Option51.String("--as");
|
|
28681
|
+
output = Option51.String("--output");
|
|
28598
28682
|
async executeCommand() {
|
|
28599
28683
|
const mode = resolveOutputMode(
|
|
28600
28684
|
this.output,
|
|
@@ -28651,7 +28735,7 @@ var SwitchCommand = class extends M8tCommand {
|
|
|
28651
28735
|
|
|
28652
28736
|
// src/commands/open.ts
|
|
28653
28737
|
import { spawn as spawn5 } from "child_process";
|
|
28654
|
-
import { Command as
|
|
28738
|
+
import { Command as Command55, Option as Option52 } from "clipanion";
|
|
28655
28739
|
|
|
28656
28740
|
// src/lib/open-targets.ts
|
|
28657
28741
|
init_errors();
|
|
@@ -28697,14 +28781,14 @@ function openUrl(url) {
|
|
|
28697
28781
|
}
|
|
28698
28782
|
var OpenCommand = class extends M8tCommand {
|
|
28699
28783
|
static paths = [["open"]];
|
|
28700
|
-
static usage =
|
|
28784
|
+
static usage = Command55.Usage({
|
|
28701
28785
|
description: "Open the deployed webapp (default), the Foundry portal, or the resource group.",
|
|
28702
28786
|
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)."
|
|
28703
28787
|
});
|
|
28704
|
-
target =
|
|
28705
|
-
print =
|
|
28706
|
-
output =
|
|
28707
|
-
resourceGroup =
|
|
28788
|
+
target = Option52.String({ required: false });
|
|
28789
|
+
print = Option52.Boolean("--print", false);
|
|
28790
|
+
output = Option52.String("--output");
|
|
28791
|
+
resourceGroup = Option52.String("--resource-group", {
|
|
28708
28792
|
description: "m8t resource group to disambiguate the gateway (multi-deployment subscriptions)."
|
|
28709
28793
|
});
|
|
28710
28794
|
async executeCommand() {
|
|
@@ -28748,7 +28832,7 @@ var OpenCommand = class extends M8tCommand {
|
|
|
28748
28832
|
};
|
|
28749
28833
|
|
|
28750
28834
|
// src/commands/dream/run.ts
|
|
28751
|
-
import { Command as
|
|
28835
|
+
import { Command as Command56, Option as Option53 } from "clipanion";
|
|
28752
28836
|
import { AzureCliCredential } from "@azure/identity";
|
|
28753
28837
|
import { TableClient as TableClient8 } from "@azure/data-tables";
|
|
28754
28838
|
import { AIProjectClient as AIProjectClient3 } from "@azure/ai-projects";
|
|
@@ -31265,28 +31349,28 @@ function redactTranscripts(input) {
|
|
|
31265
31349
|
}
|
|
31266
31350
|
var DreamRunCommand = class extends M8tCommand {
|
|
31267
31351
|
static paths = [["dream", "run"]];
|
|
31268
|
-
static usage =
|
|
31352
|
+
static usage = Command56.Usage({
|
|
31269
31353
|
description: "Dry-run the brain consumption pipeline for one worker (no model call, no writes). Pass --live to actually run it.",
|
|
31270
31354
|
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.\n\nThe bare command is READ-ONLY. --live takes the other branch: a real model call and a commit to the worker's brain repo through a minted GitHub App token."
|
|
31271
31355
|
});
|
|
31272
|
-
worker =
|
|
31356
|
+
worker = Option53.String("--worker", { description: "Worker (canonical name) to harvest. Required." });
|
|
31273
31357
|
// Opting IN to the side effects, rather than opting out of them. This command's
|
|
31274
31358
|
// own help has always described a dry run, but the bare invocation used to take
|
|
31275
31359
|
// the live branch — a real model call and a commit to the brain repo — so anyone
|
|
31276
31360
|
// acting on `--help` got the opposite of what they read.
|
|
31277
|
-
live =
|
|
31278
|
-
dryRun =
|
|
31279
|
-
since =
|
|
31280
|
-
reset =
|
|
31281
|
-
showTranscripts =
|
|
31361
|
+
live = Option53.Boolean("--live", false, { description: "Actually run it: model call + commit to the brain repo." });
|
|
31362
|
+
dryRun = Option53.Boolean("--dry-run", false, { description: "Read-only harvest. This is the default; the flag is accepted for compatibility." });
|
|
31363
|
+
since = Option53.String("--since", { description: "ISO-8601 start override (rejected if malformed or future)." });
|
|
31364
|
+
reset = Option53.Boolean("--reset", false, { description: "Ignore the stored cursor; read from the beginning." });
|
|
31365
|
+
showTranscripts = Option53.Boolean("--show-transcripts", false, {
|
|
31282
31366
|
description: "Print transcript bodies (default: metadata only)."
|
|
31283
31367
|
});
|
|
31284
|
-
subscription =
|
|
31285
|
-
endpoint =
|
|
31286
|
-
storageAccount =
|
|
31368
|
+
subscription = Option53.String("--subscription");
|
|
31369
|
+
endpoint = Option53.String("--endpoint");
|
|
31370
|
+
storageAccount = Option53.String("--storage-account", {
|
|
31287
31371
|
description: "Ledger storage account name (skips tag-based discovery)"
|
|
31288
31372
|
});
|
|
31289
|
-
output =
|
|
31373
|
+
output = Option53.String("--output");
|
|
31290
31374
|
// Resolution seam — overridden by tests; built lazily at runtime otherwise.
|
|
31291
31375
|
deps;
|
|
31292
31376
|
async executeCommand() {
|
|
@@ -31661,7 +31745,7 @@ function defaultDeps(overrides) {
|
|
|
31661
31745
|
|
|
31662
31746
|
// src/commands/conversations/sweep.ts
|
|
31663
31747
|
import { createHash as createHash5 } from "crypto";
|
|
31664
|
-
import { Command as
|
|
31748
|
+
import { Command as Command57, Option as Option54 } from "clipanion";
|
|
31665
31749
|
import { AzureCliCredential as AzureCliCredential2 } from "@azure/identity";
|
|
31666
31750
|
import { TableClient as TableClient9 } from "@azure/data-tables";
|
|
31667
31751
|
import { AIProjectClient as AIProjectClient4 } from "@azure/ai-projects";
|
|
@@ -31787,7 +31871,7 @@ function defaultDeps2() {
|
|
|
31787
31871
|
}
|
|
31788
31872
|
var ConversationsSweepCommand = class extends M8tCommand {
|
|
31789
31873
|
static paths = [["conversations", "sweep"]];
|
|
31790
|
-
static usage =
|
|
31874
|
+
static usage = Command57.Usage({
|
|
31791
31875
|
category: "Conversations",
|
|
31792
31876
|
description: "Delete expired public-visitor conversations (dry run by default)",
|
|
31793
31877
|
details: `
|
|
@@ -31803,22 +31887,22 @@ var ConversationsSweepCommand = class extends M8tCommand {
|
|
|
31803
31887
|
["Delete, capped at 50", "m8t conversations sweep --delete --max 50"]
|
|
31804
31888
|
]
|
|
31805
31889
|
});
|
|
31806
|
-
doDelete =
|
|
31890
|
+
doDelete = Option54.Boolean("--delete", false, {
|
|
31807
31891
|
description: "Perform deletions (without this flag the command only reports)"
|
|
31808
31892
|
});
|
|
31809
|
-
principal =
|
|
31893
|
+
principal = Option54.String("--principal", {
|
|
31810
31894
|
description: "Service-principal oid whose end-user keys are known-ephemeral (required with --delete; a dry run without it reports candidates grouped by principal)"
|
|
31811
31895
|
});
|
|
31812
|
-
max =
|
|
31813
|
-
graceDays =
|
|
31896
|
+
max = Option54.String("--max", "200", { description: "Maximum deletions per run" });
|
|
31897
|
+
graceDays = Option54.String("--grace-days", "14", {
|
|
31814
31898
|
description: "Days past the 30-day key life before a conversation is eligible"
|
|
31815
31899
|
});
|
|
31816
|
-
lookbackDays =
|
|
31900
|
+
lookbackDays = Option54.String("--lookback-days", "180", {
|
|
31817
31901
|
description: "How far back to scan ledger activity for candidates"
|
|
31818
31902
|
});
|
|
31819
|
-
subscription =
|
|
31820
|
-
endpoint =
|
|
31821
|
-
storageAccount =
|
|
31903
|
+
subscription = Option54.String("--subscription", { description: "Azure subscription id override" });
|
|
31904
|
+
endpoint = Option54.String("--endpoint", { description: "Foundry project endpoint override" });
|
|
31905
|
+
storageAccount = Option54.String("--storage-account", {
|
|
31822
31906
|
description: "Ledger storage account name (skips tag-based discovery)"
|
|
31823
31907
|
});
|
|
31824
31908
|
deps = defaultDeps2();
|
|
@@ -31946,7 +32030,7 @@ var ConversationsSweepCommand = class extends M8tCommand {
|
|
|
31946
32030
|
};
|
|
31947
32031
|
|
|
31948
32032
|
// src/commands/foundry/create.ts
|
|
31949
|
-
import { Command as
|
|
32033
|
+
import { Command as Command58, Option as Option55 } from "clipanion";
|
|
31950
32034
|
|
|
31951
32035
|
// src/lib/foundry-create.ts
|
|
31952
32036
|
init_errors();
|
|
@@ -32184,7 +32268,7 @@ async function createFoundryProject(args) {
|
|
|
32184
32268
|
init_errors();
|
|
32185
32269
|
var FoundryCreateCommand = class extends M8tCommand {
|
|
32186
32270
|
static paths = [["foundry", "create"]];
|
|
32187
|
-
static usage =
|
|
32271
|
+
static usage = Command58.Usage({
|
|
32188
32272
|
description: "Create an AI Foundry (AIServices) account + project + model deployment from scratch.",
|
|
32189
32273
|
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).",
|
|
32190
32274
|
examples: [
|
|
@@ -32193,16 +32277,16 @@ var FoundryCreateCommand = class extends M8tCommand {
|
|
|
32193
32277
|
["Higher capacity for a reasoning model", "$0 foundry create --resource-group rg-m8t-stack --location eastus2 --model gpt-5-mini --capacity 250"]
|
|
32194
32278
|
]
|
|
32195
32279
|
});
|
|
32196
|
-
resourceGroup =
|
|
32197
|
-
location =
|
|
32198
|
-
account =
|
|
32199
|
-
project =
|
|
32200
|
-
model =
|
|
32201
|
-
modelVersion =
|
|
32202
|
-
capacity =
|
|
32203
|
-
subscription =
|
|
32204
|
-
skipQuotaCheck =
|
|
32205
|
-
output =
|
|
32280
|
+
resourceGroup = Option55.String("--resource-group");
|
|
32281
|
+
location = Option55.String("--location");
|
|
32282
|
+
account = Option55.String("--account");
|
|
32283
|
+
project = Option55.String("--project", "m8t");
|
|
32284
|
+
model = Option55.String("--model", "gpt-4.1-mini");
|
|
32285
|
+
modelVersion = Option55.String("--model-version", "2025-04-14");
|
|
32286
|
+
capacity = Option55.String("--capacity", "50");
|
|
32287
|
+
subscription = Option55.String("--subscription");
|
|
32288
|
+
skipQuotaCheck = Option55.Boolean("--skip-quota-check", false);
|
|
32289
|
+
output = Option55.String("--output");
|
|
32206
32290
|
async executeCommand() {
|
|
32207
32291
|
const mode = resolveOutputMode(
|
|
32208
32292
|
this.output,
|
|
@@ -32274,22 +32358,22 @@ var FoundryCreateCommand = class extends M8tCommand {
|
|
|
32274
32358
|
};
|
|
32275
32359
|
|
|
32276
32360
|
// src/commands/foundry/await-ready.ts
|
|
32277
|
-
import { Command as
|
|
32361
|
+
import { Command as Command59, Option as Option56 } from "clipanion";
|
|
32278
32362
|
import { AzureCliCredential as AzureCliCredential3 } from "@azure/identity";
|
|
32279
32363
|
init_errors();
|
|
32280
32364
|
var FoundryAwaitReadyCommand = class extends M8tCommand {
|
|
32281
32365
|
static paths = [["foundry", "await-ready"]];
|
|
32282
|
-
static usage =
|
|
32366
|
+
static usage = Command59.Usage({
|
|
32283
32367
|
description: "Wait until a freshly-created Foundry project's data plane reliably serves it.",
|
|
32284
32368
|
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.",
|
|
32285
32369
|
examples: [["Wait for a project to be ready", "$0 foundry await-ready --endpoint https://acc.services.ai.azure.com/api/projects/m8t"]]
|
|
32286
32370
|
});
|
|
32287
|
-
endpoint =
|
|
32288
|
-
consecutive =
|
|
32289
|
-
attempts =
|
|
32290
|
-
interval =
|
|
32291
|
-
subscription =
|
|
32292
|
-
output =
|
|
32371
|
+
endpoint = Option56.String("--endpoint");
|
|
32372
|
+
consecutive = Option56.String("--consecutive", "3");
|
|
32373
|
+
attempts = Option56.String("--attempts", "60");
|
|
32374
|
+
interval = Option56.String("--interval", "5");
|
|
32375
|
+
subscription = Option56.String("--subscription");
|
|
32376
|
+
output = Option56.String("--output");
|
|
32293
32377
|
async executeCommand() {
|
|
32294
32378
|
const mode = resolveOutputMode(this.output, this.context.stdout);
|
|
32295
32379
|
const endpoint = typeof this.endpoint === "string" ? this.endpoint : void 0;
|
|
@@ -32323,7 +32407,7 @@ var FoundryAwaitReadyCommand = class extends M8tCommand {
|
|
|
32323
32407
|
};
|
|
32324
32408
|
|
|
32325
32409
|
// src/commands/bootstrap/preflight.ts
|
|
32326
|
-
import { Command as
|
|
32410
|
+
import { Command as Command60, Option as Option57 } from "clipanion";
|
|
32327
32411
|
|
|
32328
32412
|
// ../../packages/telemetry-contract/artifact/tier-map.ts
|
|
32329
32413
|
var EVENT_TIERS = {
|
|
@@ -32375,7 +32459,7 @@ function preflightRenderable(results) {
|
|
|
32375
32459
|
}
|
|
32376
32460
|
var BootstrapPreflightCommand = class extends M8tCommand {
|
|
32377
32461
|
static paths = [["bootstrap", "preflight"]];
|
|
32378
|
-
static usage =
|
|
32462
|
+
static usage = Command60.Usage({
|
|
32379
32463
|
description: "Loudly verify you can install m8t (Owner/UAA + directory admin) and hard-stop if not.",
|
|
32380
32464
|
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), soft-deleted Cognitive Services accounts (they keep their model quota until purged, so quota can read free and the model deployment still fail), 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.",
|
|
32381
32465
|
examples: [
|
|
@@ -32384,9 +32468,9 @@ var BootstrapPreflightCommand = class extends M8tCommand {
|
|
|
32384
32468
|
["BYO app registration (directory guests)", "$0 bootstrap preflight --client-id <appId>"]
|
|
32385
32469
|
]
|
|
32386
32470
|
});
|
|
32387
|
-
clientId =
|
|
32388
|
-
subscription =
|
|
32389
|
-
location =
|
|
32471
|
+
clientId = Option57.String("--client-id");
|
|
32472
|
+
subscription = Option57.String("--subscription");
|
|
32473
|
+
location = Option57.String("--location", {
|
|
32390
32474
|
description: "Target region. Enables the model-quota check \u2014 without it, quota is not verified."
|
|
32391
32475
|
});
|
|
32392
32476
|
async executeCommand() {
|
|
@@ -32483,10 +32567,10 @@ ${colors.error(" " + why)}
|
|
|
32483
32567
|
}
|
|
32484
32568
|
|
|
32485
32569
|
// src/commands/bootstrap/launch.ts
|
|
32486
|
-
import * as
|
|
32570
|
+
import * as fs35 from "fs";
|
|
32487
32571
|
import * as os15 from "os";
|
|
32488
32572
|
import * as path37 from "path";
|
|
32489
|
-
import { Command as
|
|
32573
|
+
import { Command as Command61, Option as Option58 } from "clipanion";
|
|
32490
32574
|
init_errors();
|
|
32491
32575
|
|
|
32492
32576
|
// src/lib/bootstrap-mi.ts
|
|
@@ -32589,6 +32673,8 @@ function buildAciCreateArgs(s) {
|
|
|
32589
32673
|
`INSTALLER_IMAGE_REF=${s.image}`
|
|
32590
32674
|
];
|
|
32591
32675
|
if (s.platformVersion) env.push(`M8T_PLATFORM_VERSION=${s.platformVersion}`);
|
|
32676
|
+
if (s.releaseManifestSha256) env.push(`M8T_RELEASE_MANIFEST_SHA256=${s.releaseManifestSha256}`);
|
|
32677
|
+
if (s.unreleasedDebug) env.push("M8T_UNRELEASED_DEBUG=1");
|
|
32592
32678
|
if (s.gatewayImageRef) env.push(`GATEWAY_IMAGE_REF=${s.gatewayImageRef}`);
|
|
32593
32679
|
if (s.foundryTracing) env.push(`FOUNDRY_TRACING=${s.foundryTracing}`);
|
|
32594
32680
|
if (s.skipBrains) env.push("SKIP_BRAINS=true");
|
|
@@ -32849,6 +32935,142 @@ ${colors.error("\u2717")} The GitHub App on disk is installed on ${colors.field(
|
|
|
32849
32935
|
`;
|
|
32850
32936
|
}
|
|
32851
32937
|
|
|
32938
|
+
// src/lib/bootstrap-launch-target.ts
|
|
32939
|
+
init_errors();
|
|
32940
|
+
var BARE_VERSION = /^\d+\.\d+\.\d+(?:-canary\.(?:0|[1-9]\d*))?$/;
|
|
32941
|
+
function bareVersionOf(raw) {
|
|
32942
|
+
const bare = raw.trim().replace(/^platform-v/, "");
|
|
32943
|
+
return BARE_VERSION.test(bare) ? bare : null;
|
|
32944
|
+
}
|
|
32945
|
+
function engineImageOf(m) {
|
|
32946
|
+
const c = m.manifest.components.installer;
|
|
32947
|
+
return `${c.ref}:${c.tag}`;
|
|
32948
|
+
}
|
|
32949
|
+
async function resolveLaunchTarget(args) {
|
|
32950
|
+
const { pinnedInstallerRef, pinnedPlatformVersion, fetchSealed } = args;
|
|
32951
|
+
const release = typeof args.release === "string" && args.release.length > 0 ? args.release : void 0;
|
|
32952
|
+
const tagOverride = typeof args.installerTag === "string" && args.installerTag.length > 0 ? args.installerTag : void 0;
|
|
32953
|
+
const imageOverride = typeof args.installerImage === "string" && args.installerImage.length > 0 ? args.installerImage : void 0;
|
|
32954
|
+
const pinnedRepo = pinnedInstallerRef.slice(0, pinnedInstallerRef.lastIndexOf(":"));
|
|
32955
|
+
const overrideEngine = imageOverride ?? (tagOverride !== void 0 ? `${pinnedRepo}:${tagOverride}` : void 0);
|
|
32956
|
+
const explicitEngine = overrideEngine !== void 0 && overrideEngine !== pinnedInstallerRef;
|
|
32957
|
+
const overrideFlag = imageOverride !== void 0 ? "--installer-image" : "--installer-tag";
|
|
32958
|
+
if (release !== void 0) {
|
|
32959
|
+
const bare = bareVersionOf(release);
|
|
32960
|
+
if (!bare) {
|
|
32961
|
+
throw new LocalCliError({
|
|
32962
|
+
code: "BOOTSTRAP_RELEASE_INVALID",
|
|
32963
|
+
message: `--release must name a platform release, got ${JSON.stringify(release)}.`,
|
|
32964
|
+
hint: "Pass a release version, e.g. --release 0.7.2 (or --release platform-v0.7.2). There is no 'latest' here on purpose: an install pins the release it installs."
|
|
32965
|
+
});
|
|
32966
|
+
}
|
|
32967
|
+
const sealedManifest2 = await fetchSealed(bare);
|
|
32968
|
+
const engineFromManifest2 = engineImageOf(sealedManifest2);
|
|
32969
|
+
if (explicitEngine && overrideEngine !== engineFromManifest2) {
|
|
32970
|
+
throw new LocalCliError({
|
|
32971
|
+
code: "BOOTSTRAP_RELEASE_OVERRIDE_CONFLICT",
|
|
32972
|
+
message: `--release ${bare} names the engine ${engineFromManifest2}, but ${overrideFlag} names ${overrideEngine}.`,
|
|
32973
|
+
hint: `Drop the override to install release ${bare} as published, or drop --release to launch that engine as an unreleased-debug install.`
|
|
32974
|
+
});
|
|
32975
|
+
}
|
|
32976
|
+
assertManifestSelfConsistent(sealedManifest2, bare);
|
|
32977
|
+
return { kind: "released", version: bare, engineImage: engineFromManifest2, manifestSha256: sealedManifest2.digest };
|
|
32978
|
+
}
|
|
32979
|
+
if (explicitEngine) {
|
|
32980
|
+
const engineImage = overrideEngine;
|
|
32981
|
+
return {
|
|
32982
|
+
kind: "unreleased-debug",
|
|
32983
|
+
engineImage,
|
|
32984
|
+
reason: `${overrideFlag} points this install at ${engineImage}, which no release manifest names. The install will deploy what that engine carries and will NOT claim a platform release version.`
|
|
32985
|
+
};
|
|
32986
|
+
}
|
|
32987
|
+
const sealedManifest = await fetchSealed(pinnedPlatformVersion);
|
|
32988
|
+
assertManifestSelfConsistent(sealedManifest, pinnedPlatformVersion);
|
|
32989
|
+
const engineFromManifest = engineImageOf(sealedManifest);
|
|
32990
|
+
if (engineFromManifest !== pinnedInstallerRef) {
|
|
32991
|
+
throw new LocalCliError({
|
|
32992
|
+
code: "BOOTSTRAP_ENGINE_MANIFEST_MISMATCH",
|
|
32993
|
+
message: `This CLI installs platform ${pinnedPlatformVersion} with the engine ${pinnedInstallerRef}, but ${pinnedPlatformVersion}'s manifest names ${engineFromManifest}. The release and this CLI disagree about which engine runs it.`,
|
|
32994
|
+
hint: `Get the newest CLI with \`npm install -g @m8t-stack/cli\` \u2014 the CLI is what pins the engine. If that does not resolve it, install the release explicitly with \`m8t bootstrap launch --release ${pinnedPlatformVersion}\`, which takes the engine from the manifest.`
|
|
32995
|
+
});
|
|
32996
|
+
}
|
|
32997
|
+
return {
|
|
32998
|
+
kind: "released",
|
|
32999
|
+
version: pinnedPlatformVersion,
|
|
33000
|
+
engineImage: pinnedInstallerRef,
|
|
33001
|
+
manifestSha256: sealedManifest.digest
|
|
33002
|
+
};
|
|
33003
|
+
}
|
|
33004
|
+
function assertManifestSelfConsistent(sealedManifest, expected) {
|
|
33005
|
+
const declared = sealedManifest.manifest.platform.version;
|
|
33006
|
+
if (declared !== expected) {
|
|
33007
|
+
throw new LocalCliError({
|
|
33008
|
+
code: "BOOTSTRAP_MANIFEST_VERSION_MISMATCH",
|
|
33009
|
+
message: `Asked the channel for platform ${expected}, but the manifest it returned declares ${declared}.`,
|
|
33010
|
+
hint: "The channel pointer may be misconfigured, or that release's manifest asset is wrong. Nothing was installed."
|
|
33011
|
+
});
|
|
33012
|
+
}
|
|
33013
|
+
}
|
|
33014
|
+
|
|
33015
|
+
// src/lib/manifest-seal.ts
|
|
33016
|
+
import * as fs34 from "fs";
|
|
33017
|
+
import { createHash as createHash8 } from "crypto";
|
|
33018
|
+
init_errors();
|
|
33019
|
+
function manifestDigestOf(raw) {
|
|
33020
|
+
return `sha256:${createHash8("sha256").update(raw, "utf8").digest("hex")}`;
|
|
33021
|
+
}
|
|
33022
|
+
async function fetchSealedManifest(source, deps = {}) {
|
|
33023
|
+
const readFile10 = deps.readFile ?? ((p) => fs34.readFileSync(p, "utf8"));
|
|
33024
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
33025
|
+
const ghToken = deps.ghToken ?? getGhToken;
|
|
33026
|
+
let raw;
|
|
33027
|
+
if ("file" in source) {
|
|
33028
|
+
try {
|
|
33029
|
+
raw = readFile10(source.file);
|
|
33030
|
+
} catch (e) {
|
|
33031
|
+
throw new LocalCliError({
|
|
33032
|
+
code: "PLATFORM_MANIFEST_READ_FAILED",
|
|
33033
|
+
message: `Could not read manifest file '${source.file}': ${e.message}`
|
|
33034
|
+
});
|
|
33035
|
+
}
|
|
33036
|
+
} else {
|
|
33037
|
+
const url = "url" in source ? source.url : source.version ? channelUrlForVersion(source.version) : CHANNEL_LATEST_URL;
|
|
33038
|
+
let token;
|
|
33039
|
+
try {
|
|
33040
|
+
token = await ghToken();
|
|
33041
|
+
} catch {
|
|
33042
|
+
token = void 0;
|
|
33043
|
+
}
|
|
33044
|
+
const res = await fetchImpl(url, { headers: token ? { Authorization: `Bearer ${token}` } : {} });
|
|
33045
|
+
if (!res.ok) {
|
|
33046
|
+
throw new LocalCliError({
|
|
33047
|
+
code: "PLATFORM_MANIFEST_FETCH_FAILED",
|
|
33048
|
+
message: `GET ${url} returned HTTP ${res.status.toString()}.`,
|
|
33049
|
+
hint: "The engine fetches this same URL and must see the same bytes. Check the release exists and its manifest asset is published."
|
|
33050
|
+
});
|
|
33051
|
+
}
|
|
33052
|
+
raw = await res.text();
|
|
33053
|
+
}
|
|
33054
|
+
let parsed;
|
|
33055
|
+
try {
|
|
33056
|
+
parsed = JSON.parse(raw);
|
|
33057
|
+
} catch (e) {
|
|
33058
|
+
throw new LocalCliError({
|
|
33059
|
+
code: "PLATFORM_MANIFEST_PARSE_FAILED",
|
|
33060
|
+
message: `Manifest is not valid JSON: ${e.message}`
|
|
33061
|
+
});
|
|
33062
|
+
}
|
|
33063
|
+
const errors = validateManifest(parsed);
|
|
33064
|
+
if (errors.length > 0) {
|
|
33065
|
+
throw new LocalCliError({
|
|
33066
|
+
code: "PLATFORM_MANIFEST_INVALID",
|
|
33067
|
+
message: `Fetched manifest failed validation:
|
|
33068
|
+
- ${errors.join("\n - ")}`
|
|
33069
|
+
});
|
|
33070
|
+
}
|
|
33071
|
+
return { raw, digest: manifestDigestOf(raw), manifest: parsed };
|
|
33072
|
+
}
|
|
33073
|
+
|
|
32852
33074
|
// src/commands/bootstrap/launch.ts
|
|
32853
33075
|
var DEFAULT_RG = "rg-m8t-stack";
|
|
32854
33076
|
var DEFAULT_INSTALLER = `${PINNED_INSTALLER.registry}/${PINNED_INSTALLER.image}`;
|
|
@@ -32857,7 +33079,7 @@ var ACI_NAME = "m8t-installer";
|
|
|
32857
33079
|
var MI_NAME = "m8t-installer-mi";
|
|
32858
33080
|
var BootstrapLaunchCommand = class extends M8tCommand {
|
|
32859
33081
|
static paths = [["bootstrap", "launch"]];
|
|
32860
|
-
static usage =
|
|
33082
|
+
static usage = Command61.Usage({
|
|
32861
33083
|
description: "Create + authorize the installer managed identity, then kick the cloud installer.",
|
|
32862
33084
|
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` and `reap`, and threads your Azure object id so the installer can grant you the platform's data-plane roles itself.",
|
|
32863
33085
|
examples: [
|
|
@@ -32868,31 +33090,37 @@ var BootstrapLaunchCommand = class extends M8tCommand {
|
|
|
32868
33090
|
["Share a support contact", "$0 bootstrap launch --location eastus2 --contact-email you@example.com --company 'Acme'"]
|
|
32869
33091
|
]
|
|
32870
33092
|
});
|
|
32871
|
-
location =
|
|
32872
|
-
resourceGroup =
|
|
32873
|
-
clientId =
|
|
32874
|
-
subscription =
|
|
32875
|
-
|
|
33093
|
+
location = Option58.String("--location");
|
|
33094
|
+
resourceGroup = Option58.String("--resource-group");
|
|
33095
|
+
clientId = Option58.String("--client-id");
|
|
33096
|
+
subscription = Option58.String("--subscription");
|
|
33097
|
+
// Install a PAST release, properly: its sealed manifest, the engine ITS manifest
|
|
33098
|
+
// names, and a stamp that honestly says which release this is. The replication
|
|
33099
|
+
// case ("they installed 0.7.2 and won't update") used to be approximated with
|
|
33100
|
+
// --installer-tag, which pins the engine and nothing else — right box, wrong
|
|
33101
|
+
// everything else, and silent about it.
|
|
33102
|
+
release = Option58.String("--release", { description: "Install a specific published release (e.g. 0.7.2) instead of the one this CLI pins. Uses that release's own engine and manifest." });
|
|
33103
|
+
installerTag = Option58.String("--installer-tag");
|
|
32876
33104
|
// Full image ref override (registry + repo + tag) — an escape hatch when the
|
|
32877
33105
|
// default org/tag is wrong for the current CLI (e.g. a stale published build).
|
|
32878
33106
|
// Wins over --installer-tag / the pinned default.
|
|
32879
|
-
installerImage =
|
|
32880
|
-
gatewayImageRef =
|
|
32881
|
-
githubAppCreds =
|
|
32882
|
-
contactEmail =
|
|
32883
|
-
company =
|
|
33107
|
+
installerImage = Option58.String("--installer-image");
|
|
33108
|
+
gatewayImageRef = Option58.String("--gateway-image-ref");
|
|
33109
|
+
githubAppCreds = Option58.String("--github-app-creds");
|
|
33110
|
+
contactEmail = Option58.String("--contact-email", { description: "Share a contact email with m8t support. Not sent unless you pass it." });
|
|
33111
|
+
company = Option58.String("--company", { description: "Share your company name with m8t support. Not sent unless you pass it." });
|
|
32884
33112
|
// Value-carrying on purpose: a bare --force would be cargo-culted into
|
|
32885
33113
|
// runbooks and harness prompts and erode the protection, whereas a faithful
|
|
32886
33114
|
// paste can never accidentally carry the victim group's name. It AUTHORIZES
|
|
32887
33115
|
// the target; --resource-group is what CHOOSES it.
|
|
32888
|
-
reinstallInto =
|
|
32889
|
-
org =
|
|
32890
|
-
noBrains =
|
|
33116
|
+
reinstallInto = Option58.String("--reinstall-into", { description: "Consent to installing into --resource-group even though it already holds resources. Must match the target group's name." });
|
|
33117
|
+
org = Option58.String("--org", { description: "Assert the GitHub App on disk is installed on this org; refuses on a mismatch." });
|
|
33118
|
+
noBrains = Option58.Boolean("--no-brains", false, { description: "Install without brain-backed workers. For test and CI rigs; the supported install creates brains." });
|
|
32891
33119
|
// The opt-out TELEMETRY.md names. Without it there is no way to decline at
|
|
32892
33120
|
// install time — the ACI env is built entirely from these options, so a
|
|
32893
33121
|
// founder setting FOUNDRY_TRACING in their own shell reaches nothing. A
|
|
32894
33122
|
// privacy notice that documents a control has to be a control that exists.
|
|
32895
|
-
foundryTracing =
|
|
33123
|
+
foundryTracing = Option58.String("--foundry-tracing", { description: "Where agent traces go: project (default, your own App Insights) | account (legacy shared) | skip (no tracing \u2014 you lose the record of what your agents did)." });
|
|
32896
33124
|
async executeCommand() {
|
|
32897
33125
|
const location = typeof this.location === "string" ? this.location : void 0;
|
|
32898
33126
|
if (!location) {
|
|
@@ -32901,15 +33129,38 @@ var BootstrapLaunchCommand = class extends M8tCommand {
|
|
|
32901
33129
|
const resourceGroupOpt = typeof this.resourceGroup === "string" ? this.resourceGroup : void 0;
|
|
32902
33130
|
const resourceGroup = resourceGroupOpt ?? DEFAULT_RG;
|
|
32903
33131
|
const clientIdOpt = typeof this.clientId === "string" ? this.clientId : void 0;
|
|
32904
|
-
const installerTag = (typeof this.installerTag === "string" ? this.installerTag : void 0) ?? DEFAULT_INSTALLER_TAG;
|
|
32905
|
-
const installerImageOverride = typeof this.installerImage === "string" ? this.installerImage : void 0;
|
|
32906
|
-
const installerImage = installerImageOverride ?? `${DEFAULT_INSTALLER}:${installerTag}`;
|
|
32907
33132
|
const gatewayImageRef = typeof this.gatewayImageRef === "string" ? this.gatewayImageRef : void 0;
|
|
32908
33133
|
const foundryTracing = parseFoundryTracingMode(typeof this.foundryTracing === "string" ? this.foundryTracing : void 0) ?? DEFAULT_FOUNDRY_TRACING;
|
|
32909
33134
|
const account = await getAzAccount();
|
|
32910
33135
|
const subscriptionId = (typeof this.subscription === "string" ? this.subscription : void 0) ?? account.subscriptionId;
|
|
32911
33136
|
const out = (m) => this.context.stderr.write(` ${colors.dim(m)}
|
|
32912
33137
|
`);
|
|
33138
|
+
const target = await resolveLaunchTarget({
|
|
33139
|
+
pinnedInstallerRef: `${DEFAULT_INSTALLER}:${DEFAULT_INSTALLER_TAG}`,
|
|
33140
|
+
pinnedPlatformVersion: PINNED_PLATFORM_VERSION,
|
|
33141
|
+
...typeof this.release === "string" ? { release: this.release } : {},
|
|
33142
|
+
...typeof this.installerTag === "string" ? { installerTag: this.installerTag } : {},
|
|
33143
|
+
...typeof this.installerImage === "string" ? { installerImage: this.installerImage } : {},
|
|
33144
|
+
// THE SAME RULE THE ENGINE APPLIES, from the same helper family. The engine
|
|
33145
|
+
// derives its URL with manifest_url_for (installer/lib.sh), which honours
|
|
33146
|
+
// M8T_UPDATE_CHANNEL_URL — and this launch threads that very variable into
|
|
33147
|
+
// the container below. Sealing against the DEFAULT channel while the engine
|
|
33148
|
+
// fetched a custom one would make the two hash different documents and
|
|
33149
|
+
// refuse every custom-channel install at preflight, blaming the network.
|
|
33150
|
+
// Every shakedown rig uses a custom channel.
|
|
33151
|
+
fetchSealed: async (version) => fetchSealedManifest(manifestSourceForVersionTag(process.env.M8T_UPDATE_CHANNEL_URL ?? "", platformTag(version)))
|
|
33152
|
+
});
|
|
33153
|
+
const installerImage = target.engineImage;
|
|
33154
|
+
const installerTag = installerImage.slice(installerImage.lastIndexOf(":") + 1);
|
|
33155
|
+
if (target.kind === "unreleased-debug") {
|
|
33156
|
+
this.context.stderr.write(
|
|
33157
|
+
`${colors.warn("!")} unreleased-debug install: ${target.reason}
|
|
33158
|
+
${colors.hint("this install will report no platform version, and the updater will treat it as unrecognised.")}
|
|
33159
|
+
`
|
|
33160
|
+
);
|
|
33161
|
+
} else {
|
|
33162
|
+
out(`installing platform ${target.version} with engine ${installerImage}`);
|
|
33163
|
+
}
|
|
32913
33164
|
const reinstallInto = typeof this.reinstallInto === "string" ? this.reinstallInto : void 0;
|
|
32914
33165
|
if (reinstallInto !== void 0 && reinstallInto.toLowerCase() !== resourceGroup.toLowerCase()) {
|
|
32915
33166
|
const targetExplanation = resourceGroupOpt === void 0 ? `you did not pass --resource-group, so the target is the default '${DEFAULT_RG}'` : `the target resource group is '${resourceGroup}'`;
|
|
@@ -32938,7 +33189,7 @@ var BootstrapLaunchCommand = class extends M8tCommand {
|
|
|
32938
33189
|
let githubApp;
|
|
32939
33190
|
if (skipBrains) {
|
|
32940
33191
|
out("installing without brain-backed workers; no GitHub App credentials will be used");
|
|
32941
|
-
} else if (!
|
|
33192
|
+
} else if (!fs35.existsSync(credsPath)) {
|
|
32942
33193
|
this.context.stderr.write(buildMissingCredsRefusal(credsPath));
|
|
32943
33194
|
throw new LocalCliError({
|
|
32944
33195
|
code: "GITHUB_APP_CREDS_MISSING",
|
|
@@ -32948,7 +33199,7 @@ var BootstrapLaunchCommand = class extends M8tCommand {
|
|
|
32948
33199
|
} else {
|
|
32949
33200
|
let parsed;
|
|
32950
33201
|
try {
|
|
32951
|
-
parsed = JSON.parse(
|
|
33202
|
+
parsed = JSON.parse(fs35.readFileSync(credsPath, "utf8"));
|
|
32952
33203
|
} catch {
|
|
32953
33204
|
throw new LocalCliError({
|
|
32954
33205
|
code: "GITHUB_APP_CREDS_INVALID",
|
|
@@ -32966,7 +33217,7 @@ var BootstrapLaunchCommand = class extends M8tCommand {
|
|
|
32966
33217
|
}
|
|
32967
33218
|
let pem;
|
|
32968
33219
|
try {
|
|
32969
|
-
pem =
|
|
33220
|
+
pem = fs35.readFileSync(parsed.pemPath, "utf8");
|
|
32970
33221
|
} catch {
|
|
32971
33222
|
throw new LocalCliError({
|
|
32972
33223
|
code: "GITHUB_APP_PEM_MISSING",
|
|
@@ -33022,7 +33273,6 @@ var BootstrapLaunchCommand = class extends M8tCommand {
|
|
|
33022
33273
|
} catch (e) {
|
|
33023
33274
|
out(`warning: could not determine your Azure object id (${e.message}) \u2014 the install will not grant you data-plane access; run 'm8t prereqs --fix' afterwards`);
|
|
33024
33275
|
}
|
|
33025
|
-
const platformVersion = installerImage === PINNED_INSTALLER.ref ? PINNED_PLATFORM_VERSION : "";
|
|
33026
33276
|
out("launching the cloud installer (ACI)\u2026");
|
|
33027
33277
|
await kickInstaller({
|
|
33028
33278
|
aciName: ACI_NAME,
|
|
@@ -33036,7 +33286,7 @@ var BootstrapLaunchCommand = class extends M8tCommand {
|
|
|
33036
33286
|
gatewayImageRef,
|
|
33037
33287
|
foundryTracing,
|
|
33038
33288
|
githubApp,
|
|
33039
|
-
...
|
|
33289
|
+
...target.kind === "released" ? { platformVersion: target.version, releaseManifestSha256: target.manifestSha256 } : { unreleasedDebug: true },
|
|
33040
33290
|
...founderObjectId ? { founderObjectId } : {},
|
|
33041
33291
|
...skipBrains ? { skipBrains: true } : {},
|
|
33042
33292
|
// Opt-in only. The signed-in UPN was previously seeded here automatically;
|
|
@@ -33056,7 +33306,7 @@ var BootstrapLaunchCommand = class extends M8tCommand {
|
|
|
33056
33306
|
};
|
|
33057
33307
|
|
|
33058
33308
|
// src/commands/bootstrap/status.ts
|
|
33059
|
-
import { Command as
|
|
33309
|
+
import { Command as Command63, Option as Option60 } from "clipanion";
|
|
33060
33310
|
init_errors();
|
|
33061
33311
|
|
|
33062
33312
|
// src/lib/bootstrap-aci-state.ts
|
|
@@ -33083,13 +33333,13 @@ async function getAciState(opts) {
|
|
|
33083
33333
|
|
|
33084
33334
|
// src/lib/bootstrap-finalize.ts
|
|
33085
33335
|
init_errors();
|
|
33086
|
-
import * as
|
|
33336
|
+
import * as fs40 from "fs/promises";
|
|
33087
33337
|
import * as os20 from "os";
|
|
33088
33338
|
import * as path44 from "path";
|
|
33089
33339
|
|
|
33090
33340
|
// src/lib/company-profile-seed.ts
|
|
33091
33341
|
import { spawn as spawn6 } from "child_process";
|
|
33092
|
-
import { closeSync, openSync, readFileSync as
|
|
33342
|
+
import { closeSync, openSync, readFileSync as readFileSync26 } from "fs";
|
|
33093
33343
|
import * as os17 from "os";
|
|
33094
33344
|
import * as path39 from "path";
|
|
33095
33345
|
init_errors();
|
|
@@ -33686,7 +33936,7 @@ async function getSignedInUserIdentity(runAzImpl = runAz) {
|
|
|
33686
33936
|
}
|
|
33687
33937
|
|
|
33688
33938
|
// src/lib/onboarding-profile-store.ts
|
|
33689
|
-
import * as
|
|
33939
|
+
import * as fs36 from "fs/promises";
|
|
33690
33940
|
import * as os16 from "os";
|
|
33691
33941
|
import * as path38 from "path";
|
|
33692
33942
|
var ONBOARDING_PROFILE_FILE = "onboarding-profile.json";
|
|
@@ -33727,7 +33977,7 @@ function canonical(value) {
|
|
|
33727
33977
|
async function readOnboardingProfile(home = os16.homedir()) {
|
|
33728
33978
|
let raw;
|
|
33729
33979
|
try {
|
|
33730
|
-
raw = await
|
|
33980
|
+
raw = await fs36.readFile(file(home), "utf8");
|
|
33731
33981
|
} catch {
|
|
33732
33982
|
return null;
|
|
33733
33983
|
}
|
|
@@ -33738,11 +33988,11 @@ async function readOnboardingProfile(home = os16.homedir()) {
|
|
|
33738
33988
|
}
|
|
33739
33989
|
}
|
|
33740
33990
|
async function writeOnboardingProfile(profile, home = os16.homedir()) {
|
|
33741
|
-
await
|
|
33991
|
+
await fs36.mkdir(dir(home), { recursive: true });
|
|
33742
33992
|
const target = file(home);
|
|
33743
33993
|
const tmp = `${target}.tmp`;
|
|
33744
|
-
await
|
|
33745
|
-
await
|
|
33994
|
+
await fs36.writeFile(tmp, JSON.stringify(profile, null, 2), { encoding: "utf8", mode: 384 });
|
|
33995
|
+
await fs36.rename(tmp, target);
|
|
33746
33996
|
}
|
|
33747
33997
|
function toOnboardingBlock(profile) {
|
|
33748
33998
|
return {
|
|
@@ -33759,7 +34009,7 @@ function toOnboardingBlock(profile) {
|
|
|
33759
34009
|
function readGithubAppCreds(credsPath) {
|
|
33760
34010
|
const p = credsPath ?? path39.join(os17.homedir(), ".m8t", "github-app.json");
|
|
33761
34011
|
try {
|
|
33762
|
-
return JSON.parse(
|
|
34012
|
+
return JSON.parse(readFileSync26(p, "utf8"));
|
|
33763
34013
|
} catch {
|
|
33764
34014
|
return null;
|
|
33765
34015
|
}
|
|
@@ -33828,7 +34078,7 @@ function upsertMemoryIndexOnce(existing, line2, targetPath2) {
|
|
|
33828
34078
|
async function applyProfileToBrain(args) {
|
|
33829
34079
|
const token = await mintInstallationTokenFromPem({
|
|
33830
34080
|
appId: args.appCreds.appId,
|
|
33831
|
-
privateKeyPem:
|
|
34081
|
+
privateKeyPem: readFileSync26(args.appCreds.pemPath, "utf8"),
|
|
33832
34082
|
installationId: args.appCreds.installationId,
|
|
33833
34083
|
fetchImpl: args.fetchImpl
|
|
33834
34084
|
});
|
|
@@ -34037,15 +34287,15 @@ function renderInstallSummary(args) {
|
|
|
34037
34287
|
|
|
34038
34288
|
// src/lib/companion-install.ts
|
|
34039
34289
|
import { constants as constants2 } from "fs";
|
|
34040
|
-
import * as
|
|
34290
|
+
import * as fs39 from "fs/promises";
|
|
34041
34291
|
import * as path42 from "path";
|
|
34042
34292
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
34043
34293
|
import { execFile as execFile2, spawn as spawn7 } from "child_process";
|
|
34044
34294
|
|
|
34045
34295
|
// src/lib/companion-artifact.ts
|
|
34046
|
-
import { createHash as
|
|
34296
|
+
import { createHash as createHash9 } from "crypto";
|
|
34047
34297
|
import { constants } from "fs";
|
|
34048
|
-
import * as
|
|
34298
|
+
import * as fs37 from "fs/promises";
|
|
34049
34299
|
import * as path40 from "path";
|
|
34050
34300
|
var SHA256 = /^[a-f0-9]{64}$/u;
|
|
34051
34301
|
var VERSION2 = /^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$/u;
|
|
@@ -34081,7 +34331,7 @@ function canonicalEntry(entry) {
|
|
|
34081
34331
|
`;
|
|
34082
34332
|
}
|
|
34083
34333
|
function artifactTreeSha256(entries) {
|
|
34084
|
-
const hash =
|
|
34334
|
+
const hash = createHash9("sha256");
|
|
34085
34335
|
const ordered = [...entries].sort(
|
|
34086
34336
|
(left, right) => left.path.localeCompare(right.path)
|
|
34087
34337
|
);
|
|
@@ -34151,17 +34401,17 @@ function parseArtifactManifest(value) {
|
|
|
34151
34401
|
};
|
|
34152
34402
|
}
|
|
34153
34403
|
async function sha256File2(filePath) {
|
|
34154
|
-
return
|
|
34404
|
+
return createHash9("sha256").update(await fs37.readFile(filePath)).digest("hex");
|
|
34155
34405
|
}
|
|
34156
34406
|
async function walk2(root, relative4 = "") {
|
|
34157
34407
|
const directory = path40.join(root, ...relative4.split("/").filter(Boolean));
|
|
34158
|
-
const children = await
|
|
34408
|
+
const children = await fs37.readdir(directory, { withFileTypes: true });
|
|
34159
34409
|
const entries = [];
|
|
34160
34410
|
for (const child of children.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
34161
34411
|
const childRelative = relative4 ? `${relative4}/${child.name}` : child.name;
|
|
34162
34412
|
normalizedRelative(childRelative);
|
|
34163
34413
|
const childPath = path40.join(directory, child.name);
|
|
34164
|
-
const stat5 = await
|
|
34414
|
+
const stat5 = await fs37.lstat(childPath);
|
|
34165
34415
|
if (stat5.isDirectory()) {
|
|
34166
34416
|
entries.push(...await walk2(root, childRelative));
|
|
34167
34417
|
} else if (stat5.isFile()) {
|
|
@@ -34173,7 +34423,7 @@ async function walk2(root, relative4 = "") {
|
|
|
34173
34423
|
sha256: await sha256File2(childPath)
|
|
34174
34424
|
});
|
|
34175
34425
|
} else if (stat5.isSymbolicLink()) {
|
|
34176
|
-
const target = await
|
|
34426
|
+
const target = await fs37.readlink(childPath);
|
|
34177
34427
|
entries.push({
|
|
34178
34428
|
type: "symlink",
|
|
34179
34429
|
path: childRelative,
|
|
@@ -34186,17 +34436,17 @@ async function walk2(root, relative4 = "") {
|
|
|
34186
34436
|
return entries;
|
|
34187
34437
|
}
|
|
34188
34438
|
async function ensureRealDirectory(root) {
|
|
34189
|
-
const stat5 = await
|
|
34439
|
+
const stat5 = await fs37.lstat(root);
|
|
34190
34440
|
if (stat5.isSymbolicLink()) throw new Error("Artifact root is a symbolic link");
|
|
34191
34441
|
if (!stat5.isDirectory()) throw new Error("Artifact root is not a directory");
|
|
34192
34442
|
}
|
|
34193
34443
|
async function validateResolvedLinks(root, entries) {
|
|
34194
|
-
const realRoot = await
|
|
34444
|
+
const realRoot = await fs37.realpath(root);
|
|
34195
34445
|
for (const entry of entries) {
|
|
34196
34446
|
if (entry.type !== "symlink") continue;
|
|
34197
34447
|
try {
|
|
34198
34448
|
const linkPath = path40.join(root, ...entry.path.split("/"));
|
|
34199
|
-
const resolved = await
|
|
34449
|
+
const resolved = await fs37.realpath(linkPath);
|
|
34200
34450
|
const relative4 = path40.relative(realRoot, resolved);
|
|
34201
34451
|
if (relative4 === ".." || relative4.startsWith(`..${path40.sep}`) || path40.isAbsolute(relative4)) {
|
|
34202
34452
|
throw new Error("Artifact symlink chain escapes the payload root");
|
|
@@ -34228,13 +34478,13 @@ async function buildArtifactManifest(payloadRoot, input) {
|
|
|
34228
34478
|
return manifest;
|
|
34229
34479
|
}
|
|
34230
34480
|
async function readArtifactManifest(manifestPath) {
|
|
34231
|
-
const before = await
|
|
34481
|
+
const before = await fs37.lstat(manifestPath);
|
|
34232
34482
|
if (before.isSymbolicLink() || !before.isFile()) {
|
|
34233
34483
|
throw new Error("Artifact manifest is a symbolic link or non-file");
|
|
34234
34484
|
}
|
|
34235
34485
|
let handle;
|
|
34236
34486
|
try {
|
|
34237
|
-
handle = await
|
|
34487
|
+
handle = await fs37.open(
|
|
34238
34488
|
manifestPath,
|
|
34239
34489
|
constants.O_RDONLY | constants.O_NOFOLLOW
|
|
34240
34490
|
);
|
|
@@ -34292,37 +34542,37 @@ async function verifyArtifactPayload(payloadRoot, manifest) {
|
|
|
34292
34542
|
async function copyArtifactPayload(sourceRoot, targetRoot, manifest) {
|
|
34293
34543
|
await verifyArtifactSource(sourceRoot, manifest);
|
|
34294
34544
|
try {
|
|
34295
|
-
const targetStat = await
|
|
34545
|
+
const targetStat = await fs37.lstat(targetRoot);
|
|
34296
34546
|
if (targetStat.isSymbolicLink()) {
|
|
34297
34547
|
throw new Error("Install target is a symbolic link");
|
|
34298
34548
|
}
|
|
34299
34549
|
if (!targetStat.isDirectory()) throw new Error("Install target is not a directory");
|
|
34300
|
-
if ((await
|
|
34550
|
+
if ((await fs37.readdir(targetRoot)).length > 0) {
|
|
34301
34551
|
throw new Error("Install staging target is not empty");
|
|
34302
34552
|
}
|
|
34303
34553
|
} catch (error) {
|
|
34304
34554
|
if (error.code !== "ENOENT") throw error;
|
|
34305
|
-
await
|
|
34555
|
+
await fs37.mkdir(targetRoot, { recursive: false, mode: 448 });
|
|
34306
34556
|
}
|
|
34307
34557
|
for (const entry of manifest.entries) {
|
|
34308
34558
|
const source = ensureContainedEntry(sourceRoot, entry.path);
|
|
34309
34559
|
const target = ensureContainedEntry(targetRoot, entry.path);
|
|
34310
|
-
await
|
|
34560
|
+
await fs37.mkdir(path40.dirname(target), { recursive: true, mode: 448 });
|
|
34311
34561
|
if (entry.type === "file") {
|
|
34312
|
-
await
|
|
34313
|
-
await
|
|
34562
|
+
await fs37.copyFile(source, target);
|
|
34563
|
+
await fs37.chmod(target, entry.mode);
|
|
34314
34564
|
} else {
|
|
34315
|
-
await
|
|
34565
|
+
await fs37.symlink(entry.target, target);
|
|
34316
34566
|
}
|
|
34317
34567
|
}
|
|
34318
34568
|
await verifyArtifactPayload(targetRoot, manifest);
|
|
34319
34569
|
}
|
|
34320
34570
|
|
|
34321
34571
|
// src/lib/companion-download.ts
|
|
34322
|
-
import { createHash as
|
|
34572
|
+
import { createHash as createHash10 } from "crypto";
|
|
34323
34573
|
import { execFile } from "child_process";
|
|
34324
34574
|
import { createReadStream } from "fs";
|
|
34325
|
-
import * as
|
|
34575
|
+
import * as fs38 from "fs/promises";
|
|
34326
34576
|
import * as os18 from "os";
|
|
34327
34577
|
import * as path41 from "path";
|
|
34328
34578
|
init_errors();
|
|
@@ -34370,11 +34620,11 @@ function declaredLength(response) {
|
|
|
34370
34620
|
return Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
34371
34621
|
}
|
|
34372
34622
|
async function usableBytes(partial) {
|
|
34373
|
-
const status = await
|
|
34623
|
+
const status = await fs38.lstat(partial).catch(() => null);
|
|
34374
34624
|
if (status === null) return 0;
|
|
34375
34625
|
if (!status.isFile()) {
|
|
34376
|
-
await
|
|
34377
|
-
if (await
|
|
34626
|
+
await fs38.rm(partial, { recursive: true, force: true }).catch(() => void 0);
|
|
34627
|
+
if (await fs38.lstat(partial).then(() => true, () => false)) {
|
|
34378
34628
|
throw new LocalCliError({
|
|
34379
34629
|
code: "COMPANION_DOWNLOAD_PATH_OCCUPIED",
|
|
34380
34630
|
message: `${partial} is not a file and could not be cleared.`,
|
|
@@ -34386,14 +34636,14 @@ async function usableBytes(partial) {
|
|
|
34386
34636
|
return status.size;
|
|
34387
34637
|
}
|
|
34388
34638
|
async function sweepForeignPartials(directory, keep) {
|
|
34389
|
-
const entries = await
|
|
34639
|
+
const entries = await fs38.readdir(directory).catch(() => []);
|
|
34390
34640
|
await Promise.all(
|
|
34391
|
-
entries.filter((name) => name.endsWith(".part") && name !== keep).map((name) =>
|
|
34641
|
+
entries.filter((name) => name.endsWith(".part") && name !== keep).map((name) => fs38.rm(path41.join(directory, name), { recursive: true, force: true }))
|
|
34392
34642
|
);
|
|
34393
34643
|
}
|
|
34394
34644
|
function sha256File3(filePath) {
|
|
34395
34645
|
return new Promise((resolve6, reject) => {
|
|
34396
|
-
const hash =
|
|
34646
|
+
const hash = createHash10("sha256");
|
|
34397
34647
|
const stream = createReadStream(filePath);
|
|
34398
34648
|
stream.on("error", reject);
|
|
34399
34649
|
stream.on("data", (chunk) => hash.update(chunk));
|
|
@@ -34407,7 +34657,7 @@ async function transferToDisk(input) {
|
|
|
34407
34657
|
for (let attempt = 0; attempt < MAX_ATTEMPTS2; attempt++) {
|
|
34408
34658
|
let existing = await usableBytes(input.partial);
|
|
34409
34659
|
if (existing > input.maxAssetBytes) {
|
|
34410
|
-
await
|
|
34660
|
+
await fs38.rm(input.partial, { force: true }).catch(() => void 0);
|
|
34411
34661
|
existing = 0;
|
|
34412
34662
|
}
|
|
34413
34663
|
let received = existing;
|
|
@@ -34424,7 +34674,7 @@ async function transferToDisk(input) {
|
|
|
34424
34674
|
if (existing > 0 && await sha256File3(input.partial).catch(() => null) === input.expectedDigest) {
|
|
34425
34675
|
return;
|
|
34426
34676
|
}
|
|
34427
|
-
await
|
|
34677
|
+
await fs38.rm(input.partial, { force: true }).catch(() => void 0);
|
|
34428
34678
|
lastError = new Error("the interrupted download no longer fits the published asset");
|
|
34429
34679
|
continue;
|
|
34430
34680
|
}
|
|
@@ -34449,7 +34699,7 @@ async function transferToDisk(input) {
|
|
|
34449
34699
|
const range = parseContentRange(response.headers.get("content-range"));
|
|
34450
34700
|
if (range?.start !== existing) {
|
|
34451
34701
|
await response.body?.cancel().catch(() => void 0);
|
|
34452
|
-
await
|
|
34702
|
+
await fs38.rm(input.partial, { force: true }).catch(() => void 0);
|
|
34453
34703
|
lastError = new Error("the server answered a different range than the one requested");
|
|
34454
34704
|
continue;
|
|
34455
34705
|
}
|
|
@@ -34462,14 +34712,14 @@ async function transferToDisk(input) {
|
|
|
34462
34712
|
const declaredBody = declaredLength(response);
|
|
34463
34713
|
if (total !== null && total > input.maxAssetBytes || declaredBody !== null && declaredBody > input.maxAssetBytes) {
|
|
34464
34714
|
await response.body?.cancel().catch(() => void 0);
|
|
34465
|
-
await
|
|
34715
|
+
await fs38.rm(input.partial, { force: true }).catch(() => void 0);
|
|
34466
34716
|
throw tooLarge(input.asset);
|
|
34467
34717
|
}
|
|
34468
34718
|
if (offset > 0) input.onEvent({ kind: "resumed", received: offset, total });
|
|
34469
34719
|
received = offset;
|
|
34470
34720
|
const body = response.body;
|
|
34471
34721
|
if (body === null) throw new Error("the response carried no body");
|
|
34472
|
-
const handle = await
|
|
34722
|
+
const handle = await fs38.open(input.partial, offset > 0 ? "a" : "w", 384);
|
|
34473
34723
|
try {
|
|
34474
34724
|
for await (const chunk of body) {
|
|
34475
34725
|
received += chunk.byteLength;
|
|
@@ -34492,7 +34742,7 @@ async function transferToDisk(input) {
|
|
|
34492
34742
|
} catch (error) {
|
|
34493
34743
|
if (error instanceof LocalCliError) {
|
|
34494
34744
|
if (error.code === "COMPANION_ASSET_TOO_LARGE") {
|
|
34495
|
-
await
|
|
34745
|
+
await fs38.rm(input.partial, { force: true }).catch(() => void 0);
|
|
34496
34746
|
}
|
|
34497
34747
|
throw error;
|
|
34498
34748
|
}
|
|
@@ -34508,7 +34758,7 @@ async function transferToDisk(input) {
|
|
|
34508
34758
|
}
|
|
34509
34759
|
}
|
|
34510
34760
|
}
|
|
34511
|
-
const kept = await
|
|
34761
|
+
const kept = await fs38.lstat(input.partial).then(
|
|
34512
34762
|
(status) => status.isFile() ? status.size : 0,
|
|
34513
34763
|
() => 0
|
|
34514
34764
|
);
|
|
@@ -34535,7 +34785,7 @@ async function downloadCompanionArtifact(component, deps = {}) {
|
|
|
34535
34785
|
const directory = deps.downloadDirectory ?? companionDownloadDirectory(os18.homedir());
|
|
34536
34786
|
const partialName = `${pinned.sha256}.part`;
|
|
34537
34787
|
const partial = path41.join(directory, partialName);
|
|
34538
|
-
await
|
|
34788
|
+
await fs38.mkdir(directory, { recursive: true, mode: 448 });
|
|
34539
34789
|
await sweepForeignPartials(directory, partialName);
|
|
34540
34790
|
await transferToDisk({
|
|
34541
34791
|
url,
|
|
@@ -34549,20 +34799,20 @@ async function downloadCompanionArtifact(component, deps = {}) {
|
|
|
34549
34799
|
});
|
|
34550
34800
|
const digest = await sha256File3(partial);
|
|
34551
34801
|
if (digest !== pinned.sha256) {
|
|
34552
|
-
await
|
|
34802
|
+
await fs38.rm(partial, { force: true }).catch(() => void 0);
|
|
34553
34803
|
throw new LocalCliError({
|
|
34554
34804
|
code: "COMPANION_ASSET_DIGEST_MISMATCH",
|
|
34555
34805
|
message: `${pinned.asset} does not match the digest the release pins for it.`,
|
|
34556
34806
|
hint: "Nothing was unpacked. This is what a corrupted download or a substituted file looks like \u2014 retry, and report it if it persists."
|
|
34557
34807
|
});
|
|
34558
34808
|
}
|
|
34559
|
-
const root = await (deps.makeTemporaryDirectory ?? (() =>
|
|
34809
|
+
const root = await (deps.makeTemporaryDirectory ?? (() => fs38.mkdtemp(path41.join(os18.tmpdir(), "m8t-companion-"))))();
|
|
34560
34810
|
const dispose = async () => {
|
|
34561
|
-
await
|
|
34811
|
+
await fs38.rm(root, { recursive: true, force: true }).catch(() => void 0);
|
|
34562
34812
|
};
|
|
34563
34813
|
try {
|
|
34564
34814
|
const unpacked = path41.join(root, "unpacked");
|
|
34565
|
-
await
|
|
34815
|
+
await fs38.mkdir(unpacked, { recursive: false, mode: 448 });
|
|
34566
34816
|
await (deps.extract ?? extractArchive)(partial, unpacked);
|
|
34567
34817
|
return {
|
|
34568
34818
|
version: component.version,
|
|
@@ -34573,7 +34823,7 @@ async function downloadCompanionArtifact(component, deps = {}) {
|
|
|
34573
34823
|
await dispose();
|
|
34574
34824
|
throw error;
|
|
34575
34825
|
} finally {
|
|
34576
|
-
await
|
|
34826
|
+
await fs38.rm(partial, { force: true }).catch(() => void 0);
|
|
34577
34827
|
}
|
|
34578
34828
|
}
|
|
34579
34829
|
|
|
@@ -34617,10 +34867,10 @@ async function setCompanionStartAtLogin(input) {
|
|
|
34617
34867
|
);
|
|
34618
34868
|
await assertNotSymlink(registration, "Start-at-login registration");
|
|
34619
34869
|
if (!input.enabled) {
|
|
34620
|
-
await
|
|
34870
|
+
await fs39.rm(registration, { force: true });
|
|
34621
34871
|
return;
|
|
34622
34872
|
}
|
|
34623
|
-
await
|
|
34873
|
+
await fs39.mkdir(launchAgents, { recursive: true, mode: 448 });
|
|
34624
34874
|
const plist = '<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0"><dict><key>Label</key><string>com.m8t.companion</string><key>ProgramArguments</key><array><string>' + xmlEscape(input.executable) + "</string></array><key>RunAtLoad</key><true/></dict></plist>\n";
|
|
34625
34875
|
await atomicWriteText(registration, plist, 384);
|
|
34626
34876
|
return;
|
|
@@ -34802,7 +35052,7 @@ function validateGatewayOrigin(value) {
|
|
|
34802
35052
|
}
|
|
34803
35053
|
async function assertNotSymlink(filePath, kind) {
|
|
34804
35054
|
try {
|
|
34805
|
-
if ((await
|
|
35055
|
+
if ((await fs39.lstat(filePath)).isSymbolicLink()) {
|
|
34806
35056
|
throw new Error(`${kind} is a symbolic link`);
|
|
34807
35057
|
}
|
|
34808
35058
|
} catch (error) {
|
|
@@ -34817,14 +35067,14 @@ async function assertOwnedDirectoryChain(anchor, targetDirectory) {
|
|
|
34817
35067
|
}
|
|
34818
35068
|
const segments = relative4.split(path42.sep).filter(Boolean);
|
|
34819
35069
|
let current = anchor;
|
|
34820
|
-
const anchorStatus = await
|
|
35070
|
+
const anchorStatus = await fs39.lstat(anchor);
|
|
34821
35071
|
if (anchorStatus.isSymbolicLink() || !anchorStatus.isDirectory()) {
|
|
34822
35072
|
throw new Error("Companion owned directory anchor is unsafe");
|
|
34823
35073
|
}
|
|
34824
35074
|
for (const segment of segments) {
|
|
34825
35075
|
current = path42.join(current, segment);
|
|
34826
35076
|
try {
|
|
34827
|
-
const status = await
|
|
35077
|
+
const status = await fs39.lstat(current);
|
|
34828
35078
|
if (status.isSymbolicLink() || !status.isDirectory()) {
|
|
34829
35079
|
throw new Error("Companion owned directory contains a symbolic link");
|
|
34830
35080
|
}
|
|
@@ -34843,13 +35093,13 @@ async function assertOwnedParents(options, paths) {
|
|
|
34843
35093
|
await assertOwnedDirectoryChain(targetAnchor, path42.dirname(paths.targetRoot));
|
|
34844
35094
|
}
|
|
34845
35095
|
async function readRegularText(filePath, maxBytes) {
|
|
34846
|
-
const before = await
|
|
35096
|
+
const before = await fs39.lstat(filePath);
|
|
34847
35097
|
if (before.isSymbolicLink() || !before.isFile()) {
|
|
34848
35098
|
throw new Error("Companion state file is not a regular file");
|
|
34849
35099
|
}
|
|
34850
35100
|
let handle;
|
|
34851
35101
|
try {
|
|
34852
|
-
handle = await
|
|
35102
|
+
handle = await fs39.open(
|
|
34853
35103
|
filePath,
|
|
34854
35104
|
constants2.O_RDONLY | constants2.O_NOFOLLOW
|
|
34855
35105
|
);
|
|
@@ -34872,27 +35122,27 @@ async function atomicWriteJson(filePath, value) {
|
|
|
34872
35122
|
}
|
|
34873
35123
|
async function atomicWriteText(filePath, contents, mode) {
|
|
34874
35124
|
await assertNotSymlink(filePath, "Companion state file");
|
|
34875
|
-
await
|
|
35125
|
+
await fs39.mkdir(path42.dirname(filePath), {
|
|
34876
35126
|
recursive: true,
|
|
34877
35127
|
mode: 448
|
|
34878
35128
|
});
|
|
34879
35129
|
const temporary = `${filePath}.${randomUUID4()}.tmp`;
|
|
34880
35130
|
try {
|
|
34881
|
-
await
|
|
35131
|
+
await fs39.writeFile(temporary, contents, {
|
|
34882
35132
|
mode,
|
|
34883
35133
|
flag: "wx"
|
|
34884
35134
|
});
|
|
34885
|
-
await
|
|
34886
|
-
await
|
|
35135
|
+
await fs39.rename(temporary, filePath);
|
|
35136
|
+
await fs39.chmod(filePath, mode).catch(() => void 0);
|
|
34887
35137
|
} finally {
|
|
34888
|
-
await
|
|
35138
|
+
await fs39.rm(temporary, { force: true }).catch(() => void 0);
|
|
34889
35139
|
}
|
|
34890
35140
|
}
|
|
34891
35141
|
async function realRegularFile(filePath, executable) {
|
|
34892
|
-
const real = await
|
|
34893
|
-
const stat5 = await
|
|
35142
|
+
const real = await fs39.realpath(filePath);
|
|
35143
|
+
const stat5 = await fs39.stat(real);
|
|
34894
35144
|
if (!stat5.isFile()) throw new Error("Companion launch target is not a file");
|
|
34895
|
-
await
|
|
35145
|
+
await fs39.access(real, executable ? constants2.X_OK : constants2.R_OK);
|
|
34896
35146
|
return real;
|
|
34897
35147
|
}
|
|
34898
35148
|
function defaultLaunch(executable) {
|
|
@@ -34908,7 +35158,7 @@ async function companionIsRunning(platform, executable) {
|
|
|
34908
35158
|
if (platform !== "win32") return false;
|
|
34909
35159
|
let handle;
|
|
34910
35160
|
try {
|
|
34911
|
-
handle = await
|
|
35161
|
+
handle = await fs39.open(executable, "r+");
|
|
34912
35162
|
} catch (error) {
|
|
34913
35163
|
const code = error.code;
|
|
34914
35164
|
if (code === "ENOENT") return false;
|
|
@@ -35022,7 +35272,7 @@ async function snapshotFile(filePath) {
|
|
|
35022
35272
|
}
|
|
35023
35273
|
async function restoreFile(filePath, bytes) {
|
|
35024
35274
|
if (bytes === null) {
|
|
35025
|
-
await
|
|
35275
|
+
await fs39.rm(filePath, { force: true });
|
|
35026
35276
|
} else {
|
|
35027
35277
|
await atomicWriteJson(filePath, JSON.parse(bytes.toString("utf8")));
|
|
35028
35278
|
}
|
|
@@ -35066,7 +35316,7 @@ async function converge(options, force) {
|
|
|
35066
35316
|
}
|
|
35067
35317
|
if (current.state === "not-installed") {
|
|
35068
35318
|
try {
|
|
35069
|
-
await
|
|
35319
|
+
await fs39.lstat(paths.targetRoot);
|
|
35070
35320
|
throw new Error(
|
|
35071
35321
|
"The fixed companion target exists without an owned install manifest"
|
|
35072
35322
|
);
|
|
@@ -35077,7 +35327,7 @@ async function converge(options, force) {
|
|
|
35077
35327
|
const stage = `${paths.targetRoot}.m8t-stage-${randomUUID4()}`;
|
|
35078
35328
|
const backup = `${paths.targetRoot}.m8t-backup-${randomUUID4()}`;
|
|
35079
35329
|
const copy = options.copyPayload ?? copyArtifactPayload;
|
|
35080
|
-
await
|
|
35330
|
+
await fs39.mkdir(path42.dirname(paths.targetRoot), {
|
|
35081
35331
|
recursive: true,
|
|
35082
35332
|
mode: 448
|
|
35083
35333
|
});
|
|
@@ -35094,12 +35344,12 @@ async function converge(options, force) {
|
|
|
35094
35344
|
try {
|
|
35095
35345
|
await copy(payloadRoot, stage, artifactManifest);
|
|
35096
35346
|
try {
|
|
35097
|
-
await
|
|
35347
|
+
await fs39.rename(paths.targetRoot, backup);
|
|
35098
35348
|
movedPrior = true;
|
|
35099
35349
|
} catch (error) {
|
|
35100
35350
|
if (error.code !== "ENOENT") throw error;
|
|
35101
35351
|
}
|
|
35102
|
-
await
|
|
35352
|
+
await fs39.rename(stage, paths.targetRoot);
|
|
35103
35353
|
installedStage = true;
|
|
35104
35354
|
const nodeExecutable = await realRegularFile(
|
|
35105
35355
|
options.nodeExecutable,
|
|
@@ -35162,7 +35412,7 @@ async function converge(options, force) {
|
|
|
35162
35412
|
);
|
|
35163
35413
|
loginChanged = true;
|
|
35164
35414
|
await (options.launch ?? defaultLaunch)(executable);
|
|
35165
|
-
await
|
|
35415
|
+
await fs39.rm(backup, { recursive: true, force: true });
|
|
35166
35416
|
return {
|
|
35167
35417
|
state: "installed",
|
|
35168
35418
|
version: artifactManifest.version,
|
|
@@ -35176,15 +35426,15 @@ async function converge(options, force) {
|
|
|
35176
35426
|
() => void 0
|
|
35177
35427
|
);
|
|
35178
35428
|
}
|
|
35179
|
-
await
|
|
35429
|
+
await fs39.rm(stage, { recursive: true, force: true }).catch(() => void 0);
|
|
35180
35430
|
if (installedStage) {
|
|
35181
|
-
await
|
|
35431
|
+
await fs39.rm(paths.targetRoot, {
|
|
35182
35432
|
recursive: true,
|
|
35183
35433
|
force: true
|
|
35184
35434
|
}).catch(() => void 0);
|
|
35185
35435
|
}
|
|
35186
35436
|
if (movedPrior) {
|
|
35187
|
-
await
|
|
35437
|
+
await fs39.rename(backup, paths.targetRoot).catch(() => void 0);
|
|
35188
35438
|
}
|
|
35189
35439
|
await Promise.all([
|
|
35190
35440
|
restoreFile(paths.installManifest, snapshots[0]),
|
|
@@ -35205,7 +35455,7 @@ async function uninstallCompanion(options) {
|
|
|
35205
35455
|
const expectedPaths = companionInstallPaths(options);
|
|
35206
35456
|
await assertOwnedParents(options, expectedPaths);
|
|
35207
35457
|
const remove = options.removeOwnedPath ?? (async (ownedPath, recursive) => {
|
|
35208
|
-
await
|
|
35458
|
+
await fs39.rm(ownedPath, { recursive, force: true });
|
|
35209
35459
|
});
|
|
35210
35460
|
const installed2 = await readInstalled(options);
|
|
35211
35461
|
if (!installed2) {
|
|
@@ -35233,7 +35483,7 @@ async function uninstallCompanion(options) {
|
|
|
35233
35483
|
// src/commands/companion/install.ts
|
|
35234
35484
|
import * as os19 from "os";
|
|
35235
35485
|
import * as path43 from "path";
|
|
35236
|
-
import { Command as
|
|
35486
|
+
import { Command as Command62, Option as Option59 } from "clipanion";
|
|
35237
35487
|
|
|
35238
35488
|
// src/lib/companion-channel.ts
|
|
35239
35489
|
async function readCompanionRelease(source = { url: CHANNEL_LATEST_URL }, deps = {}) {
|
|
@@ -35364,7 +35614,7 @@ Version: ${state.version}
|
|
|
35364
35614
|
}
|
|
35365
35615
|
var CompanionInstallCommand = class extends M8tCommand {
|
|
35366
35616
|
static paths = [["companion", "install"]];
|
|
35367
|
-
static usage =
|
|
35617
|
+
static usage = Command62.Usage({
|
|
35368
35618
|
description: "Install the desktop companions for this user from the release channel.",
|
|
35369
35619
|
examples: [
|
|
35370
35620
|
["Install the released build", "$0 companion install"],
|
|
@@ -35374,10 +35624,10 @@ var CompanionInstallCommand = class extends M8tCommand {
|
|
|
35374
35624
|
]
|
|
35375
35625
|
]
|
|
35376
35626
|
});
|
|
35377
|
-
from =
|
|
35627
|
+
from = Option59.String("--from", {
|
|
35378
35628
|
description: "A locally staged build directory instead of the released one."
|
|
35379
35629
|
});
|
|
35380
|
-
resourceGroup =
|
|
35630
|
+
resourceGroup = Option59.String("--resource-group", {
|
|
35381
35631
|
description: "Which deployment to bind to, when the subscription holds more than one."
|
|
35382
35632
|
});
|
|
35383
35633
|
async executeCommand() {
|
|
@@ -35402,7 +35652,7 @@ function resolveRepoRootMarker(args) {
|
|
|
35402
35652
|
}
|
|
35403
35653
|
async function looksLikeCheckout(dir2) {
|
|
35404
35654
|
try {
|
|
35405
|
-
return (await
|
|
35655
|
+
return (await fs40.stat(path44.join(dir2, "brain-template"))).isDirectory();
|
|
35406
35656
|
} catch {
|
|
35407
35657
|
return false;
|
|
35408
35658
|
}
|
|
@@ -35425,15 +35675,15 @@ async function finalizeInstall(args, deps = defaultFinalizeDeps) {
|
|
|
35425
35675
|
const markerDir = path44.join(deps.homedir(), ".m8t");
|
|
35426
35676
|
const markerPath = path44.join(markerDir, "repo-root");
|
|
35427
35677
|
const cwd = process.cwd();
|
|
35428
|
-
const existing = await
|
|
35678
|
+
const existing = await fs40.readFile(markerPath, "utf8").then((s) => s.trim()).catch(() => null);
|
|
35429
35679
|
const repoRoot = resolveRepoRootMarker({
|
|
35430
35680
|
...args.repoRoot !== void 0 ? { explicit: args.repoRoot } : {},
|
|
35431
35681
|
cwd,
|
|
35432
35682
|
cwdIsCheckout: await looksLikeCheckout(cwd),
|
|
35433
35683
|
existing
|
|
35434
35684
|
});
|
|
35435
|
-
await
|
|
35436
|
-
await
|
|
35685
|
+
await fs40.mkdir(markerDir, { recursive: true });
|
|
35686
|
+
await fs40.writeFile(markerPath, `${repoRoot}
|
|
35437
35687
|
`, "utf8");
|
|
35438
35688
|
let webappUrl;
|
|
35439
35689
|
try {
|
|
@@ -35592,7 +35842,7 @@ var BootstrapStatusCommand = class extends M8tCommand {
|
|
|
35592
35842
|
// runbooks and shakedown recipes that a founder may already be part-way
|
|
35593
35843
|
// through — it prints a deprecation notice and does the right thing.
|
|
35594
35844
|
static paths = [["bootstrap", "status"], ["bootstrap", "finish"]];
|
|
35595
|
-
static usage =
|
|
35845
|
+
static usage = Command63.Usage({
|
|
35596
35846
|
description: "Show the cloud installer's live status (phase, progress, result) \u2014 and finish the local setup when it lands.",
|
|
35597
35847
|
details: "Reads the durable status blob written by the installer. --watch polls until the install reaches done or failed.\n\nOn reaching done under --watch this also completes the local half of the install, which nothing else in the bootstrap path can do: it registers the webapp's sign-in redirect URI (the installer runs as a managed identity with no directory role, and at launch time the gateway FQDN did not exist yet), writes ~/.m8t/repo-root and the gateway discovery cache, and seeds your advisors' brains from the onboarding intake. Re-runnable \u2014 `m8t bootstrap finish` is a deprecated alias that does exactly this on an already-done install.",
|
|
35598
35848
|
examples: [
|
|
@@ -35601,12 +35851,12 @@ var BootstrapStatusCommand = class extends M8tCommand {
|
|
|
35601
35851
|
["Redo the local setup on a finished install", "$0 bootstrap status --finalize --repo-root /path/to/m8t"]
|
|
35602
35852
|
]
|
|
35603
35853
|
});
|
|
35604
|
-
watch =
|
|
35605
|
-
output =
|
|
35606
|
-
repoRoot =
|
|
35607
|
-
finalize =
|
|
35608
|
-
subscription =
|
|
35609
|
-
resourceGroup =
|
|
35854
|
+
watch = Option60.Boolean("--watch", false);
|
|
35855
|
+
output = Option60.String("--output");
|
|
35856
|
+
repoRoot = Option60.String("--repo-root", { description: "The m8t clone to point local tools at (default: the current directory)." });
|
|
35857
|
+
finalize = Option60.Boolean("--finalize", false, { description: "Complete the local setup against an already-done install, without watching." });
|
|
35858
|
+
subscription = Option60.String("--subscription");
|
|
35859
|
+
resourceGroup = Option60.String("--resource-group");
|
|
35610
35860
|
async executeCommand() {
|
|
35611
35861
|
const state = await readBootstrapState();
|
|
35612
35862
|
if (!state) {
|
|
@@ -35739,7 +35989,7 @@ function formatStatus(d) {
|
|
|
35739
35989
|
}
|
|
35740
35990
|
|
|
35741
35991
|
// src/commands/bootstrap/reap.ts
|
|
35742
|
-
import { Command as
|
|
35992
|
+
import { Command as Command64, Option as Option61 } from "clipanion";
|
|
35743
35993
|
init_errors();
|
|
35744
35994
|
|
|
35745
35995
|
// src/lib/bootstrap-reap.ts
|
|
@@ -35833,7 +36083,7 @@ async function reapInstaller(opts) {
|
|
|
35833
36083
|
// src/commands/bootstrap/reap.ts
|
|
35834
36084
|
var BootstrapReapCommand = class extends M8tCommand {
|
|
35835
36085
|
static paths = [["bootstrap", "reap"]];
|
|
35836
|
-
static usage =
|
|
36086
|
+
static usage = Command64.Usage({
|
|
35837
36087
|
description: "Tear down the installer scaffolding (ACI \u2192 MI \u2192 its role assignments) after a successful install.",
|
|
35838
36088
|
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.\n\n--sweep-orphans is a DIFFERENT and much broader mode: instead of reaping this install, it scans the WHOLE SUBSCRIPTION for m8t role assignments left behind by installs whose resources are gone \u2014 orphaned installer Owner-at-subscription-scope grants and orphaned gateway subscription-scope roles. It is a dry run that only lists what it found unless you also pass --yes, which deletes them.",
|
|
35839
36089
|
examples: [
|
|
@@ -35843,9 +36093,9 @@ var BootstrapReapCommand = class extends M8tCommand {
|
|
|
35843
36093
|
["\u2026and delete them", "$0 bootstrap reap --sweep-orphans --yes"]
|
|
35844
36094
|
]
|
|
35845
36095
|
});
|
|
35846
|
-
force =
|
|
35847
|
-
sweepOrphans =
|
|
35848
|
-
yes =
|
|
36096
|
+
force = Option61.Boolean("--force", false, { description: "Reap even if the install failed or never reported a status." });
|
|
36097
|
+
sweepOrphans = Option61.Boolean("--sweep-orphans", false, { description: "Subscription-wide: find m8t role assignments orphaned by earlier installs. Lists only, unless --yes." });
|
|
36098
|
+
yes = Option61.Boolean("--yes", false, { description: "With --sweep-orphans, actually delete what the sweep found." });
|
|
35849
36099
|
async executeCommand() {
|
|
35850
36100
|
if (this.sweepOrphans === true) {
|
|
35851
36101
|
const { subscriptionId: sub } = await getAzAccount();
|
|
@@ -35939,10 +36189,10 @@ Found ${String(total)} orphaned assignment(s) (${breakdown}) (dry-run). ${colors
|
|
|
35939
36189
|
};
|
|
35940
36190
|
|
|
35941
36191
|
// src/commands/bootstrap/ui.ts
|
|
35942
|
-
import { Command as
|
|
36192
|
+
import { Command as Command65, Option as Option62 } from "clipanion";
|
|
35943
36193
|
|
|
35944
36194
|
// src/lib/bootstrap-ui.ts
|
|
35945
|
-
import * as
|
|
36195
|
+
import * as fs41 from "fs";
|
|
35946
36196
|
import * as net from "net";
|
|
35947
36197
|
import * as os21 from "os";
|
|
35948
36198
|
import * as path45 from "path";
|
|
@@ -35978,7 +36228,7 @@ function parseManagedPid(text) {
|
|
|
35978
36228
|
function stopPidFile(pidPath) {
|
|
35979
36229
|
let pidStr;
|
|
35980
36230
|
try {
|
|
35981
|
-
pidStr =
|
|
36231
|
+
pidStr = fs41.readFileSync(pidPath, "utf8").trim();
|
|
35982
36232
|
} catch {
|
|
35983
36233
|
return false;
|
|
35984
36234
|
}
|
|
@@ -35995,7 +36245,7 @@ function stopPidFile(pidPath) {
|
|
|
35995
36245
|
}
|
|
35996
36246
|
}
|
|
35997
36247
|
try {
|
|
35998
|
-
|
|
36248
|
+
fs41.unlinkSync(pidPath);
|
|
35999
36249
|
} catch {
|
|
36000
36250
|
}
|
|
36001
36251
|
return true;
|
|
@@ -36014,7 +36264,7 @@ function renderDeprecationNotice() {
|
|
|
36014
36264
|
}
|
|
36015
36265
|
var BootstrapUiCommand = class extends M8tCommand {
|
|
36016
36266
|
static paths = [["bootstrap", "ui"]];
|
|
36017
|
-
static usage =
|
|
36267
|
+
static usage = Command65.Usage({
|
|
36018
36268
|
description: "DEPRECATED \u2014 does nothing. Use `m8t bootstrap profile` instead.",
|
|
36019
36269
|
details: [
|
|
36020
36270
|
"The local onboarding chat has been retired. Your details are collected by",
|
|
@@ -36034,14 +36284,14 @@ var BootstrapUiCommand = class extends M8tCommand {
|
|
|
36034
36284
|
// Accepted and ignored, deliberately: removing them would turn an old script's
|
|
36035
36285
|
// harmless no-op into an "unknown option" failure mid-install. CLI-rewrite: drop
|
|
36036
36286
|
// the whole surface when the rewrite lands.
|
|
36037
|
-
repoRoot =
|
|
36038
|
-
port =
|
|
36039
|
-
endpoint =
|
|
36040
|
-
prepOnly =
|
|
36041
|
-
skipInstall =
|
|
36042
|
-
foreground =
|
|
36043
|
-
voice =
|
|
36044
|
-
stop =
|
|
36287
|
+
repoRoot = Option62.String("--repo-root", { description: "Ignored (deprecated)." });
|
|
36288
|
+
port = Option62.String("--port", "3000", { description: "Ignored (deprecated)." });
|
|
36289
|
+
endpoint = Option62.String("--endpoint", { description: "Ignored (deprecated)." });
|
|
36290
|
+
prepOnly = Option62.Boolean("--prep-only", false, { description: "Ignored (deprecated)." });
|
|
36291
|
+
skipInstall = Option62.Boolean("--skip-install", false, { description: "Ignored (deprecated)." });
|
|
36292
|
+
foreground = Option62.Boolean("--foreground", false, { description: "Ignored (deprecated)." });
|
|
36293
|
+
voice = Option62.Boolean("--voice", false, { description: "Ignored (deprecated)." });
|
|
36294
|
+
stop = Option62.Boolean("--stop", false, {
|
|
36045
36295
|
description: "Shut down a local chat UI left running by an earlier version of this command."
|
|
36046
36296
|
});
|
|
36047
36297
|
// Not `async`: there is nothing left to await. Everything this command used to
|
|
@@ -36063,7 +36313,7 @@ var BootstrapUiCommand = class extends M8tCommand {
|
|
|
36063
36313
|
|
|
36064
36314
|
// src/commands/bootstrap/profile.ts
|
|
36065
36315
|
import * as readline3 from "readline/promises";
|
|
36066
|
-
import { Command as
|
|
36316
|
+
import { Command as Command66, Option as Option63 } from "clipanion";
|
|
36067
36317
|
|
|
36068
36318
|
// src/lib/profile-collect.ts
|
|
36069
36319
|
init_errors();
|
|
@@ -36232,7 +36482,7 @@ async function openChatInvite(deps = {}) {
|
|
|
36232
36482
|
// src/commands/bootstrap/profile.ts
|
|
36233
36483
|
var BootstrapProfileCommand = class extends M8tCommand {
|
|
36234
36484
|
static paths = [["bootstrap", "profile"]];
|
|
36235
|
-
static usage =
|
|
36485
|
+
static usage = Command66.Usage({
|
|
36236
36486
|
description: "Confirm your email + your Microsoft startup advisor, and open Ezra to talk to while the install runs.",
|
|
36237
36487
|
details: [
|
|
36238
36488
|
"Run after `m8t bootstrap launch`, in parallel with `status --watch`. Records the two",
|
|
@@ -36252,12 +36502,12 @@ var BootstrapProfileCommand = class extends M8tCommand {
|
|
|
36252
36502
|
["Skip the browser hand-off", "$0 bootstrap profile --founder-email you@example.com --print"]
|
|
36253
36503
|
]
|
|
36254
36504
|
});
|
|
36255
|
-
founderEmail =
|
|
36256
|
-
advisorName =
|
|
36257
|
-
advisorEmail =
|
|
36258
|
-
noAdvisor =
|
|
36259
|
-
noChat =
|
|
36260
|
-
print =
|
|
36505
|
+
founderEmail = Option63.String("--founder-email", { description: "The founder's contact address \u2014 Ezra copies them on its outbound mail." });
|
|
36506
|
+
advisorName = Option63.String("--advisor-name", { description: "Their Microsoft startup advisor's name." });
|
|
36507
|
+
advisorEmail = Option63.String("--advisor-email", { description: "Their Microsoft startup advisor's email." });
|
|
36508
|
+
noAdvisor = Option63.Boolean("--no-advisor", false, { description: "Record that they have no startup advisor to add (or don't know it yet)." });
|
|
36509
|
+
noChat = Option63.Boolean("--no-chat", false, { description: "Record the answers without opening the hosted Ezra." });
|
|
36510
|
+
print = Option63.Boolean("--print", false, { description: "Print the chat link instead of opening a browser (headless / SSH)." });
|
|
36261
36511
|
async executeCommand() {
|
|
36262
36512
|
const stdin = this.context.stdin;
|
|
36263
36513
|
const stdout = this.context.stdout;
|
|
@@ -36350,10 +36600,10 @@ var BootstrapProfileCommand = class extends M8tCommand {
|
|
|
36350
36600
|
};
|
|
36351
36601
|
|
|
36352
36602
|
// src/commands/bootstrap/seed-profile.ts
|
|
36353
|
-
import { Command as
|
|
36603
|
+
import { Command as Command67, Option as Option64 } from "clipanion";
|
|
36354
36604
|
var BootstrapSeedProfileCommand = class extends M8tCommand {
|
|
36355
36605
|
static paths = [["bootstrap", "seed-profile"]];
|
|
36356
|
-
static usage =
|
|
36606
|
+
static usage = Command67.Usage({
|
|
36357
36607
|
description: "Seed your advisors' brains with how to reach you, from what you confirmed at `m8t bootstrap profile`.",
|
|
36358
36608
|
details: [
|
|
36359
36609
|
"Renders memory/founder.md + memory/company-profile.md (+ their MEMORY.md index lines)",
|
|
@@ -36370,11 +36620,11 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
|
|
|
36370
36620
|
["Seed now (idempotent)", "$0 bootstrap seed-profile"]
|
|
36371
36621
|
]
|
|
36372
36622
|
});
|
|
36373
|
-
endpoint =
|
|
36374
|
-
brain =
|
|
36375
|
-
watch =
|
|
36376
|
-
timeout =
|
|
36377
|
-
githubAppCreds =
|
|
36623
|
+
endpoint = Option64.String("--endpoint", { description: "Override the Foundry endpoint (else read from the install status)." });
|
|
36624
|
+
brain = Option64.String("--brain", { description: "Seed only this one brain repo, instead of both <org>/stacey-brain and <org>/ezra-brain." });
|
|
36625
|
+
watch = Option64.Boolean("--watch", false, { description: "Poll until the intake completes (or --timeout)." });
|
|
36626
|
+
timeout = Option64.String("--timeout", { description: "Watch timeout in minutes (default 20)." });
|
|
36627
|
+
githubAppCreds = Option64.String("--github-app-creds");
|
|
36378
36628
|
async executeCommand() {
|
|
36379
36629
|
const ctx = await resolveSeedContext({
|
|
36380
36630
|
endpointOverride: typeof this.endpoint === "string" ? this.endpoint : void 0,
|
|
@@ -36459,10 +36709,10 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
|
|
|
36459
36709
|
};
|
|
36460
36710
|
|
|
36461
36711
|
// src/commands/telemetry/enroll.ts
|
|
36462
|
-
import * as
|
|
36712
|
+
import * as fs42 from "fs";
|
|
36463
36713
|
import * as os22 from "os";
|
|
36464
36714
|
import * as path46 from "path";
|
|
36465
|
-
import { Command as
|
|
36715
|
+
import { Command as Command68, Option as Option65 } from "clipanion";
|
|
36466
36716
|
init_errors();
|
|
36467
36717
|
|
|
36468
36718
|
// src/lib/telemetry-enroll.ts
|
|
@@ -36544,7 +36794,7 @@ async function resolveKeyVaultName(containerAppResourceId) {
|
|
|
36544
36794
|
}
|
|
36545
36795
|
var TelemetryEnrollCommand = class extends M8tCommand {
|
|
36546
36796
|
static paths = [["telemetry", "enroll"]];
|
|
36547
|
-
static usage =
|
|
36797
|
+
static usage = Command68.Usage({
|
|
36548
36798
|
description: "Enroll this installation for operational telemetry (pre-existing installs).",
|
|
36549
36799
|
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.",
|
|
36550
36800
|
examples: [
|
|
@@ -36552,11 +36802,11 @@ var TelemetryEnrollCommand = class extends M8tCommand {
|
|
|
36552
36802
|
["Enroll and share a support contact", "$0 telemetry enroll --contact-email you@example.com --company 'Acme'"]
|
|
36553
36803
|
]
|
|
36554
36804
|
});
|
|
36555
|
-
company =
|
|
36556
|
-
contactEmail =
|
|
36557
|
-
subscription =
|
|
36558
|
-
resourceGroup =
|
|
36559
|
-
force =
|
|
36805
|
+
company = Option65.String("--company", { description: "Share your company name with m8t support. Not sent unless you pass it." });
|
|
36806
|
+
contactEmail = Option65.String("--contact-email", { description: "Share a contact email with m8t support. Not sent unless you pass it." });
|
|
36807
|
+
subscription = Option65.String("--subscription", { description: "Azure subscription id used to find your deployment. Never sent to m8t." });
|
|
36808
|
+
resourceGroup = Option65.String("--resource-group", { description: "Resource group to disambiguate discovery, if you have more than one m8t deployment." });
|
|
36809
|
+
force = Option65.Boolean("--force", false, { description: "Enroll even when a key is already stored. Use only when m8t support asks you to." });
|
|
36560
36810
|
async executeCommand() {
|
|
36561
36811
|
const account = await getAzAccount();
|
|
36562
36812
|
const subscriptionId = (typeof this.subscription === "string" ? this.subscription : void 0) ?? account.subscriptionId;
|
|
@@ -36589,13 +36839,13 @@ var TelemetryEnrollCommand = class extends M8tCommand {
|
|
|
36589
36839
|
...company ? { company } : {},
|
|
36590
36840
|
...contactEmail ? { contactEmail } : {}
|
|
36591
36841
|
});
|
|
36592
|
-
const keyDir =
|
|
36842
|
+
const keyDir = fs42.mkdtempSync(path46.join(os22.tmpdir(), "m8t-ingest-key-"));
|
|
36593
36843
|
const keyFile = path46.join(keyDir, "key");
|
|
36594
36844
|
try {
|
|
36595
|
-
|
|
36845
|
+
fs42.writeFileSync(keyFile, ingestKey, { mode: 384 });
|
|
36596
36846
|
await runAz(["keyvault", "secret", "set", "--vault-name", kvName, "--name", INGEST_KEY_SECRET, "--file", keyFile, "--only-show-errors"]);
|
|
36597
36847
|
} finally {
|
|
36598
|
-
|
|
36848
|
+
fs42.rmSync(keyDir, { recursive: true, force: true });
|
|
36599
36849
|
}
|
|
36600
36850
|
this.context.stdout.write(
|
|
36601
36851
|
`${colors.success("\u2713")} enrolled (instance ${instanceId}); ingest key stored in Key Vault ${kvName}. The gateway picks it up on its next cycle.
|
|
@@ -36606,7 +36856,7 @@ var TelemetryEnrollCommand = class extends M8tCommand {
|
|
|
36606
36856
|
};
|
|
36607
36857
|
|
|
36608
36858
|
// src/commands/companion/bridge.ts
|
|
36609
|
-
import { Command as
|
|
36859
|
+
import { Command as Command69, Option as Option66 } from "clipanion";
|
|
36610
36860
|
|
|
36611
36861
|
// ../../packages/companion-bridge-contract/src/index.ts
|
|
36612
36862
|
var COMPANION_MESSAGE_MAX_CODE_POINTS = 32768;
|
|
@@ -38073,14 +38323,14 @@ async function runCompanionBridge(stdin, stdout, stderr, deps = defaultDeps3) {
|
|
|
38073
38323
|
return 3;
|
|
38074
38324
|
}
|
|
38075
38325
|
}
|
|
38076
|
-
var CompanionBridgeCommand = class extends
|
|
38326
|
+
var CompanionBridgeCommand = class extends Command69 {
|
|
38077
38327
|
static paths = [["companion", "_bridge"]];
|
|
38078
38328
|
/**
|
|
38079
38329
|
* One process serving many requests instead of one per request, so the
|
|
38080
38330
|
* session keeps its authenticated context between them. A CLI predating the
|
|
38081
38331
|
* flag rejects it outright, which is how the app knows to fall back.
|
|
38082
38332
|
*/
|
|
38083
|
-
serve =
|
|
38333
|
+
serve = Option66.Boolean("--serve", false);
|
|
38084
38334
|
async execute() {
|
|
38085
38335
|
if (this.serve) {
|
|
38086
38336
|
return runCompanionBridgeServe(
|
|
@@ -38098,7 +38348,7 @@ var CompanionBridgeCommand = class extends Command68 {
|
|
|
38098
38348
|
};
|
|
38099
38349
|
|
|
38100
38350
|
// src/commands/companion/status.ts
|
|
38101
|
-
import { Command as
|
|
38351
|
+
import { Command as Command70 } from "clipanion";
|
|
38102
38352
|
async function withTimeout(work, ms) {
|
|
38103
38353
|
let timer;
|
|
38104
38354
|
try {
|
|
@@ -38170,7 +38420,7 @@ Run: m8t companion install
|
|
|
38170
38420
|
}
|
|
38171
38421
|
var CompanionStatusCommand = class extends M8tCommand {
|
|
38172
38422
|
static paths = [["companion", "status"]];
|
|
38173
|
-
static usage =
|
|
38423
|
+
static usage = Command70.Usage({
|
|
38174
38424
|
description: "Verify the installed desktop companion without launching it."
|
|
38175
38425
|
});
|
|
38176
38426
|
async executeCommand() {
|
|
@@ -38184,7 +38434,7 @@ var CompanionStatusCommand = class extends M8tCommand {
|
|
|
38184
38434
|
};
|
|
38185
38435
|
|
|
38186
38436
|
// src/commands/companion/repair.ts
|
|
38187
|
-
import { Command as
|
|
38437
|
+
import { Command as Command71, Option as Option67 } from "clipanion";
|
|
38188
38438
|
async function runCompanionRepairCommand(stdout, repair) {
|
|
38189
38439
|
const state = await repair();
|
|
38190
38440
|
if (state.state === "not-released") {
|
|
@@ -38203,10 +38453,10 @@ async function runCompanionRepairCommand(stdout, repair) {
|
|
|
38203
38453
|
}
|
|
38204
38454
|
var CompanionRepairCommand = class extends M8tCommand {
|
|
38205
38455
|
static paths = [["companion", "repair"]];
|
|
38206
|
-
static usage =
|
|
38456
|
+
static usage = Command71.Usage({
|
|
38207
38457
|
description: "Restore the desktop companions and start-at-login state."
|
|
38208
38458
|
});
|
|
38209
|
-
resourceGroup =
|
|
38459
|
+
resourceGroup = Option67.String("--resource-group", {
|
|
38210
38460
|
description: "Which deployment to bind to, when the subscription holds more than one."
|
|
38211
38461
|
});
|
|
38212
38462
|
async executeCommand() {
|
|
@@ -38224,7 +38474,7 @@ var CompanionRepairCommand = class extends M8tCommand {
|
|
|
38224
38474
|
};
|
|
38225
38475
|
|
|
38226
38476
|
// src/commands/companion/uninstall.ts
|
|
38227
|
-
import { Command as
|
|
38477
|
+
import { Command as Command72 } from "clipanion";
|
|
38228
38478
|
async function runCompanionUninstallCommand(stdout, uninstall) {
|
|
38229
38479
|
const state = await uninstall();
|
|
38230
38480
|
if (state.state !== "not-installed") {
|
|
@@ -38236,7 +38486,7 @@ async function runCompanionUninstallCommand(stdout, uninstall) {
|
|
|
38236
38486
|
}
|
|
38237
38487
|
var CompanionUninstallCommand = class extends M8tCommand {
|
|
38238
38488
|
static paths = [["companion", "uninstall"]];
|
|
38239
|
-
static usage =
|
|
38489
|
+
static usage = Command72.Usage({
|
|
38240
38490
|
description: "Remove only this user's desktop companion installation."
|
|
38241
38491
|
});
|
|
38242
38492
|
async executeCommand() {
|
|
@@ -38297,6 +38547,7 @@ cli.register(PlatformConvergeCommand);
|
|
|
38297
38547
|
cli.register(PlatformClearIntentCommand);
|
|
38298
38548
|
cli.register(PlatformRequestUpdateCommand);
|
|
38299
38549
|
cli.register(PlatformSeedStampCommand);
|
|
38550
|
+
cli.register(PlatformVerifyContentCommand);
|
|
38300
38551
|
cli.register(PlatformGatewayAdoptCommand);
|
|
38301
38552
|
cli.register(PlatformPolicyCommand);
|
|
38302
38553
|
cli.register(PlatformEnableCostReportCommand);
|