@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 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: "1"`,
660
+ ` schemaVersion: "2"`,
660
661
  ` persona: "${args.persona}"`,
661
- ` agent: "${args.agent}"`,
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" + linkSection(args);
701
+ return authoritative + MIRROR_BANNER + "\n" + link;
674
702
  }
675
703
  }
676
- return DEFAULT_AUTHORITATIVE + "\n\n" + MIRROR_BANNER + "\n" + linkSection(args);
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 getRes = await fetch(
687
- `https://api.github.com/repos/${owner}/${repoName}/contents/.m8t/brain.yaml`,
688
- {
689
- headers: {
690
- Authorization: `Bearer ${minted.token}`,
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 putRes = await fetch(
718
- `https://api.github.com/repos/${owner}/${repoName}/contents/.m8t/brain.yaml`,
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.8";
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 = `http://localhost:${args.port.toString()}/callback`;
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
- const server = createServer((req2, res) => {
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 ?? "/", `http://localhost:${args.port.toString()}`);
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 convertManifestCode(code);
13077
+ const app = await convert(code);
13078
+ createdApp = app;
13009
13079
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
13010
- res.end(`<!doctype html><meta charset="utf-8"><h3>App created \u2705</h3><p>Return to the terminal.</p>`);
13011
- server.close();
13012
- resolve3(app);
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 = `http://localhost:${args.port.toString()}/`;
13032
- args.onProgress?.(`Opening ${url} \u2014 click "Create GitHub App" on GitHub's pre-filled page.`);
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, this.org);
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
- this.org
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 ${this.org} (installation ${existing.installationId}).
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 ${this.org} (existing ${existing.slug} left intact)\u2026`);
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
- const app = await createGitHubApp({
13315
- org: this.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: this.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
- const instructionsWithLoader = appendBrainLoader(base, loader);
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 parseYaml4 } from "yaml";
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 ? parseYaml4(fmMatch[1]) : {};
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 parseYaml5 } from "yaml";
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 = parseYaml5(fm[1]);
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 parseYaml6 } from "yaml";
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 = parseYaml6(fs15.readFileSync(cfg, "utf8"));
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 parseYaml7 } from "yaml";
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 = parseYaml7(match[1]);
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 parseYaml8, stringify as stringifyYaml4 } from "yaml";
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 = parseYaml8(raw);
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 parseYaml9 } from "yaml";
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 = parseYaml9(rawYaml);
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 parseYaml10, stringify as stringifyYaml5 } from "yaml";
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 parseYaml11 } from "yaml";
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
- Open ${colors.field("http://localhost:3000")} \u2192 Sign in with Microsoft \u2192 chat with Stacey.
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
  };