@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 +214 -10
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
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.
|
|
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
|
|
29661
|
-
|
|
29662
|
-
|
|
29663
|
-
|
|
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
|
-
|
|
29672
|
-
|
|
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(
|