@m8t-stack/cli 0.2.84 → 0.2.85
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 +784 -1405
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -10,11 +10,6 @@ var __export = (target, all) => {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
// src/lib/errors.ts
|
|
13
|
-
var errors_exports = {};
|
|
14
|
-
__export(errors_exports, {
|
|
15
|
-
ApiCallError: () => ApiCallError,
|
|
16
|
-
LocalCliError: () => LocalCliError
|
|
17
|
-
});
|
|
18
13
|
var LocalCliError, ApiCallError;
|
|
19
14
|
var init_errors = __esm({
|
|
20
15
|
"src/lib/errors.ts"() {
|
|
@@ -1467,7 +1462,7 @@ var init_enable_hosted_brain = __esm({
|
|
|
1467
1462
|
import { Builtins, Cli } from "clipanion";
|
|
1468
1463
|
|
|
1469
1464
|
// src/lib/package-version.ts
|
|
1470
|
-
var CLI_VERSION = "0.2.
|
|
1465
|
+
var CLI_VERSION = "0.2.85";
|
|
1471
1466
|
|
|
1472
1467
|
// src/lib/render-error.ts
|
|
1473
1468
|
init_errors();
|
|
@@ -9940,17 +9935,17 @@ var Files2 = class extends APIResource {
|
|
|
9940
9935
|
async waitForProcessing(id, { pollInterval = 5e3, maxWait = 30 * 60 * 1e3 } = {}) {
|
|
9941
9936
|
const TERMINAL_STATES = /* @__PURE__ */ new Set(["processed", "error", "deleted"]);
|
|
9942
9937
|
const start = Date.now();
|
|
9943
|
-
let
|
|
9944
|
-
while (!
|
|
9938
|
+
let file2 = await this.retrieve(id);
|
|
9939
|
+
while (!file2.status || !TERMINAL_STATES.has(file2.status)) {
|
|
9945
9940
|
await sleep(pollInterval);
|
|
9946
|
-
|
|
9941
|
+
file2 = await this.retrieve(id);
|
|
9947
9942
|
if (Date.now() - start > maxWait) {
|
|
9948
9943
|
throw new APIConnectionTimeoutError({
|
|
9949
9944
|
message: `Giving up on waiting for file ${id} to finish processing after ${maxWait} milliseconds.`
|
|
9950
9945
|
});
|
|
9951
9946
|
}
|
|
9952
9947
|
}
|
|
9953
|
-
return
|
|
9948
|
+
return file2;
|
|
9954
9949
|
}
|
|
9955
9950
|
};
|
|
9956
9951
|
|
|
@@ -11742,8 +11737,8 @@ var Files3 = class extends APIResource {
|
|
|
11742
11737
|
* Attach a file to the given vector store and wait for it to be processed.
|
|
11743
11738
|
*/
|
|
11744
11739
|
async createAndPoll(vectorStoreId, body, options) {
|
|
11745
|
-
const
|
|
11746
|
-
return await this.poll(vectorStoreId,
|
|
11740
|
+
const file2 = await this.create(vectorStoreId, body, options);
|
|
11741
|
+
return await this.poll(vectorStoreId, file2.id, options);
|
|
11747
11742
|
}
|
|
11748
11743
|
/**
|
|
11749
11744
|
* Wait for the vector store file to finish processing.
|
|
@@ -11763,8 +11758,8 @@ var Files3 = class extends APIResource {
|
|
|
11763
11758
|
const fileResponse = await this.retrieve(fileID, {
|
|
11764
11759
|
vector_store_id: vectorStoreID
|
|
11765
11760
|
}, { ...options, headers }).withResponse();
|
|
11766
|
-
const
|
|
11767
|
-
switch (
|
|
11761
|
+
const file2 = fileResponse.data;
|
|
11762
|
+
switch (file2.status) {
|
|
11768
11763
|
case "in_progress":
|
|
11769
11764
|
let sleepInterval = 5e3;
|
|
11770
11765
|
if (options?.pollIntervalMs) {
|
|
@@ -11782,7 +11777,7 @@ var Files3 = class extends APIResource {
|
|
|
11782
11777
|
break;
|
|
11783
11778
|
case "failed":
|
|
11784
11779
|
case "completed":
|
|
11785
|
-
return
|
|
11780
|
+
return file2;
|
|
11786
11781
|
}
|
|
11787
11782
|
}
|
|
11788
11783
|
}
|
|
@@ -11792,15 +11787,15 @@ var Files3 = class extends APIResource {
|
|
|
11792
11787
|
* Note the file will be asynchronously processed (you can use the alternative
|
|
11793
11788
|
* polling helper method to wait for processing to complete).
|
|
11794
11789
|
*/
|
|
11795
|
-
async upload(vectorStoreId,
|
|
11796
|
-
const fileInfo = await this._client.files.create({ file, purpose: "assistants" }, options);
|
|
11790
|
+
async upload(vectorStoreId, file2, options) {
|
|
11791
|
+
const fileInfo = await this._client.files.create({ file: file2, purpose: "assistants" }, options);
|
|
11797
11792
|
return this.create(vectorStoreId, { file_id: fileInfo.id }, options);
|
|
11798
11793
|
}
|
|
11799
11794
|
/**
|
|
11800
11795
|
* Add a file to a vector store and poll until processing is complete.
|
|
11801
11796
|
*/
|
|
11802
|
-
async uploadAndPoll(vectorStoreId,
|
|
11803
|
-
const fileInfo = await this.upload(vectorStoreId,
|
|
11797
|
+
async uploadAndPoll(vectorStoreId, file2, options) {
|
|
11798
|
+
const fileInfo = await this.upload(vectorStoreId, file2, options);
|
|
11804
11799
|
return await this.poll(vectorStoreId, fileInfo.id, options);
|
|
11805
11800
|
}
|
|
11806
11801
|
/**
|
|
@@ -13205,9 +13200,9 @@ async function readConfig() {
|
|
|
13205
13200
|
}
|
|
13206
13201
|
}
|
|
13207
13202
|
async function writeConfig(cfg) {
|
|
13208
|
-
const
|
|
13203
|
+
const dir2 = configDir();
|
|
13209
13204
|
const configPath = getConfigPath();
|
|
13210
|
-
await fs.mkdir(
|
|
13205
|
+
await fs.mkdir(dir2, { recursive: true });
|
|
13211
13206
|
const yaml = stringifyYaml(cfg, { indent: 2 });
|
|
13212
13207
|
const tmp = `${configPath}.tmp`;
|
|
13213
13208
|
await fs.writeFile(tmp, yaml, "utf8");
|
|
@@ -14676,11 +14671,11 @@ function orgFileSegment(org) {
|
|
|
14676
14671
|
return org.replace(/[^A-Za-z0-9-]+/g, "-");
|
|
14677
14672
|
}
|
|
14678
14673
|
function brainAppFilePaths(home, org, appId) {
|
|
14679
|
-
const
|
|
14674
|
+
const dir2 = path4.join(home, ".m8t");
|
|
14680
14675
|
const stem = `github-app-${orgFileSegment(org)}-${appId}`;
|
|
14681
14676
|
return {
|
|
14682
|
-
pemPath: path4.join(
|
|
14683
|
-
sidecarPath: path4.join(
|
|
14677
|
+
pemPath: path4.join(dir2, `${stem}.pem`),
|
|
14678
|
+
sidecarPath: path4.join(dir2, `${stem}.json`)
|
|
14684
14679
|
};
|
|
14685
14680
|
}
|
|
14686
14681
|
function isPreservable(v) {
|
|
@@ -15290,15 +15285,15 @@ function agentYamlPath(agentName, home = os3.homedir()) {
|
|
|
15290
15285
|
return path7.join(home, ".m8t", "foundry", `${agentName}.yaml`);
|
|
15291
15286
|
}
|
|
15292
15287
|
function readAgentYaml(agentName, home) {
|
|
15293
|
-
const
|
|
15294
|
-
if (!fs6.existsSync(
|
|
15295
|
-
const text = fs6.readFileSync(
|
|
15288
|
+
const file2 = agentYamlPath(agentName, home);
|
|
15289
|
+
if (!fs6.existsSync(file2)) return null;
|
|
15290
|
+
const text = fs6.readFileSync(file2, "utf8");
|
|
15296
15291
|
return parseYaml3(text);
|
|
15297
15292
|
}
|
|
15298
15293
|
function writeAgentYaml(agentName, data, home) {
|
|
15299
|
-
const
|
|
15300
|
-
fs6.mkdirSync(path7.dirname(
|
|
15301
|
-
fs6.writeFileSync(
|
|
15294
|
+
const file2 = agentYamlPath(agentName, home);
|
|
15295
|
+
fs6.mkdirSync(path7.dirname(file2), { recursive: true });
|
|
15296
|
+
fs6.writeFileSync(file2, stringifyYaml3(data), { mode: 384 });
|
|
15302
15297
|
}
|
|
15303
15298
|
function patchAgentYaml(agentName, patch, home) {
|
|
15304
15299
|
const current = readAgentYaml(agentName, home);
|
|
@@ -15881,9 +15876,9 @@ var AGENT_DIR = "agent";
|
|
|
15881
15876
|
var AGENT_SEED_DIRS = [...SEED_PATHS, "memory"];
|
|
15882
15877
|
var AGENT_SEED_FILES = ["NOTICE"];
|
|
15883
15878
|
var PERSONA_REL = `${AGENT_DIR}/persona.md`;
|
|
15884
|
-
function walk(base,
|
|
15885
|
-
for (const entry of fs11.readdirSync(
|
|
15886
|
-
const abs = path11.join(
|
|
15879
|
+
function walk(base, dir2, out) {
|
|
15880
|
+
for (const entry of fs11.readdirSync(dir2, { withFileTypes: true })) {
|
|
15881
|
+
const abs = path11.join(dir2, entry.name);
|
|
15887
15882
|
if (entry.isDirectory()) walk(base, abs, out);
|
|
15888
15883
|
else if (entry.isFile()) out.push(path11.relative(base, abs).split(path11.sep).join("/"));
|
|
15889
15884
|
}
|
|
@@ -15899,13 +15894,13 @@ function collectAgentContent(repoDir) {
|
|
|
15899
15894
|
}
|
|
15900
15895
|
const rels = [];
|
|
15901
15896
|
walk(repoDir, path11.join(repoDir, AGENT_DIR), rels);
|
|
15902
|
-
for (const
|
|
15903
|
-
const abs = path11.join(repoDir,
|
|
15897
|
+
for (const dir2 of AGENT_SEED_DIRS) {
|
|
15898
|
+
const abs = path11.join(repoDir, dir2);
|
|
15904
15899
|
if (fs11.existsSync(abs) && fs11.statSync(abs).isDirectory()) walk(repoDir, abs, rels);
|
|
15905
15900
|
}
|
|
15906
|
-
for (const
|
|
15907
|
-
const abs = path11.join(repoDir,
|
|
15908
|
-
if (fs11.existsSync(abs) && fs11.statSync(abs).isFile()) rels.push(
|
|
15901
|
+
for (const file2 of AGENT_SEED_FILES) {
|
|
15902
|
+
const abs = path11.join(repoDir, file2);
|
|
15903
|
+
if (fs11.existsSync(abs) && fs11.statSync(abs).isFile()) rels.push(file2);
|
|
15909
15904
|
}
|
|
15910
15905
|
const files = /* @__PURE__ */ new Map();
|
|
15911
15906
|
for (const rel of rels) files.set(rel, fs11.readFileSync(path11.join(repoDir, rel)));
|
|
@@ -16161,8 +16156,8 @@ async function derivePin(args) {
|
|
|
16161
16156
|
// src/lib/brain-seed.ts
|
|
16162
16157
|
function resolveSeed(name) {
|
|
16163
16158
|
const root = resolvePlatformRepoRoot("Seeding a brain", ["brain-seeds"]);
|
|
16164
|
-
const
|
|
16165
|
-
if (!fs13.existsSync(
|
|
16159
|
+
const dir2 = path13.join(root, "brain-seeds", name);
|
|
16160
|
+
if (!fs13.existsSync(dir2)) {
|
|
16166
16161
|
throw new LocalCliError({
|
|
16167
16162
|
code: "SEED_NOT_FOUND",
|
|
16168
16163
|
message: `--seed "${name}": no brain-seeds/${name}/ directory found.`,
|
|
@@ -16172,7 +16167,7 @@ function resolveSeed(name) {
|
|
|
16172
16167
|
hint: agentContentHint(root, name) ?? "Seeds live under brain-seeds/. Check the name, or omit --seed for an unseeded brain."
|
|
16173
16168
|
});
|
|
16174
16169
|
}
|
|
16175
|
-
return
|
|
16170
|
+
return dir2;
|
|
16176
16171
|
}
|
|
16177
16172
|
function materializeBrainTree(opts) {
|
|
16178
16173
|
fs13.cpSync(opts.templateSrc, opts.destDir, { recursive: true });
|
|
@@ -16657,20 +16652,20 @@ function resolveScaffoldCommit(repoRoot, env) {
|
|
|
16657
16652
|
return "(scaffold)";
|
|
16658
16653
|
}
|
|
16659
16654
|
}
|
|
16660
|
-
function stageAsGitRepo(
|
|
16655
|
+
function stageAsGitRepo(dir2) {
|
|
16661
16656
|
const steps = [
|
|
16662
16657
|
{ args: ["init", "-b", "main"], label: "git init" },
|
|
16663
16658
|
{ args: ["add", "."], label: "git add" },
|
|
16664
16659
|
{ args: ["commit", "-m", "Initial commit from brain-template"], label: "git commit" }
|
|
16665
16660
|
];
|
|
16666
16661
|
for (const { args, label } of steps) {
|
|
16667
|
-
const r = spawnSync("git", args, { cwd:
|
|
16662
|
+
const r = spawnSync("git", args, { cwd: dir2, encoding: "utf8" });
|
|
16668
16663
|
if (r.status !== 0) {
|
|
16669
16664
|
const exitCode = r.status !== null ? r.status.toString() : "<null>";
|
|
16670
16665
|
const detail = r.stderr.trim() || r.stdout.trim() || `exit ${exitCode}`;
|
|
16671
16666
|
throw new LocalCliError({
|
|
16672
16667
|
code: "BRAIN_GIT_INIT_FAILED",
|
|
16673
|
-
message: `${label} failed in ${
|
|
16668
|
+
message: `${label} failed in ${dir2}: ${detail}`
|
|
16674
16669
|
});
|
|
16675
16670
|
}
|
|
16676
16671
|
}
|
|
@@ -17634,13 +17629,13 @@ import * as fs16 from "fs";
|
|
|
17634
17629
|
import * as path16 from "path";
|
|
17635
17630
|
import { parse as parseYaml7 } from "yaml";
|
|
17636
17631
|
function findAncestorContaining(hint, rel) {
|
|
17637
|
-
let
|
|
17638
|
-
const fsRoot = path16.parse(
|
|
17632
|
+
let dir2 = path16.resolve(hint);
|
|
17633
|
+
const fsRoot = path16.parse(dir2).root;
|
|
17639
17634
|
for (; ; ) {
|
|
17640
|
-
if (fs16.existsSync(path16.join(
|
|
17641
|
-
const parent = path16.dirname(
|
|
17642
|
-
if (parent ===
|
|
17643
|
-
|
|
17635
|
+
if (fs16.existsSync(path16.join(dir2, rel))) return dir2;
|
|
17636
|
+
const parent = path16.dirname(dir2);
|
|
17637
|
+
if (parent === dir2 || dir2 === fsRoot) return null;
|
|
17638
|
+
dir2 = parent;
|
|
17644
17639
|
}
|
|
17645
17640
|
}
|
|
17646
17641
|
function findPersonasRoot(hint) {
|
|
@@ -17884,7 +17879,7 @@ async function awaitDataPlaneReady(opts) {
|
|
|
17884
17879
|
const consecutive = opts.consecutive ?? 3;
|
|
17885
17880
|
const attempts = opts.attempts ?? 60;
|
|
17886
17881
|
const intervalMs = opts.intervalMs ?? 5e3;
|
|
17887
|
-
const
|
|
17882
|
+
const sleep4 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
17888
17883
|
let streak = 0;
|
|
17889
17884
|
for (let i = 1; i <= attempts; i++) {
|
|
17890
17885
|
const r = await opts.probe();
|
|
@@ -17906,7 +17901,7 @@ async function awaitDataPlaneReady(opts) {
|
|
|
17906
17901
|
streak = 0;
|
|
17907
17902
|
opts.onProgress?.(`data-plane not ready (${forcedRetryable ? `status ${(r.status ?? 0).toString()}` : cls.category}), waiting\u2026`);
|
|
17908
17903
|
}
|
|
17909
|
-
if (i < attempts) await
|
|
17904
|
+
if (i < attempts) await sleep4(intervalMs);
|
|
17910
17905
|
}
|
|
17911
17906
|
return { ready: false, attempts };
|
|
17912
17907
|
}
|
|
@@ -18622,9 +18617,9 @@ import { createHash as createHash3 } from "crypto";
|
|
|
18622
18617
|
import * as fs20 from "fs";
|
|
18623
18618
|
import * as os8 from "os";
|
|
18624
18619
|
import * as path19 from "path";
|
|
18625
|
-
function readVersionFrontmatter(
|
|
18626
|
-
if (!fs20.existsSync(
|
|
18627
|
-
const text = fs20.readFileSync(
|
|
18620
|
+
function readVersionFrontmatter(file2) {
|
|
18621
|
+
if (!fs20.existsSync(file2)) return "";
|
|
18622
|
+
const text = fs20.readFileSync(file2, "utf8");
|
|
18628
18623
|
if (!text.startsWith("---")) return "";
|
|
18629
18624
|
const closeIdx = text.indexOf("\n---", 3);
|
|
18630
18625
|
if (closeIdx < 0) return "";
|
|
@@ -19103,7 +19098,7 @@ init_errors();
|
|
|
19103
19098
|
async function deployHostedWorker(args) {
|
|
19104
19099
|
const onProgress = args.onProgress ?? ((_m) => {
|
|
19105
19100
|
});
|
|
19106
|
-
const
|
|
19101
|
+
const sleep4 = args.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
19107
19102
|
const now = args.now ?? (() => Date.now());
|
|
19108
19103
|
const timeout = args.pollTimeoutMs ?? 8 * 60 * 1e3;
|
|
19109
19104
|
const interval = args.pollIntervalMs ?? 1e4;
|
|
@@ -19155,7 +19150,7 @@ async function deployHostedWorker(args) {
|
|
|
19155
19150
|
hint: "Provisioning can take 2\u20135 min; re-run with the same name to resume, or check the version status in the Foundry portal."
|
|
19156
19151
|
});
|
|
19157
19152
|
}
|
|
19158
|
-
await
|
|
19153
|
+
await sleep4(interval);
|
|
19159
19154
|
}
|
|
19160
19155
|
}
|
|
19161
19156
|
|
|
@@ -19164,21 +19159,21 @@ import * as fs22 from "fs";
|
|
|
19164
19159
|
import * as path22 from "path";
|
|
19165
19160
|
import { parse as parseYaml10 } from "yaml";
|
|
19166
19161
|
function findRepoRoot(startDir) {
|
|
19167
|
-
let
|
|
19162
|
+
let dir2 = path22.resolve(startDir);
|
|
19168
19163
|
for (; ; ) {
|
|
19169
|
-
if (fs22.existsSync(path22.join(
|
|
19170
|
-
const parent = path22.dirname(
|
|
19171
|
-
if (parent ===
|
|
19172
|
-
|
|
19164
|
+
if (fs22.existsSync(path22.join(dir2, "personas"))) return dir2;
|
|
19165
|
+
const parent = path22.dirname(dir2);
|
|
19166
|
+
if (parent === dir2) return null;
|
|
19167
|
+
dir2 = parent;
|
|
19173
19168
|
}
|
|
19174
19169
|
}
|
|
19175
19170
|
function resolvePersona(persona, cwd = process.cwd()) {
|
|
19176
19171
|
const root = findRepoRoot(cwd);
|
|
19177
19172
|
if (!root) return { persona, personaVersion: null };
|
|
19178
|
-
const
|
|
19173
|
+
const file2 = path22.join(root, "personas", persona, "persona.md");
|
|
19179
19174
|
let raw;
|
|
19180
19175
|
try {
|
|
19181
|
-
raw = fs22.readFileSync(
|
|
19176
|
+
raw = fs22.readFileSync(file2, "utf-8");
|
|
19182
19177
|
} catch {
|
|
19183
19178
|
return { persona, personaVersion: null };
|
|
19184
19179
|
}
|
|
@@ -20407,13 +20402,13 @@ function manifestSourceForVersionTag(channelUrl, version) {
|
|
|
20407
20402
|
}
|
|
20408
20403
|
var POINTER_SEGMENT = /\/releases\/(?:latest\/download|download\/[^/]+)\//;
|
|
20409
20404
|
async function fetchManifest(source, deps = {}) {
|
|
20410
|
-
const
|
|
20405
|
+
const readFile10 = deps.readFile ?? ((p) => fs23.readFileSync(p, "utf8"));
|
|
20411
20406
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
20412
20407
|
const ghToken = deps.ghToken ?? getGhToken;
|
|
20413
20408
|
let raw;
|
|
20414
20409
|
if ("file" in source) {
|
|
20415
20410
|
try {
|
|
20416
|
-
raw =
|
|
20411
|
+
raw = readFile10(source.file);
|
|
20417
20412
|
} catch (e) {
|
|
20418
20413
|
throw new LocalCliError({ code: "PLATFORM_MANIFEST_READ_FAILED", message: `Could not read manifest file '${source.file}': ${e.message}` });
|
|
20419
20414
|
}
|
|
@@ -21653,11 +21648,11 @@ function mergeThreeWay(ours, base, theirs) {
|
|
|
21653
21648
|
return { ok: false, merged: "" };
|
|
21654
21649
|
}
|
|
21655
21650
|
const lf = (s) => s.replace(/\r\n/g, "\n");
|
|
21656
|
-
const
|
|
21651
|
+
const dir2 = fs27.mkdtempSync(path28.join(os11.tmpdir(), "seed-merge-"));
|
|
21657
21652
|
try {
|
|
21658
|
-
const o = path28.join(
|
|
21659
|
-
const b = path28.join(
|
|
21660
|
-
const t = path28.join(
|
|
21653
|
+
const o = path28.join(dir2, "ours");
|
|
21654
|
+
const b = path28.join(dir2, "base");
|
|
21655
|
+
const t = path28.join(dir2, "theirs");
|
|
21661
21656
|
fs27.writeFileSync(o, lf(ours));
|
|
21662
21657
|
fs27.writeFileSync(b, lf(base));
|
|
21663
21658
|
fs27.writeFileSync(t, lf(theirs));
|
|
@@ -21674,7 +21669,7 @@ function mergeThreeWay(ours, base, theirs) {
|
|
|
21674
21669
|
}
|
|
21675
21670
|
return { ok: false, merged: "" };
|
|
21676
21671
|
} finally {
|
|
21677
|
-
fs27.rmSync(
|
|
21672
|
+
fs27.rmSync(dir2, { recursive: true, force: true });
|
|
21678
21673
|
}
|
|
21679
21674
|
}
|
|
21680
21675
|
var skip = (path47, skipReason) => ({ path: path47, kind: "skip", skipReason });
|
|
@@ -21728,9 +21723,9 @@ function composeSeedPrBody(s) {
|
|
|
21728
21723
|
}
|
|
21729
21724
|
|
|
21730
21725
|
// src/lib/seed-refresh.ts
|
|
21731
|
-
function readIf(
|
|
21732
|
-
if (!
|
|
21733
|
-
const abs = path29.join(
|
|
21726
|
+
function readIf(dir2, rel) {
|
|
21727
|
+
if (!dir2) return null;
|
|
21728
|
+
const abs = path29.join(dir2, rel);
|
|
21734
21729
|
return fs28.existsSync(abs) ? fs28.readFileSync(abs, "utf8") : null;
|
|
21735
21730
|
}
|
|
21736
21731
|
async function assembleCandidates(args) {
|
|
@@ -21864,8 +21859,8 @@ function dedupeBrainRepos(agents) {
|
|
|
21864
21859
|
function seedContentDir(contentRoot, manifest, seedName) {
|
|
21865
21860
|
const items = manifest.components.brainSeeds.items;
|
|
21866
21861
|
if (!Object.hasOwn(items, seedName)) return null;
|
|
21867
|
-
const
|
|
21868
|
-
return fs29.existsSync(
|
|
21862
|
+
const dir2 = path30.join(contentRoot, items[seedName].path);
|
|
21863
|
+
return fs29.existsSync(dir2) ? dir2 : null;
|
|
21869
21864
|
}
|
|
21870
21865
|
function bindRepoApi(token, repo) {
|
|
21871
21866
|
return {
|
|
@@ -22028,8 +22023,8 @@ async function buildConvergeDeps(args) {
|
|
|
22028
22023
|
message: `applyAgent called with non-hosted component '${a.component}'`
|
|
22029
22024
|
});
|
|
22030
22025
|
}
|
|
22031
|
-
const
|
|
22032
|
-
const agentName =
|
|
22026
|
+
const dir2 = HOSTED_AGENT_PERSONA_DIR[a.component];
|
|
22027
|
+
const agentName = dir2 ? discovered[dir2] : void 0;
|
|
22033
22028
|
if (!agentName) {
|
|
22034
22029
|
warn(`hosted agent for component '${a.component}' is not deployed on this install \u2014 skipping.`);
|
|
22035
22030
|
return "absent";
|
|
@@ -22084,8 +22079,8 @@ async function buildConvergeDeps(args) {
|
|
|
22084
22079
|
if (a.component === "personas" && a.personaName) {
|
|
22085
22080
|
agentName = discovered[a.personaName];
|
|
22086
22081
|
} else if (a.component === "codingAgent" || a.component === "azureExecutor") {
|
|
22087
|
-
const
|
|
22088
|
-
agentName =
|
|
22082
|
+
const dir2 = HOSTED_AGENT_PERSONA_DIR[a.component];
|
|
22083
|
+
agentName = dir2 ? discovered[dir2] : void 0;
|
|
22089
22084
|
}
|
|
22090
22085
|
if (!agentName) continue;
|
|
22091
22086
|
await awaitAgentQueryable({ credential: args.credential, projectEndpoint: args.project.endpoint, agentName, onProgress: args.onProgress });
|
|
@@ -27361,9 +27356,9 @@ function isAbsentCursorError(e) {
|
|
|
27361
27356
|
const anyE = e;
|
|
27362
27357
|
return anyE.statusCode === 404 || anyE.code === "ResourceNotFound" || anyE.code === "TableNotFound" || anyE.details?.errorCode === "TableNotFound" || anyE.details?.errorCode === "ResourceNotFound";
|
|
27363
27358
|
}
|
|
27364
|
-
function absentCursor(
|
|
27359
|
+
function absentCursor(canonical2) {
|
|
27365
27360
|
return {
|
|
27366
|
-
worker:
|
|
27361
|
+
worker: canonical2,
|
|
27367
27362
|
watermarks: {},
|
|
27368
27363
|
safetyLagSeconds: DEFAULT_SAFETY_LAG_SECONDS,
|
|
27369
27364
|
engineVersion: ENGINE_VERSION
|
|
@@ -27382,19 +27377,19 @@ var TableCursor = class {
|
|
|
27382
27377
|
* Any other error (e.g. 403 RBAC) propagates raw.
|
|
27383
27378
|
*/
|
|
27384
27379
|
async read(worker) {
|
|
27385
|
-
const
|
|
27380
|
+
const canonical2 = canonicalWorker(worker);
|
|
27386
27381
|
let row;
|
|
27387
27382
|
try {
|
|
27388
|
-
row = await this.client.getEntity(CURSOR_PARTITION_KEY,
|
|
27383
|
+
row = await this.client.getEntity(CURSOR_PARTITION_KEY, canonical2);
|
|
27389
27384
|
} catch (e) {
|
|
27390
27385
|
if (isAbsentCursorError(e))
|
|
27391
|
-
return absentCursor(
|
|
27386
|
+
return absentCursor(canonical2);
|
|
27392
27387
|
throw e;
|
|
27393
27388
|
}
|
|
27394
27389
|
const watermarks = row.watermarks ? JSON.parse(row.watermarks) : {};
|
|
27395
27390
|
const fetchFailures = row.fetchFailures ? JSON.parse(row.fetchFailures) : void 0;
|
|
27396
27391
|
return {
|
|
27397
|
-
worker:
|
|
27392
|
+
worker: canonical2,
|
|
27398
27393
|
watermarks,
|
|
27399
27394
|
...fetchFailures && Object.keys(fetchFailures).length > 0 ? { fetchFailures } : {},
|
|
27400
27395
|
safetyLagSeconds: row.safetyLagSeconds ?? DEFAULT_SAFETY_LAG_SECONDS,
|
|
@@ -27410,11 +27405,11 @@ var TableCursor = class {
|
|
|
27410
27405
|
* (Table cells are scalar — same rationale as agent-ledger's `artifacts`).
|
|
27411
27406
|
*/
|
|
27412
27407
|
async advance(worker, cursor) {
|
|
27413
|
-
const
|
|
27408
|
+
const canonical2 = canonicalWorker(worker);
|
|
27414
27409
|
await this.ensureTable();
|
|
27415
27410
|
const entity = {
|
|
27416
27411
|
partitionKey: CURSOR_PARTITION_KEY,
|
|
27417
|
-
rowKey:
|
|
27412
|
+
rowKey: canonical2,
|
|
27418
27413
|
watermarks: JSON.stringify(cursor.watermarks),
|
|
27419
27414
|
safetyLagSeconds: cursor.safetyLagSeconds,
|
|
27420
27415
|
engineVersion: cursor.engineVersion
|
|
@@ -27499,10 +27494,10 @@ function formatCursorPreview(cursor) {
|
|
|
27499
27494
|
|
|
27500
27495
|
// ../../packages/brain/engine/dist/esm/ledger-source.js
|
|
27501
27496
|
function physicalPksFor(worker) {
|
|
27502
|
-
const
|
|
27503
|
-
const pks = /* @__PURE__ */ new Set([
|
|
27497
|
+
const canonical2 = canonicalWorker(worker);
|
|
27498
|
+
const pks = /* @__PURE__ */ new Set([canonical2]);
|
|
27504
27499
|
for (const [physical, mapped] of Object.entries(WORKER_ALIASES)) {
|
|
27505
|
-
if (mapped ===
|
|
27500
|
+
if (mapped === canonical2)
|
|
27506
27501
|
pks.add(physical);
|
|
27507
27502
|
}
|
|
27508
27503
|
return [...pks];
|
|
@@ -27590,7 +27585,7 @@ function mapItems(raw) {
|
|
|
27590
27585
|
return acc.reverse();
|
|
27591
27586
|
}
|
|
27592
27587
|
function makeConversationSource(client, opts = {}) {
|
|
27593
|
-
const
|
|
27588
|
+
const sleep4 = opts.sleep ?? defaultSleep;
|
|
27594
27589
|
return {
|
|
27595
27590
|
async items(conversationId) {
|
|
27596
27591
|
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
@@ -27608,7 +27603,7 @@ function makeConversationSource(client, opts = {}) {
|
|
|
27608
27603
|
throw new ConvFetchError("auth", `auth failed reading ${conversationId}`);
|
|
27609
27604
|
const retryable = status === void 0 || status === TRANSIENT;
|
|
27610
27605
|
if (retryable && attempt < MAX_ATTEMPTS - 1) {
|
|
27611
|
-
await
|
|
27606
|
+
await sleep4(attempt);
|
|
27612
27607
|
continue;
|
|
27613
27608
|
}
|
|
27614
27609
|
throw new ConvFetchError("terminal", `exhausted ${String(MAX_ATTEMPTS)} retries reading ${conversationId}: ${e?.message ?? String(e)}`);
|
|
@@ -28412,7 +28407,7 @@ function extractJson(text) {
|
|
|
28412
28407
|
return JSON.parse(candidate2.slice(start, end + 1));
|
|
28413
28408
|
}
|
|
28414
28409
|
async function propose(model, system, user, opts = {}) {
|
|
28415
|
-
const
|
|
28410
|
+
const sleep4 = opts.sleep ?? defaultSleep2;
|
|
28416
28411
|
const modelName = opts.model ?? MODEL_NAME_FALLBACK;
|
|
28417
28412
|
let inputTokens = 0;
|
|
28418
28413
|
let outputTokens = 0;
|
|
@@ -28428,7 +28423,7 @@ async function propose(model, system, user, opts = {}) {
|
|
|
28428
28423
|
} catch (e) {
|
|
28429
28424
|
lastErr = `parse: ${e.message}`;
|
|
28430
28425
|
if (attempt < MAX_MODEL_ATTEMPTS - 1) {
|
|
28431
|
-
await
|
|
28426
|
+
await sleep4(attempt);
|
|
28432
28427
|
continue;
|
|
28433
28428
|
}
|
|
28434
28429
|
break;
|
|
@@ -28437,7 +28432,7 @@ async function propose(model, system, user, opts = {}) {
|
|
|
28437
28432
|
if (!Array.isArray(rawDeltas)) {
|
|
28438
28433
|
lastErr = "schema: top-level { deltas: [] } missing";
|
|
28439
28434
|
if (attempt < MAX_MODEL_ATTEMPTS - 1) {
|
|
28440
|
-
await
|
|
28435
|
+
await sleep4(attempt);
|
|
28441
28436
|
continue;
|
|
28442
28437
|
}
|
|
28443
28438
|
break;
|
|
@@ -29198,9 +29193,9 @@ async function discoverLedgerResources(opts) {
|
|
|
29198
29193
|
}
|
|
29199
29194
|
|
|
29200
29195
|
// src/lib/dream-context.ts
|
|
29201
|
-
function physicalPksFor2(
|
|
29202
|
-
const aliases = Object.entries(WORKER_ALIASES).filter(([, c]) => c ===
|
|
29203
|
-
return [
|
|
29196
|
+
function physicalPksFor2(canonical2) {
|
|
29197
|
+
const aliases = Object.entries(WORKER_ALIASES).filter(([, c]) => c === canonical2).map(([physical]) => physical);
|
|
29198
|
+
return [canonical2, ...aliases];
|
|
29204
29199
|
}
|
|
29205
29200
|
function rgFromScope(scope) {
|
|
29206
29201
|
const m = /\/resourceGroups\/([^/]+)/i.exec(scope);
|
|
@@ -29209,7 +29204,7 @@ function rgFromScope(scope) {
|
|
|
29209
29204
|
}
|
|
29210
29205
|
async function resolveDreamContext(opts) {
|
|
29211
29206
|
const { credential: credential2 } = opts;
|
|
29212
|
-
const
|
|
29207
|
+
const canonical2 = opts.worker.toLowerCase();
|
|
29213
29208
|
const account = await getAzAccount();
|
|
29214
29209
|
const subscriptionId = opts.subscription ?? account.subscriptionId;
|
|
29215
29210
|
const project = await resolveFoundryProject({
|
|
@@ -29226,11 +29221,11 @@ async function resolveDreamContext(opts) {
|
|
|
29226
29221
|
resourceGroup
|
|
29227
29222
|
});
|
|
29228
29223
|
return {
|
|
29229
|
-
worker:
|
|
29224
|
+
worker: canonical2,
|
|
29230
29225
|
projectEndpoint: project.endpoint,
|
|
29231
29226
|
ledgerTableEndpoint,
|
|
29232
29227
|
workspaceId,
|
|
29233
|
-
physicalPks: physicalPksFor2(
|
|
29228
|
+
physicalPks: physicalPksFor2(canonical2)
|
|
29234
29229
|
};
|
|
29235
29230
|
}
|
|
29236
29231
|
|
|
@@ -31054,15 +31049,15 @@ async function getAciState(opts) {
|
|
|
31054
31049
|
}
|
|
31055
31050
|
|
|
31056
31051
|
// src/lib/bootstrap-finalize.ts
|
|
31057
|
-
import * as
|
|
31058
|
-
import * as
|
|
31059
|
-
import * as
|
|
31052
|
+
import * as fs39 from "fs/promises";
|
|
31053
|
+
import * as os20 from "os";
|
|
31054
|
+
import * as path44 from "path";
|
|
31060
31055
|
|
|
31061
31056
|
// src/lib/company-profile-seed.ts
|
|
31062
31057
|
import { spawn as spawn6 } from "child_process";
|
|
31063
31058
|
import { closeSync, openSync, readFileSync as readFileSync24 } from "fs";
|
|
31064
|
-
import * as
|
|
31065
|
-
import * as
|
|
31059
|
+
import * as os17 from "os";
|
|
31060
|
+
import * as path39 from "path";
|
|
31066
31061
|
init_errors();
|
|
31067
31062
|
|
|
31068
31063
|
// src/lib/onboarding-profile.ts
|
|
@@ -31497,7 +31492,11 @@ function renderCompanyProfile(block, now = (/* @__PURE__ */ new Date()).toISOStr
|
|
|
31497
31492
|
``,
|
|
31498
31493
|
`# Company profile`,
|
|
31499
31494
|
``,
|
|
31500
|
-
|
|
31495
|
+
// Named the questionnaire until the questionnaire stopped existing. There is no
|
|
31496
|
+
// intake conversation any more — `m8t bootstrap profile` confirms two contact
|
|
31497
|
+
// facts and nothing about the company — so the old line described a thing that
|
|
31498
|
+
// never happened to this founder.
|
|
31499
|
+
`_Seeded at install._`,
|
|
31501
31500
|
``,
|
|
31502
31501
|
...bullet("Company", block.company_name),
|
|
31503
31502
|
...bullet("Stage", block.company_stage),
|
|
@@ -31523,6 +31522,45 @@ function renderCompanyProfile(block, now = (/* @__PURE__ */ new Date()).toISOStr
|
|
|
31523
31522
|
var FOUNDER_RECORD_PATH = "memory/founder.md";
|
|
31524
31523
|
var NOT_CAPTURED = "_not captured yet \u2014 just tell me and I'll add it_";
|
|
31525
31524
|
var REGION_UNKNOWN_AT_INTAKE = "_not known at intake \u2014 filled in when the request is filed_";
|
|
31525
|
+
var FOUNDER_BULLET_LABELS = {
|
|
31526
|
+
name: "- **Founder:** ",
|
|
31527
|
+
email: "- **Founder email (company_email):** ",
|
|
31528
|
+
advisor: "- **Microsoft Startup Advisor (SA):** ",
|
|
31529
|
+
subscription: "- **Azure subscription:** "
|
|
31530
|
+
};
|
|
31531
|
+
function updateFounderContacts(existing, block, inputs) {
|
|
31532
|
+
if (!existing.includes(FOUNDER_BULLET_LABELS.email)) return null;
|
|
31533
|
+
const advisorName = (block.advisor_name ?? "").trim();
|
|
31534
|
+
const advisorEmail = (block.advisor_email ?? "").trim();
|
|
31535
|
+
const advisor = inputs.advisorCleared === true ? renderAdvisor(advisorName, advisorEmail, NOT_CAPTURED) : renderAdvisor(advisorName, advisorEmail, "");
|
|
31536
|
+
const replacements = [];
|
|
31537
|
+
const add = (prefix, value) => {
|
|
31538
|
+
if (value) replacements.push([prefix, prefix + value]);
|
|
31539
|
+
};
|
|
31540
|
+
add(FOUNDER_BULLET_LABELS.name, (block.founder_name ?? "").trim());
|
|
31541
|
+
add(FOUNDER_BULLET_LABELS.email, (block.founder_email ?? "").trim());
|
|
31542
|
+
add(FOUNDER_BULLET_LABELS.advisor, advisor);
|
|
31543
|
+
add(FOUNDER_BULLET_LABELS.subscription, (inputs.subscriptionId ?? "").trim());
|
|
31544
|
+
return existing.split("\n").map((line2) => {
|
|
31545
|
+
const cr = line2.endsWith("\r") ? "\r" : "";
|
|
31546
|
+
const bare = cr ? line2.slice(0, -1) : line2;
|
|
31547
|
+
const hit = replacements.find(([prefix]) => bare.startsWith(prefix));
|
|
31548
|
+
return hit ? hit[1] + cr : line2;
|
|
31549
|
+
}).join("\n");
|
|
31550
|
+
}
|
|
31551
|
+
function renderAdvisor(name, email, absent) {
|
|
31552
|
+
if (!name && !email) return absent;
|
|
31553
|
+
return [name, email ? `<${email}>` : ""].filter(Boolean).join(" ");
|
|
31554
|
+
}
|
|
31555
|
+
function readAdvisorFromRecord(existing) {
|
|
31556
|
+
const line2 = existing.split("\n").map((l) => l.endsWith("\r") ? l.slice(0, -1) : l).find((l) => l.startsWith(FOUNDER_BULLET_LABELS.advisor));
|
|
31557
|
+
if (line2 === void 0) return null;
|
|
31558
|
+
const value = line2.slice(FOUNDER_BULLET_LABELS.advisor.length).trim();
|
|
31559
|
+
if (!value || value === NOT_CAPTURED) return null;
|
|
31560
|
+
const email = /<([^>]+)>/.exec(value)?.[1]?.trim() ?? "";
|
|
31561
|
+
const name = value.replace(/<[^>]*>/, "").trim();
|
|
31562
|
+
return name || email ? { name, email } : null;
|
|
31563
|
+
}
|
|
31526
31564
|
function renderFounderRecord(block, inputs, now = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
31527
31565
|
const pick = (...vals) => vals.map((v) => v?.trim()).find(Boolean) ?? "";
|
|
31528
31566
|
const founderName = pick(block.founder_name, inputs.azIdentity?.name);
|
|
@@ -31530,7 +31568,7 @@ function renderFounderRecord(block, inputs, now = (/* @__PURE__ */ new Date()).t
|
|
|
31530
31568
|
const advisorName = (block.advisor_name ?? "").trim();
|
|
31531
31569
|
const advisorEmail = (block.advisor_email ?? "").trim();
|
|
31532
31570
|
const subscription = (inputs.subscriptionId ?? "").trim();
|
|
31533
|
-
const advisorRendered = advisorName
|
|
31571
|
+
const advisorRendered = renderAdvisor(advisorName, advisorEmail, NOT_CAPTURED);
|
|
31534
31572
|
const request = block.pending_requests?.[0];
|
|
31535
31573
|
const requestLines2 = request === void 0 ? [] : (() => {
|
|
31536
31574
|
const modelLabel = request.model.trim() ? `\`${request.model.trim()}\`` : "(model not recorded)";
|
|
@@ -31565,7 +31603,7 @@ function renderFounderRecord(block, inputs, now = (/* @__PURE__ */ new Date()).t
|
|
|
31565
31603
|
``,
|
|
31566
31604
|
`# Founder & install context`,
|
|
31567
31605
|
``,
|
|
31568
|
-
`_Seeded at
|
|
31606
|
+
`_Seeded at install from your Azure identity + what you confirmed. Authoritative \u2014 read it; don't rewrite it (origin: operator)._`,
|
|
31569
31607
|
``,
|
|
31570
31608
|
`- **Founder:** ${founderName || NOT_CAPTURED}`,
|
|
31571
31609
|
`- **Founder email (company_email):** ${founderEmail || NOT_CAPTURED}`,
|
|
@@ -31581,168 +31619,7 @@ function renderFounderRecord(block, inputs, now = (/* @__PURE__ */ new Date()).t
|
|
|
31581
31619
|
return { founderMd, memoryIndexLine };
|
|
31582
31620
|
}
|
|
31583
31621
|
|
|
31584
|
-
// src/lib/model-cascade.ts
|
|
31585
|
-
var WHITELIST = [
|
|
31586
|
-
{ model: "gpt-5.6-luna", format: "OpenAI", version: "2026-07-09", capacity: 100 },
|
|
31587
|
-
{ model: "gpt-5.6-sol", format: "OpenAI", version: "2026-07-09", capacity: 100 },
|
|
31588
|
-
{ model: "gpt-5.6-terra", format: "OpenAI", version: "2026-07-09", capacity: 100 },
|
|
31589
|
-
{ model: "gpt-5.4", format: "OpenAI", version: "2026-03-05", capacity: 100 },
|
|
31590
|
-
{ model: "grok-4.3", format: "xAI", version: "1", capacity: 100 },
|
|
31591
|
-
{ model: "gpt-4.1-mini", format: "OpenAI", version: "2025-04-14", capacity: 50 }
|
|
31592
|
-
];
|
|
31593
|
-
var GLOBAL_STANDARD = "GlobalStandard";
|
|
31594
|
-
function planCascade(whitelist, catalog, quota) {
|
|
31595
|
-
if (!catalog.ok) {
|
|
31596
|
-
return {
|
|
31597
|
-
candidates: [],
|
|
31598
|
-
skipped: whitelist.map((r) => ({ model: r.model, outcome: "catalog-unverified", detail: catalog.error }))
|
|
31599
|
-
};
|
|
31600
|
-
}
|
|
31601
|
-
const rowsByName = /* @__PURE__ */ new Map();
|
|
31602
|
-
for (const row of catalog.rows) {
|
|
31603
|
-
const list = rowsByName.get(row.name);
|
|
31604
|
-
if (list) list.push(row);
|
|
31605
|
-
else rowsByName.set(row.name, [row]);
|
|
31606
|
-
}
|
|
31607
|
-
const candidates = [];
|
|
31608
|
-
const skipped = [];
|
|
31609
|
-
for (const rung of whitelist) {
|
|
31610
|
-
const rows = rowsByName.get(rung.model);
|
|
31611
|
-
if (!rows || rows.length === 0) {
|
|
31612
|
-
skipped.push({ model: rung.model, outcome: "not-in-catalog" });
|
|
31613
|
-
continue;
|
|
31614
|
-
}
|
|
31615
|
-
const agentRows = rows.filter((r) => r.agentsV2);
|
|
31616
|
-
if (agentRows.length === 0) {
|
|
31617
|
-
skipped.push({ model: rung.model, outcome: "not-agent-eligible" });
|
|
31618
|
-
continue;
|
|
31619
|
-
}
|
|
31620
|
-
if (!agentRows.some((r) => r.skus.includes(GLOBAL_STANDARD))) {
|
|
31621
|
-
skipped.push({ model: rung.model, outcome: "no-global-standard" });
|
|
31622
|
-
continue;
|
|
31623
|
-
}
|
|
31624
|
-
if (modelQuotaVerdict(quota, rung.model).verdict === "no_quota") {
|
|
31625
|
-
skipped.push({ model: rung.model, outcome: "no-quota" });
|
|
31626
|
-
continue;
|
|
31627
|
-
}
|
|
31628
|
-
candidates.push(rung);
|
|
31629
|
-
}
|
|
31630
|
-
return { candidates, skipped };
|
|
31631
|
-
}
|
|
31632
|
-
var QUOTA_RE = /insufficient\s*quota|not enough quota|quota limit|quota\b[^.]{0,40}exceed|exceed\w*\b[^.]{0,40}quota|capacity[^.]{0,40}exceed|exceed\w*\b[^.]{0,40}capacity/i;
|
|
31633
|
-
var REGION_RE = /not available in|not supported in|NotAvailableInRegion|InvalidResourceLocation/i;
|
|
31634
|
-
var CONFLICT_RE = /\bconflict\b|\b409\b/i;
|
|
31635
|
-
function classifyDeployError(message) {
|
|
31636
|
-
if (QUOTA_RE.test(message)) return "deploy-rejected-quota";
|
|
31637
|
-
if (REGION_RE.test(message)) return "deploy-rejected-region";
|
|
31638
|
-
return "deploy-unverified";
|
|
31639
|
-
}
|
|
31640
|
-
async function walkCascade(whitelist, plan, deploy, opts) {
|
|
31641
|
-
const outcomes = /* @__PURE__ */ new Map();
|
|
31642
|
-
for (const s of plan.skipped) outcomes.set(s.model, s);
|
|
31643
|
-
let chosen = null;
|
|
31644
|
-
let aborted = false;
|
|
31645
|
-
for (const rung of plan.candidates) {
|
|
31646
|
-
if (chosen) break;
|
|
31647
|
-
if (opts.now() >= opts.deadlineAt) {
|
|
31648
|
-
aborted = true;
|
|
31649
|
-
break;
|
|
31650
|
-
}
|
|
31651
|
-
let row;
|
|
31652
|
-
try {
|
|
31653
|
-
await deploy(rung);
|
|
31654
|
-
row = { model: rung.model, outcome: "deployed" };
|
|
31655
|
-
chosen = rung;
|
|
31656
|
-
} catch (e) {
|
|
31657
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
31658
|
-
if (CONFLICT_RE.test(msg)) {
|
|
31659
|
-
try {
|
|
31660
|
-
await deploy(rung);
|
|
31661
|
-
row = { model: rung.model, outcome: "deployed" };
|
|
31662
|
-
chosen = rung;
|
|
31663
|
-
} catch (e2) {
|
|
31664
|
-
const msg2 = e2 instanceof Error ? e2.message : String(e2);
|
|
31665
|
-
row = {
|
|
31666
|
-
model: rung.model,
|
|
31667
|
-
outcome: CONFLICT_RE.test(msg2) ? "deploy-unverified" : classifyDeployError(msg2),
|
|
31668
|
-
detail: msg2
|
|
31669
|
-
};
|
|
31670
|
-
}
|
|
31671
|
-
} else {
|
|
31672
|
-
row = { model: rung.model, outcome: classifyDeployError(msg), detail: msg };
|
|
31673
|
-
}
|
|
31674
|
-
}
|
|
31675
|
-
outcomes.set(rung.model, row);
|
|
31676
|
-
opts.onRung?.(row.model, row.outcome);
|
|
31677
|
-
}
|
|
31678
|
-
const trace = whitelist.map(
|
|
31679
|
-
(r) => outcomes.get(r.model) ?? { model: r.model, outcome: aborted ? "aborted-deadline" : "not-reached" }
|
|
31680
|
-
);
|
|
31681
|
-
return { chosen, trace };
|
|
31682
|
-
}
|
|
31683
|
-
var QUOTA_INVITATION = "You can offer to help request that quota.";
|
|
31684
|
-
var CERTAIN_UNAVAILABLE = /* @__PURE__ */ new Set([
|
|
31685
|
-
"not-in-catalog",
|
|
31686
|
-
"not-agent-eligible",
|
|
31687
|
-
"no-global-standard",
|
|
31688
|
-
"deploy-rejected-region"
|
|
31689
|
-
]);
|
|
31690
|
-
var QUOTA_BLOCKED = /* @__PURE__ */ new Set(["no-quota", "deploy-rejected-quota"]);
|
|
31691
|
-
function decideNote(whitelist, result2) {
|
|
31692
|
-
const chosenIdx = result2.chosen ? whitelist.findIndex((r) => r.model === result2.chosen?.model) : whitelist.length;
|
|
31693
|
-
const better = result2.trace.slice(0, Math.max(chosenIdx, 0));
|
|
31694
|
-
const runningModel = result2.chosen?.model ?? null;
|
|
31695
|
-
if (chosenIdx === 0) {
|
|
31696
|
-
return { status: "top", runningModel, pitchModel: null, unavailableAbovePitch: [] };
|
|
31697
|
-
}
|
|
31698
|
-
const pitch = better.find((t) => QUOTA_BLOCKED.has(t.outcome));
|
|
31699
|
-
if (pitch) {
|
|
31700
|
-
const pitchIdx = better.findIndex((t) => t.model === pitch.model);
|
|
31701
|
-
return {
|
|
31702
|
-
status: "lesser-quota",
|
|
31703
|
-
runningModel,
|
|
31704
|
-
pitchModel: pitch.model,
|
|
31705
|
-
unavailableAbovePitch: better.slice(0, pitchIdx).filter((t) => CERTAIN_UNAVAILABLE.has(t.outcome)).map((t) => t.model)
|
|
31706
|
-
};
|
|
31707
|
-
}
|
|
31708
|
-
const allCertain = better.length > 0 && better.every((t) => CERTAIN_UNAVAILABLE.has(t.outcome));
|
|
31709
|
-
return {
|
|
31710
|
-
status: allCertain ? "lesser-unavailable" : "lesser-unverified",
|
|
31711
|
-
runningModel,
|
|
31712
|
-
pitchModel: null,
|
|
31713
|
-
unavailableAbovePitch: []
|
|
31714
|
-
};
|
|
31715
|
-
}
|
|
31716
|
-
var listNames = (names) => names.length <= 1 ? names[0] ?? "" : `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}`;
|
|
31717
|
-
function renderChosenModelNote(d) {
|
|
31718
|
-
const subject = d.runningModel ? `You are running on ${d.runningModel}.` : "You are running on this install's default model.";
|
|
31719
|
-
switch (d.status) {
|
|
31720
|
-
case "top":
|
|
31721
|
-
return `${subject} That is the best available model for this install.`;
|
|
31722
|
-
case "lesser-quota": {
|
|
31723
|
-
const aside = d.unavailableAbovePitch.length > 0 ? ` ${listNames(d.unavailableAbovePitch)} ${d.unavailableAbovePitch.length > 1 ? "are" : "is"} not offered for this install.` : "";
|
|
31724
|
-
return `${subject} A stronger model, ${d.pitchModel ?? ""}, is offered here, but this subscription has no quota for it.${aside} ${QUOTA_INVITATION}`;
|
|
31725
|
-
}
|
|
31726
|
-
case "lesser-unavailable":
|
|
31727
|
-
return `${subject} No stronger model is offered for this install right now.`;
|
|
31728
|
-
case "lesser-unverified":
|
|
31729
|
-
return `${subject} It was not possible to check which stronger models this subscription can run, so do not make claims about what is or is not available.`;
|
|
31730
|
-
}
|
|
31731
|
-
}
|
|
31732
|
-
|
|
31733
31622
|
// src/lib/founder-identity.ts
|
|
31734
|
-
var INVENTORY_INVITATION = "You can offer to walk the founder through these resources.";
|
|
31735
|
-
var NOTE_INVITATIONS = [QUOTA_INVITATION, INVENTORY_INVITATION];
|
|
31736
|
-
function escapeRegExp(value) {
|
|
31737
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
31738
|
-
}
|
|
31739
|
-
function stripNoteInvitations(value) {
|
|
31740
|
-
let out = value;
|
|
31741
|
-
for (const sentence of NOTE_INVITATIONS) {
|
|
31742
|
-
out = out.replace(new RegExp(escapeRegExp(sentence), "gi"), " ");
|
|
31743
|
-
}
|
|
31744
|
-
return out.replace(/\s{2,}/g, " ").trim();
|
|
31745
|
-
}
|
|
31746
31623
|
function deriveEmailCandidate(raw) {
|
|
31747
31624
|
const mail = (raw.mail ?? "").trim();
|
|
31748
31625
|
if (mail) return mail;
|
|
@@ -31773,21 +31650,80 @@ async function getSignedInUserIdentity(runAzImpl = runAz) {
|
|
|
31773
31650
|
return { name: "", email: "" };
|
|
31774
31651
|
}
|
|
31775
31652
|
}
|
|
31776
|
-
|
|
31777
|
-
|
|
31778
|
-
|
|
31779
|
-
|
|
31780
|
-
|
|
31653
|
+
|
|
31654
|
+
// src/lib/onboarding-profile-store.ts
|
|
31655
|
+
import * as fs35 from "fs/promises";
|
|
31656
|
+
import * as os16 from "os";
|
|
31657
|
+
import * as path38 from "path";
|
|
31658
|
+
var ONBOARDING_PROFILE_FILE = "onboarding-profile.json";
|
|
31659
|
+
function dir(home) {
|
|
31660
|
+
return path38.join(home, ".m8t");
|
|
31661
|
+
}
|
|
31662
|
+
function file(home) {
|
|
31663
|
+
return path38.join(dir(home), ONBOARDING_PROFILE_FILE);
|
|
31664
|
+
}
|
|
31665
|
+
function isRecord3(value) {
|
|
31666
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
31667
|
+
}
|
|
31668
|
+
function nonBlankString(value) {
|
|
31669
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
31670
|
+
}
|
|
31671
|
+
function canonical(value) {
|
|
31672
|
+
if (!isRecord3(value)) return null;
|
|
31673
|
+
if (value.schemaVersion !== 1) return null;
|
|
31674
|
+
if (!nonBlankString(value.founderEmail)) return null;
|
|
31675
|
+
if (typeof value.founderName !== "string") return null;
|
|
31676
|
+
if (typeof value.collectedAt !== "string") return null;
|
|
31677
|
+
if (value.advisor === void 0) return null;
|
|
31678
|
+
let advisor = null;
|
|
31679
|
+
if (value.advisor !== null) {
|
|
31680
|
+
if (!isRecord3(value.advisor)) return null;
|
|
31681
|
+
if (typeof value.advisor.name !== "string" || typeof value.advisor.email !== "string") return null;
|
|
31682
|
+
advisor = { name: value.advisor.name, email: value.advisor.email };
|
|
31781
31683
|
}
|
|
31782
|
-
|
|
31783
|
-
|
|
31684
|
+
return {
|
|
31685
|
+
schemaVersion: 1,
|
|
31686
|
+
collectedAt: value.collectedAt,
|
|
31687
|
+
founderName: value.founderName,
|
|
31688
|
+
founderEmail: value.founderEmail,
|
|
31689
|
+
advisor,
|
|
31690
|
+
...value.advisorCleared === true ? { advisorCleared: true } : {}
|
|
31691
|
+
};
|
|
31692
|
+
}
|
|
31693
|
+
async function readOnboardingProfile(home = os16.homedir()) {
|
|
31694
|
+
let raw;
|
|
31695
|
+
try {
|
|
31696
|
+
raw = await fs35.readFile(file(home), "utf8");
|
|
31697
|
+
} catch {
|
|
31698
|
+
return null;
|
|
31699
|
+
}
|
|
31700
|
+
try {
|
|
31701
|
+
return canonical(JSON.parse(raw));
|
|
31702
|
+
} catch {
|
|
31703
|
+
return null;
|
|
31784
31704
|
}
|
|
31785
|
-
|
|
31705
|
+
}
|
|
31706
|
+
async function writeOnboardingProfile(profile, home = os16.homedir()) {
|
|
31707
|
+
await fs35.mkdir(dir(home), { recursive: true });
|
|
31708
|
+
const target = file(home);
|
|
31709
|
+
const tmp = `${target}.tmp`;
|
|
31710
|
+
await fs35.writeFile(tmp, JSON.stringify(profile, null, 2), { encoding: "utf8", mode: 384 });
|
|
31711
|
+
await fs35.rename(tmp, target);
|
|
31712
|
+
}
|
|
31713
|
+
function toOnboardingBlock(profile) {
|
|
31714
|
+
return {
|
|
31715
|
+
schema_version: "3",
|
|
31716
|
+
context: "",
|
|
31717
|
+
founder_name: profile.founderName,
|
|
31718
|
+
founder_email: profile.founderEmail,
|
|
31719
|
+
advisor_name: profile.advisor?.name ?? "",
|
|
31720
|
+
advisor_email: profile.advisor?.email ?? ""
|
|
31721
|
+
};
|
|
31786
31722
|
}
|
|
31787
31723
|
|
|
31788
31724
|
// src/lib/company-profile-seed.ts
|
|
31789
31725
|
function readGithubAppCreds(credsPath) {
|
|
31790
|
-
const p = credsPath ??
|
|
31726
|
+
const p = credsPath ?? path39.join(os17.homedir(), ".m8t", "github-app.json");
|
|
31791
31727
|
try {
|
|
31792
31728
|
return JSON.parse(readFileSync24(p, "utf8"));
|
|
31793
31729
|
} catch {
|
|
@@ -31862,12 +31798,8 @@ async function applyProfileToBrain(args) {
|
|
|
31862
31798
|
installationId: args.appCreds.installationId,
|
|
31863
31799
|
fetchImpl: args.fetchImpl
|
|
31864
31800
|
});
|
|
31801
|
+
const contactsOnly = args.contactsOnly === true;
|
|
31865
31802
|
const { profileMd, memoryIndexLine: companyLine } = renderCompanyProfile(args.block, args.now);
|
|
31866
|
-
const { founderMd, memoryIndexLine: founderLine } = renderFounderRecord(
|
|
31867
|
-
args.block,
|
|
31868
|
-
{ subscriptionId: args.subscriptionId, azIdentity: args.azIdentity },
|
|
31869
|
-
args.now
|
|
31870
|
-
);
|
|
31871
31803
|
const read = (p) => readRepoFileViaApp({
|
|
31872
31804
|
token,
|
|
31873
31805
|
repo: args.brainRepo,
|
|
@@ -31875,13 +31807,25 @@ async function applyProfileToBrain(args) {
|
|
|
31875
31807
|
ref: args.branch,
|
|
31876
31808
|
fetchImpl: args.fetchImpl
|
|
31877
31809
|
});
|
|
31878
|
-
const existingProfile = await read(COMPANY_PROFILE_PATH);
|
|
31810
|
+
const existingProfile = contactsOnly ? null : await read(COMPANY_PROFILE_PATH);
|
|
31879
31811
|
const existingFounder = await read(FOUNDER_RECORD_PATH);
|
|
31880
31812
|
const existingIndex = await read(MEMORY_INDEX_PATH) ?? DEFAULT_MEMORY_INDEX_HEADER;
|
|
31881
|
-
|
|
31813
|
+
const keepsExistingAdvisor = contactsOnly && existingFounder !== null && args.advisorCleared !== true && !args.block.advisor_name.trim() && !args.block.advisor_email.trim();
|
|
31814
|
+
const recovered = keepsExistingAdvisor ? readAdvisorFromRecord(existingFounder) : null;
|
|
31815
|
+
const effectiveBlock = recovered ? { ...args.block, advisor_name: recovered.name, advisor_email: recovered.email } : args.block;
|
|
31816
|
+
const { founderMd: freshFounderMd, memoryIndexLine: founderLine } = renderFounderRecord(
|
|
31817
|
+
effectiveBlock,
|
|
31818
|
+
{ subscriptionId: args.subscriptionId, azIdentity: args.azIdentity },
|
|
31819
|
+
args.now
|
|
31820
|
+
);
|
|
31821
|
+
const founderMd = contactsOnly && existingFounder !== null ? updateFounderContacts(existingFounder, args.block, {
|
|
31822
|
+
subscriptionId: args.subscriptionId,
|
|
31823
|
+
advisorCleared: args.advisorCleared
|
|
31824
|
+
}) ?? freshFounderMd : freshFounderMd;
|
|
31825
|
+
let nextIndex = contactsOnly ? existingIndex : upsertMemoryIndexOnce(existingIndex, companyLine, COMPANY_PROFILE_PATH);
|
|
31882
31826
|
nextIndex = upsertMemoryIndexOnce(nextIndex, founderLine, FOUNDER_RECORD_PATH);
|
|
31883
31827
|
const files = [
|
|
31884
|
-
...!seededDocumentMatches(existingProfile, profileMd) ? [{ path: COMPANY_PROFILE_PATH, content: profileMd }] : [],
|
|
31828
|
+
...!contactsOnly && !seededDocumentMatches(existingProfile, profileMd) ? [{ path: COMPANY_PROFILE_PATH, content: profileMd }] : [],
|
|
31885
31829
|
...!seededDocumentMatches(existingFounder, founderMd) ? [{ path: FOUNDER_RECORD_PATH, content: founderMd }] : [],
|
|
31886
31830
|
...nextIndex !== existingIndex ? [{ path: MEMORY_INDEX_PATH, content: nextIndex }] : []
|
|
31887
31831
|
];
|
|
@@ -31890,17 +31834,17 @@ async function applyProfileToBrain(args) {
|
|
|
31890
31834
|
token,
|
|
31891
31835
|
repo: args.brainRepo,
|
|
31892
31836
|
branch: args.branch,
|
|
31893
|
-
message: "seed(brain): founder + company profile from onboarding",
|
|
31837
|
+
message: contactsOnly ? "seed(brain): how to reach the founder" : "seed(brain): founder + company profile from onboarding",
|
|
31894
31838
|
files,
|
|
31895
31839
|
fetchImpl: args.fetchImpl
|
|
31896
31840
|
});
|
|
31897
31841
|
const [verifiedProfile, verifiedFounder, verifiedIndex] = await Promise.all([
|
|
31898
|
-
read(COMPANY_PROFILE_PATH),
|
|
31842
|
+
contactsOnly ? Promise.resolve(null) : read(COMPANY_PROFILE_PATH),
|
|
31899
31843
|
read(FOUNDER_RECORD_PATH),
|
|
31900
31844
|
read(MEMORY_INDEX_PATH)
|
|
31901
31845
|
]);
|
|
31902
31846
|
const mismatches = [
|
|
31903
|
-
...!seededDocumentMatches(verifiedProfile, profileMd) ? [COMPANY_PROFILE_PATH] : [],
|
|
31847
|
+
...!contactsOnly && !seededDocumentMatches(verifiedProfile, profileMd) ? [COMPANY_PROFILE_PATH] : [],
|
|
31904
31848
|
...!seededDocumentMatches(verifiedFounder, founderMd) ? [FOUNDER_RECORD_PATH] : [],
|
|
31905
31849
|
...verifiedIndex !== nextIndex ? [MEMORY_INDEX_PATH] : []
|
|
31906
31850
|
];
|
|
@@ -31948,7 +31892,7 @@ async function applyProfileToBrains(args) {
|
|
|
31948
31892
|
}
|
|
31949
31893
|
}
|
|
31950
31894
|
function spawnDetachedSeedWatch() {
|
|
31951
|
-
const logPath =
|
|
31895
|
+
const logPath = path39.join(os17.homedir(), ".m8t", "seed-profile.log");
|
|
31952
31896
|
const fd = openSync(logPath, "a");
|
|
31953
31897
|
try {
|
|
31954
31898
|
const child = spawn6(process.execPath, [process.argv[1] ?? "", "bootstrap", "seed-profile", "--watch"], {
|
|
@@ -31960,6 +31904,8 @@ function spawnDetachedSeedWatch() {
|
|
|
31960
31904
|
closeSync(fd);
|
|
31961
31905
|
}
|
|
31962
31906
|
}
|
|
31907
|
+
var NO_PROFILE_LINE = `${colors.dim("\u2139 Your advisors don't know how to reach you yet \u2014 run 'm8t bootstrap profile'.")}
|
|
31908
|
+
`;
|
|
31963
31909
|
async function reactiveSeedOnInstallComplete(args) {
|
|
31964
31910
|
const ctx = await resolveSeedContext({
|
|
31965
31911
|
endpointOverride: args.endpoint,
|
|
@@ -31968,6 +31914,23 @@ async function reactiveSeedOnInstallComplete(args) {
|
|
|
31968
31914
|
brainsOverride: args.brains
|
|
31969
31915
|
});
|
|
31970
31916
|
if (!ctx) return;
|
|
31917
|
+
const local = await readOnboardingProfile(args.home);
|
|
31918
|
+
if (local) {
|
|
31919
|
+
await applyProfileToBrains({
|
|
31920
|
+
block: toOnboardingBlock(local),
|
|
31921
|
+
brainRepos: ctx.brainRepos,
|
|
31922
|
+
branch: "main",
|
|
31923
|
+
appCreds: ctx.appCreds,
|
|
31924
|
+
subscriptionId: ctx.subscriptionId,
|
|
31925
|
+
azIdentity: { name: local.founderName, email: local.founderEmail },
|
|
31926
|
+
contactsOnly: true,
|
|
31927
|
+
advisorCleared: local.advisorCleared === true,
|
|
31928
|
+
fetchImpl: args.fetchImpl
|
|
31929
|
+
});
|
|
31930
|
+
args.stdout(`${colors.success("\u2713")} Your advisors now know how to reach you (seeded ${ctx.brainRepos.join(", ")}).
|
|
31931
|
+
`);
|
|
31932
|
+
return;
|
|
31933
|
+
}
|
|
31971
31934
|
const token = await (args.getFoundryTokenImpl ?? getFoundryToken)();
|
|
31972
31935
|
const { hadIntake, block } = await findOnboardingProfile({ endpoint: ctx.endpoint, token, fetchImpl: args.fetchImpl });
|
|
31973
31936
|
if (block) {
|
|
@@ -31985,7 +31948,10 @@ async function reactiveSeedOnInstallComplete(args) {
|
|
|
31985
31948
|
`);
|
|
31986
31949
|
return;
|
|
31987
31950
|
}
|
|
31988
|
-
if (!hadIntake)
|
|
31951
|
+
if (!hadIntake) {
|
|
31952
|
+
args.stdout(NO_PROFILE_LINE);
|
|
31953
|
+
return;
|
|
31954
|
+
}
|
|
31989
31955
|
(args.spawnWatch ?? spawnDetachedSeedWatch)();
|
|
31990
31956
|
args.stdout(
|
|
31991
31957
|
`${colors.dim("\u2139 Your advisors will pick up your company profile when you finish the intake (watching in the background).")}
|
|
@@ -32026,16 +31992,16 @@ function renderInstallSummary(args) {
|
|
|
32026
31992
|
|
|
32027
31993
|
// src/lib/companion-install.ts
|
|
32028
31994
|
import { constants as constants2 } from "fs";
|
|
32029
|
-
import * as
|
|
32030
|
-
import * as
|
|
31995
|
+
import * as fs37 from "fs/promises";
|
|
31996
|
+
import * as path41 from "path";
|
|
32031
31997
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
32032
31998
|
import { execFile, spawn as spawn7 } from "child_process";
|
|
32033
31999
|
|
|
32034
32000
|
// src/lib/companion-artifact.ts
|
|
32035
32001
|
import { createHash as createHash7 } from "crypto";
|
|
32036
32002
|
import { constants } from "fs";
|
|
32037
|
-
import * as
|
|
32038
|
-
import * as
|
|
32003
|
+
import * as fs36 from "fs/promises";
|
|
32004
|
+
import * as path40 from "path";
|
|
32039
32005
|
var SHA256 = /^[a-f0-9]{64}$/u;
|
|
32040
32006
|
var VERSION2 = /^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$/u;
|
|
32041
32007
|
function exactKeys(value, expected) {
|
|
@@ -32043,23 +32009,23 @@ function exactKeys(value, expected) {
|
|
|
32043
32009
|
return actual.length === expected.length && actual.every((key2, index) => key2 === [...expected].sort()[index]);
|
|
32044
32010
|
}
|
|
32045
32011
|
function normalizedRelative(value) {
|
|
32046
|
-
if (value.length === 0 || value.includes("\\") || value.includes("\0") ||
|
|
32012
|
+
if (value.length === 0 || value.includes("\\") || value.includes("\0") || path40.posix.isAbsolute(value)) {
|
|
32047
32013
|
throw new Error("Artifact path must be a normalized relative POSIX path");
|
|
32048
32014
|
}
|
|
32049
|
-
const normalized =
|
|
32015
|
+
const normalized = path40.posix.normalize(value);
|
|
32050
32016
|
if (normalized !== value || normalized === "." || normalized === ".." || normalized.startsWith("../")) {
|
|
32051
32017
|
throw new Error("Artifact path escapes the payload root");
|
|
32052
32018
|
}
|
|
32053
32019
|
return value;
|
|
32054
32020
|
}
|
|
32055
32021
|
function validateLink(entryPath, target) {
|
|
32056
|
-
if (target.length === 0 || target.includes("\\") || target.includes("\0") ||
|
|
32022
|
+
if (target.length === 0 || target.includes("\\") || target.includes("\0") || path40.posix.isAbsolute(target)) {
|
|
32057
32023
|
throw new Error("Artifact symlink target must be relative");
|
|
32058
32024
|
}
|
|
32059
|
-
const resolved =
|
|
32060
|
-
|
|
32025
|
+
const resolved = path40.posix.normalize(
|
|
32026
|
+
path40.posix.join(path40.posix.dirname(entryPath), target)
|
|
32061
32027
|
);
|
|
32062
|
-
if (resolved === ".." || resolved.startsWith("../") ||
|
|
32028
|
+
if (resolved === ".." || resolved.startsWith("../") || path40.posix.isAbsolute(resolved)) {
|
|
32063
32029
|
throw new Error("Artifact symlink target escapes the payload root");
|
|
32064
32030
|
}
|
|
32065
32031
|
return target;
|
|
@@ -32140,17 +32106,17 @@ function parseArtifactManifest(value) {
|
|
|
32140
32106
|
};
|
|
32141
32107
|
}
|
|
32142
32108
|
async function sha256File2(filePath) {
|
|
32143
|
-
return createHash7("sha256").update(await
|
|
32109
|
+
return createHash7("sha256").update(await fs36.readFile(filePath)).digest("hex");
|
|
32144
32110
|
}
|
|
32145
32111
|
async function walk2(root, relative4 = "") {
|
|
32146
|
-
const directory =
|
|
32147
|
-
const children = await
|
|
32112
|
+
const directory = path40.join(root, ...relative4.split("/").filter(Boolean));
|
|
32113
|
+
const children = await fs36.readdir(directory, { withFileTypes: true });
|
|
32148
32114
|
const entries = [];
|
|
32149
32115
|
for (const child of children.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
32150
32116
|
const childRelative = relative4 ? `${relative4}/${child.name}` : child.name;
|
|
32151
32117
|
normalizedRelative(childRelative);
|
|
32152
|
-
const childPath =
|
|
32153
|
-
const stat5 = await
|
|
32118
|
+
const childPath = path40.join(directory, child.name);
|
|
32119
|
+
const stat5 = await fs36.lstat(childPath);
|
|
32154
32120
|
if (stat5.isDirectory()) {
|
|
32155
32121
|
entries.push(...await walk2(root, childRelative));
|
|
32156
32122
|
} else if (stat5.isFile()) {
|
|
@@ -32162,7 +32128,7 @@ async function walk2(root, relative4 = "") {
|
|
|
32162
32128
|
sha256: await sha256File2(childPath)
|
|
32163
32129
|
});
|
|
32164
32130
|
} else if (stat5.isSymbolicLink()) {
|
|
32165
|
-
const target = await
|
|
32131
|
+
const target = await fs36.readlink(childPath);
|
|
32166
32132
|
entries.push({
|
|
32167
32133
|
type: "symlink",
|
|
32168
32134
|
path: childRelative,
|
|
@@ -32175,19 +32141,19 @@ async function walk2(root, relative4 = "") {
|
|
|
32175
32141
|
return entries;
|
|
32176
32142
|
}
|
|
32177
32143
|
async function ensureRealDirectory(root) {
|
|
32178
|
-
const stat5 = await
|
|
32144
|
+
const stat5 = await fs36.lstat(root);
|
|
32179
32145
|
if (stat5.isSymbolicLink()) throw new Error("Artifact root is a symbolic link");
|
|
32180
32146
|
if (!stat5.isDirectory()) throw new Error("Artifact root is not a directory");
|
|
32181
32147
|
}
|
|
32182
32148
|
async function validateResolvedLinks(root, entries) {
|
|
32183
|
-
const realRoot = await
|
|
32149
|
+
const realRoot = await fs36.realpath(root);
|
|
32184
32150
|
for (const entry of entries) {
|
|
32185
32151
|
if (entry.type !== "symlink") continue;
|
|
32186
32152
|
try {
|
|
32187
|
-
const linkPath =
|
|
32188
|
-
const resolved = await
|
|
32189
|
-
const relative4 =
|
|
32190
|
-
if (relative4 === ".." || relative4.startsWith(`..${
|
|
32153
|
+
const linkPath = path40.join(root, ...entry.path.split("/"));
|
|
32154
|
+
const resolved = await fs36.realpath(linkPath);
|
|
32155
|
+
const relative4 = path40.relative(realRoot, resolved);
|
|
32156
|
+
if (relative4 === ".." || relative4.startsWith(`..${path40.sep}`) || path40.isAbsolute(relative4)) {
|
|
32191
32157
|
throw new Error("Artifact symlink chain escapes the payload root");
|
|
32192
32158
|
}
|
|
32193
32159
|
} catch (error) {
|
|
@@ -32217,13 +32183,13 @@ async function buildArtifactManifest(payloadRoot, input) {
|
|
|
32217
32183
|
return manifest;
|
|
32218
32184
|
}
|
|
32219
32185
|
async function readArtifactManifest(manifestPath) {
|
|
32220
|
-
const before = await
|
|
32186
|
+
const before = await fs36.lstat(manifestPath);
|
|
32221
32187
|
if (before.isSymbolicLink() || !before.isFile()) {
|
|
32222
32188
|
throw new Error("Artifact manifest is a symbolic link or non-file");
|
|
32223
32189
|
}
|
|
32224
32190
|
let handle;
|
|
32225
32191
|
try {
|
|
32226
|
-
handle = await
|
|
32192
|
+
handle = await fs36.open(
|
|
32227
32193
|
manifestPath,
|
|
32228
32194
|
constants.O_RDONLY | constants.O_NOFOLLOW
|
|
32229
32195
|
);
|
|
@@ -32240,9 +32206,9 @@ async function readArtifactManifest(manifestPath) {
|
|
|
32240
32206
|
}
|
|
32241
32207
|
}
|
|
32242
32208
|
function ensureContainedEntry(root, entryPath) {
|
|
32243
|
-
const absolute =
|
|
32244
|
-
const relative4 =
|
|
32245
|
-
if (relative4.startsWith("..") ||
|
|
32209
|
+
const absolute = path40.join(root, ...entryPath.split("/"));
|
|
32210
|
+
const relative4 = path40.relative(root, absolute);
|
|
32211
|
+
if (relative4.startsWith("..") || path40.isAbsolute(relative4)) {
|
|
32246
32212
|
throw new Error("Artifact entry escapes payload root");
|
|
32247
32213
|
}
|
|
32248
32214
|
return absolute;
|
|
@@ -32281,27 +32247,27 @@ async function verifyArtifactPayload(payloadRoot, manifest) {
|
|
|
32281
32247
|
async function copyArtifactPayload(sourceRoot, targetRoot, manifest) {
|
|
32282
32248
|
await verifyArtifactSource(sourceRoot, manifest);
|
|
32283
32249
|
try {
|
|
32284
|
-
const targetStat = await
|
|
32250
|
+
const targetStat = await fs36.lstat(targetRoot);
|
|
32285
32251
|
if (targetStat.isSymbolicLink()) {
|
|
32286
32252
|
throw new Error("Install target is a symbolic link");
|
|
32287
32253
|
}
|
|
32288
32254
|
if (!targetStat.isDirectory()) throw new Error("Install target is not a directory");
|
|
32289
|
-
if ((await
|
|
32255
|
+
if ((await fs36.readdir(targetRoot)).length > 0) {
|
|
32290
32256
|
throw new Error("Install staging target is not empty");
|
|
32291
32257
|
}
|
|
32292
32258
|
} catch (error) {
|
|
32293
32259
|
if (error.code !== "ENOENT") throw error;
|
|
32294
|
-
await
|
|
32260
|
+
await fs36.mkdir(targetRoot, { recursive: false, mode: 448 });
|
|
32295
32261
|
}
|
|
32296
32262
|
for (const entry of manifest.entries) {
|
|
32297
32263
|
const source = ensureContainedEntry(sourceRoot, entry.path);
|
|
32298
32264
|
const target = ensureContainedEntry(targetRoot, entry.path);
|
|
32299
|
-
await
|
|
32265
|
+
await fs36.mkdir(path40.dirname(target), { recursive: true, mode: 448 });
|
|
32300
32266
|
if (entry.type === "file") {
|
|
32301
|
-
await
|
|
32302
|
-
await
|
|
32267
|
+
await fs36.copyFile(source, target);
|
|
32268
|
+
await fs36.chmod(target, entry.mode);
|
|
32303
32269
|
} else {
|
|
32304
|
-
await
|
|
32270
|
+
await fs36.symlink(entry.target, target);
|
|
32305
32271
|
}
|
|
32306
32272
|
}
|
|
32307
32273
|
await verifyArtifactPayload(targetRoot, manifest);
|
|
@@ -32317,18 +32283,18 @@ function commandError(error, message) {
|
|
|
32317
32283
|
if (code !== void 0) wrapped.code = code;
|
|
32318
32284
|
return wrapped;
|
|
32319
32285
|
}
|
|
32320
|
-
function execFileAsync(
|
|
32286
|
+
function execFileAsync(file2, args) {
|
|
32321
32287
|
return new Promise((resolve6, reject) => {
|
|
32322
|
-
execFile(
|
|
32288
|
+
execFile(file2, [...args], { windowsHide: true }, (error) => {
|
|
32323
32289
|
if (error) reject(commandError(error, "Login-item command failed"));
|
|
32324
32290
|
else resolve6();
|
|
32325
32291
|
});
|
|
32326
32292
|
});
|
|
32327
32293
|
}
|
|
32328
|
-
function execFileOutput(
|
|
32294
|
+
function execFileOutput(file2, args) {
|
|
32329
32295
|
return new Promise((resolve6, reject) => {
|
|
32330
32296
|
execFile(
|
|
32331
|
-
|
|
32297
|
+
file2,
|
|
32332
32298
|
[...args],
|
|
32333
32299
|
{ windowsHide: true },
|
|
32334
32300
|
(error, stdout) => {
|
|
@@ -32340,25 +32306,25 @@ function execFileOutput(file, args) {
|
|
|
32340
32306
|
}
|
|
32341
32307
|
async function setCompanionStartAtLogin(input) {
|
|
32342
32308
|
if (input.platform === "darwin") {
|
|
32343
|
-
const launchAgents =
|
|
32344
|
-
const registration =
|
|
32309
|
+
const launchAgents = path41.join(input.homeDirectory, "Library", "LaunchAgents");
|
|
32310
|
+
const registration = path41.join(
|
|
32345
32311
|
launchAgents,
|
|
32346
32312
|
"com.m8t.companion.plist"
|
|
32347
32313
|
);
|
|
32348
32314
|
await assertNotSymlink(registration, "Start-at-login registration");
|
|
32349
32315
|
if (!input.enabled) {
|
|
32350
|
-
await
|
|
32316
|
+
await fs37.rm(registration, { force: true });
|
|
32351
32317
|
return;
|
|
32352
32318
|
}
|
|
32353
|
-
await
|
|
32319
|
+
await fs37.mkdir(launchAgents, { recursive: true, mode: 448 });
|
|
32354
32320
|
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";
|
|
32355
32321
|
await atomicWriteText(registration, plist, 384);
|
|
32356
32322
|
return;
|
|
32357
32323
|
}
|
|
32358
32324
|
if (input.platform === "win32") {
|
|
32359
32325
|
const key2 = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
|
|
32360
|
-
const run2 = input.runCommand ?? (async (
|
|
32361
|
-
await execFileAsync(
|
|
32326
|
+
const run2 = input.runCommand ?? (async (file2, args) => {
|
|
32327
|
+
await execFileAsync(file2, args);
|
|
32362
32328
|
return "";
|
|
32363
32329
|
});
|
|
32364
32330
|
if (input.enabled) {
|
|
@@ -32402,7 +32368,7 @@ async function setCompanionStartAtLogin(input) {
|
|
|
32402
32368
|
}
|
|
32403
32369
|
async function getCompanionStartAtLogin(input) {
|
|
32404
32370
|
if (input.platform === "darwin") {
|
|
32405
|
-
const registration =
|
|
32371
|
+
const registration = path41.join(
|
|
32406
32372
|
input.homeDirectory,
|
|
32407
32373
|
"Library",
|
|
32408
32374
|
"LaunchAgents",
|
|
@@ -32464,26 +32430,26 @@ function assertSupported(options) {
|
|
|
32464
32430
|
}
|
|
32465
32431
|
function companionInstallPaths(options) {
|
|
32466
32432
|
assertSupported(options);
|
|
32467
|
-
const companionState =
|
|
32433
|
+
const companionState = path41.join(
|
|
32468
32434
|
options.homeDirectory,
|
|
32469
32435
|
".m8t",
|
|
32470
32436
|
"companion"
|
|
32471
32437
|
);
|
|
32472
|
-
const targetRoot = options.platform === "darwin" ?
|
|
32438
|
+
const targetRoot = options.platform === "darwin" ? path41.join(
|
|
32473
32439
|
options.homeDirectory,
|
|
32474
32440
|
"Applications",
|
|
32475
32441
|
"m8t Companion.app"
|
|
32476
|
-
) :
|
|
32477
|
-
options.localAppData ??
|
|
32442
|
+
) : path41.join(
|
|
32443
|
+
options.localAppData ?? path41.join(options.homeDirectory, "AppData", "Local"),
|
|
32478
32444
|
"m8t",
|
|
32479
32445
|
"companion",
|
|
32480
32446
|
"app"
|
|
32481
32447
|
);
|
|
32482
32448
|
return {
|
|
32483
32449
|
targetRoot,
|
|
32484
|
-
installManifest:
|
|
32485
|
-
runtimeBinding:
|
|
32486
|
-
preferences:
|
|
32450
|
+
installManifest: path41.join(companionState, "install-v1.json"),
|
|
32451
|
+
runtimeBinding: path41.join(companionState, "runtime-v1.json"),
|
|
32452
|
+
preferences: path41.join(companionState, "preferences-v1.json")
|
|
32487
32453
|
};
|
|
32488
32454
|
}
|
|
32489
32455
|
function exactKeys2(value, keys) {
|
|
@@ -32494,7 +32460,7 @@ function parseInstallManifest(value) {
|
|
|
32494
32460
|
throw new Error("Install manifest schema is invalid");
|
|
32495
32461
|
}
|
|
32496
32462
|
const record = value;
|
|
32497
|
-
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" || !
|
|
32463
|
+
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" || !path41.isAbsolute(record.ownedTargetRoot)) {
|
|
32498
32464
|
throw new Error("Install manifest schema is invalid");
|
|
32499
32465
|
}
|
|
32500
32466
|
return record;
|
|
@@ -32504,7 +32470,7 @@ function parseRuntimeBinding(value, platform) {
|
|
|
32504
32470
|
throw new Error("Runtime binding schema is invalid");
|
|
32505
32471
|
}
|
|
32506
32472
|
const record = value;
|
|
32507
|
-
const paths = platform === "win32" ?
|
|
32473
|
+
const paths = platform === "win32" ? path41.win32 : path41.posix;
|
|
32508
32474
|
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") {
|
|
32509
32475
|
throw new Error("Runtime binding schema is invalid");
|
|
32510
32476
|
}
|
|
@@ -32529,7 +32495,7 @@ function validateGatewayOrigin(value) {
|
|
|
32529
32495
|
}
|
|
32530
32496
|
async function assertNotSymlink(filePath, kind) {
|
|
32531
32497
|
try {
|
|
32532
|
-
if ((await
|
|
32498
|
+
if ((await fs37.lstat(filePath)).isSymbolicLink()) {
|
|
32533
32499
|
throw new Error(`${kind} is a symbolic link`);
|
|
32534
32500
|
}
|
|
32535
32501
|
} catch (error) {
|
|
@@ -32538,20 +32504,20 @@ async function assertNotSymlink(filePath, kind) {
|
|
|
32538
32504
|
}
|
|
32539
32505
|
}
|
|
32540
32506
|
async function assertOwnedDirectoryChain(anchor, targetDirectory) {
|
|
32541
|
-
const relative4 =
|
|
32542
|
-
if (relative4 === ".." || relative4.startsWith(`..${
|
|
32507
|
+
const relative4 = path41.relative(anchor, targetDirectory);
|
|
32508
|
+
if (relative4 === ".." || relative4.startsWith(`..${path41.sep}`) || path41.isAbsolute(relative4)) {
|
|
32543
32509
|
throw new Error("Companion owned directory escapes its trusted anchor");
|
|
32544
32510
|
}
|
|
32545
|
-
const segments = relative4.split(
|
|
32511
|
+
const segments = relative4.split(path41.sep).filter(Boolean);
|
|
32546
32512
|
let current = anchor;
|
|
32547
|
-
const anchorStatus = await
|
|
32513
|
+
const anchorStatus = await fs37.lstat(anchor);
|
|
32548
32514
|
if (anchorStatus.isSymbolicLink() || !anchorStatus.isDirectory()) {
|
|
32549
32515
|
throw new Error("Companion owned directory anchor is unsafe");
|
|
32550
32516
|
}
|
|
32551
32517
|
for (const segment of segments) {
|
|
32552
|
-
current =
|
|
32518
|
+
current = path41.join(current, segment);
|
|
32553
32519
|
try {
|
|
32554
|
-
const status = await
|
|
32520
|
+
const status = await fs37.lstat(current);
|
|
32555
32521
|
if (status.isSymbolicLink() || !status.isDirectory()) {
|
|
32556
32522
|
throw new Error("Companion owned directory contains a symbolic link");
|
|
32557
32523
|
}
|
|
@@ -32564,19 +32530,19 @@ async function assertOwnedDirectoryChain(anchor, targetDirectory) {
|
|
|
32564
32530
|
async function assertOwnedParents(options, paths) {
|
|
32565
32531
|
await assertOwnedDirectoryChain(
|
|
32566
32532
|
options.homeDirectory,
|
|
32567
|
-
|
|
32533
|
+
path41.dirname(paths.installManifest)
|
|
32568
32534
|
);
|
|
32569
|
-
const targetAnchor = options.platform === "win32" ? options.localAppData ??
|
|
32570
|
-
await assertOwnedDirectoryChain(targetAnchor,
|
|
32535
|
+
const targetAnchor = options.platform === "win32" ? options.localAppData ?? path41.join(options.homeDirectory, "AppData", "Local") : options.homeDirectory;
|
|
32536
|
+
await assertOwnedDirectoryChain(targetAnchor, path41.dirname(paths.targetRoot));
|
|
32571
32537
|
}
|
|
32572
32538
|
async function readRegularText(filePath, maxBytes) {
|
|
32573
|
-
const before = await
|
|
32539
|
+
const before = await fs37.lstat(filePath);
|
|
32574
32540
|
if (before.isSymbolicLink() || !before.isFile()) {
|
|
32575
32541
|
throw new Error("Companion state file is not a regular file");
|
|
32576
32542
|
}
|
|
32577
32543
|
let handle;
|
|
32578
32544
|
try {
|
|
32579
|
-
handle = await
|
|
32545
|
+
handle = await fs37.open(
|
|
32580
32546
|
filePath,
|
|
32581
32547
|
constants2.O_RDONLY | constants2.O_NOFOLLOW
|
|
32582
32548
|
);
|
|
@@ -32599,27 +32565,27 @@ async function atomicWriteJson(filePath, value) {
|
|
|
32599
32565
|
}
|
|
32600
32566
|
async function atomicWriteText(filePath, contents, mode) {
|
|
32601
32567
|
await assertNotSymlink(filePath, "Companion state file");
|
|
32602
|
-
await
|
|
32568
|
+
await fs37.mkdir(path41.dirname(filePath), {
|
|
32603
32569
|
recursive: true,
|
|
32604
32570
|
mode: 448
|
|
32605
32571
|
});
|
|
32606
32572
|
const temporary = `${filePath}.${randomUUID3()}.tmp`;
|
|
32607
32573
|
try {
|
|
32608
|
-
await
|
|
32574
|
+
await fs37.writeFile(temporary, contents, {
|
|
32609
32575
|
mode,
|
|
32610
32576
|
flag: "wx"
|
|
32611
32577
|
});
|
|
32612
|
-
await
|
|
32613
|
-
await
|
|
32578
|
+
await fs37.rename(temporary, filePath);
|
|
32579
|
+
await fs37.chmod(filePath, mode).catch(() => void 0);
|
|
32614
32580
|
} finally {
|
|
32615
|
-
await
|
|
32581
|
+
await fs37.rm(temporary, { force: true }).catch(() => void 0);
|
|
32616
32582
|
}
|
|
32617
32583
|
}
|
|
32618
32584
|
async function realRegularFile(filePath, executable) {
|
|
32619
|
-
const real = await
|
|
32620
|
-
const stat5 = await
|
|
32585
|
+
const real = await fs37.realpath(filePath);
|
|
32586
|
+
const stat5 = await fs37.stat(real);
|
|
32621
32587
|
if (!stat5.isFile()) throw new Error("Companion launch target is not a file");
|
|
32622
|
-
await
|
|
32588
|
+
await fs37.access(real, executable ? constants2.X_OK : constants2.R_OK);
|
|
32623
32589
|
return real;
|
|
32624
32590
|
}
|
|
32625
32591
|
function defaultLaunch(executable) {
|
|
@@ -32635,7 +32601,7 @@ async function companionIsRunning(platform, executable) {
|
|
|
32635
32601
|
if (platform !== "win32") return false;
|
|
32636
32602
|
let handle;
|
|
32637
32603
|
try {
|
|
32638
|
-
handle = await
|
|
32604
|
+
handle = await fs37.open(executable, "r+");
|
|
32639
32605
|
} catch (error) {
|
|
32640
32606
|
const code = error.code;
|
|
32641
32607
|
if (code === "ENOENT") return false;
|
|
@@ -32716,7 +32682,7 @@ async function statusCompanion(options) {
|
|
|
32716
32682
|
realRegularFile(runtime.nodeExecutable, options.platform !== "win32"),
|
|
32717
32683
|
realRegularFile(runtime.cliEntry, false)
|
|
32718
32684
|
]);
|
|
32719
|
-
const executable =
|
|
32685
|
+
const executable = path41.join(
|
|
32720
32686
|
paths.targetRoot,
|
|
32721
32687
|
...install.entryRelativePath.split("/")
|
|
32722
32688
|
);
|
|
@@ -32749,7 +32715,7 @@ async function snapshotFile(filePath) {
|
|
|
32749
32715
|
}
|
|
32750
32716
|
async function restoreFile(filePath, bytes) {
|
|
32751
32717
|
if (bytes === null) {
|
|
32752
|
-
await
|
|
32718
|
+
await fs37.rm(filePath, { force: true });
|
|
32753
32719
|
} else {
|
|
32754
32720
|
await atomicWriteJson(filePath, JSON.parse(bytes.toString("utf8")));
|
|
32755
32721
|
}
|
|
@@ -32764,8 +32730,8 @@ async function converge(options, force) {
|
|
|
32764
32730
|
if (artifactManifest.platform !== options.platform || artifactManifest.architecture !== options.architecture) {
|
|
32765
32731
|
throw new Error("Companion artifact does not match this OS and architecture");
|
|
32766
32732
|
}
|
|
32767
|
-
const payloadRoot =
|
|
32768
|
-
|
|
32733
|
+
const payloadRoot = path41.join(
|
|
32734
|
+
path41.dirname(options.artifactManifestPath),
|
|
32769
32735
|
"payload"
|
|
32770
32736
|
);
|
|
32771
32737
|
await verifyArtifactSource(payloadRoot, artifactManifest);
|
|
@@ -32785,7 +32751,7 @@ async function converge(options, force) {
|
|
|
32785
32751
|
if (priorInstalled) {
|
|
32786
32752
|
await assertCompanionNotRunning(
|
|
32787
32753
|
options,
|
|
32788
|
-
|
|
32754
|
+
path41.join(
|
|
32789
32755
|
priorInstalled.paths.targetRoot,
|
|
32790
32756
|
...priorInstalled.install.entryRelativePath.split("/")
|
|
32791
32757
|
)
|
|
@@ -32793,7 +32759,7 @@ async function converge(options, force) {
|
|
|
32793
32759
|
}
|
|
32794
32760
|
if (current.state === "not-installed") {
|
|
32795
32761
|
try {
|
|
32796
|
-
await
|
|
32762
|
+
await fs37.lstat(paths.targetRoot);
|
|
32797
32763
|
throw new Error(
|
|
32798
32764
|
"The fixed companion target exists without an owned install manifest"
|
|
32799
32765
|
);
|
|
@@ -32804,7 +32770,7 @@ async function converge(options, force) {
|
|
|
32804
32770
|
const stage = `${paths.targetRoot}.m8t-stage-${randomUUID3()}`;
|
|
32805
32771
|
const backup = `${paths.targetRoot}.m8t-backup-${randomUUID3()}`;
|
|
32806
32772
|
const copy = options.copyPayload ?? copyArtifactPayload;
|
|
32807
|
-
await
|
|
32773
|
+
await fs37.mkdir(path41.dirname(paths.targetRoot), {
|
|
32808
32774
|
recursive: true,
|
|
32809
32775
|
mode: 448
|
|
32810
32776
|
});
|
|
@@ -32821,12 +32787,12 @@ async function converge(options, force) {
|
|
|
32821
32787
|
try {
|
|
32822
32788
|
await copy(payloadRoot, stage, artifactManifest);
|
|
32823
32789
|
try {
|
|
32824
|
-
await
|
|
32790
|
+
await fs37.rename(paths.targetRoot, backup);
|
|
32825
32791
|
movedPrior = true;
|
|
32826
32792
|
} catch (error) {
|
|
32827
32793
|
if (error.code !== "ENOENT") throw error;
|
|
32828
32794
|
}
|
|
32829
|
-
await
|
|
32795
|
+
await fs37.rename(stage, paths.targetRoot);
|
|
32830
32796
|
installedStage = true;
|
|
32831
32797
|
const nodeExecutable = await realRegularFile(
|
|
32832
32798
|
options.nodeExecutable,
|
|
@@ -32868,7 +32834,7 @@ async function converge(options, force) {
|
|
|
32868
32834
|
await readClosedJson(paths.runtimeBinding),
|
|
32869
32835
|
options.platform
|
|
32870
32836
|
);
|
|
32871
|
-
const executable =
|
|
32837
|
+
const executable = path41.join(
|
|
32872
32838
|
paths.targetRoot,
|
|
32873
32839
|
...artifactManifest.entryRelativePath.split("/")
|
|
32874
32840
|
);
|
|
@@ -32878,7 +32844,7 @@ async function converge(options, force) {
|
|
|
32878
32844
|
platform: options.platform,
|
|
32879
32845
|
executable: ownedExecutable
|
|
32880
32846
|
}));
|
|
32881
|
-
priorLoginExecutable = priorInstalled ?
|
|
32847
|
+
priorLoginExecutable = priorInstalled ? path41.join(
|
|
32882
32848
|
priorInstalled.paths.targetRoot,
|
|
32883
32849
|
...priorInstalled.install.entryRelativePath.split("/")
|
|
32884
32850
|
) : executable;
|
|
@@ -32889,7 +32855,7 @@ async function converge(options, force) {
|
|
|
32889
32855
|
);
|
|
32890
32856
|
loginChanged = true;
|
|
32891
32857
|
await (options.launch ?? defaultLaunch)(executable);
|
|
32892
|
-
await
|
|
32858
|
+
await fs37.rm(backup, { recursive: true, force: true });
|
|
32893
32859
|
return {
|
|
32894
32860
|
state: "installed",
|
|
32895
32861
|
version: artifactManifest.version,
|
|
@@ -32903,15 +32869,15 @@ async function converge(options, force) {
|
|
|
32903
32869
|
() => void 0
|
|
32904
32870
|
);
|
|
32905
32871
|
}
|
|
32906
|
-
await
|
|
32872
|
+
await fs37.rm(stage, { recursive: true, force: true }).catch(() => void 0);
|
|
32907
32873
|
if (installedStage) {
|
|
32908
|
-
await
|
|
32874
|
+
await fs37.rm(paths.targetRoot, {
|
|
32909
32875
|
recursive: true,
|
|
32910
32876
|
force: true
|
|
32911
32877
|
}).catch(() => void 0);
|
|
32912
32878
|
}
|
|
32913
32879
|
if (movedPrior) {
|
|
32914
|
-
await
|
|
32880
|
+
await fs37.rename(backup, paths.targetRoot).catch(() => void 0);
|
|
32915
32881
|
}
|
|
32916
32882
|
await Promise.all([
|
|
32917
32883
|
restoreFile(paths.installManifest, snapshots[0]),
|
|
@@ -32937,14 +32903,14 @@ async function uninstallCompanion(options) {
|
|
|
32937
32903
|
if (install.ownedTargetRoot !== paths.targetRoot) {
|
|
32938
32904
|
throw new Error("Refusing to remove a non-owned companion target");
|
|
32939
32905
|
}
|
|
32940
|
-
const executable =
|
|
32906
|
+
const executable = path41.join(
|
|
32941
32907
|
paths.targetRoot,
|
|
32942
32908
|
...install.entryRelativePath.split("/")
|
|
32943
32909
|
);
|
|
32944
32910
|
await assertCompanionNotRunning(options, executable);
|
|
32945
32911
|
await (options.setStartAtLogin ?? (() => Promise.resolve()))(executable, false);
|
|
32946
32912
|
const remove = options.removeOwnedPath ?? (async (ownedPath, recursive) => {
|
|
32947
|
-
await
|
|
32913
|
+
await fs37.rm(ownedPath, { recursive, force: true });
|
|
32948
32914
|
});
|
|
32949
32915
|
await remove(paths.targetRoot, true);
|
|
32950
32916
|
await remove(paths.installManifest, false);
|
|
@@ -32954,8 +32920,8 @@ async function uninstallCompanion(options) {
|
|
|
32954
32920
|
}
|
|
32955
32921
|
|
|
32956
32922
|
// src/commands/companion/install.ts
|
|
32957
|
-
import * as
|
|
32958
|
-
import * as
|
|
32923
|
+
import * as os19 from "os";
|
|
32924
|
+
import * as path43 from "path";
|
|
32959
32925
|
import { Command as Command59, Option as Option56 } from "clipanion";
|
|
32960
32926
|
|
|
32961
32927
|
// src/lib/companion-channel.ts
|
|
@@ -32973,9 +32939,9 @@ async function readCompanionRelease(source = { url: CHANNEL_LATEST_URL }, deps =
|
|
|
32973
32939
|
// src/lib/companion-download.ts
|
|
32974
32940
|
import { createHash as createHash8 } from "crypto";
|
|
32975
32941
|
import { execFile as execFile2 } from "child_process";
|
|
32976
|
-
import * as
|
|
32977
|
-
import * as
|
|
32978
|
-
import * as
|
|
32942
|
+
import * as fs38 from "fs/promises";
|
|
32943
|
+
import * as os18 from "os";
|
|
32944
|
+
import * as path42 from "path";
|
|
32979
32945
|
init_errors();
|
|
32980
32946
|
var MAX_ASSET_BYTES = 512 * 1024 * 1024;
|
|
32981
32947
|
function extractArchive(archive, into) {
|
|
@@ -33028,20 +32994,20 @@ async function downloadCompanionArtifact(component, deps = {}) {
|
|
|
33028
32994
|
hint: "Nothing was unpacked. This is what a corrupted download or a substituted file looks like \u2014 retry, and report it if it persists."
|
|
33029
32995
|
});
|
|
33030
32996
|
}
|
|
33031
|
-
const root = await (deps.makeTemporaryDirectory ?? (() =>
|
|
32997
|
+
const root = await (deps.makeTemporaryDirectory ?? (() => fs38.mkdtemp(path42.join(os18.tmpdir(), "m8t-companion-"))))();
|
|
33032
32998
|
const dispose = async () => {
|
|
33033
|
-
await
|
|
32999
|
+
await fs38.rm(root, { recursive: true, force: true }).catch(() => void 0);
|
|
33034
33000
|
};
|
|
33035
33001
|
try {
|
|
33036
|
-
const archive =
|
|
33037
|
-
await
|
|
33038
|
-
const unpacked =
|
|
33039
|
-
await
|
|
33002
|
+
const archive = path42.join(root, pinned.asset);
|
|
33003
|
+
await fs38.writeFile(archive, bytes, { mode: 384 });
|
|
33004
|
+
const unpacked = path42.join(root, "unpacked");
|
|
33005
|
+
await fs38.mkdir(unpacked, { recursive: false, mode: 448 });
|
|
33040
33006
|
await (deps.extract ?? extractArchive)(archive, unpacked);
|
|
33041
|
-
await
|
|
33007
|
+
await fs38.rm(archive, { force: true });
|
|
33042
33008
|
return {
|
|
33043
33009
|
version: component.version,
|
|
33044
|
-
artifactManifestPath:
|
|
33010
|
+
artifactManifestPath: path42.join(unpacked, "artifact-v1.json"),
|
|
33045
33011
|
dispose
|
|
33046
33012
|
};
|
|
33047
33013
|
} catch (error) {
|
|
@@ -33054,7 +33020,7 @@ async function downloadCompanionArtifact(component, deps = {}) {
|
|
|
33054
33020
|
function defaultLocalCompanionInstallOptions() {
|
|
33055
33021
|
const cliEntry = process.argv[1];
|
|
33056
33022
|
if (!cliEntry) throw new Error("Cannot resolve the installed CLI entry");
|
|
33057
|
-
const homeDirectory =
|
|
33023
|
+
const homeDirectory = os19.homedir();
|
|
33058
33024
|
const platform = process.platform;
|
|
33059
33025
|
return {
|
|
33060
33026
|
homeDirectory,
|
|
@@ -33072,7 +33038,7 @@ async function convergeCompanionFromChannel(converge2, options = {}) {
|
|
|
33072
33038
|
let artifactManifestPath;
|
|
33073
33039
|
let dispose = () => Promise.resolve();
|
|
33074
33040
|
if (stagedDirectory !== void 0) {
|
|
33075
|
-
artifactManifestPath =
|
|
33041
|
+
artifactManifestPath = path43.resolve(stagedDirectory, "artifact-v1.json");
|
|
33076
33042
|
} else {
|
|
33077
33043
|
const release = await readCompanionRelease(
|
|
33078
33044
|
platformVersion !== void 0 ? { channel: true, version: platformTag(platformVersion) } : { url: CHANNEL_LATEST_URL }
|
|
@@ -33148,9 +33114,9 @@ function resolveRepoRootMarker(args) {
|
|
|
33148
33114
|
if (args.existing !== null && args.existing !== "") return args.existing;
|
|
33149
33115
|
return args.cwd;
|
|
33150
33116
|
}
|
|
33151
|
-
async function looksLikeCheckout(
|
|
33117
|
+
async function looksLikeCheckout(dir2) {
|
|
33152
33118
|
try {
|
|
33153
|
-
return (await
|
|
33119
|
+
return (await fs39.stat(path44.join(dir2, "brain-template"))).isDirectory();
|
|
33154
33120
|
} catch {
|
|
33155
33121
|
return false;
|
|
33156
33122
|
}
|
|
@@ -33166,21 +33132,21 @@ var defaultDeps3 = {
|
|
|
33166
33132
|
convergeCompanion: (platformVersion) => convergeCompanionFromChannel(installCompanion, {
|
|
33167
33133
|
...platformVersion !== void 0 ? { platformVersion } : {}
|
|
33168
33134
|
}),
|
|
33169
|
-
homedir: () =>
|
|
33135
|
+
homedir: () => os20.homedir()
|
|
33170
33136
|
};
|
|
33171
33137
|
async function finalizeInstall(args, deps = defaultDeps3) {
|
|
33172
|
-
const markerDir =
|
|
33173
|
-
const markerPath =
|
|
33138
|
+
const markerDir = path44.join(deps.homedir(), ".m8t");
|
|
33139
|
+
const markerPath = path44.join(markerDir, "repo-root");
|
|
33174
33140
|
const cwd = process.cwd();
|
|
33175
|
-
const existing = await
|
|
33141
|
+
const existing = await fs39.readFile(markerPath, "utf8").then((s) => s.trim()).catch(() => null);
|
|
33176
33142
|
const repoRoot = resolveRepoRootMarker({
|
|
33177
33143
|
...args.repoRoot !== void 0 ? { explicit: args.repoRoot } : {},
|
|
33178
33144
|
cwd,
|
|
33179
33145
|
cwdIsCheckout: await looksLikeCheckout(cwd),
|
|
33180
33146
|
existing
|
|
33181
33147
|
});
|
|
33182
|
-
await
|
|
33183
|
-
await
|
|
33148
|
+
await fs39.mkdir(markerDir, { recursive: true });
|
|
33149
|
+
await fs39.writeFile(markerPath, `${repoRoot}
|
|
33184
33150
|
`, "utf8");
|
|
33185
33151
|
let webappUrl;
|
|
33186
33152
|
try {
|
|
@@ -33258,7 +33224,7 @@ async function finalizeInstall(args, deps = defaultDeps3) {
|
|
|
33258
33224
|
}
|
|
33259
33225
|
let brainOrg = null;
|
|
33260
33226
|
try {
|
|
33261
|
-
const credsRaw = await
|
|
33227
|
+
const credsRaw = await fs39.readFile(path44.join(markerDir, "github-app.json"), "utf8");
|
|
33262
33228
|
const creds = JSON.parse(credsRaw);
|
|
33263
33229
|
brainOrg = typeof creds.org === "string" ? creds.org : null;
|
|
33264
33230
|
} catch {
|
|
@@ -33371,7 +33337,7 @@ var BootstrapStatusCommand = class extends M8tCommand {
|
|
|
33371
33337
|
typeof this.output === "string" ? this.output : void 0,
|
|
33372
33338
|
this.context.stdout
|
|
33373
33339
|
);
|
|
33374
|
-
const
|
|
33340
|
+
const sleep4 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
33375
33341
|
const watch = this.watch === true;
|
|
33376
33342
|
const invokedPath = this.path ?? [];
|
|
33377
33343
|
const viaFinishAlias = invokedPath[invokedPath.length - 1] === "finish";
|
|
@@ -33404,7 +33370,7 @@ var BootstrapStatusCommand = class extends M8tCommand {
|
|
|
33404
33370
|
}
|
|
33405
33371
|
if (mode === "pretty") this.context.stderr.write(` ${colors.dim("waiting for the installer to start\u2026")}
|
|
33406
33372
|
`);
|
|
33407
|
-
await
|
|
33373
|
+
await sleep4(1e4);
|
|
33408
33374
|
continue;
|
|
33409
33375
|
}
|
|
33410
33376
|
throw e;
|
|
@@ -33440,7 +33406,7 @@ var BootstrapStatusCommand = class extends M8tCommand {
|
|
|
33440
33406
|
);
|
|
33441
33407
|
return 1;
|
|
33442
33408
|
}
|
|
33443
|
-
await
|
|
33409
|
+
await sleep4(1e4);
|
|
33444
33410
|
}
|
|
33445
33411
|
}
|
|
33446
33412
|
/**
|
|
@@ -33690,18 +33656,13 @@ Found ${String(total)} orphaned assignment(s) (${breakdown}) (dry-run). ${colors
|
|
|
33690
33656
|
};
|
|
33691
33657
|
|
|
33692
33658
|
// src/commands/bootstrap/ui.ts
|
|
33693
|
-
import * as fs40 from "fs";
|
|
33694
|
-
import * as os21 from "os";
|
|
33695
|
-
import * as path45 from "path";
|
|
33696
33659
|
import { Command as Command62, Option as Option59 } from "clipanion";
|
|
33697
|
-
import { DefaultAzureCredential as DefaultAzureCredential25 } from "@azure/identity";
|
|
33698
|
-
init_errors();
|
|
33699
33660
|
|
|
33700
33661
|
// src/lib/bootstrap-ui.ts
|
|
33701
|
-
import * as
|
|
33662
|
+
import * as fs40 from "fs";
|
|
33702
33663
|
import * as net from "net";
|
|
33703
|
-
import * as
|
|
33704
|
-
import * as
|
|
33664
|
+
import * as os21 from "os";
|
|
33665
|
+
import * as path45 from "path";
|
|
33705
33666
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
33706
33667
|
import { spawn as spawn8, spawnSync as spawnSync6 } from "child_process";
|
|
33707
33668
|
init_errors();
|
|
@@ -33709,475 +33670,32 @@ init_rbac();
|
|
|
33709
33670
|
|
|
33710
33671
|
// src/lib/intake-agent.ts
|
|
33711
33672
|
init_esm();
|
|
33712
|
-
var INTAKE_PERSONA = "ezra-intake";
|
|
33713
|
-
var INTAKE_AGENT = INTAKE_AGENT_NAME;
|
|
33714
|
-
var ADVISOR_TO_INTAKE_CODE = /* @__PURE__ */ new Map([
|
|
33715
|
-
["ADVISOR_PERSONA_MISSING", "INTAKE_PERSONA_MISSING"],
|
|
33716
|
-
["ADVISOR_PERSONA_EMPTY", "INTAKE_PERSONA_EMPTY"],
|
|
33717
|
-
["ADVISOR_NO_MODEL", "INTAKE_NO_MODEL"],
|
|
33718
|
-
["ADVISOR_BAD_EFFORT", "INTAKE_BAD_EFFORT"],
|
|
33719
|
-
["ADVISOR_BAD_TOOLS", "INTAKE_BAD_TOOLS"]
|
|
33720
|
-
]);
|
|
33721
|
-
async function deployIntakeAgent(args) {
|
|
33722
|
-
try {
|
|
33723
|
-
return await deployPromptAdvisor({
|
|
33724
|
-
credential: args.credential,
|
|
33725
|
-
endpoint: args.endpoint,
|
|
33726
|
-
repoRoot: args.repoRoot,
|
|
33727
|
-
persona: INTAKE_PERSONA,
|
|
33728
|
-
agentName: INTAKE_AGENT,
|
|
33729
|
-
model: args.model,
|
|
33730
|
-
fieldOverrides: args.fieldOverrides
|
|
33731
|
-
});
|
|
33732
|
-
} catch (e) {
|
|
33733
|
-
if (e && typeof e === "object" && "code" in e) {
|
|
33734
|
-
const err = e;
|
|
33735
|
-
const intakeCode = ADVISOR_TO_INTAKE_CODE.get(err.code);
|
|
33736
|
-
if (intakeCode) {
|
|
33737
|
-
const { LocalCliError: LocalCliError2 } = await Promise.resolve().then(() => (init_errors(), errors_exports));
|
|
33738
|
-
throw new LocalCliError2({
|
|
33739
|
-
code: intakeCode,
|
|
33740
|
-
message: err.message,
|
|
33741
|
-
hint: err.hint,
|
|
33742
|
-
cause: err.cause
|
|
33743
|
-
});
|
|
33744
|
-
}
|
|
33745
|
-
}
|
|
33746
|
-
throw e;
|
|
33747
|
-
}
|
|
33748
|
-
}
|
|
33749
33673
|
|
|
33750
33674
|
// src/lib/bootstrap-ui.ts
|
|
33751
|
-
function
|
|
33752
|
-
|
|
33753
|
-
}
|
|
33754
|
-
function isAuthorizationShapedError(error) {
|
|
33755
|
-
const seen = /* @__PURE__ */ new Set();
|
|
33756
|
-
let current = error;
|
|
33757
|
-
while (current != null && !seen.has(current)) {
|
|
33758
|
-
seen.add(current);
|
|
33759
|
-
if (typeof current === "object") {
|
|
33760
|
-
const record = current;
|
|
33761
|
-
if (record.statusCode === 403) return true;
|
|
33762
|
-
if (typeof record.message === "string" && /403|Forbidden|PermissionDenied|not authorized/i.test(record.message)) return true;
|
|
33763
|
-
current = record.cause;
|
|
33764
|
-
continue;
|
|
33765
|
-
}
|
|
33766
|
-
if (typeof current === "string" && /403|Forbidden|PermissionDenied|not authorized/i.test(current)) return true;
|
|
33767
|
-
break;
|
|
33768
|
-
}
|
|
33769
|
-
return false;
|
|
33770
|
-
}
|
|
33771
|
-
async function deployIntakeAgentWithRetry(args) {
|
|
33772
|
-
const maxWaitMs = args.maxWaitMs ?? 3e5;
|
|
33773
|
-
const intervalMs = args.intervalMs ?? 1e4;
|
|
33774
|
-
const deadline = Date.now() + maxWaitMs;
|
|
33775
|
-
for (; ; ) {
|
|
33776
|
-
try {
|
|
33777
|
-
return await deployIntakeAgent({
|
|
33778
|
-
credential: args.credential,
|
|
33779
|
-
endpoint: args.endpoint,
|
|
33780
|
-
repoRoot: args.repoRoot,
|
|
33781
|
-
model: args.model,
|
|
33782
|
-
fieldOverrides: args.fieldOverrides
|
|
33783
|
-
});
|
|
33784
|
-
} catch (error) {
|
|
33785
|
-
if (!isAuthorizationShapedError(error)) throw error;
|
|
33786
|
-
const remainingMs = deadline - Date.now();
|
|
33787
|
-
if (remainingMs <= 0) {
|
|
33788
|
-
throw new LocalCliError({
|
|
33789
|
-
code: "BOOTSTRAP_UI_ROLE_PROPAGATION_TIMEOUT",
|
|
33790
|
-
message: "Timed out waiting for Azure role propagation before deploying the intake agent.",
|
|
33791
|
-
hint: "Azure role propagation can take a few minutes - re-run 'm8t bootstrap ui' (idempotent).",
|
|
33792
|
-
cause: error
|
|
33793
|
-
});
|
|
33794
|
-
}
|
|
33795
|
-
args.onWait?.("waiting for Azure role propagation...");
|
|
33796
|
-
await sleep3(Math.min(intervalMs, remainingMs));
|
|
33797
|
-
}
|
|
33798
|
-
}
|
|
33799
|
-
}
|
|
33800
|
-
function renderNotReadyHint(resourceGroup, excluded) {
|
|
33801
|
-
const lines = ["Check 'm8t bootstrap status --watch' \u2014 the install may have stalled in foundry-create."];
|
|
33802
|
-
if (resourceGroup) {
|
|
33803
|
-
if (excluded.length > 0) {
|
|
33804
|
-
const count = excluded.length === 1 ? "1 project" : `${excluded.length.toString()} projects`;
|
|
33805
|
-
const verb = excluded.length === 1 ? "was" : "were";
|
|
33806
|
-
lines.push(
|
|
33807
|
-
`Discovery is scoped to this install's resource group (${resourceGroup}) on purpose, so it can`,
|
|
33808
|
-
`never deploy into someone else's project. ${count} elsewhere in this subscription ${verb}`,
|
|
33809
|
-
"skipped:",
|
|
33810
|
-
...excluded.map((c) => ` ${c.endpoint}`)
|
|
33811
|
-
);
|
|
33812
|
-
}
|
|
33813
|
-
lines.push(
|
|
33814
|
-
"If ~/.m8t/bootstrap.json names a different install than the one you meant, re-run with",
|
|
33815
|
-
"--endpoint <projectEndpoint>."
|
|
33816
|
-
);
|
|
33817
|
-
}
|
|
33818
|
-
return lines.join("\n");
|
|
33819
|
-
}
|
|
33820
|
-
async function resolveFoundryEndpointWithWait(args, opts = {}) {
|
|
33821
|
-
const pollMs = opts.pollMs ?? 1e4;
|
|
33822
|
-
const timeoutMs = opts.timeoutMs ?? 15 * 6e4;
|
|
33823
|
-
const deadline = Date.now() + timeoutMs;
|
|
33824
|
-
let excluded = [];
|
|
33825
|
-
for (; ; ) {
|
|
33826
|
-
try {
|
|
33827
|
-
excluded = [];
|
|
33828
|
-
const p = await resolveFoundryProject({
|
|
33829
|
-
credential: args.credential,
|
|
33830
|
-
subscriptionId: args.subscriptionId,
|
|
33831
|
-
interactive: false,
|
|
33832
|
-
// When the install's own resource group holds multiple projects, an
|
|
33833
|
-
// explicit endpoint disambiguates instead of FOUNDRY_PROJECT_MULTIPLE.
|
|
33834
|
-
// Omitted → single-project auto-discovery within that group.
|
|
33835
|
-
endpoint: args.endpoint,
|
|
33836
|
-
resourceGroup: args.resourceGroup,
|
|
33837
|
-
onScopedOut: (dropped) => {
|
|
33838
|
-
excluded = dropped;
|
|
33839
|
-
}
|
|
33840
|
-
});
|
|
33841
|
-
return { endpoint: p.endpoint, accountScope: p.accountScope };
|
|
33842
|
-
} catch (e) {
|
|
33843
|
-
if (e instanceof LocalCliError && e.code === "FOUNDRY_PROJECT_MULTIPLE") {
|
|
33844
|
-
throw new LocalCliError({
|
|
33845
|
-
code: "BOOTSTRAP_UI_MULTIPLE_PROJECTS",
|
|
33846
|
-
message: `${e.message} bootstrap ui can't pick one safely (it must not deploy the intake agent into the wrong project).`,
|
|
33847
|
-
hint: [
|
|
33848
|
-
"Re-run naming the project you want:",
|
|
33849
|
-
'm8t bootstrap ui --repo-root "$(pwd)" --endpoint <projectEndpoint>',
|
|
33850
|
-
"'m8t bootstrap status' prints this install's endpoint (result.foundryEndpoint)."
|
|
33851
|
-
].join("\n"),
|
|
33852
|
-
cause: e
|
|
33853
|
-
});
|
|
33854
|
-
}
|
|
33855
|
-
if (!(e instanceof LocalCliError && e.code === "FOUNDRY_PROJECT_ZERO")) {
|
|
33856
|
-
throw e;
|
|
33857
|
-
}
|
|
33858
|
-
if (Date.now() >= deadline) {
|
|
33859
|
-
throw new LocalCliError({
|
|
33860
|
-
code: "BOOTSTRAP_UI_FOUNDRY_NOT_READY",
|
|
33861
|
-
message: args.resourceGroup ? `Foundry is not ready yet \u2014 no AI project appeared in resource group ${args.resourceGroup} before the timeout.` : "Foundry is not ready yet \u2014 no AI project appeared in your subscription before the timeout.",
|
|
33862
|
-
hint: renderNotReadyHint(args.resourceGroup, excluded),
|
|
33863
|
-
cause: e
|
|
33864
|
-
});
|
|
33865
|
-
}
|
|
33866
|
-
opts.onWait?.("waiting for Foundry-create to finish\u2026");
|
|
33867
|
-
await sleep3(pollMs);
|
|
33868
|
-
}
|
|
33869
|
-
}
|
|
33870
|
-
}
|
|
33871
|
-
async function getSignedInUserOid() {
|
|
33872
|
-
const oid = (await runAz(["ad", "signed-in-user", "show", "--query", "id", "-o", "tsv"])).trim();
|
|
33873
|
-
if (!oid) {
|
|
33874
|
-
throw new LocalCliError({
|
|
33875
|
-
code: "BOOTSTRAP_UI_NO_SIGNED_IN_USER",
|
|
33876
|
-
message: "Could not resolve the signed-in user (az ad signed-in-user show returned nothing).",
|
|
33877
|
-
hint: "Personal Microsoft accounts can't be resolved this way \u2014 sign in with a work/school account."
|
|
33878
|
-
});
|
|
33879
|
-
}
|
|
33880
|
-
return oid;
|
|
33881
|
-
}
|
|
33882
|
-
async function ensureFounderFoundryRole(args) {
|
|
33883
|
-
await grantFoundryUser({
|
|
33884
|
-
credential: args.credential,
|
|
33885
|
-
subscriptionId: args.subscriptionId,
|
|
33886
|
-
accountScope: args.accountScope,
|
|
33887
|
-
principalId: args.principalId,
|
|
33888
|
-
principalType: "User"
|
|
33889
|
-
});
|
|
33890
|
-
}
|
|
33891
|
-
function writeWebEnvLocal(args) {
|
|
33892
|
-
const envPath = path44.join(args.repoRoot, "apps", "web", ".env.local");
|
|
33893
|
-
let prior = "";
|
|
33894
|
-
if (fs39.existsSync(envPath)) {
|
|
33895
|
-
prior = fs39.readFileSync(envPath, "utf8");
|
|
33896
|
-
fs39.copyFileSync(envPath, envPath + ".bak");
|
|
33897
|
-
}
|
|
33898
|
-
const priorSecret = readValidInternalSecret(prior);
|
|
33899
|
-
const internalSecret = priorSecret ?? (args.randomBytesImpl ?? randomBytes3)(32).toString("base64url");
|
|
33900
|
-
if (!isValidInternalSecret(internalSecret)) {
|
|
33901
|
-
throw new LocalCliError({
|
|
33902
|
-
code: "BOOTSTRAP_UI_INTERNAL_SECRET_GENERATION_FAILED",
|
|
33903
|
-
message: "Could not generate a valid local internal service secret."
|
|
33904
|
-
});
|
|
33905
|
-
}
|
|
33906
|
-
const body = [
|
|
33907
|
-
"# Written by `m8t bootstrap ui` \u2014 local onboarding run (the onboarding intake agent).",
|
|
33908
|
-
"# Text-first: the intake voice stack is off unless the run passed --voice.",
|
|
33909
|
-
"# STORAGE unset \u2192 gateway boot skipped; the webapp forwards your MSAL token to Foundry.",
|
|
33910
|
-
`AZURE_TENANT_ID=${args.tenantId}`,
|
|
33911
|
-
`AZURE_CLIENT_ID=${args.clientId}`,
|
|
33912
|
-
`FOUNDRY_PROJECT_ENDPOINT=${args.foundryEndpoint}`,
|
|
33913
|
-
...args.voice ? ["VOICE_RELAY_PUBLIC_URL=ws://localhost:8790", "NEXT_PUBLIC_M8T_INTAKE_VOICE=1"] : [],
|
|
33914
|
-
"STORAGE_ACCOUNT_NAME=",
|
|
33915
|
-
`M8T_INTERNAL_SECRET=${internalSecret}`,
|
|
33916
|
-
"M8T_SINGLE_GATEWAY_PROCESS=1",
|
|
33917
|
-
"M8T_DISABLE_WORKER=1",
|
|
33918
|
-
"M8T_ONBOARDING=1",
|
|
33919
|
-
"NEXT_PUBLIC_M8T_ONBOARDING=1",
|
|
33920
|
-
""
|
|
33921
|
-
].join("\n");
|
|
33922
|
-
fs39.writeFileSync(envPath, body, { encoding: "utf8", mode: 384 });
|
|
33923
|
-
try {
|
|
33924
|
-
fs39.chmodSync(envPath, 384);
|
|
33925
|
-
} catch {
|
|
33926
|
-
}
|
|
33927
|
-
return envPath;
|
|
33928
|
-
}
|
|
33929
|
-
function isValidInternalSecret(value) {
|
|
33930
|
-
if (!value || !/^[A-Za-z0-9_-]{43}$/.test(value)) return false;
|
|
33931
|
-
const decoded = Buffer.from(value, "base64url");
|
|
33932
|
-
return decoded.length === 32 && decoded.toString("base64url") === value;
|
|
33933
|
-
}
|
|
33934
|
-
function readValidInternalSecret(envText) {
|
|
33935
|
-
const values = envText.split(/\r?\n/).filter((line2) => line2.startsWith("M8T_INTERNAL_SECRET=")).map((line2) => line2.slice("M8T_INTERNAL_SECRET=".length));
|
|
33936
|
-
if (values.length !== 1) return void 0;
|
|
33937
|
-
return isValidInternalSecret(values[0]) ? values[0] : void 0;
|
|
33938
|
-
}
|
|
33939
|
-
function readWebInternalSecret(repoRoot) {
|
|
33940
|
-
const envPath = path44.join(repoRoot, "apps", "web", ".env.local");
|
|
33941
|
-
let secret;
|
|
33942
|
-
try {
|
|
33943
|
-
secret = readValidInternalSecret(fs39.readFileSync(envPath, "utf8"));
|
|
33944
|
-
} catch {
|
|
33945
|
-
secret = void 0;
|
|
33946
|
-
}
|
|
33947
|
-
if (!secret) {
|
|
33948
|
-
throw new LocalCliError({
|
|
33949
|
-
code: "BOOTSTRAP_UI_INTERNAL_SECRET_MISSING",
|
|
33950
|
-
message: "The local web environment has no valid internal service secret.",
|
|
33951
|
-
hint: "Re-run 'm8t bootstrap ui' so the web app and voice relay receive the same secret."
|
|
33952
|
-
});
|
|
33953
|
-
}
|
|
33954
|
-
return secret;
|
|
33955
|
-
}
|
|
33956
|
-
function assertNodeVersion(versionString = process.version) {
|
|
33957
|
-
const major = Number(/^v?(\d+)\./.exec(versionString)?.[1] ?? "0");
|
|
33958
|
-
if (major < 20) {
|
|
33959
|
-
throw new LocalCliError({
|
|
33960
|
-
code: "BOOTSTRAP_UI_NODE_TOO_OLD",
|
|
33961
|
-
message: `apps/web needs Node 20+, but this is ${versionString}.`,
|
|
33962
|
-
hint: "Install Node 20+ (see install/prereqs-*.md), then re-run 'm8t bootstrap ui'."
|
|
33963
|
-
});
|
|
33964
|
-
}
|
|
33965
|
-
}
|
|
33966
|
-
function runInherit(cmd, cmdArgs, cwd, failCode) {
|
|
33967
|
-
return new Promise((resolve6, reject) => {
|
|
33968
|
-
const child = spawn8(cmd, cmdArgs, { cwd, stdio: "inherit", shell: process.platform === "win32" });
|
|
33969
|
-
child.on("error", (e) => {
|
|
33970
|
-
reject(new LocalCliError({ code: failCode, message: `Failed to start '${cmd}': ${e.message}` }));
|
|
33971
|
-
});
|
|
33972
|
-
child.on("close", (code) => {
|
|
33973
|
-
if (code === 0) {
|
|
33974
|
-
resolve6();
|
|
33975
|
-
} else {
|
|
33976
|
-
const opts = {
|
|
33977
|
-
code: failCode,
|
|
33978
|
-
message: `'${cmd} ${cmdArgs.join(" ")}' exited with code ${String(code)}.`
|
|
33979
|
-
};
|
|
33980
|
-
if (cmd === "pnpm") {
|
|
33981
|
-
opts.hint = "Fix the pnpm error above, then re-run 'm8t bootstrap ui' (it's idempotent).";
|
|
33982
|
-
}
|
|
33983
|
-
reject(new LocalCliError(opts));
|
|
33984
|
-
}
|
|
33985
|
-
});
|
|
33986
|
-
});
|
|
33987
|
-
}
|
|
33988
|
-
async function installWebDeps(repoRoot) {
|
|
33989
|
-
assertNodeVersion();
|
|
33990
|
-
await runInherit("corepack", ["enable"], repoRoot, "BOOTSTRAP_UI_COREPACK_FAILED").catch(() => {
|
|
33991
|
-
});
|
|
33992
|
-
await runInherit("pnpm", ["install"], repoRoot, "BOOTSTRAP_UI_INSTALL_FAILED");
|
|
33993
|
-
}
|
|
33994
|
-
function onboardingUiPaths(home = os20.homedir()) {
|
|
33995
|
-
const dir = path44.join(home, ".m8t");
|
|
33675
|
+
function onboardingUiPaths(home = os21.homedir()) {
|
|
33676
|
+
const dir2 = path45.join(home, ".m8t");
|
|
33996
33677
|
return {
|
|
33997
|
-
logPath:
|
|
33998
|
-
pidPath:
|
|
33678
|
+
logPath: path45.join(dir2, "onboarding-ui.log"),
|
|
33679
|
+
pidPath: path45.join(dir2, "onboarding-ui.pid")
|
|
33999
33680
|
};
|
|
34000
33681
|
}
|
|
34001
|
-
function onboardingRelayPaths(home =
|
|
34002
|
-
const
|
|
33682
|
+
function onboardingRelayPaths(home = os21.homedir()) {
|
|
33683
|
+
const dir2 = path45.join(home, ".m8t");
|
|
34003
33684
|
return {
|
|
34004
|
-
logPath:
|
|
34005
|
-
pidPath:
|
|
33685
|
+
logPath: path45.join(dir2, "onboarding-relay.log"),
|
|
33686
|
+
pidPath: path45.join(dir2, "onboarding-relay.pid")
|
|
34006
33687
|
};
|
|
34007
33688
|
}
|
|
34008
|
-
function isLocalPortOpen(port) {
|
|
34009
|
-
return new Promise((resolve6) => {
|
|
34010
|
-
const s = net.createConnection({ host: "127.0.0.1", port });
|
|
34011
|
-
s.setTimeout(1e3);
|
|
34012
|
-
s.on("connect", () => {
|
|
34013
|
-
s.destroy();
|
|
34014
|
-
resolve6(true);
|
|
34015
|
-
});
|
|
34016
|
-
s.on("error", () => {
|
|
34017
|
-
resolve6(false);
|
|
34018
|
-
});
|
|
34019
|
-
s.on("timeout", () => {
|
|
34020
|
-
s.destroy();
|
|
34021
|
-
resolve6(false);
|
|
34022
|
-
});
|
|
34023
|
-
});
|
|
34024
|
-
}
|
|
34025
|
-
function removePidFile(pidPath) {
|
|
34026
|
-
try {
|
|
34027
|
-
fs39.unlinkSync(pidPath);
|
|
34028
|
-
} catch {
|
|
34029
|
-
}
|
|
34030
|
-
}
|
|
34031
33689
|
function parseManagedPid(text) {
|
|
34032
33690
|
if (!/^[1-9]\d*$/.test(text)) return null;
|
|
34033
33691
|
const pid = Number(text);
|
|
34034
33692
|
if (!Number.isSafeInteger(pid) || pid < 2 || pid === process.pid || pid === process.ppid) return null;
|
|
34035
33693
|
return pid;
|
|
34036
33694
|
}
|
|
34037
|
-
function readLiveManagedPid(pidPath) {
|
|
34038
|
-
let text;
|
|
34039
|
-
try {
|
|
34040
|
-
text = fs39.readFileSync(pidPath, "utf8").trim();
|
|
34041
|
-
} catch {
|
|
34042
|
-
return null;
|
|
34043
|
-
}
|
|
34044
|
-
const pid = parseManagedPid(text);
|
|
34045
|
-
if (pid == null) {
|
|
34046
|
-
removePidFile(pidPath);
|
|
34047
|
-
return null;
|
|
34048
|
-
}
|
|
34049
|
-
try {
|
|
34050
|
-
process.kill(pid, 0);
|
|
34051
|
-
return pid;
|
|
34052
|
-
} catch (error) {
|
|
34053
|
-
const code = error.code;
|
|
34054
|
-
if (code === "ESRCH") {
|
|
34055
|
-
removePidFile(pidPath);
|
|
34056
|
-
return null;
|
|
34057
|
-
}
|
|
34058
|
-
if (code === "EPERM") return pid;
|
|
34059
|
-
throw error;
|
|
34060
|
-
}
|
|
34061
|
-
}
|
|
34062
|
-
async function serveOnboardingUiDetached(args) {
|
|
34063
|
-
const { logPath, pidPath } = onboardingUiPaths();
|
|
34064
|
-
const portNum = Number(args.port);
|
|
34065
|
-
const isPortOpen = () => isLocalPortOpen(portNum);
|
|
34066
|
-
const portOpen = await isPortOpen();
|
|
34067
|
-
const managedPid = readLiveManagedPid(pidPath);
|
|
34068
|
-
if (portOpen && managedPid != null) {
|
|
34069
|
-
return { alreadyRunning: true, logPath };
|
|
34070
|
-
}
|
|
34071
|
-
if (portOpen) {
|
|
34072
|
-
throw new LocalCliError({
|
|
34073
|
-
code: "BOOTSTRAP_UI_PORT_IN_USE",
|
|
34074
|
-
message: `Port ${args.port} is occupied by a process not started by m8t bootstrap.`,
|
|
34075
|
-
hint: `Stop the process using port ${args.port}, then re-run 'm8t bootstrap ui'.`
|
|
34076
|
-
});
|
|
34077
|
-
}
|
|
34078
|
-
if (managedPid != null) return { alreadyRunning: true, logPath };
|
|
34079
|
-
fs39.mkdirSync(path44.dirname(logPath), { recursive: true });
|
|
34080
|
-
const fd = fs39.openSync(logPath, "a");
|
|
34081
|
-
const child = spawn8("pnpm", ["--filter", "web", "dev"], {
|
|
34082
|
-
cwd: args.repoRoot,
|
|
34083
|
-
detached: true,
|
|
34084
|
-
stdio: ["ignore", fd, fd],
|
|
34085
|
-
env: { ...process.env, PORT: args.port, M8T_SINGLE_GATEWAY_PROCESS: "1" },
|
|
34086
|
-
// pnpm resolves to pnpm.cmd on Windows, which needs a shell to launch.
|
|
34087
|
-
shell: process.platform === "win32"
|
|
34088
|
-
});
|
|
34089
|
-
fs39.closeSync(fd);
|
|
34090
|
-
if (child.pid == null) {
|
|
34091
|
-
throw new LocalCliError({
|
|
34092
|
-
code: "BOOTSTRAP_UI_DEV_SPAWN_FAILED",
|
|
34093
|
-
message: "Could not start the web dev server (pnpm did not spawn).",
|
|
34094
|
-
hint: "Check that pnpm + Node 20+ are installed; see the log at " + logPath + "."
|
|
34095
|
-
});
|
|
34096
|
-
}
|
|
34097
|
-
child.unref();
|
|
34098
|
-
fs39.writeFileSync(pidPath, String(child.pid), "utf8");
|
|
34099
|
-
const deadline = Date.now() + 45e3;
|
|
34100
|
-
const sleep5 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
34101
|
-
while (Date.now() < deadline) {
|
|
34102
|
-
await sleep5(500);
|
|
34103
|
-
if (await isPortOpen()) break;
|
|
34104
|
-
}
|
|
34105
|
-
return { alreadyRunning: false, logPath };
|
|
34106
|
-
}
|
|
34107
|
-
async function serveOnboardingRelayDetached(args) {
|
|
34108
|
-
const { logPath, pidPath } = onboardingRelayPaths();
|
|
34109
|
-
const portNum = 8790;
|
|
34110
|
-
if (args.webInternalBaseUrl !== "http://localhost:3000") {
|
|
34111
|
-
throw new LocalCliError({
|
|
34112
|
-
code: "BOOTSTRAP_UI_INTERNAL_BASE_URL_INVALID",
|
|
34113
|
-
message: "The local voice relay internal base URL must be http://localhost:3000."
|
|
34114
|
-
});
|
|
34115
|
-
}
|
|
34116
|
-
const internalSecret = readWebInternalSecret(args.repoRoot);
|
|
34117
|
-
const portOpen = await isLocalPortOpen(portNum);
|
|
34118
|
-
const managedPid = readLiveManagedPid(pidPath);
|
|
34119
|
-
if (portOpen && managedPid != null) {
|
|
34120
|
-
return { alreadyRunning: true, logPath };
|
|
34121
|
-
}
|
|
34122
|
-
if (portOpen) {
|
|
34123
|
-
throw new LocalCliError({
|
|
34124
|
-
code: "BOOTSTRAP_UI_RELAY_PORT_IN_USE",
|
|
34125
|
-
message: "Port 8790 is occupied by a process not started by m8t bootstrap.",
|
|
34126
|
-
hint: "Stop the process using port 8790, then re-run 'm8t bootstrap ui'."
|
|
34127
|
-
});
|
|
34128
|
-
}
|
|
34129
|
-
if (managedPid != null) return { alreadyRunning: true, logPath };
|
|
34130
|
-
fs39.mkdirSync(path44.dirname(logPath), { recursive: true });
|
|
34131
|
-
const fd = fs39.openSync(logPath, "a");
|
|
34132
|
-
const child = spawn8("pnpm", ["--filter", "web", "exec", "tsx", "voice-relay-entry.ts"], {
|
|
34133
|
-
cwd: args.repoRoot,
|
|
34134
|
-
detached: true,
|
|
34135
|
-
stdio: ["ignore", fd, fd],
|
|
34136
|
-
env: {
|
|
34137
|
-
...process.env,
|
|
34138
|
-
FOUNDRY_PROJECT_ENDPOINT: args.foundryEndpoint,
|
|
34139
|
-
AZURE_TENANT_ID: args.tenantId,
|
|
34140
|
-
AZURE_CLIENT_ID: args.clientId,
|
|
34141
|
-
RELAY_ALLOWED_ORIGINS: "http://localhost:3000",
|
|
34142
|
-
RELAY_PORT: "8790",
|
|
34143
|
-
M8T_INTERNAL_SECRET: internalSecret,
|
|
34144
|
-
WEB_INTERNAL_BASE_URL: args.webInternalBaseUrl
|
|
34145
|
-
},
|
|
34146
|
-
// pnpm resolves to pnpm.cmd on Windows, which needs a shell to launch.
|
|
34147
|
-
shell: process.platform === "win32"
|
|
34148
|
-
});
|
|
34149
|
-
fs39.closeSync(fd);
|
|
34150
|
-
if (child.pid == null) {
|
|
34151
|
-
throw new LocalCliError({
|
|
34152
|
-
code: "BOOTSTRAP_UI_RELAY_SPAWN_FAILED",
|
|
34153
|
-
message: "Could not start the voice relay (pnpm did not spawn).",
|
|
34154
|
-
hint: "Check that pnpm + Node 20+ are installed; see the log at " + logPath + "."
|
|
34155
|
-
});
|
|
34156
|
-
}
|
|
34157
|
-
child.unref();
|
|
34158
|
-
fs39.writeFileSync(pidPath, String(child.pid), "utf8");
|
|
34159
|
-
const deadline = Date.now() + 45e3;
|
|
34160
|
-
const sleep5 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
34161
|
-
while (Date.now() < deadline) {
|
|
34162
|
-
await sleep5(500);
|
|
34163
|
-
if (await isLocalPortOpen(portNum)) break;
|
|
34164
|
-
}
|
|
34165
|
-
return { alreadyRunning: false, logPath };
|
|
34166
|
-
}
|
|
34167
|
-
function autoOpenOnboardingUi(url, open3 = tryOpenUrl) {
|
|
34168
|
-
try {
|
|
34169
|
-
const result2 = open3(url);
|
|
34170
|
-
if (result2 instanceof Promise) {
|
|
34171
|
-
result2.catch(() => {
|
|
34172
|
-
});
|
|
34173
|
-
}
|
|
34174
|
-
} catch {
|
|
34175
|
-
}
|
|
34176
|
-
}
|
|
34177
33695
|
function stopPidFile(pidPath) {
|
|
34178
33696
|
let pidStr;
|
|
34179
33697
|
try {
|
|
34180
|
-
pidStr =
|
|
33698
|
+
pidStr = fs40.readFileSync(pidPath, "utf8").trim();
|
|
34181
33699
|
} catch {
|
|
34182
33700
|
return false;
|
|
34183
33701
|
}
|
|
@@ -34194,551 +33712,386 @@ function stopPidFile(pidPath) {
|
|
|
34194
33712
|
}
|
|
34195
33713
|
}
|
|
34196
33714
|
try {
|
|
34197
|
-
|
|
33715
|
+
fs40.unlinkSync(pidPath);
|
|
34198
33716
|
} catch {
|
|
34199
33717
|
}
|
|
34200
33718
|
return true;
|
|
34201
33719
|
}
|
|
34202
|
-
function stopOnboardingUi(home =
|
|
33720
|
+
function stopOnboardingUi(home = os21.homedir()) {
|
|
34203
33721
|
const uiStopped = stopPidFile(onboardingUiPaths(home).pidPath);
|
|
34204
33722
|
const relayStopped = stopPidFile(onboardingRelayPaths(home).pidPath);
|
|
34205
33723
|
return uiStopped || relayStopped;
|
|
34206
33724
|
}
|
|
34207
|
-
function buildIntakeFieldOverrides(args) {
|
|
34208
|
-
return {
|
|
34209
|
-
founder_identity_note: args.founderIdentityNote,
|
|
34210
|
-
chosen_model_note: args.chosenModelNote,
|
|
34211
|
-
...args.inventoryNote ? { subscription_inventory_note: args.inventoryNote } : {}
|
|
34212
|
-
};
|
|
34213
|
-
}
|
|
34214
33725
|
|
|
34215
|
-
// src/
|
|
34216
|
-
|
|
34217
|
-
|
|
34218
|
-
|
|
34219
|
-
|
|
34220
|
-
} catch (e) {
|
|
34221
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
34222
|
-
const denied = /AuthorizationFailed|Forbidden|\b403\b|does not have authorization/i.test(msg);
|
|
34223
|
-
return { ok: false, error: denied ? "denied" : "unavailable" };
|
|
34224
|
-
}
|
|
34225
|
-
let raw;
|
|
34226
|
-
try {
|
|
34227
|
-
raw = JSON.parse(out);
|
|
34228
|
-
} catch {
|
|
34229
|
-
return { ok: false, error: "malformed" };
|
|
34230
|
-
}
|
|
34231
|
-
if (!Array.isArray(raw) || raw.some((e) => typeof e !== "object" || e === null)) {
|
|
34232
|
-
return { ok: false, error: "malformed" };
|
|
34233
|
-
}
|
|
34234
|
-
try {
|
|
34235
|
-
const rows = raw.map((e) => ({
|
|
34236
|
-
format: e.model?.format ?? "",
|
|
34237
|
-
name: e.model?.name ?? "",
|
|
34238
|
-
version: e.model?.version ?? "",
|
|
34239
|
-
skus: [...new Set((e.model?.skus ?? []).map((s) => s.name ?? "").filter(Boolean))],
|
|
34240
|
-
agentsV2: e.model?.capabilities?.agentsV2 === "true"
|
|
34241
|
-
}));
|
|
34242
|
-
return { ok: true, rows };
|
|
34243
|
-
} catch {
|
|
34244
|
-
return { ok: false, error: "malformed" };
|
|
34245
|
-
}
|
|
33726
|
+
// src/commands/bootstrap/ui.ts
|
|
33727
|
+
function renderDeprecationNotice() {
|
|
33728
|
+
return `${colors.error("\u26A0")} 'm8t bootstrap ui' is deprecated and does nothing \u2014 the local onboarding chat is gone for good.
|
|
33729
|
+
Your details + chat while you wait: ${colors.field("m8t bootstrap profile")} \xB7 Your own Ezra, once installed: ${colors.field("m8t open")}
|
|
33730
|
+
`;
|
|
34246
33731
|
}
|
|
33732
|
+
var BootstrapUiCommand = class extends M8tCommand {
|
|
33733
|
+
static paths = [["bootstrap", "ui"]];
|
|
33734
|
+
static usage = Command62.Usage({
|
|
33735
|
+
description: "DEPRECATED \u2014 does nothing. Use `m8t bootstrap profile` instead.",
|
|
33736
|
+
details: [
|
|
33737
|
+
"The local onboarding chat has been retired. Your details are collected by",
|
|
33738
|
+
"`m8t bootstrap profile`, which also opens the hosted Ezra to talk to while the",
|
|
33739
|
+
"install runs; `m8t open` reaches your own Ezra once the install finishes.",
|
|
33740
|
+
"",
|
|
33741
|
+
"This command deploys nothing, serves nothing, and changes nothing. Every flag",
|
|
33742
|
+
"below is accepted and ignored, so an older runbook or script still exits cleanly.",
|
|
33743
|
+
"`--stop` is the one exception: it still shuts down a chat server left running by",
|
|
33744
|
+
"an earlier version of this command."
|
|
33745
|
+
].join("\n"),
|
|
33746
|
+
examples: [
|
|
33747
|
+
["Collect your details and open Ezra instead", "$0 bootstrap profile"],
|
|
33748
|
+
["Stop a chat UI an older CLI left running", "$0 bootstrap ui --stop"]
|
|
33749
|
+
]
|
|
33750
|
+
});
|
|
33751
|
+
// Accepted and ignored, deliberately: removing them would turn an old script's
|
|
33752
|
+
// harmless no-op into an "unknown option" failure mid-install. CLI-rewrite: drop
|
|
33753
|
+
// the whole surface when the rewrite lands.
|
|
33754
|
+
repoRoot = Option59.String("--repo-root", { description: "Ignored (deprecated)." });
|
|
33755
|
+
port = Option59.String("--port", "3000", { description: "Ignored (deprecated)." });
|
|
33756
|
+
endpoint = Option59.String("--endpoint", { description: "Ignored (deprecated)." });
|
|
33757
|
+
prepOnly = Option59.Boolean("--prep-only", false, { description: "Ignored (deprecated)." });
|
|
33758
|
+
skipInstall = Option59.Boolean("--skip-install", false, { description: "Ignored (deprecated)." });
|
|
33759
|
+
foreground = Option59.Boolean("--foreground", false, { description: "Ignored (deprecated)." });
|
|
33760
|
+
voice = Option59.Boolean("--voice", false, { description: "Ignored (deprecated)." });
|
|
33761
|
+
stop = Option59.Boolean("--stop", false, {
|
|
33762
|
+
description: "Shut down a local chat UI left running by an earlier version of this command."
|
|
33763
|
+
});
|
|
33764
|
+
// Not `async`: there is nothing left to await. Everything this command used to
|
|
33765
|
+
// wait on — Foundry, the role grant, the agent deploy, pnpm — is gone.
|
|
33766
|
+
executeCommand() {
|
|
33767
|
+
if (this.stop === true) {
|
|
33768
|
+
const stopped = stopOnboardingUi();
|
|
33769
|
+
this.context.stdout.write(
|
|
33770
|
+
stopped ? `${colors.success("\u2713")} stopped the onboarding chat UI.
|
|
33771
|
+
` : ` ${colors.dim("nothing to stop (no onboarding-ui.pid found).")}
|
|
33772
|
+
`
|
|
33773
|
+
);
|
|
33774
|
+
return Promise.resolve(0);
|
|
33775
|
+
}
|
|
33776
|
+
this.context.stdout.write(renderDeprecationNotice());
|
|
33777
|
+
return Promise.resolve(0);
|
|
33778
|
+
}
|
|
33779
|
+
};
|
|
34247
33780
|
|
|
34248
|
-
// src/
|
|
34249
|
-
|
|
34250
|
-
|
|
34251
|
-
|
|
34252
|
-
|
|
34253
|
-
|
|
33781
|
+
// src/commands/bootstrap/profile.ts
|
|
33782
|
+
import * as readline3 from "readline/promises";
|
|
33783
|
+
import { Command as Command63, Option as Option60 } from "clipanion";
|
|
33784
|
+
|
|
33785
|
+
// src/lib/profile-collect.ts
|
|
33786
|
+
init_errors();
|
|
33787
|
+
function looksLikeEmail(value) {
|
|
33788
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.trim());
|
|
34254
33789
|
}
|
|
34255
|
-
function
|
|
34256
|
-
|
|
34257
|
-
return `${
|
|
33790
|
+
function renderEmailQuestion(prefill) {
|
|
33791
|
+
const base = "Your email (Ezra sends your copies of its outbound mail here)";
|
|
33792
|
+
return prefill ? `${base} [${prefill}]: ` : `${base}: `;
|
|
34258
33793
|
}
|
|
34259
|
-
|
|
34260
|
-
|
|
34261
|
-
|
|
34262
|
-
"cognitiveservices",
|
|
34263
|
-
"account",
|
|
34264
|
-
"show",
|
|
34265
|
-
"--name",
|
|
34266
|
-
account,
|
|
34267
|
-
"--resource-group",
|
|
34268
|
-
resourceGroup,
|
|
34269
|
-
"--query",
|
|
34270
|
-
"location",
|
|
34271
|
-
"-o",
|
|
34272
|
-
"tsv"
|
|
34273
|
-
]);
|
|
34274
|
-
return out.trim() || fallback;
|
|
34275
|
-
} catch {
|
|
34276
|
-
return fallback;
|
|
34277
|
-
}
|
|
33794
|
+
var ADVISOR_LEAD = "Your Microsoft startup advisor's name + email (Ezra emails them\nonly when you ask it to \u2014 e.g. quota requests). It's OK if you don't\nknow it yet \u2014 skip for now.\n";
|
|
33795
|
+
function renderAdvisorQuestion(label, prior) {
|
|
33796
|
+
return prior ? ` ${label} [${prior}] (Enter to keep): ` : ` ${label} (Enter to skip): `;
|
|
34278
33797
|
}
|
|
34279
|
-
|
|
34280
|
-
|
|
34281
|
-
|
|
34282
|
-
|
|
34283
|
-
|
|
34284
|
-
|
|
34285
|
-
|
|
34286
|
-
|
|
34287
|
-
|
|
34288
|
-
|
|
34289
|
-
|
|
34290
|
-
|
|
34291
|
-
|
|
34292
|
-
|
|
34293
|
-
|
|
34294
|
-
|
|
34295
|
-
|
|
34296
|
-
|
|
34297
|
-
|
|
34298
|
-
capacity: rung.capacity,
|
|
34299
|
-
format: rung.format
|
|
34300
|
-
});
|
|
34301
|
-
};
|
|
34302
|
-
const start = Date.now();
|
|
34303
|
-
const result2 = await walkCascade(WHITELIST, plan, deploy, {
|
|
34304
|
-
now: () => Date.now(),
|
|
34305
|
-
deadlineAt: start + (args.deadlineMs ?? DEFAULT_DEADLINE_MS),
|
|
34306
|
-
onRung: (model, outcome) => args.onNarrate?.(narrate(model, outcome, region))
|
|
34307
|
-
});
|
|
33798
|
+
var ADVISOR_SKIPPED_LINE = " No advisor recorded \u2014 tell Ezra any time and it'll add them.\n";
|
|
33799
|
+
function refuseFounderEmail() {
|
|
33800
|
+
return new LocalCliError({
|
|
33801
|
+
code: "PROFILE_FOUNDER_EMAIL_REQUIRED",
|
|
33802
|
+
message: "I need the founder's email address.\n Pass --founder-email <you@example.com> \u2014 Ezra sends their copies of its\n outbound mail there. Confirm it with them; don't guess from the Azure sign-in.",
|
|
33803
|
+
hint: "Ask the founder to confirm the address, then re-run 'm8t bootstrap profile --founder-email <addr>'."
|
|
33804
|
+
});
|
|
33805
|
+
}
|
|
33806
|
+
function refuseMalformed(flag, value) {
|
|
33807
|
+
return new LocalCliError({
|
|
33808
|
+
code: "PROFILE_EMAIL_MALFORMED",
|
|
33809
|
+
message: `'${value}' doesn't look like an email address. Pass ${flag} <you@example.com>.`
|
|
33810
|
+
});
|
|
33811
|
+
}
|
|
33812
|
+
async function collectProfile(args) {
|
|
33813
|
+
const { flags, identity, isTty, ask } = args;
|
|
33814
|
+
const previous = args.previous ?? null;
|
|
33815
|
+
const hasAdvisorFlag = (flags.advisorName ?? "").trim() !== "" || (flags.advisorEmail ?? "").trim() !== "";
|
|
33816
|
+
if (flags.noAdvisor && hasAdvisorFlag) {
|
|
34308
33817
|
return {
|
|
34309
|
-
|
|
34310
|
-
|
|
34311
|
-
|
|
33818
|
+
ok: false,
|
|
33819
|
+
error: new LocalCliError({
|
|
33820
|
+
code: "PROFILE_ADVISOR_FLAGS_CONFLICT",
|
|
33821
|
+
message: "--no-advisor cannot be combined with --advisor-name / --advisor-email.",
|
|
33822
|
+
hint: "Drop --no-advisor to record the advisor, or drop the advisor flags to skip."
|
|
33823
|
+
})
|
|
34312
33824
|
};
|
|
34313
|
-
}
|
|
34314
|
-
|
|
34315
|
-
|
|
34316
|
-
|
|
34317
|
-
|
|
34318
|
-
|
|
34319
|
-
|
|
34320
|
-
|
|
34321
|
-
|
|
34322
|
-
|
|
34323
|
-
|
|
34324
|
-
|
|
34325
|
-
|
|
34326
|
-
"microsoft.compute/virtualmachinescalesets": "virtual machine scale sets",
|
|
34327
|
-
"microsoft.app/containerapps": "container apps",
|
|
34328
|
-
"microsoft.containerservice/managedclusters": "Kubernetes clusters",
|
|
34329
|
-
"microsoft.web/sites": "web apps",
|
|
34330
|
-
// data (shared noun)
|
|
34331
|
-
"microsoft.sql/servers": "databases",
|
|
34332
|
-
"microsoft.dbforpostgresql/servers": "databases",
|
|
34333
|
-
"microsoft.dbforpostgresql/flexibleservers": "databases",
|
|
34334
|
-
"microsoft.dbformysql/servers": "databases",
|
|
34335
|
-
"microsoft.dbformysql/flexibleservers": "databases",
|
|
34336
|
-
"microsoft.documentdb/databaseaccounts": "databases",
|
|
34337
|
-
"microsoft.cache/redis": "databases",
|
|
34338
|
-
// AI (shared noun)
|
|
34339
|
-
"microsoft.cognitiveservices/accounts": "AI services",
|
|
34340
|
-
"microsoft.machinelearningservices/workspaces": "AI services",
|
|
34341
|
-
// key vaults
|
|
34342
|
-
"microsoft.keyvault/vaults": "key vaults",
|
|
34343
|
-
// container registries
|
|
34344
|
-
"microsoft.containerregistry/registries": "container registries",
|
|
34345
|
-
// messaging (shared noun)
|
|
34346
|
-
"microsoft.servicebus/namespaces": "messaging namespaces",
|
|
34347
|
-
"microsoft.eventhub/namespaces": "messaging namespaces",
|
|
34348
|
-
// networking (shared noun); public DNS zones only — private zones are plumbing
|
|
34349
|
-
"microsoft.network/virtualnetworks": "networking resources",
|
|
34350
|
-
"microsoft.network/loadbalancers": "networking resources",
|
|
34351
|
-
"microsoft.network/applicationgateways": "networking resources",
|
|
34352
|
-
"microsoft.network/dnszones": "networking resources"
|
|
34353
|
-
};
|
|
34354
|
-
var MS_PER_DAY = 864e5;
|
|
34355
|
-
var REGION_MAJORITY = 0.6;
|
|
34356
|
-
var AGE_BUCKETS = [
|
|
34357
|
-
{ maxDays: 31, phrase: "all created within the last month" },
|
|
34358
|
-
{ maxDays: 93, phrase: "the oldest created a couple of months ago" },
|
|
34359
|
-
{ maxDays: 186, phrase: "the oldest created several months ago" },
|
|
34360
|
-
{ maxDays: 365, phrase: "the oldest created about a year ago" },
|
|
34361
|
-
{ maxDays: 730, phrase: "the oldest created over a year ago" }
|
|
34362
|
-
];
|
|
34363
|
-
var OLDEST_FALLBACK = "the oldest created a couple of years ago";
|
|
34364
|
-
function ageBucket(days) {
|
|
34365
|
-
for (const b of AGE_BUCKETS) if (days <= b.maxDays) return b.phrase;
|
|
34366
|
-
return OLDEST_FALLBACK;
|
|
34367
|
-
}
|
|
34368
|
-
function summarizeInventory(rows, installResourceGroup, now) {
|
|
34369
|
-
const installRg = installResourceGroup.toLowerCase();
|
|
34370
|
-
const notable = rows.filter(
|
|
34371
|
-
(r) => r.resourceGroup.toLowerCase() !== installRg && Object.hasOwn(NOTABLE_TYPES, r.type.toLowerCase())
|
|
34372
|
-
);
|
|
34373
|
-
const notableCount = notable.length;
|
|
34374
|
-
const byNoun = /* @__PURE__ */ new Map();
|
|
34375
|
-
for (const r of notable) {
|
|
34376
|
-
const noun = NOTABLE_TYPES[r.type.toLowerCase()];
|
|
34377
|
-
byNoun.set(noun, (byNoun.get(noun) ?? 0) + 1);
|
|
34378
|
-
}
|
|
34379
|
-
const categories = [...byNoun.entries()].map(([noun, count]) => ({ noun, count })).sort((a, b) => b.count - a.count || a.noun.localeCompare(b.noun));
|
|
34380
|
-
const byRegion = /* @__PURE__ */ new Map();
|
|
34381
|
-
for (const r of notable) {
|
|
34382
|
-
if (!r.location) continue;
|
|
34383
|
-
const loc = r.location.toLowerCase();
|
|
34384
|
-
byRegion.set(loc, (byRegion.get(loc) ?? 0) + 1);
|
|
34385
|
-
}
|
|
34386
|
-
const regionSpread = byRegion.size;
|
|
34387
|
-
let topRegion = null;
|
|
34388
|
-
for (const [region, count] of byRegion) {
|
|
34389
|
-
if (notableCount > 0 && count / notableCount >= REGION_MAJORITY) {
|
|
34390
|
-
topRegion = region;
|
|
34391
|
-
break;
|
|
33825
|
+
}
|
|
33826
|
+
let founderEmail = (flags.founderEmail ?? "").trim();
|
|
33827
|
+
if (!founderEmail) {
|
|
33828
|
+
const confirmed = previous?.founderEmail.trim() ?? "";
|
|
33829
|
+
const guess = identity.email.trim();
|
|
33830
|
+
if (!isTty) {
|
|
33831
|
+
if (!confirmed) return { ok: false, error: refuseFounderEmail() };
|
|
33832
|
+
founderEmail = confirmed;
|
|
33833
|
+
} else {
|
|
33834
|
+
const prefill = confirmed || guess;
|
|
33835
|
+
const answered = (await ask(renderEmailQuestion(prefill))).trim();
|
|
33836
|
+
founderEmail = answered || prefill;
|
|
33837
|
+
if (!founderEmail) return { ok: false, error: refuseFounderEmail() };
|
|
34392
33838
|
}
|
|
34393
33839
|
}
|
|
34394
|
-
|
|
34395
|
-
|
|
34396
|
-
|
|
34397
|
-
|
|
34398
|
-
|
|
34399
|
-
|
|
33840
|
+
if (!looksLikeEmail(founderEmail)) {
|
|
33841
|
+
return { ok: false, error: refuseMalformed("--founder-email", founderEmail) };
|
|
33842
|
+
}
|
|
33843
|
+
let advisor;
|
|
33844
|
+
if (flags.noAdvisor) {
|
|
33845
|
+
advisor = null;
|
|
33846
|
+
} else {
|
|
33847
|
+
const prior = previous?.advisor ?? null;
|
|
33848
|
+
let name = (flags.advisorName ?? "").trim();
|
|
33849
|
+
let email = (flags.advisorEmail ?? "").trim();
|
|
33850
|
+
if (!hasAdvisorFlag && isTty) {
|
|
33851
|
+
name = (await ask(ADVISOR_LEAD + renderAdvisorQuestion("Name ", prior?.name ?? ""))).trim();
|
|
33852
|
+
email = (await ask(renderAdvisorQuestion("Email", prior?.email ?? ""))).trim();
|
|
33853
|
+
}
|
|
33854
|
+
name = name || (prior?.name ?? "");
|
|
33855
|
+
email = email || (prior?.email ?? "");
|
|
33856
|
+
if (email && !looksLikeEmail(email)) {
|
|
33857
|
+
return { ok: false, error: refuseMalformed("--advisor-email", email) };
|
|
33858
|
+
}
|
|
33859
|
+
advisor = name || email ? { name, email } : null;
|
|
34400
33860
|
}
|
|
34401
|
-
const oldestBucket = oldestMs === null ? null : ageBucket((now - oldestMs) / MS_PER_DAY);
|
|
34402
33861
|
return {
|
|
34403
|
-
|
|
34404
|
-
|
|
34405
|
-
|
|
34406
|
-
|
|
34407
|
-
|
|
34408
|
-
|
|
33862
|
+
ok: true,
|
|
33863
|
+
profile: {
|
|
33864
|
+
schemaVersion: 1,
|
|
33865
|
+
collectedAt: args.now(),
|
|
33866
|
+
founderName: identity.name.trim(),
|
|
33867
|
+
founderEmail,
|
|
33868
|
+
advisor,
|
|
33869
|
+
...flags.noAdvisor ? { advisorCleared: true } : {}
|
|
33870
|
+
}
|
|
34409
33871
|
};
|
|
34410
33872
|
}
|
|
34411
|
-
|
|
34412
|
-
|
|
34413
|
-
var
|
|
34414
|
-
|
|
34415
|
-
|
|
34416
|
-
|
|
34417
|
-
|
|
34418
|
-
|
|
34419
|
-
|
|
34420
|
-
|
|
34421
|
-
|
|
34422
|
-
|
|
34423
|
-
|
|
34424
|
-
|
|
34425
|
-
|
|
34426
|
-
|
|
34427
|
-
|
|
34428
|
-
|
|
34429
|
-
|
|
34430
|
-
|
|
34431
|
-
}
|
|
34432
|
-
|
|
34433
|
-
|
|
34434
|
-
return `${items.slice(0, -1).join(", ")} and ${items[items.length - 1]}`;
|
|
34435
|
-
}
|
|
34436
|
-
function piecesClause(s) {
|
|
34437
|
-
const shown = s.categories.slice(0, MAX_NOUNS).map((c) => `${String(c.count)} ${c.noun}`);
|
|
34438
|
-
if (s.categories.length > MAX_NOUNS) {
|
|
34439
|
-
return `The notable pieces: ${shown.join(", ")}, and a handful of others`;
|
|
34440
|
-
}
|
|
34441
|
-
return `The notable pieces: ${joinList(shown)}`;
|
|
34442
|
-
}
|
|
34443
|
-
function regionAgeClause(s) {
|
|
34444
|
-
const region = s.topRegion ? `mostly in ${regionDisplay(s.topRegion)}` : s.regionSpread > 1 ? `spread across ${String(s.regionSpread)} regions` : "";
|
|
34445
|
-
const parts = [region, s.oldestBucket ?? ""].filter((p) => p !== "");
|
|
34446
|
-
return parts.length > 0 ? ` \u2014 ${parts.join(", ")}` : "";
|
|
34447
|
-
}
|
|
34448
|
-
function renderInventoryNote(summary) {
|
|
34449
|
-
const body = summary.notableCount === 0 ? `${LEAD} There are no notable pre-existing resources.` : `${LEAD} ${piecesClause(summary)}${regionAgeClause(summary)}.`;
|
|
34450
|
-
return summary.verdict === "heavy" ? `${body} ${INVENTORY_INVITATION2}` : body;
|
|
34451
|
-
}
|
|
34452
|
-
|
|
34453
|
-
// src/lib/subscription-inventory.ts
|
|
34454
|
-
async function listSubscriptionResources(subscriptionId) {
|
|
34455
|
-
let out;
|
|
34456
|
-
try {
|
|
34457
|
-
out = await runAz(["resource", "list", "--subscription", subscriptionId, "--output", "json"]);
|
|
34458
|
-
} catch (e) {
|
|
34459
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
34460
|
-
const denied = /AuthorizationFailed|Forbidden|\b403\b|does not have authorization/i.test(msg);
|
|
34461
|
-
return { ok: false, error: denied ? "denied" : "unavailable" };
|
|
34462
|
-
}
|
|
34463
|
-
let raw;
|
|
33873
|
+
|
|
33874
|
+
// src/lib/ezra-guides.ts
|
|
33875
|
+
var EZRA_REPO = "m8t-labs/ezra";
|
|
33876
|
+
var EZRA_GUIDES_URL = `https://github.com/${EZRA_REPO}/blob/main/guides`;
|
|
33877
|
+
|
|
33878
|
+
// src/lib/chat-invite.ts
|
|
33879
|
+
var CHAT_INVITE_PATH = ".m8t/chat-invite.json";
|
|
33880
|
+
var CHAT_INVITE_TIMEOUT_MS = 3e3;
|
|
33881
|
+
var ALLOWED_INVITE_HOSTS = ["m8t.run"];
|
|
33882
|
+
function hostAllowed(hostname) {
|
|
33883
|
+
const host = hostname.toLowerCase();
|
|
33884
|
+
return ALLOWED_INVITE_HOSTS.some((allowed) => host === allowed || host.endsWith(`.${allowed}`));
|
|
33885
|
+
}
|
|
33886
|
+
var SHELL_UNSAFE = /[&|^<>"`$%\\\s]/;
|
|
33887
|
+
function chatInviteUrl() {
|
|
33888
|
+
return `https://raw.githubusercontent.com/${EZRA_REPO}/main/${CHAT_INVITE_PATH}`;
|
|
33889
|
+
}
|
|
33890
|
+
function isRecord4(value) {
|
|
33891
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
33892
|
+
}
|
|
33893
|
+
async function fetchChatInvite(deps = {}) {
|
|
33894
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
33895
|
+
let parsed;
|
|
34464
33896
|
try {
|
|
34465
|
-
|
|
33897
|
+
const res = await fetchImpl(chatInviteUrl(), {
|
|
33898
|
+
headers: { accept: "application/json" },
|
|
33899
|
+
signal: AbortSignal.timeout(deps.timeoutMs ?? CHAT_INVITE_TIMEOUT_MS)
|
|
33900
|
+
});
|
|
33901
|
+
if (!res.ok) return { ok: false, reason: "http-error" };
|
|
33902
|
+
const raw2 = await res.text();
|
|
33903
|
+
try {
|
|
33904
|
+
parsed = JSON.parse(raw2);
|
|
33905
|
+
} catch {
|
|
33906
|
+
return { ok: false, reason: "malformed" };
|
|
33907
|
+
}
|
|
34466
33908
|
} catch {
|
|
34467
|
-
return { ok: false,
|
|
34468
|
-
}
|
|
34469
|
-
if (!
|
|
34470
|
-
|
|
34471
|
-
}
|
|
34472
|
-
const
|
|
34473
|
-
|
|
34474
|
-
|
|
34475
|
-
location: e.location ?? "",
|
|
34476
|
-
createdTime: e.createdTime ?? null
|
|
34477
|
-
}));
|
|
34478
|
-
return { ok: true, rows };
|
|
34479
|
-
}
|
|
34480
|
-
var DEFAULT_DEADLINE_MS2 = 25e3;
|
|
34481
|
-
async function resolveSubscriptionInventory(args) {
|
|
34482
|
-
const now = args.now ?? (() => Date.now());
|
|
34483
|
-
const deadlineMs = args.deadlineMs ?? DEFAULT_DEADLINE_MS2;
|
|
34484
|
-
const scanFn = args.scanImpl ?? listSubscriptionResources;
|
|
34485
|
-
let timer;
|
|
33909
|
+
return { ok: false, reason: "unreachable" };
|
|
33910
|
+
}
|
|
33911
|
+
if (!isRecord4(parsed)) return { ok: false, reason: "malformed" };
|
|
33912
|
+
if (parsed.schemaVersion !== 1) return { ok: false, reason: "unknown-schema" };
|
|
33913
|
+
if (parsed.enabled !== true) return { ok: false, reason: "disabled" };
|
|
33914
|
+
const raw = typeof parsed.inviteUrl === "string" ? parsed.inviteUrl.trim() : "";
|
|
33915
|
+
if (!raw) return { ok: false, reason: "no-url" };
|
|
33916
|
+
let parsedUrl;
|
|
34486
33917
|
try {
|
|
34487
|
-
|
|
34488
|
-
const scan2 = scanFn(args.subscriptionId);
|
|
34489
|
-
void scan2.catch(() => {
|
|
34490
|
-
});
|
|
34491
|
-
const timeout = new Promise((resolve6) => {
|
|
34492
|
-
timer = setTimeout(() => {
|
|
34493
|
-
resolve6("timeout");
|
|
34494
|
-
}, deadlineMs);
|
|
34495
|
-
});
|
|
34496
|
-
const raced = await Promise.race([scan2, timeout]);
|
|
34497
|
-
if (raced === "timeout" || !raced.ok) return {};
|
|
34498
|
-
const summary = summarizeInventory(raced.rows, args.installResourceGroup, now());
|
|
34499
|
-
return { note: renderInventoryNote(summary) };
|
|
33918
|
+
parsedUrl = new URL(raw);
|
|
34500
33919
|
} catch {
|
|
34501
|
-
return {};
|
|
34502
|
-
}
|
|
34503
|
-
|
|
34504
|
-
}
|
|
34505
|
-
|
|
34506
|
-
|
|
34507
|
-
|
|
34508
|
-
|
|
34509
|
-
|
|
34510
|
-
|
|
34511
|
-
|
|
34512
|
-
|
|
34513
|
-
${colors.dim("First turn may say 'warming up' for a few minutes while access propagates \u2014 that's expected.")}
|
|
33920
|
+
return { ok: false, reason: "insecure-url" };
|
|
33921
|
+
}
|
|
33922
|
+
if (parsedUrl.protocol !== "https:") return { ok: false, reason: "insecure-url" };
|
|
33923
|
+
if (!hostAllowed(parsedUrl.hostname)) return { ok: false, reason: "insecure-url" };
|
|
33924
|
+
const url = parsedUrl.href;
|
|
33925
|
+
if (SHELL_UNSAFE.test(url)) return { ok: false, reason: "insecure-url" };
|
|
33926
|
+
return { ok: true, url };
|
|
33927
|
+
}
|
|
33928
|
+
function renderChatPointer(url, opts) {
|
|
33929
|
+
const lead = "While your real Ezra installs in your cloud, talk to our Ezra to see what he can do for you.\n";
|
|
33930
|
+
return opts.print ? `${lead} Open this: ${url}
|
|
33931
|
+
` : `${lead} ${url}
|
|
34514
33932
|
`;
|
|
34515
33933
|
}
|
|
34516
|
-
|
|
34517
|
-
|
|
34518
|
-
|
|
34519
|
-
|
|
34520
|
-
|
|
33934
|
+
var CHAT_UNAVAILABLE_LINE = " Chat with Ezra isn't available right now \u2014 your install is unaffected.\n";
|
|
33935
|
+
async function openChatInvite(deps = {}) {
|
|
33936
|
+
const invite = await fetchChatInvite(deps);
|
|
33937
|
+
if (!invite.ok) return CHAT_UNAVAILABLE_LINE;
|
|
33938
|
+
const print = deps.print === true;
|
|
33939
|
+
if (!print) {
|
|
33940
|
+
const open3 = deps.open ?? tryOpenUrl;
|
|
33941
|
+
try {
|
|
33942
|
+
await open3(invite.url);
|
|
33943
|
+
} catch {
|
|
33944
|
+
}
|
|
33945
|
+
}
|
|
33946
|
+
return renderChatPointer(invite.url, { print });
|
|
34521
33947
|
}
|
|
34522
|
-
|
|
34523
|
-
|
|
34524
|
-
|
|
34525
|
-
|
|
33948
|
+
|
|
33949
|
+
// src/commands/bootstrap/profile.ts
|
|
33950
|
+
var BootstrapProfileCommand = class extends M8tCommand {
|
|
33951
|
+
static paths = [["bootstrap", "profile"]];
|
|
33952
|
+
static usage = Command63.Usage({
|
|
33953
|
+
description: "Confirm your email + your Microsoft startup advisor, and open Ezra to talk to while the install runs.",
|
|
34526
33954
|
details: [
|
|
34527
|
-
"Run after `m8t bootstrap launch`, in parallel with `status --watch`.
|
|
34528
|
-
"
|
|
34529
|
-
"
|
|
34530
|
-
"only when --voice is set \u2014 see --voice for what that does and does not affect),",
|
|
34531
|
-
"installs deps, and starts the webapp detached so a coding agent can continue without opening",
|
|
34532
|
-
"a separate terminal.",
|
|
33955
|
+
"Run after `m8t bootstrap launch`, in parallel with `status --watch`. Records the two",
|
|
33956
|
+
"facts your deployed advisors need \u2014 the address Ezra copies you on, and the Microsoft",
|
|
33957
|
+
"startup advisor it can escalate to \u2014 and points you at the hosted Ezra for the wait.",
|
|
34533
33958
|
"",
|
|
34534
|
-
"
|
|
34535
|
-
"
|
|
33959
|
+
"Non-interactive by default: pass the answers as flags and nothing is prompted. With a",
|
|
33960
|
+
"terminal, any answer you did not pass is asked for, with your Azure sign-in offered as",
|
|
33961
|
+
"the default. Idempotent \u2014 re-run it any time to correct or fill in an answer.",
|
|
33962
|
+
"",
|
|
33963
|
+
"Nothing here blocks the install, and chat never fails it."
|
|
34536
33964
|
].join("\n"),
|
|
34537
33965
|
examples: [
|
|
34538
|
-
["
|
|
34539
|
-
["
|
|
34540
|
-
["
|
|
34541
|
-
["
|
|
34542
|
-
["Experimental: start the voice relay (no effect on the text-only intake)", "$0 bootstrap ui --repo-root /path/to/m8t --voice"]
|
|
33966
|
+
["Record both answers", "$0 bootstrap profile --founder-email you@example.com --advisor-name 'Sam Lee' --advisor-email sam@example.com"],
|
|
33967
|
+
["Record the email, skip the advisor", "$0 bootstrap profile --founder-email you@example.com --no-advisor"],
|
|
33968
|
+
["Answer the questions at a terminal", "$0 bootstrap profile"],
|
|
33969
|
+
["Skip the browser hand-off", "$0 bootstrap profile --founder-email you@example.com --print"]
|
|
34543
33970
|
]
|
|
34544
33971
|
});
|
|
34545
|
-
|
|
34546
|
-
|
|
34547
|
-
|
|
34548
|
-
|
|
34549
|
-
});
|
|
34550
|
-
|
|
34551
|
-
skipInstall = Option59.Boolean("--skip-install", false);
|
|
34552
|
-
stop = Option59.Boolean("--stop", false);
|
|
34553
|
-
foreground = Option59.Boolean("--foreground", false);
|
|
34554
|
-
voice = Option59.Boolean("--voice", false, {
|
|
34555
|
-
description: "Experimental: when serving, starts the voice relay and writes the intake voice env var. The onboarding intake is text-only and is unaffected by this flag \u2014 no voice worker is registered for it."
|
|
34556
|
-
});
|
|
33972
|
+
founderEmail = Option60.String("--founder-email", { description: "The founder's contact address \u2014 Ezra copies them on its outbound mail." });
|
|
33973
|
+
advisorName = Option60.String("--advisor-name", { description: "Their Microsoft startup advisor's name." });
|
|
33974
|
+
advisorEmail = Option60.String("--advisor-email", { description: "Their Microsoft startup advisor's email." });
|
|
33975
|
+
noAdvisor = Option60.Boolean("--no-advisor", false, { description: "Record that they have no startup advisor to add (or don't know it yet)." });
|
|
33976
|
+
noChat = Option60.Boolean("--no-chat", false, { description: "Record the answers without opening the hosted Ezra." });
|
|
33977
|
+
print = Option60.Boolean("--print", false, { description: "Print the chat link instead of opening a browser (headless / SSH)." });
|
|
34557
33978
|
async executeCommand() {
|
|
34558
|
-
|
|
34559
|
-
|
|
34560
|
-
|
|
34561
|
-
|
|
34562
|
-
|
|
34563
|
-
|
|
34564
|
-
|
|
34565
|
-
|
|
34566
|
-
|
|
34567
|
-
|
|
34568
|
-
|
|
34569
|
-
|
|
34570
|
-
|
|
34571
|
-
|
|
34572
|
-
|
|
34573
|
-
|
|
34574
|
-
|
|
34575
|
-
|
|
34576
|
-
|
|
34577
|
-
|
|
34578
|
-
|
|
34579
|
-
|
|
34580
|
-
|
|
34581
|
-
|
|
34582
|
-
});
|
|
34583
|
-
if (this.port !== "3000") {
|
|
34584
|
-
throw new LocalCliError({
|
|
34585
|
-
code: "BOOTSTRAP_UI_PORT_MSAL_LOCKED",
|
|
34586
|
-
message: `--port ${this.port} is not supported: the app registration's redirect URI is fixed to localhost:3000, so Microsoft sign-in only works there.`,
|
|
34587
|
-
hint: "Omit --port (it defaults to 3000). If port 3000 is busy, stop the other process using it."
|
|
33979
|
+
const stdin = this.context.stdin;
|
|
33980
|
+
const stdout = this.context.stdout;
|
|
33981
|
+
const isTty = stdin.isTTY === true;
|
|
33982
|
+
const flags = {
|
|
33983
|
+
founderEmail: typeof this.founderEmail === "string" ? this.founderEmail : void 0,
|
|
33984
|
+
advisorName: typeof this.advisorName === "string" ? this.advisorName : void 0,
|
|
33985
|
+
advisorEmail: typeof this.advisorEmail === "string" ? this.advisorEmail : void 0,
|
|
33986
|
+
noAdvisor: this.noAdvisor === true
|
|
33987
|
+
};
|
|
33988
|
+
const [identity, previous] = await Promise.all([
|
|
33989
|
+
getSignedInUserIdentity(),
|
|
33990
|
+
readOnboardingProfile()
|
|
33991
|
+
]);
|
|
33992
|
+
const mayAsk = isTty && (!(flags.founderEmail ?? "").trim() || !(flags.noAdvisor || (flags.advisorName ?? "").trim() || (flags.advisorEmail ?? "").trim()));
|
|
33993
|
+
const rl = mayAsk ? readline3.createInterface({ input: stdin, output: stdout }) : null;
|
|
33994
|
+
let collected;
|
|
33995
|
+
try {
|
|
33996
|
+
collected = await collectProfile({
|
|
33997
|
+
flags,
|
|
33998
|
+
identity,
|
|
33999
|
+
previous,
|
|
34000
|
+
isTty,
|
|
34001
|
+
ask: async (question) => rl ? await rl.question(question) : "",
|
|
34002
|
+
now: () => (/* @__PURE__ */ new Date()).toISOString()
|
|
34588
34003
|
});
|
|
34004
|
+
} finally {
|
|
34005
|
+
rl?.close();
|
|
34589
34006
|
}
|
|
34590
|
-
|
|
34591
|
-
|
|
34007
|
+
if (!collected.ok) throw collected.error;
|
|
34008
|
+
const profile = collected.profile;
|
|
34009
|
+
await writeOnboardingProfile(profile);
|
|
34010
|
+
this.context.stdout.write(`${colors.success("\u2713")} Recorded \u2014 Ezra will copy you at ${colors.field(profile.founderEmail)}.
|
|
34592
34011
|
`);
|
|
34593
|
-
|
|
34594
|
-
|
|
34595
|
-
|
|
34012
|
+
if (profile.advisor === null) this.context.stdout.write(colors.dim(ADVISOR_SKIPPED_LINE));
|
|
34013
|
+
await this.seedIfBrainsExist(profile);
|
|
34014
|
+
if (this.noChat !== true) {
|
|
34015
|
+
this.context.stdout.write(await openChatInvite({ print: this.print === true }));
|
|
34596
34016
|
}
|
|
34597
|
-
|
|
34598
|
-
|
|
34599
|
-
|
|
34600
|
-
|
|
34601
|
-
|
|
34602
|
-
|
|
34603
|
-
|
|
34604
|
-
|
|
34605
|
-
|
|
34606
|
-
|
|
34607
|
-
|
|
34608
|
-
|
|
34609
|
-
|
|
34610
|
-
|
|
34611
|
-
|
|
34612
|
-
|
|
34613
|
-
|
|
34614
|
-
|
|
34615
|
-
|
|
34616
|
-
await ensureFounderFoundryRole({ credential: credential2, subscriptionId: state.subscriptionId, principalId: oid, accountScope });
|
|
34617
|
-
const identity = await getSignedInUserIdentity();
|
|
34618
|
-
out("deploying the intake agent (ezra-intake) in the background...");
|
|
34619
|
-
const deployOutcome = (async () => {
|
|
34620
|
-
const [choice, inventory] = await Promise.all([
|
|
34621
|
-
resolveIntakeModel({
|
|
34622
|
-
accountScope,
|
|
34623
|
-
fallbackRegion: state.location,
|
|
34624
|
-
onNarrate: out
|
|
34625
|
-
}),
|
|
34626
|
-
resolveSubscriptionInventory({
|
|
34627
|
-
subscriptionId: state.subscriptionId,
|
|
34628
|
-
installResourceGroup: state.resourceGroup,
|
|
34629
|
-
onNarrate: out
|
|
34630
|
-
})
|
|
34631
|
-
]);
|
|
34632
|
-
return deployIntakeAgentWithRetry({
|
|
34633
|
-
credential: credential2,
|
|
34634
|
-
endpoint,
|
|
34635
|
-
repoRoot,
|
|
34636
|
-
model: choice.model,
|
|
34637
|
-
fieldOverrides: buildIntakeFieldOverrides({
|
|
34638
|
-
founderIdentityNote: composeFounderIdentityNote(identity),
|
|
34639
|
-
chosenModelNote: choice.note,
|
|
34640
|
-
inventoryNote: inventory.note
|
|
34641
|
-
}),
|
|
34642
|
-
onWait: out
|
|
34643
|
-
});
|
|
34644
|
-
})().then(
|
|
34645
|
-
(version) => ({ ok: true, version }),
|
|
34646
|
-
(error) => ({ ok: false, error: error instanceof Error ? error : new Error(String(error)) })
|
|
34647
|
-
);
|
|
34648
|
-
const envPath = writeWebEnvLocal({
|
|
34649
|
-
repoRoot,
|
|
34650
|
-
tenantId: account.tenantId,
|
|
34651
|
-
clientId: state.appRegClientId,
|
|
34652
|
-
foundryEndpoint: endpoint,
|
|
34653
|
-
voice: this.voice === true
|
|
34654
|
-
});
|
|
34655
|
-
if (!this.skipInstall) {
|
|
34656
|
-
out("installing apps/web dependencies (pnpm)\u2026");
|
|
34657
|
-
await installWebDeps(repoRoot);
|
|
34658
|
-
}
|
|
34659
|
-
if (this.prepOnly === true) {
|
|
34660
|
-
const outcome2 = await deployOutcome;
|
|
34661
|
-
if (!outcome2.ok) throw outcome2.error;
|
|
34662
|
-
this.context.stdout.write(renderDeploySuccess(outcome2.version, envPath));
|
|
34663
|
-
this.context.stdout.write(` ${colors.hint("serve it yourself:")} pnpm --filter web dev
|
|
34664
|
-
`);
|
|
34665
|
-
return 0;
|
|
34017
|
+
return 0;
|
|
34018
|
+
}
|
|
34019
|
+
/**
|
|
34020
|
+
* Seed now if the brains already exist, otherwise say who will.
|
|
34021
|
+
*
|
|
34022
|
+
* Best-effort on purpose. This command runs mid-install by design, when there is
|
|
34023
|
+
* usually nothing to seed yet — `bootstrap status --watch` does it at `done`. But
|
|
34024
|
+
* re-running this after the install should not leave a founder wondering whether
|
|
34025
|
+
* their correction landed, so when the brains ARE there we seed immediately.
|
|
34026
|
+
*
|
|
34027
|
+
* A failure here is reported and swallowed: the answers are already safely on disk,
|
|
34028
|
+
* and the install path will retry the seed on its own.
|
|
34029
|
+
*/
|
|
34030
|
+
async seedIfBrainsExist(profile) {
|
|
34031
|
+
let ctx = null;
|
|
34032
|
+
try {
|
|
34033
|
+
ctx = await resolveSeedContext({});
|
|
34034
|
+
} catch {
|
|
34035
|
+
ctx = null;
|
|
34666
34036
|
}
|
|
34667
|
-
if (
|
|
34668
|
-
|
|
34669
|
-
|
|
34670
|
-
|
|
34671
|
-
|
|
34672
|
-
|
|
34673
|
-
tenantId: account.tenantId,
|
|
34674
|
-
clientId: state.appRegClientId,
|
|
34675
|
-
webInternalBaseUrl: "http://localhost:3000"
|
|
34676
|
-
});
|
|
34677
|
-
out("voice relay on :8790");
|
|
34678
|
-
}
|
|
34679
|
-
const outcome2 = await deployOutcome;
|
|
34680
|
-
if (!outcome2.ok) {
|
|
34681
|
-
this.context.stderr.write(renderDeployFailure(outcome2.error));
|
|
34682
|
-
return 1;
|
|
34683
|
-
}
|
|
34684
|
-
this.context.stdout.write(renderDeploySuccess(outcome2.version, envPath));
|
|
34685
|
-
out("starting the webapp on :3000 (Ctrl-C to stop)\u2026");
|
|
34686
|
-
await runInherit("pnpm", ["--filter", "web", "dev"], repoRoot, "BOOTSTRAP_UI_DEV_FAILED");
|
|
34687
|
-
return 0;
|
|
34037
|
+
if (!ctx) {
|
|
34038
|
+
this.context.stdout.write(
|
|
34039
|
+
` ${colors.dim("Your advisors will pick this up when the install finishes.")}
|
|
34040
|
+
`
|
|
34041
|
+
);
|
|
34042
|
+
return;
|
|
34688
34043
|
}
|
|
34689
|
-
|
|
34690
|
-
|
|
34691
|
-
|
|
34692
|
-
|
|
34693
|
-
|
|
34694
|
-
|
|
34695
|
-
|
|
34696
|
-
|
|
34044
|
+
try {
|
|
34045
|
+
await applyProfileToBrains({
|
|
34046
|
+
block: toOnboardingBlock(profile),
|
|
34047
|
+
brainRepos: ctx.brainRepos,
|
|
34048
|
+
branch: "main",
|
|
34049
|
+
appCreds: ctx.appCreds,
|
|
34050
|
+
subscriptionId: ctx.subscriptionId,
|
|
34051
|
+
azIdentity: { name: profile.founderName, email: profile.founderEmail },
|
|
34052
|
+
contactsOnly: true,
|
|
34053
|
+
advisorCleared: profile.advisorCleared === true
|
|
34697
34054
|
});
|
|
34698
|
-
out("voice relay on :8790");
|
|
34699
|
-
}
|
|
34700
|
-
out("starting the webapp on :3000 in the background\u2026");
|
|
34701
|
-
const { alreadyRunning, logPath } = await serveOnboardingUiDetached({ repoRoot, port: this.port });
|
|
34702
|
-
if (alreadyRunning) {
|
|
34703
34055
|
this.context.stdout.write(
|
|
34704
|
-
`${colors.success("\u2713")}
|
|
34056
|
+
`${colors.success("\u2713")} Your advisors now know how to reach you (seeded ${ctx.brainRepos.join(", ")}).
|
|
34705
34057
|
`
|
|
34706
34058
|
);
|
|
34707
|
-
}
|
|
34708
|
-
this.context.
|
|
34709
|
-
|
|
34710
|
-
${colors.
|
|
34059
|
+
} catch (e) {
|
|
34060
|
+
this.context.stderr.write(
|
|
34061
|
+
` ${colors.dim(`saved, but the brain seed didn't land yet: ${e instanceof Error ? e.message : String(e)}`)}
|
|
34062
|
+
${colors.hint("it retries when the install finishes, or run:")} m8t bootstrap seed-profile
|
|
34711
34063
|
`
|
|
34712
34064
|
);
|
|
34713
34065
|
}
|
|
34714
|
-
autoOpenOnboardingUi(`http://localhost:${this.port}`);
|
|
34715
|
-
const outcome = await deployOutcome;
|
|
34716
|
-
if (!outcome.ok) {
|
|
34717
|
-
this.context.stderr.write(renderDeployFailure(outcome.error));
|
|
34718
|
-
return 1;
|
|
34719
|
-
}
|
|
34720
|
-
this.context.stdout.write(renderDeploySuccess(outcome.version, envPath));
|
|
34721
|
-
return 0;
|
|
34722
34066
|
}
|
|
34723
34067
|
};
|
|
34724
34068
|
|
|
34725
34069
|
// src/commands/bootstrap/seed-profile.ts
|
|
34726
|
-
import { Command as
|
|
34070
|
+
import { Command as Command64, Option as Option61 } from "clipanion";
|
|
34727
34071
|
var BootstrapSeedProfileCommand = class extends M8tCommand {
|
|
34728
34072
|
static paths = [["bootstrap", "seed-profile"]];
|
|
34729
|
-
static usage =
|
|
34730
|
-
description: "Seed your advisors' brains with
|
|
34731
|
-
details:
|
|
34073
|
+
static usage = Command64.Usage({
|
|
34074
|
+
description: "Seed your advisors' brains with how to reach you, from what you confirmed at `m8t bootstrap profile`.",
|
|
34075
|
+
details: [
|
|
34076
|
+
"Renders memory/founder.md + memory/company-profile.md (+ their MEMORY.md index lines)",
|
|
34077
|
+
"and commits them to the brains this install created, via the GitHub App. Idempotent \u2014",
|
|
34078
|
+
"an unchanged profile is a no-op.",
|
|
34079
|
+
"",
|
|
34080
|
+
"Run `m8t bootstrap profile` first; this is the manual re-run of the seed that",
|
|
34081
|
+
"`m8t bootstrap status --watch` already does when the install completes.",
|
|
34082
|
+
"",
|
|
34083
|
+
"--watch is legacy: it polls for an onboarding conversation from installs made when a",
|
|
34084
|
+
"dedicated intake agent was still deployed. It has nothing to wait for otherwise."
|
|
34085
|
+
].join("\n"),
|
|
34732
34086
|
examples: [
|
|
34733
|
-
["Seed now (idempotent)", "$0 bootstrap seed-profile"]
|
|
34734
|
-
["Wait for the founder to finish", "$0 bootstrap seed-profile --watch"]
|
|
34087
|
+
["Seed now (idempotent)", "$0 bootstrap seed-profile"]
|
|
34735
34088
|
]
|
|
34736
34089
|
});
|
|
34737
|
-
endpoint =
|
|
34738
|
-
brain =
|
|
34739
|
-
watch =
|
|
34740
|
-
timeout =
|
|
34741
|
-
githubAppCreds =
|
|
34090
|
+
endpoint = Option61.String("--endpoint", { description: "Override the Foundry endpoint (else read from the install status)." });
|
|
34091
|
+
brain = Option61.String("--brain", { description: "Seed only this one brain repo, instead of both <org>/stacey-brain and <org>/ezra-brain." });
|
|
34092
|
+
watch = Option61.Boolean("--watch", false, { description: "Poll until the intake completes (or --timeout)." });
|
|
34093
|
+
timeout = Option61.String("--timeout", { description: "Watch timeout in minutes (default 20)." });
|
|
34094
|
+
githubAppCreds = Option61.String("--github-app-creds");
|
|
34742
34095
|
async executeCommand() {
|
|
34743
34096
|
const ctx = await resolveSeedContext({
|
|
34744
34097
|
endpointOverride: typeof this.endpoint === "string" ? this.endpoint : void 0,
|
|
@@ -34750,12 +34103,30 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
|
|
|
34750
34103
|
`);
|
|
34751
34104
|
return 0;
|
|
34752
34105
|
}
|
|
34106
|
+
const local = await readOnboardingProfile();
|
|
34107
|
+
if (local) {
|
|
34108
|
+
await applyProfileToBrains({
|
|
34109
|
+
block: toOnboardingBlock(local),
|
|
34110
|
+
brainRepos: ctx.brainRepos,
|
|
34111
|
+
branch: "main",
|
|
34112
|
+
appCreds: ctx.appCreds,
|
|
34113
|
+
subscriptionId: ctx.subscriptionId,
|
|
34114
|
+
azIdentity: { name: local.founderName, email: local.founderEmail },
|
|
34115
|
+
contactsOnly: true,
|
|
34116
|
+
advisorCleared: local.advisorCleared === true
|
|
34117
|
+
});
|
|
34118
|
+
this.context.stdout.write(
|
|
34119
|
+
`${colors.success("\u2713")} seeded your advisors' brains (${ctx.brainRepos.join(", ")}) with how to reach you.
|
|
34120
|
+
`
|
|
34121
|
+
);
|
|
34122
|
+
return 0;
|
|
34123
|
+
}
|
|
34753
34124
|
const watch = this.watch === true;
|
|
34754
34125
|
const rawTimeout = typeof this.timeout === "string" ? this.timeout.trim() : "";
|
|
34755
34126
|
const parsedTimeout = Number(rawTimeout);
|
|
34756
34127
|
const timeoutMin = rawTimeout !== "" && Number.isFinite(parsedTimeout) ? parsedTimeout : 20;
|
|
34757
34128
|
const deadline = Date.now() + timeoutMin * 6e4;
|
|
34758
|
-
const
|
|
34129
|
+
const sleep4 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
34759
34130
|
for (; ; ) {
|
|
34760
34131
|
const token = await getFoundryToken();
|
|
34761
34132
|
const { hadIntake, block, rejection } = await findOnboardingProfile({ endpoint: ctx.endpoint, token });
|
|
@@ -34782,9 +34153,16 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
|
|
|
34782
34153
|
);
|
|
34783
34154
|
return 3;
|
|
34784
34155
|
}
|
|
34785
|
-
|
|
34156
|
+
if (!hadIntake) {
|
|
34157
|
+
this.context.stderr.write(
|
|
34158
|
+
` ${colors.dim("nothing collected yet \u2014 your advisors don't know how to reach you.")}
|
|
34159
|
+
${colors.hint("run:")} m8t bootstrap profile
|
|
34160
|
+
`
|
|
34161
|
+
);
|
|
34162
|
+
return 3;
|
|
34163
|
+
}
|
|
34786
34164
|
this.context.stderr.write(
|
|
34787
|
-
` ${colors.dim(
|
|
34165
|
+
` ${colors.dim("intake not complete yet \u2014 no m8t_onboarding block found.")}
|
|
34788
34166
|
${colors.hint("retry:")} m8t bootstrap seed-profile
|
|
34789
34167
|
`
|
|
34790
34168
|
);
|
|
@@ -34792,7 +34170,7 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
|
|
|
34792
34170
|
}
|
|
34793
34171
|
this.context.stderr.write(` ${colors.dim("waiting for the intake to complete\u2026")}
|
|
34794
34172
|
`);
|
|
34795
|
-
await
|
|
34173
|
+
await sleep4(2e4);
|
|
34796
34174
|
}
|
|
34797
34175
|
}
|
|
34798
34176
|
};
|
|
@@ -34801,7 +34179,7 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
|
|
|
34801
34179
|
import * as fs41 from "fs";
|
|
34802
34180
|
import * as os22 from "os";
|
|
34803
34181
|
import * as path46 from "path";
|
|
34804
|
-
import { Command as
|
|
34182
|
+
import { Command as Command65, Option as Option62 } from "clipanion";
|
|
34805
34183
|
init_errors();
|
|
34806
34184
|
|
|
34807
34185
|
// src/lib/telemetry-enroll.ts
|
|
@@ -34820,7 +34198,7 @@ async function defaultToken() {
|
|
|
34820
34198
|
const token = out.trim();
|
|
34821
34199
|
return token.length > 0 ? token : null;
|
|
34822
34200
|
}
|
|
34823
|
-
var
|
|
34201
|
+
var sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
34824
34202
|
async function enroll(args) {
|
|
34825
34203
|
const f = args.fetchImpl ?? fetch;
|
|
34826
34204
|
const url = args.baseUrl ? `${args.baseUrl.replace(/\/+$/, "")}/api/ingest/enroll` : ingestUrl("/api/ingest/enroll");
|
|
@@ -34841,7 +34219,7 @@ async function enroll(args) {
|
|
|
34841
34219
|
throw new LocalCliError({ code: "TELEMETRY_ENROLL_UNREACHABLE", message: `Could not reach the telemetry ingest to enroll: ${e instanceof Error ? e.message : String(e)}` });
|
|
34842
34220
|
}
|
|
34843
34221
|
if (res.status !== 503 || attempt === 3) break;
|
|
34844
|
-
await
|
|
34222
|
+
await sleep3(retryDelayMs * attempt);
|
|
34845
34223
|
}
|
|
34846
34224
|
if (!res?.ok) {
|
|
34847
34225
|
const detail = res ? await res.text().catch(() => "") : "";
|
|
@@ -34883,7 +34261,7 @@ async function resolveKeyVaultName(containerAppResourceId) {
|
|
|
34883
34261
|
}
|
|
34884
34262
|
var TelemetryEnrollCommand = class extends M8tCommand {
|
|
34885
34263
|
static paths = [["telemetry", "enroll"]];
|
|
34886
|
-
static usage =
|
|
34264
|
+
static usage = Command65.Usage({
|
|
34887
34265
|
description: "Enroll this installation for operational telemetry (pre-existing installs).",
|
|
34888
34266
|
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.",
|
|
34889
34267
|
examples: [
|
|
@@ -34891,11 +34269,11 @@ var TelemetryEnrollCommand = class extends M8tCommand {
|
|
|
34891
34269
|
["Enroll and share a support contact", "$0 telemetry enroll --contact-email you@example.com --company 'Acme'"]
|
|
34892
34270
|
]
|
|
34893
34271
|
});
|
|
34894
|
-
company =
|
|
34895
|
-
contactEmail =
|
|
34896
|
-
subscription =
|
|
34897
|
-
resourceGroup =
|
|
34898
|
-
force =
|
|
34272
|
+
company = Option62.String("--company", { description: "Share your company name with m8t support. Not sent unless you pass it." });
|
|
34273
|
+
contactEmail = Option62.String("--contact-email", { description: "Share a contact email with m8t support. Not sent unless you pass it." });
|
|
34274
|
+
subscription = Option62.String("--subscription", { description: "Azure subscription id used to find your deployment. Never sent to m8t." });
|
|
34275
|
+
resourceGroup = Option62.String("--resource-group", { description: "Resource group to disambiguate discovery, if you have more than one m8t deployment." });
|
|
34276
|
+
force = Option62.Boolean("--force", false, { description: "Enroll even when a key is already stored. Use only when m8t support asks you to." });
|
|
34899
34277
|
async executeCommand() {
|
|
34900
34278
|
const account = await getAzAccount();
|
|
34901
34279
|
const subscriptionId = (typeof this.subscription === "string" ? this.subscription : void 0) ?? account.subscriptionId;
|
|
@@ -34945,7 +34323,7 @@ var TelemetryEnrollCommand = class extends M8tCommand {
|
|
|
34945
34323
|
};
|
|
34946
34324
|
|
|
34947
34325
|
// src/commands/companion/bridge.ts
|
|
34948
|
-
import { Command as
|
|
34326
|
+
import { Command as Command66, Option as Option63 } from "clipanion";
|
|
34949
34327
|
|
|
34950
34328
|
// ../../packages/companion-bridge-contract/src/index.ts
|
|
34951
34329
|
var COMPANION_MESSAGE_MAX_CODE_POINTS = 32768;
|
|
@@ -34984,7 +34362,7 @@ var VERSION3 = /^[A-Za-z0-9][A-Za-z0-9._+-]{0,63}$/u;
|
|
|
34984
34362
|
function isVersionOrNull(value) {
|
|
34985
34363
|
return value === null || typeof value === "string" && VERSION3.test(value);
|
|
34986
34364
|
}
|
|
34987
|
-
function
|
|
34365
|
+
function isRecord5(value) {
|
|
34988
34366
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
34989
34367
|
}
|
|
34990
34368
|
function hasExactKeys(value, keys) {
|
|
@@ -35009,11 +34387,11 @@ function isInstantOrNull(value) {
|
|
|
35009
34387
|
return value === null || typeof value === "string" && INSTANT_SHAPE.test(value) && Number.isFinite(Date.parse(value));
|
|
35010
34388
|
}
|
|
35011
34389
|
function parseDecision(value) {
|
|
35012
|
-
if (!
|
|
34390
|
+
if (!isRecord5(value) || !isBoundedPlainString(value.callId, COMPANION_DECISION_CALL_ID_MAX_LENGTH) || !isBoundedPlainString(value.title, COMPANION_DECISION_TITLE_MAX_LENGTH) || !Array.isArray(value.options) || value.options.length < COMPANION_DECISION_OPTIONS_MIN || value.options.length > COMPANION_DECISION_OPTIONS_MAX) {
|
|
35013
34391
|
return eventError();
|
|
35014
34392
|
}
|
|
35015
34393
|
const options = value.options.map((option) => {
|
|
35016
|
-
if (!
|
|
34394
|
+
if (!isRecord5(option) || !hasExactKeys(option, ["label", "detail"]) || !isBoundedPlainString(option.label, COMPANION_DECISION_LABEL_MAX_LENGTH) || !isBoundedMessageText(option.detail, COMPANION_DECISION_DETAIL_MAX_CODE_POINTS)) {
|
|
35017
34395
|
return eventError();
|
|
35018
34396
|
}
|
|
35019
34397
|
return { label: option.label, detail: option.detail };
|
|
@@ -35031,7 +34409,7 @@ function parseDecision(value) {
|
|
|
35031
34409
|
return { ...base, status: "selected", optionIndex: value.optionIndex };
|
|
35032
34410
|
}
|
|
35033
34411
|
function parseArtifact(value) {
|
|
35034
|
-
if (!
|
|
34412
|
+
if (!isRecord5(value) || !isBoundedPlainString(value.name, COMPANION_ARTIFACT_NAME_MAX_LENGTH)) {
|
|
35035
34413
|
return eventError();
|
|
35036
34414
|
}
|
|
35037
34415
|
if (value.sizeBytes === void 0) {
|
|
@@ -35050,7 +34428,7 @@ function parseTurnArtifacts(value) {
|
|
|
35050
34428
|
return value.map(parseArtifact);
|
|
35051
34429
|
}
|
|
35052
34430
|
function parseTurn(value) {
|
|
35053
|
-
if (!
|
|
34431
|
+
if (!isRecord5(value) || !isBoundedPlainString(value.id, COMPANION_TURN_ID_MAX_LENGTH) || value.role !== "user" && value.role !== "mate" || !isInstantOrNull(value.at)) {
|
|
35054
34432
|
return eventError();
|
|
35055
34433
|
}
|
|
35056
34434
|
const keys = [
|
|
@@ -35129,7 +34507,7 @@ function isValidMateRoute(value) {
|
|
|
35129
34507
|
}
|
|
35130
34508
|
}
|
|
35131
34509
|
function parseRequest(value) {
|
|
35132
|
-
if (!
|
|
34510
|
+
if (!isRecord5(value)) return requestError();
|
|
35133
34511
|
if (value.type === "roster") {
|
|
35134
34512
|
if (!hasExactKeys(value, ["type"])) return requestError();
|
|
35135
34513
|
return { type: "roster" };
|
|
@@ -35195,7 +34573,7 @@ function parseRequestLine(line2) {
|
|
|
35195
34573
|
}
|
|
35196
34574
|
}
|
|
35197
34575
|
function parseMate(value) {
|
|
35198
|
-
if (!
|
|
34576
|
+
if (!isRecord5(value) || !hasExactKeys(value, [
|
|
35199
34577
|
"personaKey",
|
|
35200
34578
|
"agentName",
|
|
35201
34579
|
"displayName",
|
|
@@ -35220,7 +34598,7 @@ function parseMate(value) {
|
|
|
35220
34598
|
};
|
|
35221
34599
|
}
|
|
35222
34600
|
function parseEvent(value) {
|
|
35223
|
-
if (!
|
|
34601
|
+
if (!isRecord5(value)) return eventError();
|
|
35224
34602
|
if (value.type === "update") {
|
|
35225
34603
|
if (!hasExactKeys(value, ["type", "installed", "available", "severity"]) || !isVersionOrNull(value.installed) || !isVersionOrNull(value.available) || !(value.severity === null || SEVERITIES2.has(value.severity))) {
|
|
35226
34604
|
return eventError();
|
|
@@ -35359,7 +34737,7 @@ var PredispatchFailure = class extends Error {
|
|
|
35359
34737
|
}
|
|
35360
34738
|
reason;
|
|
35361
34739
|
};
|
|
35362
|
-
function
|
|
34740
|
+
function isRecord6(value) {
|
|
35363
34741
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
35364
34742
|
}
|
|
35365
34743
|
function hasControlCharacter(value) {
|
|
@@ -35429,16 +34807,16 @@ async function readJsonEnvelope(response) {
|
|
|
35429
34807
|
if (error instanceof PredispatchFailure) throw error;
|
|
35430
34808
|
throw new PredispatchFailure("not-connected");
|
|
35431
34809
|
}
|
|
35432
|
-
if (!
|
|
34810
|
+
if (!isRecord6(envelope) || envelope.ok !== true || !("data" in envelope)) {
|
|
35433
34811
|
throw new PredispatchFailure("not-connected");
|
|
35434
34812
|
}
|
|
35435
34813
|
return envelope.data;
|
|
35436
34814
|
}
|
|
35437
34815
|
function decodeAgents(data) {
|
|
35438
|
-
if (!
|
|
34816
|
+
if (!isRecord6(data) || !Array.isArray(data.agents)) {
|
|
35439
34817
|
throw new PredispatchFailure("mate-unavailable");
|
|
35440
34818
|
}
|
|
35441
|
-
return data.agents.filter(
|
|
34819
|
+
return data.agents.filter(isRecord6);
|
|
35442
34820
|
}
|
|
35443
34821
|
function resolveAgentName(agents, personaKey) {
|
|
35444
34822
|
const matches = agents.filter(
|
|
@@ -35460,18 +34838,18 @@ function requireAgentName(agents, personaKey) {
|
|
|
35460
34838
|
return name;
|
|
35461
34839
|
}
|
|
35462
34840
|
function decodeConversationId(data) {
|
|
35463
|
-
if (!
|
|
34841
|
+
if (!isRecord6(data) || typeof data.id !== "string" || !OPAQUE_ID.test(data.id)) {
|
|
35464
34842
|
throw new PredispatchFailure("not-connected");
|
|
35465
34843
|
}
|
|
35466
34844
|
return data.id;
|
|
35467
34845
|
}
|
|
35468
34846
|
function decodeLastMessageId(data) {
|
|
35469
|
-
if (!
|
|
34847
|
+
if (!isRecord6(data) || !Array.isArray(data.messages)) {
|
|
35470
34848
|
throw new PredispatchFailure("not-connected");
|
|
35471
34849
|
}
|
|
35472
34850
|
const messages = data.messages;
|
|
35473
34851
|
const last = messages.at(-1);
|
|
35474
|
-
if (!
|
|
34852
|
+
if (!isRecord6(last) || typeof last.id !== "string" || !OPAQUE_ID.test(last.id)) {
|
|
35475
34853
|
return void 0;
|
|
35476
34854
|
}
|
|
35477
34855
|
return last.id;
|
|
@@ -35557,7 +34935,7 @@ function toCompanionArtifacts(data) {
|
|
|
35557
34935
|
const artifacts = [];
|
|
35558
34936
|
for (const entry of data) {
|
|
35559
34937
|
if (artifacts.length >= COMPANION_ARTIFACTS_MAX) break;
|
|
35560
|
-
if (!
|
|
34938
|
+
if (!isRecord6(entry) || typeof entry.name !== "string") continue;
|
|
35561
34939
|
const name = boundPlainLine(entry.name, COMPANION_ARTIFACT_NAME_MAX_LENGTH);
|
|
35562
34940
|
if (name.length === 0) continue;
|
|
35563
34941
|
const size = entry.size_bytes;
|
|
@@ -35571,12 +34949,12 @@ function decodeMessageInstant(value) {
|
|
|
35571
34949
|
return typeof value === "number" && Number.isFinite(value) && value > 0 && value < EPOCH_SECONDS_LIMIT ? new Date(Math.round(value) * 1e3).toISOString() : null;
|
|
35572
34950
|
}
|
|
35573
34951
|
function decodeTurns(data, limit2) {
|
|
35574
|
-
if (!
|
|
34952
|
+
if (!isRecord6(data) || !Array.isArray(data.messages)) {
|
|
35575
34953
|
throw new PredispatchFailure("not-connected");
|
|
35576
34954
|
}
|
|
35577
34955
|
const turns = [];
|
|
35578
34956
|
for (const entry of data.messages) {
|
|
35579
|
-
if (!
|
|
34957
|
+
if (!isRecord6(entry)) continue;
|
|
35580
34958
|
if (entry.role !== "user" && entry.role !== "assistant") continue;
|
|
35581
34959
|
if (typeof entry.id !== "string" || !OPAQUE_ID.test(entry.id)) continue;
|
|
35582
34960
|
if (typeof entry.content !== "string") continue;
|
|
@@ -35603,12 +34981,12 @@ function decodeDurableDecision(entry) {
|
|
|
35603
34981
|
return toCompanionDecision(frame.directive);
|
|
35604
34982
|
}
|
|
35605
34983
|
function decodeRichTurns(data, limit2) {
|
|
35606
|
-
if (!
|
|
34984
|
+
if (!isRecord6(data) || !Array.isArray(data.messages)) {
|
|
35607
34985
|
throw new PredispatchFailure("not-connected");
|
|
35608
34986
|
}
|
|
35609
34987
|
const turns = [];
|
|
35610
34988
|
for (const entry of data.messages) {
|
|
35611
|
-
if (!
|
|
34989
|
+
if (!isRecord6(entry)) continue;
|
|
35612
34990
|
if (entry.role !== "user" && entry.role !== "assistant") continue;
|
|
35613
34991
|
if (typeof entry.id !== "string" || !OPAQUE_ID.test(entry.id)) continue;
|
|
35614
34992
|
if (typeof entry.content !== "string") continue;
|
|
@@ -35744,7 +35122,7 @@ async function drainAcceptedSse(response) {
|
|
|
35744
35122
|
continue;
|
|
35745
35123
|
}
|
|
35746
35124
|
const parsed = JSON.parse(data);
|
|
35747
|
-
if (!
|
|
35125
|
+
if (!isRecord6(parsed) || typeof parsed.type !== "string" || parsed.type === "error") {
|
|
35748
35126
|
throw new Error("invalid stream event");
|
|
35749
35127
|
}
|
|
35750
35128
|
}
|
|
@@ -35792,7 +35170,7 @@ async function streamAcceptedSse(response, now, onText, onData) {
|
|
|
35792
35170
|
return;
|
|
35793
35171
|
}
|
|
35794
35172
|
const parsed = JSON.parse(data);
|
|
35795
|
-
if (!
|
|
35173
|
+
if (!isRecord6(parsed) || typeof parsed.type !== "string" || parsed.type === "error") {
|
|
35796
35174
|
throw new Error("invalid stream event");
|
|
35797
35175
|
}
|
|
35798
35176
|
if (parsed.type === "text-delta" && typeof parsed.delta === "string" && parsed.delta.length > 0) {
|
|
@@ -36016,7 +35394,7 @@ async function classifyDecideRefusal(response) {
|
|
|
36016
35394
|
let reason;
|
|
36017
35395
|
try {
|
|
36018
35396
|
const envelope = JSON.parse(await readBoundedText(response));
|
|
36019
|
-
if (
|
|
35397
|
+
if (isRecord6(envelope) && isRecord6(envelope.error) && isRecord6(envelope.error.details)) {
|
|
36020
35398
|
reason = envelope.error.details.reason;
|
|
36021
35399
|
}
|
|
36022
35400
|
} catch {
|
|
@@ -36401,14 +35779,14 @@ async function runCompanionBridge(stdin, stdout, stderr, deps = defaultDeps4) {
|
|
|
36401
35779
|
return 3;
|
|
36402
35780
|
}
|
|
36403
35781
|
}
|
|
36404
|
-
var CompanionBridgeCommand = class extends
|
|
35782
|
+
var CompanionBridgeCommand = class extends Command66 {
|
|
36405
35783
|
static paths = [["companion", "_bridge"]];
|
|
36406
35784
|
/**
|
|
36407
35785
|
* One process serving many requests instead of one per request, so the
|
|
36408
35786
|
* session keeps its authenticated context between them. A CLI predating the
|
|
36409
35787
|
* flag rejects it outright, which is how the app knows to fall back.
|
|
36410
35788
|
*/
|
|
36411
|
-
serve =
|
|
35789
|
+
serve = Option63.Boolean("--serve", false);
|
|
36412
35790
|
async execute() {
|
|
36413
35791
|
if (this.serve) {
|
|
36414
35792
|
return runCompanionBridgeServe(
|
|
@@ -36426,7 +35804,7 @@ var CompanionBridgeCommand = class extends Command65 {
|
|
|
36426
35804
|
};
|
|
36427
35805
|
|
|
36428
35806
|
// src/commands/companion/status.ts
|
|
36429
|
-
import { Command as
|
|
35807
|
+
import { Command as Command67 } from "clipanion";
|
|
36430
35808
|
async function withTimeout(work, ms) {
|
|
36431
35809
|
let timer;
|
|
36432
35810
|
try {
|
|
@@ -36498,7 +35876,7 @@ Run: m8t companion install
|
|
|
36498
35876
|
}
|
|
36499
35877
|
var CompanionStatusCommand = class extends M8tCommand {
|
|
36500
35878
|
static paths = [["companion", "status"]];
|
|
36501
|
-
static usage =
|
|
35879
|
+
static usage = Command67.Usage({
|
|
36502
35880
|
description: "Verify the installed desktop companion without launching it."
|
|
36503
35881
|
});
|
|
36504
35882
|
async executeCommand() {
|
|
@@ -36512,7 +35890,7 @@ var CompanionStatusCommand = class extends M8tCommand {
|
|
|
36512
35890
|
};
|
|
36513
35891
|
|
|
36514
35892
|
// src/commands/companion/repair.ts
|
|
36515
|
-
import { Command as
|
|
35893
|
+
import { Command as Command68, Option as Option64 } from "clipanion";
|
|
36516
35894
|
async function runCompanionRepairCommand(stdout, repair) {
|
|
36517
35895
|
const state = await repair();
|
|
36518
35896
|
if (state.state === "not-released") {
|
|
@@ -36531,10 +35909,10 @@ async function runCompanionRepairCommand(stdout, repair) {
|
|
|
36531
35909
|
}
|
|
36532
35910
|
var CompanionRepairCommand = class extends M8tCommand {
|
|
36533
35911
|
static paths = [["companion", "repair"]];
|
|
36534
|
-
static usage =
|
|
35912
|
+
static usage = Command68.Usage({
|
|
36535
35913
|
description: "Restore the desktop companions and start-at-login state."
|
|
36536
35914
|
});
|
|
36537
|
-
resourceGroup =
|
|
35915
|
+
resourceGroup = Option64.String("--resource-group", {
|
|
36538
35916
|
description: "Which deployment to bind to, when the subscription holds more than one."
|
|
36539
35917
|
});
|
|
36540
35918
|
async executeCommand() {
|
|
@@ -36548,7 +35926,7 @@ var CompanionRepairCommand = class extends M8tCommand {
|
|
|
36548
35926
|
};
|
|
36549
35927
|
|
|
36550
35928
|
// src/commands/companion/uninstall.ts
|
|
36551
|
-
import { Command as
|
|
35929
|
+
import { Command as Command69 } from "clipanion";
|
|
36552
35930
|
async function runCompanionUninstallCommand(stdout, uninstall) {
|
|
36553
35931
|
const state = await uninstall();
|
|
36554
35932
|
if (state.state !== "not-installed") {
|
|
@@ -36560,7 +35938,7 @@ async function runCompanionUninstallCommand(stdout, uninstall) {
|
|
|
36560
35938
|
}
|
|
36561
35939
|
var CompanionUninstallCommand = class extends M8tCommand {
|
|
36562
35940
|
static paths = [["companion", "uninstall"]];
|
|
36563
|
-
static usage =
|
|
35941
|
+
static usage = Command69.Usage({
|
|
36564
35942
|
description: "Remove only this user's desktop companion installation."
|
|
36565
35943
|
});
|
|
36566
35944
|
async executeCommand() {
|
|
@@ -36639,6 +36017,7 @@ cli.register(BootstrapLaunchCommand);
|
|
|
36639
36017
|
cli.register(BootstrapStatusCommand);
|
|
36640
36018
|
cli.register(BootstrapReapCommand);
|
|
36641
36019
|
cli.register(BootstrapUiCommand);
|
|
36020
|
+
cli.register(BootstrapProfileCommand);
|
|
36642
36021
|
cli.register(BootstrapSeedProfileCommand);
|
|
36643
36022
|
cli.register(TelemetryEnrollCommand);
|
|
36644
36023
|
cli.register(CompanionBridgeCommand);
|