@m8t-stack/cli 0.2.8 → 0.2.11
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 +273 -133
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -650,19 +650,47 @@ var init_foundry_agent_get = __esm({
|
|
|
650
650
|
});
|
|
651
651
|
|
|
652
652
|
// src/lib/brain-yaml-mirror.ts
|
|
653
|
+
import { parse as parseYaml4 } from "yaml";
|
|
653
654
|
function linkSection(args) {
|
|
654
655
|
return [
|
|
655
656
|
`link:`,
|
|
656
657
|
` repo: "${args.repo}"`,
|
|
657
658
|
` branch: ${args.branch}`,
|
|
658
659
|
` topology: per-worker`,
|
|
659
|
-
` schemaVersion: "
|
|
660
|
+
` schemaVersion: "2"`,
|
|
660
661
|
` persona: "${args.persona}"`,
|
|
661
|
-
`
|
|
662
|
+
` agents:`,
|
|
663
|
+
...args.agents.map((a) => ` - ${a}`),
|
|
662
664
|
``
|
|
663
665
|
].join("\n");
|
|
664
666
|
}
|
|
667
|
+
function mergeAgents(prior, incoming) {
|
|
668
|
+
return prior.includes(incoming) ? prior : [...prior, incoming];
|
|
669
|
+
}
|
|
670
|
+
function readLinkAgents(existing) {
|
|
671
|
+
if (!existing) return [];
|
|
672
|
+
let parsed;
|
|
673
|
+
try {
|
|
674
|
+
parsed = parseYaml4(existing);
|
|
675
|
+
} catch {
|
|
676
|
+
return [];
|
|
677
|
+
}
|
|
678
|
+
if (!parsed || typeof parsed !== "object") return [];
|
|
679
|
+
const root = parsed;
|
|
680
|
+
const link = root.link && typeof root.link === "object" ? root.link : root;
|
|
681
|
+
const raw = Array.isArray(link.agents) ? link.agents : typeof link.agent === "string" ? [link.agent] : [];
|
|
682
|
+
const out = [];
|
|
683
|
+
for (const v of raw) {
|
|
684
|
+
const s = typeof v === "string" ? v.trim() : "";
|
|
685
|
+
if (!s) continue;
|
|
686
|
+
if (/^\{\{.*\}\}$/.test(s)) continue;
|
|
687
|
+
if (!out.includes(s)) out.push(s);
|
|
688
|
+
}
|
|
689
|
+
return out;
|
|
690
|
+
}
|
|
665
691
|
function buildBrainYaml(existing, args) {
|
|
692
|
+
const agents = mergeAgents(readLinkAgents(existing), args.agent);
|
|
693
|
+
const link = linkSection({ repo: args.repo, branch: args.branch, persona: args.persona, agents });
|
|
666
694
|
if (existing) {
|
|
667
695
|
const hasAuthoritative = /^engine:/m.test(existing) || /^processes:/m.test(existing);
|
|
668
696
|
if (hasAuthoritative) {
|
|
@@ -670,10 +698,10 @@ function buildBrainYaml(existing, args) {
|
|
|
670
698
|
const linkIdx = existing.search(/^link:/m);
|
|
671
699
|
const cut = mirrorIdx >= 0 ? mirrorIdx : linkIdx >= 0 ? linkIdx : existing.length;
|
|
672
700
|
const authoritative = existing.slice(0, cut).replace(/\s*$/, "\n\n");
|
|
673
|
-
return authoritative + MIRROR_BANNER + "\n" +
|
|
701
|
+
return authoritative + MIRROR_BANNER + "\n" + link;
|
|
674
702
|
}
|
|
675
703
|
}
|
|
676
|
-
return DEFAULT_AUTHORITATIVE + "\n\n" + MIRROR_BANNER + "\n" +
|
|
704
|
+
return DEFAULT_AUTHORITATIVE + "\n\n" + MIRROR_BANNER + "\n" + link;
|
|
677
705
|
}
|
|
678
706
|
async function mirrorBrainYaml(args) {
|
|
679
707
|
const minted = await mintInstallationToken({
|
|
@@ -683,58 +711,56 @@ async function mirrorBrainYaml(args) {
|
|
|
683
711
|
repository: args.repo
|
|
684
712
|
});
|
|
685
713
|
const [owner, repoName] = args.repo.split("/");
|
|
686
|
-
const
|
|
687
|
-
|
|
688
|
-
{
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
Accept: "application/vnd.github+json",
|
|
692
|
-
"X-GitHub-Api-Version": "2022-11-28"
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
);
|
|
696
|
-
let sha;
|
|
697
|
-
let existing;
|
|
698
|
-
if (getRes.ok) {
|
|
699
|
-
const data = await getRes.json();
|
|
700
|
-
sha = data.sha;
|
|
701
|
-
if (typeof data.content === "string") {
|
|
702
|
-
existing = Buffer.from(data.content, "base64").toString("utf8");
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
const content = buildBrainYaml(existing, {
|
|
706
|
-
repo: args.repo,
|
|
707
|
-
branch: args.branch,
|
|
708
|
-
persona: args.persona,
|
|
709
|
-
agent: args.agent
|
|
710
|
-
});
|
|
711
|
-
const body = {
|
|
712
|
-
message: "chore(brain): mirror .m8t/brain.yaml (m8t brain link)",
|
|
713
|
-
content: Buffer.from(content, "utf8").toString("base64"),
|
|
714
|
-
branch: args.branch,
|
|
715
|
-
...sha ? { sha } : {}
|
|
714
|
+
const url = `https://api.github.com/repos/${owner}/${repoName}/contents/.m8t/brain.yaml`;
|
|
715
|
+
const headers = {
|
|
716
|
+
Authorization: `Bearer ${minted.token}`,
|
|
717
|
+
Accept: "application/vnd.github+json",
|
|
718
|
+
"X-GitHub-Api-Version": "2022-11-28"
|
|
716
719
|
};
|
|
717
|
-
const
|
|
718
|
-
|
|
719
|
-
{
|
|
720
|
+
const MAX_ATTEMPTS2 = 3;
|
|
721
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS2; attempt++) {
|
|
722
|
+
const getRes = await fetch(url, { headers });
|
|
723
|
+
let sha;
|
|
724
|
+
let existing;
|
|
725
|
+
if (getRes.ok) {
|
|
726
|
+
const data = await getRes.json();
|
|
727
|
+
sha = data.sha;
|
|
728
|
+
if (typeof data.content === "string") {
|
|
729
|
+
existing = Buffer.from(data.content, "base64").toString("utf8");
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
const content = buildBrainYaml(existing, {
|
|
733
|
+
repo: args.repo,
|
|
734
|
+
branch: args.branch,
|
|
735
|
+
persona: args.persona,
|
|
736
|
+
agent: args.agent
|
|
737
|
+
});
|
|
738
|
+
const body = {
|
|
739
|
+
message: "chore(brain): mirror .m8t/brain.yaml (m8t brain link)",
|
|
740
|
+
content: Buffer.from(content, "utf8").toString("base64"),
|
|
741
|
+
branch: args.branch,
|
|
742
|
+
...sha ? { sha } : {}
|
|
743
|
+
};
|
|
744
|
+
const putRes = await fetch(url, {
|
|
720
745
|
method: "PUT",
|
|
721
|
-
headers: {
|
|
722
|
-
Authorization: `Bearer ${minted.token}`,
|
|
723
|
-
Accept: "application/vnd.github+json",
|
|
724
|
-
"X-GitHub-Api-Version": "2022-11-28",
|
|
725
|
-
"Content-Type": "application/json"
|
|
726
|
-
},
|
|
746
|
+
headers: { ...headers, "Content-Type": "application/json" },
|
|
727
747
|
body: JSON.stringify(body)
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
if (!putRes.ok) {
|
|
748
|
+
});
|
|
749
|
+
if (putRes.ok) return;
|
|
731
750
|
const text = await putRes.text();
|
|
751
|
+
const retryable = putRes.status === 409 || putRes.status === 422;
|
|
752
|
+
if (retryable && attempt < MAX_ATTEMPTS2) continue;
|
|
753
|
+
const suffix = retryable ? ` after ${MAX_ATTEMPTS2.toString()} attempts` : "";
|
|
732
754
|
throw new LocalCliError({
|
|
733
755
|
code: "BRAIN_YAML_MIRROR_FAILED",
|
|
734
|
-
message: `PUT .m8t/brain.yaml: HTTP ${putRes.status.toString()}
|
|
756
|
+
message: `PUT .m8t/brain.yaml: HTTP ${putRes.status.toString()}${suffix}
|
|
735
757
|
${text.slice(0, 300)}`
|
|
736
758
|
});
|
|
737
759
|
}
|
|
760
|
+
throw new LocalCliError({
|
|
761
|
+
code: "BRAIN_YAML_MIRROR_FAILED",
|
|
762
|
+
message: `PUT .m8t/brain.yaml: retry loop exited unexpectedly`
|
|
763
|
+
});
|
|
738
764
|
}
|
|
739
765
|
var DEFAULT_AUTHORITATIVE, MIRROR_BANNER;
|
|
740
766
|
var init_brain_yaml_mirror = __esm({
|
|
@@ -1169,7 +1195,7 @@ var init_enable_hosted_brain = __esm({
|
|
|
1169
1195
|
import { Builtins, Cli } from "clipanion";
|
|
1170
1196
|
|
|
1171
1197
|
// src/lib/package-version.ts
|
|
1172
|
-
var CLI_VERSION = "0.2.
|
|
1198
|
+
var CLI_VERSION = "0.2.11";
|
|
1173
1199
|
|
|
1174
1200
|
// src/lib/render-error.ts
|
|
1175
1201
|
init_errors();
|
|
@@ -12946,6 +12972,7 @@ function buildManifest(args) {
|
|
|
12946
12972
|
name: `m8t-brain-${args.org}-${args.nameSuffix}`,
|
|
12947
12973
|
url: `https://github.com/${args.org}`,
|
|
12948
12974
|
redirect_url: args.redirectUrl,
|
|
12975
|
+
setup_url: args.setupUrl,
|
|
12949
12976
|
public: false,
|
|
12950
12977
|
// workflows:write is REQUIRED — the brain template ships .github/workflows/*
|
|
12951
12978
|
// (skill-gate.yml, librarian.yml); GitHub rejects an App push of workflow files
|
|
@@ -12973,7 +13000,7 @@ async function convertManifestCode(code) {
|
|
|
12973
13000
|
// src/lib/github-app-create.ts
|
|
12974
13001
|
init_errors();
|
|
12975
13002
|
function renderManifestForm(args) {
|
|
12976
|
-
const manifest = buildManifest({ org: args.org, redirectUrl: args.redirectUrl, nameSuffix: args.nameSuffix });
|
|
13003
|
+
const manifest = buildManifest({ org: args.org, redirectUrl: args.redirectUrl, setupUrl: args.setupUrl, nameSuffix: args.nameSuffix });
|
|
12977
13004
|
return `<!doctype html><html><head><meta charset="utf-8"></head><body>
|
|
12978
13005
|
<p>Redirecting you to GitHub's pre-filled "Create GitHub App" page\u2026</p>
|
|
12979
13006
|
<form id="f" action="${newAppUrl(args.org, args.state)}" method="post">
|
|
@@ -12985,16 +13012,58 @@ document.getElementById("manifest").value = JSON.stringify(${JSON.stringify(mani
|
|
|
12985
13012
|
document.getElementById("f").submit();
|
|
12986
13013
|
</script></body></html>`;
|
|
12987
13014
|
}
|
|
13015
|
+
var PAGE_STYLE = `<style>
|
|
13016
|
+
body{font-family:-apple-system,Segoe UI,Roboto,sans-serif;max-width:560px;margin:48px auto;padding:0 20px;line-height:1.5;color:#1a1a1a}
|
|
13017
|
+
.btn{display:inline-block;background:#1f6feb;color:#fff;padding:10px 18px;border-radius:8px;text-decoration:none;font-weight:600;font-size:15px}
|
|
13018
|
+
.muted{color:#666;font-size:14px}
|
|
13019
|
+
</style>`;
|
|
13020
|
+
var shell = (body) => `<!doctype html><html><head><meta charset="utf-8">${PAGE_STYLE}</head><body>${body}</body></html>`;
|
|
13021
|
+
var INSTALLED_HTML = (org) => `<h3>\u2705 Installed on ${org} \u2014 you're all set.</h3><p>Return to your terminal.</p>`;
|
|
13022
|
+
var ERROR_HTML = (org) => `<h3>\u26A0 Couldn't confirm the install.</h3><p>Re-run <code>m8t brain app-create --org ${org}</code>, or check the terminal.</p>`;
|
|
13023
|
+
function renderStatusPage(args) {
|
|
13024
|
+
const { state, org, slug } = args;
|
|
13025
|
+
if (state === "installed") return shell(INSTALLED_HTML(org));
|
|
13026
|
+
if (state === "error") return shell(ERROR_HTML(org));
|
|
13027
|
+
const installUrl2 = `https://github.com/apps/${slug}/installations/new`;
|
|
13028
|
+
return shell(`
|
|
13029
|
+
<h3>\u2705 App created \u2014 ${args.appName}</h3>
|
|
13030
|
+
<p>One step left: install it on <b>${org}</b>.</p>
|
|
13031
|
+
<p><a href="${installUrl2}" role="button" class="btn">Install on ${org}</a></p>
|
|
13032
|
+
<p class="muted">You'll pick repositories on GitHub, then GitHub brings you right back here.</p>`);
|
|
13033
|
+
}
|
|
12988
13034
|
async function createGitHubApp(args) {
|
|
13035
|
+
const convert = args.deps?.convertManifestCode ?? convertManifestCode;
|
|
13036
|
+
const pollFallbackMs = args.pollFallbackMs ?? 8e3;
|
|
13037
|
+
const base = `http://localhost:${args.port.toString()}`;
|
|
12989
13038
|
const state = `m8t-${args.org}`;
|
|
12990
|
-
const redirectUrl =
|
|
13039
|
+
const redirectUrl = `${base}/callback`;
|
|
13040
|
+
const setupUrl = `${base}/setup`;
|
|
13041
|
+
let createdApp = null;
|
|
13042
|
+
let polledInstallationId = null;
|
|
12991
13043
|
return new Promise((resolve3, reject) => {
|
|
12992
|
-
|
|
13044
|
+
let settled = false;
|
|
13045
|
+
let server;
|
|
13046
|
+
const complete = (installationId) => {
|
|
13047
|
+
if (settled || !createdApp) return;
|
|
13048
|
+
settled = true;
|
|
13049
|
+
resolve3({ app: createdApp, installationId });
|
|
13050
|
+
setTimeout(() => {
|
|
13051
|
+
server.close();
|
|
13052
|
+
}, 2500);
|
|
13053
|
+
};
|
|
13054
|
+
server = createServer((req2, res) => {
|
|
12993
13055
|
void (async () => {
|
|
12994
|
-
const u = new URL(req2.url ?? "/",
|
|
13056
|
+
const u = new URL(req2.url ?? "/", base);
|
|
12995
13057
|
if (u.pathname === "/") {
|
|
12996
13058
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
12997
|
-
res.end(renderManifestForm({ org: args.org, state, redirectUrl, nameSuffix: args.nameSuffix }));
|
|
13059
|
+
res.end(renderManifestForm({ org: args.org, state, redirectUrl, setupUrl, nameSuffix: args.nameSuffix }));
|
|
13060
|
+
return;
|
|
13061
|
+
}
|
|
13062
|
+
if (u.pathname === "/setup") {
|
|
13063
|
+
const id = u.searchParams.get("installation_id") ?? polledInstallationId ?? "";
|
|
13064
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
13065
|
+
res.end(renderStatusPage({ state: "installed", org: args.org, slug: createdApp?.slug ?? "", appName: createdApp?.name ?? "" }));
|
|
13066
|
+
complete(id);
|
|
12998
13067
|
return;
|
|
12999
13068
|
}
|
|
13000
13069
|
if (u.pathname === "/callback") {
|
|
@@ -13005,11 +13074,26 @@ async function createGitHubApp(args) {
|
|
|
13005
13074
|
return;
|
|
13006
13075
|
}
|
|
13007
13076
|
try {
|
|
13008
|
-
const app = await
|
|
13077
|
+
const app = await convert(code);
|
|
13078
|
+
createdApp = app;
|
|
13009
13079
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
13010
|
-
res.end(
|
|
13011
|
-
|
|
13012
|
-
|
|
13080
|
+
res.end(renderStatusPage({ state: "awaiting-install", org: args.org, slug: app.slug, appName: app.name }));
|
|
13081
|
+
args.pollInstallation(app).then(
|
|
13082
|
+
(installationId) => {
|
|
13083
|
+
polledInstallationId = installationId;
|
|
13084
|
+
setTimeout(() => {
|
|
13085
|
+
complete(installationId);
|
|
13086
|
+
}, pollFallbackMs);
|
|
13087
|
+
},
|
|
13088
|
+
(e) => {
|
|
13089
|
+
if (settled) return;
|
|
13090
|
+
settled = true;
|
|
13091
|
+
setTimeout(() => {
|
|
13092
|
+
server.close();
|
|
13093
|
+
}, 2500);
|
|
13094
|
+
reject(e instanceof Error ? e : new Error(String(e)));
|
|
13095
|
+
}
|
|
13096
|
+
);
|
|
13013
13097
|
} catch (e) {
|
|
13014
13098
|
res.writeHead(500);
|
|
13015
13099
|
res.end(String(e));
|
|
@@ -13023,13 +13107,11 @@ async function createGitHubApp(args) {
|
|
|
13023
13107
|
})();
|
|
13024
13108
|
});
|
|
13025
13109
|
server.on("error", (e) => {
|
|
13026
|
-
reject(
|
|
13027
|
-
new LocalCliError({ code: "GH_APP_SERVER", message: `localhost server failed: ${e.message}` })
|
|
13028
|
-
);
|
|
13110
|
+
reject(new LocalCliError({ code: "GH_APP_SERVER", message: `localhost server failed: ${e.message}` }));
|
|
13029
13111
|
});
|
|
13030
13112
|
server.listen(args.port, () => {
|
|
13031
|
-
const url =
|
|
13032
|
-
args.onProgress?.(`Opening ${url} \u2014
|
|
13113
|
+
const url = `${base}/`;
|
|
13114
|
+
args.onProgress?.(`Opening ${url} \u2014 follow the page: Create the App, then Install it on ${args.org}.`);
|
|
13033
13115
|
void args.openBrowser(url);
|
|
13034
13116
|
});
|
|
13035
13117
|
});
|
|
@@ -13281,12 +13363,13 @@ var BrainAppCreateCommand = class extends M8tCommand {
|
|
|
13281
13363
|
hint: "For a personal account, use the gh fallback (m8t brain create without an App)."
|
|
13282
13364
|
});
|
|
13283
13365
|
}
|
|
13366
|
+
const org = this.org;
|
|
13284
13367
|
const pemOut = typeof this.pemOut === "string" ? this.pemOut : path4.join(os.homedir(), ".m8t-stack", "github-app.pem");
|
|
13285
13368
|
const credsOut = typeof this.credsOut === "string" ? this.credsOut : path4.join(os.homedir(), ".m8t-stack", "github-app.json");
|
|
13286
13369
|
const port = Number(this.port);
|
|
13287
13370
|
const onProgress = (m) => this.context.stderr.write(` ${colors.dim(m)}
|
|
13288
13371
|
`);
|
|
13289
|
-
const existing = await findValidBrainApp(credsOut,
|
|
13372
|
+
const existing = await findValidBrainApp(credsOut, org);
|
|
13290
13373
|
if (existing) {
|
|
13291
13374
|
const isTTY = this.context.stdout.isTTY === true;
|
|
13292
13375
|
let keep;
|
|
@@ -13296,59 +13379,55 @@ var BrainAppCreateCommand = class extends M8tCommand {
|
|
|
13296
13379
|
keep = await promptKeepNew(
|
|
13297
13380
|
{ stdin: this.context.stdin, stdout: this.context.stdout },
|
|
13298
13381
|
existing.slug,
|
|
13299
|
-
|
|
13382
|
+
org
|
|
13300
13383
|
);
|
|
13301
13384
|
else keep = true;
|
|
13302
13385
|
if (keep) {
|
|
13303
13386
|
this.context.stdout.write(
|
|
13304
|
-
`${colors.success("\u2713")} Reusing the existing brain App ${colors.field(existing.slug)} on ${
|
|
13387
|
+
`${colors.success("\u2713")} Reusing the existing brain App ${colors.field(existing.slug)} on ${org} (installation ${existing.installationId}).
|
|
13305
13388
|
${colors.dim("Pass --new to create a fresh App instead.")}
|
|
13306
13389
|
`
|
|
13307
13390
|
);
|
|
13308
13391
|
if (this.output === "json") this.context.stdout.write(JSON.stringify(existing) + "\n");
|
|
13309
13392
|
return 0;
|
|
13310
13393
|
}
|
|
13311
|
-
onProgress(`creating a NEW brain App on ${
|
|
13394
|
+
onProgress(`creating a NEW brain App on ${org} (existing ${existing.slug} left intact)\u2026`);
|
|
13312
13395
|
}
|
|
13313
13396
|
const nameSuffix = randomBytes(3).toString("hex");
|
|
13314
|
-
|
|
13315
|
-
org
|
|
13397
|
+
this.context.stdout.write(
|
|
13398
|
+
`${colors.field("\u2192")} Opening your browser to create + install the GitHub App on ${org}.
|
|
13399
|
+
GitHub may ask you to confirm your identity once or twice \u2014 a sign-in plus a security re-check for creating an org app. That's expected, not an error.
|
|
13400
|
+
${colors.dim(`Follow the page: click Create GitHub App, then Install (leave "All repositories"). GitHub brings you right back here when it's done.`)}
|
|
13401
|
+
`
|
|
13402
|
+
);
|
|
13403
|
+
const { app, installationId } = await createGitHubApp({
|
|
13404
|
+
org,
|
|
13316
13405
|
port,
|
|
13317
13406
|
nameSuffix,
|
|
13318
13407
|
openBrowser: (u) => {
|
|
13319
13408
|
void tryOpenUrl(u);
|
|
13320
13409
|
},
|
|
13410
|
+
pollInstallation: (a) => pollOrgInstallation({
|
|
13411
|
+
appId: a.id,
|
|
13412
|
+
pem: a.pem,
|
|
13413
|
+
org,
|
|
13414
|
+
onTick: (n) => {
|
|
13415
|
+
if (n % 5 === 0) onProgress(`\u2026waiting for install (${(n * 2).toString()}s)`);
|
|
13416
|
+
}
|
|
13417
|
+
}),
|
|
13321
13418
|
onProgress
|
|
13322
13419
|
});
|
|
13323
13420
|
fs4.mkdirSync(path4.dirname(pemOut), { recursive: true });
|
|
13324
13421
|
fs4.writeFileSync(pemOut, app.pem, { mode: 384 });
|
|
13325
13422
|
this.context.stdout.write(
|
|
13326
13423
|
`${colors.success("\u2713")} App created: ${colors.field(app.slug)} (id ${app.id}); pem \u2192 ${pemOut}
|
|
13327
|
-
|
|
13328
|
-
);
|
|
13329
|
-
const installUrl2 = `https://github.com/apps/${app.slug}/installations/new`;
|
|
13330
|
-
this.context.stdout.write(
|
|
13331
|
-
`${colors.field("\u2192")} Install it (default: All repositories):
|
|
13332
|
-
${installUrl2}
|
|
13333
|
-
`
|
|
13334
|
-
);
|
|
13335
|
-
void tryOpenUrl(installUrl2);
|
|
13336
|
-
const installationId = await pollOrgInstallation({
|
|
13337
|
-
appId: app.id,
|
|
13338
|
-
pem: app.pem,
|
|
13339
|
-
org: this.org,
|
|
13340
|
-
onTick: (n) => {
|
|
13341
|
-
if (n % 5 === 0) onProgress(`\u2026waiting for install (${(n * 2).toString()}s)`);
|
|
13342
|
-
}
|
|
13343
|
-
});
|
|
13344
|
-
this.context.stdout.write(
|
|
13345
|
-
`${colors.success("\u2713")} installed on ${this.org} (installation ${installationId})
|
|
13424
|
+
${colors.success("\u2713")} installed on ${org} (installation ${installationId})
|
|
13346
13425
|
`
|
|
13347
13426
|
);
|
|
13348
13427
|
const creds = {
|
|
13349
13428
|
appId: app.id,
|
|
13350
13429
|
slug: app.slug,
|
|
13351
|
-
org
|
|
13430
|
+
org,
|
|
13352
13431
|
installationId,
|
|
13353
13432
|
pemPath: pemOut
|
|
13354
13433
|
};
|
|
@@ -13667,6 +13746,40 @@ ${LOADER_END}
|
|
|
13667
13746
|
`;
|
|
13668
13747
|
}
|
|
13669
13748
|
|
|
13749
|
+
// src/lib/a2a-snippet.ts
|
|
13750
|
+
var SNIPPET_START = "<!-- m8t:a2a:start -->";
|
|
13751
|
+
var SNIPPET_END = "<!-- m8t:a2a:end -->";
|
|
13752
|
+
var DELEGATE_SNIPPET = `${SNIPPET_START}
|
|
13753
|
+
## Working with other workers
|
|
13754
|
+
You can delegate to other m8t workers. Before you do:
|
|
13755
|
+
1. Call \`discover_workers\` to see who's available now \u2014 each entry's card says what
|
|
13756
|
+
the worker is for, when to delegate to it, and what it accepts and returns.
|
|
13757
|
+
2. If one is a clear fit, call \`invoke_worker(target, task)\` \u2014 \`target\` is the name
|
|
13758
|
+
from the directory; \`task\` is a complete, self-contained instruction (the worker
|
|
13759
|
+
can't ask follow-ups mid-task).
|
|
13760
|
+
3. If invoke_worker returns status "in_progress" with a taskId, the worker is taking
|
|
13761
|
+
a while \u2014 call \`check_delegation(taskId)\` to fetch the result, repeating until the
|
|
13762
|
+
status is no longer "in_progress".
|
|
13763
|
+
4. Fold the worker's result into your own answer.
|
|
13764
|
+
Delegate only when another worker is genuinely better suited; otherwise answer
|
|
13765
|
+
directly. Don't re-delegate the same request in a loop.
|
|
13766
|
+
${SNIPPET_END}`;
|
|
13767
|
+
function esc(s) {
|
|
13768
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
13769
|
+
}
|
|
13770
|
+
function stripSnippet(instructions) {
|
|
13771
|
+
const re = new RegExp(`\\n*${esc(SNIPPET_START)}[\\s\\S]*?${esc(SNIPPET_END)}\\n*`, "g");
|
|
13772
|
+
return instructions.replace(re, "\n").replace(/\s+$/, "");
|
|
13773
|
+
}
|
|
13774
|
+
function appendSnippet(instructions) {
|
|
13775
|
+
return `${stripSnippet(instructions).replace(/\s+$/, "")}
|
|
13776
|
+
|
|
13777
|
+
${DELEGATE_SNIPPET}`;
|
|
13778
|
+
}
|
|
13779
|
+
function hasA2aSnippet(instructions) {
|
|
13780
|
+
return instructions.includes(SNIPPET_START);
|
|
13781
|
+
}
|
|
13782
|
+
|
|
13670
13783
|
// src/lib/brain-link.ts
|
|
13671
13784
|
init_foundry_agent_get();
|
|
13672
13785
|
init_brain_yaml_mirror();
|
|
@@ -13759,7 +13872,10 @@ async function linkBrain(args) {
|
|
|
13759
13872
|
progress("No local persona yaml \u2014 basing instructions on the deployed agent\u2026");
|
|
13760
13873
|
base = current.definition.instructions ?? "";
|
|
13761
13874
|
}
|
|
13762
|
-
|
|
13875
|
+
let instructionsWithLoader = appendBrainLoader(base, loader);
|
|
13876
|
+
if (hasA2aSnippet(current.definition.instructions ?? "")) {
|
|
13877
|
+
instructionsWithLoader = appendSnippet(instructionsWithLoader);
|
|
13878
|
+
}
|
|
13763
13879
|
const link = {
|
|
13764
13880
|
repo: args.repo,
|
|
13765
13881
|
branch: args.branch,
|
|
@@ -14891,7 +15007,7 @@ init_errors();
|
|
|
14891
15007
|
// src/lib/deploy-prompt-advisor.ts
|
|
14892
15008
|
import * as fs12 from "fs";
|
|
14893
15009
|
import * as path11 from "path";
|
|
14894
|
-
import { parse as
|
|
15010
|
+
import { parse as parseYaml5 } from "yaml";
|
|
14895
15011
|
init_foundry_agents();
|
|
14896
15012
|
init_errors();
|
|
14897
15013
|
function findPersonasRoot(hint) {
|
|
@@ -14919,7 +15035,7 @@ async function deployPromptAdvisor(args) {
|
|
|
14919
15035
|
});
|
|
14920
15036
|
}
|
|
14921
15037
|
const fmMatch = /^---\n([\s\S]*?)\n---/.exec(raw);
|
|
14922
|
-
const fm = fmMatch ?
|
|
15038
|
+
const fm = fmMatch ? parseYaml5(fmMatch[1]) : {};
|
|
14923
15039
|
const model = args.model ?? fm.targets?.foundry?.model;
|
|
14924
15040
|
if (!model) {
|
|
14925
15041
|
throw new LocalCliError({
|
|
@@ -15084,7 +15200,7 @@ import { randomBytes as randomBytes2, createHash } from "crypto";
|
|
|
15084
15200
|
init_errors();
|
|
15085
15201
|
init_esm();
|
|
15086
15202
|
import * as fs13 from "fs";
|
|
15087
|
-
import { parse as
|
|
15203
|
+
import { parse as parseYaml6 } from "yaml";
|
|
15088
15204
|
function readPersonaA2aCard(personaPath) {
|
|
15089
15205
|
let raw;
|
|
15090
15206
|
try {
|
|
@@ -15094,7 +15210,7 @@ function readPersonaA2aCard(personaPath) {
|
|
|
15094
15210
|
}
|
|
15095
15211
|
const fm = /^---\n([\s\S]*?)\n---/.exec(raw);
|
|
15096
15212
|
if (!fm) throw new LocalCliError({ code: "PERSONA_NO_FRONTMATTER", message: `Persona '${personaPath}' has no frontmatter.` });
|
|
15097
|
-
const front =
|
|
15213
|
+
const front = parseYaml6(fm[1]);
|
|
15098
15214
|
const role = typeof front.role === "string" ? front.role : "";
|
|
15099
15215
|
const cardYaml = front.targets?.foundry?.["a2a-card"];
|
|
15100
15216
|
if (!cardYaml || typeof cardYaml !== "object") {
|
|
@@ -15129,23 +15245,6 @@ var FOUNDRY_ARM_API3 = "2025-04-01-preview";
|
|
|
15129
15245
|
var FOUNDRY_DATA_SCOPE3 = "https://ai.azure.com/.default";
|
|
15130
15246
|
var ARM_SCOPE6 = "https://management.azure.com/.default";
|
|
15131
15247
|
var A2A_TOOLS = ["discover_workers", "invoke_worker", "check_delegation"];
|
|
15132
|
-
var SNIPPET_START = "<!-- m8t:a2a:start -->";
|
|
15133
|
-
var SNIPPET_END = "<!-- m8t:a2a:end -->";
|
|
15134
|
-
var DELEGATE_SNIPPET = `${SNIPPET_START}
|
|
15135
|
-
## Working with other workers
|
|
15136
|
-
You can delegate to other m8t workers. Before you do:
|
|
15137
|
-
1. Call \`discover_workers\` to see who's available now \u2014 each entry's card says what
|
|
15138
|
-
the worker is for, when to delegate to it, and what it accepts and returns.
|
|
15139
|
-
2. If one is a clear fit, call \`invoke_worker(target, task)\` \u2014 \`target\` is the name
|
|
15140
|
-
from the directory; \`task\` is a complete, self-contained instruction (the worker
|
|
15141
|
-
can't ask follow-ups mid-task).
|
|
15142
|
-
3. If invoke_worker returns status "in_progress" with a taskId, the worker is taking
|
|
15143
|
-
a while \u2014 call \`check_delegation(taskId)\` to fetch the result, repeating until the
|
|
15144
|
-
status is no longer "in_progress".
|
|
15145
|
-
4. Fold the worker's result into your own answer.
|
|
15146
|
-
Delegate only when another worker is genuinely better suited; otherwise answer
|
|
15147
|
-
directly. Don't re-delegate the same request in a loop.
|
|
15148
|
-
${SNIPPET_END}`;
|
|
15149
15248
|
async function enableA2a(args) {
|
|
15150
15249
|
const progress = args.onProgress ?? ((_m) => {
|
|
15151
15250
|
});
|
|
@@ -15204,15 +15303,6 @@ async function disableA2a(args) {
|
|
|
15204
15303
|
await deleteConnection2({ credential: args.credential, projectArmId: args.projectArmId, connectionName });
|
|
15205
15304
|
return { connectionName, foundryVersion };
|
|
15206
15305
|
}
|
|
15207
|
-
function appendSnippet(instructions) {
|
|
15208
|
-
return `${stripSnippet(instructions).replace(/\s+$/, "")}
|
|
15209
|
-
|
|
15210
|
-
${DELEGATE_SNIPPET}`;
|
|
15211
|
-
}
|
|
15212
|
-
function stripSnippet(instructions) {
|
|
15213
|
-
const re = new RegExp(`\\n*${esc(SNIPPET_START)}[\\s\\S]*?${esc(SNIPPET_END)}\\n*`, "g");
|
|
15214
|
-
return instructions.replace(re, "\n").replace(/\s+$/, "");
|
|
15215
|
-
}
|
|
15216
15306
|
function withA2aTool(tools, bridgeUrl, connectionName) {
|
|
15217
15307
|
const others = tools.filter(
|
|
15218
15308
|
(t) => !(t.type === "mcp" && t.server_label === "a2a")
|
|
@@ -15222,9 +15312,6 @@ function withA2aTool(tools, bridgeUrl, connectionName) {
|
|
|
15222
15312
|
{ type: "mcp", server_label: "a2a", server_url: bridgeUrl, allowed_tools: A2A_TOOLS, require_approval: "never", project_connection_id: connectionName }
|
|
15223
15313
|
];
|
|
15224
15314
|
}
|
|
15225
|
-
function esc(s) {
|
|
15226
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
15227
|
-
}
|
|
15228
15315
|
async function putA2aConnection(args) {
|
|
15229
15316
|
const token = await args.credential.getToken(ARM_SCOPE6);
|
|
15230
15317
|
if (!token?.token) throw new LocalCliError({ code: "ARM_AUTH", message: "Could not acquire ARM token" });
|
|
@@ -15602,7 +15689,7 @@ init_errors();
|
|
|
15602
15689
|
import * as fs15 from "fs";
|
|
15603
15690
|
import * as os6 from "os";
|
|
15604
15691
|
import * as path12 from "path";
|
|
15605
|
-
import { parse as
|
|
15692
|
+
import { parse as parseYaml7 } from "yaml";
|
|
15606
15693
|
var A2A_PATH = "/api/a2a/mcp";
|
|
15607
15694
|
function resolveBridgeUrl(opts) {
|
|
15608
15695
|
const env = opts.env ?? process.env;
|
|
@@ -15626,7 +15713,7 @@ function readConfigGatewayUrl(home) {
|
|
|
15626
15713
|
const cfg = path12.join(home ?? os6.homedir(), ".m8t-stack", "config.yaml");
|
|
15627
15714
|
if (!fs15.existsSync(cfg)) return void 0;
|
|
15628
15715
|
try {
|
|
15629
|
-
const o =
|
|
15716
|
+
const o = parseYaml7(fs15.readFileSync(cfg, "utf8"));
|
|
15630
15717
|
return typeof o.gatewayUrl === "string" ? o.gatewayUrl : void 0;
|
|
15631
15718
|
} catch {
|
|
15632
15719
|
return void 0;
|
|
@@ -16128,7 +16215,7 @@ async function deployHostedWorker(args) {
|
|
|
16128
16215
|
// src/lib/persona.ts
|
|
16129
16216
|
import * as fs17 from "fs";
|
|
16130
16217
|
import * as path15 from "path";
|
|
16131
|
-
import { parse as
|
|
16218
|
+
import { parse as parseYaml8 } from "yaml";
|
|
16132
16219
|
function findRepoRoot(startDir) {
|
|
16133
16220
|
let dir = path15.resolve(startDir);
|
|
16134
16221
|
for (; ; ) {
|
|
@@ -16152,7 +16239,7 @@ function resolvePersona(persona, cwd = process.cwd()) {
|
|
|
16152
16239
|
if (!match) return { persona, personaVersion: null };
|
|
16153
16240
|
let fm = null;
|
|
16154
16241
|
try {
|
|
16155
|
-
fm =
|
|
16242
|
+
fm = parseYaml8(match[1]);
|
|
16156
16243
|
} catch {
|
|
16157
16244
|
return { persona, personaVersion: null };
|
|
16158
16245
|
}
|
|
@@ -18932,7 +19019,7 @@ import { Command as Command40, Option as Option38 } from "clipanion";
|
|
|
18932
19019
|
// src/lib/profiles.ts
|
|
18933
19020
|
import * as fs20 from "fs/promises";
|
|
18934
19021
|
import * as path21 from "path";
|
|
18935
|
-
import { parse as
|
|
19022
|
+
import { parse as parseYaml9, stringify as stringifyYaml4 } from "yaml";
|
|
18936
19023
|
function profilesDir() {
|
|
18937
19024
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
|
|
18938
19025
|
return path21.join(home, ".m8t-stack", "profiles");
|
|
@@ -18957,7 +19044,7 @@ async function listProfiles() {
|
|
|
18957
19044
|
async function readProfile(name) {
|
|
18958
19045
|
try {
|
|
18959
19046
|
const raw = await fs20.readFile(getProfilePath(name), "utf8");
|
|
18960
|
-
const parsed =
|
|
19047
|
+
const parsed = parseYaml9(raw);
|
|
18961
19048
|
if (!parsed || typeof parsed !== "object") return null;
|
|
18962
19049
|
return parsed;
|
|
18963
19050
|
} catch (e) {
|
|
@@ -21224,7 +21311,7 @@ async function emitDigest(input, deps, digest) {
|
|
|
21224
21311
|
}
|
|
21225
21312
|
|
|
21226
21313
|
// ../../packages/brain/engine/dist/esm/dream/config.js
|
|
21227
|
-
import { parse as
|
|
21314
|
+
import { parse as parseYaml10 } from "yaml";
|
|
21228
21315
|
var DEFAULT_DREAM_MODEL = "gpt-5.4";
|
|
21229
21316
|
function asCadence(v) {
|
|
21230
21317
|
return v === "nightly" ? "nightly" : "off";
|
|
@@ -21232,7 +21319,7 @@ function asCadence(v) {
|
|
|
21232
21319
|
function parseDreamerConfig(rawYaml, env) {
|
|
21233
21320
|
let parsed;
|
|
21234
21321
|
try {
|
|
21235
|
-
parsed =
|
|
21322
|
+
parsed = parseYaml10(rawYaml);
|
|
21236
21323
|
} catch {
|
|
21237
21324
|
return { enabled: false, cadence: "off", model: env.M8T_DREAM_MODEL ?? DEFAULT_DREAM_MODEL };
|
|
21238
21325
|
}
|
|
@@ -21249,10 +21336,10 @@ function parseDreamerConfig(rawYaml, env) {
|
|
|
21249
21336
|
var ZERO_WIDTH_RE = new RegExp("\u200B|\u200C|\u200D|\uFEFF", "g");
|
|
21250
21337
|
|
|
21251
21338
|
// ../../packages/brain/engine/dist/esm/librarian/janitor/frontmatter.js
|
|
21252
|
-
import { parse as
|
|
21339
|
+
import { parse as parseYaml11, stringify as stringifyYaml5 } from "yaml";
|
|
21253
21340
|
|
|
21254
21341
|
// ../../packages/brain/engine/dist/esm/librarian/config.js
|
|
21255
|
-
import { parse as
|
|
21342
|
+
import { parse as parseYaml12 } from "yaml";
|
|
21256
21343
|
|
|
21257
21344
|
// src/lib/dream-discovery.ts
|
|
21258
21345
|
init_http();
|
|
@@ -23119,6 +23206,37 @@ async function reactiveSeedOnFinish(args) {
|
|
|
23119
23206
|
);
|
|
23120
23207
|
}
|
|
23121
23208
|
|
|
23209
|
+
// src/lib/install-summary.ts
|
|
23210
|
+
var DEFAULT_WORKERS = [
|
|
23211
|
+
{ name: "stacey", label: "Stacey \u2014 Startup Advisor", brain: "stacey-brain", executor: "stacey-coder" },
|
|
23212
|
+
{ name: "azzy", label: "Azzy \u2014 Azure Advisor", brain: "azzy-brain", executor: "azure-executor" }
|
|
23213
|
+
];
|
|
23214
|
+
function renderInstallSummary(args) {
|
|
23215
|
+
const open = args.webappUrl ? `${args.webappUrl} (or run: m8t open)` : "run: m8t open";
|
|
23216
|
+
const lines = [
|
|
23217
|
+
"\u2705 Your m8t platform is ready to use.",
|
|
23218
|
+
` Open it: ${open}`,
|
|
23219
|
+
""
|
|
23220
|
+
];
|
|
23221
|
+
if (args.brainOrg) {
|
|
23222
|
+
lines.push(" Your team:");
|
|
23223
|
+
for (const w of DEFAULT_WORKERS) {
|
|
23224
|
+
lines.push(` \u2022 ${w.label} \xB7 brain: ${args.brainOrg}/${w.brain} \xB7 executor: ${w.executor}`);
|
|
23225
|
+
}
|
|
23226
|
+
lines.push(
|
|
23227
|
+
"",
|
|
23228
|
+
" Reach them: @stacey / @azzy (or /stacey) in your coding agent",
|
|
23229
|
+
" \xB7 the webapp \xB7 or connect Telegram in the webapp (Channels)"
|
|
23230
|
+
);
|
|
23231
|
+
} else {
|
|
23232
|
+
lines.push(
|
|
23233
|
+
" No brain-backed workers yet \u2014 deploy one with: m8t coder deploy <name> (or the m8t-architect skill)",
|
|
23234
|
+
" Reach your platform via the webapp, or `m8t open`."
|
|
23235
|
+
);
|
|
23236
|
+
}
|
|
23237
|
+
return lines.join("\n") + "\n";
|
|
23238
|
+
}
|
|
23239
|
+
|
|
23122
23240
|
// src/commands/bootstrap/finish.ts
|
|
23123
23241
|
var BootstrapFinishCommand = class extends M8tCommand {
|
|
23124
23242
|
static paths = [["bootstrap", "finish"]];
|
|
@@ -23150,6 +23268,7 @@ var BootstrapFinishCommand = class extends M8tCommand {
|
|
|
23150
23268
|
`, "utf8");
|
|
23151
23269
|
const subscriptionId = (typeof this.subscription === "string" ? this.subscription : void 0) ?? state.subscriptionId;
|
|
23152
23270
|
const resourceGroup = (typeof this.resourceGroup === "string" ? this.resourceGroup : void 0) ?? state.resourceGroup;
|
|
23271
|
+
let webappUrl;
|
|
23153
23272
|
try {
|
|
23154
23273
|
const d = await discoverGateway({ subscriptionId, interactive: false, resourceGroup });
|
|
23155
23274
|
await writeConfig({
|
|
@@ -23160,6 +23279,7 @@ var BootstrapFinishCommand = class extends M8tCommand {
|
|
|
23160
23279
|
containerAppResourceId: d.containerAppResourceId,
|
|
23161
23280
|
cachedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
23162
23281
|
});
|
|
23282
|
+
webappUrl = d.gatewayUrl;
|
|
23163
23283
|
this.context.stdout.write(
|
|
23164
23284
|
`${colors.success("\u2705 The platform is live and your local tools are pointed at it.")}
|
|
23165
23285
|
gateway: ${d.gatewayUrl}
|
|
@@ -23178,6 +23298,14 @@ var BootstrapFinishCommand = class extends M8tCommand {
|
|
|
23178
23298
|
` : "") + "\n"
|
|
23179
23299
|
);
|
|
23180
23300
|
}
|
|
23301
|
+
let brainOrg = null;
|
|
23302
|
+
try {
|
|
23303
|
+
const credsRaw = await fs24.readFile(path26.join(markerDir, "github-app.json"), "utf8");
|
|
23304
|
+
const creds = JSON.parse(credsRaw);
|
|
23305
|
+
brainOrg = typeof creds.org === "string" ? creds.org : null;
|
|
23306
|
+
} catch {
|
|
23307
|
+
}
|
|
23308
|
+
this.context.stdout.write(renderInstallSummary({ webappUrl, brainOrg }) + "\n");
|
|
23181
23309
|
this.context.stdout.write(
|
|
23182
23310
|
`${colors.field("Finish wiring your coding agent:")}
|
|
23183
23311
|
\u2022 Render the m8t personas + MCP into your host \u2014 follow guides/install/m8t.md (step 4) and, optionally, guides/install/m8t-stack.md (step 5).
|
|
@@ -23392,6 +23520,16 @@ async function serveOnboardingUiDetached(args) {
|
|
|
23392
23520
|
}
|
|
23393
23521
|
return { alreadyRunning: false, logPath };
|
|
23394
23522
|
}
|
|
23523
|
+
function autoOpenOnboardingUi(url, open = tryOpenUrl) {
|
|
23524
|
+
try {
|
|
23525
|
+
const result = open(url);
|
|
23526
|
+
if (result instanceof Promise) {
|
|
23527
|
+
result.catch(() => {
|
|
23528
|
+
});
|
|
23529
|
+
}
|
|
23530
|
+
} catch {
|
|
23531
|
+
}
|
|
23532
|
+
}
|
|
23395
23533
|
function stopOnboardingUi(home = os15.homedir()) {
|
|
23396
23534
|
const { pidPath } = onboardingUiPaths(home);
|
|
23397
23535
|
let pidStr;
|
|
@@ -23557,7 +23695,8 @@ var BootstrapUiCommand = class extends M8tCommand {
|
|
|
23557
23695
|
this.context.stdout.write(
|
|
23558
23696
|
`${colors.success("\u2713")} Simple Stacey is live (stacey-intake v${version}).
|
|
23559
23697
|
env: ${envPath}
|
|
23560
|
-
|
|
23698
|
+
Opening ${colors.field("http://localhost:3000")} in your browser \u2192 Sign in with Microsoft \u2192 chat with Stacey.
|
|
23699
|
+
${colors.dim("(If it doesn't open, browse to http://localhost:3000 yourself.)")}
|
|
23561
23700
|
${colors.dim("First turn may say 'warming up' for a few minutes while access propagates \u2014 that's expected.")}
|
|
23562
23701
|
`
|
|
23563
23702
|
);
|
|
@@ -23585,6 +23724,7 @@ var BootstrapUiCommand = class extends M8tCommand {
|
|
|
23585
23724
|
`
|
|
23586
23725
|
);
|
|
23587
23726
|
}
|
|
23727
|
+
autoOpenOnboardingUi(`http://localhost:${this.port}`);
|
|
23588
23728
|
return 0;
|
|
23589
23729
|
}
|
|
23590
23730
|
};
|