@m8t-stack/cli 0.2.14 → 0.2.15
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 +282 -78
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1198,7 +1198,7 @@ var init_enable_hosted_brain = __esm({
|
|
|
1198
1198
|
import { Builtins, Cli } from "clipanion";
|
|
1199
1199
|
|
|
1200
1200
|
// src/lib/package-version.ts
|
|
1201
|
-
var CLI_VERSION = "0.2.
|
|
1201
|
+
var CLI_VERSION = "0.2.15";
|
|
1202
1202
|
|
|
1203
1203
|
// src/lib/render-error.ts
|
|
1204
1204
|
init_errors();
|
|
@@ -13197,6 +13197,18 @@ async function ghAuthLoginDevice(exec = defaultGhExec) {
|
|
|
13197
13197
|
});
|
|
13198
13198
|
}
|
|
13199
13199
|
}
|
|
13200
|
+
async function getGhToken(exec = defaultGhExec) {
|
|
13201
|
+
const r = await exec("gh", ["auth", "token"]);
|
|
13202
|
+
const tok = r.stdout.trim();
|
|
13203
|
+
if (r.exitCode !== 0 || !tok) {
|
|
13204
|
+
throw new LocalCliError({
|
|
13205
|
+
code: "GH_TOKEN_FAILED",
|
|
13206
|
+
message: `gh auth token failed (exit ${r.exitCode.toString()}): ${(r.stderr || r.stdout).slice(0, 200)}`,
|
|
13207
|
+
hint: "Run `gh auth login` to authenticate the GitHub CLI."
|
|
13208
|
+
});
|
|
13209
|
+
}
|
|
13210
|
+
return tok;
|
|
13211
|
+
}
|
|
13200
13212
|
async function getRepoId(owner, repo, exec = defaultGhExec) {
|
|
13201
13213
|
const r = await exec("gh", ["api", `/repos/${owner}/${repo}`, "--jq", ".id"]);
|
|
13202
13214
|
if (r.exitCode !== 0) {
|
|
@@ -13300,6 +13312,34 @@ async function tryOpenUrl(url, exec = defaultGhExec) {
|
|
|
13300
13312
|
} catch {
|
|
13301
13313
|
}
|
|
13302
13314
|
}
|
|
13315
|
+
function ghRepoProbe(args) {
|
|
13316
|
+
const exec = args.exec ?? defaultGhExec;
|
|
13317
|
+
const slug = `${args.owner}/${args.repo}`;
|
|
13318
|
+
return {
|
|
13319
|
+
repoStatus: async () => {
|
|
13320
|
+
const r = await exec("gh", ["api", "-i", `/repos/${slug}`]);
|
|
13321
|
+
const combined = `${r.stdout}
|
|
13322
|
+
${r.stderr}`;
|
|
13323
|
+
const header = /^HTTP\/[\d.]+\s+(\d{3})/m.exec(combined);
|
|
13324
|
+
if (header) return Number(header[1]);
|
|
13325
|
+
const ghErr = /\(HTTP (\d{3})\)/.exec(combined);
|
|
13326
|
+
if (ghErr) return Number(ghErr[1]);
|
|
13327
|
+
if (r.exitCode === 0) return 200;
|
|
13328
|
+
throw new LocalCliError({
|
|
13329
|
+
code: "GH_REPO_PROBE_FAILED",
|
|
13330
|
+
message: `gh api /repos/${slug} failed (exit ${r.exitCode.toString()}): ${(r.stderr || r.stdout).slice(0, 200)}`
|
|
13331
|
+
});
|
|
13332
|
+
},
|
|
13333
|
+
getFile: async (path29) => {
|
|
13334
|
+
const r = await exec("gh", ["api", "-H", "Accept: application/vnd.github.raw+json", `/repos/${slug}/contents/${path29}`]);
|
|
13335
|
+
return r.exitCode === 0 ? r.stdout : null;
|
|
13336
|
+
},
|
|
13337
|
+
isEmpty: async () => {
|
|
13338
|
+
const r = await exec("gh", ["api", `/repos/${slug}/contents`]);
|
|
13339
|
+
return r.exitCode !== 0;
|
|
13340
|
+
}
|
|
13341
|
+
};
|
|
13342
|
+
}
|
|
13303
13343
|
|
|
13304
13344
|
// src/commands/brain/app-create.ts
|
|
13305
13345
|
init_errors();
|
|
@@ -13534,6 +13574,7 @@ import { readFileSync as readFileSync6 } from "fs";
|
|
|
13534
13574
|
import * as fs10 from "fs";
|
|
13535
13575
|
import * as os4 from "os";
|
|
13536
13576
|
import * as path9 from "path";
|
|
13577
|
+
import * as readline2 from "readline/promises";
|
|
13537
13578
|
import { DefaultAzureCredential as DefaultAzureCredential4 } from "@azure/identity";
|
|
13538
13579
|
init_errors();
|
|
13539
13580
|
|
|
@@ -14065,6 +14106,20 @@ function materializeBrainTree(opts) {
|
|
|
14065
14106
|
init_errors();
|
|
14066
14107
|
init_esm2();
|
|
14067
14108
|
import { spawn as spawn3 } from "child_process";
|
|
14109
|
+
import { parse as parseYaml5 } from "yaml";
|
|
14110
|
+
function isM8tBrainMarker(yamlText) {
|
|
14111
|
+
if (!yamlText.trim()) return false;
|
|
14112
|
+
let parsed;
|
|
14113
|
+
try {
|
|
14114
|
+
parsed = parseYaml5(yamlText);
|
|
14115
|
+
} catch {
|
|
14116
|
+
return false;
|
|
14117
|
+
}
|
|
14118
|
+
if (!parsed || typeof parsed !== "object") return false;
|
|
14119
|
+
const root = parsed;
|
|
14120
|
+
const isObj2 = (v) => typeof v === "object" && v !== null;
|
|
14121
|
+
return isObj2(root.engine) || isObj2(root.processes) || isObj2(root.link);
|
|
14122
|
+
}
|
|
14068
14123
|
async function createBlankRepo(args) {
|
|
14069
14124
|
const res = await fetch(`https://api.github.com/orgs/${args.org}/repos`, {
|
|
14070
14125
|
method: "POST",
|
|
@@ -14195,8 +14250,119 @@ async function commitFilesViaApp(args) {
|
|
|
14195
14250
|
});
|
|
14196
14251
|
await api(`/git/refs/heads/${args.branch}`, { method: "PATCH", body: JSON.stringify({ sha: commit.sha }) });
|
|
14197
14252
|
}
|
|
14253
|
+
async function probeRepoState(probe) {
|
|
14254
|
+
const status = await probe.repoStatus();
|
|
14255
|
+
if (status === 404) return "absent";
|
|
14256
|
+
if (status !== 200) {
|
|
14257
|
+
throw new LocalCliError({
|
|
14258
|
+
code: "GH_REPO_PROBE_FAILED",
|
|
14259
|
+
message: `GET repo returned HTTP ${status.toString()} (expected 200 or 404)`
|
|
14260
|
+
});
|
|
14261
|
+
}
|
|
14262
|
+
const marker = await probe.getFile(".m8t/brain.yaml");
|
|
14263
|
+
if (marker !== null && isM8tBrainMarker(marker)) return "m8t-brain";
|
|
14264
|
+
if (await probe.isEmpty()) return "empty";
|
|
14265
|
+
return "foreign";
|
|
14266
|
+
}
|
|
14267
|
+
function appRepoProbe(args) {
|
|
14268
|
+
const doFetch = args.fetchImpl ?? fetch;
|
|
14269
|
+
const H = ghHeaders(args.token);
|
|
14270
|
+
return {
|
|
14271
|
+
repoStatus: async () => {
|
|
14272
|
+
const res = await doFetch(`${GH_API}/repos/${args.repo}`, { headers: H, signal: AbortSignal.timeout(GH_REQUEST_TIMEOUT_MS) });
|
|
14273
|
+
return res.status;
|
|
14274
|
+
},
|
|
14275
|
+
getFile: (path29) => readRepoFileViaApp({ token: args.token, repo: args.repo, path: path29, fetchImpl: args.fetchImpl }),
|
|
14276
|
+
isEmpty: async () => {
|
|
14277
|
+
const res = await doFetch(`${GH_API}/repos/${args.repo}/contents`, { headers: H, signal: AbortSignal.timeout(GH_REQUEST_TIMEOUT_MS) });
|
|
14278
|
+
return res.status === 404;
|
|
14279
|
+
}
|
|
14280
|
+
};
|
|
14281
|
+
}
|
|
14282
|
+
|
|
14283
|
+
// src/lib/brain-repo-collision.ts
|
|
14284
|
+
init_errors();
|
|
14285
|
+
var DEFAULT_MAX_ATTEMPTS = 20;
|
|
14286
|
+
function actionForState(state) {
|
|
14287
|
+
switch (state) {
|
|
14288
|
+
case "absent":
|
|
14289
|
+
return "create";
|
|
14290
|
+
case "m8t-brain":
|
|
14291
|
+
return "reuse";
|
|
14292
|
+
case "empty":
|
|
14293
|
+
return "adopt";
|
|
14294
|
+
case "foreign":
|
|
14295
|
+
throw new LocalCliError({
|
|
14296
|
+
code: "FOREIGN_REPO_COLLISION",
|
|
14297
|
+
message: `A repo already exists but isn't an m8t brain.`
|
|
14298
|
+
});
|
|
14299
|
+
}
|
|
14300
|
+
}
|
|
14301
|
+
function candidate(base, n) {
|
|
14302
|
+
return n === 1 ? base : `${base}-${n.toString()}`;
|
|
14303
|
+
}
|
|
14304
|
+
async function scan(base, classify, accept, startAt, maxAttempts) {
|
|
14305
|
+
for (let n = startAt; n <= maxAttempts; n++) {
|
|
14306
|
+
const name = candidate(base, n);
|
|
14307
|
+
const state = await classify(name);
|
|
14308
|
+
if (accept(state)) return { repoName: name, state };
|
|
14309
|
+
}
|
|
14310
|
+
throw new LocalCliError({
|
|
14311
|
+
code: "REPO_NAME_SCAN_EXHAUSTED",
|
|
14312
|
+
message: `Could not find a free repo name for "${base}" after ${maxAttempts.toString()} attempts.`
|
|
14313
|
+
});
|
|
14314
|
+
}
|
|
14315
|
+
async function resolveRepoCollision(args) {
|
|
14316
|
+
const max = args.maxAttempts ?? DEFAULT_MAX_ATTEMPTS;
|
|
14317
|
+
const state = await args.classify(args.repoName);
|
|
14318
|
+
if (args.force === "new") {
|
|
14319
|
+
const found = await scan(args.repoName, args.classify, (s) => s === "absent", 1, max);
|
|
14320
|
+
return { repoName: found.repoName, action: "create" };
|
|
14321
|
+
}
|
|
14322
|
+
if (args.force === "reuse") {
|
|
14323
|
+
return { repoName: args.repoName, action: actionForState(state) };
|
|
14324
|
+
}
|
|
14325
|
+
if (state === "absent") return { repoName: args.repoName, action: "create" };
|
|
14326
|
+
if (args.interactive && args.prompt) {
|
|
14327
|
+
const choice = await args.prompt({ repoName: args.repoName, state });
|
|
14328
|
+
if (choice === "abort") {
|
|
14329
|
+
throw new LocalCliError({ code: "BRAIN_CREATE_ABORTED", message: "Aborted by the operator." });
|
|
14330
|
+
}
|
|
14331
|
+
if (choice === "new") {
|
|
14332
|
+
const found = await scan(args.repoName, args.classify, (s) => s !== "foreign", 2, max);
|
|
14333
|
+
return { repoName: found.repoName, action: actionForState(found.state) };
|
|
14334
|
+
}
|
|
14335
|
+
return { repoName: args.repoName, action: actionForState(state) };
|
|
14336
|
+
}
|
|
14337
|
+
if (state === "foreign") {
|
|
14338
|
+
throw new LocalCliError({
|
|
14339
|
+
code: "FOREIGN_REPO_COLLISION",
|
|
14340
|
+
message: `A repo with this name already exists but isn't an m8t brain, so it won't be touched. Rename that repo or install into a different org, then re-run.`
|
|
14341
|
+
});
|
|
14342
|
+
}
|
|
14343
|
+
return { repoName: args.repoName, action: actionForState(state) };
|
|
14344
|
+
}
|
|
14198
14345
|
|
|
14199
14346
|
// src/commands/brain/create.ts
|
|
14347
|
+
async function promptCollision(ctx, info) {
|
|
14348
|
+
const rl = readline2.createInterface({ input: ctx.stdin, output: ctx.stdout });
|
|
14349
|
+
try {
|
|
14350
|
+
if (info.state === "foreign") {
|
|
14351
|
+
const ans2 = (await rl.question(
|
|
14352
|
+
`A repo "${info.repoName}" already exists but isn't an m8t brain. Create a new "${info.repoName}-2" instead? [Y/n] (n aborts) `
|
|
14353
|
+
)).trim().toLowerCase();
|
|
14354
|
+
return ans2 === "" || ans2 === "y" || ans2 === "yes" ? "new" : "abort";
|
|
14355
|
+
}
|
|
14356
|
+
const ans = (await rl.question(
|
|
14357
|
+
`An m8t brain "${info.repoName}" already exists. Reuse it? [Y/n] (n creates "${info.repoName}-2", or "a" aborts) `
|
|
14358
|
+
)).trim().toLowerCase();
|
|
14359
|
+
if (ans === "" || ans === "y" || ans === "yes") return "reuse";
|
|
14360
|
+
if (ans === "a" || ans === "abort") return "abort";
|
|
14361
|
+
return "new";
|
|
14362
|
+
} finally {
|
|
14363
|
+
rl.close();
|
|
14364
|
+
}
|
|
14365
|
+
}
|
|
14200
14366
|
function stageAsGitRepo(dir) {
|
|
14201
14367
|
const steps = [
|
|
14202
14368
|
{ args: ["init", "-b", "main"], label: "git init" },
|
|
@@ -14239,6 +14405,8 @@ var BrainCreateCommand = class extends M8tCommand {
|
|
|
14239
14405
|
appId = Option17.String("--app-id", { description: "GitHub App id (no-gh path); with --app-pem/--installation-id, create the repo via the App instead of gh." });
|
|
14240
14406
|
appPem = Option17.String("--app-pem", { description: "Path to the App private-key PEM (no-gh path)." });
|
|
14241
14407
|
installationId = Option17.String("--installation-id", { description: "Org installation id for the App (no-gh path)." });
|
|
14408
|
+
reuse = Option17.Boolean("--reuse", false, { description: "If the brain repo already exists, reuse it (non-interactive). Errors if the repo isn't a valid m8t brain." });
|
|
14409
|
+
newName = Option17.Boolean("--new", false, { description: "If the brain repo name is taken, create a fresh suffixed repo (e.g. <name>-2) instead of reusing." });
|
|
14242
14410
|
async executeCommand() {
|
|
14243
14411
|
const owner = typeof this.owner === "string" ? this.owner : void 0;
|
|
14244
14412
|
const worker = typeof this.worker === "string" ? this.worker : void 0;
|
|
@@ -14303,59 +14471,95 @@ var BrainCreateCommand = class extends M8tCommand {
|
|
|
14303
14471
|
const tmpDir = fs10.mkdtempSync(path9.join(os4.tmpdir(), `m8t-brain-create-${worker}-`));
|
|
14304
14472
|
let result;
|
|
14305
14473
|
let resolvedInstallationId = null;
|
|
14474
|
+
const force = this.reuse === true ? "reuse" : this.newName === true ? "new" : void 0;
|
|
14475
|
+
const interactive = !useAppPath && this.context.stdout.isTTY === true;
|
|
14476
|
+
let token;
|
|
14477
|
+
let classify;
|
|
14478
|
+
if (useAppPath) {
|
|
14479
|
+
token = await mintInstallationTokenFromPem({
|
|
14480
|
+
appId: appIdOpt,
|
|
14481
|
+
privateKeyPem: readFileSync6(appPemOpt, "utf8"),
|
|
14482
|
+
installationId: installationIdOpt
|
|
14483
|
+
});
|
|
14484
|
+
const tok = token;
|
|
14485
|
+
classify = (name) => probeRepoState(appRepoProbe({ token: tok, repo: `${owner}/${name}` }));
|
|
14486
|
+
} else {
|
|
14487
|
+
classify = (name) => probeRepoState(ghRepoProbe({ owner, repo: name }));
|
|
14488
|
+
}
|
|
14489
|
+
const decision = await resolveRepoCollision({
|
|
14490
|
+
repoName,
|
|
14491
|
+
classify,
|
|
14492
|
+
interactive,
|
|
14493
|
+
force,
|
|
14494
|
+
prompt: interactive ? (info) => promptCollision(
|
|
14495
|
+
{ stdin: this.context.stdin, stdout: this.context.stdout },
|
|
14496
|
+
info
|
|
14497
|
+
) : void 0
|
|
14498
|
+
});
|
|
14499
|
+
const finalName = decision.repoName;
|
|
14500
|
+
const needsPush = decision.action === "create" || decision.action === "adopt";
|
|
14501
|
+
if (finalName !== repoName) {
|
|
14502
|
+
this.context.stdout.write(`${colors.hint("note:")} using repo name ${colors.field(finalName)} (${decision.action}).
|
|
14503
|
+
`);
|
|
14504
|
+
}
|
|
14306
14505
|
try {
|
|
14307
|
-
|
|
14308
|
-
|
|
14309
|
-
|
|
14506
|
+
if (needsPush) {
|
|
14507
|
+
materializeBrainTree({ templateSrc, destDir: tmpDir, seedDir });
|
|
14508
|
+
this.context.stdout.write(
|
|
14509
|
+
seedName ? `${colors.dim("\u2192")} copied brain-template/ + overlaid seed ${colors.field(seedName)} to ${tmpDir}
|
|
14310
14510
|
` : `${colors.dim("\u2192")} copied brain-template/ to ${tmpDir}
|
|
14311
14511
|
`
|
|
14312
|
-
|
|
14512
|
+
);
|
|
14513
|
+
}
|
|
14313
14514
|
if (useAppPath) {
|
|
14314
|
-
|
|
14515
|
+
const appToken = token ?? "";
|
|
14516
|
+
if (decision.action === "create") {
|
|
14517
|
+
this.context.stdout.write(`${colors.dim("\u2192")} creating ${owner}/${finalName} via the GitHub App (no gh)\u2026
|
|
14518
|
+
`);
|
|
14519
|
+
await createBlankRepo({ token: appToken, org: owner, name: finalName, private: !this.public_ });
|
|
14520
|
+
await pushSeed({ token: appToken, org: owner, name: finalName, branch, sourceDir: tmpDir });
|
|
14521
|
+
this.context.stdout.write(`${colors.success("\u2713")} repo created + seeded via the App
|
|
14522
|
+
`);
|
|
14523
|
+
} else if (decision.action === "adopt") {
|
|
14524
|
+
this.context.stdout.write(`${colors.dim("\u2192")} seeding the existing empty repo ${owner}/${finalName} via the GitHub App\u2026
|
|
14525
|
+
`);
|
|
14526
|
+
await pushSeed({ token: appToken, org: owner, name: finalName, branch, sourceDir: tmpDir });
|
|
14527
|
+
this.context.stdout.write(`${colors.success("\u2713")} empty repo seeded via the App
|
|
14528
|
+
`);
|
|
14529
|
+
} else {
|
|
14530
|
+
this.context.stdout.write(`${colors.success("\u2713")} reusing the existing brain ${owner}/${finalName}
|
|
14315
14531
|
`);
|
|
14316
|
-
const pem = readFileSync6(appPemOpt, "utf8");
|
|
14317
|
-
const appJwt = signAppJwt({ appId: appIdOpt, privateKeyPem: pem });
|
|
14318
|
-
const tokRes = await fetch(
|
|
14319
|
-
`https://api.github.com/app/installations/${installationIdOpt}/access_tokens`,
|
|
14320
|
-
{
|
|
14321
|
-
method: "POST",
|
|
14322
|
-
headers: {
|
|
14323
|
-
Authorization: `Bearer ${appJwt}`,
|
|
14324
|
-
Accept: "application/vnd.github+json",
|
|
14325
|
-
"X-GitHub-Api-Version": "2022-11-28",
|
|
14326
|
-
"User-Agent": "m8t"
|
|
14327
|
-
}
|
|
14328
|
-
}
|
|
14329
|
-
);
|
|
14330
|
-
if (!tokRes.ok) {
|
|
14331
|
-
throw new LocalCliError({
|
|
14332
|
-
code: "GH_APP_TOKEN_FAILED",
|
|
14333
|
-
message: `mint installation token HTTP ${tokRes.status.toString()}: ${(await tokRes.text()).slice(0, 300)}`
|
|
14334
|
-
});
|
|
14335
14532
|
}
|
|
14336
|
-
const token = (await tokRes.json()).token;
|
|
14337
|
-
await createBlankRepo({ token, org: owner, name: repoName, private: !this.public_ });
|
|
14338
|
-
await pushSeed({ token, org: owner, name: repoName, branch, sourceDir: tmpDir });
|
|
14339
14533
|
resolvedInstallationId = installationIdOpt;
|
|
14340
|
-
this.context.stdout.write(`${colors.success("\u2713")} repo created + seeded via the App
|
|
14341
|
-
`);
|
|
14342
14534
|
} else {
|
|
14343
|
-
|
|
14344
|
-
|
|
14535
|
+
if (decision.action === "create") {
|
|
14536
|
+
stageAsGitRepo(tmpDir);
|
|
14537
|
+
this.context.stdout.write(`${colors.dim("\u2192")} creating repo ${owner}/${finalName} (${this.public_ ? "public" : "private"})\u2026
|
|
14538
|
+
`);
|
|
14539
|
+
await createRepo({ owner, name: finalName, private: !this.public_, source: tmpDir });
|
|
14540
|
+
this.context.stdout.write(`${colors.success("\u2713")} repo created
|
|
14541
|
+
`);
|
|
14542
|
+
} else if (decision.action === "adopt") {
|
|
14543
|
+
this.context.stdout.write(`${colors.dim("\u2192")} seeding the existing empty repo ${owner}/${finalName}\u2026
|
|
14345
14544
|
`);
|
|
14346
|
-
|
|
14347
|
-
|
|
14545
|
+
const ghToken = await getGhToken();
|
|
14546
|
+
await pushSeed({ token: ghToken, org: owner, name: finalName, branch, sourceDir: tmpDir });
|
|
14547
|
+
this.context.stdout.write(`${colors.success("\u2713")} empty repo seeded
|
|
14348
14548
|
`);
|
|
14349
|
-
|
|
14549
|
+
} else {
|
|
14550
|
+
this.context.stdout.write(`${colors.success("\u2713")} reusing the existing brain ${owner}/${finalName}
|
|
14551
|
+
`);
|
|
14552
|
+
}
|
|
14553
|
+
const repoId = await getRepoId(owner, finalName);
|
|
14350
14554
|
const { slug, appId: secretAppId, privateKeyPem } = await readAppSecrets({ credential: credential2, kvUri });
|
|
14351
14555
|
const initialJwt = signAppJwt({ appId: secretAppId, privateKeyPem });
|
|
14352
|
-
resolvedInstallationId = await probeAppInstallation({ owner, repo:
|
|
14556
|
+
resolvedInstallationId = await probeAppInstallation({ owner, repo: finalName, jwt: initialJwt });
|
|
14353
14557
|
if (resolvedInstallationId) {
|
|
14354
|
-
this.context.stdout.write(` ${colors.success("\u2713")} App already installed on ${colors.field(`${owner}/${
|
|
14558
|
+
this.context.stdout.write(` ${colors.success("\u2713")} App already installed on ${colors.field(`${owner}/${finalName}`)} (installation ${resolvedInstallationId}); skipping browser open.
|
|
14355
14559
|
`);
|
|
14356
14560
|
} else {
|
|
14357
14561
|
const url = installUrl(slug, repoId);
|
|
14358
|
-
this.context.stdout.write(`${colors.field("\u2192")} Install the m8t-brain App on ${owner}/${
|
|
14562
|
+
this.context.stdout.write(`${colors.field("\u2192")} Install the m8t-brain App on ${owner}/${finalName}:
|
|
14359
14563
|
${url}
|
|
14360
14564
|
`);
|
|
14361
14565
|
await tryOpenUrl(url);
|
|
@@ -14363,10 +14567,10 @@ var BrainCreateCommand = class extends M8tCommand {
|
|
|
14363
14567
|
`);
|
|
14364
14568
|
resolvedInstallationId = await pollForInstallation({
|
|
14365
14569
|
owner,
|
|
14366
|
-
repo:
|
|
14570
|
+
repo: finalName,
|
|
14367
14571
|
// Mint fresh JWT each tick — App JWTs are 10-min TTL, poll defaults to 5 min
|
|
14368
14572
|
// but the timeout is a soft default, so don't depend on the inequality.
|
|
14369
|
-
probe: () => probeAppInstallation({ owner, repo:
|
|
14573
|
+
probe: () => probeAppInstallation({ owner, repo: finalName, jwt: signAppJwt({ appId: secretAppId, privateKeyPem }) }),
|
|
14370
14574
|
onTick: (n) => {
|
|
14371
14575
|
if (n % 5 === 0) this.context.stderr.write(` ${colors.dim(`\u2026still waiting (${(n * 2).toString()}s)`)}
|
|
14372
14576
|
`);
|
|
@@ -14382,7 +14586,7 @@ var BrainCreateCommand = class extends M8tCommand {
|
|
|
14382
14586
|
projectEndpoint: project.endpoint,
|
|
14383
14587
|
projectArmId: `${project.accountScope}/projects/${project.projectName}`,
|
|
14384
14588
|
agentName: worker,
|
|
14385
|
-
repo: `${owner}/${
|
|
14589
|
+
repo: `${owner}/${finalName}`,
|
|
14386
14590
|
branch,
|
|
14387
14591
|
repoRoot,
|
|
14388
14592
|
installationId: resolvedInstallationId,
|
|
@@ -14396,7 +14600,7 @@ var BrainCreateCommand = class extends M8tCommand {
|
|
|
14396
14600
|
this.context.stdout.write(
|
|
14397
14601
|
renderJson({
|
|
14398
14602
|
worker,
|
|
14399
|
-
repo: `${owner}/${
|
|
14603
|
+
repo: `${owner}/${finalName}`,
|
|
14400
14604
|
branch,
|
|
14401
14605
|
installationId: resolvedInstallationId,
|
|
14402
14606
|
connectionName: result.connectionName,
|
|
@@ -14406,10 +14610,10 @@ var BrainCreateCommand = class extends M8tCommand {
|
|
|
14406
14610
|
return 0;
|
|
14407
14611
|
}
|
|
14408
14612
|
this.context.stdout.write(
|
|
14409
|
-
`${colors.success("\u2713")} created + linked ${colors.field(worker)} \u2194 ${colors.field(`${owner}/${
|
|
14613
|
+
`${colors.success("\u2713")} created + linked ${colors.field(worker)} \u2194 ${colors.field(`${owner}/${finalName}`)} (agent version ${result.foundryVersion ?? ""}).
|
|
14410
14614
|
`
|
|
14411
14615
|
);
|
|
14412
|
-
this.context.stdout.write(` ${colors.hint("brain repo:")} https://github.com/${owner}/${
|
|
14616
|
+
this.context.stdout.write(` ${colors.hint("brain repo:")} https://github.com/${owner}/${finalName}
|
|
14413
14617
|
`);
|
|
14414
14618
|
this.context.stdout.write(` ${colors.hint("token:")} rotates lazily on every invoke (60min TTL, 10min safety margin).
|
|
14415
14619
|
`);
|
|
@@ -15010,7 +15214,7 @@ init_errors();
|
|
|
15010
15214
|
// src/lib/deploy-prompt-advisor.ts
|
|
15011
15215
|
import * as fs12 from "fs";
|
|
15012
15216
|
import * as path11 from "path";
|
|
15013
|
-
import { parse as
|
|
15217
|
+
import { parse as parseYaml6 } from "yaml";
|
|
15014
15218
|
init_foundry_agents();
|
|
15015
15219
|
init_errors();
|
|
15016
15220
|
function findPersonasRoot(hint) {
|
|
@@ -15038,7 +15242,7 @@ async function deployPromptAdvisor(args) {
|
|
|
15038
15242
|
});
|
|
15039
15243
|
}
|
|
15040
15244
|
const fmMatch = /^---\n([\s\S]*?)\n---/.exec(raw);
|
|
15041
|
-
const fm = fmMatch ?
|
|
15245
|
+
const fm = fmMatch ? parseYaml6(fmMatch[1]) : {};
|
|
15042
15246
|
const model = args.model ?? fm.targets?.foundry?.model;
|
|
15043
15247
|
if (!model) {
|
|
15044
15248
|
throw new LocalCliError({
|
|
@@ -15305,7 +15509,7 @@ async function awaitAgentQueryable(args) {
|
|
|
15305
15509
|
init_errors();
|
|
15306
15510
|
init_esm();
|
|
15307
15511
|
import * as fs13 from "fs";
|
|
15308
|
-
import { parse as
|
|
15512
|
+
import { parse as parseYaml7 } from "yaml";
|
|
15309
15513
|
function readPersonaA2aCard(personaPath) {
|
|
15310
15514
|
let raw;
|
|
15311
15515
|
try {
|
|
@@ -15315,7 +15519,7 @@ function readPersonaA2aCard(personaPath) {
|
|
|
15315
15519
|
}
|
|
15316
15520
|
const fm = /^---\n([\s\S]*?)\n---/.exec(raw);
|
|
15317
15521
|
if (!fm) throw new LocalCliError({ code: "PERSONA_NO_FRONTMATTER", message: `Persona '${personaPath}' has no frontmatter.` });
|
|
15318
|
-
const front =
|
|
15522
|
+
const front = parseYaml7(fm[1]);
|
|
15319
15523
|
const role = typeof front.role === "string" ? front.role : "";
|
|
15320
15524
|
const cardYaml = front.targets?.foundry?.["a2a-card"];
|
|
15321
15525
|
if (!cardYaml || typeof cardYaml !== "object") {
|
|
@@ -15801,7 +16005,7 @@ init_errors();
|
|
|
15801
16005
|
import * as fs15 from "fs";
|
|
15802
16006
|
import * as os6 from "os";
|
|
15803
16007
|
import * as path12 from "path";
|
|
15804
|
-
import { parse as
|
|
16008
|
+
import { parse as parseYaml8 } from "yaml";
|
|
15805
16009
|
var A2A_PATH = "/api/a2a/mcp";
|
|
15806
16010
|
function resolveBridgeUrl(opts) {
|
|
15807
16011
|
const env = opts.env ?? process.env;
|
|
@@ -15825,7 +16029,7 @@ function readConfigGatewayUrl(home) {
|
|
|
15825
16029
|
const cfg = path12.join(home ?? os6.homedir(), ".m8t-stack", "config.yaml");
|
|
15826
16030
|
if (!fs15.existsSync(cfg)) return void 0;
|
|
15827
16031
|
try {
|
|
15828
|
-
const o =
|
|
16032
|
+
const o = parseYaml8(fs15.readFileSync(cfg, "utf8"));
|
|
15829
16033
|
return typeof o.gatewayUrl === "string" ? o.gatewayUrl : void 0;
|
|
15830
16034
|
} catch {
|
|
15831
16035
|
return void 0;
|
|
@@ -16327,7 +16531,7 @@ async function deployHostedWorker(args) {
|
|
|
16327
16531
|
// src/lib/persona.ts
|
|
16328
16532
|
import * as fs17 from "fs";
|
|
16329
16533
|
import * as path15 from "path";
|
|
16330
|
-
import { parse as
|
|
16534
|
+
import { parse as parseYaml9 } from "yaml";
|
|
16331
16535
|
function findRepoRoot(startDir) {
|
|
16332
16536
|
let dir = path15.resolve(startDir);
|
|
16333
16537
|
for (; ; ) {
|
|
@@ -16351,7 +16555,7 @@ function resolvePersona(persona, cwd = process.cwd()) {
|
|
|
16351
16555
|
if (!match) return { persona, personaVersion: null };
|
|
16352
16556
|
let fm = null;
|
|
16353
16557
|
try {
|
|
16354
|
-
fm =
|
|
16558
|
+
fm = parseYaml9(match[1]);
|
|
16355
16559
|
} catch {
|
|
16356
16560
|
return { persona, personaVersion: null };
|
|
16357
16561
|
}
|
|
@@ -18256,9 +18460,9 @@ var EvalSkillCommand = class extends M8tCommand {
|
|
|
18256
18460
|
return Promise.resolve(this._runCommand());
|
|
18257
18461
|
}
|
|
18258
18462
|
_runCommand() {
|
|
18259
|
-
const
|
|
18463
|
+
const candidate2 = typeof this.candidate === "string" ? this.candidate : void 0;
|
|
18260
18464
|
const skillsDir = typeof this.skillsDir === "string" ? this.skillsDir : void 0;
|
|
18261
|
-
if (!
|
|
18465
|
+
if (!candidate2) {
|
|
18262
18466
|
throw new LocalCliError({ code: "USAGE", message: "<path> positional argument is required" });
|
|
18263
18467
|
}
|
|
18264
18468
|
if (!skillsDir) {
|
|
@@ -18267,7 +18471,7 @@ var EvalSkillCommand = class extends M8tCommand {
|
|
|
18267
18471
|
const outputFlag = this.output === "json" || this.output === "auto" || this.output === "pretty" ? this.output : "pretty";
|
|
18268
18472
|
const mode = resolveOutputMode(outputFlag, this.context.stdout);
|
|
18269
18473
|
const bin = process.env.BRAIN_EVAL_BIN ?? "brain-eval";
|
|
18270
|
-
const args = ["skill",
|
|
18474
|
+
const args = ["skill", candidate2, "--skills-dir", skillsDir, "--json"];
|
|
18271
18475
|
if (this.noJudge === true) args.push("--no-judge");
|
|
18272
18476
|
if (typeof this.deployment === "string") args.push("--deployment", this.deployment);
|
|
18273
18477
|
const res = spawnSync3(bin, args, { encoding: "utf-8" });
|
|
@@ -19256,7 +19460,7 @@ import { Command as Command41, Option as Option39 } from "clipanion";
|
|
|
19256
19460
|
// src/lib/profiles.ts
|
|
19257
19461
|
import * as fs20 from "fs/promises";
|
|
19258
19462
|
import * as path21 from "path";
|
|
19259
|
-
import { parse as
|
|
19463
|
+
import { parse as parseYaml10, stringify as stringifyYaml4 } from "yaml";
|
|
19260
19464
|
function profilesDir() {
|
|
19261
19465
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
19262
19466
|
return path21.join(home, ".m8t-stack", "profiles");
|
|
@@ -19281,7 +19485,7 @@ async function listProfiles() {
|
|
|
19281
19485
|
async function readProfile(name) {
|
|
19282
19486
|
try {
|
|
19283
19487
|
const raw = await fs20.readFile(getProfilePath(name), "utf8");
|
|
19284
|
-
const parsed =
|
|
19488
|
+
const parsed = parseYaml10(raw);
|
|
19285
19489
|
if (!parsed || typeof parsed !== "object") return null;
|
|
19286
19490
|
return parsed;
|
|
19287
19491
|
} catch (e) {
|
|
@@ -20451,16 +20655,16 @@ function buildAdvancePlan(worker, cursor, physicalPks, consumedRowsByPk, failedT
|
|
|
20451
20655
|
for (const pk of physicalPks) {
|
|
20452
20656
|
const rowsForPk = consumedRowsByPk[pk] ?? [];
|
|
20453
20657
|
const { ts, consumedRowKeys } = computeWatermark(rowsForPk, now.toISOString(), safetyLagSeconds);
|
|
20454
|
-
let
|
|
20658
|
+
let candidate2 = ts;
|
|
20455
20659
|
const prior = Object.hasOwn(cursor.watermarks, pk) ? cursor.watermarks[pk].ts : void 0;
|
|
20456
20660
|
if (prior)
|
|
20457
20661
|
priorTsByPk[pk] = prior;
|
|
20458
|
-
if (prior &&
|
|
20459
|
-
|
|
20662
|
+
if (prior && candidate2 < prior)
|
|
20663
|
+
candidate2 = prior;
|
|
20460
20664
|
const failed = failedTsByPk[pk];
|
|
20461
|
-
if (failed &&
|
|
20462
|
-
|
|
20463
|
-
watermarks[pk] = { ts:
|
|
20665
|
+
if (failed && candidate2 >= failed)
|
|
20666
|
+
candidate2 = justBefore(failed);
|
|
20667
|
+
watermarks[pk] = { ts: candidate2, consumedRowKeys };
|
|
20464
20668
|
consumedKeysByPk[pk] = consumedRowKeys;
|
|
20465
20669
|
}
|
|
20466
20670
|
return {
|
|
@@ -20854,12 +21058,12 @@ function validateDelta(d) {
|
|
|
20854
21058
|
}
|
|
20855
21059
|
function extractJson(text) {
|
|
20856
21060
|
const fenced = /```(?:json)?\s*([\s\S]*?)```/.exec(text);
|
|
20857
|
-
const
|
|
20858
|
-
const start =
|
|
20859
|
-
const end =
|
|
21061
|
+
const candidate2 = (fenced ? fenced[1] : text).trim();
|
|
21062
|
+
const start = candidate2.indexOf("{");
|
|
21063
|
+
const end = candidate2.lastIndexOf("}");
|
|
20860
21064
|
if (start === -1 || end === -1 || end < start)
|
|
20861
21065
|
throw new Error("no JSON object found");
|
|
20862
|
-
return JSON.parse(
|
|
21066
|
+
return JSON.parse(candidate2.slice(start, end + 1));
|
|
20863
21067
|
}
|
|
20864
21068
|
async function propose(model, system, user, opts = {}) {
|
|
20865
21069
|
const sleep2 = opts.sleep ?? defaultSleep2;
|
|
@@ -21439,14 +21643,14 @@ function narrowAdvance(src, kept) {
|
|
|
21439
21643
|
for (const pk of Object.keys(src.watermarks)) {
|
|
21440
21644
|
const rows = keptRowsByPk[pk] ?? [];
|
|
21441
21645
|
const { ts, consumedRowKeys } = computeWatermark(rows, now, safetyLagSeconds);
|
|
21442
|
-
let
|
|
21646
|
+
let candidate2 = ts;
|
|
21443
21647
|
const prior = priorTsByPk[pk];
|
|
21444
|
-
if (prior &&
|
|
21445
|
-
|
|
21648
|
+
if (prior && candidate2 < prior)
|
|
21649
|
+
candidate2 = prior;
|
|
21446
21650
|
const failed = src.failedTsByPk[pk];
|
|
21447
|
-
if (failed &&
|
|
21448
|
-
|
|
21449
|
-
watermarks[pk] = { ts:
|
|
21651
|
+
if (failed && candidate2 >= failed)
|
|
21652
|
+
candidate2 = justBefore(failed);
|
|
21653
|
+
watermarks[pk] = { ts: candidate2, consumedRowKeys };
|
|
21450
21654
|
consumedRowsByPk[pk] = consumedRowKeys;
|
|
21451
21655
|
}
|
|
21452
21656
|
return {
|
|
@@ -21548,7 +21752,7 @@ async function emitDigest(input, deps, digest) {
|
|
|
21548
21752
|
}
|
|
21549
21753
|
|
|
21550
21754
|
// ../../packages/brain/engine/dist/esm/dream/config.js
|
|
21551
|
-
import { parse as
|
|
21755
|
+
import { parse as parseYaml11 } from "yaml";
|
|
21552
21756
|
var DEFAULT_DREAM_MODEL = "gpt-5.4";
|
|
21553
21757
|
function asCadence(v) {
|
|
21554
21758
|
return v === "nightly" ? "nightly" : "off";
|
|
@@ -21556,7 +21760,7 @@ function asCadence(v) {
|
|
|
21556
21760
|
function parseDreamerConfig(rawYaml, env) {
|
|
21557
21761
|
let parsed;
|
|
21558
21762
|
try {
|
|
21559
|
-
parsed =
|
|
21763
|
+
parsed = parseYaml11(rawYaml);
|
|
21560
21764
|
} catch {
|
|
21561
21765
|
return { enabled: false, cadence: "off", model: env.M8T_DREAM_MODEL ?? DEFAULT_DREAM_MODEL };
|
|
21562
21766
|
}
|
|
@@ -21570,16 +21774,16 @@ function parseDreamerConfig(rawYaml, env) {
|
|
|
21570
21774
|
}
|
|
21571
21775
|
|
|
21572
21776
|
// ../../packages/brain/engine/dist/esm/cost-report/config.js
|
|
21573
|
-
import { parse as
|
|
21777
|
+
import { parse as parseYaml12 } from "yaml";
|
|
21574
21778
|
|
|
21575
21779
|
// ../../packages/brain/engine/dist/esm/librarian/codify/persistence-guard.js
|
|
21576
21780
|
var ZERO_WIDTH_RE = new RegExp("\u200B|\u200C|\u200D|\uFEFF", "g");
|
|
21577
21781
|
|
|
21578
21782
|
// ../../packages/brain/engine/dist/esm/librarian/janitor/frontmatter.js
|
|
21579
|
-
import { parse as
|
|
21783
|
+
import { parse as parseYaml13, stringify as stringifyYaml5 } from "yaml";
|
|
21580
21784
|
|
|
21581
21785
|
// ../../packages/brain/engine/dist/esm/librarian/config.js
|
|
21582
|
-
import { parse as
|
|
21786
|
+
import { parse as parseYaml14 } from "yaml";
|
|
21583
21787
|
|
|
21584
21788
|
// src/lib/dream-discovery.ts
|
|
21585
21789
|
init_http();
|