@m8t-stack/cli 0.2.89 → 0.2.91
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 +1749 -835
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -921,8 +921,8 @@ async function mirrorBrainYaml(args) {
|
|
|
921
921
|
Accept: "application/vnd.github+json",
|
|
922
922
|
"X-GitHub-Api-Version": "2022-11-28"
|
|
923
923
|
};
|
|
924
|
-
const
|
|
925
|
-
for (let attempt = 1; attempt <=
|
|
924
|
+
const MAX_ATTEMPTS3 = 3;
|
|
925
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS3; attempt++) {
|
|
926
926
|
const getRes = await fetch(url, { headers });
|
|
927
927
|
let sha;
|
|
928
928
|
let existing;
|
|
@@ -953,8 +953,8 @@ async function mirrorBrainYaml(args) {
|
|
|
953
953
|
if (putRes.ok) return;
|
|
954
954
|
const text = await putRes.text();
|
|
955
955
|
const retryable = putRes.status === 409 || putRes.status === 422;
|
|
956
|
-
if (retryable && attempt <
|
|
957
|
-
const suffix = retryable ? ` after ${
|
|
956
|
+
if (retryable && attempt < MAX_ATTEMPTS3) continue;
|
|
957
|
+
const suffix = retryable ? ` after ${MAX_ATTEMPTS3.toString()} attempts` : "";
|
|
958
958
|
throw new LocalCliError({
|
|
959
959
|
code: "BRAIN_YAML_MIRROR_FAILED",
|
|
960
960
|
message: `PUT .m8t/brain.yaml: HTTP ${putRes.status.toString()}${suffix}
|
|
@@ -1461,8 +1461,33 @@ var init_enable_hosted_brain = __esm({
|
|
|
1461
1461
|
// src/cli.ts
|
|
1462
1462
|
import { Builtins, Cli } from "clipanion";
|
|
1463
1463
|
|
|
1464
|
+
// src/lib/dns-shim.ts
|
|
1465
|
+
import dns from "dns";
|
|
1466
|
+
var installed = false;
|
|
1467
|
+
function installFoundryDnsShim() {
|
|
1468
|
+
if (installed) return;
|
|
1469
|
+
installed = true;
|
|
1470
|
+
const origLookup = dns.lookup.bind(dns);
|
|
1471
|
+
dns.lookup = (hostname, options, cb) => {
|
|
1472
|
+
const opts = typeof options === "function" ? {} : options;
|
|
1473
|
+
const callback = typeof options === "function" ? options : cb;
|
|
1474
|
+
if (typeof hostname === "string" && hostname.endsWith("services.ai.azure.com")) {
|
|
1475
|
+
dns.resolve4(hostname, (err, addrs) => {
|
|
1476
|
+
if (err || !addrs?.length) {
|
|
1477
|
+
origLookup(hostname, options, cb);
|
|
1478
|
+
return;
|
|
1479
|
+
}
|
|
1480
|
+
if (opts?.all) callback(null, addrs.map((a) => ({ address: a, family: 4 })));
|
|
1481
|
+
else callback(null, addrs[0], 4);
|
|
1482
|
+
});
|
|
1483
|
+
return;
|
|
1484
|
+
}
|
|
1485
|
+
origLookup(hostname, options, cb);
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1464
1489
|
// src/lib/package-version.ts
|
|
1465
|
-
var CLI_VERSION = "0.2.
|
|
1490
|
+
var CLI_VERSION = "0.2.91";
|
|
1466
1491
|
|
|
1467
1492
|
// src/lib/render-error.ts
|
|
1468
1493
|
init_errors();
|
|
@@ -19667,19 +19692,20 @@ init_rbac();
|
|
|
19667
19692
|
// src/lib/acs-provision.ts
|
|
19668
19693
|
var MANAGED_DOMAIN = "AzureManagedDomain";
|
|
19669
19694
|
async function provisionAcsEmail(args) {
|
|
19670
|
-
const { client, subscriptionId, resourceGroup, name, dataLocation, onProgress } = args;
|
|
19671
|
-
const acsName = `acs-${name}`;
|
|
19672
|
-
const emailName = `${name}-email`;
|
|
19695
|
+
const { client, subscriptionId, resourceGroup, name, dataLocation, tags, onProgress } = args;
|
|
19696
|
+
const acsName = args.acsName ?? `acs-${name}`;
|
|
19697
|
+
const emailName = args.emailName ?? `${name}-email`;
|
|
19673
19698
|
onProgress?.(`creating ACS resource ${acsName}\u2026`);
|
|
19674
19699
|
const acs = await client.communicationServices.beginCreateOrUpdateAndWait(
|
|
19675
19700
|
resourceGroup,
|
|
19676
19701
|
acsName,
|
|
19677
|
-
{ location: "global", dataLocation }
|
|
19702
|
+
{ location: "global", dataLocation, ...tags ? { tags } : {} }
|
|
19678
19703
|
);
|
|
19679
19704
|
onProgress?.(`creating Email Communication Service ${emailName}\u2026`);
|
|
19680
19705
|
await client.emailServices.beginCreateOrUpdateAndWait(resourceGroup, emailName, {
|
|
19681
19706
|
location: "global",
|
|
19682
|
-
dataLocation
|
|
19707
|
+
dataLocation,
|
|
19708
|
+
...tags ? { tags } : {}
|
|
19683
19709
|
});
|
|
19684
19710
|
onProgress?.("provisioning the Azure-managed domain\u2026");
|
|
19685
19711
|
const domain = await client.domains.beginCreateOrUpdateAndWait(
|
|
@@ -19693,7 +19719,8 @@ async function provisionAcsEmail(args) {
|
|
|
19693
19719
|
await client.communicationServices.beginCreateOrUpdateAndWait(resourceGroup, acsName, {
|
|
19694
19720
|
location: "global",
|
|
19695
19721
|
dataLocation,
|
|
19696
|
-
linkedDomains: [domainResourceId]
|
|
19722
|
+
linkedDomains: [domainResourceId],
|
|
19723
|
+
...tags ? { tags } : {}
|
|
19697
19724
|
});
|
|
19698
19725
|
const hostName = acs.hostName ?? `${acsName}.communication.azure.com`;
|
|
19699
19726
|
const fromSenderDomain = domain.fromSenderDomain;
|
|
@@ -19706,6 +19733,515 @@ async function provisionAcsEmail(args) {
|
|
|
19706
19733
|
};
|
|
19707
19734
|
}
|
|
19708
19735
|
|
|
19736
|
+
// src/lib/acs-resolve.ts
|
|
19737
|
+
init_errors();
|
|
19738
|
+
var ACS_PLATFORM_TAG = "m8t:email";
|
|
19739
|
+
var ACS_PLATFORM_TAG_VALUE = "platform";
|
|
19740
|
+
var VERIFIED = "Verified";
|
|
19741
|
+
function emailFeatureFrom(acs, enabled) {
|
|
19742
|
+
return { enabled, acsEndpoint: acs.endpoint, acsSender: acs.sender };
|
|
19743
|
+
}
|
|
19744
|
+
function platformSuffixFromKvUri(kvUri) {
|
|
19745
|
+
const host = /^https?:\/\/([^./]+)\./i.exec(kvUri.trim())?.[1];
|
|
19746
|
+
if (!host) return null;
|
|
19747
|
+
return /^m8t-kv-(.+)$/i.exec(host)?.[1] ?? host;
|
|
19748
|
+
}
|
|
19749
|
+
function resourceGroupFromArmId(armId) {
|
|
19750
|
+
return /\/resourceGroups\/([^/]+)/i.exec(armId)?.[1] ?? null;
|
|
19751
|
+
}
|
|
19752
|
+
function classifyGrantScope(grantScope, expectedSubscriptionId) {
|
|
19753
|
+
const unusable = (reason) => ({ kind: "unusable", reason });
|
|
19754
|
+
const parts = grantScope.trim().replace(/^\/+/, "").replace(/\/+$/, "").split("/");
|
|
19755
|
+
if (parts.length < 2 || parts[0]?.toLowerCase() !== "subscriptions") {
|
|
19756
|
+
return unusable(`'${grantScope}' is not a recognisable Azure scope`);
|
|
19757
|
+
}
|
|
19758
|
+
const subscriptionId = parts[1] ?? "";
|
|
19759
|
+
if (subscriptionId === "") return unusable(`'${grantScope}' names no subscription`);
|
|
19760
|
+
if (subscriptionId.toLowerCase() !== expectedSubscriptionId.toLowerCase()) {
|
|
19761
|
+
return unusable(
|
|
19762
|
+
`the Contributor scope is in subscription ${subscriptionId}, but this deploy targets ${expectedSubscriptionId} \u2014 an ACS created here would not be covered by that grant`
|
|
19763
|
+
);
|
|
19764
|
+
}
|
|
19765
|
+
if (parts.length === 2) return { kind: "subscription", subscriptionId };
|
|
19766
|
+
if (parts.length < 4 || parts[2]?.toLowerCase() !== "resourcegroups") {
|
|
19767
|
+
return unusable(`'${grantScope}' is neither a subscription nor a resource-group scope`);
|
|
19768
|
+
}
|
|
19769
|
+
const resourceGroup = parts[3] ?? "";
|
|
19770
|
+
if (resourceGroup === "") return unusable(`'${grantScope}' names no resource group`);
|
|
19771
|
+
if (parts.length > 4) {
|
|
19772
|
+
return unusable(
|
|
19773
|
+
`the Contributor scope is a single resource (${grantScope}), so no resource group is authorised for an ACS data-plane send`
|
|
19774
|
+
);
|
|
19775
|
+
}
|
|
19776
|
+
return { kind: "resource-group", subscriptionId, resourceGroup };
|
|
19777
|
+
}
|
|
19778
|
+
function parseDomainId(id) {
|
|
19779
|
+
const m = /\/emailServices\/([^/]+)\/domains\/([^/]+)/i.exec(id);
|
|
19780
|
+
if (!m?.[1] || !m[2]) return null;
|
|
19781
|
+
return { emailServiceName: m[1], domainName: m[2] };
|
|
19782
|
+
}
|
|
19783
|
+
async function resolveAcs(args) {
|
|
19784
|
+
const { client, stamp, subscriptionId, resourceGroup, suffix, dataLocation, onProgress } = args;
|
|
19785
|
+
const recorded = stamp?.features?.email;
|
|
19786
|
+
if (recorded?.acsEndpoint && recorded.acsSender) {
|
|
19787
|
+
onProgress?.("using the ACS sender recorded in the platform stamp.");
|
|
19788
|
+
return { source: "stamp", acs: { endpoint: recorded.acsEndpoint, sender: recorded.acsSender } };
|
|
19789
|
+
}
|
|
19790
|
+
onProgress?.(`looking for an existing email-capable ACS in ${resourceGroup}\u2026`);
|
|
19791
|
+
const candidates = [];
|
|
19792
|
+
for await (const r of client.communicationServices.listByResourceGroup(resourceGroup)) {
|
|
19793
|
+
const linked = r.linkedDomains;
|
|
19794
|
+
if (!linked || linked.length === 0) continue;
|
|
19795
|
+
if (!r.name || !r.hostName) continue;
|
|
19796
|
+
candidates.push({
|
|
19797
|
+
name: r.name,
|
|
19798
|
+
hostName: r.hostName,
|
|
19799
|
+
linkedDomains: linked,
|
|
19800
|
+
tagged: r.tags?.[ACS_PLATFORM_TAG] === ACS_PLATFORM_TAG_VALUE
|
|
19801
|
+
});
|
|
19802
|
+
}
|
|
19803
|
+
candidates.sort((a, b) => a.tagged === b.tagged ? a.name.localeCompare(b.name) : a.tagged ? -1 : 1);
|
|
19804
|
+
for (const c of candidates) {
|
|
19805
|
+
const sender = await verifiedSenderFor(client, resourceGroup, c);
|
|
19806
|
+
if (sender === null) continue;
|
|
19807
|
+
onProgress?.(`adopting the existing ACS ${c.name} (verified managed domain).`);
|
|
19808
|
+
return { source: "discovered", acsName: c.name, acs: { endpoint: `https://${c.hostName}/`, sender } };
|
|
19809
|
+
}
|
|
19810
|
+
if (suffix === null || suffix === "") {
|
|
19811
|
+
throw new LocalCliError({
|
|
19812
|
+
code: "NO_PLATFORM_SUFFIX",
|
|
19813
|
+
message: "No email-capable ACS exists here and one cannot be named: this install's resource suffix could not be worked out.",
|
|
19814
|
+
hint: "Pass --kv-uri <install vault uri> (or set AZURE_KEYVAULT_URI) so the ACS can be named consistently with the rest of the platform."
|
|
19815
|
+
});
|
|
19816
|
+
}
|
|
19817
|
+
const acsName = `m8t-acs-${suffix}`;
|
|
19818
|
+
const emailName = `m8t-email-${suffix}`;
|
|
19819
|
+
onProgress?.(`no email-capable ACS found \u2014 provisioning ${acsName}\u2026`);
|
|
19820
|
+
const provisioned = await provisionAcsEmail({
|
|
19821
|
+
client,
|
|
19822
|
+
subscriptionId,
|
|
19823
|
+
resourceGroup,
|
|
19824
|
+
name: `m8t-${suffix}`,
|
|
19825
|
+
acsName,
|
|
19826
|
+
emailName,
|
|
19827
|
+
dataLocation,
|
|
19828
|
+
tags: { [ACS_PLATFORM_TAG]: ACS_PLATFORM_TAG_VALUE },
|
|
19829
|
+
onProgress
|
|
19830
|
+
});
|
|
19831
|
+
return { source: "provisioned", acsName, acs: { endpoint: provisioned.endpoint, sender: provisioned.sender } };
|
|
19832
|
+
}
|
|
19833
|
+
async function verifiedSenderFor(client, resourceGroup, c) {
|
|
19834
|
+
for (const id of c.linkedDomains) {
|
|
19835
|
+
const parsed = parseDomainId(id);
|
|
19836
|
+
if (!parsed) continue;
|
|
19837
|
+
try {
|
|
19838
|
+
const d = await client.domains.get(resourceGroup, parsed.emailServiceName, parsed.domainName);
|
|
19839
|
+
if (d.verificationStates?.domain?.status !== VERIFIED) continue;
|
|
19840
|
+
if (!d.fromSenderDomain) continue;
|
|
19841
|
+
return `DoNotReply@${d.fromSenderDomain}`;
|
|
19842
|
+
} catch {
|
|
19843
|
+
continue;
|
|
19844
|
+
}
|
|
19845
|
+
}
|
|
19846
|
+
return null;
|
|
19847
|
+
}
|
|
19848
|
+
|
|
19849
|
+
// src/lib/platform-stamp.ts
|
|
19850
|
+
import { TableClient as TableClient2 } from "@azure/data-tables";
|
|
19851
|
+
|
|
19852
|
+
// ../../packages/platform-release/dist/esm/manifest.js
|
|
19853
|
+
var SEVERITIES = ["critical", "recommended", "optional"];
|
|
19854
|
+
var IMAGE_KEYS = ["gateway", "codingAgent", "azureExecutor", "installer"];
|
|
19855
|
+
var COMPANION_TARGETS = [
|
|
19856
|
+
"darwin-arm64",
|
|
19857
|
+
"darwin-x64",
|
|
19858
|
+
"win32-arm64",
|
|
19859
|
+
"win32-x64"
|
|
19860
|
+
];
|
|
19861
|
+
function companionTargetFor(platform, architecture) {
|
|
19862
|
+
const target = `${platform}-${architecture}`;
|
|
19863
|
+
return COMPANION_TARGETS.includes(target) ? target : null;
|
|
19864
|
+
}
|
|
19865
|
+
function isObj2(v) {
|
|
19866
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
19867
|
+
}
|
|
19868
|
+
function validateManifest(m) {
|
|
19869
|
+
const errors = [];
|
|
19870
|
+
if (!isObj2(m))
|
|
19871
|
+
return ["manifest is not an object"];
|
|
19872
|
+
if (m.schemaVersion !== 1)
|
|
19873
|
+
errors.push("schemaVersion must be the integer 1");
|
|
19874
|
+
const p = m.platform;
|
|
19875
|
+
if (!isObj2(p)) {
|
|
19876
|
+
errors.push("platform is required");
|
|
19877
|
+
} else {
|
|
19878
|
+
for (const f of ["version", "tag", "releasedAt", "commit", "notes", "notesUrl"]) {
|
|
19879
|
+
if (typeof p[f] !== "string" || p[f].length === 0)
|
|
19880
|
+
errors.push(`platform.${f} is required`);
|
|
19881
|
+
}
|
|
19882
|
+
if (!SEVERITIES.includes(p.severity))
|
|
19883
|
+
errors.push(`platform.severity must be one of ${SEVERITIES.join("|")}`);
|
|
19884
|
+
if (!(typeof p.previousVersion === "string" || p.previousVersion === null)) {
|
|
19885
|
+
errors.push("platform.previousVersion must be a string or null");
|
|
19886
|
+
}
|
|
19887
|
+
}
|
|
19888
|
+
const c = m.components;
|
|
19889
|
+
if (!isObj2(c)) {
|
|
19890
|
+
errors.push("components is required");
|
|
19891
|
+
return errors;
|
|
19892
|
+
}
|
|
19893
|
+
for (const key2 of IMAGE_KEYS) {
|
|
19894
|
+
const img = c[key2];
|
|
19895
|
+
if (!isObj2(img)) {
|
|
19896
|
+
errors.push(`components.${key2} is required`);
|
|
19897
|
+
continue;
|
|
19898
|
+
}
|
|
19899
|
+
if (img.kind !== "image")
|
|
19900
|
+
errors.push(`components.${key2}.kind must be "image"`);
|
|
19901
|
+
for (const f of ["ref", "tag", "version"]) {
|
|
19902
|
+
if (typeof img[f] !== "string" || img[f].length === 0)
|
|
19903
|
+
errors.push(`components.${key2}.${f} is required`);
|
|
19904
|
+
}
|
|
19905
|
+
if (typeof img.digest !== "string" || !/^sha256:[0-9a-f]+$/.test(img.digest)) {
|
|
19906
|
+
errors.push(`components.${key2}.digest must be a sha256:\u2026 digest`);
|
|
19907
|
+
}
|
|
19908
|
+
}
|
|
19909
|
+
const cli2 = c.cli;
|
|
19910
|
+
if (!isObj2(cli2) || cli2.kind !== "npm") {
|
|
19911
|
+
errors.push("components.cli (kind: npm) is required");
|
|
19912
|
+
} else {
|
|
19913
|
+
for (const f of ["package", "version", "min", "recommended"]) {
|
|
19914
|
+
if (typeof cli2[f] !== "string" || cli2[f].length === 0)
|
|
19915
|
+
errors.push(`components.cli.${f} is required`);
|
|
19916
|
+
}
|
|
19917
|
+
}
|
|
19918
|
+
const personas = c.personas;
|
|
19919
|
+
if (!isObj2(personas) || !isObj2(personas.items)) {
|
|
19920
|
+
errors.push("components.personas.items is required");
|
|
19921
|
+
} else {
|
|
19922
|
+
for (const [name, item] of Object.entries(personas.items)) {
|
|
19923
|
+
if (!isObj2(item) || typeof item.path !== "string" || item.path.length === 0) {
|
|
19924
|
+
errors.push(`components.personas.items.${name}.path is required`);
|
|
19925
|
+
}
|
|
19926
|
+
if (!isObj2(item) || typeof item.treeSha !== "string" || item.treeSha.length < 4) {
|
|
19927
|
+
errors.push(`components.personas.items.${name}.treeSha is required`);
|
|
19928
|
+
}
|
|
19929
|
+
}
|
|
19930
|
+
}
|
|
19931
|
+
const seeds = c.brainSeeds;
|
|
19932
|
+
if (!isObj2(seeds) || !isObj2(seeds.items)) {
|
|
19933
|
+
errors.push("components.brainSeeds.items is required");
|
|
19934
|
+
} else {
|
|
19935
|
+
for (const [name, item] of Object.entries(seeds.items)) {
|
|
19936
|
+
if (!isObj2(item) || typeof item.path !== "string" || item.path.length === 0) {
|
|
19937
|
+
errors.push(`components.brainSeeds.items.${name}.path is required`);
|
|
19938
|
+
}
|
|
19939
|
+
if (!isObj2(item) || !isObj2(item.subtrees)) {
|
|
19940
|
+
errors.push(`components.brainSeeds.items.${name}.subtrees is required`);
|
|
19941
|
+
}
|
|
19942
|
+
}
|
|
19943
|
+
}
|
|
19944
|
+
const infra = c.infra;
|
|
19945
|
+
if (infra !== void 0) {
|
|
19946
|
+
if (!isObj2(infra) || infra.kind !== "infra") {
|
|
19947
|
+
errors.push('components.infra.kind must be "infra"');
|
|
19948
|
+
}
|
|
19949
|
+
if (!isObj2(infra) || typeof infra.treeSha !== "string" || infra.treeSha.length < 4) {
|
|
19950
|
+
errors.push("components.infra.treeSha is required");
|
|
19951
|
+
}
|
|
19952
|
+
}
|
|
19953
|
+
const companion = c.companion;
|
|
19954
|
+
if (companion !== void 0) {
|
|
19955
|
+
if (!isObj2(companion) || companion.kind !== "desktop") {
|
|
19956
|
+
errors.push('components.companion.kind must be "desktop"');
|
|
19957
|
+
} else {
|
|
19958
|
+
for (const f of ["version", "tag"]) {
|
|
19959
|
+
if (typeof companion[f] !== "string" || companion[f].length === 0) {
|
|
19960
|
+
errors.push(`components.companion.${f} is required`);
|
|
19961
|
+
}
|
|
19962
|
+
}
|
|
19963
|
+
if (!isObj2(companion.targets) || Object.keys(companion.targets).length === 0) {
|
|
19964
|
+
errors.push("components.companion.targets must name at least one target");
|
|
19965
|
+
} else {
|
|
19966
|
+
for (const [name, target] of Object.entries(companion.targets)) {
|
|
19967
|
+
if (!COMPANION_TARGETS.includes(name)) {
|
|
19968
|
+
errors.push(`components.companion.targets.${name} is not a known target`);
|
|
19969
|
+
continue;
|
|
19970
|
+
}
|
|
19971
|
+
if (!isObj2(target) || typeof target.asset !== "string" || target.asset.length === 0) {
|
|
19972
|
+
errors.push(`components.companion.targets.${name}.asset is required`);
|
|
19973
|
+
}
|
|
19974
|
+
if (!isObj2(target) || typeof target.sha256 !== "string" || !/^[0-9a-f]{64}$/.test(target.sha256)) {
|
|
19975
|
+
errors.push(`components.companion.targets.${name}.sha256 must be a sha256 hex digest`);
|
|
19976
|
+
}
|
|
19977
|
+
}
|
|
19978
|
+
}
|
|
19979
|
+
}
|
|
19980
|
+
}
|
|
19981
|
+
return errors;
|
|
19982
|
+
}
|
|
19983
|
+
|
|
19984
|
+
// ../../packages/platform-release/dist/esm/stamp.js
|
|
19985
|
+
var TABLE = "Metadata";
|
|
19986
|
+
var PK = "system";
|
|
19987
|
+
var RK = "platform";
|
|
19988
|
+
function isEmailFeatureUsable(f) {
|
|
19989
|
+
return f?.enabled === true && typeof f.acsEndpoint === "string" && f.acsEndpoint !== "" && typeof f.acsSender === "string" && f.acsSender !== "";
|
|
19990
|
+
}
|
|
19991
|
+
function stampToEntity(s) {
|
|
19992
|
+
return {
|
|
19993
|
+
partitionKey: PK,
|
|
19994
|
+
rowKey: RK,
|
|
19995
|
+
platformVersion: s.platformVersion,
|
|
19996
|
+
updatedAt: s.updatedAt,
|
|
19997
|
+
lastResult: s.lastResult,
|
|
19998
|
+
value: JSON.stringify(s)
|
|
19999
|
+
};
|
|
20000
|
+
}
|
|
20001
|
+
function entityToStamp(e) {
|
|
20002
|
+
const raw = e.value;
|
|
20003
|
+
if (typeof raw !== "string")
|
|
20004
|
+
return null;
|
|
20005
|
+
try {
|
|
20006
|
+
return JSON.parse(raw);
|
|
20007
|
+
} catch {
|
|
20008
|
+
return null;
|
|
20009
|
+
}
|
|
20010
|
+
}
|
|
20011
|
+
|
|
20012
|
+
// ../../packages/platform-release/dist/esm/channel-url.js
|
|
20013
|
+
var RELEASES_REPO = "m8t-labs/m8t-releases";
|
|
20014
|
+
var RELEASES_BASE = `https://github.com/${RELEASES_REPO}/releases`;
|
|
20015
|
+
var CHANNEL_LATEST_URL = `${RELEASES_BASE}/latest/download/manifest.json`;
|
|
20016
|
+
var CANARY_POINTER_TAG = "canary";
|
|
20017
|
+
var CHANNEL_CANARY_URL = `${RELEASES_BASE}/download/${CANARY_POINTER_TAG}/manifest.json`;
|
|
20018
|
+
function platformTag(version) {
|
|
20019
|
+
const bare = version.trim().replace(/^platform-/, "").replace(/^v/, "");
|
|
20020
|
+
return `platform-v${bare}`;
|
|
20021
|
+
}
|
|
20022
|
+
function channelUrlForVersion(version) {
|
|
20023
|
+
return `${RELEASES_BASE}/download/${platformTag(version)}/manifest.json`;
|
|
20024
|
+
}
|
|
20025
|
+
function releaseAssetUrl(tag, asset) {
|
|
20026
|
+
return `${RELEASES_BASE}/download/${tag}/${asset}`;
|
|
20027
|
+
}
|
|
20028
|
+
|
|
20029
|
+
// ../../packages/platform-release/dist/esm/canary-version.js
|
|
20030
|
+
var CANARY_IDENTIFIER = "canary";
|
|
20031
|
+
var STABLE_CORE = String.raw`\d+\.\d+\.\d+`;
|
|
20032
|
+
var CANARY_SUFFIX = String.raw`-${CANARY_IDENTIFIER}\.(?:0|[1-9]\d*)`;
|
|
20033
|
+
var CANARY_VERSION_PATTERN = `^${STABLE_CORE}${CANARY_SUFFIX}$`;
|
|
20034
|
+
var PLATFORM_VERSION_PATTERN = `^${STABLE_CORE}(?:${CANARY_SUFFIX})?$`;
|
|
20035
|
+
var PLATFORM_TAG_PATTERN = `^platform-v${STABLE_CORE}(?:${CANARY_SUFFIX})?$`;
|
|
20036
|
+
var CANARY_VERSION_RE = new RegExp(CANARY_VERSION_PATTERN);
|
|
20037
|
+
var STABLE_CORE_RE = new RegExp(`^${STABLE_CORE}$`);
|
|
20038
|
+
|
|
20039
|
+
// ../../packages/platform-release/dist/esm/apply-request.js
|
|
20040
|
+
var APPLY_REQUEST_PK = "system";
|
|
20041
|
+
var APPLY_REQUEST_RK = "apply-request";
|
|
20042
|
+
var IN_FLIGHT = ["claimed", "applying"];
|
|
20043
|
+
var LEASE_MS = 30 * 60 * 1e3;
|
|
20044
|
+
var MAX_LEASE_TAKEOVERS = 2;
|
|
20045
|
+
var MAX_HEALTHY_IN_FLIGHT_MS = LEASE_MS * (MAX_LEASE_TAKEOVERS + 1);
|
|
20046
|
+
function isInFlight(status) {
|
|
20047
|
+
return IN_FLIGHT.includes(status);
|
|
20048
|
+
}
|
|
20049
|
+
var BLOCKS_NEW_INTENT = ["pending", "claimed", "applying", "awaiting-engine-update"];
|
|
20050
|
+
function blocksNewIntent(status) {
|
|
20051
|
+
return BLOCKS_NEW_INTENT.includes(status);
|
|
20052
|
+
}
|
|
20053
|
+
function newIntent(target, provenance, nowIso) {
|
|
20054
|
+
return {
|
|
20055
|
+
schemaVersion: 1,
|
|
20056
|
+
target,
|
|
20057
|
+
provenance,
|
|
20058
|
+
status: "pending",
|
|
20059
|
+
phase: null,
|
|
20060
|
+
claimedBy: null,
|
|
20061
|
+
leaseUntil: null,
|
|
20062
|
+
attempt: 0,
|
|
20063
|
+
breaker: "none",
|
|
20064
|
+
result: null,
|
|
20065
|
+
createdAt: nowIso,
|
|
20066
|
+
updatedAt: nowIso
|
|
20067
|
+
};
|
|
20068
|
+
}
|
|
20069
|
+
function applyRequestToEntity(r) {
|
|
20070
|
+
return { partitionKey: APPLY_REQUEST_PK, rowKey: APPLY_REQUEST_RK, value: JSON.stringify(r), updatedAt: r.updatedAt };
|
|
20071
|
+
}
|
|
20072
|
+
function entityToApplyRequest(e) {
|
|
20073
|
+
return JSON.parse(e.value);
|
|
20074
|
+
}
|
|
20075
|
+
|
|
20076
|
+
// ../../packages/platform-release/dist/esm/update-policy.js
|
|
20077
|
+
var DEFAULT_POLICY_MODE = "auto-critical";
|
|
20078
|
+
var UPDATE_POLICY_PK = "system";
|
|
20079
|
+
var UPDATE_POLICY_RK = "update-policy";
|
|
20080
|
+
var MODES = ["notify-only", "auto-critical", "auto-all"];
|
|
20081
|
+
function isPolicyMode(x) {
|
|
20082
|
+
return typeof x === "string" && MODES.includes(x);
|
|
20083
|
+
}
|
|
20084
|
+
function updatePolicyToEntity(p) {
|
|
20085
|
+
return { partitionKey: UPDATE_POLICY_PK, rowKey: UPDATE_POLICY_RK, value: JSON.stringify(p) };
|
|
20086
|
+
}
|
|
20087
|
+
function entityToUpdatePolicy(e) {
|
|
20088
|
+
return JSON.parse(e.value);
|
|
20089
|
+
}
|
|
20090
|
+
|
|
20091
|
+
// ../../packages/platform-release/dist/esm/infra-params.js
|
|
20092
|
+
var INFRA_PARAMS_PK = "system";
|
|
20093
|
+
var INFRA_PARAMS_RK = "infra-params";
|
|
20094
|
+
function infraParamsToEntity(p) {
|
|
20095
|
+
return { partitionKey: INFRA_PARAMS_PK, rowKey: INFRA_PARAMS_RK, value: JSON.stringify(p) };
|
|
20096
|
+
}
|
|
20097
|
+
function entityToInfraParams(e) {
|
|
20098
|
+
return JSON.parse(e.value);
|
|
20099
|
+
}
|
|
20100
|
+
|
|
20101
|
+
// src/lib/platform-storage-discovery.ts
|
|
20102
|
+
init_http();
|
|
20103
|
+
init_errors();
|
|
20104
|
+
var ARM3 = "https://management.azure.com";
|
|
20105
|
+
var ARM_SCOPE7 = "https://management.azure.com/.default";
|
|
20106
|
+
var STORAGE_API = "2023-05-01";
|
|
20107
|
+
async function discoverStampStorage(opts) {
|
|
20108
|
+
const list = await authedJson({
|
|
20109
|
+
credential: opts.credential,
|
|
20110
|
+
scope: ARM_SCOPE7,
|
|
20111
|
+
method: "GET",
|
|
20112
|
+
url: `${ARM3}/subscriptions/${opts.subscriptionId}/resourceGroups/${opts.resourceGroup}/providers/Microsoft.Storage/storageAccounts?api-version=${STORAGE_API}`
|
|
20113
|
+
}) ?? {};
|
|
20114
|
+
const accounts = (list.value ?? []).filter((a) => a.properties?.primaryEndpoints?.table);
|
|
20115
|
+
const chosen = accounts.find((a) => a.tags?.m8t === "storage") ?? accounts.find((a) => a.tags?.["m8t:role"] === "gateway" || a.tags?.["m8t:role"] === "ledger") ?? (accounts.length === 1 ? accounts[0] : void 0);
|
|
20116
|
+
const tableEndpoint = chosen?.properties?.primaryEndpoints?.table?.replace(/\/$/, "");
|
|
20117
|
+
if (!tableEndpoint || !chosen?.id || !chosen.name) {
|
|
20118
|
+
throw new LocalCliError({
|
|
20119
|
+
code: "PLATFORM_STAMP_STORAGE_NOT_FOUND",
|
|
20120
|
+
message: `No table-capable storage account found in resource group ${opts.resourceGroup}.`,
|
|
20121
|
+
hint: `Found: ${accounts.map((a) => a.name).join(", ") || "(none)"}.`
|
|
20122
|
+
});
|
|
20123
|
+
}
|
|
20124
|
+
return { tableEndpoint, accountResourceId: chosen.id, accountName: chosen.name };
|
|
20125
|
+
}
|
|
20126
|
+
|
|
20127
|
+
// src/lib/metadata-table-write.ts
|
|
20128
|
+
import { TableClient, AzureNamedKeyCredential } from "@azure/data-tables";
|
|
20129
|
+
init_errors();
|
|
20130
|
+
init_rbac();
|
|
20131
|
+
function is403(e) {
|
|
20132
|
+
return e.statusCode === 403;
|
|
20133
|
+
}
|
|
20134
|
+
function is404(e) {
|
|
20135
|
+
return e.statusCode === 404;
|
|
20136
|
+
}
|
|
20137
|
+
function sleep2(ms) {
|
|
20138
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
20139
|
+
}
|
|
20140
|
+
async function makeSharedKeyClient(opts) {
|
|
20141
|
+
const rg = /\/resourceGroups\/([^/]+)\//i.exec(opts.accountResourceId)?.[1];
|
|
20142
|
+
if (!rg) return null;
|
|
20143
|
+
const key2 = (await runAz(["storage", "account", "keys", "list", "--account-name", opts.accountName, "-g", rg, "--query", "[0].value", "-o", "tsv"])).trim();
|
|
20144
|
+
if (!key2) return null;
|
|
20145
|
+
return new TableClient(opts.tableEndpoint, TABLE, new AzureNamedKeyCredential(opts.accountName, key2));
|
|
20146
|
+
}
|
|
20147
|
+
async function upsertMetadataEntity(opts) {
|
|
20148
|
+
const discover = opts.discoverImpl ?? discoverStampStorage;
|
|
20149
|
+
const makeAad = opts.aadClientImpl ?? ((endpoint, credential2) => new TableClient(endpoint, TABLE, credential2));
|
|
20150
|
+
const makeShared = opts.sharedClientImpl ?? makeSharedKeyClient;
|
|
20151
|
+
const nap = opts.sleepImpl ?? sleep2;
|
|
20152
|
+
const { tableEndpoint, accountResourceId, accountName } = await discover({
|
|
20153
|
+
credential: opts.credential,
|
|
20154
|
+
subscriptionId: opts.subscriptionId,
|
|
20155
|
+
resourceGroup: opts.resourceGroup
|
|
20156
|
+
});
|
|
20157
|
+
const aad = makeAad(tableEndpoint, opts.credential);
|
|
20158
|
+
await aad.createTable().catch(() => void 0);
|
|
20159
|
+
try {
|
|
20160
|
+
await aad.upsertEntity(opts.entity, "Replace");
|
|
20161
|
+
return;
|
|
20162
|
+
} catch (e) {
|
|
20163
|
+
if (!is403(e)) throw e;
|
|
20164
|
+
}
|
|
20165
|
+
opts.onProgress?.(`writing the ${opts.label} with the account key \u2014 your identity lacks the Storage Table data role.`);
|
|
20166
|
+
const shared = await makeShared({ tableEndpoint, accountResourceId, accountName });
|
|
20167
|
+
if (shared) {
|
|
20168
|
+
await shared.createTable().catch(() => void 0);
|
|
20169
|
+
await shared.upsertEntity(opts.entity, "Replace");
|
|
20170
|
+
return;
|
|
20171
|
+
}
|
|
20172
|
+
opts.onProgress?.("shared-key access is disabled \u2014 granting Storage Table Data Contributor to your identity (one-time)\u2026");
|
|
20173
|
+
try {
|
|
20174
|
+
const oid = await getCallerObjectId();
|
|
20175
|
+
await grantStorageTableDataContributor({
|
|
20176
|
+
credential: opts.credential,
|
|
20177
|
+
subscriptionId: opts.subscriptionId,
|
|
20178
|
+
scope: accountResourceId,
|
|
20179
|
+
principalId: oid
|
|
20180
|
+
});
|
|
20181
|
+
} catch (e) {
|
|
20182
|
+
throw new LocalCliError({
|
|
20183
|
+
code: opts.errorCode,
|
|
20184
|
+
message: `Cannot write the ${opts.label}: AAD lacks the Storage Table data role for ${accountName}, shared-key access is disabled, and self-granting the role failed.`,
|
|
20185
|
+
hint: "Grant yourself 'Storage Table Data Contributor' on the storage account and retry.",
|
|
20186
|
+
cause: e
|
|
20187
|
+
});
|
|
20188
|
+
}
|
|
20189
|
+
for (let i = 0; i < 6; i++) {
|
|
20190
|
+
await nap(1e4);
|
|
20191
|
+
try {
|
|
20192
|
+
await aad.upsertEntity(opts.entity, "Replace");
|
|
20193
|
+
return;
|
|
20194
|
+
} catch (e) {
|
|
20195
|
+
if (!is403(e)) throw e;
|
|
20196
|
+
}
|
|
20197
|
+
}
|
|
20198
|
+
throw new LocalCliError({
|
|
20199
|
+
code: opts.errorCode,
|
|
20200
|
+
message: `Cannot write the ${opts.label}: granted 'Storage Table Data Contributor' on ${accountName}, but the data-plane role has not propagated yet.`,
|
|
20201
|
+
hint: opts.retryHint
|
|
20202
|
+
});
|
|
20203
|
+
}
|
|
20204
|
+
|
|
20205
|
+
// src/lib/platform-stamp.ts
|
|
20206
|
+
async function writeStamp(opts) {
|
|
20207
|
+
const entity = stampToEntity(opts.stamp);
|
|
20208
|
+
await upsertMetadataEntity({
|
|
20209
|
+
credential: opts.credential,
|
|
20210
|
+
subscriptionId: opts.subscriptionId,
|
|
20211
|
+
resourceGroup: opts.resourceGroup,
|
|
20212
|
+
entity,
|
|
20213
|
+
label: "platform stamp",
|
|
20214
|
+
errorCode: "PLATFORM_STAMP_NO_ACCESS",
|
|
20215
|
+
retryHint: "The role assignment persists \u2014 retry 'm8t platform update' in a few minutes.",
|
|
20216
|
+
...opts.onProgress ? { onProgress: opts.onProgress } : {}
|
|
20217
|
+
});
|
|
20218
|
+
}
|
|
20219
|
+
async function readStampOutcome(opts) {
|
|
20220
|
+
const { tableEndpoint, accountResourceId, accountName } = await discoverStampStorage(opts);
|
|
20221
|
+
const aad = new TableClient2(tableEndpoint, TABLE, opts.credential);
|
|
20222
|
+
try {
|
|
20223
|
+
const row = await aad.getEntity(PK, RK);
|
|
20224
|
+
const stamp = entityToStamp(row);
|
|
20225
|
+
return stamp ? { source: "explicit", stamp } : { source: "unreadable" };
|
|
20226
|
+
} catch (e) {
|
|
20227
|
+
if (is404(e)) return { source: "absent" };
|
|
20228
|
+
if (!is403(e)) return { source: "unreadable" };
|
|
20229
|
+
}
|
|
20230
|
+
try {
|
|
20231
|
+
const shared = await makeSharedKeyClient({ tableEndpoint, accountResourceId, accountName });
|
|
20232
|
+
if (!shared) return { source: "unreadable" };
|
|
20233
|
+
const row = await shared.getEntity(PK, RK);
|
|
20234
|
+
const stamp = entityToStamp(row);
|
|
20235
|
+
return stamp ? { source: "explicit", stamp } : { source: "unreadable" };
|
|
20236
|
+
} catch {
|
|
20237
|
+
return { source: "unreadable" };
|
|
20238
|
+
}
|
|
20239
|
+
}
|
|
20240
|
+
async function readStamp(opts) {
|
|
20241
|
+
const result2 = await readStampOutcome(opts);
|
|
20242
|
+
return result2.source === "explicit" ? result2.stamp : null;
|
|
20243
|
+
}
|
|
20244
|
+
|
|
19709
20245
|
// src/commands/azure-exec/deploy.ts
|
|
19710
20246
|
import { CommunicationServiceManagementClient } from "@azure/arm-communication";
|
|
19711
20247
|
var SIZE_PRESETS2 = {
|
|
@@ -19746,7 +20282,14 @@ var AzureExecDeployCommand = class extends M8tCommand {
|
|
|
19746
20282
|
output = Option31.String("--output");
|
|
19747
20283
|
skipQuotaCheck = Option31.Boolean("--skip-quota-check", false);
|
|
19748
20284
|
grantAccessAdmin = Option31.Boolean("--grant-access-admin", false);
|
|
19749
|
-
|
|
20285
|
+
/** Deprecated no-op: outbound email is provisioned by default now. Kept for one release
|
|
20286
|
+
* so runbooks and scripts that still pass it do not hard-fail. */
|
|
20287
|
+
enableEmail = Option31.Boolean("--enable-email", false, {
|
|
20288
|
+
description: "Deprecated no-op \u2014 outbound email is provisioned by default. Use --no-email to opt out."
|
|
20289
|
+
});
|
|
20290
|
+
noEmail = Option31.Boolean("--no-email", false, {
|
|
20291
|
+
description: "Deploy without outbound email. The executor renders advisor handoffs but cannot send them."
|
|
20292
|
+
});
|
|
19750
20293
|
async executeCommand() {
|
|
19751
20294
|
if (!NAME_RE2.test(this.name)) {
|
|
19752
20295
|
throw new LocalCliError({
|
|
@@ -19796,12 +20339,8 @@ var AzureExecDeployCommand = class extends M8tCommand {
|
|
|
19796
20339
|
});
|
|
19797
20340
|
}
|
|
19798
20341
|
}
|
|
19799
|
-
if (this.enableEmail === true
|
|
19800
|
-
|
|
19801
|
-
code: "USAGE",
|
|
19802
|
-
message: "--enable-email requires --resource-group (the ACS resource lands in the Contributor-granted RG so the executor's identity can send).",
|
|
19803
|
-
hint: "Pass --resource-group <rg> (the same scope the executor is granted Contributor on)."
|
|
19804
|
-
});
|
|
20342
|
+
if (this.enableEmail === true) {
|
|
20343
|
+
warnings.add("--enable-email is a no-op now: outbound email is provisioned by default. Pass --no-email to opt out.");
|
|
19805
20344
|
}
|
|
19806
20345
|
const imageInput = this.image ?? DEFAULT_IMAGE2;
|
|
19807
20346
|
const repoRef = imageInput.includes("/") ? imageInput : `${DEFAULT_REGISTRY2}/${imageInput}`;
|
|
@@ -19843,20 +20382,62 @@ var AzureExecDeployCommand = class extends M8tCommand {
|
|
|
19843
20382
|
privateKeyPem,
|
|
19844
20383
|
write: (s) => this.context.stdout.write(s)
|
|
19845
20384
|
});
|
|
20385
|
+
const wantEmail = this.noEmail !== true;
|
|
19846
20386
|
let acsEnv = {};
|
|
19847
|
-
|
|
19848
|
-
|
|
19849
|
-
|
|
19850
|
-
const
|
|
19851
|
-
const
|
|
19852
|
-
|
|
20387
|
+
const stampRg = await this.resolvePlatformResourceGroup({ subscriptionId, interactive, warnings });
|
|
20388
|
+
const scopeClass = classifyGrantScope(grantScope, subscriptionId);
|
|
20389
|
+
if (wantEmail) {
|
|
20390
|
+
const suffix = platformSuffixFromKvUri(kvUri);
|
|
20391
|
+
const acsRg = scopeClass.kind === "resource-group" ? scopeClass.resourceGroup : scopeClass.kind === "subscription" ? stampRg : null;
|
|
20392
|
+
if (scopeClass.kind === "unusable") {
|
|
20393
|
+
warnings.add(`Outbound email was NOT provisioned: ${scopeClass.reason}. Re-run with --resource-group <rg> to place it explicitly.`);
|
|
20394
|
+
} else if (acsRg === null || suffix === null) {
|
|
20395
|
+
warnings.add(
|
|
20396
|
+
"Outbound email was NOT provisioned: could not work out which resource group it belongs in. Re-run with --resource-group <rg>, or turn it on later with `m8t platform email on`."
|
|
20397
|
+
);
|
|
20398
|
+
} else {
|
|
20399
|
+
try {
|
|
20400
|
+
onProgress?.("resolving the ACS email sender\u2026");
|
|
20401
|
+
const acsClient = new CommunicationServiceManagementClient(credential2, subscriptionId);
|
|
20402
|
+
const resolution = await resolveAcs({
|
|
20403
|
+
client: acsClient,
|
|
20404
|
+
stamp: stampRg === null ? null : await this.readStampQuietly({ credential: credential2, subscriptionId, resourceGroup: stampRg }),
|
|
20405
|
+
subscriptionId,
|
|
20406
|
+
resourceGroup: acsRg,
|
|
20407
|
+
suffix,
|
|
20408
|
+
dataLocation: "United States",
|
|
20409
|
+
onProgress
|
|
20410
|
+
});
|
|
20411
|
+
acsEnv = {
|
|
20412
|
+
M8T_ACS_ENDPOINT: resolution.acs.endpoint,
|
|
20413
|
+
M8T_ACS_SENDER: resolution.acs.sender,
|
|
20414
|
+
M8T_EMAIL_ENABLED: "true"
|
|
20415
|
+
};
|
|
20416
|
+
await this.recordEmailIntent({
|
|
20417
|
+
credential: credential2,
|
|
20418
|
+
subscriptionId,
|
|
20419
|
+
resourceGroup: stampRg,
|
|
20420
|
+
feature: emailFeatureFrom(resolution.acs, true),
|
|
20421
|
+
warnings,
|
|
20422
|
+
onProgress
|
|
20423
|
+
});
|
|
20424
|
+
} catch (e) {
|
|
20425
|
+
acsEnv = {};
|
|
20426
|
+
warnings.add(
|
|
20427
|
+
`Outbound email was NOT provisioned (${e instanceof Error ? e.message : String(e)}). The executor is deployed and usable; turn email on later with \`m8t platform email on\`.`
|
|
20428
|
+
);
|
|
20429
|
+
}
|
|
20430
|
+
}
|
|
20431
|
+
} else {
|
|
20432
|
+
onProgress?.("--no-email: skipping outbound email.");
|
|
20433
|
+
await this.recordEmailIntent({
|
|
20434
|
+
credential: credential2,
|
|
19853
20435
|
subscriptionId,
|
|
19854
|
-
resourceGroup,
|
|
19855
|
-
|
|
19856
|
-
|
|
20436
|
+
resourceGroup: stampRg,
|
|
20437
|
+
feature: { enabled: false, acsEndpoint: "", acsSender: "" },
|
|
20438
|
+
warnings,
|
|
19857
20439
|
onProgress
|
|
19858
20440
|
});
|
|
19859
|
-
acsEnv = { M8T_ACS_ENDPOINT: acs.endpoint, M8T_ACS_SENDER: acs.sender };
|
|
19860
20441
|
}
|
|
19861
20442
|
const env = {
|
|
19862
20443
|
MODEL_DEPLOYMENT_NAME: this.modelDeployment ?? DEFAULT_MODEL2,
|
|
@@ -19977,6 +20558,93 @@ var AzureExecDeployCommand = class extends M8tCommand {
|
|
|
19977
20558
|
);
|
|
19978
20559
|
return 0;
|
|
19979
20560
|
}
|
|
20561
|
+
/** Resolve the PLATFORM resource group — the one holding the stamp's storage account.
|
|
20562
|
+
*
|
|
20563
|
+
* Deliberately independent of the ACS resource group: they are often the same, but when
|
|
20564
|
+
* they are not, reading the stamp from the wrong one throws and would read as "no stamp".
|
|
20565
|
+
* Returns null (with a warning) rather than throwing, because a worker deploy must not
|
|
20566
|
+
* fail over the stamp. */
|
|
20567
|
+
async resolvePlatformResourceGroup(opts) {
|
|
20568
|
+
const disambiguator = typeof this.resourceGroup === "string" && this.resourceGroup.length > 0 ? this.resourceGroup : void 0;
|
|
20569
|
+
try {
|
|
20570
|
+
return resourceGroupFromArmId(
|
|
20571
|
+
(await discoverGateway({
|
|
20572
|
+
subscriptionId: opts.subscriptionId,
|
|
20573
|
+
interactive: opts.interactive,
|
|
20574
|
+
...disambiguator !== void 0 ? { resourceGroup: disambiguator } : {}
|
|
20575
|
+
})).containerAppResourceId
|
|
20576
|
+
);
|
|
20577
|
+
} catch (e) {
|
|
20578
|
+
if (e.code === "GATEWAY_DISCOVERY_RG_NOT_FOUND" && disambiguator !== void 0) {
|
|
20579
|
+
try {
|
|
20580
|
+
return resourceGroupFromArmId(
|
|
20581
|
+
(await discoverGateway({ subscriptionId: opts.subscriptionId, interactive: opts.interactive })).containerAppResourceId
|
|
20582
|
+
);
|
|
20583
|
+
} catch (retryErr) {
|
|
20584
|
+
opts.warnings.add(
|
|
20585
|
+
`Could not locate the platform resource group (${retryErr instanceof Error ? retryErr.message : String(retryErr)}), so the outbound-email decision could not be recorded. Run \`m8t platform email on|off\` once the platform resolves.`
|
|
20586
|
+
);
|
|
20587
|
+
return null;
|
|
20588
|
+
}
|
|
20589
|
+
}
|
|
20590
|
+
opts.warnings.add(
|
|
20591
|
+
`Could not locate the platform resource group (${e instanceof Error ? e.message : String(e)}), so the outbound-email decision could not be recorded. Run \`m8t platform email on|off\` once the platform resolves.`
|
|
20592
|
+
);
|
|
20593
|
+
return null;
|
|
20594
|
+
}
|
|
20595
|
+
}
|
|
20596
|
+
/** Read the platform stamp, distinguishing "genuinely absent" from "unreadable".
|
|
20597
|
+
*
|
|
20598
|
+
* 🔴 The distinction matters: on `absent` (a fresh install) falling through to ACS
|
|
20599
|
+
* discovery is correct, but on `unreadable` we cannot know whether an ACS is already on
|
|
20600
|
+
* record, and provisioning a duplicate billable resource on a failed read would be worse
|
|
20601
|
+
* than doing nothing. `unreadable` therefore throws, and the caller's catch turns it into
|
|
20602
|
+
* a loud skip. */
|
|
20603
|
+
async readStampQuietly(opts) {
|
|
20604
|
+
const outcome = await readStampOutcome(opts);
|
|
20605
|
+
if (outcome.source === "explicit") return outcome.stamp;
|
|
20606
|
+
if (outcome.source === "absent") return null;
|
|
20607
|
+
throw new Error(
|
|
20608
|
+
"the platform stamp exists but could not be read, so it is not safe to decide whether an ACS is already on record"
|
|
20609
|
+
);
|
|
20610
|
+
}
|
|
20611
|
+
/** Persist the outbound-email decision to the platform stamp.
|
|
20612
|
+
*
|
|
20613
|
+
* Recording intent is the durability fix: before this, email lived only in the argv of
|
|
20614
|
+
* one command. If the stamp cannot be read or written, the deploy still succeeds — but
|
|
20615
|
+
* it WARNS, because an intent that silently failed to persist is exactly the state that
|
|
20616
|
+
* hid this outage. */
|
|
20617
|
+
async recordEmailIntent(opts) {
|
|
20618
|
+
const { credential: credential2, subscriptionId, resourceGroup, feature, warnings, onProgress } = opts;
|
|
20619
|
+
if (resourceGroup === null) return;
|
|
20620
|
+
try {
|
|
20621
|
+
const current = await readStamp({ credential: credential2, subscriptionId, resourceGroup });
|
|
20622
|
+
if (current === null) {
|
|
20623
|
+
warnings.add(
|
|
20624
|
+
`Outbound email is ${feature.enabled ? "on" : "off"} for this executor, but there is no platform stamp to record it in \u2014 a later converge will not know about it. Run \`m8t platform email on\` once the stamp exists.`
|
|
20625
|
+
);
|
|
20626
|
+
return;
|
|
20627
|
+
}
|
|
20628
|
+
const prior = current.features?.email;
|
|
20629
|
+
const merged = {
|
|
20630
|
+
enabled: feature.enabled,
|
|
20631
|
+
acsEndpoint: feature.acsEndpoint || (prior?.acsEndpoint ?? ""),
|
|
20632
|
+
acsSender: feature.acsSender || (prior?.acsSender ?? "")
|
|
20633
|
+
};
|
|
20634
|
+
await writeStamp({
|
|
20635
|
+
credential: credential2,
|
|
20636
|
+
subscriptionId,
|
|
20637
|
+
resourceGroup,
|
|
20638
|
+
stamp: { ...current, features: { ...current.features, email: merged } },
|
|
20639
|
+
onProgress
|
|
20640
|
+
});
|
|
20641
|
+
onProgress?.(`recorded outbound email = ${merged.enabled ? "on" : "off"} in the platform stamp.`);
|
|
20642
|
+
} catch (e) {
|
|
20643
|
+
warnings.add(
|
|
20644
|
+
`Could not record the outbound-email decision in the platform stamp (${e instanceof Error ? e.message : String(e)}). The executor is wired correctly, but a later converge will not know the intent.`
|
|
20645
|
+
);
|
|
20646
|
+
}
|
|
20647
|
+
}
|
|
19980
20648
|
resolveScope(subscriptionId) {
|
|
19981
20649
|
if (typeof this.scope === "string" && this.scope.startsWith("/subscriptions/")) return this.scope;
|
|
19982
20650
|
if (typeof this.resourceGroup === "string" && this.resourceGroup.length > 0) {
|
|
@@ -20144,254 +20812,6 @@ async function fetchPublicTags(repo, fetchImpl = fetch) {
|
|
|
20144
20812
|
|
|
20145
20813
|
// src/lib/release-channel.ts
|
|
20146
20814
|
import * as fs23 from "fs";
|
|
20147
|
-
|
|
20148
|
-
// ../../packages/platform-release/dist/esm/manifest.js
|
|
20149
|
-
var SEVERITIES = ["critical", "recommended", "optional"];
|
|
20150
|
-
var IMAGE_KEYS = ["gateway", "codingAgent", "azureExecutor", "installer"];
|
|
20151
|
-
var COMPANION_TARGETS = [
|
|
20152
|
-
"darwin-arm64",
|
|
20153
|
-
"darwin-x64",
|
|
20154
|
-
"win32-arm64",
|
|
20155
|
-
"win32-x64"
|
|
20156
|
-
];
|
|
20157
|
-
function companionTargetFor(platform, architecture) {
|
|
20158
|
-
const target = `${platform}-${architecture}`;
|
|
20159
|
-
return COMPANION_TARGETS.includes(target) ? target : null;
|
|
20160
|
-
}
|
|
20161
|
-
function isObj2(v) {
|
|
20162
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
20163
|
-
}
|
|
20164
|
-
function validateManifest(m) {
|
|
20165
|
-
const errors = [];
|
|
20166
|
-
if (!isObj2(m))
|
|
20167
|
-
return ["manifest is not an object"];
|
|
20168
|
-
if (m.schemaVersion !== 1)
|
|
20169
|
-
errors.push("schemaVersion must be the integer 1");
|
|
20170
|
-
const p = m.platform;
|
|
20171
|
-
if (!isObj2(p)) {
|
|
20172
|
-
errors.push("platform is required");
|
|
20173
|
-
} else {
|
|
20174
|
-
for (const f of ["version", "tag", "releasedAt", "commit", "notes", "notesUrl"]) {
|
|
20175
|
-
if (typeof p[f] !== "string" || p[f].length === 0)
|
|
20176
|
-
errors.push(`platform.${f} is required`);
|
|
20177
|
-
}
|
|
20178
|
-
if (!SEVERITIES.includes(p.severity))
|
|
20179
|
-
errors.push(`platform.severity must be one of ${SEVERITIES.join("|")}`);
|
|
20180
|
-
if (!(typeof p.previousVersion === "string" || p.previousVersion === null)) {
|
|
20181
|
-
errors.push("platform.previousVersion must be a string or null");
|
|
20182
|
-
}
|
|
20183
|
-
}
|
|
20184
|
-
const c = m.components;
|
|
20185
|
-
if (!isObj2(c)) {
|
|
20186
|
-
errors.push("components is required");
|
|
20187
|
-
return errors;
|
|
20188
|
-
}
|
|
20189
|
-
for (const key2 of IMAGE_KEYS) {
|
|
20190
|
-
const img = c[key2];
|
|
20191
|
-
if (!isObj2(img)) {
|
|
20192
|
-
errors.push(`components.${key2} is required`);
|
|
20193
|
-
continue;
|
|
20194
|
-
}
|
|
20195
|
-
if (img.kind !== "image")
|
|
20196
|
-
errors.push(`components.${key2}.kind must be "image"`);
|
|
20197
|
-
for (const f of ["ref", "tag", "version"]) {
|
|
20198
|
-
if (typeof img[f] !== "string" || img[f].length === 0)
|
|
20199
|
-
errors.push(`components.${key2}.${f} is required`);
|
|
20200
|
-
}
|
|
20201
|
-
if (typeof img.digest !== "string" || !/^sha256:[0-9a-f]+$/.test(img.digest)) {
|
|
20202
|
-
errors.push(`components.${key2}.digest must be a sha256:\u2026 digest`);
|
|
20203
|
-
}
|
|
20204
|
-
}
|
|
20205
|
-
const cli2 = c.cli;
|
|
20206
|
-
if (!isObj2(cli2) || cli2.kind !== "npm") {
|
|
20207
|
-
errors.push("components.cli (kind: npm) is required");
|
|
20208
|
-
} else {
|
|
20209
|
-
for (const f of ["package", "version", "min", "recommended"]) {
|
|
20210
|
-
if (typeof cli2[f] !== "string" || cli2[f].length === 0)
|
|
20211
|
-
errors.push(`components.cli.${f} is required`);
|
|
20212
|
-
}
|
|
20213
|
-
}
|
|
20214
|
-
const personas = c.personas;
|
|
20215
|
-
if (!isObj2(personas) || !isObj2(personas.items)) {
|
|
20216
|
-
errors.push("components.personas.items is required");
|
|
20217
|
-
} else {
|
|
20218
|
-
for (const [name, item] of Object.entries(personas.items)) {
|
|
20219
|
-
if (!isObj2(item) || typeof item.path !== "string" || item.path.length === 0) {
|
|
20220
|
-
errors.push(`components.personas.items.${name}.path is required`);
|
|
20221
|
-
}
|
|
20222
|
-
if (!isObj2(item) || typeof item.treeSha !== "string" || item.treeSha.length < 4) {
|
|
20223
|
-
errors.push(`components.personas.items.${name}.treeSha is required`);
|
|
20224
|
-
}
|
|
20225
|
-
}
|
|
20226
|
-
}
|
|
20227
|
-
const seeds = c.brainSeeds;
|
|
20228
|
-
if (!isObj2(seeds) || !isObj2(seeds.items)) {
|
|
20229
|
-
errors.push("components.brainSeeds.items is required");
|
|
20230
|
-
} else {
|
|
20231
|
-
for (const [name, item] of Object.entries(seeds.items)) {
|
|
20232
|
-
if (!isObj2(item) || typeof item.path !== "string" || item.path.length === 0) {
|
|
20233
|
-
errors.push(`components.brainSeeds.items.${name}.path is required`);
|
|
20234
|
-
}
|
|
20235
|
-
if (!isObj2(item) || !isObj2(item.subtrees)) {
|
|
20236
|
-
errors.push(`components.brainSeeds.items.${name}.subtrees is required`);
|
|
20237
|
-
}
|
|
20238
|
-
}
|
|
20239
|
-
}
|
|
20240
|
-
const infra = c.infra;
|
|
20241
|
-
if (infra !== void 0) {
|
|
20242
|
-
if (!isObj2(infra) || infra.kind !== "infra") {
|
|
20243
|
-
errors.push('components.infra.kind must be "infra"');
|
|
20244
|
-
}
|
|
20245
|
-
if (!isObj2(infra) || typeof infra.treeSha !== "string" || infra.treeSha.length < 4) {
|
|
20246
|
-
errors.push("components.infra.treeSha is required");
|
|
20247
|
-
}
|
|
20248
|
-
}
|
|
20249
|
-
const companion = c.companion;
|
|
20250
|
-
if (companion !== void 0) {
|
|
20251
|
-
if (!isObj2(companion) || companion.kind !== "desktop") {
|
|
20252
|
-
errors.push('components.companion.kind must be "desktop"');
|
|
20253
|
-
} else {
|
|
20254
|
-
for (const f of ["version", "tag"]) {
|
|
20255
|
-
if (typeof companion[f] !== "string" || companion[f].length === 0) {
|
|
20256
|
-
errors.push(`components.companion.${f} is required`);
|
|
20257
|
-
}
|
|
20258
|
-
}
|
|
20259
|
-
if (!isObj2(companion.targets) || Object.keys(companion.targets).length === 0) {
|
|
20260
|
-
errors.push("components.companion.targets must name at least one target");
|
|
20261
|
-
} else {
|
|
20262
|
-
for (const [name, target] of Object.entries(companion.targets)) {
|
|
20263
|
-
if (!COMPANION_TARGETS.includes(name)) {
|
|
20264
|
-
errors.push(`components.companion.targets.${name} is not a known target`);
|
|
20265
|
-
continue;
|
|
20266
|
-
}
|
|
20267
|
-
if (!isObj2(target) || typeof target.asset !== "string" || target.asset.length === 0) {
|
|
20268
|
-
errors.push(`components.companion.targets.${name}.asset is required`);
|
|
20269
|
-
}
|
|
20270
|
-
if (!isObj2(target) || typeof target.sha256 !== "string" || !/^[0-9a-f]{64}$/.test(target.sha256)) {
|
|
20271
|
-
errors.push(`components.companion.targets.${name}.sha256 must be a sha256 hex digest`);
|
|
20272
|
-
}
|
|
20273
|
-
}
|
|
20274
|
-
}
|
|
20275
|
-
}
|
|
20276
|
-
}
|
|
20277
|
-
return errors;
|
|
20278
|
-
}
|
|
20279
|
-
|
|
20280
|
-
// ../../packages/platform-release/dist/esm/stamp.js
|
|
20281
|
-
var TABLE = "Metadata";
|
|
20282
|
-
var PK = "system";
|
|
20283
|
-
var RK = "platform";
|
|
20284
|
-
function stampToEntity(s) {
|
|
20285
|
-
return {
|
|
20286
|
-
partitionKey: PK,
|
|
20287
|
-
rowKey: RK,
|
|
20288
|
-
platformVersion: s.platformVersion,
|
|
20289
|
-
updatedAt: s.updatedAt,
|
|
20290
|
-
lastResult: s.lastResult,
|
|
20291
|
-
value: JSON.stringify(s)
|
|
20292
|
-
};
|
|
20293
|
-
}
|
|
20294
|
-
function entityToStamp(e) {
|
|
20295
|
-
const raw = e.value;
|
|
20296
|
-
if (typeof raw !== "string")
|
|
20297
|
-
return null;
|
|
20298
|
-
try {
|
|
20299
|
-
return JSON.parse(raw);
|
|
20300
|
-
} catch {
|
|
20301
|
-
return null;
|
|
20302
|
-
}
|
|
20303
|
-
}
|
|
20304
|
-
|
|
20305
|
-
// ../../packages/platform-release/dist/esm/channel-url.js
|
|
20306
|
-
var RELEASES_REPO = "m8t-labs/m8t-releases";
|
|
20307
|
-
var RELEASES_BASE = `https://github.com/${RELEASES_REPO}/releases`;
|
|
20308
|
-
var CHANNEL_LATEST_URL = `${RELEASES_BASE}/latest/download/manifest.json`;
|
|
20309
|
-
var CANARY_POINTER_TAG = "canary";
|
|
20310
|
-
var CHANNEL_CANARY_URL = `${RELEASES_BASE}/download/${CANARY_POINTER_TAG}/manifest.json`;
|
|
20311
|
-
function platformTag(version) {
|
|
20312
|
-
const bare = version.trim().replace(/^platform-/, "").replace(/^v/, "");
|
|
20313
|
-
return `platform-v${bare}`;
|
|
20314
|
-
}
|
|
20315
|
-
function channelUrlForVersion(version) {
|
|
20316
|
-
return `${RELEASES_BASE}/download/${platformTag(version)}/manifest.json`;
|
|
20317
|
-
}
|
|
20318
|
-
function releaseAssetUrl(tag, asset) {
|
|
20319
|
-
return `${RELEASES_BASE}/download/${tag}/${asset}`;
|
|
20320
|
-
}
|
|
20321
|
-
|
|
20322
|
-
// ../../packages/platform-release/dist/esm/canary-version.js
|
|
20323
|
-
var CANARY_IDENTIFIER = "canary";
|
|
20324
|
-
var STABLE_CORE = String.raw`\d+\.\d+\.\d+`;
|
|
20325
|
-
var CANARY_SUFFIX = String.raw`-${CANARY_IDENTIFIER}\.(?:0|[1-9]\d*)`;
|
|
20326
|
-
var CANARY_VERSION_PATTERN = `^${STABLE_CORE}${CANARY_SUFFIX}$`;
|
|
20327
|
-
var PLATFORM_VERSION_PATTERN = `^${STABLE_CORE}(?:${CANARY_SUFFIX})?$`;
|
|
20328
|
-
var PLATFORM_TAG_PATTERN = `^platform-v${STABLE_CORE}(?:${CANARY_SUFFIX})?$`;
|
|
20329
|
-
var CANARY_VERSION_RE = new RegExp(CANARY_VERSION_PATTERN);
|
|
20330
|
-
var STABLE_CORE_RE = new RegExp(`^${STABLE_CORE}$`);
|
|
20331
|
-
|
|
20332
|
-
// ../../packages/platform-release/dist/esm/apply-request.js
|
|
20333
|
-
var APPLY_REQUEST_PK = "system";
|
|
20334
|
-
var APPLY_REQUEST_RK = "apply-request";
|
|
20335
|
-
var IN_FLIGHT = ["claimed", "applying"];
|
|
20336
|
-
var LEASE_MS = 30 * 60 * 1e3;
|
|
20337
|
-
var MAX_LEASE_TAKEOVERS = 2;
|
|
20338
|
-
var MAX_HEALTHY_IN_FLIGHT_MS = LEASE_MS * (MAX_LEASE_TAKEOVERS + 1);
|
|
20339
|
-
function isInFlight(status) {
|
|
20340
|
-
return IN_FLIGHT.includes(status);
|
|
20341
|
-
}
|
|
20342
|
-
var BLOCKS_NEW_INTENT = ["pending", "claimed", "applying", "awaiting-engine-update"];
|
|
20343
|
-
function blocksNewIntent(status) {
|
|
20344
|
-
return BLOCKS_NEW_INTENT.includes(status);
|
|
20345
|
-
}
|
|
20346
|
-
function newIntent(target, provenance, nowIso) {
|
|
20347
|
-
return {
|
|
20348
|
-
schemaVersion: 1,
|
|
20349
|
-
target,
|
|
20350
|
-
provenance,
|
|
20351
|
-
status: "pending",
|
|
20352
|
-
phase: null,
|
|
20353
|
-
claimedBy: null,
|
|
20354
|
-
leaseUntil: null,
|
|
20355
|
-
attempt: 0,
|
|
20356
|
-
breaker: "none",
|
|
20357
|
-
result: null,
|
|
20358
|
-
createdAt: nowIso,
|
|
20359
|
-
updatedAt: nowIso
|
|
20360
|
-
};
|
|
20361
|
-
}
|
|
20362
|
-
function applyRequestToEntity(r) {
|
|
20363
|
-
return { partitionKey: APPLY_REQUEST_PK, rowKey: APPLY_REQUEST_RK, value: JSON.stringify(r), updatedAt: r.updatedAt };
|
|
20364
|
-
}
|
|
20365
|
-
function entityToApplyRequest(e) {
|
|
20366
|
-
return JSON.parse(e.value);
|
|
20367
|
-
}
|
|
20368
|
-
|
|
20369
|
-
// ../../packages/platform-release/dist/esm/update-policy.js
|
|
20370
|
-
var DEFAULT_POLICY_MODE = "auto-critical";
|
|
20371
|
-
var UPDATE_POLICY_PK = "system";
|
|
20372
|
-
var UPDATE_POLICY_RK = "update-policy";
|
|
20373
|
-
var MODES = ["notify-only", "auto-critical", "auto-all"];
|
|
20374
|
-
function isPolicyMode(x) {
|
|
20375
|
-
return typeof x === "string" && MODES.includes(x);
|
|
20376
|
-
}
|
|
20377
|
-
function updatePolicyToEntity(p) {
|
|
20378
|
-
return { partitionKey: UPDATE_POLICY_PK, rowKey: UPDATE_POLICY_RK, value: JSON.stringify(p) };
|
|
20379
|
-
}
|
|
20380
|
-
function entityToUpdatePolicy(e) {
|
|
20381
|
-
return JSON.parse(e.value);
|
|
20382
|
-
}
|
|
20383
|
-
|
|
20384
|
-
// ../../packages/platform-release/dist/esm/infra-params.js
|
|
20385
|
-
var INFRA_PARAMS_PK = "system";
|
|
20386
|
-
var INFRA_PARAMS_RK = "infra-params";
|
|
20387
|
-
function infraParamsToEntity(p) {
|
|
20388
|
-
return { partitionKey: INFRA_PARAMS_PK, rowKey: INFRA_PARAMS_RK, value: JSON.stringify(p) };
|
|
20389
|
-
}
|
|
20390
|
-
function entityToInfraParams(e) {
|
|
20391
|
-
return JSON.parse(e.value);
|
|
20392
|
-
}
|
|
20393
|
-
|
|
20394
|
-
// src/lib/release-channel.ts
|
|
20395
20815
|
init_errors();
|
|
20396
20816
|
function manifestSourceForVersionTag(channelUrl, version) {
|
|
20397
20817
|
const tag = platformTag(version);
|
|
@@ -20551,153 +20971,6 @@ async function resolveReleaseContentWithAgents(manifest, opts = {}) {
|
|
|
20551
20971
|
return contentDir;
|
|
20552
20972
|
}
|
|
20553
20973
|
|
|
20554
|
-
// src/lib/platform-stamp.ts
|
|
20555
|
-
import { TableClient as TableClient2 } from "@azure/data-tables";
|
|
20556
|
-
|
|
20557
|
-
// src/lib/platform-storage-discovery.ts
|
|
20558
|
-
init_http();
|
|
20559
|
-
init_errors();
|
|
20560
|
-
var ARM3 = "https://management.azure.com";
|
|
20561
|
-
var ARM_SCOPE7 = "https://management.azure.com/.default";
|
|
20562
|
-
var STORAGE_API = "2023-05-01";
|
|
20563
|
-
async function discoverStampStorage(opts) {
|
|
20564
|
-
const list = await authedJson({
|
|
20565
|
-
credential: opts.credential,
|
|
20566
|
-
scope: ARM_SCOPE7,
|
|
20567
|
-
method: "GET",
|
|
20568
|
-
url: `${ARM3}/subscriptions/${opts.subscriptionId}/resourceGroups/${opts.resourceGroup}/providers/Microsoft.Storage/storageAccounts?api-version=${STORAGE_API}`
|
|
20569
|
-
}) ?? {};
|
|
20570
|
-
const accounts = (list.value ?? []).filter((a) => a.properties?.primaryEndpoints?.table);
|
|
20571
|
-
const chosen = accounts.find((a) => a.tags?.m8t === "storage") ?? accounts.find((a) => a.tags?.["m8t:role"] === "gateway" || a.tags?.["m8t:role"] === "ledger") ?? (accounts.length === 1 ? accounts[0] : void 0);
|
|
20572
|
-
const tableEndpoint = chosen?.properties?.primaryEndpoints?.table?.replace(/\/$/, "");
|
|
20573
|
-
if (!tableEndpoint || !chosen?.id || !chosen.name) {
|
|
20574
|
-
throw new LocalCliError({
|
|
20575
|
-
code: "PLATFORM_STAMP_STORAGE_NOT_FOUND",
|
|
20576
|
-
message: `No table-capable storage account found in resource group ${opts.resourceGroup}.`,
|
|
20577
|
-
hint: `Found: ${accounts.map((a) => a.name).join(", ") || "(none)"}.`
|
|
20578
|
-
});
|
|
20579
|
-
}
|
|
20580
|
-
return { tableEndpoint, accountResourceId: chosen.id, accountName: chosen.name };
|
|
20581
|
-
}
|
|
20582
|
-
|
|
20583
|
-
// src/lib/metadata-table-write.ts
|
|
20584
|
-
import { TableClient, AzureNamedKeyCredential } from "@azure/data-tables";
|
|
20585
|
-
init_errors();
|
|
20586
|
-
init_rbac();
|
|
20587
|
-
function is403(e) {
|
|
20588
|
-
return e.statusCode === 403;
|
|
20589
|
-
}
|
|
20590
|
-
function is404(e) {
|
|
20591
|
-
return e.statusCode === 404;
|
|
20592
|
-
}
|
|
20593
|
-
function sleep2(ms) {
|
|
20594
|
-
return new Promise((r) => setTimeout(r, ms));
|
|
20595
|
-
}
|
|
20596
|
-
async function makeSharedKeyClient(opts) {
|
|
20597
|
-
const rg = /\/resourceGroups\/([^/]+)\//i.exec(opts.accountResourceId)?.[1];
|
|
20598
|
-
if (!rg) return null;
|
|
20599
|
-
const key2 = (await runAz(["storage", "account", "keys", "list", "--account-name", opts.accountName, "-g", rg, "--query", "[0].value", "-o", "tsv"])).trim();
|
|
20600
|
-
if (!key2) return null;
|
|
20601
|
-
return new TableClient(opts.tableEndpoint, TABLE, new AzureNamedKeyCredential(opts.accountName, key2));
|
|
20602
|
-
}
|
|
20603
|
-
async function upsertMetadataEntity(opts) {
|
|
20604
|
-
const discover = opts.discoverImpl ?? discoverStampStorage;
|
|
20605
|
-
const makeAad = opts.aadClientImpl ?? ((endpoint, credential2) => new TableClient(endpoint, TABLE, credential2));
|
|
20606
|
-
const makeShared = opts.sharedClientImpl ?? makeSharedKeyClient;
|
|
20607
|
-
const nap = opts.sleepImpl ?? sleep2;
|
|
20608
|
-
const { tableEndpoint, accountResourceId, accountName } = await discover({
|
|
20609
|
-
credential: opts.credential,
|
|
20610
|
-
subscriptionId: opts.subscriptionId,
|
|
20611
|
-
resourceGroup: opts.resourceGroup
|
|
20612
|
-
});
|
|
20613
|
-
const aad = makeAad(tableEndpoint, opts.credential);
|
|
20614
|
-
await aad.createTable().catch(() => void 0);
|
|
20615
|
-
try {
|
|
20616
|
-
await aad.upsertEntity(opts.entity, "Replace");
|
|
20617
|
-
return;
|
|
20618
|
-
} catch (e) {
|
|
20619
|
-
if (!is403(e)) throw e;
|
|
20620
|
-
}
|
|
20621
|
-
opts.onProgress?.(`writing the ${opts.label} with the account key \u2014 your identity lacks the Storage Table data role.`);
|
|
20622
|
-
const shared = await makeShared({ tableEndpoint, accountResourceId, accountName });
|
|
20623
|
-
if (shared) {
|
|
20624
|
-
await shared.createTable().catch(() => void 0);
|
|
20625
|
-
await shared.upsertEntity(opts.entity, "Replace");
|
|
20626
|
-
return;
|
|
20627
|
-
}
|
|
20628
|
-
opts.onProgress?.("shared-key access is disabled \u2014 granting Storage Table Data Contributor to your identity (one-time)\u2026");
|
|
20629
|
-
try {
|
|
20630
|
-
const oid = await getCallerObjectId();
|
|
20631
|
-
await grantStorageTableDataContributor({
|
|
20632
|
-
credential: opts.credential,
|
|
20633
|
-
subscriptionId: opts.subscriptionId,
|
|
20634
|
-
scope: accountResourceId,
|
|
20635
|
-
principalId: oid
|
|
20636
|
-
});
|
|
20637
|
-
} catch (e) {
|
|
20638
|
-
throw new LocalCliError({
|
|
20639
|
-
code: opts.errorCode,
|
|
20640
|
-
message: `Cannot write the ${opts.label}: AAD lacks the Storage Table data role for ${accountName}, shared-key access is disabled, and self-granting the role failed.`,
|
|
20641
|
-
hint: "Grant yourself 'Storage Table Data Contributor' on the storage account and retry.",
|
|
20642
|
-
cause: e
|
|
20643
|
-
});
|
|
20644
|
-
}
|
|
20645
|
-
for (let i = 0; i < 6; i++) {
|
|
20646
|
-
await nap(1e4);
|
|
20647
|
-
try {
|
|
20648
|
-
await aad.upsertEntity(opts.entity, "Replace");
|
|
20649
|
-
return;
|
|
20650
|
-
} catch (e) {
|
|
20651
|
-
if (!is403(e)) throw e;
|
|
20652
|
-
}
|
|
20653
|
-
}
|
|
20654
|
-
throw new LocalCliError({
|
|
20655
|
-
code: opts.errorCode,
|
|
20656
|
-
message: `Cannot write the ${opts.label}: granted 'Storage Table Data Contributor' on ${accountName}, but the data-plane role has not propagated yet.`,
|
|
20657
|
-
hint: opts.retryHint
|
|
20658
|
-
});
|
|
20659
|
-
}
|
|
20660
|
-
|
|
20661
|
-
// src/lib/platform-stamp.ts
|
|
20662
|
-
async function writeStamp(opts) {
|
|
20663
|
-
const entity = stampToEntity(opts.stamp);
|
|
20664
|
-
await upsertMetadataEntity({
|
|
20665
|
-
credential: opts.credential,
|
|
20666
|
-
subscriptionId: opts.subscriptionId,
|
|
20667
|
-
resourceGroup: opts.resourceGroup,
|
|
20668
|
-
entity,
|
|
20669
|
-
label: "platform stamp",
|
|
20670
|
-
errorCode: "PLATFORM_STAMP_NO_ACCESS",
|
|
20671
|
-
retryHint: "The role assignment persists \u2014 retry 'm8t platform update' in a few minutes.",
|
|
20672
|
-
...opts.onProgress ? { onProgress: opts.onProgress } : {}
|
|
20673
|
-
});
|
|
20674
|
-
}
|
|
20675
|
-
async function readStampOutcome(opts) {
|
|
20676
|
-
const { tableEndpoint, accountResourceId, accountName } = await discoverStampStorage(opts);
|
|
20677
|
-
const aad = new TableClient2(tableEndpoint, TABLE, opts.credential);
|
|
20678
|
-
try {
|
|
20679
|
-
const row = await aad.getEntity(PK, RK);
|
|
20680
|
-
const stamp = entityToStamp(row);
|
|
20681
|
-
return stamp ? { source: "explicit", stamp } : { source: "unreadable" };
|
|
20682
|
-
} catch (e) {
|
|
20683
|
-
if (is404(e)) return { source: "absent" };
|
|
20684
|
-
if (!is403(e)) return { source: "unreadable" };
|
|
20685
|
-
}
|
|
20686
|
-
try {
|
|
20687
|
-
const shared = await makeSharedKeyClient({ tableEndpoint, accountResourceId, accountName });
|
|
20688
|
-
if (!shared) return { source: "unreadable" };
|
|
20689
|
-
const row = await shared.getEntity(PK, RK);
|
|
20690
|
-
const stamp = entityToStamp(row);
|
|
20691
|
-
return stamp ? { source: "explicit", stamp } : { source: "unreadable" };
|
|
20692
|
-
} catch {
|
|
20693
|
-
return { source: "unreadable" };
|
|
20694
|
-
}
|
|
20695
|
-
}
|
|
20696
|
-
async function readStamp(opts) {
|
|
20697
|
-
const result2 = await readStampOutcome(opts);
|
|
20698
|
-
return result2.source === "explicit" ? result2.stamp : null;
|
|
20699
|
-
}
|
|
20700
|
-
|
|
20701
20974
|
// src/lib/platform-converge.ts
|
|
20702
20975
|
init_esm();
|
|
20703
20976
|
import * as fs26 from "fs";
|
|
@@ -21115,6 +21388,9 @@ async function applyPlan(plan, deps, ctx) {
|
|
|
21115
21388
|
stamp.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
21116
21389
|
await deps.writeStamp(stamp);
|
|
21117
21390
|
}
|
|
21391
|
+
if (deps.reconcileEmail) {
|
|
21392
|
+
await deps.reconcileEmail();
|
|
21393
|
+
}
|
|
21118
21394
|
await deps.healthGate(ctx, applied);
|
|
21119
21395
|
stamp.lastResult = "success";
|
|
21120
21396
|
delete stamp.lastError;
|
|
@@ -21594,6 +21870,73 @@ import { DefaultAzureCredential as DefaultAzureCredential17 } from "@azure/ident
|
|
|
21594
21870
|
// src/lib/platform-converge-cli.ts
|
|
21595
21871
|
import * as path31 from "path";
|
|
21596
21872
|
|
|
21873
|
+
// src/lib/executor-email-env.ts
|
|
21874
|
+
init_foundry_agent_get();
|
|
21875
|
+
init_foundry_agent_version();
|
|
21876
|
+
var EMAIL_ENV_KEYS = ["M8T_ACS_ENDPOINT", "M8T_ACS_SENDER", "M8T_EMAIL_ENABLED"];
|
|
21877
|
+
var FOUNDRY_HOSTED_HEADER = { "Foundry-Features": "HostedAgents=V1Preview" };
|
|
21878
|
+
function applyEmailEnv(def, feature) {
|
|
21879
|
+
const live = def.environment_variables ?? {};
|
|
21880
|
+
const carried = Object.fromEntries(
|
|
21881
|
+
Object.entries(live).filter(([k]) => !EMAIL_ENV_KEYS.includes(k))
|
|
21882
|
+
);
|
|
21883
|
+
if (feature !== null && isEmailFeatureUsable(feature)) {
|
|
21884
|
+
return {
|
|
21885
|
+
...def,
|
|
21886
|
+
environment_variables: {
|
|
21887
|
+
...carried,
|
|
21888
|
+
M8T_ACS_ENDPOINT: feature.acsEndpoint,
|
|
21889
|
+
M8T_ACS_SENDER: feature.acsSender,
|
|
21890
|
+
M8T_EMAIL_ENABLED: "true"
|
|
21891
|
+
}
|
|
21892
|
+
};
|
|
21893
|
+
}
|
|
21894
|
+
return { ...def, environment_variables: carried };
|
|
21895
|
+
}
|
|
21896
|
+
async function reconcileExecutorEmailEnv(args) {
|
|
21897
|
+
const { credential: credential2, projectEndpoint, agentName, feature, onProgress } = args;
|
|
21898
|
+
let current;
|
|
21899
|
+
try {
|
|
21900
|
+
current = await getAgentVersion({ credential: credential2, projectEndpoint, agentName });
|
|
21901
|
+
} catch (e) {
|
|
21902
|
+
const code = e.code;
|
|
21903
|
+
if (code === "AGENT_NOT_FOUND") return { changed: false, absent: true };
|
|
21904
|
+
return { changed: false, absent: false, unreadable: e instanceof Error ? e.message : String(e) };
|
|
21905
|
+
}
|
|
21906
|
+
const next = applyEmailEnv(current.definition, feature);
|
|
21907
|
+
const before = current.definition.environment_variables ?? {};
|
|
21908
|
+
const after = next.environment_variables;
|
|
21909
|
+
if (EMAIL_ENV_KEYS.every((k) => before[k] === after[k])) {
|
|
21910
|
+
return { changed: false, absent: false };
|
|
21911
|
+
}
|
|
21912
|
+
onProgress?.(`reconciling outbound-email env on ${agentName}\u2026`);
|
|
21913
|
+
const version = await createAgentVersion({
|
|
21914
|
+
credential: credential2,
|
|
21915
|
+
projectEndpoint,
|
|
21916
|
+
agentName,
|
|
21917
|
+
definition: next,
|
|
21918
|
+
// Carry the live metadata through unchanged: it holds the a2a card and brain markers,
|
|
21919
|
+
// and dropping those has bitten this repo before.
|
|
21920
|
+
metadata: current.metadata ?? {},
|
|
21921
|
+
extraHeaders: FOUNDRY_HOSTED_HEADER
|
|
21922
|
+
});
|
|
21923
|
+
return { changed: true, absent: false, version };
|
|
21924
|
+
}
|
|
21925
|
+
var EXECUTOR_PERSONA = "azure-executor";
|
|
21926
|
+
var EXECUTOR_AGENT_FALLBACK = "ezra-executor";
|
|
21927
|
+
async function resolveExecutorAgentName(args) {
|
|
21928
|
+
if (typeof args.override === "string" && args.override.length > 0) {
|
|
21929
|
+
return { agentName: args.override, provenance: "override" };
|
|
21930
|
+
}
|
|
21931
|
+
try {
|
|
21932
|
+
const agents = await listM8tAgents({ credential: args.credential, projectEndpoint: args.projectEndpoint });
|
|
21933
|
+
const hit = agents.find((a) => a.metadata.persona === EXECUTOR_PERSONA);
|
|
21934
|
+
if (hit) return { agentName: hit.name, provenance: "discovered" };
|
|
21935
|
+
} catch {
|
|
21936
|
+
}
|
|
21937
|
+
return { agentName: EXECUTOR_AGENT_FALLBACK, provenance: "fallback" };
|
|
21938
|
+
}
|
|
21939
|
+
|
|
21597
21940
|
// src/lib/platform-infra-params.ts
|
|
21598
21941
|
import { TableClient as TableClient3 } from "@azure/data-tables";
|
|
21599
21942
|
async function openInfraParamsTable(opts) {
|
|
@@ -22071,6 +22414,37 @@ async function buildConvergeDeps(args) {
|
|
|
22071
22414
|
async applyGateway(a, ctx) {
|
|
22072
22415
|
return applyGatewayImage(a, ctx, args.gatewayResourceId);
|
|
22073
22416
|
},
|
|
22417
|
+
async reconcileEmail() {
|
|
22418
|
+
const dir2 = HOSTED_AGENT_PERSONA_DIR.azureExecutor;
|
|
22419
|
+
const agentName = dir2 ? discovered[dir2] : void 0;
|
|
22420
|
+
if (!agentName) return;
|
|
22421
|
+
const outcome = await readStampOutcome({
|
|
22422
|
+
credential: args.credential,
|
|
22423
|
+
subscriptionId: args.subscriptionId,
|
|
22424
|
+
resourceGroup: args.resourceGroup
|
|
22425
|
+
}).catch(() => ({ source: "unreadable" }));
|
|
22426
|
+
if (outcome.source === "unreadable") {
|
|
22427
|
+
warn("skipped the outbound-email reconcile: the platform stamp could not be read, so the recorded intent is unknown.");
|
|
22428
|
+
return;
|
|
22429
|
+
}
|
|
22430
|
+
const feature = outcome.source === "explicit" ? outcome.stamp.features?.email ?? null : null;
|
|
22431
|
+
try {
|
|
22432
|
+
const r = await reconcileExecutorEmailEnv({
|
|
22433
|
+
credential: args.credential,
|
|
22434
|
+
projectEndpoint: args.project.endpoint,
|
|
22435
|
+
agentName,
|
|
22436
|
+
feature,
|
|
22437
|
+
onProgress: args.onProgress
|
|
22438
|
+
});
|
|
22439
|
+
if (r.unreadable !== void 0) {
|
|
22440
|
+
warn(`could not read ${agentName} to reconcile outbound email (${r.unreadable}) \u2014 left untouched.`);
|
|
22441
|
+
} else if (r.changed) {
|
|
22442
|
+
args.onProgress?.(`outbound-email env reconciled on ${agentName} (version ${r.version ?? "?"}).`);
|
|
22443
|
+
}
|
|
22444
|
+
} catch (e) {
|
|
22445
|
+
warn(`could not reconcile outbound-email env on ${agentName}: ${e instanceof Error ? e.message : String(e)}`);
|
|
22446
|
+
}
|
|
22447
|
+
},
|
|
22074
22448
|
async applyInfra(a, ctx) {
|
|
22075
22449
|
const target = infraTargetSha(ctx.manifest, args.tree);
|
|
22076
22450
|
if (a.reason !== "forced") return applyInfraDeploy(a, ctx, target);
|
|
@@ -22335,6 +22709,27 @@ var PlatformUpdateCommand = class extends M8tCommand {
|
|
|
22335
22709
|
return 0;
|
|
22336
22710
|
}
|
|
22337
22711
|
if (plan.actions.length === 0) {
|
|
22712
|
+
if (only === void 0 || only === "azureExecutor") {
|
|
22713
|
+
try {
|
|
22714
|
+
const noDriftDeps = await buildConvergeDeps({
|
|
22715
|
+
credential: credential2,
|
|
22716
|
+
subscriptionId,
|
|
22717
|
+
resourceGroup,
|
|
22718
|
+
contentDir,
|
|
22719
|
+
manifest,
|
|
22720
|
+
tree,
|
|
22721
|
+
project,
|
|
22722
|
+
gatewayResourceId: gw.containerAppResourceId,
|
|
22723
|
+
gatewayUrl: gw.gatewayUrl,
|
|
22724
|
+
suffix,
|
|
22725
|
+
onProgress,
|
|
22726
|
+
onWarn
|
|
22727
|
+
});
|
|
22728
|
+
if (noDriftDeps.reconcileEmail) await noDriftDeps.reconcileEmail();
|
|
22729
|
+
} catch (e) {
|
|
22730
|
+
onWarn(`could not reconcile outbound email: ${e instanceof Error ? e.message : String(e)}`);
|
|
22731
|
+
}
|
|
22732
|
+
}
|
|
22338
22733
|
if (mode === "json") this.context.stdout.write(renderJson({ ...plan, applied: false }) + "\n");
|
|
22339
22734
|
else log(colors.success("already up to date \u2713"));
|
|
22340
22735
|
return 0;
|
|
@@ -22741,9 +23136,9 @@ async function readBreaker(client) {
|
|
|
22741
23136
|
|
|
22742
23137
|
// src/commands/platform/converge.ts
|
|
22743
23138
|
init_errors();
|
|
22744
|
-
function isForwardTarget(target,
|
|
22745
|
-
if (!
|
|
22746
|
-
return comparePlatformVersion(target,
|
|
23139
|
+
function isForwardTarget(target, installed2) {
|
|
23140
|
+
if (!installed2) return true;
|
|
23141
|
+
return comparePlatformVersion(target, installed2) >= 0;
|
|
22747
23142
|
}
|
|
22748
23143
|
async function failIfStillInFlight(client, now, error, opts = {}) {
|
|
22749
23144
|
const cur = await readApplyRequest(client);
|
|
@@ -22812,15 +23207,15 @@ var PlatformConvergeCommand = class extends M8tCommand {
|
|
|
22812
23207
|
subscriptionId: ctx.subscriptionId,
|
|
22813
23208
|
resourceGroup: ctx.resourceGroup
|
|
22814
23209
|
});
|
|
22815
|
-
const
|
|
22816
|
-
if (!isForwardTarget(claimed.target,
|
|
23210
|
+
const installed2 = stamp ? stamp.platformVersion : null;
|
|
23211
|
+
if (!isForwardTarget(claimed.target, installed2)) {
|
|
22817
23212
|
await patchApplyRequest(client, (r) => ({
|
|
22818
23213
|
...r,
|
|
22819
23214
|
status: "failed",
|
|
22820
|
-
result: { appliedVersion: null, error: `refused downgrade ${
|
|
23215
|
+
result: { appliedVersion: null, error: `refused downgrade ${installed2 ?? "(none)"} \u2192 ${claimed.target}` },
|
|
22821
23216
|
updatedAt: now()
|
|
22822
23217
|
}));
|
|
22823
|
-
log(`refused: target ${claimed.target} is a downgrade from installed ${
|
|
23218
|
+
log(`refused: target ${claimed.target} is a downgrade from installed ${installed2 ?? "(none)"}.`);
|
|
22824
23219
|
return 1;
|
|
22825
23220
|
}
|
|
22826
23221
|
const manifest = await fetchManifest(targetManifestSource(ctx, claimed.target));
|
|
@@ -23669,46 +24064,222 @@ var PlatformEnableCostReportCommand = class extends M8tCommand {
|
|
|
23669
24064
|
}
|
|
23670
24065
|
};
|
|
23671
24066
|
|
|
23672
|
-
// src/commands/platform/
|
|
24067
|
+
// src/commands/platform/email.ts
|
|
23673
24068
|
import { Command as Command42, Option as Option39 } from "clipanion";
|
|
23674
|
-
import { confirm as confirm6 } from "@inquirer/prompts";
|
|
23675
24069
|
import { DefaultAzureCredential as DefaultAzureCredential22 } from "@azure/identity";
|
|
24070
|
+
import { CommunicationServiceManagementClient as CommunicationServiceManagementClient2 } from "@azure/arm-communication";
|
|
24071
|
+
init_errors();
|
|
24072
|
+
var PlatformEmailCommand = class extends M8tCommand {
|
|
24073
|
+
static paths = [["platform", "email"]];
|
|
24074
|
+
static usage = Command42.Usage({
|
|
24075
|
+
description: "Turn outbound email (advisor handoffs) on or off for this install.",
|
|
24076
|
+
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.",
|
|
24077
|
+
examples: [
|
|
24078
|
+
["Turn outbound email on", "m8t platform email on"],
|
|
24079
|
+
["Turn it off (a shared, public-facing deployment should stay off)", "m8t platform email off"]
|
|
24080
|
+
]
|
|
24081
|
+
});
|
|
24082
|
+
state = Option39.String({ required: true, name: "on|off" });
|
|
24083
|
+
subscription = Option39.String("--subscription");
|
|
24084
|
+
resourceGroup = Option39.String("--resource-group", {
|
|
24085
|
+
description: "m8t resource group, to disambiguate in a multi-deployment subscription."
|
|
24086
|
+
});
|
|
24087
|
+
agent = Option39.String("--agent", { description: `Executor agent name. Resolved by persona when omitted (usually ${EXECUTOR_AGENT_FALLBACK}).` });
|
|
24088
|
+
kvUri = Option39.String("--kv-uri", {
|
|
24089
|
+
description: "Install's Key Vault URI. Only needed if no ACS is on record yet and one must be provisioned."
|
|
24090
|
+
});
|
|
24091
|
+
endpoint = Option39.String("--endpoint", {
|
|
24092
|
+
description: "Foundry project endpoint, to disambiguate a subscription holding several."
|
|
24093
|
+
});
|
|
24094
|
+
output = Option39.String("--output");
|
|
24095
|
+
async executeCommand() {
|
|
24096
|
+
const wanted = this.state.trim().toLowerCase();
|
|
24097
|
+
if (wanted !== "on" && wanted !== "off") {
|
|
24098
|
+
throw new LocalCliError({
|
|
24099
|
+
code: "USAGE",
|
|
24100
|
+
message: `Expected 'on' or 'off', got '${this.state}'.`,
|
|
24101
|
+
hint: "Run `m8t platform email on` or `m8t platform email off`."
|
|
24102
|
+
});
|
|
24103
|
+
}
|
|
24104
|
+
const enable = wanted === "on";
|
|
24105
|
+
const mode = resolveOutputMode(
|
|
24106
|
+
this.output,
|
|
24107
|
+
this.context.stdout
|
|
24108
|
+
);
|
|
24109
|
+
const onProgress = mode === "pretty" ? (m) => this.context.stderr.write(` ${colors.dim(m)}
|
|
24110
|
+
`) : void 0;
|
|
24111
|
+
const ctx = await resolveGatewayContext({
|
|
24112
|
+
interactive: mode !== "json",
|
|
24113
|
+
subscriptionId: this.subscription,
|
|
24114
|
+
resourceGroup: this.resourceGroup
|
|
24115
|
+
});
|
|
24116
|
+
const { resourceGroup } = parseContainerAppResourceId(ctx.containerAppResourceId);
|
|
24117
|
+
const subscriptionId = ctx.subscriptionId;
|
|
24118
|
+
const credential2 = new DefaultAzureCredential22();
|
|
24119
|
+
const stamp = await readStamp({ credential: credential2, subscriptionId, resourceGroup });
|
|
24120
|
+
if (stamp === null) {
|
|
24121
|
+
throw new LocalCliError({
|
|
24122
|
+
code: "NO_PLATFORM_STAMP",
|
|
24123
|
+
message: `No platform stamp found in ${resourceGroup} \u2014 there is nowhere to record the decision.`,
|
|
24124
|
+
hint: "This command needs an install written by the current CLI. Check you are pointed at the right deployment with `m8t status`."
|
|
24125
|
+
});
|
|
24126
|
+
}
|
|
24127
|
+
let feature;
|
|
24128
|
+
if (enable) {
|
|
24129
|
+
const suffix = this.resolveSuffix();
|
|
24130
|
+
const resolution = await resolveAcs({
|
|
24131
|
+
client: new CommunicationServiceManagementClient2(credential2, subscriptionId),
|
|
24132
|
+
stamp,
|
|
24133
|
+
subscriptionId,
|
|
24134
|
+
resourceGroup,
|
|
24135
|
+
suffix,
|
|
24136
|
+
dataLocation: "United States",
|
|
24137
|
+
onProgress
|
|
24138
|
+
});
|
|
24139
|
+
onProgress?.(`sender: ${resolution.acs.sender} (${resolution.source}).`);
|
|
24140
|
+
feature = emailFeatureFrom(resolution.acs, true);
|
|
24141
|
+
} else {
|
|
24142
|
+
const prior = stamp.features?.email;
|
|
24143
|
+
feature = { enabled: false, acsEndpoint: prior?.acsEndpoint ?? "", acsSender: prior?.acsSender ?? "" };
|
|
24144
|
+
}
|
|
24145
|
+
await writeStamp({
|
|
24146
|
+
credential: credential2,
|
|
24147
|
+
subscriptionId,
|
|
24148
|
+
resourceGroup,
|
|
24149
|
+
stamp: { ...stamp, features: { ...stamp.features, email: feature } },
|
|
24150
|
+
onProgress
|
|
24151
|
+
});
|
|
24152
|
+
const project = await resolveFoundryProject({
|
|
24153
|
+
credential: credential2,
|
|
24154
|
+
subscriptionId,
|
|
24155
|
+
interactive: mode !== "json",
|
|
24156
|
+
...typeof this.endpoint === "string" && this.endpoint.length > 0 ? { endpoint: this.endpoint } : {},
|
|
24157
|
+
...typeof this.resourceGroup === "string" && this.resourceGroup.length > 0 ? { resourceGroup: this.resourceGroup } : {}
|
|
24158
|
+
});
|
|
24159
|
+
const { agentName, provenance } = await resolveExecutorAgentName({
|
|
24160
|
+
credential: credential2,
|
|
24161
|
+
projectEndpoint: project.endpoint,
|
|
24162
|
+
...typeof this.agent === "string" && this.agent.length > 0 ? { override: this.agent } : {}
|
|
24163
|
+
});
|
|
24164
|
+
const reconciled = await reconcileExecutorEmailEnv({
|
|
24165
|
+
credential: credential2,
|
|
24166
|
+
projectEndpoint: project.endpoint,
|
|
24167
|
+
agentName,
|
|
24168
|
+
feature,
|
|
24169
|
+
onProgress
|
|
24170
|
+
});
|
|
24171
|
+
if (mode === "json") {
|
|
24172
|
+
this.context.stdout.write(
|
|
24173
|
+
renderJson({
|
|
24174
|
+
email: enable ? "on" : "off",
|
|
24175
|
+
resourceGroup,
|
|
24176
|
+
acsEndpoint: feature.acsEndpoint,
|
|
24177
|
+
acsSender: feature.acsSender,
|
|
24178
|
+
executor: agentName,
|
|
24179
|
+
executorNameProvenance: provenance,
|
|
24180
|
+
executorReconciled: reconciled.changed,
|
|
24181
|
+
executorAbsent: reconciled.absent,
|
|
24182
|
+
...reconciled.unreadable !== void 0 ? { executorUnreadable: reconciled.unreadable } : {},
|
|
24183
|
+
...reconciled.version !== void 0 ? { executorVersion: reconciled.version } : {}
|
|
24184
|
+
}) + "\n"
|
|
24185
|
+
);
|
|
24186
|
+
return reconciled.unreadable === void 0 ? 0 : 1;
|
|
24187
|
+
}
|
|
24188
|
+
this.context.stdout.write(
|
|
24189
|
+
renderKeyValueBlock([
|
|
24190
|
+
{ key: "outbound email", value: enable ? "on" : "off" },
|
|
24191
|
+
{ key: "resource group", value: resourceGroup },
|
|
24192
|
+
{ key: "sender", value: feature.acsSender === "" ? "(none on record)" : feature.acsSender },
|
|
24193
|
+
{
|
|
24194
|
+
key: "executor",
|
|
24195
|
+
// 🔴 `unreadable` MUST be its own branch. Falling through to "already correct" would
|
|
24196
|
+
// have this command — the one an operator runs to FIX email — affirmatively claim
|
|
24197
|
+
// success while having changed nothing, which is the exact conflation of broken with
|
|
24198
|
+
// fine that this whole change exists to eliminate.
|
|
24199
|
+
value: reconciled.unreadable !== void 0 ? `${agentName} (could NOT be read: ${reconciled.unreadable} \u2014 wiring NOT reconciled)` : reconciled.absent ? `${agentName} (not deployed \u2014 nothing to reconcile)` : reconciled.changed ? `${agentName} \u2192 version ${reconciled.version ?? "?"}` : `${agentName} (already correct)`
|
|
24200
|
+
}
|
|
24201
|
+
]) + "\n"
|
|
24202
|
+
);
|
|
24203
|
+
if (reconciled.absent) {
|
|
24204
|
+
this.context.stdout.write(
|
|
24205
|
+
` ${colors.hint("note:")} no executor is deployed here, so nothing can send yet. The decision is recorded and will apply when one is deployed.
|
|
24206
|
+
`
|
|
24207
|
+
);
|
|
24208
|
+
}
|
|
24209
|
+
if (provenance === "fallback" && reconciled.absent) {
|
|
24210
|
+
this.context.stdout.write(
|
|
24211
|
+
` ${colors.hint("note:")} '${agentName}' is a fallback name \u2014 the executor could not be discovered by persona. If yours is named differently, re-run with --agent <name>.
|
|
24212
|
+
`
|
|
24213
|
+
);
|
|
24214
|
+
}
|
|
24215
|
+
if (reconciled.unreadable !== void 0) {
|
|
24216
|
+
this.context.stdout.write(
|
|
24217
|
+
colors.error(
|
|
24218
|
+
`the decision is recorded, but ${agentName} was NOT reconciled \u2014 it exists and could not be read. Nothing about sending has changed yet; fix access and re-run.`
|
|
24219
|
+
) + "\n"
|
|
24220
|
+
);
|
|
24221
|
+
return 1;
|
|
24222
|
+
}
|
|
24223
|
+
this.context.stdout.write(
|
|
24224
|
+
colors.success(enable ? "outbound email is on \u2713" : "outbound email is off \u2713") + "\n"
|
|
24225
|
+
);
|
|
24226
|
+
return 0;
|
|
24227
|
+
}
|
|
24228
|
+
/** The install's resource suffix, or null when it cannot be worked out.
|
|
24229
|
+
*
|
|
24230
|
+
* Returns null rather than throwing: it is only needed if `resolveAcs` reaches its
|
|
24231
|
+
* provisioning leg, and failing the whole command over a missing AZURE_KEYVAULT_URI would
|
|
24232
|
+
* break the flip on every install that already has an ACS on record. */
|
|
24233
|
+
resolveSuffix() {
|
|
24234
|
+
try {
|
|
24235
|
+
const explicit = typeof this.kvUri === "string" && this.kvUri.length > 0 ? this.kvUri : void 0;
|
|
24236
|
+
return platformSuffixFromKvUri(discoverKvUri(process.env, explicit));
|
|
24237
|
+
} catch {
|
|
24238
|
+
return null;
|
|
24239
|
+
}
|
|
24240
|
+
}
|
|
24241
|
+
};
|
|
24242
|
+
|
|
24243
|
+
// src/commands/platform/enable-auto-update.ts
|
|
24244
|
+
import { Command as Command43, Option as Option40 } from "clipanion";
|
|
24245
|
+
import { confirm as confirm6 } from "@inquirer/prompts";
|
|
24246
|
+
import { DefaultAzureCredential as DefaultAzureCredential23 } from "@azure/identity";
|
|
23676
24247
|
init_errors();
|
|
23677
24248
|
var PlatformEnableAutoUpdateCommand = class extends M8tCommand {
|
|
23678
24249
|
static paths = [["platform", "enable-auto-update"]];
|
|
23679
|
-
static usage =
|
|
24250
|
+
static usage = Command43.Usage({
|
|
23680
24251
|
category: "Platform",
|
|
23681
24252
|
description: "Retrofit the auto-updater (MI + cron job + role assignments) onto an existing install.",
|
|
23682
24253
|
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."
|
|
23683
24254
|
});
|
|
23684
|
-
subscription =
|
|
23685
|
-
resourceGroup =
|
|
24255
|
+
subscription = Option40.String("--subscription");
|
|
24256
|
+
resourceGroup = Option40.String("--resource-group", {
|
|
23686
24257
|
description: "m8t resource group to disambiguate the gateway (multi-deployment subscriptions)."
|
|
23687
24258
|
});
|
|
23688
|
-
suffix =
|
|
24259
|
+
suffix = Option40.String("--suffix", {
|
|
23689
24260
|
description: "The existing deployment's resource-name suffix. Required when it cannot be recovered from system/infra-params or the live gateway."
|
|
23690
24261
|
});
|
|
23691
|
-
installerImage =
|
|
24262
|
+
installerImage = Option40.String("--installer-image", {
|
|
23692
24263
|
required: true,
|
|
23693
24264
|
description: "Updater CA-Job image ref (the converge engine at the current release). Required \u2014 an empty image would skip provisioning."
|
|
23694
24265
|
});
|
|
23695
|
-
updateCron =
|
|
24266
|
+
updateCron = Option40.String("--update-cron", {
|
|
23696
24267
|
description: "Cron schedule for the updater job (bicep default applies when omitted)."
|
|
23697
24268
|
});
|
|
23698
|
-
channelUrl =
|
|
24269
|
+
channelUrl = Option40.String("--channel-url", {
|
|
23699
24270
|
description: "Release-channel URL the updater job polls (bicep default applies when omitted)."
|
|
23700
24271
|
});
|
|
23701
|
-
location =
|
|
24272
|
+
location = Option40.String("--location", {
|
|
23702
24273
|
description: "Region for the updater identity + job. Defaults to the resource group's existing resources."
|
|
23703
24274
|
});
|
|
23704
|
-
foundryTracing =
|
|
24275
|
+
foundryTracing = Option40.String("--foundry-tracing", {
|
|
23705
24276
|
description: "project | account | skip. Pass the value the install was deployed with \u2014 omitting it lets the bicep default (project) switch tracing on."
|
|
23706
24277
|
});
|
|
23707
|
-
endpoint =
|
|
24278
|
+
endpoint = Option40.String("--endpoint", {
|
|
23708
24279
|
description: "Foundry project endpoint URL. Disambiguates the project in a multi-project subscription."
|
|
23709
24280
|
});
|
|
23710
|
-
yes =
|
|
23711
|
-
output =
|
|
24281
|
+
yes = Option40.Boolean("--yes", false);
|
|
24282
|
+
output = Option40.String("--output");
|
|
23712
24283
|
async executeCommand() {
|
|
23713
24284
|
const mode = resolveOutputMode(
|
|
23714
24285
|
this.output,
|
|
@@ -23727,7 +24298,7 @@ var PlatformEnableAutoUpdateCommand = class extends M8tCommand {
|
|
|
23727
24298
|
resourceGroup: this.resourceGroup
|
|
23728
24299
|
});
|
|
23729
24300
|
const { resourceGroup, name: gatewayName } = parseContainerAppResourceId(gw.containerAppResourceId);
|
|
23730
|
-
const credential2 = new
|
|
24301
|
+
const credential2 = new DefaultAzureCredential23();
|
|
23731
24302
|
const account = await getAzAccount();
|
|
23732
24303
|
const subscriptionId = this.subscription ?? account.subscriptionId;
|
|
23733
24304
|
const explicitSuffix = typeof this.suffix === "string" && this.suffix.length > 0 ? this.suffix : void 0;
|
|
@@ -23845,7 +24416,7 @@ var PlatformEnableAutoUpdateCommand = class extends M8tCommand {
|
|
|
23845
24416
|
};
|
|
23846
24417
|
|
|
23847
24418
|
// src/commands/deploy.ts
|
|
23848
|
-
import { Command as
|
|
24419
|
+
import { Command as Command44, Option as Option41 } from "clipanion";
|
|
23849
24420
|
|
|
23850
24421
|
// src/lib/app-reg.ts
|
|
23851
24422
|
init_esm();
|
|
@@ -24393,15 +24964,15 @@ function classifyWhatIf(changes) {
|
|
|
24393
24964
|
var DEFAULT_IMAGE_REF = "ghcr.io/m8t-labs/m8t:latest";
|
|
24394
24965
|
var DeployCommand = class extends M8tCommand {
|
|
24395
24966
|
static paths = [["deploy"]];
|
|
24396
|
-
static usage =
|
|
24967
|
+
static usage = Command44.Usage({
|
|
24397
24968
|
description: "Deploy (or update) the m8t gateway/webapp stack via Bicep.",
|
|
24398
24969
|
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."
|
|
24399
24970
|
});
|
|
24400
|
-
subscription =
|
|
24401
|
-
resourceGroup =
|
|
24402
|
-
location =
|
|
24403
|
-
suffix =
|
|
24404
|
-
imageRef =
|
|
24971
|
+
subscription = Option41.String("--subscription");
|
|
24972
|
+
resourceGroup = Option41.String("--resource-group", "rg-m8t-stack");
|
|
24973
|
+
location = Option41.String("--location", "eastus");
|
|
24974
|
+
suffix = Option41.String("--suffix", "");
|
|
24975
|
+
imageRef = Option41.String("--image-ref", DEFAULT_IMAGE_REF);
|
|
24405
24976
|
// Gateway-only override. Empty ⇒ the gateway uses --image-ref, which is the
|
|
24406
24977
|
// from-zero case. It exists because the gateway and the voice relay do NOT
|
|
24407
24978
|
// always run the same image: a converge preserves a BYOC gateway on its own
|
|
@@ -24409,28 +24980,28 @@ var DeployCommand = class extends M8tCommand {
|
|
|
24409
24980
|
// (`--what-if`) that can only express one image therefore reports the other
|
|
24410
24981
|
// app as drift on every single run, forever — which is exactly what the
|
|
24411
24982
|
// infra-drift gate did from 2026-08-11.
|
|
24412
|
-
gatewayImageRef =
|
|
24413
|
-
acrPullIdentity =
|
|
24414
|
-
acrResourceId =
|
|
24415
|
-
foundryEndpoint =
|
|
24416
|
-
foundryResourceId =
|
|
24417
|
-
foundryTracing =
|
|
24983
|
+
gatewayImageRef = Option41.String("--gateway-image-ref", "");
|
|
24984
|
+
acrPullIdentity = Option41.String("--acrpull-identity");
|
|
24985
|
+
acrResourceId = Option41.String("--acr-resource-id");
|
|
24986
|
+
foundryEndpoint = Option41.String("--foundry-endpoint");
|
|
24987
|
+
foundryResourceId = Option41.String("--foundry-resource-id");
|
|
24988
|
+
foundryTracing = Option41.String("--foundry-tracing");
|
|
24418
24989
|
// project | account | skip (bicep default: project)
|
|
24419
|
-
clientId =
|
|
24420
|
-
whatIf =
|
|
24990
|
+
clientId = Option41.String("--client-id");
|
|
24991
|
+
whatIf = Option41.Boolean("--what-if", false);
|
|
24421
24992
|
// Only meaningful with --what-if. Routes the comparison through the
|
|
24422
24993
|
// value-free renderers (see ./lib/whatif-redact.js) instead of the default
|
|
24423
24994
|
// before/after renderer. Defaults false so a local, interactive run keeps
|
|
24424
24995
|
// showing values — that is the whole diagnostic point of --what-if.
|
|
24425
24996
|
// Automation that forwards this output anywhere non-private (a CI log, an
|
|
24426
24997
|
// issue) MUST pass --redact.
|
|
24427
|
-
redact =
|
|
24428
|
-
output =
|
|
24998
|
+
redact = Option41.Boolean("--redact", false);
|
|
24999
|
+
output = Option41.String("--output");
|
|
24429
25000
|
// Subscription-scoped role assignments. Omitted ⇒ the template default (true).
|
|
24430
25001
|
// Pass false when deploying as a principal scoped to the resource group only:
|
|
24431
25002
|
// it cannot deploy at subscription scope, and those assignments persist
|
|
24432
25003
|
// idempotently from the initial deployment anyway.
|
|
24433
|
-
assignSubscriptionRoles =
|
|
25004
|
+
assignSubscriptionRoles = Option41.String("--assign-subscription-roles");
|
|
24434
25005
|
/**
|
|
24435
25006
|
* The installer image the updater Container-Apps Job runs.
|
|
24436
25007
|
*
|
|
@@ -24441,23 +25012,23 @@ var DeployCommand = class extends M8tCommand {
|
|
|
24441
25012
|
* than that — the comparison proposes REMOVING an updater job that exists and
|
|
24442
25013
|
* should, and reports it as drift on every single run.
|
|
24443
25014
|
*/
|
|
24444
|
-
installerImage =
|
|
25015
|
+
installerImage = Option41.String("--installer-image");
|
|
24445
25016
|
// Referee — all optional, undefined by default ⇒ the bicep defaults
|
|
24446
25017
|
// apply (dormant: gatewayCpu=0.25, gatewayMemory=0.5Gi, every referee/exam var
|
|
24447
25018
|
// empty). Only pass these when explicitly enabling the referee exam stack.
|
|
24448
|
-
gatewayCpu =
|
|
24449
|
-
gatewayMemory =
|
|
24450
|
-
refereeEnabled =
|
|
24451
|
-
refereeBrainRepos =
|
|
24452
|
-
refereeFeedRepo =
|
|
24453
|
-
refereeInstallationId =
|
|
24454
|
-
refereeWebhookHmacKvUri =
|
|
24455
|
-
examKvUri =
|
|
24456
|
-
examLaWorkspaceId =
|
|
24457
|
-
brainEvalDeployment =
|
|
24458
|
-
brainAppLogin =
|
|
24459
|
-
refereeCheckpointDir =
|
|
24460
|
-
examApiBase =
|
|
25019
|
+
gatewayCpu = Option41.String("--gateway-cpu");
|
|
25020
|
+
gatewayMemory = Option41.String("--gateway-memory");
|
|
25021
|
+
refereeEnabled = Option41.String("--referee-enabled");
|
|
25022
|
+
refereeBrainRepos = Option41.String("--referee-brain-repos");
|
|
25023
|
+
refereeFeedRepo = Option41.String("--referee-feed-repo");
|
|
25024
|
+
refereeInstallationId = Option41.String("--referee-installation-id");
|
|
25025
|
+
refereeWebhookHmacKvUri = Option41.String("--referee-webhook-hmac-kv-uri");
|
|
25026
|
+
examKvUri = Option41.String("--exam-kv-uri");
|
|
25027
|
+
examLaWorkspaceId = Option41.String("--exam-la-workspace-id");
|
|
25028
|
+
brainEvalDeployment = Option41.String("--brain-eval-deployment");
|
|
25029
|
+
brainAppLogin = Option41.String("--brain-app-login");
|
|
25030
|
+
refereeCheckpointDir = Option41.String("--referee-checkpoint-dir");
|
|
25031
|
+
examApiBase = Option41.String("--exam-api-base");
|
|
24461
25032
|
async executeCommand() {
|
|
24462
25033
|
const mode = resolveOutputMode(
|
|
24463
25034
|
this.output,
|
|
@@ -24640,7 +25211,7 @@ var DeployCommand = class extends M8tCommand {
|
|
|
24640
25211
|
|
|
24641
25212
|
// src/commands/eval/skill.ts
|
|
24642
25213
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
24643
|
-
import { Command as
|
|
25214
|
+
import { Command as Command45, Option as Option42 } from "clipanion";
|
|
24644
25215
|
init_errors();
|
|
24645
25216
|
var DECISIONS = /* @__PURE__ */ new Set(["promote", "reject", "needs_review"]);
|
|
24646
25217
|
var JUDGE_STATUSES = /* @__PURE__ */ new Set(["ok", "skipped", "unavailable"]);
|
|
@@ -24665,14 +25236,14 @@ function parseVerdict(stdout) {
|
|
|
24665
25236
|
}
|
|
24666
25237
|
var EvalSkillCommand = class extends M8tCommand {
|
|
24667
25238
|
static paths = [["eval", "skill"]];
|
|
24668
|
-
static usage =
|
|
25239
|
+
static usage = Command45.Usage({
|
|
24669
25240
|
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)."
|
|
24670
25241
|
});
|
|
24671
|
-
candidate =
|
|
24672
|
-
skillsDir =
|
|
24673
|
-
noJudge =
|
|
24674
|
-
deployment =
|
|
24675
|
-
output =
|
|
25242
|
+
candidate = Option42.String();
|
|
25243
|
+
skillsDir = Option42.String("--skills-dir");
|
|
25244
|
+
noJudge = Option42.Boolean("--no-judge", false);
|
|
25245
|
+
deployment = Option42.String("--deployment");
|
|
25246
|
+
output = Option42.String("--output");
|
|
24676
25247
|
executeCommand() {
|
|
24677
25248
|
return Promise.resolve(this._runCommand());
|
|
24678
25249
|
}
|
|
@@ -24731,7 +25302,7 @@ var EvalSkillCommand = class extends M8tCommand {
|
|
|
24731
25302
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
24732
25303
|
import { writeFileSync as writeFileSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync21, existsSync as existsSync20, readdirSync as readdirSync3 } from "fs";
|
|
24733
25304
|
import { join as join30 } from "path";
|
|
24734
|
-
import { Command as
|
|
25305
|
+
import { Command as Command46, Option as Option43 } from "clipanion";
|
|
24735
25306
|
init_errors();
|
|
24736
25307
|
init_esm();
|
|
24737
25308
|
function parseArmToken(tok, opts) {
|
|
@@ -24961,24 +25532,24 @@ function buildPlan(args) {
|
|
|
24961
25532
|
}
|
|
24962
25533
|
var EvalExamCommand = class extends M8tCommand {
|
|
24963
25534
|
static paths = [["eval", "exam"]];
|
|
24964
|
-
static usage =
|
|
25535
|
+
static usage = Command46.Usage({
|
|
24965
25536
|
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."
|
|
24966
25537
|
});
|
|
24967
|
-
worker =
|
|
24968
|
-
arms =
|
|
24969
|
-
taskSet =
|
|
24970
|
-
examType =
|
|
24971
|
-
skill =
|
|
24972
|
-
reps =
|
|
24973
|
-
probes =
|
|
24974
|
-
pool =
|
|
24975
|
-
out =
|
|
24976
|
-
dryRun =
|
|
24977
|
-
keepArms =
|
|
24978
|
-
allowStub =
|
|
24979
|
-
deployment =
|
|
24980
|
-
output =
|
|
24981
|
-
observeWaitS =
|
|
25538
|
+
worker = Option43.String();
|
|
25539
|
+
arms = Option43.String("--arms");
|
|
25540
|
+
taskSet = Option43.String("--task-set");
|
|
25541
|
+
examType = Option43.String("--exam-type");
|
|
25542
|
+
skill = Option43.String("--skill");
|
|
25543
|
+
reps = Option43.String("-n,--reps");
|
|
25544
|
+
probes = Option43.String("--probes");
|
|
25545
|
+
pool = Option43.String("--pool");
|
|
25546
|
+
out = Option43.String("--out");
|
|
25547
|
+
dryRun = Option43.Boolean("--dry-run", false);
|
|
25548
|
+
keepArms = Option43.Boolean("--keep-arms", false);
|
|
25549
|
+
allowStub = Option43.Boolean("--allow-stub", false);
|
|
25550
|
+
deployment = Option43.String("--deployment");
|
|
25551
|
+
output = Option43.String("--output");
|
|
25552
|
+
observeWaitS = Option43.String("--observe-wait-s");
|
|
24982
25553
|
async executeCommand() {
|
|
24983
25554
|
await Promise.resolve();
|
|
24984
25555
|
const worker = typeof this.worker === "string" ? this.worker : void 0;
|
|
@@ -25093,10 +25664,10 @@ var EvalExamCommand = class extends M8tCommand {
|
|
|
25093
25664
|
};
|
|
25094
25665
|
|
|
25095
25666
|
// src/commands/version.ts
|
|
25096
|
-
import { Command as
|
|
25667
|
+
import { Command as Command47, Option as Option44 } from "clipanion";
|
|
25097
25668
|
var VersionCommand = class extends M8tCommand {
|
|
25098
25669
|
static paths = [["version"], ["--version"], ["-v"]];
|
|
25099
|
-
static usage =
|
|
25670
|
+
static usage = Command47.Usage({
|
|
25100
25671
|
description: "Print the CLI version.",
|
|
25101
25672
|
details: "Prints the m8t CLI version. With --verbose, also prints Node version and platform.",
|
|
25102
25673
|
examples: [
|
|
@@ -25104,8 +25675,8 @@ var VersionCommand = class extends M8tCommand {
|
|
|
25104
25675
|
["Print as JSON", "$0 version --output json"]
|
|
25105
25676
|
]
|
|
25106
25677
|
});
|
|
25107
|
-
output =
|
|
25108
|
-
verbose =
|
|
25678
|
+
output = Option44.String("--output", { description: "pretty | json | auto (default)" });
|
|
25679
|
+
verbose = Option44.Boolean("--verbose", false);
|
|
25109
25680
|
executeCommand() {
|
|
25110
25681
|
const mode = resolveOutputMode(
|
|
25111
25682
|
this.output ?? "auto",
|
|
@@ -25136,18 +25707,18 @@ var VersionCommand = class extends M8tCommand {
|
|
|
25136
25707
|
};
|
|
25137
25708
|
|
|
25138
25709
|
// src/commands/whoami.ts
|
|
25139
|
-
import { Command as
|
|
25710
|
+
import { Command as Command48, Option as Option45 } from "clipanion";
|
|
25140
25711
|
var WhoamiCommand = class extends M8tCommand {
|
|
25141
25712
|
static paths = [["whoami"]];
|
|
25142
|
-
static usage =
|
|
25713
|
+
static usage = Command48.Usage({
|
|
25143
25714
|
description: "Show your identity + the gateway you'll talk to. Probes the backend."
|
|
25144
25715
|
});
|
|
25145
|
-
output =
|
|
25146
|
-
verbose =
|
|
25147
|
-
subscription =
|
|
25716
|
+
output = Option45.String("--output");
|
|
25717
|
+
verbose = Option45.Boolean("--verbose", false);
|
|
25718
|
+
subscription = Option45.String("--subscription", {
|
|
25148
25719
|
description: "Azure subscription ID to discover gateway in (defaults to active az subscription)."
|
|
25149
25720
|
});
|
|
25150
|
-
resourceGroup =
|
|
25721
|
+
resourceGroup = Option45.String("--resource-group", {
|
|
25151
25722
|
description: "m8t resource group to disambiguate the gateway (multi-deployment subscriptions)."
|
|
25152
25723
|
});
|
|
25153
25724
|
async executeCommand() {
|
|
@@ -25212,7 +25783,7 @@ var WhoamiCommand = class extends M8tCommand {
|
|
|
25212
25783
|
};
|
|
25213
25784
|
|
|
25214
25785
|
// src/commands/status.ts
|
|
25215
|
-
import { Command as
|
|
25786
|
+
import { Command as Command49, Option as Option46 } from "clipanion";
|
|
25216
25787
|
|
|
25217
25788
|
// src/lib/azd.ts
|
|
25218
25789
|
init_errors();
|
|
@@ -25277,10 +25848,10 @@ async function resolveLocalContext() {
|
|
|
25277
25848
|
// src/commands/status.ts
|
|
25278
25849
|
var StatusCommand = class extends M8tCommand {
|
|
25279
25850
|
static paths = [["status"]];
|
|
25280
|
-
static usage =
|
|
25851
|
+
static usage = Command49.Usage({
|
|
25281
25852
|
description: "Show the local m8t context: identity, config.yaml, gateway cache, azd mode."
|
|
25282
25853
|
});
|
|
25283
|
-
output =
|
|
25854
|
+
output = Option46.String("--output");
|
|
25284
25855
|
async executeCommand() {
|
|
25285
25856
|
const mode = resolveOutputMode(
|
|
25286
25857
|
this.output,
|
|
@@ -25318,8 +25889,8 @@ var StatusCommand = class extends M8tCommand {
|
|
|
25318
25889
|
};
|
|
25319
25890
|
|
|
25320
25891
|
// src/commands/doctor.ts
|
|
25321
|
-
import { Command as
|
|
25322
|
-
import { DefaultAzureCredential as
|
|
25892
|
+
import { Command as Command50, Option as Option47 } from "clipanion";
|
|
25893
|
+
import { DefaultAzureCredential as DefaultAzureCredential24 } from "@azure/identity";
|
|
25323
25894
|
import * as fs30 from "fs";
|
|
25324
25895
|
import * as os12 from "os";
|
|
25325
25896
|
import * as path33 from "path";
|
|
@@ -25523,6 +26094,43 @@ function checkLegacyStateDir(probe) {
|
|
|
25523
26094
|
remediation: "review ~/.m8t-stack for anything you still need, then remove it"
|
|
25524
26095
|
};
|
|
25525
26096
|
}
|
|
26097
|
+
function checkOutboundEmail(probe) {
|
|
26098
|
+
const name = "outbound email";
|
|
26099
|
+
const intended = probe.stampFeature?.enabled === true;
|
|
26100
|
+
if (!intended) {
|
|
26101
|
+
return {
|
|
26102
|
+
name,
|
|
26103
|
+
status: "INFO",
|
|
26104
|
+
detail: "off \u2014 advisor handoffs render but do not send",
|
|
26105
|
+
remediation: "m8t platform email on"
|
|
26106
|
+
};
|
|
26107
|
+
}
|
|
26108
|
+
if (probe.executorEnv === null) {
|
|
26109
|
+
return {
|
|
26110
|
+
name,
|
|
26111
|
+
status: "WARN",
|
|
26112
|
+
detail: `enabled in the stamp, but no ${probe.executorName} is deployed \u2014 nothing can send`,
|
|
26113
|
+
remediation: `m8t azure-exec deploy ${probe.executorName} --resource-group <rg> --brain <owner/repo>`
|
|
26114
|
+
};
|
|
26115
|
+
}
|
|
26116
|
+
const endpoint = probe.executorEnv.M8T_ACS_ENDPOINT ?? "";
|
|
26117
|
+
const sender = probe.executorEnv.M8T_ACS_SENDER ?? "";
|
|
26118
|
+
const flag = probe.executorEnv.M8T_EMAIL_ENABLED ?? "";
|
|
26119
|
+
if (endpoint !== "" && sender !== "" && flag !== "") {
|
|
26120
|
+
return { name, status: "PASS", detail: `on \u2014 sending as ${sender}` };
|
|
26121
|
+
}
|
|
26122
|
+
const missing = [
|
|
26123
|
+
...flag === "" ? ["M8T_EMAIL_ENABLED"] : [],
|
|
26124
|
+
...endpoint === "" ? ["M8T_ACS_ENDPOINT"] : [],
|
|
26125
|
+
...sender === "" ? ["M8T_ACS_SENDER"] : []
|
|
26126
|
+
];
|
|
26127
|
+
return {
|
|
26128
|
+
name,
|
|
26129
|
+
status: "FAIL",
|
|
26130
|
+
detail: `MISCONFIGURED \u2014 the stamp says email is on, but ${probe.executorName} is missing ${missing.join(", ")}. Ezra will refuse to send and report a platform bug.`,
|
|
26131
|
+
remediation: "m8t platform email on"
|
|
26132
|
+
};
|
|
26133
|
+
}
|
|
25526
26134
|
|
|
25527
26135
|
// src/commands/doctor.ts
|
|
25528
26136
|
init_foundry_agent_get();
|
|
@@ -25829,12 +26437,12 @@ function probeLegacyStateDir() {
|
|
|
25829
26437
|
}
|
|
25830
26438
|
var DoctorCommand = class extends M8tCommand {
|
|
25831
26439
|
static paths = [["doctor"]];
|
|
25832
|
-
static usage =
|
|
26440
|
+
static usage = Command50.Usage({
|
|
25833
26441
|
description: "Diagnose the local m8t setup: az login, config.yaml, gateway, Foundry data-plane."
|
|
25834
26442
|
});
|
|
25835
|
-
output =
|
|
25836
|
-
agent =
|
|
25837
|
-
resourceGroup =
|
|
26443
|
+
output = Option47.String("--output");
|
|
26444
|
+
agent = Option47.String("--agent");
|
|
26445
|
+
resourceGroup = Option47.String("--resource-group", {
|
|
25838
26446
|
description: "m8t resource group to disambiguate the gateway (multi-deployment subscriptions)."
|
|
25839
26447
|
});
|
|
25840
26448
|
async executeCommand() {
|
|
@@ -25907,7 +26515,7 @@ var DoctorCommand = class extends M8tCommand {
|
|
|
25907
26515
|
let kvStatus = 0;
|
|
25908
26516
|
if (kv) {
|
|
25909
26517
|
try {
|
|
25910
|
-
const token = await new
|
|
26518
|
+
const token = await new DefaultAzureCredential24().getToken("https://vault.azure.net/.default");
|
|
25911
26519
|
const res = await fetch(`${kv.uri.replace(/\/$/, "")}/secrets?api-version=7.4&maxresults=1`, {
|
|
25912
26520
|
headers: { Authorization: `Bearer ${token.token}` }
|
|
25913
26521
|
});
|
|
@@ -25924,10 +26532,64 @@ var DoctorCommand = class extends M8tCommand {
|
|
|
25924
26532
|
const region = foundryAccountId ? await resolveAccountLocation(foundryAccountId) : null;
|
|
25925
26533
|
const usages = region ? await listModelQuota(region) : [];
|
|
25926
26534
|
emit(checkModelQuota(deployments, usages));
|
|
26535
|
+
checking("outbound email");
|
|
26536
|
+
try {
|
|
26537
|
+
const credential2 = new DefaultAzureCredential24();
|
|
26538
|
+
const outcome = platformRg && platformSub ? await readStampOutcome({ credential: credential2, subscriptionId: platformSub, resourceGroup: platformRg }).catch(
|
|
26539
|
+
() => ({ source: "unreadable" })
|
|
26540
|
+
) : { source: "unreadable" };
|
|
26541
|
+
if (outcome.source === "unreadable") {
|
|
26542
|
+
emit({
|
|
26543
|
+
name: "outbound email",
|
|
26544
|
+
status: "INFO",
|
|
26545
|
+
detail: "could not read the recorded intent (platform stamp unreadable) \u2014 state unknown, not necessarily off"
|
|
26546
|
+
});
|
|
26547
|
+
} else {
|
|
26548
|
+
const { agentName: executorName } = await resolveExecutorAgentName({
|
|
26549
|
+
credential: credential2,
|
|
26550
|
+
projectEndpoint: foundry.projectEndpoint
|
|
26551
|
+
});
|
|
26552
|
+
const stamp = outcome.source === "explicit" ? outcome.stamp : null;
|
|
26553
|
+
let executorEnv = null;
|
|
26554
|
+
let executorUnreadable = null;
|
|
26555
|
+
try {
|
|
26556
|
+
const cur = await getAgentVersion({
|
|
26557
|
+
credential: credential2,
|
|
26558
|
+
projectEndpoint: foundry.projectEndpoint,
|
|
26559
|
+
agentName: executorName
|
|
26560
|
+
});
|
|
26561
|
+
executorEnv = cur.definition.environment_variables ?? {};
|
|
26562
|
+
} catch (err) {
|
|
26563
|
+
if (err.code === "AGENT_NOT_FOUND") executorEnv = null;
|
|
26564
|
+
else executorUnreadable = err instanceof Error ? err.message : String(err);
|
|
26565
|
+
}
|
|
26566
|
+
if (executorUnreadable !== null) {
|
|
26567
|
+
emit({
|
|
26568
|
+
name: "outbound email",
|
|
26569
|
+
status: "INFO",
|
|
26570
|
+
detail: `could not read ${executorName} (${executorUnreadable}) \u2014 wiring unknown`
|
|
26571
|
+
});
|
|
26572
|
+
} else {
|
|
26573
|
+
emit(
|
|
26574
|
+
checkOutboundEmail({
|
|
26575
|
+
stampFeature: stamp?.features?.email ?? null,
|
|
26576
|
+
executorEnv,
|
|
26577
|
+
executorName
|
|
26578
|
+
})
|
|
26579
|
+
);
|
|
26580
|
+
}
|
|
26581
|
+
}
|
|
26582
|
+
} catch (e) {
|
|
26583
|
+
emit({
|
|
26584
|
+
name: "outbound email",
|
|
26585
|
+
status: "INFO",
|
|
26586
|
+
detail: `could not check: ${e instanceof Error ? e.message : String(e)}`
|
|
26587
|
+
});
|
|
26588
|
+
}
|
|
25927
26589
|
if (typeof this.agent === "string" && this.agent) {
|
|
25928
26590
|
checking(`delivery grant for ${this.agent}`);
|
|
25929
26591
|
try {
|
|
25930
|
-
const credential2 = new
|
|
26592
|
+
const credential2 = new DefaultAzureCredential24();
|
|
25931
26593
|
const cur = await getAgentVersion({
|
|
25932
26594
|
credential: credential2,
|
|
25933
26595
|
projectEndpoint: foundry.projectEndpoint,
|
|
@@ -25970,11 +26632,11 @@ var DoctorCommand = class extends M8tCommand {
|
|
|
25970
26632
|
};
|
|
25971
26633
|
|
|
25972
26634
|
// src/commands/prereqs.ts
|
|
25973
|
-
import { Command as
|
|
26635
|
+
import { Command as Command51, Option as Option48 } from "clipanion";
|
|
25974
26636
|
init_errors();
|
|
25975
26637
|
|
|
25976
26638
|
// src/lib/prereq-deps.ts
|
|
25977
|
-
import { DefaultAzureCredential as
|
|
26639
|
+
import { DefaultAzureCredential as DefaultAzureCredential25 } from "@azure/identity";
|
|
25978
26640
|
|
|
25979
26641
|
// src/lib/bootstrap-preflight.ts
|
|
25980
26642
|
var ADMIN_DIRECTORY_ROLES = ["Global Administrator", "Application Administrator", "Cloud Application Administrator"];
|
|
@@ -26086,7 +26748,7 @@ function buildPrereqDeps(opts = {}) {
|
|
|
26086
26748
|
probeRedirectUri,
|
|
26087
26749
|
fixFoundryAccess,
|
|
26088
26750
|
fixKeyVaultAccess,
|
|
26089
|
-
credential: () => credentialSingleton2 ??= new
|
|
26751
|
+
credential: () => credentialSingleton2 ??= new DefaultAzureCredential25()
|
|
26090
26752
|
};
|
|
26091
26753
|
}
|
|
26092
26754
|
|
|
@@ -26676,7 +27338,7 @@ function renderVerdict(v) {
|
|
|
26676
27338
|
}
|
|
26677
27339
|
var PrereqsCommand = class extends M8tCommand {
|
|
26678
27340
|
static paths = [["prereqs"]];
|
|
26679
|
-
static usage =
|
|
27341
|
+
static usage = Command51.Usage({
|
|
26680
27342
|
description: "Check (and optionally fix) everything m8t needs \u2014 to install, or for you to use it.",
|
|
26681
27343
|
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.",
|
|
26682
27344
|
examples: [
|
|
@@ -26687,15 +27349,15 @@ var PrereqsCommand = class extends M8tCommand {
|
|
|
26687
27349
|
["Set a teammate up (needs role-assignment rights)", "$0 prereqs --fix --for alice@contoso.com"]
|
|
26688
27350
|
]
|
|
26689
27351
|
});
|
|
26690
|
-
fix =
|
|
26691
|
-
for_ =
|
|
26692
|
-
phase =
|
|
26693
|
-
region =
|
|
26694
|
-
model =
|
|
26695
|
-
clientId =
|
|
26696
|
-
subscription =
|
|
26697
|
-
resourceGroup =
|
|
26698
|
-
output =
|
|
27352
|
+
fix = Option48.Boolean("--fix", false, { description: "Repair what is repairable. Without it the command only reports." });
|
|
27353
|
+
for_ = Option48.String("--for", { description: "UPN or object id of another person. Usage phase only." });
|
|
27354
|
+
phase = Option48.String("--phase", { description: "Force 'install' or 'usage' instead of auto-detecting." });
|
|
27355
|
+
region = Option48.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)." });
|
|
27356
|
+
model = Option48.String("--model", { description: `Model whose quota the install needs (default ${INSTALL_REASONING_MODEL}).` });
|
|
27357
|
+
clientId = Option48.String("--client-id", { description: "A ready app registration, in place of directory-admin rights." });
|
|
27358
|
+
subscription = Option48.String("--subscription");
|
|
27359
|
+
resourceGroup = Option48.String("--resource-group");
|
|
27360
|
+
output = Option48.String("--output");
|
|
26699
27361
|
async executeCommand() {
|
|
26700
27362
|
const mode = resolveOutputMode(this.output, this.context.stdout);
|
|
26701
27363
|
const json = mode === "json";
|
|
@@ -26751,7 +27413,7 @@ var PrereqsCommand = class extends M8tCommand {
|
|
|
26751
27413
|
};
|
|
26752
27414
|
|
|
26753
27415
|
// src/commands/switch.ts
|
|
26754
|
-
import { Command as
|
|
27416
|
+
import { Command as Command52, Option as Option49 } from "clipanion";
|
|
26755
27417
|
|
|
26756
27418
|
// src/lib/profiles.ts
|
|
26757
27419
|
import * as fs31 from "fs/promises";
|
|
@@ -26891,14 +27553,14 @@ async function profileSwitch(name, asName) {
|
|
|
26891
27553
|
init_errors();
|
|
26892
27554
|
var SwitchCommand = class extends M8tCommand {
|
|
26893
27555
|
static paths = [["switch"]];
|
|
26894
|
-
static usage =
|
|
27556
|
+
static usage = Command52.Usage({
|
|
26895
27557
|
description: "Re-point local config at another deployment: --subscription <id|name> (discovery) or <profile>."
|
|
26896
27558
|
});
|
|
26897
|
-
profile =
|
|
26898
|
-
subscription =
|
|
26899
|
-
list =
|
|
26900
|
-
as =
|
|
26901
|
-
output =
|
|
27559
|
+
profile = Option49.String({ required: false });
|
|
27560
|
+
subscription = Option49.String("--subscription");
|
|
27561
|
+
list = Option49.Boolean("--list", false);
|
|
27562
|
+
as = Option49.String("--as");
|
|
27563
|
+
output = Option49.String("--output");
|
|
26902
27564
|
async executeCommand() {
|
|
26903
27565
|
const mode = resolveOutputMode(
|
|
26904
27566
|
this.output,
|
|
@@ -26955,7 +27617,7 @@ var SwitchCommand = class extends M8tCommand {
|
|
|
26955
27617
|
|
|
26956
27618
|
// src/commands/open.ts
|
|
26957
27619
|
import { spawn as spawn5 } from "child_process";
|
|
26958
|
-
import { Command as
|
|
27620
|
+
import { Command as Command53, Option as Option50 } from "clipanion";
|
|
26959
27621
|
|
|
26960
27622
|
// src/lib/open-targets.ts
|
|
26961
27623
|
init_errors();
|
|
@@ -27001,14 +27663,14 @@ function openUrl(url) {
|
|
|
27001
27663
|
}
|
|
27002
27664
|
var OpenCommand = class extends M8tCommand {
|
|
27003
27665
|
static paths = [["open"]];
|
|
27004
|
-
static usage =
|
|
27666
|
+
static usage = Command53.Usage({
|
|
27005
27667
|
description: "Open the deployed webapp (default), the Foundry portal, or the resource group.",
|
|
27006
27668
|
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)."
|
|
27007
27669
|
});
|
|
27008
|
-
target =
|
|
27009
|
-
print =
|
|
27010
|
-
output =
|
|
27011
|
-
resourceGroup =
|
|
27670
|
+
target = Option50.String({ required: false });
|
|
27671
|
+
print = Option50.Boolean("--print", false);
|
|
27672
|
+
output = Option50.String("--output");
|
|
27673
|
+
resourceGroup = Option50.String("--resource-group", {
|
|
27012
27674
|
description: "m8t resource group to disambiguate the gateway (multi-deployment subscriptions)."
|
|
27013
27675
|
});
|
|
27014
27676
|
async executeCommand() {
|
|
@@ -27052,7 +27714,7 @@ var OpenCommand = class extends M8tCommand {
|
|
|
27052
27714
|
};
|
|
27053
27715
|
|
|
27054
27716
|
// src/commands/dream/run.ts
|
|
27055
|
-
import { Command as
|
|
27717
|
+
import { Command as Command54, Option as Option51 } from "clipanion";
|
|
27056
27718
|
import { AzureCliCredential } from "@azure/identity";
|
|
27057
27719
|
import { TableClient as TableClient7 } from "@azure/data-tables";
|
|
27058
27720
|
import { AIProjectClient as AIProjectClient3 } from "@azure/ai-projects";
|
|
@@ -29346,28 +30008,28 @@ function redactTranscripts(input) {
|
|
|
29346
30008
|
}
|
|
29347
30009
|
var DreamRunCommand = class extends M8tCommand {
|
|
29348
30010
|
static paths = [["dream", "run"]];
|
|
29349
|
-
static usage =
|
|
30011
|
+
static usage = Command54.Usage({
|
|
29350
30012
|
description: "Dry-run the brain consumption pipeline for one worker (no model call, no writes). Pass --live to actually run it.",
|
|
29351
30013
|
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."
|
|
29352
30014
|
});
|
|
29353
|
-
worker =
|
|
30015
|
+
worker = Option51.String("--worker", { description: "Worker (canonical name) to harvest. Required." });
|
|
29354
30016
|
// Opting IN to the side effects, rather than opting out of them. This command's
|
|
29355
30017
|
// own help has always described a dry run, but the bare invocation used to take
|
|
29356
30018
|
// the live branch — a real model call and a commit to the brain repo — so anyone
|
|
29357
30019
|
// acting on `--help` got the opposite of what they read.
|
|
29358
|
-
live =
|
|
29359
|
-
dryRun =
|
|
29360
|
-
since =
|
|
29361
|
-
reset =
|
|
29362
|
-
showTranscripts =
|
|
30020
|
+
live = Option51.Boolean("--live", false, { description: "Actually run it: model call + commit to the brain repo." });
|
|
30021
|
+
dryRun = Option51.Boolean("--dry-run", false, { description: "Read-only harvest. This is the default; the flag is accepted for compatibility." });
|
|
30022
|
+
since = Option51.String("--since", { description: "ISO-8601 start override (rejected if malformed or future)." });
|
|
30023
|
+
reset = Option51.Boolean("--reset", false, { description: "Ignore the stored cursor; read from the beginning." });
|
|
30024
|
+
showTranscripts = Option51.Boolean("--show-transcripts", false, {
|
|
29363
30025
|
description: "Print transcript bodies (default: metadata only)."
|
|
29364
30026
|
});
|
|
29365
|
-
subscription =
|
|
29366
|
-
endpoint =
|
|
29367
|
-
storageAccount =
|
|
30027
|
+
subscription = Option51.String("--subscription");
|
|
30028
|
+
endpoint = Option51.String("--endpoint");
|
|
30029
|
+
storageAccount = Option51.String("--storage-account", {
|
|
29368
30030
|
description: "Ledger storage account name (skips tag-based discovery)"
|
|
29369
30031
|
});
|
|
29370
|
-
output =
|
|
30032
|
+
output = Option51.String("--output");
|
|
29371
30033
|
// Resolution seam — overridden by tests; built lazily at runtime otherwise.
|
|
29372
30034
|
deps;
|
|
29373
30035
|
async executeCommand() {
|
|
@@ -29718,7 +30380,7 @@ function defaultDeps(overrides) {
|
|
|
29718
30380
|
|
|
29719
30381
|
// src/commands/conversations/sweep.ts
|
|
29720
30382
|
import { createHash as createHash4 } from "crypto";
|
|
29721
|
-
import { Command as
|
|
30383
|
+
import { Command as Command55, Option as Option52 } from "clipanion";
|
|
29722
30384
|
import { AzureCliCredential as AzureCliCredential2 } from "@azure/identity";
|
|
29723
30385
|
import { TableClient as TableClient8 } from "@azure/data-tables";
|
|
29724
30386
|
import { AIProjectClient as AIProjectClient4 } from "@azure/ai-projects";
|
|
@@ -29844,7 +30506,7 @@ function defaultDeps2() {
|
|
|
29844
30506
|
}
|
|
29845
30507
|
var ConversationsSweepCommand = class extends M8tCommand {
|
|
29846
30508
|
static paths = [["conversations", "sweep"]];
|
|
29847
|
-
static usage =
|
|
30509
|
+
static usage = Command55.Usage({
|
|
29848
30510
|
category: "Conversations",
|
|
29849
30511
|
description: "Delete expired public-visitor conversations (dry run by default)",
|
|
29850
30512
|
details: `
|
|
@@ -29860,22 +30522,22 @@ var ConversationsSweepCommand = class extends M8tCommand {
|
|
|
29860
30522
|
["Delete, capped at 50", "m8t conversations sweep --delete --max 50"]
|
|
29861
30523
|
]
|
|
29862
30524
|
});
|
|
29863
|
-
doDelete =
|
|
30525
|
+
doDelete = Option52.Boolean("--delete", false, {
|
|
29864
30526
|
description: "Perform deletions (without this flag the command only reports)"
|
|
29865
30527
|
});
|
|
29866
|
-
principal =
|
|
30528
|
+
principal = Option52.String("--principal", {
|
|
29867
30529
|
description: "Service-principal oid whose end-user keys are known-ephemeral (required with --delete; a dry run without it reports candidates grouped by principal)"
|
|
29868
30530
|
});
|
|
29869
|
-
max =
|
|
29870
|
-
graceDays =
|
|
30531
|
+
max = Option52.String("--max", "200", { description: "Maximum deletions per run" });
|
|
30532
|
+
graceDays = Option52.String("--grace-days", "14", {
|
|
29871
30533
|
description: "Days past the 30-day key life before a conversation is eligible"
|
|
29872
30534
|
});
|
|
29873
|
-
lookbackDays =
|
|
30535
|
+
lookbackDays = Option52.String("--lookback-days", "180", {
|
|
29874
30536
|
description: "How far back to scan ledger activity for candidates"
|
|
29875
30537
|
});
|
|
29876
|
-
subscription =
|
|
29877
|
-
endpoint =
|
|
29878
|
-
storageAccount =
|
|
30538
|
+
subscription = Option52.String("--subscription", { description: "Azure subscription id override" });
|
|
30539
|
+
endpoint = Option52.String("--endpoint", { description: "Foundry project endpoint override" });
|
|
30540
|
+
storageAccount = Option52.String("--storage-account", {
|
|
29879
30541
|
description: "Ledger storage account name (skips tag-based discovery)"
|
|
29880
30542
|
});
|
|
29881
30543
|
deps = defaultDeps2();
|
|
@@ -30003,7 +30665,7 @@ var ConversationsSweepCommand = class extends M8tCommand {
|
|
|
30003
30665
|
};
|
|
30004
30666
|
|
|
30005
30667
|
// src/commands/foundry/create.ts
|
|
30006
|
-
import { Command as
|
|
30668
|
+
import { Command as Command56, Option as Option53 } from "clipanion";
|
|
30007
30669
|
|
|
30008
30670
|
// src/lib/foundry-create.ts
|
|
30009
30671
|
init_errors();
|
|
@@ -30241,7 +30903,7 @@ async function createFoundryProject(args) {
|
|
|
30241
30903
|
init_errors();
|
|
30242
30904
|
var FoundryCreateCommand = class extends M8tCommand {
|
|
30243
30905
|
static paths = [["foundry", "create"]];
|
|
30244
|
-
static usage =
|
|
30906
|
+
static usage = Command56.Usage({
|
|
30245
30907
|
description: "Create an AI Foundry (AIServices) account + project + model deployment from scratch.",
|
|
30246
30908
|
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).",
|
|
30247
30909
|
examples: [
|
|
@@ -30250,16 +30912,16 @@ var FoundryCreateCommand = class extends M8tCommand {
|
|
|
30250
30912
|
["Higher capacity for a reasoning model", "$0 foundry create --resource-group rg-m8t-stack --location eastus2 --model gpt-5-mini --capacity 250"]
|
|
30251
30913
|
]
|
|
30252
30914
|
});
|
|
30253
|
-
resourceGroup =
|
|
30254
|
-
location =
|
|
30255
|
-
account =
|
|
30256
|
-
project =
|
|
30257
|
-
model =
|
|
30258
|
-
modelVersion =
|
|
30259
|
-
capacity =
|
|
30260
|
-
subscription =
|
|
30261
|
-
skipQuotaCheck =
|
|
30262
|
-
output =
|
|
30915
|
+
resourceGroup = Option53.String("--resource-group");
|
|
30916
|
+
location = Option53.String("--location");
|
|
30917
|
+
account = Option53.String("--account");
|
|
30918
|
+
project = Option53.String("--project", "m8t");
|
|
30919
|
+
model = Option53.String("--model", "gpt-4.1-mini");
|
|
30920
|
+
modelVersion = Option53.String("--model-version", "2025-04-14");
|
|
30921
|
+
capacity = Option53.String("--capacity", "50");
|
|
30922
|
+
subscription = Option53.String("--subscription");
|
|
30923
|
+
skipQuotaCheck = Option53.Boolean("--skip-quota-check", false);
|
|
30924
|
+
output = Option53.String("--output");
|
|
30263
30925
|
async executeCommand() {
|
|
30264
30926
|
const mode = resolveOutputMode(
|
|
30265
30927
|
this.output,
|
|
@@ -30331,22 +30993,22 @@ var FoundryCreateCommand = class extends M8tCommand {
|
|
|
30331
30993
|
};
|
|
30332
30994
|
|
|
30333
30995
|
// src/commands/foundry/await-ready.ts
|
|
30334
|
-
import { Command as
|
|
30996
|
+
import { Command as Command57, Option as Option54 } from "clipanion";
|
|
30335
30997
|
import { AzureCliCredential as AzureCliCredential3 } from "@azure/identity";
|
|
30336
30998
|
init_errors();
|
|
30337
30999
|
var FoundryAwaitReadyCommand = class extends M8tCommand {
|
|
30338
31000
|
static paths = [["foundry", "await-ready"]];
|
|
30339
|
-
static usage =
|
|
31001
|
+
static usage = Command57.Usage({
|
|
30340
31002
|
description: "Wait until a freshly-created Foundry project's data plane reliably serves it.",
|
|
30341
31003
|
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.",
|
|
30342
31004
|
examples: [["Wait for a project to be ready", "$0 foundry await-ready --endpoint https://acc.services.ai.azure.com/api/projects/m8t"]]
|
|
30343
31005
|
});
|
|
30344
|
-
endpoint =
|
|
30345
|
-
consecutive =
|
|
30346
|
-
attempts =
|
|
30347
|
-
interval =
|
|
30348
|
-
subscription =
|
|
30349
|
-
output =
|
|
31006
|
+
endpoint = Option54.String("--endpoint");
|
|
31007
|
+
consecutive = Option54.String("--consecutive", "3");
|
|
31008
|
+
attempts = Option54.String("--attempts", "60");
|
|
31009
|
+
interval = Option54.String("--interval", "5");
|
|
31010
|
+
subscription = Option54.String("--subscription");
|
|
31011
|
+
output = Option54.String("--output");
|
|
30350
31012
|
async executeCommand() {
|
|
30351
31013
|
const mode = resolveOutputMode(this.output, this.context.stdout);
|
|
30352
31014
|
const endpoint = typeof this.endpoint === "string" ? this.endpoint : void 0;
|
|
@@ -30380,7 +31042,7 @@ var FoundryAwaitReadyCommand = class extends M8tCommand {
|
|
|
30380
31042
|
};
|
|
30381
31043
|
|
|
30382
31044
|
// src/commands/bootstrap/preflight.ts
|
|
30383
|
-
import { Command as
|
|
31045
|
+
import { Command as Command58, Option as Option55 } from "clipanion";
|
|
30384
31046
|
|
|
30385
31047
|
// ../../packages/telemetry-contract/artifact/tier-map.ts
|
|
30386
31048
|
var EVENT_TIERS = {
|
|
@@ -30432,7 +31094,7 @@ function preflightRenderable(results) {
|
|
|
30432
31094
|
}
|
|
30433
31095
|
var BootstrapPreflightCommand = class extends M8tCommand {
|
|
30434
31096
|
static paths = [["bootstrap", "preflight"]];
|
|
30435
|
-
static usage =
|
|
31097
|
+
static usage = Command58.Usage({
|
|
30436
31098
|
description: "Loudly verify you can install m8t (Owner/UAA + directory admin) and hard-stop if not.",
|
|
30437
31099
|
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.",
|
|
30438
31100
|
examples: [
|
|
@@ -30441,9 +31103,9 @@ var BootstrapPreflightCommand = class extends M8tCommand {
|
|
|
30441
31103
|
["BYO app registration (directory guests)", "$0 bootstrap preflight --client-id <appId>"]
|
|
30442
31104
|
]
|
|
30443
31105
|
});
|
|
30444
|
-
clientId =
|
|
30445
|
-
subscription =
|
|
30446
|
-
location =
|
|
31106
|
+
clientId = Option55.String("--client-id");
|
|
31107
|
+
subscription = Option55.String("--subscription");
|
|
31108
|
+
location = Option55.String("--location", {
|
|
30447
31109
|
description: "Target region. Enables the model-quota check \u2014 without it, quota is not verified."
|
|
30448
31110
|
});
|
|
30449
31111
|
async executeCommand() {
|
|
@@ -30543,7 +31205,7 @@ ${colors.error(" " + why)}
|
|
|
30543
31205
|
import * as fs34 from "fs";
|
|
30544
31206
|
import * as os15 from "os";
|
|
30545
31207
|
import * as path37 from "path";
|
|
30546
|
-
import { Command as
|
|
31208
|
+
import { Command as Command59, Option as Option56 } from "clipanion";
|
|
30547
31209
|
init_errors();
|
|
30548
31210
|
|
|
30549
31211
|
// src/lib/bootstrap-mi.ts
|
|
@@ -30913,7 +31575,7 @@ var ACI_NAME = "m8t-installer";
|
|
|
30913
31575
|
var MI_NAME = "m8t-installer-mi";
|
|
30914
31576
|
var BootstrapLaunchCommand = class extends M8tCommand {
|
|
30915
31577
|
static paths = [["bootstrap", "launch"]];
|
|
30916
|
-
static usage =
|
|
31578
|
+
static usage = Command59.Usage({
|
|
30917
31579
|
description: "Create + authorize the installer managed identity, then kick the cloud installer.",
|
|
30918
31580
|
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.",
|
|
30919
31581
|
examples: [
|
|
@@ -30924,26 +31586,26 @@ var BootstrapLaunchCommand = class extends M8tCommand {
|
|
|
30924
31586
|
["Share a support contact", "$0 bootstrap launch --location eastus2 --contact-email you@example.com --company 'Acme'"]
|
|
30925
31587
|
]
|
|
30926
31588
|
});
|
|
30927
|
-
location =
|
|
30928
|
-
resourceGroup =
|
|
30929
|
-
clientId =
|
|
30930
|
-
subscription =
|
|
30931
|
-
installerTag =
|
|
31589
|
+
location = Option56.String("--location");
|
|
31590
|
+
resourceGroup = Option56.String("--resource-group");
|
|
31591
|
+
clientId = Option56.String("--client-id");
|
|
31592
|
+
subscription = Option56.String("--subscription");
|
|
31593
|
+
installerTag = Option56.String("--installer-tag");
|
|
30932
31594
|
// Full image ref override (registry + repo + tag) — an escape hatch when the
|
|
30933
31595
|
// default org/tag is wrong for the current CLI (e.g. a stale published build).
|
|
30934
31596
|
// Wins over --installer-tag / the pinned default.
|
|
30935
|
-
installerImage =
|
|
30936
|
-
gatewayImageRef =
|
|
30937
|
-
githubAppCreds =
|
|
30938
|
-
contactEmail =
|
|
30939
|
-
company =
|
|
31597
|
+
installerImage = Option56.String("--installer-image");
|
|
31598
|
+
gatewayImageRef = Option56.String("--gateway-image-ref");
|
|
31599
|
+
githubAppCreds = Option56.String("--github-app-creds");
|
|
31600
|
+
contactEmail = Option56.String("--contact-email", { description: "Share a contact email with m8t support. Not sent unless you pass it." });
|
|
31601
|
+
company = Option56.String("--company", { description: "Share your company name with m8t support. Not sent unless you pass it." });
|
|
30940
31602
|
// Value-carrying on purpose: a bare --force would be cargo-culted into
|
|
30941
31603
|
// runbooks and harness prompts and erode the protection, whereas a faithful
|
|
30942
31604
|
// paste can never accidentally carry the victim group's name. It AUTHORIZES
|
|
30943
31605
|
// the target; --resource-group is what CHOOSES it.
|
|
30944
|
-
reinstallInto =
|
|
30945
|
-
org =
|
|
30946
|
-
noBrains =
|
|
31606
|
+
reinstallInto = Option56.String("--reinstall-into", { description: "Consent to installing into --resource-group even though it already holds resources. Must match the target group's name." });
|
|
31607
|
+
org = Option56.String("--org", { description: "Assert the GitHub App on disk is installed on this org; refuses on a mismatch." });
|
|
31608
|
+
noBrains = Option56.Boolean("--no-brains", false, { description: "Install without brain-backed workers. For test and CI rigs; the supported install creates brains." });
|
|
30947
31609
|
async executeCommand() {
|
|
30948
31610
|
const location = typeof this.location === "string" ? this.location : void 0;
|
|
30949
31611
|
if (!location) {
|
|
@@ -31104,7 +31766,7 @@ var BootstrapLaunchCommand = class extends M8tCommand {
|
|
|
31104
31766
|
};
|
|
31105
31767
|
|
|
31106
31768
|
// src/commands/bootstrap/status.ts
|
|
31107
|
-
import { Command as
|
|
31769
|
+
import { Command as Command61, Option as Option58 } from "clipanion";
|
|
31108
31770
|
init_errors();
|
|
31109
31771
|
|
|
31110
31772
|
// src/lib/bootstrap-aci-state.ts
|
|
@@ -31130,6 +31792,7 @@ async function getAciState(opts) {
|
|
|
31130
31792
|
}
|
|
31131
31793
|
|
|
31132
31794
|
// src/lib/bootstrap-finalize.ts
|
|
31795
|
+
init_errors();
|
|
31133
31796
|
import * as fs39 from "fs/promises";
|
|
31134
31797
|
import * as os20 from "os";
|
|
31135
31798
|
import * as path44 from "path";
|
|
@@ -32046,10 +32709,10 @@ var DEFAULT_WORKERS = [
|
|
|
32046
32709
|
{ name: "ezra", label: "Ezra - Azure Expert", brain: "ezra-brain" }
|
|
32047
32710
|
];
|
|
32048
32711
|
function renderInstallSummary(args) {
|
|
32049
|
-
const
|
|
32712
|
+
const open4 = args.webappUrl ? `${args.webappUrl} (or run: m8t open)` : "run: m8t open";
|
|
32050
32713
|
const lines = [
|
|
32051
32714
|
"\u2705 Your m8t platform is ready to use.",
|
|
32052
|
-
` Open it: ${
|
|
32715
|
+
` Open it: ${open4}`,
|
|
32053
32716
|
""
|
|
32054
32717
|
];
|
|
32055
32718
|
if (args.brainOrg) {
|
|
@@ -32073,10 +32736,10 @@ function renderInstallSummary(args) {
|
|
|
32073
32736
|
|
|
32074
32737
|
// src/lib/companion-install.ts
|
|
32075
32738
|
import { constants as constants2 } from "fs";
|
|
32076
|
-
import * as
|
|
32077
|
-
import * as
|
|
32739
|
+
import * as fs38 from "fs/promises";
|
|
32740
|
+
import * as path42 from "path";
|
|
32078
32741
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
32079
|
-
import { execFile, spawn as spawn7 } from "child_process";
|
|
32742
|
+
import { execFile as execFile2, spawn as spawn7 } from "child_process";
|
|
32080
32743
|
|
|
32081
32744
|
// src/lib/companion-artifact.ts
|
|
32082
32745
|
import { createHash as createHash7 } from "crypto";
|
|
@@ -32354,6 +33017,265 @@ async function copyArtifactPayload(sourceRoot, targetRoot, manifest) {
|
|
|
32354
33017
|
await verifyArtifactPayload(targetRoot, manifest);
|
|
32355
33018
|
}
|
|
32356
33019
|
|
|
33020
|
+
// src/lib/companion-download.ts
|
|
33021
|
+
import { createHash as createHash8 } from "crypto";
|
|
33022
|
+
import { execFile } from "child_process";
|
|
33023
|
+
import { createReadStream } from "fs";
|
|
33024
|
+
import * as fs37 from "fs/promises";
|
|
33025
|
+
import * as os18 from "os";
|
|
33026
|
+
import * as path41 from "path";
|
|
33027
|
+
init_errors();
|
|
33028
|
+
var MAX_ASSET_BYTES = 512 * 1024 * 1024;
|
|
33029
|
+
var MAX_ATTEMPTS2 = 3;
|
|
33030
|
+
var BACKOFF_MS = [1e3, 3e3];
|
|
33031
|
+
function megabytes(bytes) {
|
|
33032
|
+
return `${Math.round(bytes / 1e6).toString()} MB`;
|
|
33033
|
+
}
|
|
33034
|
+
function extractArchive(archive, into) {
|
|
33035
|
+
return new Promise((resolve6, reject) => {
|
|
33036
|
+
execFile("tar", ["-xzf", archive, "-C", into], { windowsHide: true }, (error) => {
|
|
33037
|
+
if (error) reject(new Error(`Could not unpack the companion build: ${error.message}`));
|
|
33038
|
+
else resolve6();
|
|
33039
|
+
});
|
|
33040
|
+
});
|
|
33041
|
+
}
|
|
33042
|
+
function companionDownloadDirectory(homeDirectory) {
|
|
33043
|
+
return path41.join(homeDirectory, ".m8t", "companion", "downloads");
|
|
33044
|
+
}
|
|
33045
|
+
function companionAssetFor(component, platform = process.platform, architecture = process.arch) {
|
|
33046
|
+
const target = companionTargetFor(platform, architecture);
|
|
33047
|
+
if (target === null) return null;
|
|
33048
|
+
const pinned = component.targets[target];
|
|
33049
|
+
return pinned ? { target, asset: pinned.asset, sha256: pinned.sha256 } : null;
|
|
33050
|
+
}
|
|
33051
|
+
function tooLarge(asset) {
|
|
33052
|
+
return new LocalCliError({
|
|
33053
|
+
code: "COMPANION_ASSET_TOO_LARGE",
|
|
33054
|
+
message: `${asset} is larger than this command will accept.`
|
|
33055
|
+
});
|
|
33056
|
+
}
|
|
33057
|
+
function parseContentRange(value) {
|
|
33058
|
+
const matched = /^bytes (\d+)-(\d+)\/(\d+|\*)$/u.exec((value ?? "").trim());
|
|
33059
|
+
if (!matched?.[1] || !matched[3]) return null;
|
|
33060
|
+
return {
|
|
33061
|
+
start: Number(matched[1]),
|
|
33062
|
+
total: matched[3] === "*" ? null : Number(matched[3])
|
|
33063
|
+
};
|
|
33064
|
+
}
|
|
33065
|
+
function declaredLength(response) {
|
|
33066
|
+
const raw = response.headers.get("content-length");
|
|
33067
|
+
if (raw === null) return null;
|
|
33068
|
+
const value = Number(raw);
|
|
33069
|
+
return Number.isSafeInteger(value) && value >= 0 ? value : null;
|
|
33070
|
+
}
|
|
33071
|
+
async function usableBytes(partial) {
|
|
33072
|
+
const status = await fs37.lstat(partial).catch(() => null);
|
|
33073
|
+
if (status === null) return 0;
|
|
33074
|
+
if (!status.isFile()) {
|
|
33075
|
+
await fs37.rm(partial, { recursive: true, force: true }).catch(() => void 0);
|
|
33076
|
+
if (await fs37.lstat(partial).then(() => true, () => false)) {
|
|
33077
|
+
throw new LocalCliError({
|
|
33078
|
+
code: "COMPANION_DOWNLOAD_PATH_OCCUPIED",
|
|
33079
|
+
message: `${partial} is not a file and could not be cleared.`,
|
|
33080
|
+
hint: "Something that is not a download is sitting where the companion build goes. Remove it, then run this again."
|
|
33081
|
+
});
|
|
33082
|
+
}
|
|
33083
|
+
return 0;
|
|
33084
|
+
}
|
|
33085
|
+
return status.size;
|
|
33086
|
+
}
|
|
33087
|
+
async function sweepForeignPartials(directory, keep) {
|
|
33088
|
+
const entries = await fs37.readdir(directory).catch(() => []);
|
|
33089
|
+
await Promise.all(
|
|
33090
|
+
entries.filter((name) => name.endsWith(".part") && name !== keep).map((name) => fs37.rm(path41.join(directory, name), { recursive: true, force: true }))
|
|
33091
|
+
);
|
|
33092
|
+
}
|
|
33093
|
+
function sha256File3(filePath) {
|
|
33094
|
+
return new Promise((resolve6, reject) => {
|
|
33095
|
+
const hash = createHash8("sha256");
|
|
33096
|
+
const stream = createReadStream(filePath);
|
|
33097
|
+
stream.on("error", reject);
|
|
33098
|
+
stream.on("data", (chunk) => hash.update(chunk));
|
|
33099
|
+
stream.on("end", () => {
|
|
33100
|
+
resolve6(hash.digest("hex"));
|
|
33101
|
+
});
|
|
33102
|
+
});
|
|
33103
|
+
}
|
|
33104
|
+
async function transferToDisk(input) {
|
|
33105
|
+
let lastError;
|
|
33106
|
+
for (let attempt = 0; attempt < MAX_ATTEMPTS2; attempt++) {
|
|
33107
|
+
let existing = await usableBytes(input.partial);
|
|
33108
|
+
if (existing > input.maxAssetBytes) {
|
|
33109
|
+
await fs37.rm(input.partial, { force: true }).catch(() => void 0);
|
|
33110
|
+
existing = 0;
|
|
33111
|
+
}
|
|
33112
|
+
let received = existing;
|
|
33113
|
+
let answered = false;
|
|
33114
|
+
try {
|
|
33115
|
+
const controller = new AbortController();
|
|
33116
|
+
const response = await input.fetchImpl(input.url, {
|
|
33117
|
+
signal: controller.signal,
|
|
33118
|
+
...existing > 0 ? { headers: { Range: `bytes=${existing.toString()}-` } } : {}
|
|
33119
|
+
});
|
|
33120
|
+
answered = true;
|
|
33121
|
+
if (response.status === 416) {
|
|
33122
|
+
await response.body?.cancel().catch(() => void 0);
|
|
33123
|
+
if (existing > 0 && await sha256File3(input.partial).catch(() => null) === input.expectedDigest) {
|
|
33124
|
+
return;
|
|
33125
|
+
}
|
|
33126
|
+
await fs37.rm(input.partial, { force: true }).catch(() => void 0);
|
|
33127
|
+
lastError = new Error("the interrupted download no longer fits the published asset");
|
|
33128
|
+
continue;
|
|
33129
|
+
}
|
|
33130
|
+
if (!response.ok) {
|
|
33131
|
+
await response.body?.cancel().catch(() => void 0);
|
|
33132
|
+
const failed = new LocalCliError({
|
|
33133
|
+
code: "COMPANION_ASSET_FETCH_FAILED",
|
|
33134
|
+
message: `GET ${input.url} returned HTTP ${response.status.toString()}.`,
|
|
33135
|
+
hint: "The release the platform is pinned to should carry this file. Try again, or run 'm8t companion repair' once the network is back."
|
|
33136
|
+
});
|
|
33137
|
+
if (response.status < 500) throw failed;
|
|
33138
|
+
lastError = failed;
|
|
33139
|
+
if (attempt + 1 < MAX_ATTEMPTS2) {
|
|
33140
|
+
input.onEvent({ kind: "retrying", received: existing, attempt: attempt + 1, cause: "server-error" });
|
|
33141
|
+
await input.delay(BACKOFF_MS[Math.min(attempt, BACKOFF_MS.length - 1)]);
|
|
33142
|
+
}
|
|
33143
|
+
continue;
|
|
33144
|
+
}
|
|
33145
|
+
let offset;
|
|
33146
|
+
let total;
|
|
33147
|
+
if (response.status === 206) {
|
|
33148
|
+
const range = parseContentRange(response.headers.get("content-range"));
|
|
33149
|
+
if (range?.start !== existing) {
|
|
33150
|
+
await response.body?.cancel().catch(() => void 0);
|
|
33151
|
+
await fs37.rm(input.partial, { force: true }).catch(() => void 0);
|
|
33152
|
+
lastError = new Error("the server answered a different range than the one requested");
|
|
33153
|
+
continue;
|
|
33154
|
+
}
|
|
33155
|
+
offset = existing;
|
|
33156
|
+
total = range.total;
|
|
33157
|
+
} else {
|
|
33158
|
+
offset = 0;
|
|
33159
|
+
total = declaredLength(response);
|
|
33160
|
+
}
|
|
33161
|
+
const declaredBody = declaredLength(response);
|
|
33162
|
+
if (total !== null && total > input.maxAssetBytes || declaredBody !== null && declaredBody > input.maxAssetBytes) {
|
|
33163
|
+
await response.body?.cancel().catch(() => void 0);
|
|
33164
|
+
await fs37.rm(input.partial, { force: true }).catch(() => void 0);
|
|
33165
|
+
throw tooLarge(input.asset);
|
|
33166
|
+
}
|
|
33167
|
+
if (offset > 0) input.onEvent({ kind: "resumed", received: offset, total });
|
|
33168
|
+
received = offset;
|
|
33169
|
+
const body = response.body;
|
|
33170
|
+
if (body === null) throw new Error("the response carried no body");
|
|
33171
|
+
const handle = await fs37.open(input.partial, offset > 0 ? "a" : "w", 384);
|
|
33172
|
+
try {
|
|
33173
|
+
for await (const chunk of body) {
|
|
33174
|
+
received += chunk.byteLength;
|
|
33175
|
+
if (received > input.maxAssetBytes) {
|
|
33176
|
+
controller.abort();
|
|
33177
|
+
throw tooLarge(input.asset);
|
|
33178
|
+
}
|
|
33179
|
+
await handle.write(chunk);
|
|
33180
|
+
input.onEvent({ kind: "progress", received, total });
|
|
33181
|
+
}
|
|
33182
|
+
} finally {
|
|
33183
|
+
await handle.close();
|
|
33184
|
+
}
|
|
33185
|
+
if (total !== null && received < total) {
|
|
33186
|
+
throw new Error(
|
|
33187
|
+
`the transfer stopped at ${received.toString()} of ${total.toString()} bytes`
|
|
33188
|
+
);
|
|
33189
|
+
}
|
|
33190
|
+
return;
|
|
33191
|
+
} catch (error) {
|
|
33192
|
+
if (error instanceof LocalCliError) {
|
|
33193
|
+
if (error.code === "COMPANION_ASSET_TOO_LARGE") {
|
|
33194
|
+
await fs37.rm(input.partial, { force: true }).catch(() => void 0);
|
|
33195
|
+
}
|
|
33196
|
+
throw error;
|
|
33197
|
+
}
|
|
33198
|
+
lastError = error;
|
|
33199
|
+
if (attempt + 1 < MAX_ATTEMPTS2) {
|
|
33200
|
+
input.onEvent({
|
|
33201
|
+
kind: "retrying",
|
|
33202
|
+
received,
|
|
33203
|
+
attempt: attempt + 1,
|
|
33204
|
+
cause: answered ? "dropped" : "not-connected"
|
|
33205
|
+
});
|
|
33206
|
+
await input.delay(BACKOFF_MS[Math.min(attempt, BACKOFF_MS.length - 1)]);
|
|
33207
|
+
}
|
|
33208
|
+
}
|
|
33209
|
+
}
|
|
33210
|
+
const kept = await fs37.lstat(input.partial).then(
|
|
33211
|
+
(status) => status.isFile() ? status.size : 0,
|
|
33212
|
+
() => 0
|
|
33213
|
+
);
|
|
33214
|
+
const keptLabel = megabytes(kept);
|
|
33215
|
+
throw new LocalCliError({
|
|
33216
|
+
code: "COMPANION_DOWNLOAD_INCOMPLETE",
|
|
33217
|
+
message: "The companion build could not be downloaded.",
|
|
33218
|
+
...keptLabel === "0 MB" ? {} : { hint: `The ${keptLabel} already downloaded is kept. Run it again to carry on from there.` },
|
|
33219
|
+
cause: lastError
|
|
33220
|
+
});
|
|
33221
|
+
}
|
|
33222
|
+
async function downloadCompanionArtifact(component, deps = {}) {
|
|
33223
|
+
const platform = deps.platform ?? process.platform;
|
|
33224
|
+
const architecture = deps.architecture ?? process.arch;
|
|
33225
|
+
const pinned = companionAssetFor(component, platform, architecture);
|
|
33226
|
+
if (pinned === null) {
|
|
33227
|
+
throw new LocalCliError({
|
|
33228
|
+
code: "COMPANION_NO_BUILD_FOR_HOST",
|
|
33229
|
+
message: `Release ${component.version} carries no desktop companion for ${platform}-${architecture}.`,
|
|
33230
|
+
hint: "The companions are built for macOS and Windows on arm64 and x64."
|
|
33231
|
+
});
|
|
33232
|
+
}
|
|
33233
|
+
const url = releaseAssetUrl(component.tag, pinned.asset);
|
|
33234
|
+
const directory = deps.downloadDirectory ?? companionDownloadDirectory(os18.homedir());
|
|
33235
|
+
const partialName = `${pinned.sha256}.part`;
|
|
33236
|
+
const partial = path41.join(directory, partialName);
|
|
33237
|
+
await fs37.mkdir(directory, { recursive: true, mode: 448 });
|
|
33238
|
+
await sweepForeignPartials(directory, partialName);
|
|
33239
|
+
await transferToDisk({
|
|
33240
|
+
url,
|
|
33241
|
+
asset: pinned.asset,
|
|
33242
|
+
partial,
|
|
33243
|
+
fetchImpl: deps.fetchImpl ?? fetch,
|
|
33244
|
+
onEvent: deps.onEvent ?? (() => void 0),
|
|
33245
|
+
delay: deps.delay ?? ((ms) => new Promise((resolve6) => setTimeout(resolve6, ms))),
|
|
33246
|
+
maxAssetBytes: deps.maxAssetBytes ?? MAX_ASSET_BYTES,
|
|
33247
|
+
expectedDigest: pinned.sha256
|
|
33248
|
+
});
|
|
33249
|
+
const digest = await sha256File3(partial);
|
|
33250
|
+
if (digest !== pinned.sha256) {
|
|
33251
|
+
await fs37.rm(partial, { force: true }).catch(() => void 0);
|
|
33252
|
+
throw new LocalCliError({
|
|
33253
|
+
code: "COMPANION_ASSET_DIGEST_MISMATCH",
|
|
33254
|
+
message: `${pinned.asset} does not match the digest the release pins for it.`,
|
|
33255
|
+
hint: "Nothing was unpacked. This is what a corrupted download or a substituted file looks like \u2014 retry, and report it if it persists."
|
|
33256
|
+
});
|
|
33257
|
+
}
|
|
33258
|
+
const root = await (deps.makeTemporaryDirectory ?? (() => fs37.mkdtemp(path41.join(os18.tmpdir(), "m8t-companion-"))))();
|
|
33259
|
+
const dispose = async () => {
|
|
33260
|
+
await fs37.rm(root, { recursive: true, force: true }).catch(() => void 0);
|
|
33261
|
+
};
|
|
33262
|
+
try {
|
|
33263
|
+
const unpacked = path41.join(root, "unpacked");
|
|
33264
|
+
await fs37.mkdir(unpacked, { recursive: false, mode: 448 });
|
|
33265
|
+
await (deps.extract ?? extractArchive)(partial, unpacked);
|
|
33266
|
+
return {
|
|
33267
|
+
version: component.version,
|
|
33268
|
+
artifactManifestPath: path41.join(unpacked, "artifact-v1.json"),
|
|
33269
|
+
dispose
|
|
33270
|
+
};
|
|
33271
|
+
} catch (error) {
|
|
33272
|
+
await dispose();
|
|
33273
|
+
throw error;
|
|
33274
|
+
} finally {
|
|
33275
|
+
await fs37.rm(partial, { force: true }).catch(() => void 0);
|
|
33276
|
+
}
|
|
33277
|
+
}
|
|
33278
|
+
|
|
32357
33279
|
// src/lib/companion-install.ts
|
|
32358
33280
|
function xmlEscape(value) {
|
|
32359
33281
|
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
@@ -32366,7 +33288,7 @@ function commandError(error, message) {
|
|
|
32366
33288
|
}
|
|
32367
33289
|
function execFileAsync(file2, args) {
|
|
32368
33290
|
return new Promise((resolve6, reject) => {
|
|
32369
|
-
|
|
33291
|
+
execFile2(file2, [...args], { windowsHide: true }, (error) => {
|
|
32370
33292
|
if (error) reject(commandError(error, "Login-item command failed"));
|
|
32371
33293
|
else resolve6();
|
|
32372
33294
|
});
|
|
@@ -32374,7 +33296,7 @@ function execFileAsync(file2, args) {
|
|
|
32374
33296
|
}
|
|
32375
33297
|
function execFileOutput(file2, args) {
|
|
32376
33298
|
return new Promise((resolve6, reject) => {
|
|
32377
|
-
|
|
33299
|
+
execFile2(
|
|
32378
33300
|
file2,
|
|
32379
33301
|
[...args],
|
|
32380
33302
|
{ windowsHide: true },
|
|
@@ -32387,17 +33309,17 @@ function execFileOutput(file2, args) {
|
|
|
32387
33309
|
}
|
|
32388
33310
|
async function setCompanionStartAtLogin(input) {
|
|
32389
33311
|
if (input.platform === "darwin") {
|
|
32390
|
-
const launchAgents =
|
|
32391
|
-
const registration =
|
|
33312
|
+
const launchAgents = path42.join(input.homeDirectory, "Library", "LaunchAgents");
|
|
33313
|
+
const registration = path42.join(
|
|
32392
33314
|
launchAgents,
|
|
32393
33315
|
"com.m8t.companion.plist"
|
|
32394
33316
|
);
|
|
32395
33317
|
await assertNotSymlink(registration, "Start-at-login registration");
|
|
32396
33318
|
if (!input.enabled) {
|
|
32397
|
-
await
|
|
33319
|
+
await fs38.rm(registration, { force: true });
|
|
32398
33320
|
return;
|
|
32399
33321
|
}
|
|
32400
|
-
await
|
|
33322
|
+
await fs38.mkdir(launchAgents, { recursive: true, mode: 448 });
|
|
32401
33323
|
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";
|
|
32402
33324
|
await atomicWriteText(registration, plist, 384);
|
|
32403
33325
|
return;
|
|
@@ -32449,7 +33371,7 @@ async function setCompanionStartAtLogin(input) {
|
|
|
32449
33371
|
}
|
|
32450
33372
|
async function getCompanionStartAtLogin(input) {
|
|
32451
33373
|
if (input.platform === "darwin") {
|
|
32452
|
-
const registration =
|
|
33374
|
+
const registration = path42.join(
|
|
32453
33375
|
input.homeDirectory,
|
|
32454
33376
|
"Library",
|
|
32455
33377
|
"LaunchAgents",
|
|
@@ -32511,26 +33433,29 @@ function assertSupported(options) {
|
|
|
32511
33433
|
}
|
|
32512
33434
|
function companionInstallPaths(options) {
|
|
32513
33435
|
assertSupported(options);
|
|
32514
|
-
const companionState =
|
|
33436
|
+
const companionState = path42.join(
|
|
32515
33437
|
options.homeDirectory,
|
|
32516
33438
|
".m8t",
|
|
32517
33439
|
"companion"
|
|
32518
33440
|
);
|
|
32519
|
-
const targetRoot = options.platform === "darwin" ?
|
|
33441
|
+
const targetRoot = options.platform === "darwin" ? path42.join(
|
|
32520
33442
|
options.homeDirectory,
|
|
32521
33443
|
"Applications",
|
|
32522
33444
|
"m8t Companion.app"
|
|
32523
|
-
) :
|
|
32524
|
-
options.localAppData ??
|
|
33445
|
+
) : path42.join(
|
|
33446
|
+
options.localAppData ?? path42.join(options.homeDirectory, "AppData", "Local"),
|
|
32525
33447
|
"m8t",
|
|
32526
33448
|
"companion",
|
|
32527
33449
|
"app"
|
|
32528
33450
|
);
|
|
32529
33451
|
return {
|
|
32530
33452
|
targetRoot,
|
|
32531
|
-
installManifest:
|
|
32532
|
-
runtimeBinding:
|
|
32533
|
-
preferences:
|
|
33453
|
+
installManifest: path42.join(companionState, "install-v1.json"),
|
|
33454
|
+
runtimeBinding: path42.join(companionState, "runtime-v1.json"),
|
|
33455
|
+
preferences: path42.join(companionState, "preferences-v1.json"),
|
|
33456
|
+
// Asked of the downloader rather than rebuilt here. Two modules computing
|
|
33457
|
+
// one path is how an uninstall comes to sweep a directory nothing writes.
|
|
33458
|
+
downloads: companionDownloadDirectory(options.homeDirectory)
|
|
32534
33459
|
};
|
|
32535
33460
|
}
|
|
32536
33461
|
function exactKeys2(value, keys) {
|
|
@@ -32541,7 +33466,7 @@ function parseInstallManifest(value) {
|
|
|
32541
33466
|
throw new Error("Install manifest schema is invalid");
|
|
32542
33467
|
}
|
|
32543
33468
|
const record = value;
|
|
32544
|
-
if (!exactKeys2(record, INSTALL_KEYS) || record.schemaVersion !== 1 || typeof record.version !== "string" || record.platform !== "darwin" && record.platform !== "win32" || record.architecture !== "arm64" && record.architecture !== "x64" || typeof record.entryRelativePath !== "string" || typeof record.artifactTreeSha256 !== "string" || !/^[a-f0-9]{64}$/u.test(record.artifactTreeSha256) || typeof record.ownedTargetRoot !== "string" || !
|
|
33469
|
+
if (!exactKeys2(record, INSTALL_KEYS) || record.schemaVersion !== 1 || typeof record.version !== "string" || record.platform !== "darwin" && record.platform !== "win32" || record.architecture !== "arm64" && record.architecture !== "x64" || typeof record.entryRelativePath !== "string" || typeof record.artifactTreeSha256 !== "string" || !/^[a-f0-9]{64}$/u.test(record.artifactTreeSha256) || typeof record.ownedTargetRoot !== "string" || !path42.isAbsolute(record.ownedTargetRoot)) {
|
|
32545
33470
|
throw new Error("Install manifest schema is invalid");
|
|
32546
33471
|
}
|
|
32547
33472
|
return record;
|
|
@@ -32551,7 +33476,7 @@ function parseRuntimeBinding(value, platform) {
|
|
|
32551
33476
|
throw new Error("Runtime binding schema is invalid");
|
|
32552
33477
|
}
|
|
32553
33478
|
const record = value;
|
|
32554
|
-
const paths = platform === "win32" ?
|
|
33479
|
+
const paths = platform === "win32" ? path42.win32 : path42.posix;
|
|
32555
33480
|
if (!exactKeys2(record, RUNTIME_KEYS) || record.schemaVersion !== 1 || typeof record.nodeExecutable !== "string" || !paths.isAbsolute(record.nodeExecutable) || typeof record.cliEntry !== "string" || !paths.isAbsolute(record.cliEntry) || typeof record.gatewayOrigin !== "string") {
|
|
32556
33481
|
throw new Error("Runtime binding schema is invalid");
|
|
32557
33482
|
}
|
|
@@ -32576,7 +33501,7 @@ function validateGatewayOrigin(value) {
|
|
|
32576
33501
|
}
|
|
32577
33502
|
async function assertNotSymlink(filePath, kind) {
|
|
32578
33503
|
try {
|
|
32579
|
-
if ((await
|
|
33504
|
+
if ((await fs38.lstat(filePath)).isSymbolicLink()) {
|
|
32580
33505
|
throw new Error(`${kind} is a symbolic link`);
|
|
32581
33506
|
}
|
|
32582
33507
|
} catch (error) {
|
|
@@ -32585,20 +33510,20 @@ async function assertNotSymlink(filePath, kind) {
|
|
|
32585
33510
|
}
|
|
32586
33511
|
}
|
|
32587
33512
|
async function assertOwnedDirectoryChain(anchor, targetDirectory) {
|
|
32588
|
-
const relative4 =
|
|
32589
|
-
if (relative4 === ".." || relative4.startsWith(`..${
|
|
33513
|
+
const relative4 = path42.relative(anchor, targetDirectory);
|
|
33514
|
+
if (relative4 === ".." || relative4.startsWith(`..${path42.sep}`) || path42.isAbsolute(relative4)) {
|
|
32590
33515
|
throw new Error("Companion owned directory escapes its trusted anchor");
|
|
32591
33516
|
}
|
|
32592
|
-
const segments = relative4.split(
|
|
33517
|
+
const segments = relative4.split(path42.sep).filter(Boolean);
|
|
32593
33518
|
let current = anchor;
|
|
32594
|
-
const anchorStatus = await
|
|
33519
|
+
const anchorStatus = await fs38.lstat(anchor);
|
|
32595
33520
|
if (anchorStatus.isSymbolicLink() || !anchorStatus.isDirectory()) {
|
|
32596
33521
|
throw new Error("Companion owned directory anchor is unsafe");
|
|
32597
33522
|
}
|
|
32598
33523
|
for (const segment of segments) {
|
|
32599
|
-
current =
|
|
33524
|
+
current = path42.join(current, segment);
|
|
32600
33525
|
try {
|
|
32601
|
-
const status = await
|
|
33526
|
+
const status = await fs38.lstat(current);
|
|
32602
33527
|
if (status.isSymbolicLink() || !status.isDirectory()) {
|
|
32603
33528
|
throw new Error("Companion owned directory contains a symbolic link");
|
|
32604
33529
|
}
|
|
@@ -32611,19 +33536,19 @@ async function assertOwnedDirectoryChain(anchor, targetDirectory) {
|
|
|
32611
33536
|
async function assertOwnedParents(options, paths) {
|
|
32612
33537
|
await assertOwnedDirectoryChain(
|
|
32613
33538
|
options.homeDirectory,
|
|
32614
|
-
|
|
33539
|
+
path42.dirname(paths.installManifest)
|
|
32615
33540
|
);
|
|
32616
|
-
const targetAnchor = options.platform === "win32" ? options.localAppData ??
|
|
32617
|
-
await assertOwnedDirectoryChain(targetAnchor,
|
|
33541
|
+
const targetAnchor = options.platform === "win32" ? options.localAppData ?? path42.join(options.homeDirectory, "AppData", "Local") : options.homeDirectory;
|
|
33542
|
+
await assertOwnedDirectoryChain(targetAnchor, path42.dirname(paths.targetRoot));
|
|
32618
33543
|
}
|
|
32619
33544
|
async function readRegularText(filePath, maxBytes) {
|
|
32620
|
-
const before = await
|
|
33545
|
+
const before = await fs38.lstat(filePath);
|
|
32621
33546
|
if (before.isSymbolicLink() || !before.isFile()) {
|
|
32622
33547
|
throw new Error("Companion state file is not a regular file");
|
|
32623
33548
|
}
|
|
32624
33549
|
let handle;
|
|
32625
33550
|
try {
|
|
32626
|
-
handle = await
|
|
33551
|
+
handle = await fs38.open(
|
|
32627
33552
|
filePath,
|
|
32628
33553
|
constants2.O_RDONLY | constants2.O_NOFOLLOW
|
|
32629
33554
|
);
|
|
@@ -32646,27 +33571,27 @@ async function atomicWriteJson(filePath, value) {
|
|
|
32646
33571
|
}
|
|
32647
33572
|
async function atomicWriteText(filePath, contents, mode) {
|
|
32648
33573
|
await assertNotSymlink(filePath, "Companion state file");
|
|
32649
|
-
await
|
|
33574
|
+
await fs38.mkdir(path42.dirname(filePath), {
|
|
32650
33575
|
recursive: true,
|
|
32651
33576
|
mode: 448
|
|
32652
33577
|
});
|
|
32653
33578
|
const temporary = `${filePath}.${randomUUID3()}.tmp`;
|
|
32654
33579
|
try {
|
|
32655
|
-
await
|
|
33580
|
+
await fs38.writeFile(temporary, contents, {
|
|
32656
33581
|
mode,
|
|
32657
33582
|
flag: "wx"
|
|
32658
33583
|
});
|
|
32659
|
-
await
|
|
32660
|
-
await
|
|
33584
|
+
await fs38.rename(temporary, filePath);
|
|
33585
|
+
await fs38.chmod(filePath, mode).catch(() => void 0);
|
|
32661
33586
|
} finally {
|
|
32662
|
-
await
|
|
33587
|
+
await fs38.rm(temporary, { force: true }).catch(() => void 0);
|
|
32663
33588
|
}
|
|
32664
33589
|
}
|
|
32665
33590
|
async function realRegularFile(filePath, executable) {
|
|
32666
|
-
const real = await
|
|
32667
|
-
const stat5 = await
|
|
33591
|
+
const real = await fs38.realpath(filePath);
|
|
33592
|
+
const stat5 = await fs38.stat(real);
|
|
32668
33593
|
if (!stat5.isFile()) throw new Error("Companion launch target is not a file");
|
|
32669
|
-
await
|
|
33594
|
+
await fs38.access(real, executable ? constants2.X_OK : constants2.R_OK);
|
|
32670
33595
|
return real;
|
|
32671
33596
|
}
|
|
32672
33597
|
function defaultLaunch(executable) {
|
|
@@ -32682,7 +33607,7 @@ async function companionIsRunning(platform, executable) {
|
|
|
32682
33607
|
if (platform !== "win32") return false;
|
|
32683
33608
|
let handle;
|
|
32684
33609
|
try {
|
|
32685
|
-
handle = await
|
|
33610
|
+
handle = await fs38.open(executable, "r+");
|
|
32686
33611
|
} catch (error) {
|
|
32687
33612
|
const code = error.code;
|
|
32688
33613
|
if (code === "ENOENT") return false;
|
|
@@ -32727,17 +33652,17 @@ async function statusCompanion(options) {
|
|
|
32727
33652
|
reason: error instanceof Error ? error.message : "Unsafe owned directory"
|
|
32728
33653
|
};
|
|
32729
33654
|
}
|
|
32730
|
-
let
|
|
33655
|
+
let installed2;
|
|
32731
33656
|
try {
|
|
32732
|
-
|
|
33657
|
+
installed2 = await readInstalled(options);
|
|
32733
33658
|
} catch (error) {
|
|
32734
33659
|
return {
|
|
32735
33660
|
state: "needs-repair",
|
|
32736
33661
|
reason: error instanceof Error ? error.message : "Invalid install manifest"
|
|
32737
33662
|
};
|
|
32738
33663
|
}
|
|
32739
|
-
if (!
|
|
32740
|
-
const { paths, install } =
|
|
33664
|
+
if (!installed2) return { state: "not-installed" };
|
|
33665
|
+
const { paths, install } = installed2;
|
|
32741
33666
|
if (install.platform !== options.platform || install.architecture !== options.architecture || install.ownedTargetRoot !== paths.targetRoot) {
|
|
32742
33667
|
return {
|
|
32743
33668
|
state: "needs-repair",
|
|
@@ -32763,7 +33688,7 @@ async function statusCompanion(options) {
|
|
|
32763
33688
|
realRegularFile(runtime.nodeExecutable, options.platform !== "win32"),
|
|
32764
33689
|
realRegularFile(runtime.cliEntry, false)
|
|
32765
33690
|
]);
|
|
32766
|
-
const executable =
|
|
33691
|
+
const executable = path42.join(
|
|
32767
33692
|
paths.targetRoot,
|
|
32768
33693
|
...install.entryRelativePath.split("/")
|
|
32769
33694
|
);
|
|
@@ -32796,7 +33721,7 @@ async function snapshotFile(filePath) {
|
|
|
32796
33721
|
}
|
|
32797
33722
|
async function restoreFile(filePath, bytes) {
|
|
32798
33723
|
if (bytes === null) {
|
|
32799
|
-
await
|
|
33724
|
+
await fs38.rm(filePath, { force: true });
|
|
32800
33725
|
} else {
|
|
32801
33726
|
await atomicWriteJson(filePath, JSON.parse(bytes.toString("utf8")));
|
|
32802
33727
|
}
|
|
@@ -32811,8 +33736,8 @@ async function converge(options, force) {
|
|
|
32811
33736
|
if (artifactManifest.platform !== options.platform || artifactManifest.architecture !== options.architecture) {
|
|
32812
33737
|
throw new Error("Companion artifact does not match this OS and architecture");
|
|
32813
33738
|
}
|
|
32814
|
-
const payloadRoot =
|
|
32815
|
-
|
|
33739
|
+
const payloadRoot = path42.join(
|
|
33740
|
+
path42.dirname(options.artifactManifestPath),
|
|
32816
33741
|
"payload"
|
|
32817
33742
|
);
|
|
32818
33743
|
await verifyArtifactSource(payloadRoot, artifactManifest);
|
|
@@ -32832,7 +33757,7 @@ async function converge(options, force) {
|
|
|
32832
33757
|
if (priorInstalled) {
|
|
32833
33758
|
await assertCompanionNotRunning(
|
|
32834
33759
|
options,
|
|
32835
|
-
|
|
33760
|
+
path42.join(
|
|
32836
33761
|
priorInstalled.paths.targetRoot,
|
|
32837
33762
|
...priorInstalled.install.entryRelativePath.split("/")
|
|
32838
33763
|
)
|
|
@@ -32840,7 +33765,7 @@ async function converge(options, force) {
|
|
|
32840
33765
|
}
|
|
32841
33766
|
if (current.state === "not-installed") {
|
|
32842
33767
|
try {
|
|
32843
|
-
await
|
|
33768
|
+
await fs38.lstat(paths.targetRoot);
|
|
32844
33769
|
throw new Error(
|
|
32845
33770
|
"The fixed companion target exists without an owned install manifest"
|
|
32846
33771
|
);
|
|
@@ -32851,7 +33776,7 @@ async function converge(options, force) {
|
|
|
32851
33776
|
const stage = `${paths.targetRoot}.m8t-stage-${randomUUID3()}`;
|
|
32852
33777
|
const backup = `${paths.targetRoot}.m8t-backup-${randomUUID3()}`;
|
|
32853
33778
|
const copy = options.copyPayload ?? copyArtifactPayload;
|
|
32854
|
-
await
|
|
33779
|
+
await fs38.mkdir(path42.dirname(paths.targetRoot), {
|
|
32855
33780
|
recursive: true,
|
|
32856
33781
|
mode: 448
|
|
32857
33782
|
});
|
|
@@ -32868,12 +33793,12 @@ async function converge(options, force) {
|
|
|
32868
33793
|
try {
|
|
32869
33794
|
await copy(payloadRoot, stage, artifactManifest);
|
|
32870
33795
|
try {
|
|
32871
|
-
await
|
|
33796
|
+
await fs38.rename(paths.targetRoot, backup);
|
|
32872
33797
|
movedPrior = true;
|
|
32873
33798
|
} catch (error) {
|
|
32874
33799
|
if (error.code !== "ENOENT") throw error;
|
|
32875
33800
|
}
|
|
32876
|
-
await
|
|
33801
|
+
await fs38.rename(stage, paths.targetRoot);
|
|
32877
33802
|
installedStage = true;
|
|
32878
33803
|
const nodeExecutable = await realRegularFile(
|
|
32879
33804
|
options.nodeExecutable,
|
|
@@ -32915,7 +33840,7 @@ async function converge(options, force) {
|
|
|
32915
33840
|
await readClosedJson(paths.runtimeBinding),
|
|
32916
33841
|
options.platform
|
|
32917
33842
|
);
|
|
32918
|
-
const executable =
|
|
33843
|
+
const executable = path42.join(
|
|
32919
33844
|
paths.targetRoot,
|
|
32920
33845
|
...artifactManifest.entryRelativePath.split("/")
|
|
32921
33846
|
);
|
|
@@ -32925,7 +33850,7 @@ async function converge(options, force) {
|
|
|
32925
33850
|
platform: options.platform,
|
|
32926
33851
|
executable: ownedExecutable
|
|
32927
33852
|
}));
|
|
32928
|
-
priorLoginExecutable = priorInstalled ?
|
|
33853
|
+
priorLoginExecutable = priorInstalled ? path42.join(
|
|
32929
33854
|
priorInstalled.paths.targetRoot,
|
|
32930
33855
|
...priorInstalled.install.entryRelativePath.split("/")
|
|
32931
33856
|
) : executable;
|
|
@@ -32936,7 +33861,7 @@ async function converge(options, force) {
|
|
|
32936
33861
|
);
|
|
32937
33862
|
loginChanged = true;
|
|
32938
33863
|
await (options.launch ?? defaultLaunch)(executable);
|
|
32939
|
-
await
|
|
33864
|
+
await fs38.rm(backup, { recursive: true, force: true });
|
|
32940
33865
|
return {
|
|
32941
33866
|
state: "installed",
|
|
32942
33867
|
version: artifactManifest.version,
|
|
@@ -32950,15 +33875,15 @@ async function converge(options, force) {
|
|
|
32950
33875
|
() => void 0
|
|
32951
33876
|
);
|
|
32952
33877
|
}
|
|
32953
|
-
await
|
|
33878
|
+
await fs38.rm(stage, { recursive: true, force: true }).catch(() => void 0);
|
|
32954
33879
|
if (installedStage) {
|
|
32955
|
-
await
|
|
33880
|
+
await fs38.rm(paths.targetRoot, {
|
|
32956
33881
|
recursive: true,
|
|
32957
33882
|
force: true
|
|
32958
33883
|
}).catch(() => void 0);
|
|
32959
33884
|
}
|
|
32960
33885
|
if (movedPrior) {
|
|
32961
|
-
await
|
|
33886
|
+
await fs38.rename(backup, paths.targetRoot).catch(() => void 0);
|
|
32962
33887
|
}
|
|
32963
33888
|
await Promise.all([
|
|
32964
33889
|
restoreFile(paths.installManifest, snapshots[0]),
|
|
@@ -32978,32 +33903,36 @@ async function uninstallCompanion(options) {
|
|
|
32978
33903
|
assertSupported(options);
|
|
32979
33904
|
const expectedPaths = companionInstallPaths(options);
|
|
32980
33905
|
await assertOwnedParents(options, expectedPaths);
|
|
32981
|
-
const
|
|
32982
|
-
|
|
32983
|
-
|
|
33906
|
+
const remove = options.removeOwnedPath ?? (async (ownedPath, recursive) => {
|
|
33907
|
+
await fs38.rm(ownedPath, { recursive, force: true });
|
|
33908
|
+
});
|
|
33909
|
+
const installed2 = await readInstalled(options);
|
|
33910
|
+
if (!installed2) {
|
|
33911
|
+
await remove(expectedPaths.downloads, true);
|
|
33912
|
+
return { state: "not-installed" };
|
|
33913
|
+
}
|
|
33914
|
+
const { paths, install } = installed2;
|
|
32984
33915
|
if (install.ownedTargetRoot !== paths.targetRoot) {
|
|
32985
33916
|
throw new Error("Refusing to remove a non-owned companion target");
|
|
32986
33917
|
}
|
|
32987
|
-
const executable =
|
|
33918
|
+
const executable = path42.join(
|
|
32988
33919
|
paths.targetRoot,
|
|
32989
33920
|
...install.entryRelativePath.split("/")
|
|
32990
33921
|
);
|
|
32991
33922
|
await assertCompanionNotRunning(options, executable);
|
|
32992
33923
|
await (options.setStartAtLogin ?? (() => Promise.resolve()))(executable, false);
|
|
32993
|
-
const remove = options.removeOwnedPath ?? (async (ownedPath, recursive) => {
|
|
32994
|
-
await fs37.rm(ownedPath, { recursive, force: true });
|
|
32995
|
-
});
|
|
32996
33924
|
await remove(paths.targetRoot, true);
|
|
32997
33925
|
await remove(paths.installManifest, false);
|
|
32998
33926
|
await remove(paths.runtimeBinding, false);
|
|
32999
33927
|
await remove(paths.preferences, false);
|
|
33928
|
+
await remove(paths.downloads, true);
|
|
33000
33929
|
return { state: "not-installed" };
|
|
33001
33930
|
}
|
|
33002
33931
|
|
|
33003
33932
|
// src/commands/companion/install.ts
|
|
33004
33933
|
import * as os19 from "os";
|
|
33005
33934
|
import * as path43 from "path";
|
|
33006
|
-
import { Command as
|
|
33935
|
+
import { Command as Command60, Option as Option57 } from "clipanion";
|
|
33007
33936
|
|
|
33008
33937
|
// src/lib/companion-channel.ts
|
|
33009
33938
|
async function readCompanionRelease(source = { url: CHANNEL_LATEST_URL }, deps = {}) {
|
|
@@ -33017,84 +33946,55 @@ async function readCompanionRelease(source = { url: CHANNEL_LATEST_URL }, deps =
|
|
|
33017
33946
|
};
|
|
33018
33947
|
}
|
|
33019
33948
|
|
|
33020
|
-
// src/lib/companion-download.ts
|
|
33021
|
-
|
|
33022
|
-
|
|
33023
|
-
|
|
33024
|
-
|
|
33025
|
-
|
|
33026
|
-
|
|
33027
|
-
|
|
33028
|
-
|
|
33029
|
-
|
|
33030
|
-
|
|
33031
|
-
|
|
33032
|
-
|
|
33033
|
-
|
|
33034
|
-
|
|
33035
|
-
}
|
|
33036
|
-
|
|
33037
|
-
|
|
33038
|
-
|
|
33039
|
-
|
|
33040
|
-
|
|
33041
|
-
|
|
33042
|
-
|
|
33043
|
-
|
|
33044
|
-
|
|
33045
|
-
|
|
33046
|
-
|
|
33047
|
-
|
|
33048
|
-
|
|
33049
|
-
|
|
33050
|
-
|
|
33051
|
-
|
|
33052
|
-
|
|
33053
|
-
|
|
33054
|
-
|
|
33055
|
-
|
|
33056
|
-
|
|
33057
|
-
|
|
33058
|
-
|
|
33059
|
-
|
|
33060
|
-
|
|
33061
|
-
|
|
33062
|
-
|
|
33063
|
-
|
|
33064
|
-
|
|
33065
|
-
|
|
33066
|
-
|
|
33067
|
-
|
|
33068
|
-
});
|
|
33069
|
-
}
|
|
33070
|
-
const digest = createHash8("sha256").update(bytes).digest("hex");
|
|
33071
|
-
if (digest !== pinned.sha256) {
|
|
33072
|
-
throw new LocalCliError({
|
|
33073
|
-
code: "COMPANION_ASSET_DIGEST_MISMATCH",
|
|
33074
|
-
message: `${pinned.asset} does not match the digest the release pins for it.`,
|
|
33075
|
-
hint: "Nothing was unpacked. This is what a corrupted download or a substituted file looks like \u2014 retry, and report it if it persists."
|
|
33076
|
-
});
|
|
33077
|
-
}
|
|
33078
|
-
const root = await (deps.makeTemporaryDirectory ?? (() => fs38.mkdtemp(path42.join(os18.tmpdir(), "m8t-companion-"))))();
|
|
33079
|
-
const dispose = async () => {
|
|
33080
|
-
await fs38.rm(root, { recursive: true, force: true }).catch(() => void 0);
|
|
33949
|
+
// src/lib/companion-download-progress.ts
|
|
33950
|
+
var STEP_FRACTION = 0.25;
|
|
33951
|
+
var INTERVAL_MS = 3e4;
|
|
33952
|
+
function downloadProgressLines(options) {
|
|
33953
|
+
const now = options.now ?? Date.now;
|
|
33954
|
+
let lastStep = 0;
|
|
33955
|
+
let lastReceived = 0;
|
|
33956
|
+
let lastEmitAt = null;
|
|
33957
|
+
const emit = (line2) => {
|
|
33958
|
+
lastEmitAt = now();
|
|
33959
|
+
options.write(line2);
|
|
33960
|
+
};
|
|
33961
|
+
return (event) => {
|
|
33962
|
+
if (event.kind === "retrying") {
|
|
33963
|
+
emit(
|
|
33964
|
+
event.cause === "not-connected" ? " Couldn't connect \u2014 retrying.\n" : event.cause === "server-error" ? " The download service answered with an error \u2014 retrying.\n" : ` The connection dropped at ${megabytes(event.received)} \u2014 retrying.
|
|
33965
|
+
`
|
|
33966
|
+
);
|
|
33967
|
+
return;
|
|
33968
|
+
}
|
|
33969
|
+
if (event.kind === "resumed") {
|
|
33970
|
+
if (event.total !== null && event.total > 0) {
|
|
33971
|
+
lastStep = Math.floor(event.received / event.total / STEP_FRACTION);
|
|
33972
|
+
}
|
|
33973
|
+
lastReceived = event.received;
|
|
33974
|
+
emit(` Resuming an interrupted download from ${megabytes(event.received)}.
|
|
33975
|
+
`);
|
|
33976
|
+
return;
|
|
33977
|
+
}
|
|
33978
|
+
lastEmitAt ??= now();
|
|
33979
|
+
if (event.received < lastReceived) lastStep = 0;
|
|
33980
|
+
lastReceived = event.received;
|
|
33981
|
+
const total = event.total !== null && event.total > 0 ? event.total : null;
|
|
33982
|
+
if (total === null) {
|
|
33983
|
+
if (now() - lastEmitAt < INTERVAL_MS) return;
|
|
33984
|
+
emit(` ${megabytes(event.received)} downloaded.
|
|
33985
|
+
`);
|
|
33986
|
+
return;
|
|
33987
|
+
}
|
|
33988
|
+
const fraction = event.received / total;
|
|
33989
|
+
if (fraction >= 1) return;
|
|
33990
|
+
const step = Math.floor(fraction / STEP_FRACTION);
|
|
33991
|
+
if (step <= lastStep && now() - lastEmitAt < INTERVAL_MS) return;
|
|
33992
|
+
lastStep = step;
|
|
33993
|
+
emit(
|
|
33994
|
+
` ${Math.floor(fraction * 100).toString()}% \xB7 ${megabytes(event.received)} of ${megabytes(total)}
|
|
33995
|
+
`
|
|
33996
|
+
);
|
|
33081
33997
|
};
|
|
33082
|
-
try {
|
|
33083
|
-
const archive = path42.join(root, pinned.asset);
|
|
33084
|
-
await fs38.writeFile(archive, bytes, { mode: 384 });
|
|
33085
|
-
const unpacked = path42.join(root, "unpacked");
|
|
33086
|
-
await fs38.mkdir(unpacked, { recursive: false, mode: 448 });
|
|
33087
|
-
await (deps.extract ?? extractArchive)(archive, unpacked);
|
|
33088
|
-
await fs38.rm(archive, { force: true });
|
|
33089
|
-
return {
|
|
33090
|
-
version: component.version,
|
|
33091
|
-
artifactManifestPath: path42.join(unpacked, "artifact-v1.json"),
|
|
33092
|
-
dispose
|
|
33093
|
-
};
|
|
33094
|
-
} catch (error) {
|
|
33095
|
-
await dispose();
|
|
33096
|
-
throw error;
|
|
33097
|
-
}
|
|
33098
33998
|
}
|
|
33099
33999
|
|
|
33100
34000
|
// src/commands/companion/install.ts
|
|
@@ -33115,7 +34015,7 @@ function defaultLocalCompanionInstallOptions() {
|
|
|
33115
34015
|
};
|
|
33116
34016
|
}
|
|
33117
34017
|
async function convergeCompanionFromChannel(converge2, options = {}) {
|
|
33118
|
-
const { from: stagedDirectory, resourceGroup, platformVersion } = options;
|
|
34018
|
+
const { from: stagedDirectory, resourceGroup, platformVersion, onDownloadEvent } = options;
|
|
33119
34019
|
let artifactManifestPath;
|
|
33120
34020
|
let dispose = () => Promise.resolve();
|
|
33121
34021
|
if (stagedDirectory !== void 0) {
|
|
@@ -33125,7 +34025,9 @@ async function convergeCompanionFromChannel(converge2, options = {}) {
|
|
|
33125
34025
|
platformVersion !== void 0 ? { channel: true, version: platformTag(platformVersion) } : { url: CHANNEL_LATEST_URL }
|
|
33126
34026
|
);
|
|
33127
34027
|
if (release === null) return { state: "not-released" };
|
|
33128
|
-
const build = await downloadCompanionArtifact(release.component
|
|
34028
|
+
const build = await downloadCompanionArtifact(release.component, {
|
|
34029
|
+
...onDownloadEvent !== void 0 ? { onEvent: onDownloadEvent } : {}
|
|
34030
|
+
});
|
|
33129
34031
|
artifactManifestPath = build.artifactManifestPath;
|
|
33130
34032
|
dispose = build.dispose;
|
|
33131
34033
|
}
|
|
@@ -33161,7 +34063,7 @@ Version: ${state.version}
|
|
|
33161
34063
|
}
|
|
33162
34064
|
var CompanionInstallCommand = class extends M8tCommand {
|
|
33163
34065
|
static paths = [["companion", "install"]];
|
|
33164
|
-
static usage =
|
|
34066
|
+
static usage = Command60.Usage({
|
|
33165
34067
|
description: "Install the desktop companions for this user from the release channel.",
|
|
33166
34068
|
examples: [
|
|
33167
34069
|
["Install the released build", "$0 companion install"],
|
|
@@ -33171,18 +34073,20 @@ var CompanionInstallCommand = class extends M8tCommand {
|
|
|
33171
34073
|
]
|
|
33172
34074
|
]
|
|
33173
34075
|
});
|
|
33174
|
-
from =
|
|
34076
|
+
from = Option57.String("--from", {
|
|
33175
34077
|
description: "A locally staged build directory instead of the released one."
|
|
33176
34078
|
});
|
|
33177
|
-
resourceGroup =
|
|
34079
|
+
resourceGroup = Option57.String("--resource-group", {
|
|
33178
34080
|
description: "Which deployment to bind to, when the subscription holds more than one."
|
|
33179
34081
|
});
|
|
33180
34082
|
async executeCommand() {
|
|
34083
|
+
const stdout = this.context.stdout;
|
|
33181
34084
|
return runCompanionInstallCommand(
|
|
33182
|
-
|
|
34085
|
+
stdout,
|
|
33183
34086
|
() => convergeCompanionFromChannel(installCompanion, {
|
|
33184
34087
|
...this.from !== void 0 ? { from: this.from } : {},
|
|
33185
|
-
...this.resourceGroup !== void 0 ? { resourceGroup: this.resourceGroup } : {}
|
|
34088
|
+
...this.resourceGroup !== void 0 ? { resourceGroup: this.resourceGroup } : {},
|
|
34089
|
+
onDownloadEvent: downloadProgressLines({ write: (line2) => stdout.write(line2) })
|
|
33186
34090
|
})
|
|
33187
34091
|
);
|
|
33188
34092
|
}
|
|
@@ -33202,7 +34106,7 @@ async function looksLikeCheckout(dir2) {
|
|
|
33202
34106
|
return false;
|
|
33203
34107
|
}
|
|
33204
34108
|
}
|
|
33205
|
-
var
|
|
34109
|
+
var defaultFinalizeDeps = Object.freeze({
|
|
33206
34110
|
discoverGateway,
|
|
33207
34111
|
writeConfig,
|
|
33208
34112
|
ensureGatewayRedirectUri,
|
|
@@ -33210,12 +34114,13 @@ var defaultDeps3 = {
|
|
|
33210
34114
|
reactiveSeed: reactiveSeedOnInstallComplete,
|
|
33211
34115
|
companionsSupportHost,
|
|
33212
34116
|
companionStatus: () => statusCompanion(defaultLocalCompanionInstallOptions()),
|
|
33213
|
-
convergeCompanion: (platformVersion) => convergeCompanionFromChannel(installCompanion, {
|
|
33214
|
-
...platformVersion !== void 0 ? { platformVersion } : {}
|
|
34117
|
+
convergeCompanion: (platformVersion, onDownloadEvent) => convergeCompanionFromChannel(installCompanion, {
|
|
34118
|
+
...platformVersion !== void 0 ? { platformVersion } : {},
|
|
34119
|
+
...onDownloadEvent !== void 0 ? { onDownloadEvent } : {}
|
|
33215
34120
|
}),
|
|
33216
34121
|
homedir: () => os20.homedir()
|
|
33217
|
-
};
|
|
33218
|
-
async function finalizeInstall(args, deps =
|
|
34122
|
+
});
|
|
34123
|
+
async function finalizeInstall(args, deps = defaultFinalizeDeps) {
|
|
33219
34124
|
const markerDir = path44.join(deps.homedir(), ".m8t");
|
|
33220
34125
|
const markerPath = path44.join(markerDir, "repo-root");
|
|
33221
34126
|
const cwd = process.cwd();
|
|
@@ -33353,7 +34258,10 @@ ${colors.field("Then open a brand new chat/session")} \u2014 new skills + MCP se
|
|
|
33353
34258
|
`
|
|
33354
34259
|
);
|
|
33355
34260
|
try {
|
|
33356
|
-
const companion = await deps.convergeCompanion(
|
|
34261
|
+
const companion = await deps.convergeCompanion(
|
|
34262
|
+
pinned,
|
|
34263
|
+
downloadProgressLines({ write: args.stdout })
|
|
34264
|
+
);
|
|
33357
34265
|
if (companion.state === "installed") {
|
|
33358
34266
|
args.stdout("Desktop companions installed - they are at the edge of your screen.\n\n");
|
|
33359
34267
|
} else if (companion.state === "not-released") {
|
|
@@ -33369,7 +34277,7 @@ They arrive with a platform release \u2014 run: m8t platform update
|
|
|
33369
34277
|
);
|
|
33370
34278
|
}
|
|
33371
34279
|
} catch (e) {
|
|
33372
|
-
const reason = e instanceof Error ? e.message : String(e);
|
|
34280
|
+
const reason = e instanceof LocalCliError && e.code === "COMPANION_DOWNLOAD_INCOMPLETE" && e.hint !== void 0 ? e.hint : e instanceof Error ? e.message : String(e);
|
|
33373
34281
|
args.stdout(
|
|
33374
34282
|
`Platform installation succeeded, but the desktop companions need repair.
|
|
33375
34283
|
Run: m8t companion repair
|
|
@@ -33390,7 +34298,7 @@ var BootstrapStatusCommand = class extends M8tCommand {
|
|
|
33390
34298
|
// runbooks and shakedown recipes that a founder may already be part-way
|
|
33391
34299
|
// through — it prints a deprecation notice and does the right thing.
|
|
33392
34300
|
static paths = [["bootstrap", "status"], ["bootstrap", "finish"]];
|
|
33393
|
-
static usage =
|
|
34301
|
+
static usage = Command61.Usage({
|
|
33394
34302
|
description: "Show the cloud installer's live status (phase, progress, result) \u2014 and finish the local setup when it lands.",
|
|
33395
34303
|
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.",
|
|
33396
34304
|
examples: [
|
|
@@ -33399,12 +34307,12 @@ var BootstrapStatusCommand = class extends M8tCommand {
|
|
|
33399
34307
|
["Redo the local setup on a finished install", "$0 bootstrap status --finalize --repo-root /path/to/m8t"]
|
|
33400
34308
|
]
|
|
33401
34309
|
});
|
|
33402
|
-
watch =
|
|
33403
|
-
output =
|
|
33404
|
-
repoRoot =
|
|
33405
|
-
finalize =
|
|
33406
|
-
subscription =
|
|
33407
|
-
resourceGroup =
|
|
34310
|
+
watch = Option58.Boolean("--watch", false);
|
|
34311
|
+
output = Option58.String("--output");
|
|
34312
|
+
repoRoot = Option58.String("--repo-root", { description: "The m8t clone to point local tools at (default: the current directory)." });
|
|
34313
|
+
finalize = Option58.Boolean("--finalize", false, { description: "Complete the local setup against an already-done install, without watching." });
|
|
34314
|
+
subscription = Option58.String("--subscription");
|
|
34315
|
+
resourceGroup = Option58.String("--resource-group");
|
|
33408
34316
|
async executeCommand() {
|
|
33409
34317
|
const state = await readBootstrapState();
|
|
33410
34318
|
if (!state) {
|
|
@@ -33537,7 +34445,7 @@ function formatStatus(d) {
|
|
|
33537
34445
|
}
|
|
33538
34446
|
|
|
33539
34447
|
// src/commands/bootstrap/reap.ts
|
|
33540
|
-
import { Command as
|
|
34448
|
+
import { Command as Command62, Option as Option59 } from "clipanion";
|
|
33541
34449
|
init_errors();
|
|
33542
34450
|
|
|
33543
34451
|
// src/lib/bootstrap-reap.ts
|
|
@@ -33631,7 +34539,7 @@ async function reapInstaller(opts) {
|
|
|
33631
34539
|
// src/commands/bootstrap/reap.ts
|
|
33632
34540
|
var BootstrapReapCommand = class extends M8tCommand {
|
|
33633
34541
|
static paths = [["bootstrap", "reap"]];
|
|
33634
|
-
static usage =
|
|
34542
|
+
static usage = Command62.Usage({
|
|
33635
34543
|
description: "Tear down the installer scaffolding (ACI \u2192 MI \u2192 its role assignments) after a successful install.",
|
|
33636
34544
|
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.",
|
|
33637
34545
|
examples: [
|
|
@@ -33641,9 +34549,9 @@ var BootstrapReapCommand = class extends M8tCommand {
|
|
|
33641
34549
|
["\u2026and delete them", "$0 bootstrap reap --sweep-orphans --yes"]
|
|
33642
34550
|
]
|
|
33643
34551
|
});
|
|
33644
|
-
force =
|
|
33645
|
-
sweepOrphans =
|
|
33646
|
-
yes =
|
|
34552
|
+
force = Option59.Boolean("--force", false, { description: "Reap even if the install failed or never reported a status." });
|
|
34553
|
+
sweepOrphans = Option59.Boolean("--sweep-orphans", false, { description: "Subscription-wide: find m8t role assignments orphaned by earlier installs. Lists only, unless --yes." });
|
|
34554
|
+
yes = Option59.Boolean("--yes", false, { description: "With --sweep-orphans, actually delete what the sweep found." });
|
|
33647
34555
|
async executeCommand() {
|
|
33648
34556
|
if (this.sweepOrphans === true) {
|
|
33649
34557
|
const { subscriptionId: sub } = await getAzAccount();
|
|
@@ -33737,7 +34645,7 @@ Found ${String(total)} orphaned assignment(s) (${breakdown}) (dry-run). ${colors
|
|
|
33737
34645
|
};
|
|
33738
34646
|
|
|
33739
34647
|
// src/commands/bootstrap/ui.ts
|
|
33740
|
-
import { Command as
|
|
34648
|
+
import { Command as Command63, Option as Option60 } from "clipanion";
|
|
33741
34649
|
|
|
33742
34650
|
// src/lib/bootstrap-ui.ts
|
|
33743
34651
|
import * as fs40 from "fs";
|
|
@@ -33812,7 +34720,7 @@ function renderDeprecationNotice() {
|
|
|
33812
34720
|
}
|
|
33813
34721
|
var BootstrapUiCommand = class extends M8tCommand {
|
|
33814
34722
|
static paths = [["bootstrap", "ui"]];
|
|
33815
|
-
static usage =
|
|
34723
|
+
static usage = Command63.Usage({
|
|
33816
34724
|
description: "DEPRECATED \u2014 does nothing. Use `m8t bootstrap profile` instead.",
|
|
33817
34725
|
details: [
|
|
33818
34726
|
"The local onboarding chat has been retired. Your details are collected by",
|
|
@@ -33832,14 +34740,14 @@ var BootstrapUiCommand = class extends M8tCommand {
|
|
|
33832
34740
|
// Accepted and ignored, deliberately: removing them would turn an old script's
|
|
33833
34741
|
// harmless no-op into an "unknown option" failure mid-install. CLI-rewrite: drop
|
|
33834
34742
|
// the whole surface when the rewrite lands.
|
|
33835
|
-
repoRoot =
|
|
33836
|
-
port =
|
|
33837
|
-
endpoint =
|
|
33838
|
-
prepOnly =
|
|
33839
|
-
skipInstall =
|
|
33840
|
-
foreground =
|
|
33841
|
-
voice =
|
|
33842
|
-
stop =
|
|
34743
|
+
repoRoot = Option60.String("--repo-root", { description: "Ignored (deprecated)." });
|
|
34744
|
+
port = Option60.String("--port", "3000", { description: "Ignored (deprecated)." });
|
|
34745
|
+
endpoint = Option60.String("--endpoint", { description: "Ignored (deprecated)." });
|
|
34746
|
+
prepOnly = Option60.Boolean("--prep-only", false, { description: "Ignored (deprecated)." });
|
|
34747
|
+
skipInstall = Option60.Boolean("--skip-install", false, { description: "Ignored (deprecated)." });
|
|
34748
|
+
foreground = Option60.Boolean("--foreground", false, { description: "Ignored (deprecated)." });
|
|
34749
|
+
voice = Option60.Boolean("--voice", false, { description: "Ignored (deprecated)." });
|
|
34750
|
+
stop = Option60.Boolean("--stop", false, {
|
|
33843
34751
|
description: "Shut down a local chat UI left running by an earlier version of this command."
|
|
33844
34752
|
});
|
|
33845
34753
|
// Not `async`: there is nothing left to await. Everything this command used to
|
|
@@ -33861,7 +34769,7 @@ var BootstrapUiCommand = class extends M8tCommand {
|
|
|
33861
34769
|
|
|
33862
34770
|
// src/commands/bootstrap/profile.ts
|
|
33863
34771
|
import * as readline3 from "readline/promises";
|
|
33864
|
-
import { Command as
|
|
34772
|
+
import { Command as Command64, Option as Option61 } from "clipanion";
|
|
33865
34773
|
|
|
33866
34774
|
// src/lib/profile-collect.ts
|
|
33867
34775
|
init_errors();
|
|
@@ -34018,9 +34926,9 @@ async function openChatInvite(deps = {}) {
|
|
|
34018
34926
|
if (!invite.ok) return CHAT_UNAVAILABLE_LINE;
|
|
34019
34927
|
const print = deps.print === true;
|
|
34020
34928
|
if (!print) {
|
|
34021
|
-
const
|
|
34929
|
+
const open4 = deps.open ?? tryOpenUrl;
|
|
34022
34930
|
try {
|
|
34023
|
-
await
|
|
34931
|
+
await open4(invite.url);
|
|
34024
34932
|
} catch {
|
|
34025
34933
|
}
|
|
34026
34934
|
}
|
|
@@ -34030,7 +34938,7 @@ async function openChatInvite(deps = {}) {
|
|
|
34030
34938
|
// src/commands/bootstrap/profile.ts
|
|
34031
34939
|
var BootstrapProfileCommand = class extends M8tCommand {
|
|
34032
34940
|
static paths = [["bootstrap", "profile"]];
|
|
34033
|
-
static usage =
|
|
34941
|
+
static usage = Command64.Usage({
|
|
34034
34942
|
description: "Confirm your email + your Microsoft startup advisor, and open Ezra to talk to while the install runs.",
|
|
34035
34943
|
details: [
|
|
34036
34944
|
"Run after `m8t bootstrap launch`, in parallel with `status --watch`. Records the two",
|
|
@@ -34050,12 +34958,12 @@ var BootstrapProfileCommand = class extends M8tCommand {
|
|
|
34050
34958
|
["Skip the browser hand-off", "$0 bootstrap profile --founder-email you@example.com --print"]
|
|
34051
34959
|
]
|
|
34052
34960
|
});
|
|
34053
|
-
founderEmail =
|
|
34054
|
-
advisorName =
|
|
34055
|
-
advisorEmail =
|
|
34056
|
-
noAdvisor =
|
|
34057
|
-
noChat =
|
|
34058
|
-
print =
|
|
34961
|
+
founderEmail = Option61.String("--founder-email", { description: "The founder's contact address \u2014 Ezra copies them on its outbound mail." });
|
|
34962
|
+
advisorName = Option61.String("--advisor-name", { description: "Their Microsoft startup advisor's name." });
|
|
34963
|
+
advisorEmail = Option61.String("--advisor-email", { description: "Their Microsoft startup advisor's email." });
|
|
34964
|
+
noAdvisor = Option61.Boolean("--no-advisor", false, { description: "Record that they have no startup advisor to add (or don't know it yet)." });
|
|
34965
|
+
noChat = Option61.Boolean("--no-chat", false, { description: "Record the answers without opening the hosted Ezra." });
|
|
34966
|
+
print = Option61.Boolean("--print", false, { description: "Print the chat link instead of opening a browser (headless / SSH)." });
|
|
34059
34967
|
async executeCommand() {
|
|
34060
34968
|
const stdin = this.context.stdin;
|
|
34061
34969
|
const stdout = this.context.stdout;
|
|
@@ -34148,10 +35056,10 @@ var BootstrapProfileCommand = class extends M8tCommand {
|
|
|
34148
35056
|
};
|
|
34149
35057
|
|
|
34150
35058
|
// src/commands/bootstrap/seed-profile.ts
|
|
34151
|
-
import { Command as
|
|
35059
|
+
import { Command as Command65, Option as Option62 } from "clipanion";
|
|
34152
35060
|
var BootstrapSeedProfileCommand = class extends M8tCommand {
|
|
34153
35061
|
static paths = [["bootstrap", "seed-profile"]];
|
|
34154
|
-
static usage =
|
|
35062
|
+
static usage = Command65.Usage({
|
|
34155
35063
|
description: "Seed your advisors' brains with how to reach you, from what you confirmed at `m8t bootstrap profile`.",
|
|
34156
35064
|
details: [
|
|
34157
35065
|
"Renders memory/founder.md + memory/company-profile.md (+ their MEMORY.md index lines)",
|
|
@@ -34168,11 +35076,11 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
|
|
|
34168
35076
|
["Seed now (idempotent)", "$0 bootstrap seed-profile"]
|
|
34169
35077
|
]
|
|
34170
35078
|
});
|
|
34171
|
-
endpoint =
|
|
34172
|
-
brain =
|
|
34173
|
-
watch =
|
|
34174
|
-
timeout =
|
|
34175
|
-
githubAppCreds =
|
|
35079
|
+
endpoint = Option62.String("--endpoint", { description: "Override the Foundry endpoint (else read from the install status)." });
|
|
35080
|
+
brain = Option62.String("--brain", { description: "Seed only this one brain repo, instead of both <org>/stacey-brain and <org>/ezra-brain." });
|
|
35081
|
+
watch = Option62.Boolean("--watch", false, { description: "Poll until the intake completes (or --timeout)." });
|
|
35082
|
+
timeout = Option62.String("--timeout", { description: "Watch timeout in minutes (default 20)." });
|
|
35083
|
+
githubAppCreds = Option62.String("--github-app-creds");
|
|
34176
35084
|
async executeCommand() {
|
|
34177
35085
|
const ctx = await resolveSeedContext({
|
|
34178
35086
|
endpointOverride: typeof this.endpoint === "string" ? this.endpoint : void 0,
|
|
@@ -34260,7 +35168,7 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
|
|
|
34260
35168
|
import * as fs41 from "fs";
|
|
34261
35169
|
import * as os22 from "os";
|
|
34262
35170
|
import * as path46 from "path";
|
|
34263
|
-
import { Command as
|
|
35171
|
+
import { Command as Command66, Option as Option63 } from "clipanion";
|
|
34264
35172
|
init_errors();
|
|
34265
35173
|
|
|
34266
35174
|
// src/lib/telemetry-enroll.ts
|
|
@@ -34342,7 +35250,7 @@ async function resolveKeyVaultName(containerAppResourceId) {
|
|
|
34342
35250
|
}
|
|
34343
35251
|
var TelemetryEnrollCommand = class extends M8tCommand {
|
|
34344
35252
|
static paths = [["telemetry", "enroll"]];
|
|
34345
|
-
static usage =
|
|
35253
|
+
static usage = Command66.Usage({
|
|
34346
35254
|
description: "Enroll this installation for operational telemetry (pre-existing installs).",
|
|
34347
35255
|
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.",
|
|
34348
35256
|
examples: [
|
|
@@ -34350,11 +35258,11 @@ var TelemetryEnrollCommand = class extends M8tCommand {
|
|
|
34350
35258
|
["Enroll and share a support contact", "$0 telemetry enroll --contact-email you@example.com --company 'Acme'"]
|
|
34351
35259
|
]
|
|
34352
35260
|
});
|
|
34353
|
-
company =
|
|
34354
|
-
contactEmail =
|
|
34355
|
-
subscription =
|
|
34356
|
-
resourceGroup =
|
|
34357
|
-
force =
|
|
35261
|
+
company = Option63.String("--company", { description: "Share your company name with m8t support. Not sent unless you pass it." });
|
|
35262
|
+
contactEmail = Option63.String("--contact-email", { description: "Share a contact email with m8t support. Not sent unless you pass it." });
|
|
35263
|
+
subscription = Option63.String("--subscription", { description: "Azure subscription id used to find your deployment. Never sent to m8t." });
|
|
35264
|
+
resourceGroup = Option63.String("--resource-group", { description: "Resource group to disambiguate discovery, if you have more than one m8t deployment." });
|
|
35265
|
+
force = Option63.Boolean("--force", false, { description: "Enroll even when a key is already stored. Use only when m8t support asks you to." });
|
|
34358
35266
|
async executeCommand() {
|
|
34359
35267
|
const account = await getAzAccount();
|
|
34360
35268
|
const subscriptionId = (typeof this.subscription === "string" ? this.subscription : void 0) ?? account.subscriptionId;
|
|
@@ -34404,7 +35312,7 @@ var TelemetryEnrollCommand = class extends M8tCommand {
|
|
|
34404
35312
|
};
|
|
34405
35313
|
|
|
34406
35314
|
// src/commands/companion/bridge.ts
|
|
34407
|
-
import { Command as
|
|
35315
|
+
import { Command as Command67, Option as Option64 } from "clipanion";
|
|
34408
35316
|
|
|
34409
35317
|
// ../../packages/companion-bridge-contract/src/index.ts
|
|
34410
35318
|
var COMPANION_MESSAGE_MAX_CODE_POINTS = 32768;
|
|
@@ -35166,9 +36074,9 @@ async function rosterCompanions(sink, deps = {}) {
|
|
|
35166
36074
|
controller.signal
|
|
35167
36075
|
);
|
|
35168
36076
|
const now = (deps.now ?? Date.now)();
|
|
35169
|
-
const
|
|
36077
|
+
const installed2 = await (deps.installEpoch ?? readInstallEpoch)();
|
|
35170
36078
|
const activeSince = new Date(
|
|
35171
|
-
|
|
36079
|
+
installed2 === null || installed2 > now ? now : installed2
|
|
35172
36080
|
).toISOString();
|
|
35173
36081
|
const PRESENTABLE = [
|
|
35174
36082
|
{ personaKey: "startup-advisor", displayName: "Stacey", portraitKey: "stacey" },
|
|
@@ -35591,11 +36499,11 @@ async function fetchConversationTurns(request, sink, deps, decode) {
|
|
|
35591
36499
|
|
|
35592
36500
|
// src/lib/companion-update-check.ts
|
|
35593
36501
|
async function checkCompanionUpdate(options, deps = {}) {
|
|
35594
|
-
const
|
|
36502
|
+
const installed2 = await statusCompanion(options).then((state) => state.state === "installed" ? state.version : null).catch(() => null);
|
|
35595
36503
|
const release = await (deps.readRelease ?? readCompanionRelease)().catch(() => null);
|
|
35596
36504
|
return {
|
|
35597
36505
|
type: "update",
|
|
35598
|
-
installed,
|
|
36506
|
+
installed: installed2,
|
|
35599
36507
|
available: release?.component.version ?? null,
|
|
35600
36508
|
severity: release === null ? null : release.severity
|
|
35601
36509
|
};
|
|
@@ -35787,7 +36695,7 @@ async function runCompanionBridgeServe(stdin, stdout, stderr, deps = {}) {
|
|
|
35787
36695
|
}
|
|
35788
36696
|
|
|
35789
36697
|
// src/commands/companion/bridge.ts
|
|
35790
|
-
var
|
|
36698
|
+
var defaultDeps3 = {
|
|
35791
36699
|
roster: rosterCompanions,
|
|
35792
36700
|
send: sendCompanionMessage,
|
|
35793
36701
|
converse: converseCompanionMessage,
|
|
@@ -35825,7 +36733,7 @@ function exitFor(terminal) {
|
|
|
35825
36733
|
}
|
|
35826
36734
|
return 2;
|
|
35827
36735
|
}
|
|
35828
|
-
async function runCompanionBridge(stdin, stdout, stderr, deps =
|
|
36736
|
+
async function runCompanionBridge(stdin, stdout, stderr, deps = defaultDeps3) {
|
|
35829
36737
|
let request;
|
|
35830
36738
|
try {
|
|
35831
36739
|
request = parseRequestLine(await readSingleRequest(stdin));
|
|
@@ -35860,14 +36768,14 @@ async function runCompanionBridge(stdin, stdout, stderr, deps = defaultDeps4) {
|
|
|
35860
36768
|
return 3;
|
|
35861
36769
|
}
|
|
35862
36770
|
}
|
|
35863
|
-
var CompanionBridgeCommand = class extends
|
|
36771
|
+
var CompanionBridgeCommand = class extends Command67 {
|
|
35864
36772
|
static paths = [["companion", "_bridge"]];
|
|
35865
36773
|
/**
|
|
35866
36774
|
* One process serving many requests instead of one per request, so the
|
|
35867
36775
|
* session keeps its authenticated context between them. A CLI predating the
|
|
35868
36776
|
* flag rejects it outright, which is how the app knows to fall back.
|
|
35869
36777
|
*/
|
|
35870
|
-
serve =
|
|
36778
|
+
serve = Option64.Boolean("--serve", false);
|
|
35871
36779
|
async execute() {
|
|
35872
36780
|
if (this.serve) {
|
|
35873
36781
|
return runCompanionBridgeServe(
|
|
@@ -35885,7 +36793,7 @@ var CompanionBridgeCommand = class extends Command66 {
|
|
|
35885
36793
|
};
|
|
35886
36794
|
|
|
35887
36795
|
// src/commands/companion/status.ts
|
|
35888
|
-
import { Command as
|
|
36796
|
+
import { Command as Command68 } from "clipanion";
|
|
35889
36797
|
async function withTimeout(work, ms) {
|
|
35890
36798
|
let timer;
|
|
35891
36799
|
try {
|
|
@@ -35957,7 +36865,7 @@ Run: m8t companion install
|
|
|
35957
36865
|
}
|
|
35958
36866
|
var CompanionStatusCommand = class extends M8tCommand {
|
|
35959
36867
|
static paths = [["companion", "status"]];
|
|
35960
|
-
static usage =
|
|
36868
|
+
static usage = Command68.Usage({
|
|
35961
36869
|
description: "Verify the installed desktop companion without launching it."
|
|
35962
36870
|
});
|
|
35963
36871
|
async executeCommand() {
|
|
@@ -35971,7 +36879,7 @@ var CompanionStatusCommand = class extends M8tCommand {
|
|
|
35971
36879
|
};
|
|
35972
36880
|
|
|
35973
36881
|
// src/commands/companion/repair.ts
|
|
35974
|
-
import { Command as
|
|
36882
|
+
import { Command as Command69, Option as Option65 } from "clipanion";
|
|
35975
36883
|
async function runCompanionRepairCommand(stdout, repair) {
|
|
35976
36884
|
const state = await repair();
|
|
35977
36885
|
if (state.state === "not-released") {
|
|
@@ -35990,24 +36898,28 @@ async function runCompanionRepairCommand(stdout, repair) {
|
|
|
35990
36898
|
}
|
|
35991
36899
|
var CompanionRepairCommand = class extends M8tCommand {
|
|
35992
36900
|
static paths = [["companion", "repair"]];
|
|
35993
|
-
static usage =
|
|
36901
|
+
static usage = Command69.Usage({
|
|
35994
36902
|
description: "Restore the desktop companions and start-at-login state."
|
|
35995
36903
|
});
|
|
35996
|
-
resourceGroup =
|
|
36904
|
+
resourceGroup = Option65.String("--resource-group", {
|
|
35997
36905
|
description: "Which deployment to bind to, when the subscription holds more than one."
|
|
35998
36906
|
});
|
|
35999
36907
|
async executeCommand() {
|
|
36908
|
+
const stdout = this.context.stdout;
|
|
36000
36909
|
return runCompanionRepairCommand(
|
|
36001
|
-
|
|
36910
|
+
stdout,
|
|
36002
36911
|
() => convergeCompanionFromChannel(repairCompanion, {
|
|
36003
|
-
...this.resourceGroup !== void 0 ? { resourceGroup: this.resourceGroup } : {}
|
|
36912
|
+
...this.resourceGroup !== void 0 ? { resourceGroup: this.resourceGroup } : {},
|
|
36913
|
+
// Repair is the command a founder reaches for after an interrupted
|
|
36914
|
+
// install, so it is the one most likely to pick up a partial download.
|
|
36915
|
+
onDownloadEvent: downloadProgressLines({ write: (line2) => stdout.write(line2) })
|
|
36004
36916
|
})
|
|
36005
36917
|
);
|
|
36006
36918
|
}
|
|
36007
36919
|
};
|
|
36008
36920
|
|
|
36009
36921
|
// src/commands/companion/uninstall.ts
|
|
36010
|
-
import { Command as
|
|
36922
|
+
import { Command as Command70 } from "clipanion";
|
|
36011
36923
|
async function runCompanionUninstallCommand(stdout, uninstall) {
|
|
36012
36924
|
const state = await uninstall();
|
|
36013
36925
|
if (state.state !== "not-installed") {
|
|
@@ -36019,7 +36931,7 @@ async function runCompanionUninstallCommand(stdout, uninstall) {
|
|
|
36019
36931
|
}
|
|
36020
36932
|
var CompanionUninstallCommand = class extends M8tCommand {
|
|
36021
36933
|
static paths = [["companion", "uninstall"]];
|
|
36022
|
-
static usage =
|
|
36934
|
+
static usage = Command70.Usage({
|
|
36023
36935
|
description: "Remove only this user's desktop companion installation."
|
|
36024
36936
|
});
|
|
36025
36937
|
async executeCommand() {
|
|
@@ -36032,6 +36944,7 @@ var CompanionUninstallCommand = class extends M8tCommand {
|
|
|
36032
36944
|
};
|
|
36033
36945
|
|
|
36034
36946
|
// src/cli.ts
|
|
36947
|
+
installFoundryDnsShim();
|
|
36035
36948
|
var cli = new Cli({
|
|
36036
36949
|
binaryName: "m8t",
|
|
36037
36950
|
binaryLabel: "m8t CLI \u2014 manage m8t deployments",
|
|
@@ -36081,6 +36994,7 @@ cli.register(PlatformRequestUpdateCommand);
|
|
|
36081
36994
|
cli.register(PlatformSeedStampCommand);
|
|
36082
36995
|
cli.register(PlatformPolicyCommand);
|
|
36083
36996
|
cli.register(PlatformEnableCostReportCommand);
|
|
36997
|
+
cli.register(PlatformEmailCommand);
|
|
36084
36998
|
cli.register(PlatformEnableAutoUpdateCommand);
|
|
36085
36999
|
cli.register(DeployCommand);
|
|
36086
37000
|
cli.register(AgentDeployAdvisorCommand);
|