@m8t-stack/cli 0.2.4 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1139,7 +1139,7 @@ var init_enable_hosted_brain = __esm({
1139
1139
  import { Builtins, Cli } from "clipanion";
1140
1140
 
1141
1141
  // src/lib/package-version.ts
1142
- var CLI_VERSION = "0.2.4";
1142
+ var CLI_VERSION = "0.2.5";
1143
1143
 
1144
1144
  // src/lib/render-error.ts
1145
1145
  init_errors();
@@ -2219,10 +2219,10 @@ var BindListCommand = class extends M8tCommand {
2219
2219
  const qs = new URLSearchParams();
2220
2220
  if (this.channel) qs.set("channel", this.channel);
2221
2221
  if (this.status) qs.set("status", this.status);
2222
- const path27 = qs.toString().length > 0 ? `/api/bindings?${qs.toString()}` : "/api/bindings";
2222
+ const path28 = qs.toString().length > 0 ? `/api/bindings?${qs.toString()}` : "/api/bindings";
2223
2223
  const data = await apiCall(
2224
2224
  { gatewayUrl: ctx.gatewayUrl, gatewayClientId: ctx.gatewayClientId },
2225
- { method: "GET", path: path27 },
2225
+ { method: "GET", path: path28 },
2226
2226
  (msg) => this.context.stderr.write(msg + "\n")
2227
2227
  );
2228
2228
  if (mode === "json") {
@@ -4039,6 +4039,7 @@ function materializeBrainTree(opts) {
4039
4039
 
4040
4040
  // src/lib/github-app-repo.ts
4041
4041
  init_errors();
4042
+ init_esm2();
4042
4043
  import { spawn as spawn3 } from "child_process";
4043
4044
  async function createBlankRepo(args) {
4044
4045
  const res = await fetch(`https://api.github.com/orgs/${args.org}/repos`, {
@@ -4097,6 +4098,79 @@ async function pushSeed(args) {
4097
4098
  await run("git", ["remote", "add", "origin", remote], args.sourceDir, env);
4098
4099
  await run("git", ["push", "-q", "origin", args.branch], args.sourceDir, env);
4099
4100
  }
4101
+ var GH_API = "https://api.github.com";
4102
+ var GH_REQUEST_TIMEOUT_MS = 3e4;
4103
+ function ghHeaders(token, scheme = "token") {
4104
+ return {
4105
+ Authorization: `${scheme} ${token}`,
4106
+ Accept: "application/vnd.github+json",
4107
+ "X-GitHub-Api-Version": "2022-11-28",
4108
+ "User-Agent": "m8t",
4109
+ "Content-Type": "application/json"
4110
+ };
4111
+ }
4112
+ async function mintInstallationTokenFromPem(args) {
4113
+ const doFetch = args.fetchImpl ?? fetch;
4114
+ const jwt = signAppJwt({ appId: args.appId, privateKeyPem: args.privateKeyPem });
4115
+ const res = await doFetch(`${GH_API}/app/installations/${args.installationId}/access_tokens`, {
4116
+ method: "POST",
4117
+ headers: ghHeaders(jwt, "Bearer"),
4118
+ signal: AbortSignal.timeout(GH_REQUEST_TIMEOUT_MS)
4119
+ });
4120
+ const text = await res.text();
4121
+ if (!res.ok) {
4122
+ throw new LocalCliError({
4123
+ code: "GH_APP_TOKEN_MINT_FAILED",
4124
+ message: `POST /app/installations/${args.installationId}/access_tokens HTTP ${res.status.toString()}: ${text.slice(0, 300)}`
4125
+ });
4126
+ }
4127
+ return JSON.parse(text).token;
4128
+ }
4129
+ async function readRepoFileViaApp(args) {
4130
+ const doFetch = args.fetchImpl ?? fetch;
4131
+ const res = await doFetch(`${GH_API}/repos/${args.repo}/contents/${args.path}`, { headers: ghHeaders(args.token), signal: AbortSignal.timeout(GH_REQUEST_TIMEOUT_MS) });
4132
+ if (res.status === 404) return null;
4133
+ const text = await res.text();
4134
+ if (!res.ok) {
4135
+ throw new LocalCliError({
4136
+ code: "GH_APP_FILE_READ_FAILED",
4137
+ message: `GET /repos/${args.repo}/contents/${args.path} HTTP ${res.status.toString()}: ${text.slice(0, 300)}`
4138
+ });
4139
+ }
4140
+ const data = JSON.parse(text);
4141
+ return Buffer.from(data.content ?? "", "base64").toString("utf8");
4142
+ }
4143
+ async function commitFilesViaApp(args) {
4144
+ const doFetch = args.fetchImpl ?? fetch;
4145
+ const H = ghHeaders(args.token);
4146
+ const api = async (p, init) => {
4147
+ const res = await doFetch(`${GH_API}/repos/${args.repo}${p}`, init ? { ...init, headers: H, signal: AbortSignal.timeout(GH_REQUEST_TIMEOUT_MS) } : { headers: H, signal: AbortSignal.timeout(GH_REQUEST_TIMEOUT_MS) });
4148
+ const text = await res.text();
4149
+ if (!res.ok) {
4150
+ throw new LocalCliError({
4151
+ code: "GH_APP_COMMIT_FAILED",
4152
+ message: `${init?.method ?? "GET"} ${p} HTTP ${res.status.toString()}: ${text.slice(0, 300)}`
4153
+ });
4154
+ }
4155
+ return text ? JSON.parse(text) : {};
4156
+ };
4157
+ const ref = await api(`/git/ref/heads/${args.branch}`);
4158
+ const baseCommitSha = ref.object.sha;
4159
+ const baseCommit = await api(`/git/commits/${baseCommitSha}`);
4160
+ const blobs = await Promise.all(args.files.map((f) => api(`/git/blobs`, { method: "POST", body: JSON.stringify({ content: f.content, encoding: "utf-8" }) })));
4161
+ const tree = await api(`/git/trees`, {
4162
+ method: "POST",
4163
+ body: JSON.stringify({
4164
+ base_tree: baseCommit.tree.sha,
4165
+ tree: args.files.map((f, i) => ({ path: f.path, mode: "100644", type: "blob", sha: blobs[i].sha }))
4166
+ })
4167
+ });
4168
+ const commit = await api(`/git/commits`, {
4169
+ method: "POST",
4170
+ body: JSON.stringify({ message: args.message, tree: tree.sha, parents: [baseCommitSha] })
4171
+ });
4172
+ await api(`/git/refs/heads/${args.branch}`, { method: "PATCH", body: JSON.stringify({ sha: commit.sha }) });
4173
+ }
4100
4174
 
4101
4175
  // src/commands/brain/create.ts
4102
4176
  function stageAsGitRepo(dir) {
@@ -7035,8 +7109,8 @@ async function fetchPublicTags(repo, fetchImpl = fetch) {
7035
7109
  hint: "Pass a ghcr.io/<owner>/<name> repo, or use the BYOC private-ACR deploy flow."
7036
7110
  });
7037
7111
  }
7038
- const path27 = repo.replace(/^ghcr\.io\//, "");
7039
- const tokenRes = await fetchImpl(`https://ghcr.io/token?scope=repository:${path27}:pull`);
7112
+ const path28 = repo.replace(/^ghcr\.io\//, "");
7113
+ const tokenRes = await fetchImpl(`https://ghcr.io/token?scope=repository:${path28}:pull`);
7040
7114
  if (!tokenRes.ok) {
7041
7115
  throw new LocalCliError({
7042
7116
  code: "PLATFORM_GHCR_TOKEN_FAILED",
@@ -7052,7 +7126,7 @@ async function fetchPublicTags(repo, fetchImpl = fetch) {
7052
7126
  hint: "Make the package public (deploy/ghcr-package-visibility.md), then retry."
7053
7127
  });
7054
7128
  }
7055
- const tagsRes = await fetchImpl(`https://ghcr.io/v2/${path27}/tags/list`, {
7129
+ const tagsRes = await fetchImpl(`https://ghcr.io/v2/${path28}/tags/list`, {
7056
7130
  headers: { Authorization: `Bearer ${token}` }
7057
7131
  });
7058
7132
  if (!tagsRes.ok) {
@@ -7717,9 +7791,9 @@ function flattenDelta(delta, prefix = "") {
7717
7791
  const out = [];
7718
7792
  for (const d of delta) {
7719
7793
  const segment = /^[0-9]+$/.test(d.path) ? `[${d.path}]` : prefix ? `.${d.path}` : d.path;
7720
- const path27 = prefix ? `${prefix}${segment}` : d.path;
7721
- if (d.children && d.children.length > 0) out.push(...flattenDelta(d.children, path27));
7722
- else out.push({ ...d, path: path27 });
7794
+ const path28 = prefix ? `${prefix}${segment}` : d.path;
7795
+ if (d.children && d.children.length > 0) out.push(...flattenDelta(d.children, path28));
7796
+ else out.push({ ...d, path: path28 });
7723
7797
  }
7724
7798
  return out;
7725
7799
  }
@@ -10149,7 +10223,7 @@ function buildAdvancePlan(worker, cursor, physicalPks, consumedRowsByPk, failedT
10149
10223
  // ../../packages/brain/engine/dist/esm/dream/writer.js
10150
10224
  var API2 = "https://api.github.com";
10151
10225
  var WRITER_ALLOWED_PREFIX = /^(memory|inbox|quarantine|artifacts)\//;
10152
- var isAllowedWritePath = (path27) => WRITER_ALLOWED_PREFIX.test(path27) && !path27.split("/").includes("..");
10226
+ var isAllowedWritePath = (path28) => WRITER_ALLOWED_PREFIX.test(path28) && !path28.split("/").includes("..");
10153
10227
  var unquote = (s) => s.trim().replace(/^["']|["']$/g, "");
10154
10228
  function parseFrontmatter(content) {
10155
10229
  const m = /^---\n([\s\S]*?)\n---/.exec(content);
@@ -10188,8 +10262,8 @@ function parseFrontmatter(content) {
10188
10262
  function makeGitDataWriter(args) {
10189
10263
  const doFetch = args.fetchImpl ?? fetch;
10190
10264
  const repoPath = args.repository;
10191
- async function gh(token, method, path27, body) {
10192
- const res = await doFetch(`${API2}/repos/${repoPath}${path27}`, {
10265
+ async function gh(token, method, path28, body) {
10266
+ const res = await doFetch(`${API2}/repos/${repoPath}${path28}`, {
10193
10267
  method,
10194
10268
  headers: {
10195
10269
  Authorization: `Bearer ${token}`,
@@ -10202,8 +10276,8 @@ function makeGitDataWriter(args) {
10202
10276
  const text = await res.text();
10203
10277
  return { status: res.status, ok: res.ok, json: text ? JSON.parse(text) : {} };
10204
10278
  }
10205
- async function readFileWith(token, path27) {
10206
- const encodedPath = path27.split("/").map(encodeURIComponent).join("/");
10279
+ async function readFileWith(token, path28) {
10280
+ const encodedPath = path28.split("/").map(encodeURIComponent).join("/");
10207
10281
  const r = await gh(token, "GET", `/contents/${encodedPath}?ref=${args.branch}`);
10208
10282
  if (r.status === 404)
10209
10283
  return null;
@@ -10219,9 +10293,9 @@ function makeGitDataWriter(args) {
10219
10293
  throw new Error(`fetchTip: HTTP ${String(r.status)}`);
10220
10294
  return r.json.object.sha;
10221
10295
  },
10222
- async readFile(path27) {
10296
+ async readFile(path28) {
10223
10297
  const token = await args.mintToken();
10224
- return readFileWith(token, path27);
10298
+ return readFileWith(token, path28);
10225
10299
  },
10226
10300
  async listMemory() {
10227
10301
  const token = await args.mintToken();
@@ -10230,9 +10304,9 @@ function makeGitDataWriter(args) {
10230
10304
  return [];
10231
10305
  const paths = r.json.tree.filter((e) => e.type === "blob" && e.path.startsWith("memory/") && e.path.endsWith(".md")).map((e) => e.path);
10232
10306
  const out = [];
10233
- for (const path27 of paths) {
10234
- const content = await readFileWith(token, path27);
10235
- out.push({ path: path27, frontmatter: content ? parseFrontmatter(content) : {} });
10307
+ for (const path28 of paths) {
10308
+ const content = await readFileWith(token, path28);
10309
+ out.push({ path: path28, frontmatter: content ? parseFrontmatter(content) : {} });
10236
10310
  }
10237
10311
  return out;
10238
10312
  },
@@ -10669,7 +10743,7 @@ async function loadMemoryContext(brain) {
10669
10743
  return {
10670
10744
  files,
10671
10745
  indexEntries,
10672
- originOf: (path27) => originByPath.get(path27) ?? "worker"
10746
+ originOf: (path28) => originByPath.get(path28) ?? "worker"
10673
10747
  };
10674
10748
  }
10675
10749
 
@@ -10690,14 +10764,14 @@ function checkEvidenceMembership(delta, harvested) {
10690
10764
  }
10691
10765
  var BatchAbort = class extends Error {
10692
10766
  path;
10693
- constructor(path27, message) {
10767
+ constructor(path28, message) {
10694
10768
  super(message);
10695
- this.path = path27;
10769
+ this.path = path28;
10696
10770
  this.name = "BatchAbort";
10697
10771
  }
10698
10772
  };
10699
- var isIndexFile = (path27) => /(^|\/)[^/]*_index\.md$/.test(path27);
10700
- var isMemoryIndex = (path27) => path27 === "memory/MEMORY.md";
10773
+ var isIndexFile = (path28) => /(^|\/)[^/]*_index\.md$/.test(path28);
10774
+ var isMemoryIndex = (path28) => path28 === "memory/MEMORY.md";
10701
10775
  function targetPath(delta) {
10702
10776
  switch (delta.verb) {
10703
10777
  case "new":
@@ -10714,29 +10788,29 @@ function targetPath(delta) {
10714
10788
  }
10715
10789
  async function checkOriginTier(delta, lookup) {
10716
10790
  const evidence = evidenceOf(delta) ?? [];
10717
- const path27 = targetPath(delta);
10718
- if (path27 === null)
10791
+ const path28 = targetPath(delta);
10792
+ if (path28 === null)
10719
10793
  return { ok: true };
10720
- if (isIndexFile(path27) && !isMemoryIndex(path27)) {
10721
- return { ok: false, flagged: { kind: "flagged", path: path27, tension: `refused: ${path27} is an index file (only memory/MEMORY.md is editable)`, evidence } };
10794
+ if (isIndexFile(path28) && !isMemoryIndex(path28)) {
10795
+ return { ok: false, flagged: { kind: "flagged", path: path28, tension: `refused: ${path28} is an index file (only memory/MEMORY.md is editable)`, evidence } };
10722
10796
  }
10723
- const target = await lookup(path27);
10724
- const isStructural = path27.startsWith("references/");
10797
+ const target = await lookup(path28);
10798
+ const isStructural = path28.startsWith("references/");
10725
10799
  const origin = target?.frontmatter.origin ?? (isStructural ? "operator" : "worker");
10726
10800
  const editable = origin === "worker" || origin === "dream";
10727
10801
  if (editable)
10728
10802
  return { ok: true };
10729
10803
  const removesOperatorTarget = delta.verb === "retract" || delta.verb === "supersede" && delta.oldPath !== delta.newPath;
10730
10804
  if (removesOperatorTarget) {
10731
- throw new BatchAbort(path27, `supersede/retract OLD target ${path27} is origin: ${origin} \u2014 aborting batch (never half-apply)`);
10805
+ throw new BatchAbort(path28, `supersede/retract OLD target ${path28} is origin: ${origin} \u2014 aborting batch (never half-apply)`);
10732
10806
  }
10733
- return { ok: false, flagged: { kind: "flagged", path: path27, tension: `operator-origin (${origin}); flag-only`, evidence } };
10807
+ return { ok: false, flagged: { kind: "flagged", path: path28, tension: `operator-origin (${origin}); flag-only`, evidence } };
10734
10808
  }
10735
10809
  var ALLOWED_MEMORY = /^memory\/.+\.md$/;
10736
10810
  var ALLOWED_INBOX = /^inbox\//;
10737
10811
  var ALLOWED_QUARANTINE = /^quarantine\//;
10738
10812
  var ALLOWED_FLAG = /^memory\//;
10739
- var hasTraversal = (path27) => path27.split("/").includes("..");
10813
+ var hasTraversal = (path28) => path28.split("/").includes("..");
10740
10814
  function checkPathAllowed(delta) {
10741
10815
  const evidence = evidenceOf(delta) ?? [];
10742
10816
  const reject = (detail) => ({ ok: false, rejected: { kind: "rejected", detail, evidence } });
@@ -10843,7 +10917,7 @@ ${inject}
10843
10917
  ---
10844
10918
  `);
10845
10919
  }
10846
- var indexLine = (path27, title, date, tags) => `- \`${path27}\` \u2014 **${title}**: ${title}. (${date} \xB7 ${tags.join(", ")})`;
10920
+ var indexLine = (path28, title, date, tags) => `- \`${path28}\` \u2014 **${title}**: ${title}. (${date} \xB7 ${tags.join(", ")})`;
10847
10921
  function splitIndex(index) {
10848
10922
  const all = index.split("\n");
10849
10923
  const firstLineIdx = all.findIndex((l) => l.startsWith("- `memory/"));
@@ -11016,16 +11090,16 @@ function defaultDreamSeams(opts = {}) {
11016
11090
  const mem = await memory(deps.brain);
11017
11091
  const harvested = harvestedIds(input);
11018
11092
  const ctx = { readFile: (p) => deps.brain.readFile(p), at: deps.clock() };
11019
- const lookup = (path27) => Promise.resolve(mem.files.find((f) => f.path === path27) ?? null);
11093
+ const lookup = (path28) => Promise.resolve(mem.files.find((f) => f.path === path28) ?? null);
11020
11094
  const digest = [...pendingRejected];
11021
11095
  let changes = [];
11022
11096
  let indexEdit;
11023
11097
  try {
11024
11098
  for (const raw of deltas) {
11025
11099
  const delta = forcePolicyQuarantine(raw);
11026
- const path27 = checkPathAllowed(delta);
11027
- if (!path27.ok) {
11028
- digest.push(path27.rejected);
11100
+ const path28 = checkPathAllowed(delta);
11101
+ if (!path28.ok) {
11102
+ digest.push(path28.rejected);
11029
11103
  continue;
11030
11104
  }
11031
11105
  const ev = checkEvidenceMembership(delta, harvested);
@@ -12686,10 +12760,189 @@ var BootstrapReapCommand = class extends M8tCommand {
12686
12760
 
12687
12761
  // src/commands/bootstrap/finish.ts
12688
12762
  import * as fs24 from "fs/promises";
12763
+ import * as os14 from "os";
12764
+ import * as path25 from "path";
12765
+ import { Command as Command48, Option as Option46 } from "clipanion";
12766
+ init_errors();
12767
+
12768
+ // src/lib/company-profile-seed.ts
12769
+ import { spawn as spawn6 } from "child_process";
12770
+ import { closeSync, openSync, readFileSync as readFileSync16 } from "fs";
12689
12771
  import * as os13 from "os";
12690
12772
  import * as path24 from "path";
12691
- import { Command as Command48, Option as Option46 } from "clipanion";
12692
12773
  init_errors();
12774
+
12775
+ // src/lib/onboarding-profile.ts
12776
+ var COMPANY_PROFILE_PATH = "memory/company-profile.md";
12777
+ var DEFAULT_MEMORY_INDEX_HEADER = [
12778
+ `# Memory index`,
12779
+ ``,
12780
+ `> Your memories, newest first. The summary on each line is usually enough to answer \u2014 open the linked file only when you need full detail. Each path is repo-root; copy it verbatim, never invent one.`,
12781
+ ``
12782
+ ].join("\n");
12783
+ function parseOnboardingBlock(text) {
12784
+ const candidates = [
12785
+ ...[...text.matchAll(/```(?:json)?\s*([\s\S]*?)```/g)].map((m) => m[1]),
12786
+ text
12787
+ ].reverse();
12788
+ for (const c of candidates) {
12789
+ try {
12790
+ const o = JSON.parse(c.trim()).m8t_onboarding;
12791
+ if (o?.company_stage && o.context) return o;
12792
+ } catch {
12793
+ }
12794
+ }
12795
+ return null;
12796
+ }
12797
+ async function safeJson(res) {
12798
+ try {
12799
+ const t = await res.text();
12800
+ return t ? JSON.parse(t) : null;
12801
+ } catch {
12802
+ return null;
12803
+ }
12804
+ }
12805
+ async function findOnboardingProfile(args) {
12806
+ const doFetch = args.fetchImpl ?? fetch;
12807
+ const H = { Authorization: `Bearer ${args.token}` };
12808
+ const listRes = await doFetch(`${args.endpoint}/conversations?api-version=v1&order=desc&limit=20`, { headers: H });
12809
+ const list = await safeJson(listRes);
12810
+ const convs = (list?.data ?? []).filter((c) => c.metadata?.agent === "stacey-intake");
12811
+ for (const conv of convs) {
12812
+ const itemsRes = await doFetch(`${args.endpoint}/conversations/${conv.id}/items?api-version=v1`, { headers: H });
12813
+ const items = await safeJson(itemsRes);
12814
+ const text = (items?.data ?? []).flatMap((m) => (m.content ?? []).map((c) => c.text ?? "")).join("\n");
12815
+ const block = parseOnboardingBlock(text);
12816
+ if (block) return { hadIntake: true, block };
12817
+ }
12818
+ return { hadIntake: convs.length > 0, block: null };
12819
+ }
12820
+ function renderCompanyProfile(block, now = (/* @__PURE__ */ new Date()).toISOString()) {
12821
+ const dash = (v) => v?.trim() ? v.trim() : "\u2014";
12822
+ const profileMd = [
12823
+ `---`,
12824
+ `type: memory`,
12825
+ `title: Company profile`,
12826
+ `created: ${now}`,
12827
+ `updated: ${now}`,
12828
+ `tags: [company-profile, onboarding, founder]`,
12829
+ `origin: operator`,
12830
+ `---`,
12831
+ ``,
12832
+ `# Company profile`,
12833
+ ``,
12834
+ `_Seeded from the onboarding questionnaire._`,
12835
+ ``,
12836
+ `- **Stage:** ${dash(block.company_stage)}`,
12837
+ `- **ICP:** ${dash(block.icp)}`,
12838
+ `- **Industry:** ${dash(block.industry)}`,
12839
+ `- **Team size:** ${dash(block.team_size)}`,
12840
+ ``,
12841
+ `## Context`,
12842
+ ``,
12843
+ block.context.trim(),
12844
+ ``
12845
+ ].join("\n");
12846
+ const bits = [block.company_stage.trim(), block.industry?.trim(), block.icp?.trim() ? `ICP ${block.icp.trim()}` : void 0].filter(Boolean).join(" \xB7 ");
12847
+ const memoryIndexLine = `- \`${COMPANY_PROFILE_PATH}\` \u2014 **Company profile**: ${bits}. (seeded from onboarding)`;
12848
+ return { profileMd, memoryIndexLine };
12849
+ }
12850
+ function upsertMemoryIndex(existing, line2) {
12851
+ const lines = existing.split("\n");
12852
+ const existingIdx = lines.findIndex((l) => l.includes(`\`${COMPANY_PROFILE_PATH}\``));
12853
+ if (existingIdx >= 0) {
12854
+ lines[existingIdx] = line2;
12855
+ return lines.join("\n");
12856
+ }
12857
+ const firstItemIdx = lines.findIndex((l) => /^-\s+`memory\//.test(l));
12858
+ if (firstItemIdx >= 0) {
12859
+ lines.splice(firstItemIdx, 0, line2);
12860
+ return lines.join("\n");
12861
+ }
12862
+ return existing.replace(/\s*$/, "\n\n") + line2 + "\n";
12863
+ }
12864
+
12865
+ // src/lib/company-profile-seed.ts
12866
+ function readGithubAppCreds(credsPath) {
12867
+ const p = credsPath ?? path24.join(os13.homedir(), ".m8t-stack", "github-app.json");
12868
+ try {
12869
+ return JSON.parse(readFileSync16(p, "utf8"));
12870
+ } catch {
12871
+ return null;
12872
+ }
12873
+ }
12874
+ async function resolveSeedContext(opts) {
12875
+ const appCreds = opts.appCredsOverride ?? readGithubAppCreds(opts.credsPath);
12876
+ if (!appCreds) return null;
12877
+ let endpoint = opts.endpointOverride;
12878
+ if (!endpoint) {
12879
+ const state = await readBootstrapState();
12880
+ if (!state) {
12881
+ throw new LocalCliError({ code: "BOOTSTRAP_NO_STATE", message: "No bootstrap state found.", hint: "Run 'm8t bootstrap launch' first, or pass --endpoint." });
12882
+ }
12883
+ const doc = await readStatusBlob({ saName: state.statusSaName, resourceGroup: state.resourceGroup, subscriptionId: state.subscriptionId });
12884
+ endpoint = doc.result?.foundryEndpoint;
12885
+ if (!endpoint) {
12886
+ throw new LocalCliError({ code: "SEED_NO_ENDPOINT", message: "Could not resolve the Foundry endpoint from the install status.", hint: "Pass --endpoint <foundryEndpoint>." });
12887
+ }
12888
+ }
12889
+ return { endpoint, brainRepo: opts.brainOverride ?? `${appCreds.org}/stacey-brain`, appCreds };
12890
+ }
12891
+ async function applyProfileToBrain(args) {
12892
+ const token = await mintInstallationTokenFromPem({
12893
+ appId: args.appCreds.appId,
12894
+ privateKeyPem: readFileSync16(args.appCreds.pemPath, "utf8"),
12895
+ installationId: args.appCreds.installationId,
12896
+ fetchImpl: args.fetchImpl
12897
+ });
12898
+ const { profileMd, memoryIndexLine } = renderCompanyProfile(args.block, args.now);
12899
+ const existingIndex = await readRepoFileViaApp({ token, repo: args.brainRepo, path: "memory/MEMORY.md", fetchImpl: args.fetchImpl }) ?? DEFAULT_MEMORY_INDEX_HEADER;
12900
+ await commitFilesViaApp({
12901
+ token,
12902
+ repo: args.brainRepo,
12903
+ branch: args.branch,
12904
+ message: "seed(brain): company profile from onboarding questionnaire",
12905
+ files: [
12906
+ { path: COMPANY_PROFILE_PATH, content: profileMd },
12907
+ { path: "memory/MEMORY.md", content: upsertMemoryIndex(existingIndex, memoryIndexLine) }
12908
+ ],
12909
+ fetchImpl: args.fetchImpl
12910
+ });
12911
+ }
12912
+ function spawnDetachedSeedWatch() {
12913
+ const logPath = path24.join(os13.homedir(), ".m8t-stack", "seed-profile.log");
12914
+ const fd = openSync(logPath, "a");
12915
+ try {
12916
+ const child = spawn6(process.execPath, [process.argv[1] ?? "", "bootstrap", "seed-profile", "--watch"], {
12917
+ detached: true,
12918
+ stdio: ["ignore", fd, fd]
12919
+ });
12920
+ child.unref();
12921
+ } finally {
12922
+ closeSync(fd);
12923
+ }
12924
+ }
12925
+ async function reactiveSeedOnFinish(args) {
12926
+ const ctx = await resolveSeedContext({ endpointOverride: args.endpoint, appCredsOverride: args.appCredsOverride });
12927
+ if (!ctx) return;
12928
+ const token = await (args.getFoundryTokenImpl ?? getFoundryToken)();
12929
+ const { hadIntake, block } = await findOnboardingProfile({ endpoint: ctx.endpoint, token, fetchImpl: args.fetchImpl });
12930
+ if (block) {
12931
+ await applyProfileToBrain({ block, brainRepo: ctx.brainRepo, branch: "main", appCreds: ctx.appCreds, fetchImpl: args.fetchImpl });
12932
+ args.stdout(`${colors.success("\u2713")} Clever Stacey now knows your company (seeded ${ctx.brainRepo}).
12933
+ `);
12934
+ return;
12935
+ }
12936
+ if (!hadIntake) return;
12937
+ (args.spawnWatch ?? spawnDetachedSeedWatch)();
12938
+ args.stdout(
12939
+ `${colors.dim("\u2139 Stacey will pick up your company profile when you finish the questionnaire (watching in the background).")}
12940
+ ${colors.hint("or run:")} m8t bootstrap seed-profile
12941
+ `
12942
+ );
12943
+ }
12944
+
12945
+ // src/commands/bootstrap/finish.ts
12693
12946
  var BootstrapFinishCommand = class extends M8tCommand {
12694
12947
  static paths = [["bootstrap", "finish"]];
12695
12948
  static usage = Command48.Usage({
@@ -12713,9 +12966,9 @@ var BootstrapFinishCommand = class extends M8tCommand {
12713
12966
  });
12714
12967
  }
12715
12968
  const repoRoot = (typeof this.repoRoot === "string" ? this.repoRoot : void 0) ?? process.cwd();
12716
- const markerDir = path24.join(os13.homedir(), ".m8t-stack");
12969
+ const markerDir = path25.join(os14.homedir(), ".m8t-stack");
12717
12970
  await fs24.mkdir(markerDir, { recursive: true });
12718
- await fs24.writeFile(path24.join(markerDir, "repo-root"), `${repoRoot}
12971
+ await fs24.writeFile(path25.join(markerDir, "repo-root"), `${repoRoot}
12719
12972
  `, "utf8");
12720
12973
  const subscriptionId = (typeof this.subscription === "string" ? this.subscription : void 0) ?? state.subscriptionId;
12721
12974
  const d = await discoverGateway({ subscriptionId, interactive: false });
@@ -12739,14 +12992,25 @@ ${colors.field("Finish wiring your coding agent:")}
12739
12992
  ${colors.field("Then open a brand new chat/session")} \u2014 new skills + MCP servers load only on a fresh start.
12740
12993
  `
12741
12994
  );
12995
+ try {
12996
+ await reactiveSeedOnFinish({
12997
+ endpoint: doc.result?.foundryEndpoint,
12998
+ stdout: (s) => this.context.stdout.write(s)
12999
+ });
13000
+ } catch (e) {
13001
+ this.context.stderr.write(
13002
+ ` ${colors.dim(`(company-profile seed deferred: ${e instanceof Error ? e.message : String(e)} \u2014 run 'm8t bootstrap seed-profile' later)`)}
13003
+ `
13004
+ );
13005
+ }
12742
13006
  return 0;
12743
13007
  }
12744
13008
  };
12745
13009
 
12746
13010
  // src/commands/bootstrap/ui.ts
12747
13011
  import * as fs26 from "fs";
12748
- import * as os15 from "os";
12749
- import * as path26 from "path";
13012
+ import * as os16 from "os";
13013
+ import * as path27 from "path";
12750
13014
  import { Command as Command49, Option as Option47 } from "clipanion";
12751
13015
  import { DefaultAzureCredential as DefaultAzureCredential17 } from "@azure/identity";
12752
13016
  init_errors();
@@ -12754,9 +13018,9 @@ init_errors();
12754
13018
  // src/lib/bootstrap-ui.ts
12755
13019
  import * as fs25 from "fs";
12756
13020
  import * as net from "net";
12757
- import * as os14 from "os";
12758
- import * as path25 from "path";
12759
- import { spawn as spawn6 } from "child_process";
13021
+ import * as os15 from "os";
13022
+ import * as path26 from "path";
13023
+ import { spawn as spawn7 } from "child_process";
12760
13024
  init_errors();
12761
13025
  init_rbac();
12762
13026
  async function resolveFoundryEndpointWithWait(args, opts = {}) {
@@ -12814,7 +13078,7 @@ async function ensureFounderFoundryRole(args) {
12814
13078
  });
12815
13079
  }
12816
13080
  function writeWebEnvLocal(args) {
12817
- const envPath = path25.join(args.repoRoot, "apps", "web", ".env.local");
13081
+ const envPath = path26.join(args.repoRoot, "apps", "web", ".env.local");
12818
13082
  if (fs25.existsSync(envPath)) {
12819
13083
  fs25.copyFileSync(envPath, envPath + ".bak");
12820
13084
  }
@@ -12845,7 +13109,7 @@ function assertNodeVersion(versionString = process.version) {
12845
13109
  }
12846
13110
  function runInherit(cmd, cmdArgs, cwd, failCode) {
12847
13111
  return new Promise((resolve3, reject) => {
12848
- const child = spawn6(cmd, cmdArgs, { cwd, stdio: "inherit", shell: process.platform === "win32" });
13112
+ const child = spawn7(cmd, cmdArgs, { cwd, stdio: "inherit", shell: process.platform === "win32" });
12849
13113
  child.on("error", (e) => {
12850
13114
  reject(new LocalCliError({ code: failCode, message: `Failed to start '${cmd}': ${e.message}` }));
12851
13115
  });
@@ -12871,11 +13135,11 @@ async function installWebDeps(repoRoot) {
12871
13135
  });
12872
13136
  await runInherit("pnpm", ["install"], repoRoot, "BOOTSTRAP_UI_INSTALL_FAILED");
12873
13137
  }
12874
- function onboardingUiPaths(home = os14.homedir()) {
12875
- const dir = path25.join(home, ".m8t-stack");
13138
+ function onboardingUiPaths(home = os15.homedir()) {
13139
+ const dir = path26.join(home, ".m8t-stack");
12876
13140
  return {
12877
- logPath: path25.join(dir, "onboarding-ui.log"),
12878
- pidPath: path25.join(dir, "onboarding-ui.pid")
13141
+ logPath: path26.join(dir, "onboarding-ui.log"),
13142
+ pidPath: path26.join(dir, "onboarding-ui.pid")
12879
13143
  };
12880
13144
  }
12881
13145
  async function serveOnboardingUiDetached(args) {
@@ -12899,9 +13163,9 @@ async function serveOnboardingUiDetached(args) {
12899
13163
  if (await isPortOpen()) {
12900
13164
  return { alreadyRunning: true, logPath };
12901
13165
  }
12902
- fs25.mkdirSync(path25.dirname(logPath), { recursive: true });
13166
+ fs25.mkdirSync(path26.dirname(logPath), { recursive: true });
12903
13167
  const fd = fs25.openSync(logPath, "a");
12904
- const child = spawn6("pnpm", ["--filter", "web", "dev"], {
13168
+ const child = spawn7("pnpm", ["--filter", "web", "dev"], {
12905
13169
  cwd: args.repoRoot,
12906
13170
  detached: true,
12907
13171
  stdio: ["ignore", fd, fd],
@@ -12926,7 +13190,7 @@ async function serveOnboardingUiDetached(args) {
12926
13190
  }
12927
13191
  return { alreadyRunning: false, logPath };
12928
13192
  }
12929
- function stopOnboardingUi(home = os14.homedir()) {
13193
+ function stopOnboardingUi(home = os15.homedir()) {
12930
13194
  const { pidPath } = onboardingUiPaths(home);
12931
13195
  let pidStr;
12932
13196
  try {
@@ -13058,7 +13322,7 @@ var BootstrapUiCommand = class extends M8tCommand {
13058
13322
  this.context.stderr.write(` ${colors.dim(m)}
13059
13323
  `);
13060
13324
  };
13061
- if (fs26.existsSync(path26.join(os15.homedir(), ".m8t-stack", "config.yaml"))) {
13325
+ if (fs26.existsSync(path27.join(os16.homedir(), ".m8t-stack", "config.yaml"))) {
13062
13326
  out(colors.error("\u26A0 an existing ~/.m8t-stack/config.yaml will override the onboarding app registration in apps/web \u2014 if Microsoft sign-in fails, move it aside and retry."));
13063
13327
  }
13064
13328
  const credential2 = new DefaultAzureCredential17();
@@ -13123,6 +13387,64 @@ var BootstrapUiCommand = class extends M8tCommand {
13123
13387
  }
13124
13388
  };
13125
13389
 
13390
+ // src/commands/bootstrap/seed-profile.ts
13391
+ import { Command as Command50, Option as Option48 } from "clipanion";
13392
+ var BootstrapSeedProfileCommand = class extends M8tCommand {
13393
+ static paths = [["bootstrap", "seed-profile"]];
13394
+ static usage = Command50.Usage({
13395
+ description: "Seed Clever Stacey's brain with the company profile from the onboarding questionnaire.",
13396
+ details: "Reads the latest stacey-intake conversation, renders memory/company-profile.md (+ its MEMORY.md index line), and commits both to <org>/stacey-brain via the GitHub App. Idempotent. --watch polls until the founder completes the questionnaire.",
13397
+ examples: [
13398
+ ["Seed now (idempotent)", "$0 bootstrap seed-profile"],
13399
+ ["Wait for the founder to finish", "$0 bootstrap seed-profile --watch"]
13400
+ ]
13401
+ });
13402
+ endpoint = Option48.String("--endpoint", { description: "Override the Foundry endpoint (else read from the install status)." });
13403
+ brain = Option48.String("--brain", { description: "Override the brain repo (default <org>/stacey-brain)." });
13404
+ watch = Option48.Boolean("--watch", false, { description: "Poll until the questionnaire completes (or --timeout)." });
13405
+ timeout = Option48.String("--timeout", { description: "Watch timeout in minutes (default 20)." });
13406
+ githubAppCreds = Option48.String("--github-app-creds");
13407
+ async executeCommand() {
13408
+ const ctx = await resolveSeedContext({
13409
+ endpointOverride: typeof this.endpoint === "string" ? this.endpoint : void 0,
13410
+ brainOverride: typeof this.brain === "string" ? this.brain : void 0,
13411
+ credsPath: typeof this.githubAppCreds === "string" ? this.githubAppCreds : void 0
13412
+ });
13413
+ if (!ctx) {
13414
+ this.context.stderr.write(` ${colors.dim("no GitHub App creds (~/.m8t-stack/github-app.json) \u2014 no brain to seed; skipping.")}
13415
+ `);
13416
+ return 0;
13417
+ }
13418
+ const watch = this.watch === true;
13419
+ const rawTimeout = typeof this.timeout === "string" ? this.timeout.trim() : "";
13420
+ const parsedTimeout = Number(rawTimeout);
13421
+ const timeoutMin = rawTimeout !== "" && Number.isFinite(parsedTimeout) ? parsedTimeout : 20;
13422
+ const deadline = Date.now() + timeoutMin * 6e4;
13423
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
13424
+ for (; ; ) {
13425
+ const token = await getFoundryToken();
13426
+ const { hadIntake, block } = await findOnboardingProfile({ endpoint: ctx.endpoint, token });
13427
+ if (block) {
13428
+ await applyProfileToBrain({ block, brainRepo: ctx.brainRepo, branch: "main", appCreds: ctx.appCreds });
13429
+ this.context.stdout.write(`${colors.success("\u2713")} seeded Clever Stacey's brain (${ctx.brainRepo}) from your questionnaire.
13430
+ `);
13431
+ return 0;
13432
+ }
13433
+ if (!watch || Date.now() >= deadline) {
13434
+ this.context.stderr.write(
13435
+ ` ${colors.dim(hadIntake ? "questionnaire not complete yet \u2014 no m8t_onboarding block found." : "no onboarding questionnaire found.")}
13436
+ ${colors.hint("retry:")} m8t bootstrap seed-profile
13437
+ `
13438
+ );
13439
+ return 3;
13440
+ }
13441
+ this.context.stderr.write(` ${colors.dim("waiting for the questionnaire to complete\u2026")}
13442
+ `);
13443
+ await sleep(2e4);
13444
+ }
13445
+ }
13446
+ };
13447
+
13126
13448
  // src/cli.ts
13127
13449
  var cli = new Cli({
13128
13450
  binaryName: "m8t",
@@ -13178,6 +13500,7 @@ cli.register(BootstrapStatusCommand);
13178
13500
  cli.register(BootstrapReapCommand);
13179
13501
  cli.register(BootstrapFinishCommand);
13180
13502
  cli.register(BootstrapUiCommand);
13503
+ cli.register(BootstrapSeedProfileCommand);
13181
13504
  async function main() {
13182
13505
  try {
13183
13506
  return await cli.run(process.argv.slice(2));