@m8t-stack/cli 0.2.28 → 0.2.29

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
@@ -1247,7 +1247,7 @@ var init_enable_hosted_brain = __esm({
1247
1247
  import { Builtins, Cli } from "clipanion";
1248
1248
 
1249
1249
  // src/lib/package-version.ts
1250
- var CLI_VERSION = "0.2.28";
1250
+ var CLI_VERSION = "0.2.29";
1251
1251
 
1252
1252
  // src/lib/render-error.ts
1253
1253
  init_errors();
@@ -1442,49 +1442,31 @@ var BACKEND_RULES = [
1442
1442
  }
1443
1443
  ];
1444
1444
  var LOCAL_RULES = {
1445
- // Task 12 — foundry-agent-get
1446
1445
  AGENT_CREATE_VERSION_FAILED: "Re-run with `--verbose` for the full response body.",
1447
1446
  AGENT_CREATE_VERSION_NO_VERSION: "Foundry returned 2xx but no version \u2014 likely an API regression. Re-try; if it persists, file an issue.",
1448
1447
  AGENT_GET_FAILED: "Re-run with `--verbose` for the full response body.",
1449
1448
  AGENT_NOT_FOUND: "Use `m8t brain create <name>` to create the worker first, OR run `m8t deploy <name>` to register an existing agent.",
1450
- // Task 14 — brain-unlink
1451
1449
  AGENT_REDEPLOY_FAILED: "Re-run with `--verbose` for the full response body.",
1452
1450
  AGENT_REDEPLOY_NO_VERSION: "Foundry returned 2xx but no version. Re-try; if it persists, file an issue.",
1453
- // Task 10/13/14
1454
1451
  AGENT_YAML_NOT_FOUND: "Deploy the worker via `m8t deploy <name>` or the architect first.",
1455
- // Task 16 — brain link command
1456
1452
  APP_HEALTH_FAILED: "Run `m8t brain check-app` for details, then re-run this command.",
1457
- // Task 14 — brain-unlink
1458
1453
  APP_UNINSTALL_FAILED: "GitHub App-uninstall DELETE failed. Re-run with `--keep-app-install` if you want to skip this step.",
1459
- // Task 13 — brain-link
1460
1454
  ARM_AUTH: "Run `az login` and retry.",
1461
1455
  // F2 smoke fix — m8t brain create: stageAsGitRepo step
1462
1456
  BRAIN_GIT_INIT_FAILED: "If git is not installed, install it. Otherwise re-run with --verbose.",
1463
- // Task 14 — brain-unlink
1464
1457
  BRAIN_NOT_LINKED: "The worker has no brain link. Use `m8t brain link` or `m8t brain create` first.",
1465
- // Task 13 — brain-link
1466
1458
  BRAIN_YAML_MIRROR_FAILED: "GitHub Contents API PUT failed. Verify the App has `contents: write` permission on the repo.",
1467
- // Task 13 — brain-link
1468
1459
  CONN_CREATE_FAILED: "Foundry connection PUT failed. Verify Cognitive Services Contributor role on the project.",
1469
- // Task 14 — brain-unlink
1470
1460
  CONN_DELETE_FAILED: "Foundry connection DELETE failed. Verify your ARM subscription has Contributor on the project.",
1471
- // Task 13 — brain-link
1472
1461
  CONN_GET_FAILED: "Foundry connection GET failed. Verify your ARM subscription has access to the project.",
1473
- // Task 13 — brain-link.ts (rename pending: should be FOUNDRY_AUTH_FAILED)
1462
+ // rename pending: should be FOUNDRY_AUTH_FAILED
1474
1463
  FOUNDRY_AUTH: "Run `az login` and retry.",
1475
- // Task 12 — foundry-agent-get
1476
1464
  FOUNDRY_AUTH_FAILED: "Run `az login` and retry.",
1477
- // Task 15+16
1478
1465
  KV_URI_NOT_FOUND: "Pass `--kv-uri` or set `AZURE_KEYVAULT_URI` / `KEYVAULT_URI`.",
1479
- // Task 13 — brain-link
1480
1466
  LINK_NO_INSTALLATION_ID: "Internal error \u2014 the CLI command must poll for the installation id before calling linkBrain. Report at https://github.com/m8t/m8t/issues.",
1481
- // Task 16 — brain link command
1482
1467
  M8T_REPO_ROOT_MISSING: "Re-run `m8t install` from a fresh m8t checkout \u2014 the post-install hook writes ~/.m8t/repo-root.",
1483
- // Task 14 — persona-render
1484
1468
  PERSONA_PATH_NOT_ABSOLUTE: "The persona path in ~/.m8t/foundry/<worker>.yaml must be absolute. Re-deploy the worker via the architect.",
1485
- // Task 9 — persona-render
1486
1469
  PERSONA_PLACEHOLDER_UNSATISFIED: "Re-deploy the worker via the architect to repopulate fillableFieldValues, OR edit ~/.m8t/foundry/<worker>.yaml manually.",
1487
- // Task 9 — persona-render
1488
1470
  PERSONA_READ_FAILED: "Verify the persona path in ~/.m8t/foundry/<worker>.yaml; the architect should have populated it at deploy time.",
1489
1471
  // ── pre-existing entries ──────────────────────────────────────────────────
1490
1472
  AUTH_NOT_SIGNED_IN: "you are not signed in to Azure \u2014 run 'az login' first.",
@@ -11524,7 +11506,7 @@ function redactAzArgs(args) {
11524
11506
  }
11525
11507
  function runAz(args) {
11526
11508
  return new Promise((resolve3, reject) => {
11527
- const proc = spawn("az", args, { stdio: ["ignore", "pipe", "pipe"] });
11509
+ const proc = spawn("az", args, { stdio: ["ignore", "pipe", "pipe"], shell: process.platform === "win32" });
11528
11510
  const out = [];
11529
11511
  const err = [];
11530
11512
  proc.stdout.on("data", (d) => out.push(d));
@@ -13391,9 +13373,9 @@ async function probeAppInstallation(args) {
13391
13373
  ${body.slice(0, 300)}`);
13392
13374
  }
13393
13375
  async function tryOpenUrl(url, exec = defaultGhExec) {
13394
- const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
13376
+ const [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
13395
13377
  try {
13396
- await exec(cmd, [url]);
13378
+ await exec(cmd, args);
13397
13379
  } catch {
13398
13380
  }
13399
13381
  }
@@ -13825,7 +13807,7 @@ function renderPersonaBody(personaPath, fillableFieldValues, instanceName) {
13825
13807
  message: `Could not read persona file '${personaPath}': ${e.message}`
13826
13808
  });
13827
13809
  }
13828
- const fmMatch = /^---\n[\s\S]*?\n---\n+/.exec(raw);
13810
+ const fmMatch = /^---\r?\n[\s\S]*?\r?\n---(?:\r?\n)+/.exec(raw);
13829
13811
  const body = fmMatch ? raw.slice(fmMatch[0].length) : raw;
13830
13812
  let rendered = body;
13831
13813
  for (const [name, value] of Object.entries(fillableFieldValues)) {
@@ -15547,7 +15529,7 @@ async function deployPromptAdvisor(args) {
15547
15529
  cause: e
15548
15530
  });
15549
15531
  }
15550
- const fmMatch = /^---\n([\s\S]*?)\n---/.exec(raw);
15532
+ const fmMatch = /^---\r?\n([\s\S]*?)\r?\n---/.exec(raw);
15551
15533
  const fm = fmMatch ? parseYaml6(fmMatch[1]) : {};
15552
15534
  const model = args.model ?? fm.targets?.foundry?.model;
15553
15535
  if (!model) {
@@ -15823,7 +15805,7 @@ function readPersonaA2aCard(personaPath) {
15823
15805
  } catch (e) {
15824
15806
  throw new LocalCliError({ code: "PERSONA_READ_FAILED", message: `Could not read persona '${personaPath}': ${e.message}` });
15825
15807
  }
15826
- const fm = /^---\n([\s\S]*?)\n---/.exec(raw);
15808
+ const fm = /^---\r?\n([\s\S]*?)\r?\n---/.exec(raw);
15827
15809
  if (!fm) throw new LocalCliError({ code: "PERSONA_NO_FRONTMATTER", message: `Persona '${personaPath}' has no frontmatter.` });
15828
15810
  const front = parseYaml7(fm[1]);
15829
15811
  const role = typeof front.role === "string" ? front.role : "";
@@ -16899,7 +16881,7 @@ function resolvePersona(persona, cwd = process.cwd()) {
16899
16881
  } catch {
16900
16882
  return { persona, personaVersion: null };
16901
16883
  }
16902
- const match = /^---\n([\s\S]*?)\n---/.exec(raw);
16884
+ const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(raw);
16903
16885
  if (!match) return { persona, personaVersion: null };
16904
16886
  let fm = null;
16905
16887
  try {
@@ -18727,7 +18709,7 @@ function declaredFillableFields(personaPath) {
18727
18709
  } catch {
18728
18710
  return [];
18729
18711
  }
18730
- const fmMatch = /^---\n([\s\S]*?)\n---/.exec(raw);
18712
+ const fmMatch = /^---\r?\n([\s\S]*?)\r?\n---/.exec(raw);
18731
18713
  if (!fmMatch) return [];
18732
18714
  try {
18733
18715
  const fm = parseYaml10(fmMatch[1]);
@@ -21635,7 +21617,7 @@ init_errors();
21635
21617
  import { spawn as spawn4 } from "child_process";
21636
21618
  function runAzd(args) {
21637
21619
  return new Promise((resolve3, reject) => {
21638
- const proc = spawn4("azd", args, { stdio: ["ignore", "pipe", "pipe"] });
21620
+ const proc = spawn4("azd", args, { stdio: ["ignore", "pipe", "pipe"], shell: process.platform === "win32" });
21639
21621
  const out = [];
21640
21622
  const err = [];
21641
21623
  proc.stdout.on("data", (d) => out.push(d));
@@ -25370,6 +25352,32 @@ var FoundryAwaitReadyCommand = class extends M8tCommand {
25370
25352
 
25371
25353
  // src/commands/bootstrap/preflight.ts
25372
25354
  import { Command as Command48, Option as Option45 } from "clipanion";
25355
+
25356
+ // ../../packages/telemetry-contract/artifact/tier-map.ts
25357
+ var EVENT_TIERS = {
25358
+ "enrolled": 1,
25359
+ "install-started": 1,
25360
+ "install-succeeded": 1,
25361
+ "install-failed": 1,
25362
+ "heartbeat": 1,
25363
+ "update-applied": 1,
25364
+ "update-failed": 1,
25365
+ "consent-changed": 1
25366
+ // extended additively for tier-2 usage events: "usage-rollup": 2
25367
+ };
25368
+ var EVENT_ENUM = Object.keys(EVENT_TIERS);
25369
+
25370
+ // ../../packages/telemetry-contract/src/disclosure.ts
25371
+ var DISCLOSURE_TIER1 = "Operational data. To support your installation, m8t receives limited operational data linked to it \u2014 installation and update events, installed versions, and service health signals, along with the company name and contact email you provide. This is service data used only to operate and support the platform; it contains none of your content. Details: TELEMETRY.md.";
25372
+
25373
+ // ../../packages/telemetry-contract/src/endpoints.ts
25374
+ var INGEST_BASE_URL = (process.env.M8T_INGEST_BASE_URL ?? "").trim() || "https://m8t-admin.wonderfuldesert-721e332f.eastus2.azurecontainerapps.io";
25375
+ function ingestUrl(path37) {
25376
+ const base = INGEST_BASE_URL.replace(/\/+$/, "");
25377
+ return `${base}${path37.startsWith("/") ? path37 : `/${path37}`}`;
25378
+ }
25379
+
25380
+ // src/commands/bootstrap/preflight.ts
25373
25381
  init_errors();
25374
25382
 
25375
25383
  // src/lib/bootstrap-preflight.ts
@@ -25459,6 +25467,9 @@ var BootstrapPreflightCommand = class extends M8tCommand {
25459
25467
  const subscriptionId = (typeof this.subscription === "string" ? this.subscription : void 0) ?? account.subscriptionId;
25460
25468
  const oid = await getCallerObjectId();
25461
25469
  this.context.stdout.write(buildPreflightBanner({ upn: account.upn, tenantId: account.tenantId, subscriptionId }) + "\n");
25470
+ this.context.stdout.write(`
25471
+ ${colors.dim(DISCLOSURE_TIER1)}
25472
+ `);
25462
25473
  this.context.stdout.write("Checking your access\u2026\n");
25463
25474
  const sub = await checkSubScopeAdmin(oid, subscriptionId);
25464
25475
  this.context.stdout.write(line(sub.ok, `${sub.role ?? "Owner/UAA"} at subscription scope`, "resource + role-assignment rights"));
@@ -25610,6 +25621,7 @@ function buildAciCreateArgs(s) {
25610
25621
  ];
25611
25622
  if (s.gatewayImageRef) env.push(`GATEWAY_IMAGE_REF=${s.gatewayImageRef}`);
25612
25623
  if (s.foundryTracing) env.push(`FOUNDRY_TRACING=${s.foundryTracing}`);
25624
+ if (s.enrollContactEmail) env.push(`M8T_ENROLL_CONTACT_EMAIL=${s.enrollContactEmail}`);
25613
25625
  if (s.githubApp) {
25614
25626
  const g = s.githubApp;
25615
25627
  env.push(
@@ -25760,7 +25772,7 @@ async function writeBootstrapState(state, home = os13.homedir()) {
25760
25772
  // src/commands/bootstrap/launch.ts
25761
25773
  var DEFAULT_RG = "rg-m8t-stack";
25762
25774
  var DEFAULT_INSTALLER = "ghcr.io/m8t-labs/m8t-installer";
25763
- var DEFAULT_INSTALLER_TAG = "v0.1.33";
25775
+ var DEFAULT_INSTALLER_TAG = "v0.1.34";
25764
25776
  var ACI_NAME = "m8t-installer";
25765
25777
  var MI_NAME = "m8t-installer-mi";
25766
25778
  var BootstrapLaunchCommand = class extends M8tCommand {
@@ -25876,7 +25888,8 @@ var BootstrapLaunchCommand = class extends M8tCommand {
25876
25888
  image: installerImage,
25877
25889
  gatewayImageRef,
25878
25890
  foundryTracing: "skip",
25879
- githubApp
25891
+ githubApp,
25892
+ enrollContactEmail: account.upn
25880
25893
  });
25881
25894
  this.context.stdout.write(
25882
25895
  `${colors.success("\u2713")} installer launched in ${colors.field(resourceGroup)} (${location}).
@@ -26640,7 +26653,7 @@ import * as fs32 from "fs";
26640
26653
  import * as net from "net";
26641
26654
  import * as os17 from "os";
26642
26655
  import * as path35 from "path";
26643
- import { spawn as spawn7 } from "child_process";
26656
+ import { spawn as spawn7, spawnSync as spawnSync6 } from "child_process";
26644
26657
  init_errors();
26645
26658
  init_rbac();
26646
26659
 
@@ -26906,7 +26919,8 @@ async function serveOnboardingUiDetached(args) {
26906
26919
  detached: true,
26907
26920
  stdio: ["ignore", fd, fd],
26908
26921
  env: { ...process.env, PORT: args.port },
26909
- shell: false
26922
+ // pnpm resolves to pnpm.cmd on Windows, which needs a shell to launch.
26923
+ shell: process.platform === "win32"
26910
26924
  });
26911
26925
  fs32.closeSync(fd);
26912
26926
  if (child.pid == null) {
@@ -26946,7 +26960,8 @@ async function serveOnboardingRelayDetached(args) {
26946
26960
  RELAY_ALLOWED_ORIGINS: "http://localhost:3000",
26947
26961
  RELAY_PORT: "8790"
26948
26962
  },
26949
- shell: false
26963
+ // pnpm resolves to pnpm.cmd on Windows, which needs a shell to launch.
26964
+ shell: process.platform === "win32"
26950
26965
  });
26951
26966
  fs32.closeSync(fd);
26952
26967
  if (child.pid == null) {
@@ -26985,10 +27000,14 @@ function stopPidFile(pidPath) {
26985
27000
  }
26986
27001
  const pid = Number(pidStr);
26987
27002
  if (!Number.isNaN(pid) && pid > 0) {
26988
- try {
26989
- process.kill(-pid, "SIGTERM");
26990
- } catch (e) {
26991
- if (e.code !== "ESRCH") throw e;
27003
+ if (process.platform === "win32") {
27004
+ spawnSync6("taskkill", ["/pid", String(pid), "/T", "/F"], { stdio: "ignore" });
27005
+ } else {
27006
+ try {
27007
+ process.kill(-pid, "SIGTERM");
27008
+ } catch (e) {
27009
+ if (e.code !== "ESRCH") throw e;
27010
+ }
26992
27011
  }
26993
27012
  }
26994
27013
  try {
@@ -27240,6 +27259,112 @@ var BootstrapSeedProfileCommand = class extends M8tCommand {
27240
27259
  }
27241
27260
  };
27242
27261
 
27262
+ // src/commands/telemetry/enroll.ts
27263
+ import { Command as Command55, Option as Option52 } from "clipanion";
27264
+ init_errors();
27265
+
27266
+ // src/lib/telemetry-enroll.ts
27267
+ init_errors();
27268
+ async function enroll(args) {
27269
+ const f = args.fetchImpl ?? fetch;
27270
+ const url = args.baseUrl ? `${args.baseUrl.replace(/\/+$/, "")}/api/ingest/enroll` : ingestUrl("/api/ingest/enroll");
27271
+ let res;
27272
+ try {
27273
+ res = await f(url, {
27274
+ method: "POST",
27275
+ headers: { "content-type": "application/json" },
27276
+ body: JSON.stringify({ schemaVersion: 1, company: args.company, contactEmail: args.contactEmail, subscriptionId: args.subscriptionId, proof: null })
27277
+ });
27278
+ } catch (e) {
27279
+ throw new LocalCliError({ code: "TELEMETRY_ENROLL_UNREACHABLE", message: `Could not reach the telemetry ingest to enroll: ${e instanceof Error ? e.message : String(e)}` });
27280
+ }
27281
+ if (!res.ok) {
27282
+ const body = await res.text().catch(() => "");
27283
+ throw new LocalCliError({ code: "TELEMETRY_ENROLL_FAILED", message: `Enroll failed (HTTP ${res.status.toString()}): ${body}` });
27284
+ }
27285
+ const json = await res.json();
27286
+ if (!json.instanceId || !json.ingestKey) {
27287
+ throw new LocalCliError({ code: "TELEMETRY_ENROLL_BAD_RESPONSE", message: "Enroll response missing instanceId/ingestKey." });
27288
+ }
27289
+ return { instanceId: json.instanceId, ingestKey: json.ingestKey };
27290
+ }
27291
+
27292
+ // src/commands/telemetry/enroll.ts
27293
+ var INGEST_KEY_SECRET = "m8t-ingest-key";
27294
+ async function resolveKeyVaultName(containerAppResourceId) {
27295
+ const kvUri = (await runAz([
27296
+ "containerapp",
27297
+ "show",
27298
+ "--ids",
27299
+ containerAppResourceId,
27300
+ "--query",
27301
+ "properties.template.containers[0].env[?name=='KEY_VAULT_URI'].value | [0]",
27302
+ "-o",
27303
+ "tsv"
27304
+ ])).trim();
27305
+ if (!kvUri) {
27306
+ throw new LocalCliError({
27307
+ code: "TELEMETRY_ENROLL_NO_KV",
27308
+ message: "Could not resolve the platform Key Vault: the gateway Container App has no KEY_VAULT_URI env var.",
27309
+ hint: "Verify the deployment via 'az containerapp show --ids <id> --query properties.template.containers[0].env'."
27310
+ });
27311
+ }
27312
+ const host = new URL(kvUri).hostname;
27313
+ const name = host.split(".")[0];
27314
+ if (!name) {
27315
+ throw new LocalCliError({ code: "TELEMETRY_ENROLL_BAD_KV_URI", message: `Could not parse a Key Vault name from KEY_VAULT_URI '${kvUri}'.` });
27316
+ }
27317
+ return name;
27318
+ }
27319
+ var TelemetryEnrollCommand = class extends M8tCommand {
27320
+ static paths = [["telemetry", "enroll"]];
27321
+ static usage = Command55.Usage({
27322
+ description: "Enroll this installation for operational telemetry (pre-existing installs).",
27323
+ details: "Mints an instance id + ingest key from the m8t telemetry ingest and stores the key in your platform Key Vault, the same place the installer writes it on a fresh install. Idempotent: refuses if a key already exists unless --force.",
27324
+ examples: [["Enroll", "$0 telemetry enroll"]]
27325
+ });
27326
+ company = Option52.String("--company", { description: "Your company name (optional)." });
27327
+ contactEmail = Option52.String("--contact-email", { description: "Contact email (default: your signed-in az UPN)." });
27328
+ subscription = Option52.String("--subscription", { description: "Azure subscription id (default: your active az subscription)." });
27329
+ resourceGroup = Option52.String("--resource-group", { description: "Resource group to disambiguate discovery, if you have more than one m8t deployment." });
27330
+ force = Option52.Boolean("--force", false, { description: "Re-enroll even if a key is already stored (mints a new instance id)." });
27331
+ async executeCommand() {
27332
+ const account = await getAzAccount();
27333
+ const subscriptionId = (typeof this.subscription === "string" ? this.subscription : void 0) ?? account.subscriptionId;
27334
+ const contactEmail = (typeof this.contactEmail === "string" ? this.contactEmail : void 0) ?? account.upn;
27335
+ const company = (typeof this.company === "string" ? this.company : void 0) ?? "";
27336
+ const d = await discoverGateway({
27337
+ subscriptionId,
27338
+ interactive: false,
27339
+ resourceGroup: typeof this.resourceGroup === "string" ? this.resourceGroup : void 0
27340
+ });
27341
+ const kvName = await resolveKeyVaultName(d.containerAppResourceId);
27342
+ if (this.force !== true) {
27343
+ let existing = "";
27344
+ try {
27345
+ existing = await runAz(["keyvault", "secret", "show", "--vault-name", kvName, "--name", INGEST_KEY_SECRET, "--query", "value", "-o", "tsv"]);
27346
+ } catch (e) {
27347
+ rethrowIfAzUnavailable(e);
27348
+ existing = "";
27349
+ }
27350
+ if (existing.trim()) {
27351
+ throw new LocalCliError({
27352
+ code: "TELEMETRY_ALREADY_ENROLLED",
27353
+ message: "This installation is already enrolled.",
27354
+ hint: "Pass --force to re-enroll (mints a new instance id)."
27355
+ });
27356
+ }
27357
+ }
27358
+ const { instanceId, ingestKey } = await enroll({ company, contactEmail, subscriptionId });
27359
+ await runAz(["keyvault", "secret", "set", "--vault-name", kvName, "--name", INGEST_KEY_SECRET, "--value", ingestKey, "--only-show-errors"]);
27360
+ this.context.stdout.write(
27361
+ `${colors.success("\u2713")} enrolled (instance ${instanceId}); ingest key stored in Key Vault ${kvName}. The gateway picks it up on its next cycle.
27362
+ `
27363
+ );
27364
+ return 0;
27365
+ }
27366
+ };
27367
+
27243
27368
  // src/cli.ts
27244
27369
  var cli = new Cli({
27245
27370
  binaryName: "m8t",
@@ -27300,6 +27425,7 @@ cli.register(BootstrapReapCommand);
27300
27425
  cli.register(BootstrapFinishCommand);
27301
27426
  cli.register(BootstrapUiCommand);
27302
27427
  cli.register(BootstrapSeedProfileCommand);
27428
+ cli.register(TelemetryEnrollCommand);
27303
27429
  async function main() {
27304
27430
  try {
27305
27431
  return await cli.run(process.argv.slice(2));