@m8t-stack/cli 0.2.12 → 0.2.13

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
@@ -1195,7 +1195,7 @@ var init_enable_hosted_brain = __esm({
1195
1195
  import { Builtins, Cli } from "clipanion";
1196
1196
 
1197
1197
  // src/lib/package-version.ts
1198
- var CLI_VERSION = "0.2.12";
1198
+ var CLI_VERSION = "0.2.13";
1199
1199
 
1200
1200
  // src/lib/render-error.ts
1201
1201
  init_errors();
@@ -15064,10 +15064,9 @@ async function deployPromptAdvisor(args) {
15064
15064
  const fillableFields = fm["fillable-fields"] ?? [];
15065
15065
  const valuesMap = {};
15066
15066
  for (const field of fillableFields) {
15067
- if (field.default !== void 0) {
15068
- valuesMap[field.name] = field.default;
15069
- }
15067
+ if (field.default !== void 0) valuesMap[field.name] = field.default;
15070
15068
  }
15069
+ for (const [k, v] of Object.entries(args.fieldOverrides ?? {})) valuesMap[k] = v;
15071
15070
  const instructions = renderPersonaBody(personaPath, valuesMap);
15072
15071
  return createPromptVersion({
15073
15072
  credential: args.credential,
@@ -23160,9 +23159,9 @@ function companyProfileBodyEquals(a, b) {
23160
23159
  const strip = (s) => s.replace(/^(created|updated):.*$/gm, "").trim();
23161
23160
  return strip(a) === strip(b);
23162
23161
  }
23163
- function upsertMemoryIndex(existing, line2) {
23162
+ function upsertMemoryIndex(existing, line2, targetPath2 = COMPANY_PROFILE_PATH) {
23164
23163
  const lines = existing.split("\n");
23165
- const existingIdx = lines.findIndex((l) => l.includes(`\`${COMPANY_PROFILE_PATH}\``));
23164
+ const existingIdx = lines.findIndex((l) => l.includes(`\`${targetPath2}\``));
23166
23165
  if (existingIdx >= 0) {
23167
23166
  lines[existingIdx] = line2;
23168
23167
  return lines.join("\n");
@@ -23174,6 +23173,88 @@ function upsertMemoryIndex(existing, line2) {
23174
23173
  }
23175
23174
  return existing.replace(/\s*$/, "\n\n") + line2 + "\n";
23176
23175
  }
23176
+ var FOUNDER_RECORD_PATH = "memory/founder.md";
23177
+ var NOT_CAPTURED = "_not captured yet \u2014 add it anytime (run `m8t bootstrap seed-profile`, or just tell me)_";
23178
+ function renderFounderRecord(block, inputs, now = (/* @__PURE__ */ new Date()).toISOString()) {
23179
+ const pick = (...vals) => vals.map((v) => v?.trim()).find(Boolean) ?? "";
23180
+ const founderName = pick(block.founder_name, inputs.azIdentity?.name);
23181
+ const founderEmail = pick(block.founder_email, inputs.azIdentity?.email);
23182
+ const advisorName = (block.advisor_name ?? "").trim();
23183
+ const advisorEmail = (block.advisor_email ?? "").trim();
23184
+ const subscription = (inputs.subscriptionId ?? "").trim();
23185
+ const teamSize = (block.team_size ?? "").trim();
23186
+ const advisorRendered = advisorName || advisorEmail ? [advisorName, advisorEmail ? `<${advisorEmail}>` : ""].filter(Boolean).join(" ") : NOT_CAPTURED;
23187
+ const founderMd = [
23188
+ `---`,
23189
+ `type: memory`,
23190
+ `title: "Founder & install context"`,
23191
+ `created: ${now}`,
23192
+ `updated: ${now}`,
23193
+ `tags: [founder, install-context, onboarding]`,
23194
+ `origin: operator`,
23195
+ `---`,
23196
+ ``,
23197
+ `# Founder & install context`,
23198
+ ``,
23199
+ `_Seeded at onboarding from your Azure identity + the questionnaire. Authoritative \u2014 read it; don't rewrite it (origin: operator)._`,
23200
+ ``,
23201
+ `- **Founder:** ${founderName || NOT_CAPTURED}`,
23202
+ `- **Founder email (company_email):** ${founderEmail || NOT_CAPTURED}`,
23203
+ `- **Microsoft Startup Advisor (SA):** ${advisorRendered}`,
23204
+ `- **Azure subscription:** ${subscription || NOT_CAPTURED}`,
23205
+ `- **Team size:** ${teamSize || "\u2014"}`,
23206
+ ``
23207
+ ].join("\n");
23208
+ const idxAdvisor = advisorEmail || advisorName || "\u2014";
23209
+ const idxSub = subscription ? `${subscription.slice(0, 8)}\u2026` : "\u2014";
23210
+ const memoryIndexLine = `- \`${FOUNDER_RECORD_PATH}\` \u2014 **Founder & install context**: ${founderName || "founder"} \xB7 advisor ${idxAdvisor} \xB7 sub ${idxSub}. (seeded from onboarding)`;
23211
+ return { founderMd, memoryIndexLine };
23212
+ }
23213
+ function founderRecordBodyEquals(a, b) {
23214
+ const strip = (s) => s.replace(/^(created|updated):.*$/gm, "").trim();
23215
+ return strip(a) === strip(b);
23216
+ }
23217
+
23218
+ // src/lib/founder-identity.ts
23219
+ function deriveEmailCandidate(raw) {
23220
+ const mail = (raw.mail ?? "").trim();
23221
+ if (mail) return mail;
23222
+ const upn = (raw.upn ?? "").trim();
23223
+ if (!upn) return "";
23224
+ const ext = upn.indexOf("#EXT#");
23225
+ if (ext > 0) {
23226
+ const local = upn.slice(0, ext);
23227
+ const at = local.lastIndexOf("_");
23228
+ return at > 0 ? `${local.slice(0, at)}@${local.slice(at + 1)}` : "";
23229
+ }
23230
+ return upn.includes("@") ? upn : "";
23231
+ }
23232
+ async function getSignedInUserIdentity(runAzImpl = runAz) {
23233
+ try {
23234
+ const raw = await runAzImpl([
23235
+ "ad",
23236
+ "signed-in-user",
23237
+ "show",
23238
+ "--query",
23239
+ "{displayName:displayName, mail:mail, upn:userPrincipalName}",
23240
+ "-o",
23241
+ "json"
23242
+ ]);
23243
+ const p = JSON.parse(raw);
23244
+ return { name: (p.displayName ?? "").trim(), email: deriveEmailCandidate({ mail: p.mail, upn: p.upn }) };
23245
+ } catch {
23246
+ return { name: "", email: "" };
23247
+ }
23248
+ }
23249
+ function composeFounderIdentityNote(id) {
23250
+ if (id.name && id.email) {
23251
+ return `You're speaking with **${id.name}**, signed in to Azure (their email looks like **${id.email}**). Open by confirming both, in your own voice \u2014 e.g. "I see you're signed in as ${id.name}, and I'll use ${id.email} for your advisor handoffs and cost reports \u2014 is that right, or should I change either?" Record exactly what they approve or correct.`;
23252
+ }
23253
+ if (id.name) {
23254
+ return `You're speaking with **${id.name}** (signed in to Azure). Confirm their name, then ask for the best contact email to reach them (that's where advisor handoffs and cost reports go).`;
23255
+ }
23256
+ return `Ask the founder their name and the best contact email to reach them (that's where advisor handoffs and cost reports go).`;
23257
+ }
23177
23258
 
23178
23259
  // src/lib/company-profile-seed.ts
23179
23260
  function readGithubAppCreds(credsPath) {
@@ -23188,18 +23269,21 @@ async function resolveSeedContext(opts) {
23188
23269
  const appCreds = opts.appCredsOverride ?? readGithubAppCreds(opts.credsPath);
23189
23270
  if (!appCreds) return null;
23190
23271
  let endpoint = opts.endpointOverride;
23272
+ let subscriptionId = opts.subscriptionId;
23191
23273
  if (!endpoint) {
23192
23274
  const state = await readBootstrapState();
23193
23275
  if (!state) {
23194
23276
  throw new LocalCliError({ code: "BOOTSTRAP_NO_STATE", message: "No bootstrap state found.", hint: "Run 'm8t bootstrap launch' first, or pass --endpoint." });
23195
23277
  }
23278
+ subscriptionId ??= state.subscriptionId;
23196
23279
  const doc = await readStatusBlob({ saName: state.statusSaName, resourceGroup: state.resourceGroup, subscriptionId: state.subscriptionId });
23197
23280
  endpoint = doc.result?.foundryEndpoint;
23198
23281
  if (!endpoint) {
23199
23282
  throw new LocalCliError({ code: "SEED_NO_ENDPOINT", message: "Could not resolve the Foundry endpoint from the install status.", hint: "Pass --endpoint <foundryEndpoint>." });
23200
23283
  }
23201
23284
  }
23202
- return { endpoint, brainRepo: opts.brainOverride ?? `${appCreds.org}/stacey-brain`, appCreds };
23285
+ const brainRepos = opts.brainOverride ? [opts.brainOverride] : [`${appCreds.org}/stacey-brain`, `${appCreds.org}/azzy-brain`];
23286
+ return { endpoint, brainRepos, appCreds, subscriptionId };
23203
23287
  }
23204
23288
  async function applyProfileToBrain(args) {
23205
23289
  const token = await mintInstallationTokenFromPem({
@@ -23208,20 +23292,31 @@ async function applyProfileToBrain(args) {
23208
23292
  installationId: args.appCreds.installationId,
23209
23293
  fetchImpl: args.fetchImpl
23210
23294
  });
23211
- const { profileMd, memoryIndexLine } = renderCompanyProfile(args.block, args.now);
23212
- const existingProfile = await readRepoFileViaApp({ token, repo: args.brainRepo, path: COMPANY_PROFILE_PATH, fetchImpl: args.fetchImpl });
23213
- const existingIndex = await readRepoFileViaApp({ token, repo: args.brainRepo, path: "memory/MEMORY.md", fetchImpl: args.fetchImpl }) ?? DEFAULT_MEMORY_INDEX_HEADER;
23214
- const nextIndex = upsertMemoryIndex(existingIndex, memoryIndexLine);
23215
- if (existingProfile && companyProfileBodyEquals(existingProfile, profileMd) && nextIndex === existingIndex) {
23295
+ const { profileMd, memoryIndexLine: companyLine } = renderCompanyProfile(args.block, args.now);
23296
+ const { founderMd, memoryIndexLine: founderLine } = renderFounderRecord(
23297
+ args.block,
23298
+ { subscriptionId: args.subscriptionId, azIdentity: args.azIdentity },
23299
+ args.now
23300
+ );
23301
+ const read = (p) => readRepoFileViaApp({ token, repo: args.brainRepo, path: p, fetchImpl: args.fetchImpl });
23302
+ const existingProfile = await read(COMPANY_PROFILE_PATH);
23303
+ const existingFounder = await read(FOUNDER_RECORD_PATH);
23304
+ const existingIndex = await read("memory/MEMORY.md") ?? DEFAULT_MEMORY_INDEX_HEADER;
23305
+ let nextIndex = upsertMemoryIndex(existingIndex, companyLine, COMPANY_PROFILE_PATH);
23306
+ nextIndex = upsertMemoryIndex(nextIndex, founderLine, FOUNDER_RECORD_PATH);
23307
+ const profileSame = existingProfile != null && companyProfileBodyEquals(existingProfile, profileMd);
23308
+ const founderSame = existingFounder != null && founderRecordBodyEquals(existingFounder, founderMd);
23309
+ if (profileSame && founderSame && nextIndex === existingIndex) {
23216
23310
  return;
23217
23311
  }
23218
23312
  await commitFilesViaApp({
23219
23313
  token,
23220
23314
  repo: args.brainRepo,
23221
23315
  branch: args.branch,
23222
- message: "seed(brain): company profile from onboarding questionnaire",
23316
+ message: "seed(brain): founder + company profile from onboarding",
23223
23317
  files: [
23224
23318
  { path: COMPANY_PROFILE_PATH, content: profileMd },
23319
+ { path: FOUNDER_RECORD_PATH, content: founderMd },
23225
23320
  { path: "memory/MEMORY.md", content: nextIndex }
23226
23321
  ],
23227
23322
  fetchImpl: args.fetchImpl
@@ -23241,20 +23336,31 @@ function spawnDetachedSeedWatch() {
23241
23336
  }
23242
23337
  }
23243
23338
  async function reactiveSeedOnFinish(args) {
23244
- const ctx = await resolveSeedContext({ endpointOverride: args.endpoint, appCredsOverride: args.appCredsOverride });
23339
+ const ctx = await resolveSeedContext({ endpointOverride: args.endpoint, appCredsOverride: args.appCredsOverride, subscriptionId: args.subscriptionId });
23245
23340
  if (!ctx) return;
23246
23341
  const token = await (args.getFoundryTokenImpl ?? getFoundryToken)();
23247
23342
  const { hadIntake, block } = await findOnboardingProfile({ endpoint: ctx.endpoint, token, fetchImpl: args.fetchImpl });
23248
23343
  if (block) {
23249
- await applyProfileToBrain({ block, brainRepo: ctx.brainRepo, branch: "main", appCreds: ctx.appCreds, fetchImpl: args.fetchImpl });
23250
- args.stdout(`${colors.success("\u2713")} Clever Stacey now knows your company (seeded ${ctx.brainRepo}).
23344
+ const azIdentity = args.azIdentity ?? await (args.getSignedInUserIdentityImpl ?? getSignedInUserIdentity)();
23345
+ for (const brainRepo of ctx.brainRepos) {
23346
+ await applyProfileToBrain({
23347
+ block,
23348
+ brainRepo,
23349
+ branch: "main",
23350
+ appCreds: ctx.appCreds,
23351
+ subscriptionId: ctx.subscriptionId,
23352
+ azIdentity,
23353
+ fetchImpl: args.fetchImpl
23354
+ });
23355
+ }
23356
+ args.stdout(`${colors.success("\u2713")} Your advisors now know your company + how to reach you (seeded ${ctx.brainRepos.join(", ")}).
23251
23357
  `);
23252
23358
  return;
23253
23359
  }
23254
23360
  if (!hadIntake) return;
23255
23361
  (args.spawnWatch ?? spawnDetachedSeedWatch)();
23256
23362
  args.stdout(
23257
- `${colors.dim("\u2139 Stacey will pick up your company profile when you finish the questionnaire (watching in the background).")}
23363
+ `${colors.dim("\u2139 Your advisors will pick up your company profile when you finish the questionnaire (watching in the background).")}
23258
23364
  ${colors.hint("or run:")} m8t bootstrap seed-profile
23259
23365
  `
23260
23366
  );
@@ -23371,6 +23477,7 @@ ${colors.field("Then open a brand new chat/session")} \u2014 new skills + MCP se
23371
23477
  try {
23372
23478
  await reactiveSeedOnFinish({
23373
23479
  endpoint: doc.result?.foundryEndpoint,
23480
+ subscriptionId,
23374
23481
  stdout: (s) => this.context.stdout.write(s)
23375
23482
  });
23376
23483
  } catch (e) {
@@ -23617,7 +23724,8 @@ async function deploySimpleStacey(args) {
23617
23724
  endpoint: args.endpoint,
23618
23725
  repoRoot: args.repoRoot,
23619
23726
  persona: SIMPLE_STACEY_PERSONA,
23620
- agentName: SIMPLE_STACEY_AGENT
23727
+ agentName: SIMPLE_STACEY_AGENT,
23728
+ fieldOverrides: args.fieldOverrides
23621
23729
  });
23622
23730
  } catch (e) {
23623
23731
  if (e && typeof e === "object" && "code" in e) {
@@ -23735,7 +23843,13 @@ var BootstrapUiCommand = class extends M8tCommand {
23735
23843
  const oid = await getSignedInUserOid();
23736
23844
  await ensureFounderFoundryRole({ credential: credential2, subscriptionId: state.subscriptionId, principalId: oid, accountScope });
23737
23845
  out("deploying Simple Stacey (stacey-intake)\u2026");
23738
- const version = await deploySimpleStacey({ credential: credential2, endpoint, repoRoot });
23846
+ const identity = await getSignedInUserIdentity();
23847
+ const version = await deploySimpleStacey({
23848
+ credential: credential2,
23849
+ endpoint,
23850
+ repoRoot,
23851
+ fieldOverrides: { founder_identity_note: composeFounderIdentityNote(identity) }
23852
+ });
23739
23853
  const envPath = writeWebEnvLocal({
23740
23854
  repoRoot,
23741
23855
  tenantId: account.tenantId,
@@ -23788,8 +23902,8 @@ import { Command as Command51, Option as Option49 } from "clipanion";
23788
23902
  var BootstrapSeedProfileCommand = class extends M8tCommand {
23789
23903
  static paths = [["bootstrap", "seed-profile"]];
23790
23904
  static usage = Command51.Usage({
23791
- description: "Seed Clever Stacey's brain with the company profile from the onboarding questionnaire.",
23792
- details: "Reads the latest stacey-intake conversation, renders memory/company-profile.md (+ its MEMORY.md index line), and commits both to <org>/stacey-brain via the GitHub App. Idempotent. --watch polls until the founder completes the questionnaire.",
23905
+ description: "Seed your advisors' brains with the founder + company profile from the onboarding questionnaire.",
23906
+ details: "Reads the latest stacey-intake conversation, renders memory/founder.md + memory/company-profile.md (+ their MEMORY.md index lines), and commits them to both <org>/stacey-brain and <org>/azzy-brain via the GitHub App. Idempotent. --watch polls until the founder completes the questionnaire.",
23793
23907
  examples: [
23794
23908
  ["Seed now (idempotent)", "$0 bootstrap seed-profile"],
23795
23909
  ["Wait for the founder to finish", "$0 bootstrap seed-profile --watch"]
@@ -23821,8 +23935,11 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
23821
23935
  const token = await getFoundryToken();
23822
23936
  const { hadIntake, block } = await findOnboardingProfile({ endpoint: ctx.endpoint, token });
23823
23937
  if (block) {
23824
- await applyProfileToBrain({ block, brainRepo: ctx.brainRepo, branch: "main", appCreds: ctx.appCreds });
23825
- this.context.stdout.write(`${colors.success("\u2713")} seeded Clever Stacey's brain (${ctx.brainRepo}) from your questionnaire.
23938
+ const azIdentity = await getSignedInUserIdentity();
23939
+ for (const brainRepo of ctx.brainRepos) {
23940
+ await applyProfileToBrain({ block, brainRepo, branch: "main", appCreds: ctx.appCreds, subscriptionId: ctx.subscriptionId, azIdentity });
23941
+ }
23942
+ this.context.stdout.write(`${colors.success("\u2713")} seeded your advisors' brains (${ctx.brainRepos.join(", ")}) from your questionnaire.
23826
23943
  `);
23827
23944
  return 0;
23828
23945
  }