@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 +379 -56
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
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.
|
|
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
|
|
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:
|
|
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
|
|
7039
|
-
const tokenRes = await fetchImpl(`https://ghcr.io/token?scope=repository:${
|
|
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/${
|
|
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
|
|
7721
|
-
if (d.children && d.children.length > 0) out.push(...flattenDelta(d.children,
|
|
7722
|
-
else out.push({ ...d, path:
|
|
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 = (
|
|
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,
|
|
10192
|
-
const res = await doFetch(`${API2}/repos/${repoPath}${
|
|
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,
|
|
10206
|
-
const encodedPath =
|
|
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(
|
|
10296
|
+
async readFile(path28) {
|
|
10223
10297
|
const token = await args.mintToken();
|
|
10224
|
-
return readFileWith(token,
|
|
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
|
|
10234
|
-
const content = await readFileWith(token,
|
|
10235
|
-
out.push({ path:
|
|
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: (
|
|
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(
|
|
10767
|
+
constructor(path28, message) {
|
|
10694
10768
|
super(message);
|
|
10695
|
-
this.path =
|
|
10769
|
+
this.path = path28;
|
|
10696
10770
|
this.name = "BatchAbort";
|
|
10697
10771
|
}
|
|
10698
10772
|
};
|
|
10699
|
-
var isIndexFile = (
|
|
10700
|
-
var isMemoryIndex = (
|
|
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
|
|
10718
|
-
if (
|
|
10791
|
+
const path28 = targetPath(delta);
|
|
10792
|
+
if (path28 === null)
|
|
10719
10793
|
return { ok: true };
|
|
10720
|
-
if (isIndexFile(
|
|
10721
|
-
return { ok: false, flagged: { kind: "flagged", path:
|
|
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(
|
|
10724
|
-
const isStructural =
|
|
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(
|
|
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:
|
|
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 = (
|
|
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 = (
|
|
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 = (
|
|
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
|
|
11027
|
-
if (!
|
|
11028
|
-
digest.push(
|
|
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 =
|
|
12969
|
+
const markerDir = path25.join(os14.homedir(), ".m8t-stack");
|
|
12717
12970
|
await fs24.mkdir(markerDir, { recursive: true });
|
|
12718
|
-
await fs24.writeFile(
|
|
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
|
|
12749
|
-
import * as
|
|
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
|
|
12758
|
-
import * as
|
|
12759
|
-
import { spawn as
|
|
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 =
|
|
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 =
|
|
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 =
|
|
12875
|
-
const dir =
|
|
13138
|
+
function onboardingUiPaths(home = os15.homedir()) {
|
|
13139
|
+
const dir = path26.join(home, ".m8t-stack");
|
|
12876
13140
|
return {
|
|
12877
|
-
logPath:
|
|
12878
|
-
pidPath:
|
|
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(
|
|
13166
|
+
fs25.mkdirSync(path26.dirname(logPath), { recursive: true });
|
|
12903
13167
|
const fd = fs25.openSync(logPath, "a");
|
|
12904
|
-
const child =
|
|
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 =
|
|
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(
|
|
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));
|