@m8t-stack/cli 0.2.43 → 0.2.44

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
@@ -1310,7 +1310,7 @@ var init_enable_hosted_brain = __esm({
1310
1310
  import { Builtins, Cli } from "clipanion";
1311
1311
 
1312
1312
  // src/lib/package-version.ts
1313
- var CLI_VERSION = "0.2.43";
1313
+ var CLI_VERSION = "0.2.44";
1314
1314
 
1315
1315
  // src/lib/render-error.ts
1316
1316
  init_errors();
@@ -29303,6 +29303,13 @@ function stopOnboardingUi(home = os17.homedir()) {
29303
29303
  const relayStopped = stopPidFile(onboardingRelayPaths(home).pidPath);
29304
29304
  return uiStopped || relayStopped;
29305
29305
  }
29306
+ function buildIntakeFieldOverrides(args) {
29307
+ return {
29308
+ founder_identity_note: args.founderIdentityNote,
29309
+ chosen_model_note: args.chosenModelNote,
29310
+ ...args.inventoryNote ? { subscription_inventory_note: args.inventoryNote } : {}
29311
+ };
29312
+ }
29306
29313
 
29307
29314
  // src/lib/model-catalog.ts
29308
29315
  async function listAgentModels(region) {
@@ -29555,6 +29562,195 @@ async function resolveIntakeModel(args) {
29555
29562
  }
29556
29563
  }
29557
29564
 
29565
+ // src/lib/inventory-summary.ts
29566
+ var HEAVY_THRESHOLD = 10;
29567
+ var INVENTORY_INVITATION = "You can offer to walk the founder through these resources.";
29568
+ var NOTABLE_TYPES = {
29569
+ // storage
29570
+ "microsoft.storage/storageaccounts": "storage accounts",
29571
+ // compute
29572
+ "microsoft.compute/virtualmachines": "virtual machines",
29573
+ "microsoft.compute/virtualmachinescalesets": "virtual machine scale sets",
29574
+ "microsoft.app/containerapps": "container apps",
29575
+ "microsoft.containerservice/managedclusters": "Kubernetes clusters",
29576
+ "microsoft.web/sites": "web apps",
29577
+ // data (shared noun)
29578
+ "microsoft.sql/servers": "databases",
29579
+ "microsoft.dbforpostgresql/servers": "databases",
29580
+ "microsoft.dbforpostgresql/flexibleservers": "databases",
29581
+ "microsoft.dbformysql/servers": "databases",
29582
+ "microsoft.dbformysql/flexibleservers": "databases",
29583
+ "microsoft.documentdb/databaseaccounts": "databases",
29584
+ "microsoft.cache/redis": "databases",
29585
+ // AI (shared noun)
29586
+ "microsoft.cognitiveservices/accounts": "AI services",
29587
+ "microsoft.machinelearningservices/workspaces": "AI services",
29588
+ // key vaults
29589
+ "microsoft.keyvault/vaults": "key vaults",
29590
+ // container registries
29591
+ "microsoft.containerregistry/registries": "container registries",
29592
+ // messaging (shared noun)
29593
+ "microsoft.servicebus/namespaces": "messaging namespaces",
29594
+ "microsoft.eventhub/namespaces": "messaging namespaces",
29595
+ // networking (shared noun); public DNS zones only — private zones are plumbing
29596
+ "microsoft.network/virtualnetworks": "networking resources",
29597
+ "microsoft.network/loadbalancers": "networking resources",
29598
+ "microsoft.network/applicationgateways": "networking resources",
29599
+ "microsoft.network/dnszones": "networking resources"
29600
+ };
29601
+ var MS_PER_DAY = 864e5;
29602
+ var REGION_MAJORITY = 0.6;
29603
+ var AGE_BUCKETS = [
29604
+ { maxDays: 31, phrase: "all created within the last month" },
29605
+ { maxDays: 93, phrase: "the oldest created a couple of months ago" },
29606
+ { maxDays: 186, phrase: "the oldest created several months ago" },
29607
+ { maxDays: 365, phrase: "the oldest created about a year ago" },
29608
+ { maxDays: 730, phrase: "the oldest created over a year ago" }
29609
+ ];
29610
+ var OLDEST_FALLBACK = "the oldest created a couple of years ago";
29611
+ function ageBucket(days) {
29612
+ for (const b of AGE_BUCKETS) if (days <= b.maxDays) return b.phrase;
29613
+ return OLDEST_FALLBACK;
29614
+ }
29615
+ function summarizeInventory(rows, installResourceGroup, now) {
29616
+ const installRg = installResourceGroup.toLowerCase();
29617
+ const notable = rows.filter(
29618
+ (r) => r.resourceGroup.toLowerCase() !== installRg && Object.hasOwn(NOTABLE_TYPES, r.type.toLowerCase())
29619
+ );
29620
+ const notableCount = notable.length;
29621
+ const byNoun = /* @__PURE__ */ new Map();
29622
+ for (const r of notable) {
29623
+ const noun = NOTABLE_TYPES[r.type.toLowerCase()];
29624
+ byNoun.set(noun, (byNoun.get(noun) ?? 0) + 1);
29625
+ }
29626
+ const categories = [...byNoun.entries()].map(([noun, count]) => ({ noun, count })).sort((a, b) => b.count - a.count || a.noun.localeCompare(b.noun));
29627
+ const byRegion = /* @__PURE__ */ new Map();
29628
+ for (const r of notable) {
29629
+ if (!r.location) continue;
29630
+ const loc = r.location.toLowerCase();
29631
+ byRegion.set(loc, (byRegion.get(loc) ?? 0) + 1);
29632
+ }
29633
+ const regionSpread = byRegion.size;
29634
+ let topRegion = null;
29635
+ for (const [region, count] of byRegion) {
29636
+ if (notableCount > 0 && count / notableCount >= REGION_MAJORITY) {
29637
+ topRegion = region;
29638
+ break;
29639
+ }
29640
+ }
29641
+ let oldestMs = null;
29642
+ for (const r of notable) {
29643
+ if (!r.createdTime) continue;
29644
+ const t = Date.parse(r.createdTime);
29645
+ if (Number.isNaN(t)) continue;
29646
+ if (oldestMs === null || t < oldestMs) oldestMs = t;
29647
+ }
29648
+ const oldestBucket = oldestMs === null ? null : ageBucket((now - oldestMs) / MS_PER_DAY);
29649
+ return {
29650
+ verdict: notableCount >= HEAVY_THRESHOLD ? "heavy" : "light",
29651
+ notableCount,
29652
+ categories,
29653
+ topRegion,
29654
+ regionSpread,
29655
+ oldestBucket
29656
+ };
29657
+ }
29658
+ var LEAD = "I looked over what's already in this subscription, outside the resources m8t is setting up.";
29659
+ var MAX_NOUNS = 4;
29660
+ var REGION_NAMES = {
29661
+ eastus: "East US",
29662
+ eastus2: "East US 2",
29663
+ westus: "West US",
29664
+ westus2: "West US 2",
29665
+ westus3: "West US 3",
29666
+ centralus: "Central US",
29667
+ southcentralus: "South Central US",
29668
+ westeurope: "West Europe",
29669
+ northeurope: "North Europe",
29670
+ uksouth: "UK South",
29671
+ swedencentral: "Sweden Central",
29672
+ australiaeast: "Australia East",
29673
+ eastasia: "East Asia",
29674
+ southeastasia: "Southeast Asia"
29675
+ };
29676
+ function regionDisplay(region) {
29677
+ return REGION_NAMES[region.toLowerCase()] ?? region;
29678
+ }
29679
+ function joinList(items) {
29680
+ if (items.length <= 1) return items[0] ?? "";
29681
+ return `${items.slice(0, -1).join(", ")} and ${items[items.length - 1]}`;
29682
+ }
29683
+ function piecesClause(s) {
29684
+ const shown = s.categories.slice(0, MAX_NOUNS).map((c) => `${String(c.count)} ${c.noun}`);
29685
+ if (s.categories.length > MAX_NOUNS) {
29686
+ return `The notable pieces: ${shown.join(", ")}, and a handful of others`;
29687
+ }
29688
+ return `The notable pieces: ${joinList(shown)}`;
29689
+ }
29690
+ function regionAgeClause(s) {
29691
+ const region = s.topRegion ? `mostly in ${regionDisplay(s.topRegion)}` : s.regionSpread > 1 ? `spread across ${String(s.regionSpread)} regions` : "";
29692
+ const parts = [region, s.oldestBucket ?? ""].filter((p) => p !== "");
29693
+ return parts.length > 0 ? ` \u2014 ${parts.join(", ")}` : "";
29694
+ }
29695
+ function renderInventoryNote(summary) {
29696
+ const body = summary.notableCount === 0 ? `${LEAD} There are no notable pre-existing resources.` : `${LEAD} ${piecesClause(summary)}${regionAgeClause(summary)}.`;
29697
+ return summary.verdict === "heavy" ? `${body} ${INVENTORY_INVITATION}` : body;
29698
+ }
29699
+
29700
+ // src/lib/subscription-inventory.ts
29701
+ async function listSubscriptionResources(subscriptionId) {
29702
+ let out;
29703
+ try {
29704
+ out = await runAz(["resource", "list", "--subscription", subscriptionId, "--output", "json"]);
29705
+ } catch (e) {
29706
+ const msg = e instanceof Error ? e.message : String(e);
29707
+ const denied = /AuthorizationFailed|Forbidden|\b403\b|does not have authorization/i.test(msg);
29708
+ return { ok: false, error: denied ? "denied" : "unavailable" };
29709
+ }
29710
+ let raw;
29711
+ try {
29712
+ raw = JSON.parse(out);
29713
+ } catch {
29714
+ return { ok: false, error: "malformed" };
29715
+ }
29716
+ if (!Array.isArray(raw) || raw.some((e) => typeof e !== "object" || e === null)) {
29717
+ return { ok: false, error: "malformed" };
29718
+ }
29719
+ const rows = raw.map((e) => ({
29720
+ type: e.type ?? "",
29721
+ resourceGroup: e.resourceGroup ?? "",
29722
+ location: e.location ?? "",
29723
+ createdTime: e.createdTime ?? null
29724
+ }));
29725
+ return { ok: true, rows };
29726
+ }
29727
+ var DEFAULT_DEADLINE_MS2 = 25e3;
29728
+ async function resolveSubscriptionInventory(args) {
29729
+ const now = args.now ?? (() => Date.now());
29730
+ const deadlineMs = args.deadlineMs ?? DEFAULT_DEADLINE_MS2;
29731
+ const scanFn = args.scanImpl ?? listSubscriptionResources;
29732
+ let timer;
29733
+ try {
29734
+ args.onNarrate?.("checking what's already in your subscription...");
29735
+ const scan2 = scanFn(args.subscriptionId);
29736
+ void scan2.catch(() => {
29737
+ });
29738
+ const timeout = new Promise((resolve3) => {
29739
+ timer = setTimeout(() => {
29740
+ resolve3("timeout");
29741
+ }, deadlineMs);
29742
+ });
29743
+ const raced = await Promise.race([scan2, timeout]);
29744
+ if (raced === "timeout" || !raced.ok) return {};
29745
+ const summary = summarizeInventory(raced.rows, args.installResourceGroup, now());
29746
+ return { note: renderInventoryNote(summary) };
29747
+ } catch {
29748
+ return {};
29749
+ } finally {
29750
+ if (timer) clearTimeout(timer);
29751
+ }
29752
+ }
29753
+
29558
29754
  // src/commands/bootstrap/ui.ts
29559
29755
  function renderDeploySuccess(version, envPath) {
29560
29756
  return `${colors.success("\u2713")} Simple Stacey is live (stacey-intake v${version}).
@@ -29657,20 +29853,28 @@ var BootstrapUiCommand = class extends M8tCommand {
29657
29853
  const identity = await getSignedInUserIdentity();
29658
29854
  out("deploying Simple Stacey (stacey-intake) in the background...");
29659
29855
  const deployOutcome = (async () => {
29660
- const choice = await resolveIntakeModel({
29661
- accountScope,
29662
- fallbackRegion: state.location,
29663
- onNarrate: out
29664
- });
29856
+ const [choice, inventory] = await Promise.all([
29857
+ resolveIntakeModel({
29858
+ accountScope,
29859
+ fallbackRegion: state.location,
29860
+ onNarrate: out
29861
+ }),
29862
+ resolveSubscriptionInventory({
29863
+ subscriptionId: state.subscriptionId,
29864
+ installResourceGroup: state.resourceGroup,
29865
+ onNarrate: out
29866
+ })
29867
+ ]);
29665
29868
  return deploySimpleStaceyWithRetry({
29666
29869
  credential: credential2,
29667
29870
  endpoint,
29668
29871
  repoRoot,
29669
29872
  model: choice.model,
29670
- fieldOverrides: {
29671
- founder_identity_note: composeFounderIdentityNote(identity),
29672
- chosen_model_note: choice.note
29673
- },
29873
+ fieldOverrides: buildIntakeFieldOverrides({
29874
+ founderIdentityNote: composeFounderIdentityNote(identity),
29875
+ chosenModelNote: choice.note,
29876
+ inventoryNote: inventory.note
29877
+ }),
29674
29878
  onWait: out
29675
29879
  });
29676
29880
  })().then(