@m8t-stack/cli 0.2.12 → 0.2.14

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
@@ -781,7 +781,10 @@ var init_brain_yaml_mirror = __esm({
781
781
  ` enabled: true`,
782
782
  ` lifecycle: { inbox: 14d, stale: 30d, archive: 90d }`,
783
783
  ` artifactRetention: 30d`,
784
- ` digestRetention: 90d`
784
+ ` digestRetention: 90d`,
785
+ ` costReport:`,
786
+ ` enabled: true`,
787
+ ` cadenceDays: 14`
785
788
  ].join("\n");
786
789
  MIRROR_BANNER = [
787
790
  `# \u2500\u2500 MIRROR \u2014 link record convenience copy. NOT the source of truth \u2014`,
@@ -1195,7 +1198,7 @@ var init_enable_hosted_brain = __esm({
1195
1198
  import { Builtins, Cli } from "clipanion";
1196
1199
 
1197
1200
  // src/lib/package-version.ts
1198
- var CLI_VERSION = "0.2.12";
1201
+ var CLI_VERSION = "0.2.14";
1199
1202
 
1200
1203
  // src/lib/render-error.ts
1201
1204
  init_errors();
@@ -15064,10 +15067,9 @@ async function deployPromptAdvisor(args) {
15064
15067
  const fillableFields = fm["fillable-fields"] ?? [];
15065
15068
  const valuesMap = {};
15066
15069
  for (const field of fillableFields) {
15067
- if (field.default !== void 0) {
15068
- valuesMap[field.name] = field.default;
15069
- }
15070
+ if (field.default !== void 0) valuesMap[field.name] = field.default;
15070
15071
  }
15072
+ for (const [k, v] of Object.entries(args.fieldOverrides ?? {})) valuesMap[k] = v;
15071
15073
  const instructions = renderPersonaBody(personaPath, valuesMap);
15072
15074
  return createPromptVersion({
15073
15075
  credential: args.credential,
@@ -17426,9 +17428,134 @@ var PlatformUpdateCommand = class extends M8tCommand {
17426
17428
  }
17427
17429
  };
17428
17430
 
17429
- // src/commands/deploy.ts
17431
+ // src/commands/platform/enable-cost-report.ts
17430
17432
  import { Command as Command33, Option as Option31 } from "clipanion";
17431
17433
 
17434
+ // src/lib/wire-gateway-acs.ts
17435
+ init_rbac();
17436
+ async function wireGatewayForAcs(args) {
17437
+ try {
17438
+ await args.az([
17439
+ "role",
17440
+ "assignment",
17441
+ "create",
17442
+ "--assignee-object-id",
17443
+ args.gatewayPrincipalId,
17444
+ "--assignee-principal-type",
17445
+ "ServicePrincipal",
17446
+ "--role",
17447
+ CONTRIBUTOR_ROLE_ID,
17448
+ "--scope",
17449
+ args.acsResourceId
17450
+ ]);
17451
+ } catch (e) {
17452
+ if (!(e instanceof Error && /RoleAssignmentExists/i.test(e.message))) throw e;
17453
+ }
17454
+ await args.az([
17455
+ "containerapp",
17456
+ "update",
17457
+ "-n",
17458
+ args.gatewayApp,
17459
+ "-g",
17460
+ args.resourceGroup,
17461
+ "--set-env-vars",
17462
+ `M8T_ACS_ENDPOINT=${args.endpoint}`,
17463
+ `M8T_ACS_SENDER=${args.sender}`,
17464
+ "M8T_ENABLE_COST_REPORTER=1"
17465
+ ]);
17466
+ }
17467
+
17468
+ // src/commands/platform/enable-cost-report.ts
17469
+ init_errors();
17470
+ var PlatformEnableCostReportCommand = class extends M8tCommand {
17471
+ static paths = [["platform", "enable-cost-report"]];
17472
+ static usage = Command33.Usage({
17473
+ description: "Wire the deployed gateway to send the bi-weekly cost report via ACS Email.",
17474
+ details: "Discovers the live gateway Container App, grants its managed identity Contributor at the ACS resource scope (authorising the ACS Email send), and sets M8T_ACS_ENDPOINT / M8T_ACS_SENDER / M8T_ENABLE_COST_REPORTER=1 on the gateway. Idempotent \u2014 safely re-runnable. ACS is created during the executor deploy; pass its endpoint, sender, and resource id (from that deploy or 'az communication list')."
17475
+ });
17476
+ subscription = Option31.String("--subscription");
17477
+ resourceGroup = Option31.String("--resource-group", {
17478
+ description: "m8t-stack resource group to disambiguate the gateway (multi-deployment subscriptions)."
17479
+ });
17480
+ acsEndpoint = Option31.String("--acs-endpoint", { required: true, description: "ACS data-plane endpoint, e.g. https://acs-<name>.communication.azure.com/" });
17481
+ acsSender = Option31.String("--acs-sender", { required: true, description: "Managed-domain sender address, e.g. DoNotReply@<guid>.azurecomm.net" });
17482
+ acsResourceId = Option31.String("--acs-resource-id", { required: true, description: "ARM resource id of the acs-<name> Communication Service \u2014 the role-grant scope." });
17483
+ output = Option31.String("--output");
17484
+ async executeCommand() {
17485
+ const mode = resolveOutputMode(
17486
+ this.output,
17487
+ this.context.stdout
17488
+ );
17489
+ const log = (msg) => {
17490
+ if (mode !== "json") this.context.stdout.write(msg + "\n");
17491
+ };
17492
+ const ctx = await resolveGatewayContext({
17493
+ interactive: mode !== "json",
17494
+ subscriptionId: this.subscription,
17495
+ resourceGroup: this.resourceGroup
17496
+ });
17497
+ const { resourceGroup, name } = parseContainerAppResourceId(ctx.containerAppResourceId);
17498
+ const gatewayPrincipalId = (await runAz([
17499
+ "containerapp",
17500
+ "show",
17501
+ "-n",
17502
+ name,
17503
+ "-g",
17504
+ resourceGroup,
17505
+ "--query",
17506
+ "identity.principalId",
17507
+ "-o",
17508
+ "tsv"
17509
+ ])).trim();
17510
+ if (gatewayPrincipalId === "") {
17511
+ throw new LocalCliError({
17512
+ code: "GATEWAY_NO_MANAGED_IDENTITY",
17513
+ message: `Gateway '${name}' has no managed identity principalId to grant.`,
17514
+ hint: "Assign a system-assigned identity to the gateway Container App, then re-run."
17515
+ });
17516
+ }
17517
+ await wireGatewayForAcs({
17518
+ az: runAz,
17519
+ resourceGroup,
17520
+ acsResourceId: this.acsResourceId,
17521
+ gatewayApp: name,
17522
+ endpoint: this.acsEndpoint,
17523
+ sender: this.acsSender,
17524
+ gatewayPrincipalId
17525
+ });
17526
+ if (mode === "json") {
17527
+ this.context.stdout.write(
17528
+ renderJson({
17529
+ gateway: name,
17530
+ resourceGroup,
17531
+ roleGranted: "Contributor",
17532
+ scope: this.acsResourceId,
17533
+ envSet: {
17534
+ M8T_ACS_ENDPOINT: this.acsEndpoint,
17535
+ M8T_ACS_SENDER: this.acsSender,
17536
+ M8T_ENABLE_COST_REPORTER: "1"
17537
+ }
17538
+ }) + "\n"
17539
+ );
17540
+ return 0;
17541
+ }
17542
+ this.context.stdout.write(
17543
+ renderKeyValueBlock([
17544
+ { key: "gateway", value: name },
17545
+ { key: "role granted", value: `Contributor @ ${this.acsResourceId}` },
17546
+ { key: "M8T_ACS_ENDPOINT", value: this.acsEndpoint },
17547
+ { key: "M8T_ACS_SENDER", value: this.acsSender },
17548
+ { key: "M8T_ENABLE_COST_REPORTER", value: "1" }
17549
+ ]) + "\n"
17550
+ );
17551
+ log(colors.success("cost report enabled \u2014 the gateway will send it via ACS \u2713"));
17552
+ return 0;
17553
+ }
17554
+ };
17555
+
17556
+ // src/commands/deploy.ts
17557
+ import { Command as Command34, Option as Option32 } from "clipanion";
17558
+
17432
17559
  // src/lib/app-reg.ts
17433
17560
  init_errors();
17434
17561
  var APP_NAME = "m8t-stack-webapp";
@@ -17961,24 +18088,24 @@ function classifyWhatIf(changes) {
17961
18088
  var DEFAULT_IMAGE_REF = "ghcr.io/m8t-run/m8t:latest";
17962
18089
  var DeployCommand = class extends M8tCommand {
17963
18090
  static paths = [["deploy"]];
17964
- static usage = Command33.Usage({
18091
+ static usage = Command34.Usage({
17965
18092
  description: "Deploy (or update) the m8t gateway/webapp stack via Bicep.",
17966
18093
  details: "Ensures the Entra app reg (or pass --client-id to reuse an existing one \u2014 required if you can't create app regs), writes ~/.m8t-stack/config.yaml, ensures the resource group, and runs deploy/main.bicep. The repo is located via ~/.m8t-stack/repo-root."
17967
18094
  });
17968
- subscription = Option31.String("--subscription");
17969
- resourceGroup = Option31.String("--resource-group", "rg-m8t-stack");
17970
- location = Option31.String("--location", "eastus");
17971
- suffix = Option31.String("--suffix", "");
17972
- imageRef = Option31.String("--image-ref", DEFAULT_IMAGE_REF);
17973
- acrPullIdentity = Option31.String("--acrpull-identity");
17974
- acrResourceId = Option31.String("--acr-resource-id");
17975
- foundryEndpoint = Option31.String("--foundry-endpoint");
17976
- foundryResourceId = Option31.String("--foundry-resource-id");
17977
- foundryTracing = Option31.String("--foundry-tracing");
18095
+ subscription = Option32.String("--subscription");
18096
+ resourceGroup = Option32.String("--resource-group", "rg-m8t-stack");
18097
+ location = Option32.String("--location", "eastus");
18098
+ suffix = Option32.String("--suffix", "");
18099
+ imageRef = Option32.String("--image-ref", DEFAULT_IMAGE_REF);
18100
+ acrPullIdentity = Option32.String("--acrpull-identity");
18101
+ acrResourceId = Option32.String("--acr-resource-id");
18102
+ foundryEndpoint = Option32.String("--foundry-endpoint");
18103
+ foundryResourceId = Option32.String("--foundry-resource-id");
18104
+ foundryTracing = Option32.String("--foundry-tracing");
17978
18105
  // project | account | skip (bicep default: project)
17979
- clientId = Option31.String("--client-id");
17980
- whatIf = Option31.Boolean("--what-if", false);
17981
- output = Option31.String("--output");
18106
+ clientId = Option32.String("--client-id");
18107
+ whatIf = Option32.Boolean("--what-if", false);
18108
+ output = Option32.String("--output");
17982
18109
  async executeCommand() {
17983
18110
  const mode = resolveOutputMode(
17984
18111
  this.output,
@@ -18092,7 +18219,7 @@ var DeployCommand = class extends M8tCommand {
18092
18219
 
18093
18220
  // src/commands/eval/skill.ts
18094
18221
  import { spawnSync as spawnSync3 } from "child_process";
18095
- import { Command as Command34, Option as Option32 } from "clipanion";
18222
+ import { Command as Command35, Option as Option33 } from "clipanion";
18096
18223
  init_errors();
18097
18224
  var DECISIONS = /* @__PURE__ */ new Set(["promote", "reject", "needs_review"]);
18098
18225
  var JUDGE_STATUSES = /* @__PURE__ */ new Set(["ok", "skipped", "unavailable"]);
@@ -18117,14 +18244,14 @@ function parseVerdict(stdout) {
18117
18244
  }
18118
18245
  var EvalSkillCommand = class extends M8tCommand {
18119
18246
  static paths = [["eval", "skill"]];
18120
- static usage = Command34.Usage({
18247
+ static usage = Command35.Usage({
18121
18248
  description: "Vet one inbox skill candidate (Brain Faculties Eval F1): promote / reject / needs_review. Shells out to the Python `brain-eval` core (override its path with $BRAIN_EVAL_BIN)."
18122
18249
  });
18123
- candidate = Option32.String();
18124
- skillsDir = Option32.String("--skills-dir");
18125
- noJudge = Option32.Boolean("--no-judge", false);
18126
- deployment = Option32.String("--deployment");
18127
- output = Option32.String("--output");
18250
+ candidate = Option33.String();
18251
+ skillsDir = Option33.String("--skills-dir");
18252
+ noJudge = Option33.Boolean("--no-judge", false);
18253
+ deployment = Option33.String("--deployment");
18254
+ output = Option33.String("--output");
18128
18255
  executeCommand() {
18129
18256
  return Promise.resolve(this._runCommand());
18130
18257
  }
@@ -18183,7 +18310,7 @@ var EvalSkillCommand = class extends M8tCommand {
18183
18310
  import { spawnSync as spawnSync4 } from "child_process";
18184
18311
  import { writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync13, existsSync as existsSync12, readdirSync } from "fs";
18185
18312
  import { join as join19 } from "path";
18186
- import { Command as Command35, Option as Option33 } from "clipanion";
18313
+ import { Command as Command36, Option as Option34 } from "clipanion";
18187
18314
  init_errors();
18188
18315
  init_esm();
18189
18316
  function parseArmToken(tok, opts) {
@@ -18397,23 +18524,23 @@ function buildPlan(args) {
18397
18524
  }
18398
18525
  var EvalExamCommand = class extends M8tCommand {
18399
18526
  static paths = [["eval", "exam"]];
18400
- static usage = Command35.Usage({
18527
+ static usage = Command36.Usage({
18401
18528
  description: "Run a brain exam (Brain Referee E2-F2): impact A/B + dream-delta. Resolves the plan, then shells out to the Python `brain-exam` orchestrator (override its path with $BRAIN_EXAM_BIN). Renders an ExamVerdict: three-valued verdict + power note + per-task flips."
18402
18529
  });
18403
- worker = Option33.String();
18404
- arms = Option33.String("--arms");
18405
- taskSet = Option33.String("--task-set");
18406
- examType = Option33.String("--exam-type");
18407
- reps = Option33.String("-n,--reps");
18408
- probes = Option33.String("--probes");
18409
- pool = Option33.String("--pool");
18410
- out = Option33.String("--out");
18411
- dryRun = Option33.Boolean("--dry-run", false);
18412
- keepArms = Option33.Boolean("--keep-arms", false);
18413
- allowStub = Option33.Boolean("--allow-stub", false);
18414
- deployment = Option33.String("--deployment");
18415
- output = Option33.String("--output");
18416
- observeWaitS = Option33.String("--observe-wait-s");
18530
+ worker = Option34.String();
18531
+ arms = Option34.String("--arms");
18532
+ taskSet = Option34.String("--task-set");
18533
+ examType = Option34.String("--exam-type");
18534
+ reps = Option34.String("-n,--reps");
18535
+ probes = Option34.String("--probes");
18536
+ pool = Option34.String("--pool");
18537
+ out = Option34.String("--out");
18538
+ dryRun = Option34.Boolean("--dry-run", false);
18539
+ keepArms = Option34.Boolean("--keep-arms", false);
18540
+ allowStub = Option34.Boolean("--allow-stub", false);
18541
+ deployment = Option34.String("--deployment");
18542
+ output = Option34.String("--output");
18543
+ observeWaitS = Option34.String("--observe-wait-s");
18417
18544
  async executeCommand() {
18418
18545
  await Promise.resolve();
18419
18546
  const worker = typeof this.worker === "string" ? this.worker : void 0;
@@ -18527,10 +18654,10 @@ var EvalExamCommand = class extends M8tCommand {
18527
18654
  };
18528
18655
 
18529
18656
  // src/commands/version.ts
18530
- import { Command as Command36, Option as Option34 } from "clipanion";
18657
+ import { Command as Command37, Option as Option35 } from "clipanion";
18531
18658
  var VersionCommand = class extends M8tCommand {
18532
18659
  static paths = [["version"], ["--version"], ["-v"]];
18533
- static usage = Command36.Usage({
18660
+ static usage = Command37.Usage({
18534
18661
  description: "Print the CLI version.",
18535
18662
  details: "Prints the m8t CLI version. With --verbose, also prints Node version and platform.",
18536
18663
  examples: [
@@ -18538,8 +18665,8 @@ var VersionCommand = class extends M8tCommand {
18538
18665
  ["Print as JSON", "$0 version --output json"]
18539
18666
  ]
18540
18667
  });
18541
- output = Option34.String("--output", { description: "pretty | json | auto (default)" });
18542
- verbose = Option34.Boolean("--verbose", false);
18668
+ output = Option35.String("--output", { description: "pretty | json | auto (default)" });
18669
+ verbose = Option35.Boolean("--verbose", false);
18543
18670
  executeCommand() {
18544
18671
  const mode = resolveOutputMode(
18545
18672
  this.output ?? "auto",
@@ -18570,18 +18697,18 @@ var VersionCommand = class extends M8tCommand {
18570
18697
  };
18571
18698
 
18572
18699
  // src/commands/whoami.ts
18573
- import { Command as Command37, Option as Option35 } from "clipanion";
18700
+ import { Command as Command38, Option as Option36 } from "clipanion";
18574
18701
  var WhoamiCommand = class extends M8tCommand {
18575
18702
  static paths = [["whoami"]];
18576
- static usage = Command37.Usage({
18703
+ static usage = Command38.Usage({
18577
18704
  description: "Show your identity + the gateway you'll talk to. Probes the backend."
18578
18705
  });
18579
- output = Option35.String("--output");
18580
- verbose = Option35.Boolean("--verbose", false);
18581
- subscription = Option35.String("--subscription", {
18706
+ output = Option36.String("--output");
18707
+ verbose = Option36.Boolean("--verbose", false);
18708
+ subscription = Option36.String("--subscription", {
18582
18709
  description: "Azure subscription ID to discover gateway in (defaults to active az subscription)."
18583
18710
  });
18584
- resourceGroup = Option35.String("--resource-group", {
18711
+ resourceGroup = Option36.String("--resource-group", {
18585
18712
  description: "m8t-stack resource group to disambiguate the gateway (multi-deployment subscriptions)."
18586
18713
  });
18587
18714
  async executeCommand() {
@@ -18646,7 +18773,7 @@ var WhoamiCommand = class extends M8tCommand {
18646
18773
  };
18647
18774
 
18648
18775
  // src/commands/status.ts
18649
- import { Command as Command38, Option as Option36 } from "clipanion";
18776
+ import { Command as Command39, Option as Option37 } from "clipanion";
18650
18777
 
18651
18778
  // src/lib/azd.ts
18652
18779
  init_errors();
@@ -18711,10 +18838,10 @@ async function resolveLocalContext() {
18711
18838
  // src/commands/status.ts
18712
18839
  var StatusCommand = class extends M8tCommand {
18713
18840
  static paths = [["status"]];
18714
- static usage = Command38.Usage({
18841
+ static usage = Command39.Usage({
18715
18842
  description: "Show the local m8t context: identity, config.yaml, gateway cache, azd mode."
18716
18843
  });
18717
- output = Option36.String("--output");
18844
+ output = Option37.String("--output");
18718
18845
  async executeCommand() {
18719
18846
  const mode = resolveOutputMode(
18720
18847
  this.output,
@@ -18752,7 +18879,7 @@ var StatusCommand = class extends M8tCommand {
18752
18879
  };
18753
18880
 
18754
18881
  // src/commands/doctor.ts
18755
- import { Command as Command39, Option as Option37 } from "clipanion";
18882
+ import { Command as Command40, Option as Option38 } from "clipanion";
18756
18883
  import { DefaultAzureCredential as DefaultAzureCredential16 } from "@azure/identity";
18757
18884
  import * as fs19 from "fs";
18758
18885
  import * as os9 from "os";
@@ -19003,12 +19130,12 @@ function probeRepoRoot() {
19003
19130
  }
19004
19131
  var DoctorCommand = class extends M8tCommand {
19005
19132
  static paths = [["doctor"]];
19006
- static usage = Command39.Usage({
19133
+ static usage = Command40.Usage({
19007
19134
  description: "Diagnose the local m8t setup: az login, config.yaml, gateway, Foundry data-plane."
19008
19135
  });
19009
- output = Option37.String("--output");
19010
- agent = Option37.String("--agent");
19011
- resourceGroup = Option37.String("--resource-group", {
19136
+ output = Option38.String("--output");
19137
+ agent = Option38.String("--agent");
19138
+ resourceGroup = Option38.String("--resource-group", {
19012
19139
  description: "m8t-stack resource group to disambiguate the gateway (multi-deployment subscriptions)."
19013
19140
  });
19014
19141
  async executeCommand() {
@@ -19124,7 +19251,7 @@ var DoctorCommand = class extends M8tCommand {
19124
19251
  };
19125
19252
 
19126
19253
  // src/commands/switch.ts
19127
- import { Command as Command40, Option as Option38 } from "clipanion";
19254
+ import { Command as Command41, Option as Option39 } from "clipanion";
19128
19255
 
19129
19256
  // src/lib/profiles.ts
19130
19257
  import * as fs20 from "fs/promises";
@@ -19264,14 +19391,14 @@ async function profileSwitch(name, asName) {
19264
19391
  init_errors();
19265
19392
  var SwitchCommand = class extends M8tCommand {
19266
19393
  static paths = [["switch"]];
19267
- static usage = Command40.Usage({
19394
+ static usage = Command41.Usage({
19268
19395
  description: "Re-point local config at another deployment: --subscription <id|name> (discovery) or <profile>."
19269
19396
  });
19270
- profile = Option38.String({ required: false });
19271
- subscription = Option38.String("--subscription");
19272
- list = Option38.Boolean("--list", false);
19273
- as = Option38.String("--as");
19274
- output = Option38.String("--output");
19397
+ profile = Option39.String({ required: false });
19398
+ subscription = Option39.String("--subscription");
19399
+ list = Option39.Boolean("--list", false);
19400
+ as = Option39.String("--as");
19401
+ output = Option39.String("--output");
19275
19402
  async executeCommand() {
19276
19403
  const mode = resolveOutputMode(
19277
19404
  this.output,
@@ -19328,7 +19455,7 @@ var SwitchCommand = class extends M8tCommand {
19328
19455
 
19329
19456
  // src/commands/open.ts
19330
19457
  import { spawn as spawn5 } from "child_process";
19331
- import { Command as Command41, Option as Option39 } from "clipanion";
19458
+ import { Command as Command42, Option as Option40 } from "clipanion";
19332
19459
 
19333
19460
  // src/lib/open-targets.ts
19334
19461
  init_errors();
@@ -19374,14 +19501,14 @@ function openUrl(url) {
19374
19501
  }
19375
19502
  var OpenCommand = class extends M8tCommand {
19376
19503
  static paths = [["open"]];
19377
- static usage = Command41.Usage({
19504
+ static usage = Command42.Usage({
19378
19505
  description: "Open the deployed webapp (default), the Foundry portal, or the resource group.",
19379
19506
  details: "Targets: webapp (deployed app, default) | foundry (ai.azure.com) | portal (resource group in the Azure portal). Pass --print to emit the URL instead of launching a browser (also the default when stdout isn't a TTY)."
19380
19507
  });
19381
- target = Option39.String({ required: false });
19382
- print = Option39.Boolean("--print", false);
19383
- output = Option39.String("--output");
19384
- resourceGroup = Option39.String("--resource-group", {
19508
+ target = Option40.String({ required: false });
19509
+ print = Option40.Boolean("--print", false);
19510
+ output = Option40.String("--output");
19511
+ resourceGroup = Option40.String("--resource-group", {
19385
19512
  description: "m8t-stack resource group to disambiguate the gateway (multi-deployment subscriptions)."
19386
19513
  });
19387
19514
  async executeCommand() {
@@ -19425,7 +19552,7 @@ var OpenCommand = class extends M8tCommand {
19425
19552
  };
19426
19553
 
19427
19554
  // src/commands/dream/run.ts
19428
- import { Command as Command42, Option as Option40 } from "clipanion";
19555
+ import { Command as Command43, Option as Option41 } from "clipanion";
19429
19556
  import { AzureCliCredential } from "@azure/identity";
19430
19557
  import { TableClient as TableClient2 } from "@azure/data-tables";
19431
19558
  import { AIProjectClient as AIProjectClient3 } from "@azure/ai-projects";
@@ -21442,14 +21569,17 @@ function parseDreamerConfig(rawYaml, env) {
21442
21569
  };
21443
21570
  }
21444
21571
 
21572
+ // ../../packages/brain/engine/dist/esm/cost-report/config.js
21573
+ import { parse as parseYaml11 } from "yaml";
21574
+
21445
21575
  // ../../packages/brain/engine/dist/esm/librarian/codify/persistence-guard.js
21446
21576
  var ZERO_WIDTH_RE = new RegExp("\u200B|\u200C|\u200D|\uFEFF", "g");
21447
21577
 
21448
21578
  // ../../packages/brain/engine/dist/esm/librarian/janitor/frontmatter.js
21449
- import { parse as parseYaml11, stringify as stringifyYaml5 } from "yaml";
21579
+ import { parse as parseYaml12, stringify as stringifyYaml5 } from "yaml";
21450
21580
 
21451
21581
  // ../../packages/brain/engine/dist/esm/librarian/config.js
21452
- import { parse as parseYaml12 } from "yaml";
21582
+ import { parse as parseYaml13 } from "yaml";
21453
21583
 
21454
21584
  // src/lib/dream-discovery.ts
21455
21585
  init_http();
@@ -21566,20 +21696,20 @@ function redactTranscripts(input) {
21566
21696
  }
21567
21697
  var DreamRunCommand = class extends M8tCommand {
21568
21698
  static paths = [["dream", "run"]];
21569
- static usage = Command42.Usage({
21699
+ static usage = Command43.Usage({
21570
21700
  description: "Dry-run the brain consumption pipeline for one worker (no model call, no writes).",
21571
21701
  details: "Builds AzureCliCredential + resolves the Foundry project, ledger table, and Log Analytics workspace, runs the consumption pipeline, and prints the skip-ledger, the partition invariant, and harvest stats. Transcripts are metadata-only unless --show-transcripts is passed."
21572
21702
  });
21573
- worker = Option40.String("--worker", { description: "Worker (canonical name) to harvest. Required." });
21574
- dryRun = Option40.Boolean("--dry-run", false, { description: "Read-only harvest; no model call, no writes." });
21575
- since = Option40.String("--since", { description: "ISO-8601 start override (rejected if malformed or future)." });
21576
- reset = Option40.Boolean("--reset", false, { description: "Ignore the stored cursor; read from the beginning." });
21577
- showTranscripts = Option40.Boolean("--show-transcripts", false, {
21703
+ worker = Option41.String("--worker", { description: "Worker (canonical name) to harvest. Required." });
21704
+ dryRun = Option41.Boolean("--dry-run", false, { description: "Read-only harvest; no model call, no writes." });
21705
+ since = Option41.String("--since", { description: "ISO-8601 start override (rejected if malformed or future)." });
21706
+ reset = Option41.Boolean("--reset", false, { description: "Ignore the stored cursor; read from the beginning." });
21707
+ showTranscripts = Option41.Boolean("--show-transcripts", false, {
21578
21708
  description: "Print transcript bodies (default: metadata only)."
21579
21709
  });
21580
- subscription = Option40.String("--subscription");
21581
- endpoint = Option40.String("--endpoint");
21582
- output = Option40.String("--output");
21710
+ subscription = Option41.String("--subscription");
21711
+ endpoint = Option41.String("--endpoint");
21712
+ output = Option41.String("--output");
21583
21713
  // Resolution seam — overridden by tests; built lazily at runtime otherwise.
21584
21714
  deps;
21585
21715
  async executeCommand() {
@@ -21927,7 +22057,7 @@ function defaultDeps(overrides) {
21927
22057
  }
21928
22058
 
21929
22059
  // src/commands/foundry/create.ts
21930
- import { Command as Command43, Option as Option41 } from "clipanion";
22060
+ import { Command as Command44, Option as Option42 } from "clipanion";
21931
22061
 
21932
22062
  // src/lib/foundry-create.ts
21933
22063
  init_errors();
@@ -22165,7 +22295,7 @@ async function createFoundryProject(args) {
22165
22295
  init_errors();
22166
22296
  var FoundryCreateCommand = class extends M8tCommand {
22167
22297
  static paths = [["foundry", "create"]];
22168
- static usage = Command43.Usage({
22298
+ static usage = Command44.Usage({
22169
22299
  description: "Create an AI Foundry (AIServices) account + project + model deployment from scratch.",
22170
22300
  details: "Non-interactive and idempotent. Creates the AIServices account (custom subdomain + project management), a project, and a model deployment (default gpt-4.1-mini @ capacity 50). Region must be hosted-agent-eligible. Emits the project endpoint as structured output. Re-run is a clean no-op (account/project skipped if present; deployment capacity converges UP, never down).",
22171
22301
  examples: [
@@ -22174,16 +22304,16 @@ var FoundryCreateCommand = class extends M8tCommand {
22174
22304
  ["Higher capacity for a reasoning model", "$0 foundry create --resource-group rg-m8t-stack --location eastus2 --model gpt-5-mini --capacity 250"]
22175
22305
  ]
22176
22306
  });
22177
- resourceGroup = Option41.String("--resource-group");
22178
- location = Option41.String("--location");
22179
- account = Option41.String("--account");
22180
- project = Option41.String("--project", "m8t");
22181
- model = Option41.String("--model", "gpt-4.1-mini");
22182
- modelVersion = Option41.String("--model-version", "2025-04-14");
22183
- capacity = Option41.String("--capacity", "50");
22184
- subscription = Option41.String("--subscription");
22185
- skipQuotaCheck = Option41.Boolean("--skip-quota-check", false);
22186
- output = Option41.String("--output");
22307
+ resourceGroup = Option42.String("--resource-group");
22308
+ location = Option42.String("--location");
22309
+ account = Option42.String("--account");
22310
+ project = Option42.String("--project", "m8t");
22311
+ model = Option42.String("--model", "gpt-4.1-mini");
22312
+ modelVersion = Option42.String("--model-version", "2025-04-14");
22313
+ capacity = Option42.String("--capacity", "50");
22314
+ subscription = Option42.String("--subscription");
22315
+ skipQuotaCheck = Option42.Boolean("--skip-quota-check", false);
22316
+ output = Option42.String("--output");
22187
22317
  async executeCommand() {
22188
22318
  const mode = resolveOutputMode(
22189
22319
  this.output,
@@ -22255,22 +22385,22 @@ var FoundryCreateCommand = class extends M8tCommand {
22255
22385
  };
22256
22386
 
22257
22387
  // src/commands/foundry/await-ready.ts
22258
- import { Command as Command44, Option as Option42 } from "clipanion";
22388
+ import { Command as Command45, Option as Option43 } from "clipanion";
22259
22389
  import { AzureCliCredential as AzureCliCredential2 } from "@azure/identity";
22260
22390
  init_errors();
22261
22391
  var FoundryAwaitReadyCommand = class extends M8tCommand {
22262
22392
  static paths = [["foundry", "await-ready"]];
22263
- static usage = Command44.Usage({
22393
+ static usage = Command45.Usage({
22264
22394
  description: "Wait until a freshly-created Foundry project's data plane reliably serves it.",
22265
22395
  details: "Probes the project (GET /agents) until it returns 200 on a few consecutive tries, or fails clearly after a bounded budget. A newly-created account can serve intermittent 404 'Project not found' for minutes; run this after 'foundry create' and before deploying agents so the worker phase doesn't catch the unstable window.",
22266
22396
  examples: [["Wait for a project to be ready", "$0 foundry await-ready --endpoint https://acc.services.ai.azure.com/api/projects/m8t"]]
22267
22397
  });
22268
- endpoint = Option42.String("--endpoint");
22269
- consecutive = Option42.String("--consecutive", "3");
22270
- attempts = Option42.String("--attempts", "60");
22271
- interval = Option42.String("--interval", "5");
22272
- subscription = Option42.String("--subscription");
22273
- output = Option42.String("--output");
22398
+ endpoint = Option43.String("--endpoint");
22399
+ consecutive = Option43.String("--consecutive", "3");
22400
+ attempts = Option43.String("--attempts", "60");
22401
+ interval = Option43.String("--interval", "5");
22402
+ subscription = Option43.String("--subscription");
22403
+ output = Option43.String("--output");
22274
22404
  async executeCommand() {
22275
22405
  const mode = resolveOutputMode(this.output, this.context.stdout);
22276
22406
  const endpoint = typeof this.endpoint === "string" ? this.endpoint : void 0;
@@ -22304,7 +22434,7 @@ var FoundryAwaitReadyCommand = class extends M8tCommand {
22304
22434
  };
22305
22435
 
22306
22436
  // src/commands/bootstrap/preflight.ts
22307
- import { Command as Command45, Option as Option43 } from "clipanion";
22437
+ import { Command as Command46, Option as Option44 } from "clipanion";
22308
22438
  init_errors();
22309
22439
 
22310
22440
  // src/lib/bootstrap-preflight.ts
@@ -22378,7 +22508,7 @@ function buildPreflightBanner(who) {
22378
22508
  // src/commands/bootstrap/preflight.ts
22379
22509
  var BootstrapPreflightCommand = class extends M8tCommand {
22380
22510
  static paths = [["bootstrap", "preflight"]];
22381
- static usage = Command45.Usage({
22511
+ static usage = Command46.Usage({
22382
22512
  description: "Loudly verify you can install m8t-stack (Owner/UAA + directory admin) and hard-stop if not.",
22383
22513
  details: "Step 1 of `m8t bootstrap`. Prints an unmissable admin-credentials notice, then checks: Owner or User Access Administrator at subscription scope (hard requirement), directory-admin capability to register the app (or pass --client-id), and registers Microsoft.ContainerInstance. Exits non-zero with the exact failing check + remedy.",
22384
22514
  examples: [
@@ -22386,8 +22516,8 @@ var BootstrapPreflightCommand = class extends M8tCommand {
22386
22516
  ["BYO app registration (directory guests)", "$0 bootstrap preflight --client-id <appId>"]
22387
22517
  ]
22388
22518
  });
22389
- clientId = Option43.String("--client-id");
22390
- subscription = Option43.String("--subscription");
22519
+ clientId = Option44.String("--client-id");
22520
+ subscription = Option44.String("--subscription");
22391
22521
  async executeCommand() {
22392
22522
  const clientId = typeof this.clientId === "string" ? this.clientId : void 0;
22393
22523
  const account = await getAzAccount();
@@ -22446,7 +22576,7 @@ ${colors.error(" " + why)}
22446
22576
  import * as fs23 from "fs";
22447
22577
  import * as os12 from "os";
22448
22578
  import * as path24 from "path";
22449
- import { Command as Command46, Option as Option44 } from "clipanion";
22579
+ import { Command as Command47, Option as Option45 } from "clipanion";
22450
22580
  init_errors();
22451
22581
 
22452
22582
  // src/lib/bootstrap-mi.ts
@@ -22699,7 +22829,7 @@ var ACI_NAME = "m8t-installer";
22699
22829
  var MI_NAME = "m8t-installer-mi";
22700
22830
  var BootstrapLaunchCommand = class extends M8tCommand {
22701
22831
  static paths = [["bootstrap", "launch"]];
22702
- static usage = Command46.Usage({
22832
+ static usage = Command47.Usage({
22703
22833
  description: "Create + authorize the installer managed identity, then kick the cloud installer.",
22704
22834
  details: "Step 2 of `m8t bootstrap` (run after `preflight`). Ensures the resource group, creates the m8t-stack app registration (or uses --client-id), creates a user-assigned managed identity granted Owner at subscription scope, and launches the published m8t-installer image as an ACI run-to-completion job under that identity. Writes ~/.m8t-stack/bootstrap.json for `status`/`reap`/`finish`.",
22705
22835
  examples: [
@@ -22708,13 +22838,13 @@ var BootstrapLaunchCommand = class extends M8tCommand {
22708
22838
  ["Pin a specific installer tag", "$0 bootstrap launch --location eastus2 --installer-tag v0.1.0"]
22709
22839
  ]
22710
22840
  });
22711
- location = Option44.String("--location");
22712
- resourceGroup = Option44.String("--resource-group");
22713
- clientId = Option44.String("--client-id");
22714
- subscription = Option44.String("--subscription");
22715
- installerTag = Option44.String("--installer-tag");
22716
- gatewayImageRef = Option44.String("--gateway-image-ref");
22717
- githubAppCreds = Option44.String("--github-app-creds");
22841
+ location = Option45.String("--location");
22842
+ resourceGroup = Option45.String("--resource-group");
22843
+ clientId = Option45.String("--client-id");
22844
+ subscription = Option45.String("--subscription");
22845
+ installerTag = Option45.String("--installer-tag");
22846
+ gatewayImageRef = Option45.String("--gateway-image-ref");
22847
+ githubAppCreds = Option45.String("--github-app-creds");
22718
22848
  async executeCommand() {
22719
22849
  const location = typeof this.location === "string" ? this.location : void 0;
22720
22850
  if (!location) {
@@ -22815,7 +22945,7 @@ var BootstrapLaunchCommand = class extends M8tCommand {
22815
22945
  };
22816
22946
 
22817
22947
  // src/commands/bootstrap/status.ts
22818
- import { Command as Command47, Option as Option45 } from "clipanion";
22948
+ import { Command as Command48, Option as Option46 } from "clipanion";
22819
22949
  init_errors();
22820
22950
 
22821
22951
  // src/lib/bootstrap-aci-state.ts
@@ -22843,13 +22973,13 @@ async function getAciState(opts) {
22843
22973
  // src/commands/bootstrap/status.ts
22844
22974
  var BootstrapStatusCommand = class extends M8tCommand {
22845
22975
  static paths = [["bootstrap", "status"]];
22846
- static usage = Command47.Usage({
22976
+ static usage = Command48.Usage({
22847
22977
  description: "Show the cloud installer's live status (phase, progress, result).",
22848
22978
  details: "Reads the durable status blob written by the installer. --watch polls until the install reaches done or failed.",
22849
22979
  examples: [["One read", "$0 bootstrap status"], ["Watch to completion", "$0 bootstrap status --watch"]]
22850
22980
  });
22851
- watch = Option45.Boolean("--watch", false);
22852
- output = Option45.String("--output");
22981
+ watch = Option46.Boolean("--watch", false);
22982
+ output = Option46.String("--output");
22853
22983
  async executeCommand() {
22854
22984
  const state = await readBootstrapState();
22855
22985
  if (!state) {
@@ -22920,7 +23050,7 @@ function formatStatus(d) {
22920
23050
  }
22921
23051
 
22922
23052
  // src/commands/bootstrap/reap.ts
22923
- import { Command as Command48, Option as Option46 } from "clipanion";
23053
+ import { Command as Command49, Option as Option47 } from "clipanion";
22924
23054
  init_errors();
22925
23055
 
22926
23056
  // src/lib/bootstrap-reap.ts
@@ -22996,14 +23126,14 @@ async function reapInstaller(opts) {
22996
23126
  // src/commands/bootstrap/reap.ts
22997
23127
  var BootstrapReapCommand = class extends M8tCommand {
22998
23128
  static paths = [["bootstrap", "reap"]];
22999
- static usage = Command48.Usage({
23129
+ static usage = Command49.Usage({
23000
23130
  description: "Tear down the installer scaffolding (ACI \u2192 MI \u2192 its role assignments) after a successful install.",
23001
23131
  details: "Runs locally on the 'done' signal (the installer can't delete its own identity). The platform RG and the gateway's own assignments persist. A failed install is left intact for diagnosis unless --force.",
23002
23132
  examples: [["Reap after done", "$0 bootstrap reap"]]
23003
23133
  });
23004
- force = Option46.Boolean("--force", false);
23005
- sweepOrphans = Option46.Boolean("--sweep-orphans", false);
23006
- yes = Option46.Boolean("--yes", false);
23134
+ force = Option47.Boolean("--force", false);
23135
+ sweepOrphans = Option47.Boolean("--sweep-orphans", false);
23136
+ yes = Option47.Boolean("--yes", false);
23007
23137
  async executeCommand() {
23008
23138
  if (this.sweepOrphans === true) {
23009
23139
  const { subscriptionId: sub } = await getAzAccount();
@@ -23071,7 +23201,7 @@ Found ${String(found.length)} orphaned Owner@sub assignment(s) (dry-run). ${colo
23071
23201
  import * as fs24 from "fs/promises";
23072
23202
  import * as os14 from "os";
23073
23203
  import * as path26 from "path";
23074
- import { Command as Command49, Option as Option47 } from "clipanion";
23204
+ import { Command as Command50, Option as Option48 } from "clipanion";
23075
23205
  init_errors();
23076
23206
 
23077
23207
  // src/lib/company-profile-seed.ts
@@ -23160,9 +23290,9 @@ function companyProfileBodyEquals(a, b) {
23160
23290
  const strip = (s) => s.replace(/^(created|updated):.*$/gm, "").trim();
23161
23291
  return strip(a) === strip(b);
23162
23292
  }
23163
- function upsertMemoryIndex(existing, line2) {
23293
+ function upsertMemoryIndex(existing, line2, targetPath2 = COMPANY_PROFILE_PATH) {
23164
23294
  const lines = existing.split("\n");
23165
- const existingIdx = lines.findIndex((l) => l.includes(`\`${COMPANY_PROFILE_PATH}\``));
23295
+ const existingIdx = lines.findIndex((l) => l.includes(`\`${targetPath2}\``));
23166
23296
  if (existingIdx >= 0) {
23167
23297
  lines[existingIdx] = line2;
23168
23298
  return lines.join("\n");
@@ -23174,6 +23304,88 @@ function upsertMemoryIndex(existing, line2) {
23174
23304
  }
23175
23305
  return existing.replace(/\s*$/, "\n\n") + line2 + "\n";
23176
23306
  }
23307
+ var FOUNDER_RECORD_PATH = "memory/founder.md";
23308
+ var NOT_CAPTURED = "_not captured yet \u2014 add it anytime (run `m8t bootstrap seed-profile`, or just tell me)_";
23309
+ function renderFounderRecord(block, inputs, now = (/* @__PURE__ */ new Date()).toISOString()) {
23310
+ const pick = (...vals) => vals.map((v) => v?.trim()).find(Boolean) ?? "";
23311
+ const founderName = pick(block.founder_name, inputs.azIdentity?.name);
23312
+ const founderEmail = pick(block.founder_email, inputs.azIdentity?.email);
23313
+ const advisorName = (block.advisor_name ?? "").trim();
23314
+ const advisorEmail = (block.advisor_email ?? "").trim();
23315
+ const subscription = (inputs.subscriptionId ?? "").trim();
23316
+ const teamSize = (block.team_size ?? "").trim();
23317
+ const advisorRendered = advisorName || advisorEmail ? [advisorName, advisorEmail ? `<${advisorEmail}>` : ""].filter(Boolean).join(" ") : NOT_CAPTURED;
23318
+ const founderMd = [
23319
+ `---`,
23320
+ `type: memory`,
23321
+ `title: "Founder & install context"`,
23322
+ `created: ${now}`,
23323
+ `updated: ${now}`,
23324
+ `tags: [founder, install-context, onboarding]`,
23325
+ `origin: operator`,
23326
+ `---`,
23327
+ ``,
23328
+ `# Founder & install context`,
23329
+ ``,
23330
+ `_Seeded at onboarding from your Azure identity + the questionnaire. Authoritative \u2014 read it; don't rewrite it (origin: operator)._`,
23331
+ ``,
23332
+ `- **Founder:** ${founderName || NOT_CAPTURED}`,
23333
+ `- **Founder email (company_email):** ${founderEmail || NOT_CAPTURED}`,
23334
+ `- **Microsoft Startup Advisor (SA):** ${advisorRendered}`,
23335
+ `- **Azure subscription:** ${subscription || NOT_CAPTURED}`,
23336
+ `- **Team size:** ${teamSize || "\u2014"}`,
23337
+ ``
23338
+ ].join("\n");
23339
+ const idxAdvisor = advisorEmail || advisorName || "\u2014";
23340
+ const idxSub = subscription ? `${subscription.slice(0, 8)}\u2026` : "\u2014";
23341
+ const memoryIndexLine = `- \`${FOUNDER_RECORD_PATH}\` \u2014 **Founder & install context**: ${founderName || "founder"} \xB7 advisor ${idxAdvisor} \xB7 sub ${idxSub}. (seeded from onboarding)`;
23342
+ return { founderMd, memoryIndexLine };
23343
+ }
23344
+ function founderRecordBodyEquals(a, b) {
23345
+ const strip = (s) => s.replace(/^(created|updated):.*$/gm, "").trim();
23346
+ return strip(a) === strip(b);
23347
+ }
23348
+
23349
+ // src/lib/founder-identity.ts
23350
+ function deriveEmailCandidate(raw) {
23351
+ const mail = (raw.mail ?? "").trim();
23352
+ if (mail) return mail;
23353
+ const upn = (raw.upn ?? "").trim();
23354
+ if (!upn) return "";
23355
+ const ext = upn.indexOf("#EXT#");
23356
+ if (ext > 0) {
23357
+ const local = upn.slice(0, ext);
23358
+ const at = local.lastIndexOf("_");
23359
+ return at > 0 ? `${local.slice(0, at)}@${local.slice(at + 1)}` : "";
23360
+ }
23361
+ return upn.includes("@") ? upn : "";
23362
+ }
23363
+ async function getSignedInUserIdentity(runAzImpl = runAz) {
23364
+ try {
23365
+ const raw = await runAzImpl([
23366
+ "ad",
23367
+ "signed-in-user",
23368
+ "show",
23369
+ "--query",
23370
+ "{displayName:displayName, mail:mail, upn:userPrincipalName}",
23371
+ "-o",
23372
+ "json"
23373
+ ]);
23374
+ const p = JSON.parse(raw);
23375
+ return { name: (p.displayName ?? "").trim(), email: deriveEmailCandidate({ mail: p.mail, upn: p.upn }) };
23376
+ } catch {
23377
+ return { name: "", email: "" };
23378
+ }
23379
+ }
23380
+ function composeFounderIdentityNote(id) {
23381
+ if (id.name && id.email) {
23382
+ 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.`;
23383
+ }
23384
+ if (id.name) {
23385
+ 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).`;
23386
+ }
23387
+ return `Ask the founder their name and the best contact email to reach them (that's where advisor handoffs and cost reports go).`;
23388
+ }
23177
23389
 
23178
23390
  // src/lib/company-profile-seed.ts
23179
23391
  function readGithubAppCreds(credsPath) {
@@ -23188,18 +23400,21 @@ async function resolveSeedContext(opts) {
23188
23400
  const appCreds = opts.appCredsOverride ?? readGithubAppCreds(opts.credsPath);
23189
23401
  if (!appCreds) return null;
23190
23402
  let endpoint = opts.endpointOverride;
23403
+ let subscriptionId = opts.subscriptionId;
23191
23404
  if (!endpoint) {
23192
23405
  const state = await readBootstrapState();
23193
23406
  if (!state) {
23194
23407
  throw new LocalCliError({ code: "BOOTSTRAP_NO_STATE", message: "No bootstrap state found.", hint: "Run 'm8t bootstrap launch' first, or pass --endpoint." });
23195
23408
  }
23409
+ subscriptionId ??= state.subscriptionId;
23196
23410
  const doc = await readStatusBlob({ saName: state.statusSaName, resourceGroup: state.resourceGroup, subscriptionId: state.subscriptionId });
23197
23411
  endpoint = doc.result?.foundryEndpoint;
23198
23412
  if (!endpoint) {
23199
23413
  throw new LocalCliError({ code: "SEED_NO_ENDPOINT", message: "Could not resolve the Foundry endpoint from the install status.", hint: "Pass --endpoint <foundryEndpoint>." });
23200
23414
  }
23201
23415
  }
23202
- return { endpoint, brainRepo: opts.brainOverride ?? `${appCreds.org}/stacey-brain`, appCreds };
23416
+ const brainRepos = opts.brainOverride ? [opts.brainOverride] : [`${appCreds.org}/stacey-brain`, `${appCreds.org}/azzy-brain`];
23417
+ return { endpoint, brainRepos, appCreds, subscriptionId };
23203
23418
  }
23204
23419
  async function applyProfileToBrain(args) {
23205
23420
  const token = await mintInstallationTokenFromPem({
@@ -23208,20 +23423,31 @@ async function applyProfileToBrain(args) {
23208
23423
  installationId: args.appCreds.installationId,
23209
23424
  fetchImpl: args.fetchImpl
23210
23425
  });
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) {
23426
+ const { profileMd, memoryIndexLine: companyLine } = renderCompanyProfile(args.block, args.now);
23427
+ const { founderMd, memoryIndexLine: founderLine } = renderFounderRecord(
23428
+ args.block,
23429
+ { subscriptionId: args.subscriptionId, azIdentity: args.azIdentity },
23430
+ args.now
23431
+ );
23432
+ const read = (p) => readRepoFileViaApp({ token, repo: args.brainRepo, path: p, fetchImpl: args.fetchImpl });
23433
+ const existingProfile = await read(COMPANY_PROFILE_PATH);
23434
+ const existingFounder = await read(FOUNDER_RECORD_PATH);
23435
+ const existingIndex = await read("memory/MEMORY.md") ?? DEFAULT_MEMORY_INDEX_HEADER;
23436
+ let nextIndex = upsertMemoryIndex(existingIndex, companyLine, COMPANY_PROFILE_PATH);
23437
+ nextIndex = upsertMemoryIndex(nextIndex, founderLine, FOUNDER_RECORD_PATH);
23438
+ const profileSame = existingProfile != null && companyProfileBodyEquals(existingProfile, profileMd);
23439
+ const founderSame = existingFounder != null && founderRecordBodyEquals(existingFounder, founderMd);
23440
+ if (profileSame && founderSame && nextIndex === existingIndex) {
23216
23441
  return;
23217
23442
  }
23218
23443
  await commitFilesViaApp({
23219
23444
  token,
23220
23445
  repo: args.brainRepo,
23221
23446
  branch: args.branch,
23222
- message: "seed(brain): company profile from onboarding questionnaire",
23447
+ message: "seed(brain): founder + company profile from onboarding",
23223
23448
  files: [
23224
23449
  { path: COMPANY_PROFILE_PATH, content: profileMd },
23450
+ { path: FOUNDER_RECORD_PATH, content: founderMd },
23225
23451
  { path: "memory/MEMORY.md", content: nextIndex }
23226
23452
  ],
23227
23453
  fetchImpl: args.fetchImpl
@@ -23241,20 +23467,31 @@ function spawnDetachedSeedWatch() {
23241
23467
  }
23242
23468
  }
23243
23469
  async function reactiveSeedOnFinish(args) {
23244
- const ctx = await resolveSeedContext({ endpointOverride: args.endpoint, appCredsOverride: args.appCredsOverride });
23470
+ const ctx = await resolveSeedContext({ endpointOverride: args.endpoint, appCredsOverride: args.appCredsOverride, subscriptionId: args.subscriptionId });
23245
23471
  if (!ctx) return;
23246
23472
  const token = await (args.getFoundryTokenImpl ?? getFoundryToken)();
23247
23473
  const { hadIntake, block } = await findOnboardingProfile({ endpoint: ctx.endpoint, token, fetchImpl: args.fetchImpl });
23248
23474
  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}).
23475
+ const azIdentity = args.azIdentity ?? await (args.getSignedInUserIdentityImpl ?? getSignedInUserIdentity)();
23476
+ for (const brainRepo of ctx.brainRepos) {
23477
+ await applyProfileToBrain({
23478
+ block,
23479
+ brainRepo,
23480
+ branch: "main",
23481
+ appCreds: ctx.appCreds,
23482
+ subscriptionId: ctx.subscriptionId,
23483
+ azIdentity,
23484
+ fetchImpl: args.fetchImpl
23485
+ });
23486
+ }
23487
+ args.stdout(`${colors.success("\u2713")} Your advisors now know your company + how to reach you (seeded ${ctx.brainRepos.join(", ")}).
23251
23488
  `);
23252
23489
  return;
23253
23490
  }
23254
23491
  if (!hadIntake) return;
23255
23492
  (args.spawnWatch ?? spawnDetachedSeedWatch)();
23256
23493
  args.stdout(
23257
- `${colors.dim("\u2139 Stacey will pick up your company profile when you finish the questionnaire (watching in the background).")}
23494
+ `${colors.dim("\u2139 Your advisors will pick up your company profile when you finish the questionnaire (watching in the background).")}
23258
23495
  ${colors.hint("or run:")} m8t bootstrap seed-profile
23259
23496
  `
23260
23497
  );
@@ -23294,14 +23531,14 @@ function renderInstallSummary(args) {
23294
23531
  // src/commands/bootstrap/finish.ts
23295
23532
  var BootstrapFinishCommand = class extends M8tCommand {
23296
23533
  static paths = [["bootstrap", "finish"]];
23297
- static usage = Command49.Usage({
23534
+ static usage = Command50.Usage({
23298
23535
  description: "Point your local tools at the now-live platform (repo-root marker, discovery cache, next steps).",
23299
23536
  details: "Final step of `m8t bootstrap` (after the install reaches done). Writes ~/.m8t-stack/repo-root, points your local tools at the live gateway, and \u2014 if you completed the onboarding questionnaire \u2014 seeds your brain-backed advisor's brain with your company profile (best-effort; never blocks finish). Run `m8t bootstrap seed-profile` to seed manually later.",
23300
23537
  examples: [["Finish local", "$0 bootstrap finish --repo-root /path/to/m8t"]]
23301
23538
  });
23302
- repoRoot = Option47.String("--repo-root");
23303
- subscription = Option47.String("--subscription");
23304
- resourceGroup = Option47.String("--resource-group");
23539
+ repoRoot = Option48.String("--repo-root");
23540
+ subscription = Option48.String("--subscription");
23541
+ resourceGroup = Option48.String("--resource-group");
23305
23542
  async executeCommand() {
23306
23543
  const state = await readBootstrapState();
23307
23544
  if (!state) {
@@ -23371,6 +23608,7 @@ ${colors.field("Then open a brand new chat/session")} \u2014 new skills + MCP se
23371
23608
  try {
23372
23609
  await reactiveSeedOnFinish({
23373
23610
  endpoint: doc.result?.foundryEndpoint,
23611
+ subscriptionId,
23374
23612
  stdout: (s) => this.context.stdout.write(s)
23375
23613
  });
23376
23614
  } catch (e) {
@@ -23387,7 +23625,7 @@ ${colors.field("Then open a brand new chat/session")} \u2014 new skills + MCP se
23387
23625
  import * as fs26 from "fs";
23388
23626
  import * as os16 from "os";
23389
23627
  import * as path28 from "path";
23390
- import { Command as Command50, Option as Option48 } from "clipanion";
23628
+ import { Command as Command51, Option as Option49 } from "clipanion";
23391
23629
  import { DefaultAzureCredential as DefaultAzureCredential17 } from "@azure/identity";
23392
23630
  init_errors();
23393
23631
 
@@ -23617,7 +23855,8 @@ async function deploySimpleStacey(args) {
23617
23855
  endpoint: args.endpoint,
23618
23856
  repoRoot: args.repoRoot,
23619
23857
  persona: SIMPLE_STACEY_PERSONA,
23620
- agentName: SIMPLE_STACEY_AGENT
23858
+ agentName: SIMPLE_STACEY_AGENT,
23859
+ fieldOverrides: args.fieldOverrides
23621
23860
  });
23622
23861
  } catch (e) {
23623
23862
  if (e && typeof e === "object" && "code" in e) {
@@ -23657,7 +23896,7 @@ async function deploySimpleStacey(args) {
23657
23896
  // src/commands/bootstrap/ui.ts
23658
23897
  var BootstrapUiCommand = class extends M8tCommand {
23659
23898
  static paths = [["bootstrap", "ui"]];
23660
- static usage = Command50.Usage({
23899
+ static usage = Command51.Usage({
23661
23900
  description: "Deploy Simple Stacey + start the local onboarding chat UI in the background (returns immediately).",
23662
23901
  details: [
23663
23902
  "Run after `m8t bootstrap launch`, in parallel with `status --watch`. Waits for the cloud",
@@ -23675,15 +23914,15 @@ var BootstrapUiCommand = class extends M8tCommand {
23675
23914
  ["Foreground (blocking, old behaviour)", "$0 bootstrap ui --repo-root /path/to/m8t --foreground"]
23676
23915
  ]
23677
23916
  });
23678
- repoRoot = Option48.String("--repo-root");
23679
- port = Option48.String("--port", "3000");
23680
- endpoint = Option48.String("--endpoint", {
23917
+ repoRoot = Option49.String("--repo-root");
23918
+ port = Option49.String("--port", "3000");
23919
+ endpoint = Option49.String("--endpoint", {
23681
23920
  description: "Foundry project endpoint to target \u2014 disambiguates when the subscription has multiple projects."
23682
23921
  });
23683
- prepOnly = Option48.Boolean("--prep-only", false);
23684
- skipInstall = Option48.Boolean("--skip-install", false);
23685
- stop = Option48.Boolean("--stop", false);
23686
- foreground = Option48.Boolean("--foreground", false);
23922
+ prepOnly = Option49.Boolean("--prep-only", false);
23923
+ skipInstall = Option49.Boolean("--skip-install", false);
23924
+ stop = Option49.Boolean("--stop", false);
23925
+ foreground = Option49.Boolean("--foreground", false);
23687
23926
  async executeCommand() {
23688
23927
  if (this.stop === true) {
23689
23928
  const stopped = stopOnboardingUi();
@@ -23735,7 +23974,13 @@ var BootstrapUiCommand = class extends M8tCommand {
23735
23974
  const oid = await getSignedInUserOid();
23736
23975
  await ensureFounderFoundryRole({ credential: credential2, subscriptionId: state.subscriptionId, principalId: oid, accountScope });
23737
23976
  out("deploying Simple Stacey (stacey-intake)\u2026");
23738
- const version = await deploySimpleStacey({ credential: credential2, endpoint, repoRoot });
23977
+ const identity = await getSignedInUserIdentity();
23978
+ const version = await deploySimpleStacey({
23979
+ credential: credential2,
23980
+ endpoint,
23981
+ repoRoot,
23982
+ fieldOverrides: { founder_identity_note: composeFounderIdentityNote(identity) }
23983
+ });
23739
23984
  const envPath = writeWebEnvLocal({
23740
23985
  repoRoot,
23741
23986
  tenantId: account.tenantId,
@@ -23784,22 +24029,22 @@ var BootstrapUiCommand = class extends M8tCommand {
23784
24029
  };
23785
24030
 
23786
24031
  // src/commands/bootstrap/seed-profile.ts
23787
- import { Command as Command51, Option as Option49 } from "clipanion";
24032
+ import { Command as Command52, Option as Option50 } from "clipanion";
23788
24033
  var BootstrapSeedProfileCommand = class extends M8tCommand {
23789
24034
  static paths = [["bootstrap", "seed-profile"]];
23790
- 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.",
24035
+ static usage = Command52.Usage({
24036
+ description: "Seed your advisors' brains with the founder + company profile from the onboarding questionnaire.",
24037
+ 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
24038
  examples: [
23794
24039
  ["Seed now (idempotent)", "$0 bootstrap seed-profile"],
23795
24040
  ["Wait for the founder to finish", "$0 bootstrap seed-profile --watch"]
23796
24041
  ]
23797
24042
  });
23798
- endpoint = Option49.String("--endpoint", { description: "Override the Foundry endpoint (else read from the install status)." });
23799
- brain = Option49.String("--brain", { description: "Override the brain repo (default <org>/stacey-brain)." });
23800
- watch = Option49.Boolean("--watch", false, { description: "Poll until the questionnaire completes (or --timeout)." });
23801
- timeout = Option49.String("--timeout", { description: "Watch timeout in minutes (default 20)." });
23802
- githubAppCreds = Option49.String("--github-app-creds");
24043
+ endpoint = Option50.String("--endpoint", { description: "Override the Foundry endpoint (else read from the install status)." });
24044
+ brain = Option50.String("--brain", { description: "Override the brain repo (default <org>/stacey-brain)." });
24045
+ watch = Option50.Boolean("--watch", false, { description: "Poll until the questionnaire completes (or --timeout)." });
24046
+ timeout = Option50.String("--timeout", { description: "Watch timeout in minutes (default 20)." });
24047
+ githubAppCreds = Option50.String("--github-app-creds");
23803
24048
  async executeCommand() {
23804
24049
  const ctx = await resolveSeedContext({
23805
24050
  endpointOverride: typeof this.endpoint === "string" ? this.endpoint : void 0,
@@ -23821,8 +24066,11 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
23821
24066
  const token = await getFoundryToken();
23822
24067
  const { hadIntake, block } = await findOnboardingProfile({ endpoint: ctx.endpoint, token });
23823
24068
  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.
24069
+ const azIdentity = await getSignedInUserIdentity();
24070
+ for (const brainRepo of ctx.brainRepos) {
24071
+ await applyProfileToBrain({ block, brainRepo, branch: "main", appCreds: ctx.appCreds, subscriptionId: ctx.subscriptionId, azIdentity });
24072
+ }
24073
+ this.context.stdout.write(`${colors.success("\u2713")} seeded your advisors' brains (${ctx.brainRepos.join(", ")}) from your questionnaire.
23826
24074
  `);
23827
24075
  return 0;
23828
24076
  }
@@ -23881,6 +24129,7 @@ cli.register(CoderTeardownCommand);
23881
24129
  cli.register(AzureExecDeployCommand);
23882
24130
  cli.register(PlatformStatusCommand);
23883
24131
  cli.register(PlatformUpdateCommand);
24132
+ cli.register(PlatformEnableCostReportCommand);
23884
24133
  cli.register(DeployCommand);
23885
24134
  cli.register(AgentDeployAdvisorCommand);
23886
24135
  cli.register(AgentRemoveCommand);