@m8t-stack/cli 0.2.84 → 0.2.86

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 +822 -1418
  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.86";
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";
@@ -22048,6 +22043,13 @@ async function buildConvergeDeps(args) {
22048
22043
  const target = infraTargetSha(ctx.manifest, args.tree);
22049
22044
  const infraTable = await openInfraParamsTable({ credential: args.credential, subscriptionId: args.subscriptionId, resourceGroup: args.resourceGroup });
22050
22045
  const stamped = (await readInfraParams(infraTable))?.bicep;
22046
+ if (!stamped) {
22047
+ throw new LocalCliError({
22048
+ code: "PLATFORM_INFRA_PARAMS_MISSING",
22049
+ message: "This install has no stamped system/infra-params row, so the updater cannot converge infra: the row is the only place the real tenantId/clientId/Foundry endpoint survive, and they cannot be reconstructed from scratch.",
22050
+ hint: "Run `m8t platform enable-auto-update` against this deployment (it captures the full bicep parameter set), then let the next tick retry."
22051
+ });
22052
+ }
22051
22053
  args.onProgress?.("subscription-scoped modules skipped: updater is RG-scoped; sub-level changes require a founder-run CLI converge.");
22052
22054
  const gatewayName = args.suffix ? `m8t-gateway-${args.suffix}` : void 0;
22053
22055
  const voiceInternalSecret = gatewayName ? await readLiveVoiceInternalSecret({
@@ -22063,17 +22065,18 @@ async function buildConvergeDeps(args) {
22063
22065
  const opts = {
22064
22066
  suffix: args.suffix,
22065
22067
  assignSubscriptionRoles: false,
22066
- ...stamped ? {
22067
- tenantId: stamped.tenantId,
22068
- clientId: stamped.clientId,
22069
- foundryEndpoint: stamped.foundryProjectEndpoint,
22070
- foundryResourceId: stamped.foundryResourceId,
22071
- acrPullIdentity: stamped.acrPullIdentityResourceId,
22072
- acrResourceId: stamped.acrResourceId,
22073
- ...stamped.foundryTracingMode ? { foundryTracingMode: stamped.foundryTracingMode } : {},
22074
- ...stamped.channelUrl ? { channelUrl: stamped.channelUrl } : {},
22075
- ...stamped.location ? { location: stamped.location } : {}
22076
- } : {},
22068
+ // Unconditional: the guard above has already refused an absent row. The
22069
+ // ternary that used to be here read as "these are optional" — which is
22070
+ // how the empty-endpoint death got written in the first place.
22071
+ tenantId: stamped.tenantId,
22072
+ clientId: stamped.clientId,
22073
+ foundryEndpoint: stamped.foundryProjectEndpoint,
22074
+ foundryResourceId: stamped.foundryResourceId,
22075
+ acrPullIdentity: stamped.acrPullIdentityResourceId,
22076
+ acrResourceId: stamped.acrResourceId,
22077
+ ...stamped.foundryTracingMode ? { foundryTracingMode: stamped.foundryTracingMode } : {},
22078
+ ...stamped.channelUrl ? { channelUrl: stamped.channelUrl } : {},
22079
+ ...stamped.location ? { location: stamped.location } : {},
22077
22080
  ...voiceInternalSecret ? { voiceInternalSecret } : {}
22078
22081
  };
22079
22082
  return applyInfraDeploy(a, ctx, target, opts);
@@ -22084,8 +22087,8 @@ async function buildConvergeDeps(args) {
22084
22087
  if (a.component === "personas" && a.personaName) {
22085
22088
  agentName = discovered[a.personaName];
22086
22089
  } else if (a.component === "codingAgent" || a.component === "azureExecutor") {
22087
- const dir = HOSTED_AGENT_PERSONA_DIR[a.component];
22088
- agentName = dir ? discovered[dir] : void 0;
22090
+ const dir2 = HOSTED_AGENT_PERSONA_DIR[a.component];
22091
+ agentName = dir2 ? discovered[dir2] : void 0;
22089
22092
  }
22090
22093
  if (!agentName) continue;
22091
22094
  await awaitAgentQueryable({ credential: args.credential, projectEndpoint: args.project.endpoint, agentName, onProgress: args.onProgress });
@@ -22624,7 +22627,21 @@ async function rollbackOrHold(args, notify, error) {
22624
22627
  return "held";
22625
22628
  }
22626
22629
  if (rollbackTarget) {
22627
- await reconvergeToPrevious(args, rollbackTarget);
22630
+ try {
22631
+ await reconvergeToPrevious(args, rollbackTarget);
22632
+ } catch (e) {
22633
+ const clip = (s) => s.length > 4e3 ? `${s.slice(0, 4e3)} \u2026[truncated]` : s;
22634
+ const combined = `${clip(error)} \u2014 and the rollback to ${rollbackTarget} failed: ${clip(e.message)} \u2014 the rollback may have partially applied; check the installed stamp's components before retrying.`;
22635
+ await patchApplyRequest(args.client, (r) => ({
22636
+ ...r,
22637
+ status: "held",
22638
+ breaker: "held",
22639
+ result: { appliedVersion: r.result?.appliedVersion ?? null, error: combined },
22640
+ updatedAt: args.now().toISOString()
22641
+ }));
22642
+ await notify({ outcome: "held", target, error: combined });
22643
+ return "held";
22644
+ }
22628
22645
  }
22629
22646
  await patchApplyRequest(args.client, (r) => ({
22630
22647
  ...r,
@@ -22865,6 +22882,9 @@ var PlatformConvergeCommand = class extends M8tCommand {
22865
22882
  channelUrl: ctx.channelUrl,
22866
22883
  repoRoot,
22867
22884
  now: () => /* @__PURE__ */ new Date()
22885
+ }).catch(async (e) => {
22886
+ await failIfStillInFlight(client, now, e.message);
22887
+ throw e;
22868
22888
  });
22869
22889
  log(`converge outcome: ${outcome}.`);
22870
22890
  return outcome === "success" ? 0 : 1;
@@ -27361,9 +27381,9 @@ function isAbsentCursorError(e) {
27361
27381
  const anyE = e;
27362
27382
  return anyE.statusCode === 404 || anyE.code === "ResourceNotFound" || anyE.code === "TableNotFound" || anyE.details?.errorCode === "TableNotFound" || anyE.details?.errorCode === "ResourceNotFound";
27363
27383
  }
27364
- function absentCursor(canonical) {
27384
+ function absentCursor(canonical2) {
27365
27385
  return {
27366
- worker: canonical,
27386
+ worker: canonical2,
27367
27387
  watermarks: {},
27368
27388
  safetyLagSeconds: DEFAULT_SAFETY_LAG_SECONDS,
27369
27389
  engineVersion: ENGINE_VERSION
@@ -27382,19 +27402,19 @@ var TableCursor = class {
27382
27402
  * Any other error (e.g. 403 RBAC) propagates raw.
27383
27403
  */
27384
27404
  async read(worker) {
27385
- const canonical = canonicalWorker(worker);
27405
+ const canonical2 = canonicalWorker(worker);
27386
27406
  let row;
27387
27407
  try {
27388
- row = await this.client.getEntity(CURSOR_PARTITION_KEY, canonical);
27408
+ row = await this.client.getEntity(CURSOR_PARTITION_KEY, canonical2);
27389
27409
  } catch (e) {
27390
27410
  if (isAbsentCursorError(e))
27391
- return absentCursor(canonical);
27411
+ return absentCursor(canonical2);
27392
27412
  throw e;
27393
27413
  }
27394
27414
  const watermarks = row.watermarks ? JSON.parse(row.watermarks) : {};
27395
27415
  const fetchFailures = row.fetchFailures ? JSON.parse(row.fetchFailures) : void 0;
27396
27416
  return {
27397
- worker: canonical,
27417
+ worker: canonical2,
27398
27418
  watermarks,
27399
27419
  ...fetchFailures && Object.keys(fetchFailures).length > 0 ? { fetchFailures } : {},
27400
27420
  safetyLagSeconds: row.safetyLagSeconds ?? DEFAULT_SAFETY_LAG_SECONDS,
@@ -27410,11 +27430,11 @@ var TableCursor = class {
27410
27430
  * (Table cells are scalar — same rationale as agent-ledger's `artifacts`).
27411
27431
  */
27412
27432
  async advance(worker, cursor) {
27413
- const canonical = canonicalWorker(worker);
27433
+ const canonical2 = canonicalWorker(worker);
27414
27434
  await this.ensureTable();
27415
27435
  const entity = {
27416
27436
  partitionKey: CURSOR_PARTITION_KEY,
27417
- rowKey: canonical,
27437
+ rowKey: canonical2,
27418
27438
  watermarks: JSON.stringify(cursor.watermarks),
27419
27439
  safetyLagSeconds: cursor.safetyLagSeconds,
27420
27440
  engineVersion: cursor.engineVersion
@@ -27499,10 +27519,10 @@ function formatCursorPreview(cursor) {
27499
27519
 
27500
27520
  // ../../packages/brain/engine/dist/esm/ledger-source.js
27501
27521
  function physicalPksFor(worker) {
27502
- const canonical = canonicalWorker(worker);
27503
- const pks = /* @__PURE__ */ new Set([canonical]);
27522
+ const canonical2 = canonicalWorker(worker);
27523
+ const pks = /* @__PURE__ */ new Set([canonical2]);
27504
27524
  for (const [physical, mapped] of Object.entries(WORKER_ALIASES)) {
27505
- if (mapped === canonical)
27525
+ if (mapped === canonical2)
27506
27526
  pks.add(physical);
27507
27527
  }
27508
27528
  return [...pks];
@@ -27590,7 +27610,7 @@ function mapItems(raw) {
27590
27610
  return acc.reverse();
27591
27611
  }
27592
27612
  function makeConversationSource(client, opts = {}) {
27593
- const sleep5 = opts.sleep ?? defaultSleep;
27613
+ const sleep4 = opts.sleep ?? defaultSleep;
27594
27614
  return {
27595
27615
  async items(conversationId) {
27596
27616
  for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
@@ -27608,7 +27628,7 @@ function makeConversationSource(client, opts = {}) {
27608
27628
  throw new ConvFetchError("auth", `auth failed reading ${conversationId}`);
27609
27629
  const retryable = status === void 0 || status === TRANSIENT;
27610
27630
  if (retryable && attempt < MAX_ATTEMPTS - 1) {
27611
- await sleep5(attempt);
27631
+ await sleep4(attempt);
27612
27632
  continue;
27613
27633
  }
27614
27634
  throw new ConvFetchError("terminal", `exhausted ${String(MAX_ATTEMPTS)} retries reading ${conversationId}: ${e?.message ?? String(e)}`);
@@ -28412,7 +28432,7 @@ function extractJson(text) {
28412
28432
  return JSON.parse(candidate2.slice(start, end + 1));
28413
28433
  }
28414
28434
  async function propose(model, system, user, opts = {}) {
28415
- const sleep5 = opts.sleep ?? defaultSleep2;
28435
+ const sleep4 = opts.sleep ?? defaultSleep2;
28416
28436
  const modelName = opts.model ?? MODEL_NAME_FALLBACK;
28417
28437
  let inputTokens = 0;
28418
28438
  let outputTokens = 0;
@@ -28428,7 +28448,7 @@ async function propose(model, system, user, opts = {}) {
28428
28448
  } catch (e) {
28429
28449
  lastErr = `parse: ${e.message}`;
28430
28450
  if (attempt < MAX_MODEL_ATTEMPTS - 1) {
28431
- await sleep5(attempt);
28451
+ await sleep4(attempt);
28432
28452
  continue;
28433
28453
  }
28434
28454
  break;
@@ -28437,7 +28457,7 @@ async function propose(model, system, user, opts = {}) {
28437
28457
  if (!Array.isArray(rawDeltas)) {
28438
28458
  lastErr = "schema: top-level { deltas: [] } missing";
28439
28459
  if (attempt < MAX_MODEL_ATTEMPTS - 1) {
28440
- await sleep5(attempt);
28460
+ await sleep4(attempt);
28441
28461
  continue;
28442
28462
  }
28443
28463
  break;
@@ -29198,9 +29218,9 @@ async function discoverLedgerResources(opts) {
29198
29218
  }
29199
29219
 
29200
29220
  // 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];
29221
+ function physicalPksFor2(canonical2) {
29222
+ const aliases = Object.entries(WORKER_ALIASES).filter(([, c]) => c === canonical2).map(([physical]) => physical);
29223
+ return [canonical2, ...aliases];
29204
29224
  }
29205
29225
  function rgFromScope(scope) {
29206
29226
  const m = /\/resourceGroups\/([^/]+)/i.exec(scope);
@@ -29209,7 +29229,7 @@ function rgFromScope(scope) {
29209
29229
  }
29210
29230
  async function resolveDreamContext(opts) {
29211
29231
  const { credential: credential2 } = opts;
29212
- const canonical = opts.worker.toLowerCase();
29232
+ const canonical2 = opts.worker.toLowerCase();
29213
29233
  const account = await getAzAccount();
29214
29234
  const subscriptionId = opts.subscription ?? account.subscriptionId;
29215
29235
  const project = await resolveFoundryProject({
@@ -29226,11 +29246,11 @@ async function resolveDreamContext(opts) {
29226
29246
  resourceGroup
29227
29247
  });
29228
29248
  return {
29229
- worker: canonical,
29249
+ worker: canonical2,
29230
29250
  projectEndpoint: project.endpoint,
29231
29251
  ledgerTableEndpoint,
29232
29252
  workspaceId,
29233
- physicalPks: physicalPksFor2(canonical)
29253
+ physicalPks: physicalPksFor2(canonical2)
29234
29254
  };
29235
29255
  }
29236
29256
 
@@ -30832,7 +30852,7 @@ ${colors.error("\u2717")} The GitHub App on disk is installed on ${colors.field(
30832
30852
  // src/commands/bootstrap/launch.ts
30833
30853
  var DEFAULT_RG = "rg-m8t-stack";
30834
30854
  var DEFAULT_INSTALLER = "ghcr.io/m8t-labs/m8t-installer";
30835
- var DEFAULT_INSTALLER_TAG = "v0.1.62";
30855
+ var DEFAULT_INSTALLER_TAG = "v0.1.63";
30836
30856
  var ACI_NAME = "m8t-installer";
30837
30857
  var MI_NAME = "m8t-installer-mi";
30838
30858
  var BootstrapLaunchCommand = class extends M8tCommand {
@@ -31054,15 +31074,15 @@ async function getAciState(opts) {
31054
31074
  }
31055
31075
 
31056
31076
  // src/lib/bootstrap-finalize.ts
31057
- import * as fs38 from "fs/promises";
31058
- import * as os19 from "os";
31059
- import * as path43 from "path";
31077
+ import * as fs39 from "fs/promises";
31078
+ import * as os20 from "os";
31079
+ import * as path44 from "path";
31060
31080
 
31061
31081
  // src/lib/company-profile-seed.ts
31062
31082
  import { spawn as spawn6 } from "child_process";
31063
31083
  import { closeSync, openSync, readFileSync as readFileSync24 } from "fs";
31064
- import * as os16 from "os";
31065
- import * as path38 from "path";
31084
+ import * as os17 from "os";
31085
+ import * as path39 from "path";
31066
31086
  init_errors();
31067
31087
 
31068
31088
  // src/lib/onboarding-profile.ts
@@ -31497,7 +31517,11 @@ function renderCompanyProfile(block, now = (/* @__PURE__ */ new Date()).toISOStr
31497
31517
  ``,
31498
31518
  `# Company profile`,
31499
31519
  ``,
31500
- `_Seeded from the onboarding questionnaire._`,
31520
+ // Named the questionnaire until the questionnaire stopped existing. There is no
31521
+ // intake conversation any more — `m8t bootstrap profile` confirms two contact
31522
+ // facts and nothing about the company — so the old line described a thing that
31523
+ // never happened to this founder.
31524
+ `_Seeded at install._`,
31501
31525
  ``,
31502
31526
  ...bullet("Company", block.company_name),
31503
31527
  ...bullet("Stage", block.company_stage),
@@ -31523,6 +31547,45 @@ function renderCompanyProfile(block, now = (/* @__PURE__ */ new Date()).toISOStr
31523
31547
  var FOUNDER_RECORD_PATH = "memory/founder.md";
31524
31548
  var NOT_CAPTURED = "_not captured yet \u2014 just tell me and I'll add it_";
31525
31549
  var REGION_UNKNOWN_AT_INTAKE = "_not known at intake \u2014 filled in when the request is filed_";
31550
+ var FOUNDER_BULLET_LABELS = {
31551
+ name: "- **Founder:** ",
31552
+ email: "- **Founder email (company_email):** ",
31553
+ advisor: "- **Microsoft Startup Advisor (SA):** ",
31554
+ subscription: "- **Azure subscription:** "
31555
+ };
31556
+ function updateFounderContacts(existing, block, inputs) {
31557
+ if (!existing.includes(FOUNDER_BULLET_LABELS.email)) return null;
31558
+ const advisorName = (block.advisor_name ?? "").trim();
31559
+ const advisorEmail = (block.advisor_email ?? "").trim();
31560
+ const advisor = inputs.advisorCleared === true ? renderAdvisor(advisorName, advisorEmail, NOT_CAPTURED) : renderAdvisor(advisorName, advisorEmail, "");
31561
+ const replacements = [];
31562
+ const add = (prefix, value) => {
31563
+ if (value) replacements.push([prefix, prefix + value]);
31564
+ };
31565
+ add(FOUNDER_BULLET_LABELS.name, (block.founder_name ?? "").trim());
31566
+ add(FOUNDER_BULLET_LABELS.email, (block.founder_email ?? "").trim());
31567
+ add(FOUNDER_BULLET_LABELS.advisor, advisor);
31568
+ add(FOUNDER_BULLET_LABELS.subscription, (inputs.subscriptionId ?? "").trim());
31569
+ return existing.split("\n").map((line2) => {
31570
+ const cr = line2.endsWith("\r") ? "\r" : "";
31571
+ const bare = cr ? line2.slice(0, -1) : line2;
31572
+ const hit = replacements.find(([prefix]) => bare.startsWith(prefix));
31573
+ return hit ? hit[1] + cr : line2;
31574
+ }).join("\n");
31575
+ }
31576
+ function renderAdvisor(name, email, absent) {
31577
+ if (!name && !email) return absent;
31578
+ return [name, email ? `<${email}>` : ""].filter(Boolean).join(" ");
31579
+ }
31580
+ function readAdvisorFromRecord(existing) {
31581
+ const line2 = existing.split("\n").map((l) => l.endsWith("\r") ? l.slice(0, -1) : l).find((l) => l.startsWith(FOUNDER_BULLET_LABELS.advisor));
31582
+ if (line2 === void 0) return null;
31583
+ const value = line2.slice(FOUNDER_BULLET_LABELS.advisor.length).trim();
31584
+ if (!value || value === NOT_CAPTURED) return null;
31585
+ const email = /<([^>]+)>/.exec(value)?.[1]?.trim() ?? "";
31586
+ const name = value.replace(/<[^>]*>/, "").trim();
31587
+ return name || email ? { name, email } : null;
31588
+ }
31526
31589
  function renderFounderRecord(block, inputs, now = (/* @__PURE__ */ new Date()).toISOString()) {
31527
31590
  const pick = (...vals) => vals.map((v) => v?.trim()).find(Boolean) ?? "";
31528
31591
  const founderName = pick(block.founder_name, inputs.azIdentity?.name);
@@ -31530,7 +31593,7 @@ function renderFounderRecord(block, inputs, now = (/* @__PURE__ */ new Date()).t
31530
31593
  const advisorName = (block.advisor_name ?? "").trim();
31531
31594
  const advisorEmail = (block.advisor_email ?? "").trim();
31532
31595
  const subscription = (inputs.subscriptionId ?? "").trim();
31533
- const advisorRendered = advisorName || advisorEmail ? [advisorName, advisorEmail ? `<${advisorEmail}>` : ""].filter(Boolean).join(" ") : NOT_CAPTURED;
31596
+ const advisorRendered = renderAdvisor(advisorName, advisorEmail, NOT_CAPTURED);
31534
31597
  const request = block.pending_requests?.[0];
31535
31598
  const requestLines2 = request === void 0 ? [] : (() => {
31536
31599
  const modelLabel = request.model.trim() ? `\`${request.model.trim()}\`` : "(model not recorded)";
@@ -31565,7 +31628,7 @@ function renderFounderRecord(block, inputs, now = (/* @__PURE__ */ new Date()).t
31565
31628
  ``,
31566
31629
  `# Founder & install context`,
31567
31630
  ``,
31568
- `_Seeded at onboarding from your Azure identity + the questionnaire. Authoritative \u2014 read it; don't rewrite it (origin: operator)._`,
31631
+ `_Seeded at install from your Azure identity + what you confirmed. Authoritative \u2014 read it; don't rewrite it (origin: operator)._`,
31569
31632
  ``,
31570
31633
  `- **Founder:** ${founderName || NOT_CAPTURED}`,
31571
31634
  `- **Founder email (company_email):** ${founderEmail || NOT_CAPTURED}`,
@@ -31581,168 +31644,7 @@ function renderFounderRecord(block, inputs, now = (/* @__PURE__ */ new Date()).t
31581
31644
  return { founderMd, memoryIndexLine };
31582
31645
  }
31583
31646
 
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
31647
  // 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
31648
  function deriveEmailCandidate(raw) {
31747
31649
  const mail = (raw.mail ?? "").trim();
31748
31650
  if (mail) return mail;
@@ -31773,21 +31675,80 @@ async function getSignedInUserIdentity(runAzImpl = runAz) {
31773
31675
  return { name: "", email: "" };
31774
31676
  }
31775
31677
  }
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.`;
31678
+
31679
+ // src/lib/onboarding-profile-store.ts
31680
+ import * as fs35 from "fs/promises";
31681
+ import * as os16 from "os";
31682
+ import * as path38 from "path";
31683
+ var ONBOARDING_PROFILE_FILE = "onboarding-profile.json";
31684
+ function dir(home) {
31685
+ return path38.join(home, ".m8t");
31686
+ }
31687
+ function file(home) {
31688
+ return path38.join(dir(home), ONBOARDING_PROFILE_FILE);
31689
+ }
31690
+ function isRecord3(value) {
31691
+ return typeof value === "object" && value !== null && !Array.isArray(value);
31692
+ }
31693
+ function nonBlankString(value) {
31694
+ return typeof value === "string" && value.trim().length > 0;
31695
+ }
31696
+ function canonical(value) {
31697
+ if (!isRecord3(value)) return null;
31698
+ if (value.schemaVersion !== 1) return null;
31699
+ if (!nonBlankString(value.founderEmail)) return null;
31700
+ if (typeof value.founderName !== "string") return null;
31701
+ if (typeof value.collectedAt !== "string") return null;
31702
+ if (value.advisor === void 0) return null;
31703
+ let advisor = null;
31704
+ if (value.advisor !== null) {
31705
+ if (!isRecord3(value.advisor)) return null;
31706
+ if (typeof value.advisor.name !== "string" || typeof value.advisor.email !== "string") return null;
31707
+ advisor = { name: value.advisor.name, email: value.advisor.email };
31781
31708
  }
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).`;
31709
+ return {
31710
+ schemaVersion: 1,
31711
+ collectedAt: value.collectedAt,
31712
+ founderName: value.founderName,
31713
+ founderEmail: value.founderEmail,
31714
+ advisor,
31715
+ ...value.advisorCleared === true ? { advisorCleared: true } : {}
31716
+ };
31717
+ }
31718
+ async function readOnboardingProfile(home = os16.homedir()) {
31719
+ let raw;
31720
+ try {
31721
+ raw = await fs35.readFile(file(home), "utf8");
31722
+ } catch {
31723
+ return null;
31724
+ }
31725
+ try {
31726
+ return canonical(JSON.parse(raw));
31727
+ } catch {
31728
+ return null;
31784
31729
  }
31785
- return `Ask the founder their name and the best contact email to reach them (that's where advisor handoffs and cost reports go).`;
31730
+ }
31731
+ async function writeOnboardingProfile(profile, home = os16.homedir()) {
31732
+ await fs35.mkdir(dir(home), { recursive: true });
31733
+ const target = file(home);
31734
+ const tmp = `${target}.tmp`;
31735
+ await fs35.writeFile(tmp, JSON.stringify(profile, null, 2), { encoding: "utf8", mode: 384 });
31736
+ await fs35.rename(tmp, target);
31737
+ }
31738
+ function toOnboardingBlock(profile) {
31739
+ return {
31740
+ schema_version: "3",
31741
+ context: "",
31742
+ founder_name: profile.founderName,
31743
+ founder_email: profile.founderEmail,
31744
+ advisor_name: profile.advisor?.name ?? "",
31745
+ advisor_email: profile.advisor?.email ?? ""
31746
+ };
31786
31747
  }
31787
31748
 
31788
31749
  // src/lib/company-profile-seed.ts
31789
31750
  function readGithubAppCreds(credsPath) {
31790
- const p = credsPath ?? path38.join(os16.homedir(), ".m8t", "github-app.json");
31751
+ const p = credsPath ?? path39.join(os17.homedir(), ".m8t", "github-app.json");
31791
31752
  try {
31792
31753
  return JSON.parse(readFileSync24(p, "utf8"));
31793
31754
  } catch {
@@ -31862,12 +31823,8 @@ async function applyProfileToBrain(args) {
31862
31823
  installationId: args.appCreds.installationId,
31863
31824
  fetchImpl: args.fetchImpl
31864
31825
  });
31826
+ const contactsOnly = args.contactsOnly === true;
31865
31827
  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
31828
  const read = (p) => readRepoFileViaApp({
31872
31829
  token,
31873
31830
  repo: args.brainRepo,
@@ -31875,13 +31832,25 @@ async function applyProfileToBrain(args) {
31875
31832
  ref: args.branch,
31876
31833
  fetchImpl: args.fetchImpl
31877
31834
  });
31878
- const existingProfile = await read(COMPANY_PROFILE_PATH);
31835
+ const existingProfile = contactsOnly ? null : await read(COMPANY_PROFILE_PATH);
31879
31836
  const existingFounder = await read(FOUNDER_RECORD_PATH);
31880
31837
  const existingIndex = await read(MEMORY_INDEX_PATH) ?? DEFAULT_MEMORY_INDEX_HEADER;
31881
- let nextIndex = upsertMemoryIndexOnce(existingIndex, companyLine, COMPANY_PROFILE_PATH);
31838
+ const keepsExistingAdvisor = contactsOnly && existingFounder !== null && args.advisorCleared !== true && !args.block.advisor_name.trim() && !args.block.advisor_email.trim();
31839
+ const recovered = keepsExistingAdvisor ? readAdvisorFromRecord(existingFounder) : null;
31840
+ const effectiveBlock = recovered ? { ...args.block, advisor_name: recovered.name, advisor_email: recovered.email } : args.block;
31841
+ const { founderMd: freshFounderMd, memoryIndexLine: founderLine } = renderFounderRecord(
31842
+ effectiveBlock,
31843
+ { subscriptionId: args.subscriptionId, azIdentity: args.azIdentity },
31844
+ args.now
31845
+ );
31846
+ const founderMd = contactsOnly && existingFounder !== null ? updateFounderContacts(existingFounder, args.block, {
31847
+ subscriptionId: args.subscriptionId,
31848
+ advisorCleared: args.advisorCleared
31849
+ }) ?? freshFounderMd : freshFounderMd;
31850
+ let nextIndex = contactsOnly ? existingIndex : upsertMemoryIndexOnce(existingIndex, companyLine, COMPANY_PROFILE_PATH);
31882
31851
  nextIndex = upsertMemoryIndexOnce(nextIndex, founderLine, FOUNDER_RECORD_PATH);
31883
31852
  const files = [
31884
- ...!seededDocumentMatches(existingProfile, profileMd) ? [{ path: COMPANY_PROFILE_PATH, content: profileMd }] : [],
31853
+ ...!contactsOnly && !seededDocumentMatches(existingProfile, profileMd) ? [{ path: COMPANY_PROFILE_PATH, content: profileMd }] : [],
31885
31854
  ...!seededDocumentMatches(existingFounder, founderMd) ? [{ path: FOUNDER_RECORD_PATH, content: founderMd }] : [],
31886
31855
  ...nextIndex !== existingIndex ? [{ path: MEMORY_INDEX_PATH, content: nextIndex }] : []
31887
31856
  ];
@@ -31890,17 +31859,17 @@ async function applyProfileToBrain(args) {
31890
31859
  token,
31891
31860
  repo: args.brainRepo,
31892
31861
  branch: args.branch,
31893
- message: "seed(brain): founder + company profile from onboarding",
31862
+ message: contactsOnly ? "seed(brain): how to reach the founder" : "seed(brain): founder + company profile from onboarding",
31894
31863
  files,
31895
31864
  fetchImpl: args.fetchImpl
31896
31865
  });
31897
31866
  const [verifiedProfile, verifiedFounder, verifiedIndex] = await Promise.all([
31898
- read(COMPANY_PROFILE_PATH),
31867
+ contactsOnly ? Promise.resolve(null) : read(COMPANY_PROFILE_PATH),
31899
31868
  read(FOUNDER_RECORD_PATH),
31900
31869
  read(MEMORY_INDEX_PATH)
31901
31870
  ]);
31902
31871
  const mismatches = [
31903
- ...!seededDocumentMatches(verifiedProfile, profileMd) ? [COMPANY_PROFILE_PATH] : [],
31872
+ ...!contactsOnly && !seededDocumentMatches(verifiedProfile, profileMd) ? [COMPANY_PROFILE_PATH] : [],
31904
31873
  ...!seededDocumentMatches(verifiedFounder, founderMd) ? [FOUNDER_RECORD_PATH] : [],
31905
31874
  ...verifiedIndex !== nextIndex ? [MEMORY_INDEX_PATH] : []
31906
31875
  ];
@@ -31948,7 +31917,7 @@ async function applyProfileToBrains(args) {
31948
31917
  }
31949
31918
  }
31950
31919
  function spawnDetachedSeedWatch() {
31951
- const logPath = path38.join(os16.homedir(), ".m8t", "seed-profile.log");
31920
+ const logPath = path39.join(os17.homedir(), ".m8t", "seed-profile.log");
31952
31921
  const fd = openSync(logPath, "a");
31953
31922
  try {
31954
31923
  const child = spawn6(process.execPath, [process.argv[1] ?? "", "bootstrap", "seed-profile", "--watch"], {
@@ -31960,6 +31929,8 @@ function spawnDetachedSeedWatch() {
31960
31929
  closeSync(fd);
31961
31930
  }
31962
31931
  }
31932
+ var NO_PROFILE_LINE = `${colors.dim("\u2139 Your advisors don't know how to reach you yet \u2014 run 'm8t bootstrap profile'.")}
31933
+ `;
31963
31934
  async function reactiveSeedOnInstallComplete(args) {
31964
31935
  const ctx = await resolveSeedContext({
31965
31936
  endpointOverride: args.endpoint,
@@ -31968,6 +31939,23 @@ async function reactiveSeedOnInstallComplete(args) {
31968
31939
  brainsOverride: args.brains
31969
31940
  });
31970
31941
  if (!ctx) return;
31942
+ const local = await readOnboardingProfile(args.home);
31943
+ if (local) {
31944
+ await applyProfileToBrains({
31945
+ block: toOnboardingBlock(local),
31946
+ brainRepos: ctx.brainRepos,
31947
+ branch: "main",
31948
+ appCreds: ctx.appCreds,
31949
+ subscriptionId: ctx.subscriptionId,
31950
+ azIdentity: { name: local.founderName, email: local.founderEmail },
31951
+ contactsOnly: true,
31952
+ advisorCleared: local.advisorCleared === true,
31953
+ fetchImpl: args.fetchImpl
31954
+ });
31955
+ args.stdout(`${colors.success("\u2713")} Your advisors now know how to reach you (seeded ${ctx.brainRepos.join(", ")}).
31956
+ `);
31957
+ return;
31958
+ }
31971
31959
  const token = await (args.getFoundryTokenImpl ?? getFoundryToken)();
31972
31960
  const { hadIntake, block } = await findOnboardingProfile({ endpoint: ctx.endpoint, token, fetchImpl: args.fetchImpl });
31973
31961
  if (block) {
@@ -31985,7 +31973,10 @@ async function reactiveSeedOnInstallComplete(args) {
31985
31973
  `);
31986
31974
  return;
31987
31975
  }
31988
- if (!hadIntake) return;
31976
+ if (!hadIntake) {
31977
+ args.stdout(NO_PROFILE_LINE);
31978
+ return;
31979
+ }
31989
31980
  (args.spawnWatch ?? spawnDetachedSeedWatch)();
31990
31981
  args.stdout(
31991
31982
  `${colors.dim("\u2139 Your advisors will pick up your company profile when you finish the intake (watching in the background).")}
@@ -32026,16 +32017,16 @@ function renderInstallSummary(args) {
32026
32017
 
32027
32018
  // src/lib/companion-install.ts
32028
32019
  import { constants as constants2 } from "fs";
32029
- import * as fs36 from "fs/promises";
32030
- import * as path40 from "path";
32020
+ import * as fs37 from "fs/promises";
32021
+ import * as path41 from "path";
32031
32022
  import { randomUUID as randomUUID3 } from "crypto";
32032
32023
  import { execFile, spawn as spawn7 } from "child_process";
32033
32024
 
32034
32025
  // src/lib/companion-artifact.ts
32035
32026
  import { createHash as createHash7 } from "crypto";
32036
32027
  import { constants } from "fs";
32037
- import * as fs35 from "fs/promises";
32038
- import * as path39 from "path";
32028
+ import * as fs36 from "fs/promises";
32029
+ import * as path40 from "path";
32039
32030
  var SHA256 = /^[a-f0-9]{64}$/u;
32040
32031
  var VERSION2 = /^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$/u;
32041
32032
  function exactKeys(value, expected) {
@@ -32043,23 +32034,23 @@ function exactKeys(value, expected) {
32043
32034
  return actual.length === expected.length && actual.every((key2, index) => key2 === [...expected].sort()[index]);
32044
32035
  }
32045
32036
  function normalizedRelative(value) {
32046
- if (value.length === 0 || value.includes("\\") || value.includes("\0") || path39.posix.isAbsolute(value)) {
32037
+ if (value.length === 0 || value.includes("\\") || value.includes("\0") || path40.posix.isAbsolute(value)) {
32047
32038
  throw new Error("Artifact path must be a normalized relative POSIX path");
32048
32039
  }
32049
- const normalized = path39.posix.normalize(value);
32040
+ const normalized = path40.posix.normalize(value);
32050
32041
  if (normalized !== value || normalized === "." || normalized === ".." || normalized.startsWith("../")) {
32051
32042
  throw new Error("Artifact path escapes the payload root");
32052
32043
  }
32053
32044
  return value;
32054
32045
  }
32055
32046
  function validateLink(entryPath, target) {
32056
- if (target.length === 0 || target.includes("\\") || target.includes("\0") || path39.posix.isAbsolute(target)) {
32047
+ if (target.length === 0 || target.includes("\\") || target.includes("\0") || path40.posix.isAbsolute(target)) {
32057
32048
  throw new Error("Artifact symlink target must be relative");
32058
32049
  }
32059
- const resolved = path39.posix.normalize(
32060
- path39.posix.join(path39.posix.dirname(entryPath), target)
32050
+ const resolved = path40.posix.normalize(
32051
+ path40.posix.join(path40.posix.dirname(entryPath), target)
32061
32052
  );
32062
- if (resolved === ".." || resolved.startsWith("../") || path39.posix.isAbsolute(resolved)) {
32053
+ if (resolved === ".." || resolved.startsWith("../") || path40.posix.isAbsolute(resolved)) {
32063
32054
  throw new Error("Artifact symlink target escapes the payload root");
32064
32055
  }
32065
32056
  return target;
@@ -32140,17 +32131,17 @@ function parseArtifactManifest(value) {
32140
32131
  };
32141
32132
  }
32142
32133
  async function sha256File2(filePath) {
32143
- return createHash7("sha256").update(await fs35.readFile(filePath)).digest("hex");
32134
+ return createHash7("sha256").update(await fs36.readFile(filePath)).digest("hex");
32144
32135
  }
32145
32136
  async function walk2(root, relative4 = "") {
32146
- const directory = path39.join(root, ...relative4.split("/").filter(Boolean));
32147
- const children = await fs35.readdir(directory, { withFileTypes: true });
32137
+ const directory = path40.join(root, ...relative4.split("/").filter(Boolean));
32138
+ const children = await fs36.readdir(directory, { withFileTypes: true });
32148
32139
  const entries = [];
32149
32140
  for (const child of children.sort((left, right) => left.name.localeCompare(right.name))) {
32150
32141
  const childRelative = relative4 ? `${relative4}/${child.name}` : child.name;
32151
32142
  normalizedRelative(childRelative);
32152
- const childPath = path39.join(directory, child.name);
32153
- const stat5 = await fs35.lstat(childPath);
32143
+ const childPath = path40.join(directory, child.name);
32144
+ const stat5 = await fs36.lstat(childPath);
32154
32145
  if (stat5.isDirectory()) {
32155
32146
  entries.push(...await walk2(root, childRelative));
32156
32147
  } else if (stat5.isFile()) {
@@ -32162,7 +32153,7 @@ async function walk2(root, relative4 = "") {
32162
32153
  sha256: await sha256File2(childPath)
32163
32154
  });
32164
32155
  } else if (stat5.isSymbolicLink()) {
32165
- const target = await fs35.readlink(childPath);
32156
+ const target = await fs36.readlink(childPath);
32166
32157
  entries.push({
32167
32158
  type: "symlink",
32168
32159
  path: childRelative,
@@ -32175,19 +32166,19 @@ async function walk2(root, relative4 = "") {
32175
32166
  return entries;
32176
32167
  }
32177
32168
  async function ensureRealDirectory(root) {
32178
- const stat5 = await fs35.lstat(root);
32169
+ const stat5 = await fs36.lstat(root);
32179
32170
  if (stat5.isSymbolicLink()) throw new Error("Artifact root is a symbolic link");
32180
32171
  if (!stat5.isDirectory()) throw new Error("Artifact root is not a directory");
32181
32172
  }
32182
32173
  async function validateResolvedLinks(root, entries) {
32183
- const realRoot = await fs35.realpath(root);
32174
+ const realRoot = await fs36.realpath(root);
32184
32175
  for (const entry of entries) {
32185
32176
  if (entry.type !== "symlink") continue;
32186
32177
  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)) {
32178
+ const linkPath = path40.join(root, ...entry.path.split("/"));
32179
+ const resolved = await fs36.realpath(linkPath);
32180
+ const relative4 = path40.relative(realRoot, resolved);
32181
+ if (relative4 === ".." || relative4.startsWith(`..${path40.sep}`) || path40.isAbsolute(relative4)) {
32191
32182
  throw new Error("Artifact symlink chain escapes the payload root");
32192
32183
  }
32193
32184
  } catch (error) {
@@ -32217,13 +32208,13 @@ async function buildArtifactManifest(payloadRoot, input) {
32217
32208
  return manifest;
32218
32209
  }
32219
32210
  async function readArtifactManifest(manifestPath) {
32220
- const before = await fs35.lstat(manifestPath);
32211
+ const before = await fs36.lstat(manifestPath);
32221
32212
  if (before.isSymbolicLink() || !before.isFile()) {
32222
32213
  throw new Error("Artifact manifest is a symbolic link or non-file");
32223
32214
  }
32224
32215
  let handle;
32225
32216
  try {
32226
- handle = await fs35.open(
32217
+ handle = await fs36.open(
32227
32218
  manifestPath,
32228
32219
  constants.O_RDONLY | constants.O_NOFOLLOW
32229
32220
  );
@@ -32240,9 +32231,9 @@ async function readArtifactManifest(manifestPath) {
32240
32231
  }
32241
32232
  }
32242
32233
  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)) {
32234
+ const absolute = path40.join(root, ...entryPath.split("/"));
32235
+ const relative4 = path40.relative(root, absolute);
32236
+ if (relative4.startsWith("..") || path40.isAbsolute(relative4)) {
32246
32237
  throw new Error("Artifact entry escapes payload root");
32247
32238
  }
32248
32239
  return absolute;
@@ -32281,27 +32272,27 @@ async function verifyArtifactPayload(payloadRoot, manifest) {
32281
32272
  async function copyArtifactPayload(sourceRoot, targetRoot, manifest) {
32282
32273
  await verifyArtifactSource(sourceRoot, manifest);
32283
32274
  try {
32284
- const targetStat = await fs35.lstat(targetRoot);
32275
+ const targetStat = await fs36.lstat(targetRoot);
32285
32276
  if (targetStat.isSymbolicLink()) {
32286
32277
  throw new Error("Install target is a symbolic link");
32287
32278
  }
32288
32279
  if (!targetStat.isDirectory()) throw new Error("Install target is not a directory");
32289
- if ((await fs35.readdir(targetRoot)).length > 0) {
32280
+ if ((await fs36.readdir(targetRoot)).length > 0) {
32290
32281
  throw new Error("Install staging target is not empty");
32291
32282
  }
32292
32283
  } catch (error) {
32293
32284
  if (error.code !== "ENOENT") throw error;
32294
- await fs35.mkdir(targetRoot, { recursive: false, mode: 448 });
32285
+ await fs36.mkdir(targetRoot, { recursive: false, mode: 448 });
32295
32286
  }
32296
32287
  for (const entry of manifest.entries) {
32297
32288
  const source = ensureContainedEntry(sourceRoot, entry.path);
32298
32289
  const target = ensureContainedEntry(targetRoot, entry.path);
32299
- await fs35.mkdir(path39.dirname(target), { recursive: true, mode: 448 });
32290
+ await fs36.mkdir(path40.dirname(target), { recursive: true, mode: 448 });
32300
32291
  if (entry.type === "file") {
32301
- await fs35.copyFile(source, target);
32302
- await fs35.chmod(target, entry.mode);
32292
+ await fs36.copyFile(source, target);
32293
+ await fs36.chmod(target, entry.mode);
32303
32294
  } else {
32304
- await fs35.symlink(entry.target, target);
32295
+ await fs36.symlink(entry.target, target);
32305
32296
  }
32306
32297
  }
32307
32298
  await verifyArtifactPayload(targetRoot, manifest);
@@ -32317,18 +32308,18 @@ function commandError(error, message) {
32317
32308
  if (code !== void 0) wrapped.code = code;
32318
32309
  return wrapped;
32319
32310
  }
32320
- function execFileAsync(file, args) {
32311
+ function execFileAsync(file2, args) {
32321
32312
  return new Promise((resolve6, reject) => {
32322
- execFile(file, [...args], { windowsHide: true }, (error) => {
32313
+ execFile(file2, [...args], { windowsHide: true }, (error) => {
32323
32314
  if (error) reject(commandError(error, "Login-item command failed"));
32324
32315
  else resolve6();
32325
32316
  });
32326
32317
  });
32327
32318
  }
32328
- function execFileOutput(file, args) {
32319
+ function execFileOutput(file2, args) {
32329
32320
  return new Promise((resolve6, reject) => {
32330
32321
  execFile(
32331
- file,
32322
+ file2,
32332
32323
  [...args],
32333
32324
  { windowsHide: true },
32334
32325
  (error, stdout) => {
@@ -32340,25 +32331,25 @@ function execFileOutput(file, args) {
32340
32331
  }
32341
32332
  async function setCompanionStartAtLogin(input) {
32342
32333
  if (input.platform === "darwin") {
32343
- const launchAgents = path40.join(input.homeDirectory, "Library", "LaunchAgents");
32344
- const registration = path40.join(
32334
+ const launchAgents = path41.join(input.homeDirectory, "Library", "LaunchAgents");
32335
+ const registration = path41.join(
32345
32336
  launchAgents,
32346
32337
  "com.m8t.companion.plist"
32347
32338
  );
32348
32339
  await assertNotSymlink(registration, "Start-at-login registration");
32349
32340
  if (!input.enabled) {
32350
- await fs36.rm(registration, { force: true });
32341
+ await fs37.rm(registration, { force: true });
32351
32342
  return;
32352
32343
  }
32353
- await fs36.mkdir(launchAgents, { recursive: true, mode: 448 });
32344
+ await fs37.mkdir(launchAgents, { recursive: true, mode: 448 });
32354
32345
  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
32346
  await atomicWriteText(registration, plist, 384);
32356
32347
  return;
32357
32348
  }
32358
32349
  if (input.platform === "win32") {
32359
32350
  const key2 = "HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
32360
- const run2 = input.runCommand ?? (async (file, args) => {
32361
- await execFileAsync(file, args);
32351
+ const run2 = input.runCommand ?? (async (file2, args) => {
32352
+ await execFileAsync(file2, args);
32362
32353
  return "";
32363
32354
  });
32364
32355
  if (input.enabled) {
@@ -32402,7 +32393,7 @@ async function setCompanionStartAtLogin(input) {
32402
32393
  }
32403
32394
  async function getCompanionStartAtLogin(input) {
32404
32395
  if (input.platform === "darwin") {
32405
- const registration = path40.join(
32396
+ const registration = path41.join(
32406
32397
  input.homeDirectory,
32407
32398
  "Library",
32408
32399
  "LaunchAgents",
@@ -32464,26 +32455,26 @@ function assertSupported(options) {
32464
32455
  }
32465
32456
  function companionInstallPaths(options) {
32466
32457
  assertSupported(options);
32467
- const companionState = path40.join(
32458
+ const companionState = path41.join(
32468
32459
  options.homeDirectory,
32469
32460
  ".m8t",
32470
32461
  "companion"
32471
32462
  );
32472
- const targetRoot = options.platform === "darwin" ? path40.join(
32463
+ const targetRoot = options.platform === "darwin" ? path41.join(
32473
32464
  options.homeDirectory,
32474
32465
  "Applications",
32475
32466
  "m8t Companion.app"
32476
- ) : path40.join(
32477
- options.localAppData ?? path40.join(options.homeDirectory, "AppData", "Local"),
32467
+ ) : path41.join(
32468
+ options.localAppData ?? path41.join(options.homeDirectory, "AppData", "Local"),
32478
32469
  "m8t",
32479
32470
  "companion",
32480
32471
  "app"
32481
32472
  );
32482
32473
  return {
32483
32474
  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")
32475
+ installManifest: path41.join(companionState, "install-v1.json"),
32476
+ runtimeBinding: path41.join(companionState, "runtime-v1.json"),
32477
+ preferences: path41.join(companionState, "preferences-v1.json")
32487
32478
  };
32488
32479
  }
32489
32480
  function exactKeys2(value, keys) {
@@ -32494,7 +32485,7 @@ function parseInstallManifest(value) {
32494
32485
  throw new Error("Install manifest schema is invalid");
32495
32486
  }
32496
32487
  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)) {
32488
+ 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
32489
  throw new Error("Install manifest schema is invalid");
32499
32490
  }
32500
32491
  return record;
@@ -32504,7 +32495,7 @@ function parseRuntimeBinding(value, platform) {
32504
32495
  throw new Error("Runtime binding schema is invalid");
32505
32496
  }
32506
32497
  const record = value;
32507
- const paths = platform === "win32" ? path40.win32 : path40.posix;
32498
+ const paths = platform === "win32" ? path41.win32 : path41.posix;
32508
32499
  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
32500
  throw new Error("Runtime binding schema is invalid");
32510
32501
  }
@@ -32529,7 +32520,7 @@ function validateGatewayOrigin(value) {
32529
32520
  }
32530
32521
  async function assertNotSymlink(filePath, kind) {
32531
32522
  try {
32532
- if ((await fs36.lstat(filePath)).isSymbolicLink()) {
32523
+ if ((await fs37.lstat(filePath)).isSymbolicLink()) {
32533
32524
  throw new Error(`${kind} is a symbolic link`);
32534
32525
  }
32535
32526
  } catch (error) {
@@ -32538,20 +32529,20 @@ async function assertNotSymlink(filePath, kind) {
32538
32529
  }
32539
32530
  }
32540
32531
  async function assertOwnedDirectoryChain(anchor, targetDirectory) {
32541
- const relative4 = path40.relative(anchor, targetDirectory);
32542
- if (relative4 === ".." || relative4.startsWith(`..${path40.sep}`) || path40.isAbsolute(relative4)) {
32532
+ const relative4 = path41.relative(anchor, targetDirectory);
32533
+ if (relative4 === ".." || relative4.startsWith(`..${path41.sep}`) || path41.isAbsolute(relative4)) {
32543
32534
  throw new Error("Companion owned directory escapes its trusted anchor");
32544
32535
  }
32545
- const segments = relative4.split(path40.sep).filter(Boolean);
32536
+ const segments = relative4.split(path41.sep).filter(Boolean);
32546
32537
  let current = anchor;
32547
- const anchorStatus = await fs36.lstat(anchor);
32538
+ const anchorStatus = await fs37.lstat(anchor);
32548
32539
  if (anchorStatus.isSymbolicLink() || !anchorStatus.isDirectory()) {
32549
32540
  throw new Error("Companion owned directory anchor is unsafe");
32550
32541
  }
32551
32542
  for (const segment of segments) {
32552
- current = path40.join(current, segment);
32543
+ current = path41.join(current, segment);
32553
32544
  try {
32554
- const status = await fs36.lstat(current);
32545
+ const status = await fs37.lstat(current);
32555
32546
  if (status.isSymbolicLink() || !status.isDirectory()) {
32556
32547
  throw new Error("Companion owned directory contains a symbolic link");
32557
32548
  }
@@ -32564,19 +32555,19 @@ async function assertOwnedDirectoryChain(anchor, targetDirectory) {
32564
32555
  async function assertOwnedParents(options, paths) {
32565
32556
  await assertOwnedDirectoryChain(
32566
32557
  options.homeDirectory,
32567
- path40.dirname(paths.installManifest)
32558
+ path41.dirname(paths.installManifest)
32568
32559
  );
32569
- const targetAnchor = options.platform === "win32" ? options.localAppData ?? path40.join(options.homeDirectory, "AppData", "Local") : options.homeDirectory;
32570
- await assertOwnedDirectoryChain(targetAnchor, path40.dirname(paths.targetRoot));
32560
+ const targetAnchor = options.platform === "win32" ? options.localAppData ?? path41.join(options.homeDirectory, "AppData", "Local") : options.homeDirectory;
32561
+ await assertOwnedDirectoryChain(targetAnchor, path41.dirname(paths.targetRoot));
32571
32562
  }
32572
32563
  async function readRegularText(filePath, maxBytes) {
32573
- const before = await fs36.lstat(filePath);
32564
+ const before = await fs37.lstat(filePath);
32574
32565
  if (before.isSymbolicLink() || !before.isFile()) {
32575
32566
  throw new Error("Companion state file is not a regular file");
32576
32567
  }
32577
32568
  let handle;
32578
32569
  try {
32579
- handle = await fs36.open(
32570
+ handle = await fs37.open(
32580
32571
  filePath,
32581
32572
  constants2.O_RDONLY | constants2.O_NOFOLLOW
32582
32573
  );
@@ -32599,27 +32590,27 @@ async function atomicWriteJson(filePath, value) {
32599
32590
  }
32600
32591
  async function atomicWriteText(filePath, contents, mode) {
32601
32592
  await assertNotSymlink(filePath, "Companion state file");
32602
- await fs36.mkdir(path40.dirname(filePath), {
32593
+ await fs37.mkdir(path41.dirname(filePath), {
32603
32594
  recursive: true,
32604
32595
  mode: 448
32605
32596
  });
32606
32597
  const temporary = `${filePath}.${randomUUID3()}.tmp`;
32607
32598
  try {
32608
- await fs36.writeFile(temporary, contents, {
32599
+ await fs37.writeFile(temporary, contents, {
32609
32600
  mode,
32610
32601
  flag: "wx"
32611
32602
  });
32612
- await fs36.rename(temporary, filePath);
32613
- await fs36.chmod(filePath, mode).catch(() => void 0);
32603
+ await fs37.rename(temporary, filePath);
32604
+ await fs37.chmod(filePath, mode).catch(() => void 0);
32614
32605
  } finally {
32615
- await fs36.rm(temporary, { force: true }).catch(() => void 0);
32606
+ await fs37.rm(temporary, { force: true }).catch(() => void 0);
32616
32607
  }
32617
32608
  }
32618
32609
  async function realRegularFile(filePath, executable) {
32619
- const real = await fs36.realpath(filePath);
32620
- const stat5 = await fs36.stat(real);
32610
+ const real = await fs37.realpath(filePath);
32611
+ const stat5 = await fs37.stat(real);
32621
32612
  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);
32613
+ await fs37.access(real, executable ? constants2.X_OK : constants2.R_OK);
32623
32614
  return real;
32624
32615
  }
32625
32616
  function defaultLaunch(executable) {
@@ -32635,7 +32626,7 @@ async function companionIsRunning(platform, executable) {
32635
32626
  if (platform !== "win32") return false;
32636
32627
  let handle;
32637
32628
  try {
32638
- handle = await fs36.open(executable, "r+");
32629
+ handle = await fs37.open(executable, "r+");
32639
32630
  } catch (error) {
32640
32631
  const code = error.code;
32641
32632
  if (code === "ENOENT") return false;
@@ -32716,7 +32707,7 @@ async function statusCompanion(options) {
32716
32707
  realRegularFile(runtime.nodeExecutable, options.platform !== "win32"),
32717
32708
  realRegularFile(runtime.cliEntry, false)
32718
32709
  ]);
32719
- const executable = path40.join(
32710
+ const executable = path41.join(
32720
32711
  paths.targetRoot,
32721
32712
  ...install.entryRelativePath.split("/")
32722
32713
  );
@@ -32749,7 +32740,7 @@ async function snapshotFile(filePath) {
32749
32740
  }
32750
32741
  async function restoreFile(filePath, bytes) {
32751
32742
  if (bytes === null) {
32752
- await fs36.rm(filePath, { force: true });
32743
+ await fs37.rm(filePath, { force: true });
32753
32744
  } else {
32754
32745
  await atomicWriteJson(filePath, JSON.parse(bytes.toString("utf8")));
32755
32746
  }
@@ -32764,8 +32755,8 @@ async function converge(options, force) {
32764
32755
  if (artifactManifest.platform !== options.platform || artifactManifest.architecture !== options.architecture) {
32765
32756
  throw new Error("Companion artifact does not match this OS and architecture");
32766
32757
  }
32767
- const payloadRoot = path40.join(
32768
- path40.dirname(options.artifactManifestPath),
32758
+ const payloadRoot = path41.join(
32759
+ path41.dirname(options.artifactManifestPath),
32769
32760
  "payload"
32770
32761
  );
32771
32762
  await verifyArtifactSource(payloadRoot, artifactManifest);
@@ -32785,7 +32776,7 @@ async function converge(options, force) {
32785
32776
  if (priorInstalled) {
32786
32777
  await assertCompanionNotRunning(
32787
32778
  options,
32788
- path40.join(
32779
+ path41.join(
32789
32780
  priorInstalled.paths.targetRoot,
32790
32781
  ...priorInstalled.install.entryRelativePath.split("/")
32791
32782
  )
@@ -32793,7 +32784,7 @@ async function converge(options, force) {
32793
32784
  }
32794
32785
  if (current.state === "not-installed") {
32795
32786
  try {
32796
- await fs36.lstat(paths.targetRoot);
32787
+ await fs37.lstat(paths.targetRoot);
32797
32788
  throw new Error(
32798
32789
  "The fixed companion target exists without an owned install manifest"
32799
32790
  );
@@ -32804,7 +32795,7 @@ async function converge(options, force) {
32804
32795
  const stage = `${paths.targetRoot}.m8t-stage-${randomUUID3()}`;
32805
32796
  const backup = `${paths.targetRoot}.m8t-backup-${randomUUID3()}`;
32806
32797
  const copy = options.copyPayload ?? copyArtifactPayload;
32807
- await fs36.mkdir(path40.dirname(paths.targetRoot), {
32798
+ await fs37.mkdir(path41.dirname(paths.targetRoot), {
32808
32799
  recursive: true,
32809
32800
  mode: 448
32810
32801
  });
@@ -32821,12 +32812,12 @@ async function converge(options, force) {
32821
32812
  try {
32822
32813
  await copy(payloadRoot, stage, artifactManifest);
32823
32814
  try {
32824
- await fs36.rename(paths.targetRoot, backup);
32815
+ await fs37.rename(paths.targetRoot, backup);
32825
32816
  movedPrior = true;
32826
32817
  } catch (error) {
32827
32818
  if (error.code !== "ENOENT") throw error;
32828
32819
  }
32829
- await fs36.rename(stage, paths.targetRoot);
32820
+ await fs37.rename(stage, paths.targetRoot);
32830
32821
  installedStage = true;
32831
32822
  const nodeExecutable = await realRegularFile(
32832
32823
  options.nodeExecutable,
@@ -32868,7 +32859,7 @@ async function converge(options, force) {
32868
32859
  await readClosedJson(paths.runtimeBinding),
32869
32860
  options.platform
32870
32861
  );
32871
- const executable = path40.join(
32862
+ const executable = path41.join(
32872
32863
  paths.targetRoot,
32873
32864
  ...artifactManifest.entryRelativePath.split("/")
32874
32865
  );
@@ -32878,7 +32869,7 @@ async function converge(options, force) {
32878
32869
  platform: options.platform,
32879
32870
  executable: ownedExecutable
32880
32871
  }));
32881
- priorLoginExecutable = priorInstalled ? path40.join(
32872
+ priorLoginExecutable = priorInstalled ? path41.join(
32882
32873
  priorInstalled.paths.targetRoot,
32883
32874
  ...priorInstalled.install.entryRelativePath.split("/")
32884
32875
  ) : executable;
@@ -32889,7 +32880,7 @@ async function converge(options, force) {
32889
32880
  );
32890
32881
  loginChanged = true;
32891
32882
  await (options.launch ?? defaultLaunch)(executable);
32892
- await fs36.rm(backup, { recursive: true, force: true });
32883
+ await fs37.rm(backup, { recursive: true, force: true });
32893
32884
  return {
32894
32885
  state: "installed",
32895
32886
  version: artifactManifest.version,
@@ -32903,15 +32894,15 @@ async function converge(options, force) {
32903
32894
  () => void 0
32904
32895
  );
32905
32896
  }
32906
- await fs36.rm(stage, { recursive: true, force: true }).catch(() => void 0);
32897
+ await fs37.rm(stage, { recursive: true, force: true }).catch(() => void 0);
32907
32898
  if (installedStage) {
32908
- await fs36.rm(paths.targetRoot, {
32899
+ await fs37.rm(paths.targetRoot, {
32909
32900
  recursive: true,
32910
32901
  force: true
32911
32902
  }).catch(() => void 0);
32912
32903
  }
32913
32904
  if (movedPrior) {
32914
- await fs36.rename(backup, paths.targetRoot).catch(() => void 0);
32905
+ await fs37.rename(backup, paths.targetRoot).catch(() => void 0);
32915
32906
  }
32916
32907
  await Promise.all([
32917
32908
  restoreFile(paths.installManifest, snapshots[0]),
@@ -32937,14 +32928,14 @@ async function uninstallCompanion(options) {
32937
32928
  if (install.ownedTargetRoot !== paths.targetRoot) {
32938
32929
  throw new Error("Refusing to remove a non-owned companion target");
32939
32930
  }
32940
- const executable = path40.join(
32931
+ const executable = path41.join(
32941
32932
  paths.targetRoot,
32942
32933
  ...install.entryRelativePath.split("/")
32943
32934
  );
32944
32935
  await assertCompanionNotRunning(options, executable);
32945
32936
  await (options.setStartAtLogin ?? (() => Promise.resolve()))(executable, false);
32946
32937
  const remove = options.removeOwnedPath ?? (async (ownedPath, recursive) => {
32947
- await fs36.rm(ownedPath, { recursive, force: true });
32938
+ await fs37.rm(ownedPath, { recursive, force: true });
32948
32939
  });
32949
32940
  await remove(paths.targetRoot, true);
32950
32941
  await remove(paths.installManifest, false);
@@ -32954,8 +32945,8 @@ async function uninstallCompanion(options) {
32954
32945
  }
32955
32946
 
32956
32947
  // src/commands/companion/install.ts
32957
- import * as os18 from "os";
32958
- import * as path42 from "path";
32948
+ import * as os19 from "os";
32949
+ import * as path43 from "path";
32959
32950
  import { Command as Command59, Option as Option56 } from "clipanion";
32960
32951
 
32961
32952
  // src/lib/companion-channel.ts
@@ -32973,9 +32964,9 @@ async function readCompanionRelease(source = { url: CHANNEL_LATEST_URL }, deps =
32973
32964
  // src/lib/companion-download.ts
32974
32965
  import { createHash as createHash8 } from "crypto";
32975
32966
  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";
32967
+ import * as fs38 from "fs/promises";
32968
+ import * as os18 from "os";
32969
+ import * as path42 from "path";
32979
32970
  init_errors();
32980
32971
  var MAX_ASSET_BYTES = 512 * 1024 * 1024;
32981
32972
  function extractArchive(archive, into) {
@@ -33028,20 +33019,20 @@ async function downloadCompanionArtifact(component, deps = {}) {
33028
33019
  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
33020
  });
33030
33021
  }
33031
- const root = await (deps.makeTemporaryDirectory ?? (() => fs37.mkdtemp(path41.join(os17.tmpdir(), "m8t-companion-"))))();
33022
+ const root = await (deps.makeTemporaryDirectory ?? (() => fs38.mkdtemp(path42.join(os18.tmpdir(), "m8t-companion-"))))();
33032
33023
  const dispose = async () => {
33033
- await fs37.rm(root, { recursive: true, force: true }).catch(() => void 0);
33024
+ await fs38.rm(root, { recursive: true, force: true }).catch(() => void 0);
33034
33025
  };
33035
33026
  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 });
33027
+ const archive = path42.join(root, pinned.asset);
33028
+ await fs38.writeFile(archive, bytes, { mode: 384 });
33029
+ const unpacked = path42.join(root, "unpacked");
33030
+ await fs38.mkdir(unpacked, { recursive: false, mode: 448 });
33040
33031
  await (deps.extract ?? extractArchive)(archive, unpacked);
33041
- await fs37.rm(archive, { force: true });
33032
+ await fs38.rm(archive, { force: true });
33042
33033
  return {
33043
33034
  version: component.version,
33044
- artifactManifestPath: path41.join(unpacked, "artifact-v1.json"),
33035
+ artifactManifestPath: path42.join(unpacked, "artifact-v1.json"),
33045
33036
  dispose
33046
33037
  };
33047
33038
  } catch (error) {
@@ -33054,7 +33045,7 @@ async function downloadCompanionArtifact(component, deps = {}) {
33054
33045
  function defaultLocalCompanionInstallOptions() {
33055
33046
  const cliEntry = process.argv[1];
33056
33047
  if (!cliEntry) throw new Error("Cannot resolve the installed CLI entry");
33057
- const homeDirectory = os18.homedir();
33048
+ const homeDirectory = os19.homedir();
33058
33049
  const platform = process.platform;
33059
33050
  return {
33060
33051
  homeDirectory,
@@ -33072,7 +33063,7 @@ async function convergeCompanionFromChannel(converge2, options = {}) {
33072
33063
  let artifactManifestPath;
33073
33064
  let dispose = () => Promise.resolve();
33074
33065
  if (stagedDirectory !== void 0) {
33075
- artifactManifestPath = path42.resolve(stagedDirectory, "artifact-v1.json");
33066
+ artifactManifestPath = path43.resolve(stagedDirectory, "artifact-v1.json");
33076
33067
  } else {
33077
33068
  const release = await readCompanionRelease(
33078
33069
  platformVersion !== void 0 ? { channel: true, version: platformTag(platformVersion) } : { url: CHANNEL_LATEST_URL }
@@ -33148,9 +33139,9 @@ function resolveRepoRootMarker(args) {
33148
33139
  if (args.existing !== null && args.existing !== "") return args.existing;
33149
33140
  return args.cwd;
33150
33141
  }
33151
- async function looksLikeCheckout(dir) {
33142
+ async function looksLikeCheckout(dir2) {
33152
33143
  try {
33153
- return (await fs38.stat(path43.join(dir, "brain-template"))).isDirectory();
33144
+ return (await fs39.stat(path44.join(dir2, "brain-template"))).isDirectory();
33154
33145
  } catch {
33155
33146
  return false;
33156
33147
  }
@@ -33166,21 +33157,21 @@ var defaultDeps3 = {
33166
33157
  convergeCompanion: (platformVersion) => convergeCompanionFromChannel(installCompanion, {
33167
33158
  ...platformVersion !== void 0 ? { platformVersion } : {}
33168
33159
  }),
33169
- homedir: () => os19.homedir()
33160
+ homedir: () => os20.homedir()
33170
33161
  };
33171
33162
  async function finalizeInstall(args, deps = defaultDeps3) {
33172
- const markerDir = path43.join(deps.homedir(), ".m8t");
33173
- const markerPath = path43.join(markerDir, "repo-root");
33163
+ const markerDir = path44.join(deps.homedir(), ".m8t");
33164
+ const markerPath = path44.join(markerDir, "repo-root");
33174
33165
  const cwd = process.cwd();
33175
- const existing = await fs38.readFile(markerPath, "utf8").then((s) => s.trim()).catch(() => null);
33166
+ const existing = await fs39.readFile(markerPath, "utf8").then((s) => s.trim()).catch(() => null);
33176
33167
  const repoRoot = resolveRepoRootMarker({
33177
33168
  ...args.repoRoot !== void 0 ? { explicit: args.repoRoot } : {},
33178
33169
  cwd,
33179
33170
  cwdIsCheckout: await looksLikeCheckout(cwd),
33180
33171
  existing
33181
33172
  });
33182
- await fs38.mkdir(markerDir, { recursive: true });
33183
- await fs38.writeFile(markerPath, `${repoRoot}
33173
+ await fs39.mkdir(markerDir, { recursive: true });
33174
+ await fs39.writeFile(markerPath, `${repoRoot}
33184
33175
  `, "utf8");
33185
33176
  let webappUrl;
33186
33177
  try {
@@ -33258,7 +33249,7 @@ async function finalizeInstall(args, deps = defaultDeps3) {
33258
33249
  }
33259
33250
  let brainOrg = null;
33260
33251
  try {
33261
- const credsRaw = await fs38.readFile(path43.join(markerDir, "github-app.json"), "utf8");
33252
+ const credsRaw = await fs39.readFile(path44.join(markerDir, "github-app.json"), "utf8");
33262
33253
  const creds = JSON.parse(credsRaw);
33263
33254
  brainOrg = typeof creds.org === "string" ? creds.org : null;
33264
33255
  } catch {
@@ -33371,7 +33362,7 @@ var BootstrapStatusCommand = class extends M8tCommand {
33371
33362
  typeof this.output === "string" ? this.output : void 0,
33372
33363
  this.context.stdout
33373
33364
  );
33374
- const sleep5 = (ms) => new Promise((r) => setTimeout(r, ms));
33365
+ const sleep4 = (ms) => new Promise((r) => setTimeout(r, ms));
33375
33366
  const watch = this.watch === true;
33376
33367
  const invokedPath = this.path ?? [];
33377
33368
  const viaFinishAlias = invokedPath[invokedPath.length - 1] === "finish";
@@ -33404,7 +33395,7 @@ var BootstrapStatusCommand = class extends M8tCommand {
33404
33395
  }
33405
33396
  if (mode === "pretty") this.context.stderr.write(` ${colors.dim("waiting for the installer to start\u2026")}
33406
33397
  `);
33407
- await sleep5(1e4);
33398
+ await sleep4(1e4);
33408
33399
  continue;
33409
33400
  }
33410
33401
  throw e;
@@ -33440,7 +33431,7 @@ var BootstrapStatusCommand = class extends M8tCommand {
33440
33431
  );
33441
33432
  return 1;
33442
33433
  }
33443
- await sleep5(1e4);
33434
+ await sleep4(1e4);
33444
33435
  }
33445
33436
  }
33446
33437
  /**
@@ -33690,18 +33681,13 @@ Found ${String(total)} orphaned assignment(s) (${breakdown}) (dry-run). ${colors
33690
33681
  };
33691
33682
 
33692
33683
  // src/commands/bootstrap/ui.ts
33693
- import * as fs40 from "fs";
33694
- import * as os21 from "os";
33695
- import * as path45 from "path";
33696
33684
  import { Command as Command62, Option as Option59 } from "clipanion";
33697
- import { DefaultAzureCredential as DefaultAzureCredential25 } from "@azure/identity";
33698
- init_errors();
33699
33685
 
33700
33686
  // src/lib/bootstrap-ui.ts
33701
- import * as fs39 from "fs";
33687
+ import * as fs40 from "fs";
33702
33688
  import * as net from "net";
33703
- import * as os20 from "os";
33704
- import * as path44 from "path";
33689
+ import * as os21 from "os";
33690
+ import * as path45 from "path";
33705
33691
  import { randomBytes as randomBytes3 } from "crypto";
33706
33692
  import { spawn as spawn8, spawnSync as spawnSync6 } from "child_process";
33707
33693
  init_errors();
@@ -33709,475 +33695,32 @@ init_rbac();
33709
33695
 
33710
33696
  // src/lib/intake-agent.ts
33711
33697
  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
33698
 
33750
33699
  // 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");
33700
+ function onboardingUiPaths(home = os21.homedir()) {
33701
+ const dir2 = path45.join(home, ".m8t");
33996
33702
  return {
33997
- logPath: path44.join(dir, "onboarding-ui.log"),
33998
- pidPath: path44.join(dir, "onboarding-ui.pid")
33703
+ logPath: path45.join(dir2, "onboarding-ui.log"),
33704
+ pidPath: path45.join(dir2, "onboarding-ui.pid")
33999
33705
  };
34000
33706
  }
34001
- function onboardingRelayPaths(home = os20.homedir()) {
34002
- const dir = path44.join(home, ".m8t");
33707
+ function onboardingRelayPaths(home = os21.homedir()) {
33708
+ const dir2 = path45.join(home, ".m8t");
34003
33709
  return {
34004
- logPath: path44.join(dir, "onboarding-relay.log"),
34005
- pidPath: path44.join(dir, "onboarding-relay.pid")
33710
+ logPath: path45.join(dir2, "onboarding-relay.log"),
33711
+ pidPath: path45.join(dir2, "onboarding-relay.pid")
34006
33712
  };
34007
33713
  }
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
33714
  function parseManagedPid(text) {
34032
33715
  if (!/^[1-9]\d*$/.test(text)) return null;
34033
33716
  const pid = Number(text);
34034
33717
  if (!Number.isSafeInteger(pid) || pid < 2 || pid === process.pid || pid === process.ppid) return null;
34035
33718
  return pid;
34036
33719
  }
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
33720
  function stopPidFile(pidPath) {
34178
33721
  let pidStr;
34179
33722
  try {
34180
- pidStr = fs39.readFileSync(pidPath, "utf8").trim();
33723
+ pidStr = fs40.readFileSync(pidPath, "utf8").trim();
34181
33724
  } catch {
34182
33725
  return false;
34183
33726
  }
@@ -34194,551 +33737,386 @@ function stopPidFile(pidPath) {
34194
33737
  }
34195
33738
  }
34196
33739
  try {
34197
- fs39.unlinkSync(pidPath);
33740
+ fs40.unlinkSync(pidPath);
34198
33741
  } catch {
34199
33742
  }
34200
33743
  return true;
34201
33744
  }
34202
- function stopOnboardingUi(home = os20.homedir()) {
33745
+ function stopOnboardingUi(home = os21.homedir()) {
34203
33746
  const uiStopped = stopPidFile(onboardingUiPaths(home).pidPath);
34204
33747
  const relayStopped = stopPidFile(onboardingRelayPaths(home).pidPath);
34205
33748
  return uiStopped || relayStopped;
34206
33749
  }
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
33750
 
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
- }
33751
+ // src/commands/bootstrap/ui.ts
33752
+ function renderDeprecationNotice() {
33753
+ return `${colors.error("\u26A0")} 'm8t bootstrap ui' is deprecated and does nothing \u2014 the local onboarding chat is gone for good.
33754
+ Your details + chat while you wait: ${colors.field("m8t bootstrap profile")} \xB7 Your own Ezra, once installed: ${colors.field("m8t open")}
33755
+ `;
34246
33756
  }
33757
+ var BootstrapUiCommand = class extends M8tCommand {
33758
+ static paths = [["bootstrap", "ui"]];
33759
+ static usage = Command62.Usage({
33760
+ description: "DEPRECATED \u2014 does nothing. Use `m8t bootstrap profile` instead.",
33761
+ details: [
33762
+ "The local onboarding chat has been retired. Your details are collected by",
33763
+ "`m8t bootstrap profile`, which also opens the hosted Ezra to talk to while the",
33764
+ "install runs; `m8t open` reaches your own Ezra once the install finishes.",
33765
+ "",
33766
+ "This command deploys nothing, serves nothing, and changes nothing. Every flag",
33767
+ "below is accepted and ignored, so an older runbook or script still exits cleanly.",
33768
+ "`--stop` is the one exception: it still shuts down a chat server left running by",
33769
+ "an earlier version of this command."
33770
+ ].join("\n"),
33771
+ examples: [
33772
+ ["Collect your details and open Ezra instead", "$0 bootstrap profile"],
33773
+ ["Stop a chat UI an older CLI left running", "$0 bootstrap ui --stop"]
33774
+ ]
33775
+ });
33776
+ // Accepted and ignored, deliberately: removing them would turn an old script's
33777
+ // harmless no-op into an "unknown option" failure mid-install. CLI-rewrite: drop
33778
+ // the whole surface when the rewrite lands.
33779
+ repoRoot = Option59.String("--repo-root", { description: "Ignored (deprecated)." });
33780
+ port = Option59.String("--port", "3000", { description: "Ignored (deprecated)." });
33781
+ endpoint = Option59.String("--endpoint", { description: "Ignored (deprecated)." });
33782
+ prepOnly = Option59.Boolean("--prep-only", false, { description: "Ignored (deprecated)." });
33783
+ skipInstall = Option59.Boolean("--skip-install", false, { description: "Ignored (deprecated)." });
33784
+ foreground = Option59.Boolean("--foreground", false, { description: "Ignored (deprecated)." });
33785
+ voice = Option59.Boolean("--voice", false, { description: "Ignored (deprecated)." });
33786
+ stop = Option59.Boolean("--stop", false, {
33787
+ description: "Shut down a local chat UI left running by an earlier version of this command."
33788
+ });
33789
+ // Not `async`: there is nothing left to await. Everything this command used to
33790
+ // wait on — Foundry, the role grant, the agent deploy, pnpm — is gone.
33791
+ executeCommand() {
33792
+ if (this.stop === true) {
33793
+ const stopped = stopOnboardingUi();
33794
+ this.context.stdout.write(
33795
+ stopped ? `${colors.success("\u2713")} stopped the onboarding chat UI.
33796
+ ` : ` ${colors.dim("nothing to stop (no onboarding-ui.pid found).")}
33797
+ `
33798
+ );
33799
+ return Promise.resolve(0);
33800
+ }
33801
+ this.context.stdout.write(renderDeprecationNotice());
33802
+ return Promise.resolve(0);
33803
+ }
33804
+ };
33805
+
33806
+ // src/commands/bootstrap/profile.ts
33807
+ import * as readline3 from "readline/promises";
33808
+ import { Command as Command63, Option as Option60 } from "clipanion";
34247
33809
 
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;
33810
+ // src/lib/profile-collect.ts
33811
+ init_errors();
33812
+ function looksLikeEmail(value) {
33813
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.trim());
34254
33814
  }
34255
- function narrate(model, outcome, region) {
34256
- if (outcome === "deployed") return `${model} - ready`;
34257
- return `${model} - not available for your subscription in ${region}`;
33815
+ function renderEmailQuestion(prefill) {
33816
+ const base = "Your email (Ezra sends your copies of its outbound mail here)";
33817
+ return prefill ? `${base} [${prefill}]: ` : `${base}: `;
34258
33818
  }
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
- }
33819
+ 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";
33820
+ function renderAdvisorQuestion(label, prior) {
33821
+ return prior ? ` ${label} [${prior}] (Enter to keep): ` : ` ${label} (Enter to skip): `;
34278
33822
  }
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
- });
33823
+ var ADVISOR_SKIPPED_LINE = " No advisor recorded \u2014 tell Ezra any time and it'll add them.\n";
33824
+ function refuseFounderEmail() {
33825
+ return new LocalCliError({
33826
+ code: "PROFILE_FOUNDER_EMAIL_REQUIRED",
33827
+ 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.",
33828
+ hint: "Ask the founder to confirm the address, then re-run 'm8t bootstrap profile --founder-email <addr>'."
33829
+ });
33830
+ }
33831
+ function refuseMalformed(flag, value) {
33832
+ return new LocalCliError({
33833
+ code: "PROFILE_EMAIL_MALFORMED",
33834
+ message: `'${value}' doesn't look like an email address. Pass ${flag} <you@example.com>.`
33835
+ });
33836
+ }
33837
+ async function collectProfile(args) {
33838
+ const { flags, identity, isTty, ask } = args;
33839
+ const previous = args.previous ?? null;
33840
+ const hasAdvisorFlag = (flags.advisorName ?? "").trim() !== "" || (flags.advisorEmail ?? "").trim() !== "";
33841
+ if (flags.noAdvisor && hasAdvisorFlag) {
34308
33842
  return {
34309
- model: result2.chosen?.model,
34310
- note: renderChosenModelNote(decideNote(WHITELIST, result2)),
34311
- trace: result2.trace
33843
+ ok: false,
33844
+ error: new LocalCliError({
33845
+ code: "PROFILE_ADVISOR_FLAGS_CONFLICT",
33846
+ message: "--no-advisor cannot be combined with --advisor-name / --advisor-email.",
33847
+ hint: "Drop --no-advisor to record the advisor, or drop the advisor flags to skip."
33848
+ })
34312
33849
  };
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;
33850
+ }
33851
+ let founderEmail = (flags.founderEmail ?? "").trim();
33852
+ if (!founderEmail) {
33853
+ const confirmed = previous?.founderEmail.trim() ?? "";
33854
+ const guess = identity.email.trim();
33855
+ if (!isTty) {
33856
+ if (!confirmed) return { ok: false, error: refuseFounderEmail() };
33857
+ founderEmail = confirmed;
33858
+ } else {
33859
+ const prefill = confirmed || guess;
33860
+ const answered = (await ask(renderEmailQuestion(prefill))).trim();
33861
+ founderEmail = answered || prefill;
33862
+ if (!founderEmail) return { ok: false, error: refuseFounderEmail() };
34392
33863
  }
34393
33864
  }
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;
33865
+ if (!looksLikeEmail(founderEmail)) {
33866
+ return { ok: false, error: refuseMalformed("--founder-email", founderEmail) };
33867
+ }
33868
+ let advisor;
33869
+ if (flags.noAdvisor) {
33870
+ advisor = null;
33871
+ } else {
33872
+ const prior = previous?.advisor ?? null;
33873
+ let name = (flags.advisorName ?? "").trim();
33874
+ let email = (flags.advisorEmail ?? "").trim();
33875
+ if (!hasAdvisorFlag && isTty) {
33876
+ name = (await ask(ADVISOR_LEAD + renderAdvisorQuestion("Name ", prior?.name ?? ""))).trim();
33877
+ email = (await ask(renderAdvisorQuestion("Email", prior?.email ?? ""))).trim();
33878
+ }
33879
+ name = name || (prior?.name ?? "");
33880
+ email = email || (prior?.email ?? "");
33881
+ if (email && !looksLikeEmail(email)) {
33882
+ return { ok: false, error: refuseMalformed("--advisor-email", email) };
33883
+ }
33884
+ advisor = name || email ? { name, email } : null;
34400
33885
  }
34401
- const oldestBucket = oldestMs === null ? null : ageBucket((now - oldestMs) / MS_PER_DAY);
34402
33886
  return {
34403
- verdict: notableCount >= HEAVY_THRESHOLD ? "heavy" : "light",
34404
- notableCount,
34405
- categories,
34406
- topRegion,
34407
- regionSpread,
34408
- oldestBucket
33887
+ ok: true,
33888
+ profile: {
33889
+ schemaVersion: 1,
33890
+ collectedAt: args.now(),
33891
+ founderName: identity.name.trim(),
33892
+ founderEmail,
33893
+ advisor,
33894
+ ...flags.noAdvisor ? { advisorCleared: true } : {}
33895
+ }
34409
33896
  };
34410
33897
  }
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;
33898
+
33899
+ // src/lib/ezra-guides.ts
33900
+ var EZRA_REPO = "m8t-labs/ezra";
33901
+ var EZRA_GUIDES_URL = `https://github.com/${EZRA_REPO}/blob/main/guides`;
33902
+
33903
+ // src/lib/chat-invite.ts
33904
+ var CHAT_INVITE_PATH = ".m8t/chat-invite.json";
33905
+ var CHAT_INVITE_TIMEOUT_MS = 3e3;
33906
+ var ALLOWED_INVITE_HOSTS = ["m8t.run"];
33907
+ function hostAllowed(hostname) {
33908
+ const host = hostname.toLowerCase();
33909
+ return ALLOWED_INVITE_HOSTS.some((allowed) => host === allowed || host.endsWith(`.${allowed}`));
33910
+ }
33911
+ var SHELL_UNSAFE = /[&|^<>"`$%\\\s]/;
33912
+ function chatInviteUrl() {
33913
+ return `https://raw.githubusercontent.com/${EZRA_REPO}/main/${CHAT_INVITE_PATH}`;
33914
+ }
33915
+ function isRecord4(value) {
33916
+ return typeof value === "object" && value !== null && !Array.isArray(value);
33917
+ }
33918
+ async function fetchChatInvite(deps = {}) {
33919
+ const fetchImpl = deps.fetchImpl ?? fetch;
33920
+ let parsed;
34464
33921
  try {
34465
- raw = JSON.parse(out);
33922
+ const res = await fetchImpl(chatInviteUrl(), {
33923
+ headers: { accept: "application/json" },
33924
+ signal: AbortSignal.timeout(deps.timeoutMs ?? CHAT_INVITE_TIMEOUT_MS)
33925
+ });
33926
+ if (!res.ok) return { ok: false, reason: "http-error" };
33927
+ const raw2 = await res.text();
33928
+ try {
33929
+ parsed = JSON.parse(raw2);
33930
+ } catch {
33931
+ return { ok: false, reason: "malformed" };
33932
+ }
34466
33933
  } 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;
33934
+ return { ok: false, reason: "unreachable" };
33935
+ }
33936
+ if (!isRecord4(parsed)) return { ok: false, reason: "malformed" };
33937
+ if (parsed.schemaVersion !== 1) return { ok: false, reason: "unknown-schema" };
33938
+ if (parsed.enabled !== true) return { ok: false, reason: "disabled" };
33939
+ const raw = typeof parsed.inviteUrl === "string" ? parsed.inviteUrl.trim() : "";
33940
+ if (!raw) return { ok: false, reason: "no-url" };
33941
+ let parsedUrl;
34486
33942
  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) };
33943
+ parsedUrl = new URL(raw);
34500
33944
  } 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.")}
33945
+ return { ok: false, reason: "insecure-url" };
33946
+ }
33947
+ if (parsedUrl.protocol !== "https:") return { ok: false, reason: "insecure-url" };
33948
+ if (!hostAllowed(parsedUrl.hostname)) return { ok: false, reason: "insecure-url" };
33949
+ const url = parsedUrl.href;
33950
+ if (SHELL_UNSAFE.test(url)) return { ok: false, reason: "insecure-url" };
33951
+ return { ok: true, url };
33952
+ }
33953
+ function renderChatPointer(url, opts) {
33954
+ const lead = "While your real Ezra installs in your cloud, talk to our Ezra to see what he can do for you.\n";
33955
+ return opts.print ? `${lead} Open this: ${url}
33956
+ ` : `${lead} ${url}
34514
33957
  `;
34515
33958
  }
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
- `;
33959
+ var CHAT_UNAVAILABLE_LINE = " Chat with Ezra isn't available right now \u2014 your install is unaffected.\n";
33960
+ async function openChatInvite(deps = {}) {
33961
+ const invite = await fetchChatInvite(deps);
33962
+ if (!invite.ok) return CHAT_UNAVAILABLE_LINE;
33963
+ const print = deps.print === true;
33964
+ if (!print) {
33965
+ const open3 = deps.open ?? tryOpenUrl;
33966
+ try {
33967
+ await open3(invite.url);
33968
+ } catch {
33969
+ }
33970
+ }
33971
+ return renderChatPointer(invite.url, { print });
34521
33972
  }
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).",
33973
+
33974
+ // src/commands/bootstrap/profile.ts
33975
+ var BootstrapProfileCommand = class extends M8tCommand {
33976
+ static paths = [["bootstrap", "profile"]];
33977
+ static usage = Command63.Usage({
33978
+ description: "Confirm your email + your Microsoft startup advisor, and open Ezra to talk to while the install runs.",
34526
33979
  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.",
33980
+ "Run after `m8t bootstrap launch`, in parallel with `status --watch`. Records the two",
33981
+ "facts your deployed advisors need \u2014 the address Ezra copies you on, and the Microsoft",
33982
+ "startup advisor it can escalate to \u2014 and points you at the hosted Ezra for the wait.",
34533
33983
  "",
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."
33984
+ "Non-interactive by default: pass the answers as flags and nothing is prompted. With a",
33985
+ "terminal, any answer you did not pass is asked for, with your Azure sign-in offered as",
33986
+ "the default. Idempotent \u2014 re-run it any time to correct or fill in an answer.",
33987
+ "",
33988
+ "Nothing here blocks the install, and chat never fails it."
34536
33989
  ].join("\n"),
34537
33990
  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"]
33991
+ ["Record both answers", "$0 bootstrap profile --founder-email you@example.com --advisor-name 'Sam Lee' --advisor-email sam@example.com"],
33992
+ ["Record the email, skip the advisor", "$0 bootstrap profile --founder-email you@example.com --no-advisor"],
33993
+ ["Answer the questions at a terminal", "$0 bootstrap profile"],
33994
+ ["Skip the browser hand-off", "$0 bootstrap profile --founder-email you@example.com --print"]
34543
33995
  ]
34544
33996
  });
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
- });
33997
+ founderEmail = Option60.String("--founder-email", { description: "The founder's contact address \u2014 Ezra copies them on its outbound mail." });
33998
+ advisorName = Option60.String("--advisor-name", { description: "Their Microsoft startup advisor's name." });
33999
+ advisorEmail = Option60.String("--advisor-email", { description: "Their Microsoft startup advisor's email." });
34000
+ noAdvisor = Option60.Boolean("--no-advisor", false, { description: "Record that they have no startup advisor to add (or don't know it yet)." });
34001
+ noChat = Option60.Boolean("--no-chat", false, { description: "Record the answers without opening the hosted Ezra." });
34002
+ print = Option60.Boolean("--print", false, { description: "Print the chat link instead of opening a browser (headless / SSH)." });
34557
34003
  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."
34004
+ const stdin = this.context.stdin;
34005
+ const stdout = this.context.stdout;
34006
+ const isTty = stdin.isTTY === true;
34007
+ const flags = {
34008
+ founderEmail: typeof this.founderEmail === "string" ? this.founderEmail : void 0,
34009
+ advisorName: typeof this.advisorName === "string" ? this.advisorName : void 0,
34010
+ advisorEmail: typeof this.advisorEmail === "string" ? this.advisorEmail : void 0,
34011
+ noAdvisor: this.noAdvisor === true
34012
+ };
34013
+ const [identity, previous] = await Promise.all([
34014
+ getSignedInUserIdentity(),
34015
+ readOnboardingProfile()
34016
+ ]);
34017
+ const mayAsk = isTty && (!(flags.founderEmail ?? "").trim() || !(flags.noAdvisor || (flags.advisorName ?? "").trim() || (flags.advisorEmail ?? "").trim()));
34018
+ const rl = mayAsk ? readline3.createInterface({ input: stdin, output: stdout }) : null;
34019
+ let collected;
34020
+ try {
34021
+ collected = await collectProfile({
34022
+ flags,
34023
+ identity,
34024
+ previous,
34025
+ isTty,
34026
+ ask: async (question) => rl ? await rl.question(question) : "",
34027
+ now: () => (/* @__PURE__ */ new Date()).toISOString()
34588
34028
  });
34029
+ } finally {
34030
+ rl?.close();
34589
34031
  }
34590
- const out = (m) => {
34591
- this.context.stderr.write(` ${colors.dim(m)}
34032
+ if (!collected.ok) throw collected.error;
34033
+ const profile = collected.profile;
34034
+ await writeOnboardingProfile(profile);
34035
+ this.context.stdout.write(`${colors.success("\u2713")} Recorded \u2014 Ezra will copy you at ${colors.field(profile.founderEmail)}.
34592
34036
  `);
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."));
34037
+ if (profile.advisor === null) this.context.stdout.write(colors.dim(ADVISOR_SKIPPED_LINE));
34038
+ await this.seedIfBrainsExist(profile);
34039
+ if (this.noChat !== true) {
34040
+ this.context.stdout.write(await openChatInvite({ print: this.print === true }));
34596
34041
  }
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;
34042
+ return 0;
34043
+ }
34044
+ /**
34045
+ * Seed now if the brains already exist, otherwise say who will.
34046
+ *
34047
+ * Best-effort on purpose. This command runs mid-install by design, when there is
34048
+ * usually nothing to seed yet — `bootstrap status --watch` does it at `done`. But
34049
+ * re-running this after the install should not leave a founder wondering whether
34050
+ * their correction landed, so when the brains ARE there we seed immediately.
34051
+ *
34052
+ * A failure here is reported and swallowed: the answers are already safely on disk,
34053
+ * and the install path will retry the seed on its own.
34054
+ */
34055
+ async seedIfBrainsExist(profile) {
34056
+ let ctx = null;
34057
+ try {
34058
+ ctx = await resolveSeedContext({});
34059
+ } catch {
34060
+ ctx = null;
34666
34061
  }
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;
34062
+ if (!ctx) {
34063
+ this.context.stdout.write(
34064
+ ` ${colors.dim("Your advisors will pick this up when the install finishes.")}
34065
+ `
34066
+ );
34067
+ return;
34688
34068
  }
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"
34069
+ try {
34070
+ await applyProfileToBrains({
34071
+ block: toOnboardingBlock(profile),
34072
+ brainRepos: ctx.brainRepos,
34073
+ branch: "main",
34074
+ appCreds: ctx.appCreds,
34075
+ subscriptionId: ctx.subscriptionId,
34076
+ azIdentity: { name: profile.founderName, email: profile.founderEmail },
34077
+ contactsOnly: true,
34078
+ advisorCleared: profile.advisorCleared === true
34697
34079
  });
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
34080
  this.context.stdout.write(
34704
- `${colors.success("\u2713")} chat UI already running at http://localhost:${this.port}
34081
+ `${colors.success("\u2713")} Your advisors now know how to reach you (seeded ${ctx.brainRepos.join(", ")}).
34705
34082
  `
34706
34083
  );
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`)}
34084
+ } catch (e) {
34085
+ this.context.stderr.write(
34086
+ ` ${colors.dim(`saved, but the brain seed didn't land yet: ${e instanceof Error ? e.message : String(e)}`)}
34087
+ ${colors.hint("it retries when the install finishes, or run:")} m8t bootstrap seed-profile
34711
34088
  `
34712
34089
  );
34713
34090
  }
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
34091
  }
34723
34092
  };
34724
34093
 
34725
34094
  // src/commands/bootstrap/seed-profile.ts
34726
- import { Command as Command63, Option as Option60 } from "clipanion";
34095
+ import { Command as Command64, Option as Option61 } from "clipanion";
34727
34096
  var BootstrapSeedProfileCommand = class extends M8tCommand {
34728
34097
  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.",
34098
+ static usage = Command64.Usage({
34099
+ description: "Seed your advisors' brains with how to reach you, from what you confirmed at `m8t bootstrap profile`.",
34100
+ details: [
34101
+ "Renders memory/founder.md + memory/company-profile.md (+ their MEMORY.md index lines)",
34102
+ "and commits them to the brains this install created, via the GitHub App. Idempotent \u2014",
34103
+ "an unchanged profile is a no-op.",
34104
+ "",
34105
+ "Run `m8t bootstrap profile` first; this is the manual re-run of the seed that",
34106
+ "`m8t bootstrap status --watch` already does when the install completes.",
34107
+ "",
34108
+ "--watch is legacy: it polls for an onboarding conversation from installs made when a",
34109
+ "dedicated intake agent was still deployed. It has nothing to wait for otherwise."
34110
+ ].join("\n"),
34732
34111
  examples: [
34733
- ["Seed now (idempotent)", "$0 bootstrap seed-profile"],
34734
- ["Wait for the founder to finish", "$0 bootstrap seed-profile --watch"]
34112
+ ["Seed now (idempotent)", "$0 bootstrap seed-profile"]
34735
34113
  ]
34736
34114
  });
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");
34115
+ endpoint = Option61.String("--endpoint", { description: "Override the Foundry endpoint (else read from the install status)." });
34116
+ brain = Option61.String("--brain", { description: "Seed only this one brain repo, instead of both <org>/stacey-brain and <org>/ezra-brain." });
34117
+ watch = Option61.Boolean("--watch", false, { description: "Poll until the intake completes (or --timeout)." });
34118
+ timeout = Option61.String("--timeout", { description: "Watch timeout in minutes (default 20)." });
34119
+ githubAppCreds = Option61.String("--github-app-creds");
34742
34120
  async executeCommand() {
34743
34121
  const ctx = await resolveSeedContext({
34744
34122
  endpointOverride: typeof this.endpoint === "string" ? this.endpoint : void 0,
@@ -34750,12 +34128,30 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
34750
34128
  `);
34751
34129
  return 0;
34752
34130
  }
34131
+ const local = await readOnboardingProfile();
34132
+ if (local) {
34133
+ await applyProfileToBrains({
34134
+ block: toOnboardingBlock(local),
34135
+ brainRepos: ctx.brainRepos,
34136
+ branch: "main",
34137
+ appCreds: ctx.appCreds,
34138
+ subscriptionId: ctx.subscriptionId,
34139
+ azIdentity: { name: local.founderName, email: local.founderEmail },
34140
+ contactsOnly: true,
34141
+ advisorCleared: local.advisorCleared === true
34142
+ });
34143
+ this.context.stdout.write(
34144
+ `${colors.success("\u2713")} seeded your advisors' brains (${ctx.brainRepos.join(", ")}) with how to reach you.
34145
+ `
34146
+ );
34147
+ return 0;
34148
+ }
34753
34149
  const watch = this.watch === true;
34754
34150
  const rawTimeout = typeof this.timeout === "string" ? this.timeout.trim() : "";
34755
34151
  const parsedTimeout = Number(rawTimeout);
34756
34152
  const timeoutMin = rawTimeout !== "" && Number.isFinite(parsedTimeout) ? parsedTimeout : 20;
34757
34153
  const deadline = Date.now() + timeoutMin * 6e4;
34758
- const sleep5 = (ms) => new Promise((r) => setTimeout(r, ms));
34154
+ const sleep4 = (ms) => new Promise((r) => setTimeout(r, ms));
34759
34155
  for (; ; ) {
34760
34156
  const token = await getFoundryToken();
34761
34157
  const { hadIntake, block, rejection } = await findOnboardingProfile({ endpoint: ctx.endpoint, token });
@@ -34782,9 +34178,16 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
34782
34178
  );
34783
34179
  return 3;
34784
34180
  }
34785
- const why = hadIntake ? "intake not complete yet \u2014 no m8t_onboarding block found." : "no onboarding intake found.";
34181
+ if (!hadIntake) {
34182
+ this.context.stderr.write(
34183
+ ` ${colors.dim("nothing collected yet \u2014 your advisors don't know how to reach you.")}
34184
+ ${colors.hint("run:")} m8t bootstrap profile
34185
+ `
34186
+ );
34187
+ return 3;
34188
+ }
34786
34189
  this.context.stderr.write(
34787
- ` ${colors.dim(why)}
34190
+ ` ${colors.dim("intake not complete yet \u2014 no m8t_onboarding block found.")}
34788
34191
  ${colors.hint("retry:")} m8t bootstrap seed-profile
34789
34192
  `
34790
34193
  );
@@ -34792,7 +34195,7 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
34792
34195
  }
34793
34196
  this.context.stderr.write(` ${colors.dim("waiting for the intake to complete\u2026")}
34794
34197
  `);
34795
- await sleep5(2e4);
34198
+ await sleep4(2e4);
34796
34199
  }
34797
34200
  }
34798
34201
  };
@@ -34801,7 +34204,7 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
34801
34204
  import * as fs41 from "fs";
34802
34205
  import * as os22 from "os";
34803
34206
  import * as path46 from "path";
34804
- import { Command as Command64, Option as Option61 } from "clipanion";
34207
+ import { Command as Command65, Option as Option62 } from "clipanion";
34805
34208
  init_errors();
34806
34209
 
34807
34210
  // src/lib/telemetry-enroll.ts
@@ -34820,7 +34223,7 @@ async function defaultToken() {
34820
34223
  const token = out.trim();
34821
34224
  return token.length > 0 ? token : null;
34822
34225
  }
34823
- var sleep4 = (ms) => new Promise((r) => setTimeout(r, ms));
34226
+ var sleep3 = (ms) => new Promise((r) => setTimeout(r, ms));
34824
34227
  async function enroll(args) {
34825
34228
  const f = args.fetchImpl ?? fetch;
34826
34229
  const url = args.baseUrl ? `${args.baseUrl.replace(/\/+$/, "")}/api/ingest/enroll` : ingestUrl("/api/ingest/enroll");
@@ -34841,7 +34244,7 @@ async function enroll(args) {
34841
34244
  throw new LocalCliError({ code: "TELEMETRY_ENROLL_UNREACHABLE", message: `Could not reach the telemetry ingest to enroll: ${e instanceof Error ? e.message : String(e)}` });
34842
34245
  }
34843
34246
  if (res.status !== 503 || attempt === 3) break;
34844
- await sleep4(retryDelayMs * attempt);
34247
+ await sleep3(retryDelayMs * attempt);
34845
34248
  }
34846
34249
  if (!res?.ok) {
34847
34250
  const detail = res ? await res.text().catch(() => "") : "";
@@ -34883,7 +34286,7 @@ async function resolveKeyVaultName(containerAppResourceId) {
34883
34286
  }
34884
34287
  var TelemetryEnrollCommand = class extends M8tCommand {
34885
34288
  static paths = [["telemetry", "enroll"]];
34886
- static usage = Command64.Usage({
34289
+ static usage = Command65.Usage({
34887
34290
  description: "Enroll this installation for operational telemetry (pre-existing installs).",
34888
34291
  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
34292
  examples: [
@@ -34891,11 +34294,11 @@ var TelemetryEnrollCommand = class extends M8tCommand {
34891
34294
  ["Enroll and share a support contact", "$0 telemetry enroll --contact-email you@example.com --company 'Acme'"]
34892
34295
  ]
34893
34296
  });
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." });
34297
+ company = Option62.String("--company", { description: "Share your company name with m8t support. Not sent unless you pass it." });
34298
+ contactEmail = Option62.String("--contact-email", { description: "Share a contact email with m8t support. Not sent unless you pass it." });
34299
+ subscription = Option62.String("--subscription", { description: "Azure subscription id used to find your deployment. Never sent to m8t." });
34300
+ resourceGroup = Option62.String("--resource-group", { description: "Resource group to disambiguate discovery, if you have more than one m8t deployment." });
34301
+ force = Option62.Boolean("--force", false, { description: "Enroll even when a key is already stored. Use only when m8t support asks you to." });
34899
34302
  async executeCommand() {
34900
34303
  const account = await getAzAccount();
34901
34304
  const subscriptionId = (typeof this.subscription === "string" ? this.subscription : void 0) ?? account.subscriptionId;
@@ -34945,7 +34348,7 @@ var TelemetryEnrollCommand = class extends M8tCommand {
34945
34348
  };
34946
34349
 
34947
34350
  // src/commands/companion/bridge.ts
34948
- import { Command as Command65, Option as Option62 } from "clipanion";
34351
+ import { Command as Command66, Option as Option63 } from "clipanion";
34949
34352
 
34950
34353
  // ../../packages/companion-bridge-contract/src/index.ts
34951
34354
  var COMPANION_MESSAGE_MAX_CODE_POINTS = 32768;
@@ -34984,7 +34387,7 @@ var VERSION3 = /^[A-Za-z0-9][A-Za-z0-9._+-]{0,63}$/u;
34984
34387
  function isVersionOrNull(value) {
34985
34388
  return value === null || typeof value === "string" && VERSION3.test(value);
34986
34389
  }
34987
- function isRecord3(value) {
34390
+ function isRecord5(value) {
34988
34391
  return typeof value === "object" && value !== null && !Array.isArray(value);
34989
34392
  }
34990
34393
  function hasExactKeys(value, keys) {
@@ -35009,11 +34412,11 @@ function isInstantOrNull(value) {
35009
34412
  return value === null || typeof value === "string" && INSTANT_SHAPE.test(value) && Number.isFinite(Date.parse(value));
35010
34413
  }
35011
34414
  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) {
34415
+ 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
34416
  return eventError();
35014
34417
  }
35015
34418
  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)) {
34419
+ 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
34420
  return eventError();
35018
34421
  }
35019
34422
  return { label: option.label, detail: option.detail };
@@ -35031,7 +34434,7 @@ function parseDecision(value) {
35031
34434
  return { ...base, status: "selected", optionIndex: value.optionIndex };
35032
34435
  }
35033
34436
  function parseArtifact(value) {
35034
- if (!isRecord3(value) || !isBoundedPlainString(value.name, COMPANION_ARTIFACT_NAME_MAX_LENGTH)) {
34437
+ if (!isRecord5(value) || !isBoundedPlainString(value.name, COMPANION_ARTIFACT_NAME_MAX_LENGTH)) {
35035
34438
  return eventError();
35036
34439
  }
35037
34440
  if (value.sizeBytes === void 0) {
@@ -35050,7 +34453,7 @@ function parseTurnArtifacts(value) {
35050
34453
  return value.map(parseArtifact);
35051
34454
  }
35052
34455
  function parseTurn(value) {
35053
- if (!isRecord3(value) || !isBoundedPlainString(value.id, COMPANION_TURN_ID_MAX_LENGTH) || value.role !== "user" && value.role !== "mate" || !isInstantOrNull(value.at)) {
34456
+ if (!isRecord5(value) || !isBoundedPlainString(value.id, COMPANION_TURN_ID_MAX_LENGTH) || value.role !== "user" && value.role !== "mate" || !isInstantOrNull(value.at)) {
35054
34457
  return eventError();
35055
34458
  }
35056
34459
  const keys = [
@@ -35129,7 +34532,7 @@ function isValidMateRoute(value) {
35129
34532
  }
35130
34533
  }
35131
34534
  function parseRequest(value) {
35132
- if (!isRecord3(value)) return requestError();
34535
+ if (!isRecord5(value)) return requestError();
35133
34536
  if (value.type === "roster") {
35134
34537
  if (!hasExactKeys(value, ["type"])) return requestError();
35135
34538
  return { type: "roster" };
@@ -35195,7 +34598,7 @@ function parseRequestLine(line2) {
35195
34598
  }
35196
34599
  }
35197
34600
  function parseMate(value) {
35198
- if (!isRecord3(value) || !hasExactKeys(value, [
34601
+ if (!isRecord5(value) || !hasExactKeys(value, [
35199
34602
  "personaKey",
35200
34603
  "agentName",
35201
34604
  "displayName",
@@ -35220,7 +34623,7 @@ function parseMate(value) {
35220
34623
  };
35221
34624
  }
35222
34625
  function parseEvent(value) {
35223
- if (!isRecord3(value)) return eventError();
34626
+ if (!isRecord5(value)) return eventError();
35224
34627
  if (value.type === "update") {
35225
34628
  if (!hasExactKeys(value, ["type", "installed", "available", "severity"]) || !isVersionOrNull(value.installed) || !isVersionOrNull(value.available) || !(value.severity === null || SEVERITIES2.has(value.severity))) {
35226
34629
  return eventError();
@@ -35359,7 +34762,7 @@ var PredispatchFailure = class extends Error {
35359
34762
  }
35360
34763
  reason;
35361
34764
  };
35362
- function isRecord4(value) {
34765
+ function isRecord6(value) {
35363
34766
  return typeof value === "object" && value !== null && !Array.isArray(value);
35364
34767
  }
35365
34768
  function hasControlCharacter(value) {
@@ -35429,16 +34832,16 @@ async function readJsonEnvelope(response) {
35429
34832
  if (error instanceof PredispatchFailure) throw error;
35430
34833
  throw new PredispatchFailure("not-connected");
35431
34834
  }
35432
- if (!isRecord4(envelope) || envelope.ok !== true || !("data" in envelope)) {
34835
+ if (!isRecord6(envelope) || envelope.ok !== true || !("data" in envelope)) {
35433
34836
  throw new PredispatchFailure("not-connected");
35434
34837
  }
35435
34838
  return envelope.data;
35436
34839
  }
35437
34840
  function decodeAgents(data) {
35438
- if (!isRecord4(data) || !Array.isArray(data.agents)) {
34841
+ if (!isRecord6(data) || !Array.isArray(data.agents)) {
35439
34842
  throw new PredispatchFailure("mate-unavailable");
35440
34843
  }
35441
- return data.agents.filter(isRecord4);
34844
+ return data.agents.filter(isRecord6);
35442
34845
  }
35443
34846
  function resolveAgentName(agents, personaKey) {
35444
34847
  const matches = agents.filter(
@@ -35460,18 +34863,18 @@ function requireAgentName(agents, personaKey) {
35460
34863
  return name;
35461
34864
  }
35462
34865
  function decodeConversationId(data) {
35463
- if (!isRecord4(data) || typeof data.id !== "string" || !OPAQUE_ID.test(data.id)) {
34866
+ if (!isRecord6(data) || typeof data.id !== "string" || !OPAQUE_ID.test(data.id)) {
35464
34867
  throw new PredispatchFailure("not-connected");
35465
34868
  }
35466
34869
  return data.id;
35467
34870
  }
35468
34871
  function decodeLastMessageId(data) {
35469
- if (!isRecord4(data) || !Array.isArray(data.messages)) {
34872
+ if (!isRecord6(data) || !Array.isArray(data.messages)) {
35470
34873
  throw new PredispatchFailure("not-connected");
35471
34874
  }
35472
34875
  const messages = data.messages;
35473
34876
  const last = messages.at(-1);
35474
- if (!isRecord4(last) || typeof last.id !== "string" || !OPAQUE_ID.test(last.id)) {
34877
+ if (!isRecord6(last) || typeof last.id !== "string" || !OPAQUE_ID.test(last.id)) {
35475
34878
  return void 0;
35476
34879
  }
35477
34880
  return last.id;
@@ -35557,7 +34960,7 @@ function toCompanionArtifacts(data) {
35557
34960
  const artifacts = [];
35558
34961
  for (const entry of data) {
35559
34962
  if (artifacts.length >= COMPANION_ARTIFACTS_MAX) break;
35560
- if (!isRecord4(entry) || typeof entry.name !== "string") continue;
34963
+ if (!isRecord6(entry) || typeof entry.name !== "string") continue;
35561
34964
  const name = boundPlainLine(entry.name, COMPANION_ARTIFACT_NAME_MAX_LENGTH);
35562
34965
  if (name.length === 0) continue;
35563
34966
  const size = entry.size_bytes;
@@ -35571,12 +34974,12 @@ function decodeMessageInstant(value) {
35571
34974
  return typeof value === "number" && Number.isFinite(value) && value > 0 && value < EPOCH_SECONDS_LIMIT ? new Date(Math.round(value) * 1e3).toISOString() : null;
35572
34975
  }
35573
34976
  function decodeTurns(data, limit2) {
35574
- if (!isRecord4(data) || !Array.isArray(data.messages)) {
34977
+ if (!isRecord6(data) || !Array.isArray(data.messages)) {
35575
34978
  throw new PredispatchFailure("not-connected");
35576
34979
  }
35577
34980
  const turns = [];
35578
34981
  for (const entry of data.messages) {
35579
- if (!isRecord4(entry)) continue;
34982
+ if (!isRecord6(entry)) continue;
35580
34983
  if (entry.role !== "user" && entry.role !== "assistant") continue;
35581
34984
  if (typeof entry.id !== "string" || !OPAQUE_ID.test(entry.id)) continue;
35582
34985
  if (typeof entry.content !== "string") continue;
@@ -35603,12 +35006,12 @@ function decodeDurableDecision(entry) {
35603
35006
  return toCompanionDecision(frame.directive);
35604
35007
  }
35605
35008
  function decodeRichTurns(data, limit2) {
35606
- if (!isRecord4(data) || !Array.isArray(data.messages)) {
35009
+ if (!isRecord6(data) || !Array.isArray(data.messages)) {
35607
35010
  throw new PredispatchFailure("not-connected");
35608
35011
  }
35609
35012
  const turns = [];
35610
35013
  for (const entry of data.messages) {
35611
- if (!isRecord4(entry)) continue;
35014
+ if (!isRecord6(entry)) continue;
35612
35015
  if (entry.role !== "user" && entry.role !== "assistant") continue;
35613
35016
  if (typeof entry.id !== "string" || !OPAQUE_ID.test(entry.id)) continue;
35614
35017
  if (typeof entry.content !== "string") continue;
@@ -35744,7 +35147,7 @@ async function drainAcceptedSse(response) {
35744
35147
  continue;
35745
35148
  }
35746
35149
  const parsed = JSON.parse(data);
35747
- if (!isRecord4(parsed) || typeof parsed.type !== "string" || parsed.type === "error") {
35150
+ if (!isRecord6(parsed) || typeof parsed.type !== "string" || parsed.type === "error") {
35748
35151
  throw new Error("invalid stream event");
35749
35152
  }
35750
35153
  }
@@ -35792,7 +35195,7 @@ async function streamAcceptedSse(response, now, onText, onData) {
35792
35195
  return;
35793
35196
  }
35794
35197
  const parsed = JSON.parse(data);
35795
- if (!isRecord4(parsed) || typeof parsed.type !== "string" || parsed.type === "error") {
35198
+ if (!isRecord6(parsed) || typeof parsed.type !== "string" || parsed.type === "error") {
35796
35199
  throw new Error("invalid stream event");
35797
35200
  }
35798
35201
  if (parsed.type === "text-delta" && typeof parsed.delta === "string" && parsed.delta.length > 0) {
@@ -36016,7 +35419,7 @@ async function classifyDecideRefusal(response) {
36016
35419
  let reason;
36017
35420
  try {
36018
35421
  const envelope = JSON.parse(await readBoundedText(response));
36019
- if (isRecord4(envelope) && isRecord4(envelope.error) && isRecord4(envelope.error.details)) {
35422
+ if (isRecord6(envelope) && isRecord6(envelope.error) && isRecord6(envelope.error.details)) {
36020
35423
  reason = envelope.error.details.reason;
36021
35424
  }
36022
35425
  } catch {
@@ -36401,14 +35804,14 @@ async function runCompanionBridge(stdin, stdout, stderr, deps = defaultDeps4) {
36401
35804
  return 3;
36402
35805
  }
36403
35806
  }
36404
- var CompanionBridgeCommand = class extends Command65 {
35807
+ var CompanionBridgeCommand = class extends Command66 {
36405
35808
  static paths = [["companion", "_bridge"]];
36406
35809
  /**
36407
35810
  * One process serving many requests instead of one per request, so the
36408
35811
  * session keeps its authenticated context between them. A CLI predating the
36409
35812
  * flag rejects it outright, which is how the app knows to fall back.
36410
35813
  */
36411
- serve = Option62.Boolean("--serve", false);
35814
+ serve = Option63.Boolean("--serve", false);
36412
35815
  async execute() {
36413
35816
  if (this.serve) {
36414
35817
  return runCompanionBridgeServe(
@@ -36426,7 +35829,7 @@ var CompanionBridgeCommand = class extends Command65 {
36426
35829
  };
36427
35830
 
36428
35831
  // src/commands/companion/status.ts
36429
- import { Command as Command66 } from "clipanion";
35832
+ import { Command as Command67 } from "clipanion";
36430
35833
  async function withTimeout(work, ms) {
36431
35834
  let timer;
36432
35835
  try {
@@ -36498,7 +35901,7 @@ Run: m8t companion install
36498
35901
  }
36499
35902
  var CompanionStatusCommand = class extends M8tCommand {
36500
35903
  static paths = [["companion", "status"]];
36501
- static usage = Command66.Usage({
35904
+ static usage = Command67.Usage({
36502
35905
  description: "Verify the installed desktop companion without launching it."
36503
35906
  });
36504
35907
  async executeCommand() {
@@ -36512,7 +35915,7 @@ var CompanionStatusCommand = class extends M8tCommand {
36512
35915
  };
36513
35916
 
36514
35917
  // src/commands/companion/repair.ts
36515
- import { Command as Command67, Option as Option63 } from "clipanion";
35918
+ import { Command as Command68, Option as Option64 } from "clipanion";
36516
35919
  async function runCompanionRepairCommand(stdout, repair) {
36517
35920
  const state = await repair();
36518
35921
  if (state.state === "not-released") {
@@ -36531,10 +35934,10 @@ async function runCompanionRepairCommand(stdout, repair) {
36531
35934
  }
36532
35935
  var CompanionRepairCommand = class extends M8tCommand {
36533
35936
  static paths = [["companion", "repair"]];
36534
- static usage = Command67.Usage({
35937
+ static usage = Command68.Usage({
36535
35938
  description: "Restore the desktop companions and start-at-login state."
36536
35939
  });
36537
- resourceGroup = Option63.String("--resource-group", {
35940
+ resourceGroup = Option64.String("--resource-group", {
36538
35941
  description: "Which deployment to bind to, when the subscription holds more than one."
36539
35942
  });
36540
35943
  async executeCommand() {
@@ -36548,7 +35951,7 @@ var CompanionRepairCommand = class extends M8tCommand {
36548
35951
  };
36549
35952
 
36550
35953
  // src/commands/companion/uninstall.ts
36551
- import { Command as Command68 } from "clipanion";
35954
+ import { Command as Command69 } from "clipanion";
36552
35955
  async function runCompanionUninstallCommand(stdout, uninstall) {
36553
35956
  const state = await uninstall();
36554
35957
  if (state.state !== "not-installed") {
@@ -36560,7 +35963,7 @@ async function runCompanionUninstallCommand(stdout, uninstall) {
36560
35963
  }
36561
35964
  var CompanionUninstallCommand = class extends M8tCommand {
36562
35965
  static paths = [["companion", "uninstall"]];
36563
- static usage = Command68.Usage({
35966
+ static usage = Command69.Usage({
36564
35967
  description: "Remove only this user's desktop companion installation."
36565
35968
  });
36566
35969
  async executeCommand() {
@@ -36639,6 +36042,7 @@ cli.register(BootstrapLaunchCommand);
36639
36042
  cli.register(BootstrapStatusCommand);
36640
36043
  cli.register(BootstrapReapCommand);
36641
36044
  cli.register(BootstrapUiCommand);
36045
+ cli.register(BootstrapProfileCommand);
36642
36046
  cli.register(BootstrapSeedProfileCommand);
36643
36047
  cli.register(TelemetryEnrollCommand);
36644
36048
  cli.register(CompanionBridgeCommand);