@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.
Files changed (3) hide show
  1. package/dist/cli.js +784 -1405
  2. package/dist/cli.js.map +1 -1
  3. 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.84";
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 file = await this.retrieve(id);
9944
- while (!file.status || !TERMINAL_STATES.has(file.status)) {
9938
+ let file2 = await this.retrieve(id);
9939
+ while (!file2.status || !TERMINAL_STATES.has(file2.status)) {
9945
9940
  await sleep(pollInterval);
9946
- file = await this.retrieve(id);
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 file;
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 file = await this.create(vectorStoreId, body, options);
11746
- return await this.poll(vectorStoreId, file.id, options);
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 file = fileResponse.data;
11767
- switch (file.status) {
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 file;
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, file, options) {
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, file, options) {
11803
- const fileInfo = await this.upload(vectorStoreId, file, options);
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 dir = configDir();
13203
+ const dir2 = configDir();
13209
13204
  const configPath = getConfigPath();
13210
- await fs.mkdir(dir, { recursive: true });
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 dir = path4.join(home, ".m8t");
14674
+ const dir2 = path4.join(home, ".m8t");
14680
14675
  const stem = `github-app-${orgFileSegment(org)}-${appId}`;
14681
14676
  return {
14682
- pemPath: path4.join(dir, `${stem}.pem`),
14683
- sidecarPath: path4.join(dir, `${stem}.json`)
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 file = agentYamlPath(agentName, home);
15294
- if (!fs6.existsSync(file)) return null;
15295
- const text = fs6.readFileSync(file, "utf8");
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 file = agentYamlPath(agentName, home);
15300
- fs6.mkdirSync(path7.dirname(file), { recursive: true });
15301
- fs6.writeFileSync(file, stringifyYaml3(data), { mode: 384 });
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, dir, out) {
15885
- for (const entry of fs11.readdirSync(dir, { withFileTypes: true })) {
15886
- const abs = path11.join(dir, entry.name);
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 dir of AGENT_SEED_DIRS) {
15903
- const abs = path11.join(repoDir, dir);
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 file of AGENT_SEED_FILES) {
15907
- const abs = path11.join(repoDir, file);
15908
- if (fs11.existsSync(abs) && fs11.statSync(abs).isFile()) rels.push(file);
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 dir = path13.join(root, "brain-seeds", name);
16165
- if (!fs13.existsSync(dir)) {
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 dir;
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(dir) {
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: dir, encoding: "utf8" });
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 ${dir}: ${detail}`
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 dir = path16.resolve(hint);
17638
- const fsRoot = path16.parse(dir).root;
17632
+ let dir2 = path16.resolve(hint);
17633
+ const fsRoot = path16.parse(dir2).root;
17639
17634
  for (; ; ) {
17640
- if (fs16.existsSync(path16.join(dir, rel))) return dir;
17641
- const parent = path16.dirname(dir);
17642
- if (parent === dir || dir === fsRoot) return null;
17643
- dir = parent;
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 sleep5 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
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 sleep5(intervalMs);
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(file) {
18626
- if (!fs20.existsSync(file)) return "";
18627
- const text = fs20.readFileSync(file, "utf8");
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 sleep5 = args.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
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 sleep5(interval);
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 dir = path22.resolve(startDir);
19162
+ let dir2 = path22.resolve(startDir);
19168
19163
  for (; ; ) {
19169
- if (fs22.existsSync(path22.join(dir, "personas"))) return dir;
19170
- const parent = path22.dirname(dir);
19171
- if (parent === dir) return null;
19172
- dir = parent;
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 file = path22.join(root, "personas", persona, "persona.md");
19173
+ const file2 = path22.join(root, "personas", persona, "persona.md");
19179
19174
  let raw;
19180
19175
  try {
19181
- raw = fs22.readFileSync(file, "utf-8");
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 readFile9 = deps.readFile ?? ((p) => fs23.readFileSync(p, "utf8"));
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 = readFile9(source.file);
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 dir = fs27.mkdtempSync(path28.join(os11.tmpdir(), "seed-merge-"));
21651
+ const dir2 = fs27.mkdtempSync(path28.join(os11.tmpdir(), "seed-merge-"));
21657
21652
  try {
21658
- const o = path28.join(dir, "ours");
21659
- const b = path28.join(dir, "base");
21660
- const t = path28.join(dir, "theirs");
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(dir, { recursive: true, force: true });
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(dir, rel) {
21732
- if (!dir) return null;
21733
- const abs = path29.join(dir, rel);
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 dir = path30.join(contentRoot, items[seedName].path);
21868
- return fs29.existsSync(dir) ? dir : null;
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 dir = HOSTED_AGENT_PERSONA_DIR[a.component];
22032
- const agentName = dir ? discovered[dir] : void 0;
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 dir = HOSTED_AGENT_PERSONA_DIR[a.component];
22088
- agentName = dir ? discovered[dir] : void 0;
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(canonical) {
27359
+ function absentCursor(canonical2) {
27365
27360
  return {
27366
- worker: canonical,
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 canonical = canonicalWorker(worker);
27380
+ const canonical2 = canonicalWorker(worker);
27386
27381
  let row;
27387
27382
  try {
27388
- row = await this.client.getEntity(CURSOR_PARTITION_KEY, canonical);
27383
+ row = await this.client.getEntity(CURSOR_PARTITION_KEY, canonical2);
27389
27384
  } catch (e) {
27390
27385
  if (isAbsentCursorError(e))
27391
- return absentCursor(canonical);
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: canonical,
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 canonical = canonicalWorker(worker);
27408
+ const canonical2 = canonicalWorker(worker);
27414
27409
  await this.ensureTable();
27415
27410
  const entity = {
27416
27411
  partitionKey: CURSOR_PARTITION_KEY,
27417
- rowKey: canonical,
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 canonical = canonicalWorker(worker);
27503
- const pks = /* @__PURE__ */ new Set([canonical]);
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 === canonical)
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 sleep5 = opts.sleep ?? defaultSleep;
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 sleep5(attempt);
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 sleep5 = opts.sleep ?? defaultSleep2;
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 sleep5(attempt);
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 sleep5(attempt);
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(canonical) {
29202
- const aliases = Object.entries(WORKER_ALIASES).filter(([, c]) => c === canonical).map(([physical]) => physical);
29203
- return [canonical, ...aliases];
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 canonical = opts.worker.toLowerCase();
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: canonical,
29224
+ worker: canonical2,
29230
29225
  projectEndpoint: project.endpoint,
29231
29226
  ledgerTableEndpoint,
29232
29227
  workspaceId,
29233
- physicalPks: physicalPksFor2(canonical)
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 fs38 from "fs/promises";
31058
- import * as os19 from "os";
31059
- import * as path43 from "path";
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 os16 from "os";
31065
- import * as path38 from "path";
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
- `_Seeded from the onboarding questionnaire._`,
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 || advisorEmail ? [advisorName, advisorEmail ? `<${advisorEmail}>` : ""].filter(Boolean).join(" ") : NOT_CAPTURED;
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 onboarding from your Azure identity + the questionnaire. Authoritative \u2014 read it; don't rewrite it (origin: operator)._`,
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
- function composeFounderIdentityNote(id) {
31777
- const name = stripNoteInvitations(id.name);
31778
- const email = stripNoteInvitations(id.email);
31779
- if (name && email) {
31780
- return `You're speaking with **${name}**, signed in to Azure (their email looks like **${email}**). Open by confirming both, in your own voice \u2014 e.g. "I see you're signed in as ${name}, and I'll use ${email} for your advisor handoffs and cost reports \u2014 is that right, or should I change either?" Record exactly what they approve or correct.`;
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
- if (name) {
31783
- return `You're speaking with **${name}** (signed in to Azure). Confirm their name, then ask for the best contact email to reach them (that's where advisor handoffs and cost reports go).`;
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
- return `Ask the founder their name and the best contact email to reach them (that's where advisor handoffs and cost reports go).`;
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 ?? path38.join(os16.homedir(), ".m8t", "github-app.json");
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
- let nextIndex = upsertMemoryIndexOnce(existingIndex, companyLine, COMPANY_PROFILE_PATH);
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 = path38.join(os16.homedir(), ".m8t", "seed-profile.log");
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) return;
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 fs36 from "fs/promises";
32030
- import * as path40 from "path";
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 fs35 from "fs/promises";
32038
- import * as path39 from "path";
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") || path39.posix.isAbsolute(value)) {
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 = path39.posix.normalize(value);
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") || path39.posix.isAbsolute(target)) {
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 = path39.posix.normalize(
32060
- path39.posix.join(path39.posix.dirname(entryPath), target)
32025
+ const resolved = path40.posix.normalize(
32026
+ path40.posix.join(path40.posix.dirname(entryPath), target)
32061
32027
  );
32062
- if (resolved === ".." || resolved.startsWith("../") || path39.posix.isAbsolute(resolved)) {
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 fs35.readFile(filePath)).digest("hex");
32109
+ return createHash7("sha256").update(await fs36.readFile(filePath)).digest("hex");
32144
32110
  }
32145
32111
  async function walk2(root, relative4 = "") {
32146
- const directory = path39.join(root, ...relative4.split("/").filter(Boolean));
32147
- const children = await fs35.readdir(directory, { withFileTypes: true });
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 = path39.join(directory, child.name);
32153
- const stat5 = await fs35.lstat(childPath);
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 fs35.readlink(childPath);
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 fs35.lstat(root);
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 fs35.realpath(root);
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 = path39.join(root, ...entry.path.split("/"));
32188
- const resolved = await fs35.realpath(linkPath);
32189
- const relative4 = path39.relative(realRoot, resolved);
32190
- if (relative4 === ".." || relative4.startsWith(`..${path39.sep}`) || path39.isAbsolute(relative4)) {
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 fs35.lstat(manifestPath);
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 fs35.open(
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 = path39.join(root, ...entryPath.split("/"));
32244
- const relative4 = path39.relative(root, absolute);
32245
- if (relative4.startsWith("..") || path39.isAbsolute(relative4)) {
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 fs35.lstat(targetRoot);
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 fs35.readdir(targetRoot)).length > 0) {
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 fs35.mkdir(targetRoot, { recursive: false, mode: 448 });
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 fs35.mkdir(path39.dirname(target), { recursive: true, mode: 448 });
32265
+ await fs36.mkdir(path40.dirname(target), { recursive: true, mode: 448 });
32300
32266
  if (entry.type === "file") {
32301
- await fs35.copyFile(source, target);
32302
- await fs35.chmod(target, entry.mode);
32267
+ await fs36.copyFile(source, target);
32268
+ await fs36.chmod(target, entry.mode);
32303
32269
  } else {
32304
- await fs35.symlink(entry.target, target);
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(file, args) {
32286
+ function execFileAsync(file2, args) {
32321
32287
  return new Promise((resolve6, reject) => {
32322
- execFile(file, [...args], { windowsHide: true }, (error) => {
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(file, args) {
32294
+ function execFileOutput(file2, args) {
32329
32295
  return new Promise((resolve6, reject) => {
32330
32296
  execFile(
32331
- file,
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 = path40.join(input.homeDirectory, "Library", "LaunchAgents");
32344
- const registration = path40.join(
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 fs36.rm(registration, { force: true });
32316
+ await fs37.rm(registration, { force: true });
32351
32317
  return;
32352
32318
  }
32353
- await fs36.mkdir(launchAgents, { recursive: true, mode: 448 });
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 (file, args) => {
32361
- await execFileAsync(file, args);
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 = path40.join(
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 = path40.join(
32433
+ const companionState = path41.join(
32468
32434
  options.homeDirectory,
32469
32435
  ".m8t",
32470
32436
  "companion"
32471
32437
  );
32472
- const targetRoot = options.platform === "darwin" ? path40.join(
32438
+ const targetRoot = options.platform === "darwin" ? path41.join(
32473
32439
  options.homeDirectory,
32474
32440
  "Applications",
32475
32441
  "m8t Companion.app"
32476
- ) : path40.join(
32477
- options.localAppData ?? path40.join(options.homeDirectory, "AppData", "Local"),
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: path40.join(companionState, "install-v1.json"),
32485
- runtimeBinding: path40.join(companionState, "runtime-v1.json"),
32486
- preferences: path40.join(companionState, "preferences-v1.json")
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" || !path40.isAbsolute(record.ownedTargetRoot)) {
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" ? path40.win32 : path40.posix;
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 fs36.lstat(filePath)).isSymbolicLink()) {
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 = path40.relative(anchor, targetDirectory);
32542
- if (relative4 === ".." || relative4.startsWith(`..${path40.sep}`) || path40.isAbsolute(relative4)) {
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(path40.sep).filter(Boolean);
32511
+ const segments = relative4.split(path41.sep).filter(Boolean);
32546
32512
  let current = anchor;
32547
- const anchorStatus = await fs36.lstat(anchor);
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 = path40.join(current, segment);
32518
+ current = path41.join(current, segment);
32553
32519
  try {
32554
- const status = await fs36.lstat(current);
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
- path40.dirname(paths.installManifest)
32533
+ path41.dirname(paths.installManifest)
32568
32534
  );
32569
- const targetAnchor = options.platform === "win32" ? options.localAppData ?? path40.join(options.homeDirectory, "AppData", "Local") : options.homeDirectory;
32570
- await assertOwnedDirectoryChain(targetAnchor, path40.dirname(paths.targetRoot));
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 fs36.lstat(filePath);
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 fs36.open(
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 fs36.mkdir(path40.dirname(filePath), {
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 fs36.writeFile(temporary, contents, {
32574
+ await fs37.writeFile(temporary, contents, {
32609
32575
  mode,
32610
32576
  flag: "wx"
32611
32577
  });
32612
- await fs36.rename(temporary, filePath);
32613
- await fs36.chmod(filePath, mode).catch(() => void 0);
32578
+ await fs37.rename(temporary, filePath);
32579
+ await fs37.chmod(filePath, mode).catch(() => void 0);
32614
32580
  } finally {
32615
- await fs36.rm(temporary, { force: true }).catch(() => void 0);
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 fs36.realpath(filePath);
32620
- const stat5 = await fs36.stat(real);
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 fs36.access(real, executable ? constants2.X_OK : constants2.R_OK);
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 fs36.open(executable, "r+");
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 = path40.join(
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 fs36.rm(filePath, { force: true });
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 = path40.join(
32768
- path40.dirname(options.artifactManifestPath),
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
- path40.join(
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 fs36.lstat(paths.targetRoot);
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 fs36.mkdir(path40.dirname(paths.targetRoot), {
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 fs36.rename(paths.targetRoot, backup);
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 fs36.rename(stage, paths.targetRoot);
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 = path40.join(
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 ? path40.join(
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 fs36.rm(backup, { recursive: true, force: true });
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 fs36.rm(stage, { recursive: true, force: true }).catch(() => void 0);
32872
+ await fs37.rm(stage, { recursive: true, force: true }).catch(() => void 0);
32907
32873
  if (installedStage) {
32908
- await fs36.rm(paths.targetRoot, {
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 fs36.rename(backup, paths.targetRoot).catch(() => void 0);
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 = path40.join(
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 fs36.rm(ownedPath, { recursive, force: true });
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 os18 from "os";
32958
- import * as path42 from "path";
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 fs37 from "fs/promises";
32977
- import * as os17 from "os";
32978
- import * as path41 from "path";
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 ?? (() => fs37.mkdtemp(path41.join(os17.tmpdir(), "m8t-companion-"))))();
32997
+ const root = await (deps.makeTemporaryDirectory ?? (() => fs38.mkdtemp(path42.join(os18.tmpdir(), "m8t-companion-"))))();
33032
32998
  const dispose = async () => {
33033
- await fs37.rm(root, { recursive: true, force: true }).catch(() => void 0);
32999
+ await fs38.rm(root, { recursive: true, force: true }).catch(() => void 0);
33034
33000
  };
33035
33001
  try {
33036
- const archive = path41.join(root, pinned.asset);
33037
- await fs37.writeFile(archive, bytes, { mode: 384 });
33038
- const unpacked = path41.join(root, "unpacked");
33039
- await fs37.mkdir(unpacked, { recursive: false, mode: 448 });
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 fs37.rm(archive, { force: true });
33007
+ await fs38.rm(archive, { force: true });
33042
33008
  return {
33043
33009
  version: component.version,
33044
- artifactManifestPath: path41.join(unpacked, "artifact-v1.json"),
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 = os18.homedir();
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 = path42.resolve(stagedDirectory, "artifact-v1.json");
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(dir) {
33117
+ async function looksLikeCheckout(dir2) {
33152
33118
  try {
33153
- return (await fs38.stat(path43.join(dir, "brain-template"))).isDirectory();
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: () => os19.homedir()
33135
+ homedir: () => os20.homedir()
33170
33136
  };
33171
33137
  async function finalizeInstall(args, deps = defaultDeps3) {
33172
- const markerDir = path43.join(deps.homedir(), ".m8t");
33173
- const markerPath = path43.join(markerDir, "repo-root");
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 fs38.readFile(markerPath, "utf8").then((s) => s.trim()).catch(() => null);
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 fs38.mkdir(markerDir, { recursive: true });
33183
- await fs38.writeFile(markerPath, `${repoRoot}
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 fs38.readFile(path43.join(markerDir, "github-app.json"), "utf8");
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 sleep5 = (ms) => new Promise((r) => setTimeout(r, ms));
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 sleep5(1e4);
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 sleep5(1e4);
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 fs39 from "fs";
33662
+ import * as fs40 from "fs";
33702
33663
  import * as net from "net";
33703
- import * as os20 from "os";
33704
- import * as path44 from "path";
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 sleep3(ms) {
33752
- return new Promise((resolve6) => setTimeout(resolve6, ms));
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: path44.join(dir, "onboarding-ui.log"),
33998
- pidPath: path44.join(dir, "onboarding-ui.pid")
33678
+ logPath: path45.join(dir2, "onboarding-ui.log"),
33679
+ pidPath: path45.join(dir2, "onboarding-ui.pid")
33999
33680
  };
34000
33681
  }
34001
- function onboardingRelayPaths(home = os20.homedir()) {
34002
- const dir = path44.join(home, ".m8t");
33682
+ function onboardingRelayPaths(home = os21.homedir()) {
33683
+ const dir2 = path45.join(home, ".m8t");
34003
33684
  return {
34004
- logPath: path44.join(dir, "onboarding-relay.log"),
34005
- pidPath: path44.join(dir, "onboarding-relay.pid")
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 = fs39.readFileSync(pidPath, "utf8").trim();
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
- fs39.unlinkSync(pidPath);
33715
+ fs40.unlinkSync(pidPath);
34198
33716
  } catch {
34199
33717
  }
34200
33718
  return true;
34201
33719
  }
34202
- function stopOnboardingUi(home = os20.homedir()) {
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/lib/model-catalog.ts
34216
- async function listAgentModels(region) {
34217
- let out;
34218
- try {
34219
- out = await runAz(["cognitiveservices", "model", "list", "-l", region, "--output", "json"]);
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/lib/intake-model.ts
34249
- var DEFAULT_DEADLINE_MS = 18e4;
34250
- var SCOPE_RE = /\/resourceGroups\/([^/]+)\/providers\/Microsoft\.CognitiveServices\/accounts\/([^/]+)/i;
34251
- function parseAccountScope(scope) {
34252
- const m = SCOPE_RE.exec(scope);
34253
- return m ? { resourceGroup: m[1], account: m[2] } : null;
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 narrate(model, outcome, region) {
34256
- if (outcome === "deployed") return `${model} - ready`;
34257
- return `${model} - not available for your subscription in ${region}`;
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
- async function accountRegion(account, resourceGroup, fallback) {
34260
- try {
34261
- const out = await runAz([
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
- async function resolveIntakeModel(args) {
34280
- const floorTrace = (outcome, detail) => WHITELIST.map((r) => ({ model: r.model, outcome, ...detail === void 0 ? {} : { detail } }));
34281
- const degradeToFloor = (detail) => {
34282
- const trace = floorTrace("catalog-unverified", detail);
34283
- return { model: void 0, note: renderChosenModelNote(decideNote(WHITELIST, { chosen: null, trace })), trace };
34284
- };
34285
- const parsed = parseAccountScope(args.accountScope);
34286
- if (!parsed) return degradeToFloor(`unparseable account scope: ${args.accountScope}`);
34287
- try {
34288
- const region = await accountRegion(parsed.account, parsed.resourceGroup, args.fallbackRegion);
34289
- args.onNarrate?.("choosing the best model your subscription can run...");
34290
- const [catalog, quota] = await Promise.all([listAgentModels(region), listModelQuota(region)]);
34291
- const plan = planCascade(WHITELIST, catalog, quota);
34292
- const deploy = async (rung) => {
34293
- await ensureDeployment({
34294
- account: parsed.account,
34295
- resourceGroup: parsed.resourceGroup,
34296
- model: rung.model,
34297
- modelVersion: rung.version,
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
- model: result2.chosen?.model,
34310
- note: renderChosenModelNote(decideNote(WHITELIST, result2)),
34311
- trace: result2.trace
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
- } catch (e) {
34314
- return degradeToFloor(e instanceof Error ? e.message : String(e));
34315
- }
34316
- }
34317
-
34318
- // src/lib/inventory-summary.ts
34319
- var HEAVY_THRESHOLD = 10;
34320
- var INVENTORY_INVITATION2 = "You can offer to walk the founder through these resources.";
34321
- var NOTABLE_TYPES = {
34322
- // storage
34323
- "microsoft.storage/storageaccounts": "storage accounts",
34324
- // compute
34325
- "microsoft.compute/virtualmachines": "virtual machines",
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
- let oldestMs = null;
34395
- for (const r of notable) {
34396
- if (!r.createdTime) continue;
34397
- const t = Date.parse(r.createdTime);
34398
- if (Number.isNaN(t)) continue;
34399
- if (oldestMs === null || t < oldestMs) oldestMs = t;
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
- verdict: notableCount >= HEAVY_THRESHOLD ? "heavy" : "light",
34404
- notableCount,
34405
- categories,
34406
- topRegion,
34407
- regionSpread,
34408
- oldestBucket
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
- var LEAD = "I looked over what's already in this subscription, outside the resources m8t is setting up.";
34412
- var MAX_NOUNS = 4;
34413
- var REGION_NAMES = {
34414
- eastus: "East US",
34415
- eastus2: "East US 2",
34416
- westus: "West US",
34417
- westus2: "West US 2",
34418
- westus3: "West US 3",
34419
- centralus: "Central US",
34420
- southcentralus: "South Central US",
34421
- westeurope: "West Europe",
34422
- northeurope: "North Europe",
34423
- uksouth: "UK South",
34424
- swedencentral: "Sweden Central",
34425
- australiaeast: "Australia East",
34426
- eastasia: "East Asia",
34427
- southeastasia: "Southeast Asia"
34428
- };
34429
- function regionDisplay(region) {
34430
- return REGION_NAMES[region.toLowerCase()] ?? region;
34431
- }
34432
- function joinList(items) {
34433
- if (items.length <= 1) return items[0] ?? "";
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
- raw = JSON.parse(out);
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, error: "malformed" };
34468
- }
34469
- if (!Array.isArray(raw) || raw.some((e) => typeof e !== "object" || e === null)) {
34470
- return { ok: false, error: "malformed" };
34471
- }
34472
- const rows = raw.map((e) => ({
34473
- type: e.type ?? "",
34474
- resourceGroup: e.resourceGroup ?? "",
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
- args.onNarrate?.("checking what's already in your subscription...");
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
- } finally {
34503
- if (timer) clearTimeout(timer);
34504
- }
34505
- }
34506
-
34507
- // src/commands/bootstrap/ui.ts
34508
- function renderDeploySuccess(version, envPath) {
34509
- return `${colors.success("\u2713")} Ezra is live (${INTAKE_AGENT} v${version}).
34510
- env: ${envPath}
34511
- Opening ${colors.field("http://localhost:3000")} in your browser \u2192 Sign in with Microsoft \u2192 chat with Ezra.
34512
- ${colors.dim("(If it doesn't open, browse to http://localhost:3000 yourself.)")}
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
- function renderDeployFailure(error) {
34517
- const hint = error instanceof LocalCliError && error.hint ? error.hint : "Re-run 'm8t bootstrap ui' (idempotent).";
34518
- return `${colors.error("\u2717")} Ezra deploy failed: ${error.message}
34519
- ${colors.hint(hint)}
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
- var BootstrapUiCommand = class extends M8tCommand {
34523
- static paths = [["bootstrap", "ui"]];
34524
- static usage = Command62.Usage({
34525
- description: "Deploy Ezra + start the local onboarding chat UI in the background (returns immediately).",
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`. Waits for the cloud",
34528
- "installer's foundry-create phase, grants you Foundry data-plane access, deploys the no-brain",
34529
- "onboarding intake agent (ezra-intake), writes apps/web/.env.local (voice relay config is added",
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
- "The command returns as soon as the server is ready (or after ~45 s if it is still compiling).",
34535
- "Use `--stop` to shut it down, `--foreground` to keep the old blocking behaviour."
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
- ["Start the onboarding UI (backgrounds the server, returns)", "$0 bootstrap ui --repo-root /path/to/m8t"],
34539
- ["Stop the running onboarding UI", "$0 bootstrap ui --stop"],
34540
- ["Prep only (don't serve at all)", "$0 bootstrap ui --repo-root /path/to/m8t --prep-only"],
34541
- ["Foreground (blocking, old behaviour)", "$0 bootstrap ui --repo-root /path/to/m8t --foreground"],
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
- repoRoot = Option59.String("--repo-root");
34546
- port = Option59.String("--port", "3000");
34547
- endpoint = Option59.String("--endpoint", {
34548
- description: "Foundry project endpoint to target \u2014 overrides discovery when this install's resource group holds more than one Foundry project."
34549
- });
34550
- prepOnly = Option59.Boolean("--prep-only", false);
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
- if (this.stop === true) {
34559
- const stopped = stopOnboardingUi();
34560
- if (stopped) {
34561
- this.context.stdout.write(`${colors.success("\u2713")} stopped the onboarding chat UI.
34562
- `);
34563
- } else {
34564
- this.context.stdout.write(` ${colors.dim("nothing to stop (no onboarding-ui.pid found).")}
34565
- `);
34566
- }
34567
- return 0;
34568
- }
34569
- const state = await readBootstrapState();
34570
- if (!state) {
34571
- throw new LocalCliError({
34572
- code: "BOOTSTRAP_NO_STATE",
34573
- message: "No bootstrap is in flight (no ~/.m8t/bootstrap.json).",
34574
- hint: "Run 'm8t bootstrap launch --location <region>' first."
34575
- });
34576
- }
34577
- const explicitRoot = typeof this.repoRoot === "string" ? this.repoRoot : void 0;
34578
- const repoRoot = explicitRoot ?? process.cwd();
34579
- assertPlatformCheckout(repoRoot, "'m8t bootstrap ui'", ["apps/web"], {
34580
- source: explicitRoot === void 0 ? "the working directory" : "--repo-root",
34581
- hint: "The local onboarding chat is part of the platform's web app, which is not published, so this step is unavailable today. Nothing else is blocked: let the install finish, then run 'm8t open' to meet Ezra in your deployed web app."
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
- const out = (m) => {
34591
- this.context.stderr.write(` ${colors.dim(m)}
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
- if (fs40.existsSync(path45.join(os21.homedir(), ".m8t", "config.yaml"))) {
34595
- out(colors.error("\u26A0 an existing ~/.m8t/config.yaml will override the onboarding app registration in apps/web \u2014 if Microsoft sign-in fails, move it aside and retry."));
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
- const credential2 = new DefaultAzureCredential25();
34598
- const account = await getAzAccount();
34599
- assertNodeVersion();
34600
- out("waiting for Foundry (the installer's foundry-create phase)\u2026");
34601
- const { endpoint, accountScope } = await resolveFoundryEndpointWithWait(
34602
- {
34603
- credential: credential2,
34604
- subscriptionId: state.subscriptionId,
34605
- endpoint: typeof this.endpoint === "string" ? this.endpoint : void 0,
34606
- // Scoped to the resource group this install owns — the state file names
34607
- // it (and the installer was handed the same one), so there is no
34608
- // guessing between deployments in a shared subscription. Same rationale
34609
- // as discoverGateway in bootstrap-finalize.ts.
34610
- resourceGroup: state.resourceGroup
34611
- },
34612
- { onWait: out }
34613
- );
34614
- out("granting you Foundry data-plane access\u2026");
34615
- const oid = await getSignedInUserOid();
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 (this.foreground === true) {
34668
- if (this.voice === true) {
34669
- out("starting the voice relay on :8790 in the background\u2026");
34670
- await serveOnboardingRelayDetached({
34671
- repoRoot,
34672
- foundryEndpoint: endpoint,
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
- if (this.voice === true) {
34690
- out("starting the voice relay on :8790 in the background\u2026");
34691
- await serveOnboardingRelayDetached({
34692
- repoRoot,
34693
- foundryEndpoint: endpoint,
34694
- tenantId: account.tenantId,
34695
- clientId: state.appRegClientId,
34696
- webInternalBaseUrl: "http://localhost:3000"
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")} chat UI already running at http://localhost:${this.port}
34056
+ `${colors.success("\u2713")} Your advisors now know how to reach you (seeded ${ctx.brainRepos.join(", ")}).
34705
34057
  `
34706
34058
  );
34707
- } else {
34708
- this.context.stdout.write(
34709
- `${colors.success("\u2713")} chat UI is live at http://localhost:${this.port}
34710
- ${colors.dim(`logs: ${logPath} \xB7 stop: m8t bootstrap ui --stop`)}
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 Command63, Option as Option60 } from "clipanion";
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 = Command63.Usage({
34730
- description: "Seed your advisors' brains with the founder + company profile from the onboarding intake.",
34731
- details: "Reads the latest onboarding conversation, renders memory/founder.md + memory/company-profile.md (+ their MEMORY.md index lines), and commits them to both <org>/stacey-brain and <org>/ezra-brain via the GitHub App. Idempotent. --watch polls until the founder finishes the intake.",
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 = Option60.String("--endpoint", { description: "Override the Foundry endpoint (else read from the install status)." });
34738
- brain = Option60.String("--brain", { description: "Seed only this one brain repo, instead of both <org>/stacey-brain and <org>/ezra-brain." });
34739
- watch = Option60.Boolean("--watch", false, { description: "Poll until the intake completes (or --timeout)." });
34740
- timeout = Option60.String("--timeout", { description: "Watch timeout in minutes (default 20)." });
34741
- githubAppCreds = Option60.String("--github-app-creds");
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 sleep5 = (ms) => new Promise((r) => setTimeout(r, ms));
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
- const why = hadIntake ? "intake not complete yet \u2014 no m8t_onboarding block found." : "no onboarding intake found.";
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(why)}
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 sleep5(2e4);
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 Command64, Option as Option61 } from "clipanion";
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 sleep4 = (ms) => new Promise((r) => setTimeout(r, ms));
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 sleep4(retryDelayMs * attempt);
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 = Command64.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 = Option61.String("--company", { description: "Share your company name with m8t support. Not sent unless you pass it." });
34895
- contactEmail = Option61.String("--contact-email", { description: "Share a contact email with m8t support. Not sent unless you pass it." });
34896
- subscription = Option61.String("--subscription", { description: "Azure subscription id used to find your deployment. Never sent to m8t." });
34897
- resourceGroup = Option61.String("--resource-group", { description: "Resource group to disambiguate discovery, if you have more than one m8t deployment." });
34898
- force = Option61.Boolean("--force", false, { description: "Enroll even when a key is already stored. Use only when m8t support asks you to." });
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 Command65, Option as Option62 } from "clipanion";
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 isRecord3(value) {
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 (!isRecord3(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) {
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 (!isRecord3(option) || !hasExactKeys(option, ["label", "detail"]) || !isBoundedPlainString(option.label, COMPANION_DECISION_LABEL_MAX_LENGTH) || !isBoundedMessageText(option.detail, COMPANION_DECISION_DETAIL_MAX_CODE_POINTS)) {
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 (!isRecord3(value) || !isBoundedPlainString(value.name, COMPANION_ARTIFACT_NAME_MAX_LENGTH)) {
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 (!isRecord3(value) || !isBoundedPlainString(value.id, COMPANION_TURN_ID_MAX_LENGTH) || value.role !== "user" && value.role !== "mate" || !isInstantOrNull(value.at)) {
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 (!isRecord3(value)) return requestError();
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 (!isRecord3(value) || !hasExactKeys(value, [
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 (!isRecord3(value)) return eventError();
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 isRecord4(value) {
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 (!isRecord4(envelope) || envelope.ok !== true || !("data" in envelope)) {
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 (!isRecord4(data) || !Array.isArray(data.agents)) {
34816
+ if (!isRecord6(data) || !Array.isArray(data.agents)) {
35439
34817
  throw new PredispatchFailure("mate-unavailable");
35440
34818
  }
35441
- return data.agents.filter(isRecord4);
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 (!isRecord4(data) || typeof data.id !== "string" || !OPAQUE_ID.test(data.id)) {
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 (!isRecord4(data) || !Array.isArray(data.messages)) {
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 (!isRecord4(last) || typeof last.id !== "string" || !OPAQUE_ID.test(last.id)) {
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 (!isRecord4(entry) || typeof entry.name !== "string") continue;
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 (!isRecord4(data) || !Array.isArray(data.messages)) {
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 (!isRecord4(entry)) continue;
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 (!isRecord4(data) || !Array.isArray(data.messages)) {
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 (!isRecord4(entry)) continue;
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 (!isRecord4(parsed) || typeof parsed.type !== "string" || parsed.type === "error") {
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 (!isRecord4(parsed) || typeof parsed.type !== "string" || parsed.type === "error") {
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 (isRecord4(envelope) && isRecord4(envelope.error) && isRecord4(envelope.error.details)) {
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 Command65 {
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 = Option62.Boolean("--serve", false);
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 Command66 } from "clipanion";
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 = Command66.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 Command67, Option as Option63 } from "clipanion";
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 = Command67.Usage({
35912
+ static usage = Command68.Usage({
36535
35913
  description: "Restore the desktop companions and start-at-login state."
36536
35914
  });
36537
- resourceGroup = Option63.String("--resource-group", {
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 Command68 } from "clipanion";
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 = Command68.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);