@sonnechasser/ntrp 0.1.3 → 0.1.7

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.
@@ -1268,6 +1268,1168 @@ CREATE INDEX IF NOT EXISTS idx_health_readings_batch ON health_readings(upload_b
1268
1268
  }
1269
1269
  });
1270
1270
 
1271
+ // src/config/store.ts
1272
+ var store_exports = {};
1273
+ __export(store_exports, {
1274
+ deleteConfigValue: () => deleteConfigValue,
1275
+ getConfigValue: () => getConfigValue,
1276
+ getExportsDir: () => getExportsDir,
1277
+ getKnowledgeDir: () => getKnowledgeDir,
1278
+ getMemoryDir: () => getMemoryDir,
1279
+ getStrategiesDir: () => getStrategiesDir,
1280
+ getWinsDir: () => getWinsDir,
1281
+ loadConfig: () => loadConfig,
1282
+ ntrpHome: () => ntrpHome,
1283
+ resetConfigCache: () => resetConfigCache,
1284
+ saveConfig: () => saveConfig,
1285
+ setConfigValue: () => setConfigValue
1286
+ });
1287
+ import { readFileSync, writeFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
1288
+ import { homedir } from "os";
1289
+ import { join as join2, resolve as resolve2 } from "path";
1290
+ function ntrpHome() {
1291
+ return NTRP_DIR2;
1292
+ }
1293
+ function ensureDir2() {
1294
+ if (!existsSync2(NTRP_DIR2)) {
1295
+ mkdirSync2(NTRP_DIR2, { recursive: true });
1296
+ }
1297
+ }
1298
+ function loadConfig() {
1299
+ if (cachedConfig) return cachedConfig;
1300
+ ensureDir2();
1301
+ if (!existsSync2(CONFIG_PATH)) {
1302
+ cachedConfig = {};
1303
+ return cachedConfig;
1304
+ }
1305
+ try {
1306
+ cachedConfig = JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
1307
+ } catch {
1308
+ cachedConfig = {};
1309
+ }
1310
+ return cachedConfig;
1311
+ }
1312
+ function saveConfig(config) {
1313
+ ensureDir2();
1314
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n");
1315
+ cachedConfig = config;
1316
+ }
1317
+ function resetConfigCache() {
1318
+ cachedConfig = null;
1319
+ }
1320
+ function getConfigValue(key) {
1321
+ if (key === "api-key") return loadConfig()["api-key"];
1322
+ if (key === "license-key") return process.env.NTRP_LICENSE_KEY ?? loadConfig()["license-key"];
1323
+ const config = loadConfig();
1324
+ return config[key];
1325
+ }
1326
+ function setConfigValue(key, value) {
1327
+ const config = loadConfig();
1328
+ config[key] = value;
1329
+ saveConfig(config);
1330
+ }
1331
+ function deleteConfigValue(key) {
1332
+ const config = loadConfig();
1333
+ delete config[key];
1334
+ saveConfig(config);
1335
+ }
1336
+ function getExportsDir() {
1337
+ const config = loadConfig();
1338
+ const dir = resolve2(config["export-dir"] ?? join2(NTRP_DIR2, "exports"));
1339
+ if (!existsSync2(dir)) {
1340
+ mkdirSync2(dir, { recursive: true });
1341
+ }
1342
+ return dir;
1343
+ }
1344
+ function getStrategiesDir() {
1345
+ const dir = join2(NTRP_DIR2, "strategies");
1346
+ if (!existsSync2(dir)) {
1347
+ mkdirSync2(dir, { recursive: true });
1348
+ writeFileSync(join2(dir, "README.md"), `# Strategies
1349
+
1350
+ This directory holds your GTM strategy files. Each file describes a strategy you're executing.
1351
+
1352
+ ## How to use
1353
+
1354
+ 1. Create a markdown file for each active strategy (e.g., \`multi-thread-q2.md\`)
1355
+ 2. Describe the goal, target segment, and success criteria
1356
+ 3. Reference playbook plays that support this strategy
1357
+ 4. After diagnosis, check if vital signs improved in the targeted area
1358
+
1359
+ ## Example
1360
+
1361
+ \`\`\`markdown
1362
+ # Multi-Thread Enterprise Deals \u2014 Q2
1363
+
1364
+ **Goal:** Reduce single-threaded deals from 65% to under 30%
1365
+ **Segment:** Enterprise accounts > $100K
1366
+ **Play:** Multi-Thread Your Deals
1367
+ **Success metric:** Thread depth score > 70
1368
+ \`\`\`
1369
+ `);
1370
+ }
1371
+ return dir;
1372
+ }
1373
+ function getMemoryDir() {
1374
+ const dir = join2(NTRP_DIR2, "memory");
1375
+ if (!existsSync2(dir)) {
1376
+ mkdirSync2(dir, { recursive: true });
1377
+ }
1378
+ return dir;
1379
+ }
1380
+ function getKnowledgeDir() {
1381
+ const dir = join2(NTRP_DIR2, "knowledge");
1382
+ if (!existsSync2(dir)) {
1383
+ mkdirSync2(dir, { recursive: true });
1384
+ writeFileSync(join2(dir, "README.md"), `# Knowledge Packs
1385
+
1386
+ Drop case studies, GTM frameworks, benchmark reports, or playbooks here as
1387
+ markdown, text, or PDF. NTRP ingests them with \`/knowledge add <file>\` and
1388
+ references the most relevant passages during analysis \u2014 so the agent can learn
1389
+ from work done outside this platform.
1390
+
1391
+ ## How to use
1392
+
1393
+ 1. Add a file: \`/knowledge add ~/Downloads/plg-benchmarks-2026.pdf\`
1394
+ 2. List what's indexed: \`/knowledge list\`
1395
+ 3. Ask a question \u2014 relevant passages are pulled in automatically.
1396
+ `);
1397
+ }
1398
+ return dir;
1399
+ }
1400
+ function getWinsDir() {
1401
+ const dir = join2(NTRP_DIR2, "wins");
1402
+ if (!existsSync2(dir)) {
1403
+ mkdirSync2(dir, { recursive: true });
1404
+ writeFileSync(join2(dir, "README.md"), `# Wins
1405
+
1406
+ This directory logs outcomes when a strategy or play succeeds. Each win creates a record that future diagnoses can reference.
1407
+
1408
+ ## How to use
1409
+
1410
+ 1. After executing a play, log the result here (e.g., \`2026-04-clean-pipeline.md\`)
1411
+ 2. Include: what you did, what changed, before/after scores
1412
+ 3. Future AI findings will reference wins to track improvement over time
1413
+
1414
+ ## Example
1415
+
1416
+ \`\`\`markdown
1417
+ # Pipeline Cleanup \u2014 April 2026
1418
+
1419
+ **Play:** Clean Dead Pipeline
1420
+ **Before:** Freshness 29/100, $3.1M stale pipeline
1421
+ **After:** Freshness 72/100, removed 45 zombie deals
1422
+ **Impact:** Forecast accuracy improved from 62% to 84%
1423
+ \`\`\`
1424
+ `);
1425
+ }
1426
+ return dir;
1427
+ }
1428
+ var NTRP_DIR2, CONFIG_PATH, cachedConfig;
1429
+ var init_store = __esm({
1430
+ "src/config/store.ts"() {
1431
+ "use strict";
1432
+ NTRP_DIR2 = process.env.NTRP_HOME ? resolve2(process.env.NTRP_HOME) : join2(homedir(), ".ntrp");
1433
+ CONFIG_PATH = join2(NTRP_DIR2, "config.json");
1434
+ cachedConfig = null;
1435
+ }
1436
+ });
1437
+
1438
+ // src/config/install.ts
1439
+ import { randomUUID as randomUUID2 } from "crypto";
1440
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
1441
+ import { join as join3 } from "path";
1442
+ function installPath() {
1443
+ return join3(ntrpHome(), "install.json");
1444
+ }
1445
+ function ensureDir3() {
1446
+ const dir = ntrpHome();
1447
+ if (!existsSync3(dir)) {
1448
+ mkdirSync3(dir, { recursive: true });
1449
+ }
1450
+ }
1451
+ function isValidInstall(value) {
1452
+ if (!value || typeof value !== "object") return false;
1453
+ const r = value;
1454
+ return r.schema_version === 1 && typeof r.install_id === "string" && r.install_id.length > 0 && typeof r.created_at === "string";
1455
+ }
1456
+ function ensureInstall() {
1457
+ if (cachedInstall) return cachedInstall;
1458
+ const path = installPath();
1459
+ if (existsSync3(path)) {
1460
+ try {
1461
+ const parsed = JSON.parse(readFileSync2(path, "utf-8"));
1462
+ if (isValidInstall(parsed)) {
1463
+ cachedInstall = parsed;
1464
+ return parsed;
1465
+ }
1466
+ } catch {
1467
+ }
1468
+ }
1469
+ const record = {
1470
+ schema_version: 1,
1471
+ install_id: randomUUID2(),
1472
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
1473
+ };
1474
+ ensureDir3();
1475
+ writeFileSync2(path, JSON.stringify(record, null, 2) + "\n");
1476
+ cachedInstall = record;
1477
+ return record;
1478
+ }
1479
+ function getInstallId() {
1480
+ return ensureInstall().install_id;
1481
+ }
1482
+ var cachedInstall;
1483
+ var init_install = __esm({
1484
+ "src/config/install.ts"() {
1485
+ "use strict";
1486
+ init_store();
1487
+ cachedInstall = null;
1488
+ }
1489
+ });
1490
+
1491
+ // src/config/progress-migrate.ts
1492
+ import { existsSync as existsSync4, readFileSync as readFileSync3, renameSync, writeFileSync as writeFileSync3 } from "fs";
1493
+ import { join as join4 } from "path";
1494
+ function legacyStatePath() {
1495
+ return join4(ntrpHome(), "state.json");
1496
+ }
1497
+ function legacyStateBackupPath() {
1498
+ return join4(ntrpHome(), "state.json.bak");
1499
+ }
1500
+ function progressPath() {
1501
+ return join4(ntrpHome(), "progress.json");
1502
+ }
1503
+ function isValidLegacyState(value) {
1504
+ if (!value || typeof value !== "object") return false;
1505
+ const s = value;
1506
+ return s.schema_version === 1 && typeof s.total_minutes_saved === "number" && Array.isArray(s.credits) && Array.isArray(s.milestones_unlocked);
1507
+ }
1508
+ function migrateLegacyStateIfNeeded(installId) {
1509
+ if (existsSync4(progressPath())) return null;
1510
+ const legacyPath = legacyStatePath();
1511
+ if (!existsSync4(legacyPath)) return null;
1512
+ try {
1513
+ const parsed = JSON.parse(readFileSync3(legacyPath, "utf-8"));
1514
+ if (!isValidLegacyState(parsed)) return null;
1515
+ const { schema_version: _v, ...rest } = parsed;
1516
+ const progress = {
1517
+ ...rest,
1518
+ schema_version: 2,
1519
+ install_id: installId
1520
+ };
1521
+ writeFileSync3(progressPath(), JSON.stringify(progress, null, 2) + "\n");
1522
+ try {
1523
+ renameSync(legacyPath, legacyStateBackupPath());
1524
+ } catch {
1525
+ }
1526
+ return progress;
1527
+ } catch {
1528
+ return null;
1529
+ }
1530
+ }
1531
+ var init_progress_migrate = __esm({
1532
+ "src/config/progress-migrate.ts"() {
1533
+ "use strict";
1534
+ init_store();
1535
+ }
1536
+ });
1537
+
1538
+ // src/whimsy/usage-backfill.ts
1539
+ function isoWeekKey(d) {
1540
+ const date = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
1541
+ const day = date.getUTCDay() || 7;
1542
+ date.setUTCDate(date.getUTCDate() + 4 - day);
1543
+ const yearStart = new Date(Date.UTC(date.getUTCFullYear(), 0, 1));
1544
+ const weekNo = Math.ceil(((date.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
1545
+ return `${date.getUTCFullYear()}-W${String(weekNo).padStart(2, "0")}`;
1546
+ }
1547
+ function actionBase(action) {
1548
+ return action.split(":")[0] ?? action;
1549
+ }
1550
+ function bumpWeekly(weekly, week, patch) {
1551
+ const idx = weekly.findIndex((w) => w.week === week);
1552
+ const row = idx >= 0 ? { ...weekly[idx] } : { week, minutes_saved: 0, llm_calls: 0, actions: 0 };
1553
+ if (patch.minutes_saved) row.minutes_saved += patch.minutes_saved;
1554
+ if (patch.actions) row.actions += patch.actions;
1555
+ return idx >= 0 ? weekly.map((w, i) => i === idx ? row : w) : [...weekly, row];
1556
+ }
1557
+ function rebuildFromCredits(credits) {
1558
+ let diagnoses = 0;
1559
+ let metrics_runs = 0;
1560
+ let deliverables = 0;
1561
+ let nl_exchanges = 0;
1562
+ let weekly = [];
1563
+ let first_active_at;
1564
+ let last_active_at;
1565
+ for (const credit of credits) {
1566
+ if (!first_active_at || credit.at < first_active_at) first_active_at = credit.at;
1567
+ if (!last_active_at || credit.at > last_active_at) last_active_at = credit.at;
1568
+ const base = actionBase(credit.action);
1569
+ if (base === "diagnose" || base === "diagnose_findings") diagnoses++;
1570
+ if (base === "metrics" || base === "metrics_findings") metrics_runs++;
1571
+ if (base === "deliverable" || base === "deliverable_deck") deliverables++;
1572
+ if (base === "nl_answer") nl_exchanges++;
1573
+ const week = isoWeekKey(new Date(credit.at));
1574
+ weekly = bumpWeekly(weekly, week, { minutes_saved: credit.minutes, actions: 1 });
1575
+ }
1576
+ return {
1577
+ first_active_at,
1578
+ last_active_at,
1579
+ diagnoses,
1580
+ metrics_runs,
1581
+ deliverables,
1582
+ nl_exchanges,
1583
+ weekly
1584
+ };
1585
+ }
1586
+ function mergeWeekly(existing, fromCredits) {
1587
+ const byWeek = /* @__PURE__ */ new Map();
1588
+ for (const row of fromCredits) {
1589
+ byWeek.set(row.week, { ...row });
1590
+ }
1591
+ for (const row of existing) {
1592
+ const prior = byWeek.get(row.week);
1593
+ if (prior) {
1594
+ byWeek.set(row.week, {
1595
+ week: row.week,
1596
+ minutes_saved: Math.max(prior.minutes_saved, row.minutes_saved),
1597
+ actions: Math.max(prior.actions, row.actions),
1598
+ llm_calls: row.llm_calls
1599
+ });
1600
+ } else {
1601
+ byWeek.set(row.week, { ...row });
1602
+ }
1603
+ }
1604
+ return [...byWeek.values()].sort((a, b) => a.week.localeCompare(b.week));
1605
+ }
1606
+ function migrateUsageIfNeeded(state) {
1607
+ if (state.credits.length === 0) return { state, changed: false };
1608
+ if (state.usage?.first_active_at) return { state, changed: false };
1609
+ const fromCredits = rebuildFromCredits(state.credits);
1610
+ const prior = state.usage;
1611
+ const usage = {
1612
+ sessions_closed: prior?.sessions_closed ?? 0,
1613
+ llm_calls: prior?.llm_calls ?? 0,
1614
+ input_tokens: prior?.input_tokens ?? 0,
1615
+ output_tokens: prior?.output_tokens ?? 0,
1616
+ ...fromCredits,
1617
+ weekly: mergeWeekly(prior?.weekly ?? [], fromCredits.weekly)
1618
+ };
1619
+ return { state: { ...state, usage }, changed: true };
1620
+ }
1621
+ var init_usage_backfill = __esm({
1622
+ "src/whimsy/usage-backfill.ts"() {
1623
+ "use strict";
1624
+ }
1625
+ });
1626
+
1627
+ // src/config/progress.ts
1628
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
1629
+ import { join as join5 } from "path";
1630
+ function progressPath2() {
1631
+ return join5(ntrpHome(), "progress.json");
1632
+ }
1633
+ function ensureDir4() {
1634
+ const dir = ntrpHome();
1635
+ if (!existsSync5(dir)) {
1636
+ mkdirSync4(dir, { recursive: true });
1637
+ }
1638
+ }
1639
+ function emptyProgress(installId) {
1640
+ return {
1641
+ schema_version: 2,
1642
+ install_id: installId,
1643
+ total_minutes_saved: 0,
1644
+ credits: [],
1645
+ milestones_unlocked: []
1646
+ };
1647
+ }
1648
+ function isValidProgress(value) {
1649
+ if (!value || typeof value !== "object") return false;
1650
+ const s = value;
1651
+ return s.schema_version === 2 && typeof s.install_id === "string" && typeof s.total_minutes_saved === "number" && Array.isArray(s.credits) && Array.isArray(s.milestones_unlocked);
1652
+ }
1653
+ function reconcileInstallId(state) {
1654
+ const localId = getInstallId();
1655
+ if (state.install_id === localId) return { state, changed: false };
1656
+ if (!installMismatchWarned) {
1657
+ installMismatchWarned = true;
1658
+ console.warn(
1659
+ " progress.json install_id did not match this machine \u2014 rebound to local install."
1660
+ );
1661
+ }
1662
+ return { state: { ...state, install_id: localId }, changed: true };
1663
+ }
1664
+ function readProgressFile() {
1665
+ const path = progressPath2();
1666
+ if (!existsSync5(path)) return { state: null, changed: false };
1667
+ try {
1668
+ const parsed = JSON.parse(readFileSync4(path, "utf-8"));
1669
+ if (!isValidProgress(parsed)) return { state: null, changed: false };
1670
+ return reconcileInstallId(parsed);
1671
+ } catch {
1672
+ return { state: null, changed: false };
1673
+ }
1674
+ }
1675
+ function loadProgress() {
1676
+ ensureInstall();
1677
+ const installId = getInstallId();
1678
+ let state = null;
1679
+ let changed = false;
1680
+ const fromFile = readProgressFile();
1681
+ if (fromFile.state) {
1682
+ state = fromFile.state;
1683
+ changed = fromFile.changed;
1684
+ }
1685
+ if (!state) {
1686
+ const migrated2 = migrateLegacyStateIfNeeded(installId);
1687
+ if (migrated2) {
1688
+ state = migrated2;
1689
+ changed = true;
1690
+ }
1691
+ }
1692
+ if (!state) {
1693
+ state = emptyProgress(installId);
1694
+ changed = true;
1695
+ }
1696
+ const { state: usageMigrated, changed: usageChanged } = migrateUsageIfNeeded(state);
1697
+ state = usageMigrated;
1698
+ if (usageChanged) changed = true;
1699
+ if (changed) saveProgress(state);
1700
+ return state;
1701
+ }
1702
+ function saveProgress(state) {
1703
+ ensureDir4();
1704
+ const next = {
1705
+ ...state,
1706
+ schema_version: 2,
1707
+ install_id: getInstallId()
1708
+ };
1709
+ writeFileSync4(progressPath2(), JSON.stringify(next, null, 2) + "\n");
1710
+ }
1711
+ function appendCredit(state, credit) {
1712
+ const credits = [...state.credits, credit];
1713
+ if (credits.length > CREDIT_HISTORY_CAP) {
1714
+ credits.splice(0, credits.length - CREDIT_HISTORY_CAP);
1715
+ }
1716
+ return {
1717
+ ...state,
1718
+ total_minutes_saved: state.total_minutes_saved + credit.minutes,
1719
+ credits
1720
+ };
1721
+ }
1722
+ function hasCreditAction(state, action) {
1723
+ return state.credits.some((c) => c.action === action);
1724
+ }
1725
+ var CREDIT_HISTORY_CAP, installMismatchWarned;
1726
+ var init_progress = __esm({
1727
+ "src/config/progress.ts"() {
1728
+ "use strict";
1729
+ init_install();
1730
+ init_progress_migrate();
1731
+ init_store();
1732
+ init_usage_backfill();
1733
+ CREDIT_HISTORY_CAP = 100;
1734
+ installMismatchWarned = false;
1735
+ }
1736
+ });
1737
+
1738
+ // src/ui/theme.ts
1739
+ import chalk from "chalk";
1740
+ function paint(token, text) {
1741
+ if (token === "dim") return chalk.dim(text);
1742
+ return chalk.hex(TOKENS[token])(text);
1743
+ }
1744
+ function bold(text) {
1745
+ return chalk.bold(text);
1746
+ }
1747
+ function badge(label, tone = "muted") {
1748
+ const normalized = ` ${label.toUpperCase()} `;
1749
+ switch (tone) {
1750
+ case "success":
1751
+ return chalk.hex(TOKENS.success)(normalized);
1752
+ case "warning":
1753
+ return chalk.hex(TOKENS.warning)(normalized);
1754
+ case "error":
1755
+ return chalk.hex(TOKENS.error)(normalized);
1756
+ case "info":
1757
+ return chalk.hex(TOKENS.info)(normalized);
1758
+ case "accent":
1759
+ return chalk.hex(TOKENS.accent)(normalized);
1760
+ case "muted":
1761
+ return chalk.dim(normalized);
1762
+ }
1763
+ }
1764
+ function sectionHeading(label) {
1765
+ return `${paint("accent", "\u25B8")} ${paint("accent", bold(label))}`;
1766
+ }
1767
+ function actionHint(label, command, detail) {
1768
+ const suffix = detail ? chalk.dim(` ${detail}`) : "";
1769
+ return `${chalk.dim(label)} ${paint("accent", command)}${suffix}`;
1770
+ }
1771
+ function scoreBar(score, status, width = 14) {
1772
+ const filled = Math.round(score / 100 * width);
1773
+ const color = chalk.hex(STATUS_COLORS[status]);
1774
+ let filledPart = "";
1775
+ for (let i = 0; i < filled; i++) {
1776
+ filledPart += i % 2 === 0 ? "\u2588" : "\u2593";
1777
+ }
1778
+ const emptyPart = "\u2591".repeat(width - filled);
1779
+ return color(filledPart) + chalk.dim(emptyPart);
1780
+ }
1781
+ var STATUS_COLORS, TOKENS;
1782
+ var init_theme = __esm({
1783
+ "src/ui/theme.ts"() {
1784
+ "use strict";
1785
+ init_formatters();
1786
+ STATUS_COLORS = {
1787
+ green: "#22c55e",
1788
+ yellow: "#eab308",
1789
+ red: "#ef4444"
1790
+ };
1791
+ TOKENS = {
1792
+ accent: "#14b8a6",
1793
+ accentBright: "#2dd4bf",
1794
+ border: "#334155",
1795
+ borderMuted: "#1e293b",
1796
+ dim: "#64748b",
1797
+ text: "#e2e8f0",
1798
+ error: "#ef4444",
1799
+ warning: "#eab308",
1800
+ success: "#22c55e",
1801
+ info: "#3b82f6"
1802
+ };
1803
+ }
1804
+ });
1805
+
1806
+ // src/whimsy/time-milestones.ts
1807
+ function getMilestoneById(id) {
1808
+ return TIME_MILESTONES.find((m) => m.id === id);
1809
+ }
1810
+ function nextMilestone(totalHours, unlocked) {
1811
+ for (const m of TIME_MILESTONES) {
1812
+ if (!unlocked.includes(m.id) && totalHours < m.hours) {
1813
+ return m;
1814
+ }
1815
+ }
1816
+ return null;
1817
+ }
1818
+ function newlyUnlockedMilestones(previousMinutes, newMinutes, unlocked) {
1819
+ const prevHours = previousMinutes / 60;
1820
+ const newHours = newMinutes / 60;
1821
+ return TIME_MILESTONES.filter(
1822
+ (m) => !unlocked.includes(m.id) && newHours >= m.hours && prevHours < m.hours
1823
+ );
1824
+ }
1825
+ var TIME_MILESTONES;
1826
+ var init_time_milestones = __esm({
1827
+ "src/whimsy/time-milestones.ts"() {
1828
+ "use strict";
1829
+ TIME_MILESTONES = [
1830
+ {
1831
+ id: "first_hour",
1832
+ hours: 1,
1833
+ title: "First hour back",
1834
+ message: "First hour back. That's one pipeline standup you didn't have to sit through."
1835
+ },
1836
+ {
1837
+ id: "half_day",
1838
+ hours: 4,
1839
+ title: "Half day",
1840
+ message: "4 hours saved \u2014 a half-day an analyst would've billed you for."
1841
+ },
1842
+ {
1843
+ id: "analyst_day",
1844
+ hours: 8,
1845
+ title: "Analyst day",
1846
+ message: "A full analyst day, reclaimed."
1847
+ },
1848
+ {
1849
+ id: "long_weekend",
1850
+ hours: 24,
1851
+ title: "Three days",
1852
+ message: "Three analyst days. You could've been in spreadsheets."
1853
+ },
1854
+ {
1855
+ id: "analyst_week",
1856
+ hours: 40,
1857
+ title: "Analyst week",
1858
+ message: "A week of analyst time. Your calendar thanks you."
1859
+ },
1860
+ {
1861
+ id: "analyst_fortnight",
1862
+ hours: 80,
1863
+ title: "Two weeks",
1864
+ message: "Two weeks of manual pipeline archaeology \u2014 skipped."
1865
+ },
1866
+ {
1867
+ id: "analyst_month",
1868
+ hours: 160,
1869
+ title: "Analyst month",
1870
+ message: "A month of analyst hours. That's a hiring conversation you didn't need."
1871
+ },
1872
+ {
1873
+ id: "quarter_fte",
1874
+ hours: 500,
1875
+ title: "Quarter FTE",
1876
+ message: "500 hours. That's a quarter of a full-time analyst year."
1877
+ },
1878
+ {
1879
+ id: "two_quarters",
1880
+ hours: 600,
1881
+ title: "Two quarters",
1882
+ message: "600 hours \u2014 half a fiscal year of analyst time, back in your calendar."
1883
+ },
1884
+ {
1885
+ id: "nine_months",
1886
+ hours: 720,
1887
+ title: "Nine months",
1888
+ message: "720 hours. Three quarters of a year \u2014 most teams never get this much outside help."
1889
+ },
1890
+ {
1891
+ id: "eleven_months",
1892
+ hours: 840,
1893
+ title: "Eleven months",
1894
+ message: "840 hours saved. You're one month shy of a full annual arc."
1895
+ },
1896
+ {
1897
+ id: "annual_arc",
1898
+ hours: 960,
1899
+ title: "Annual arc",
1900
+ message: "960 hours \u2014 a year of normal use, banked. The subscription paid for itself."
1901
+ },
1902
+ {
1903
+ id: "subscription_year",
1904
+ hours: 1100,
1905
+ title: "Subscription year",
1906
+ message: "1,100 hours. A full year plus wiggle room \u2014 even power users rarely climb higher."
1907
+ }
1908
+ ];
1909
+ }
1910
+ });
1911
+
1912
+ // src/whimsy/time-perspectives.ts
1913
+ function getPerspectiveById(id) {
1914
+ return TIME_PERSPECTIVES.find((p) => p.id === id);
1915
+ }
1916
+ function ratioInBand(perspective, totalHours) {
1917
+ const ratio = totalHours / perspective.reference_hours;
1918
+ const min = perspective.min_ratio ?? 0.3;
1919
+ const max = perspective.max_ratio ?? 300;
1920
+ return ratio >= min && ratio <= max;
1921
+ }
1922
+ function pickPerspective(totalHours, options = {}) {
1923
+ if (totalHours <= 0) return null;
1924
+ const exclude = new Set(options.excludeIds ?? []);
1925
+ let candidates = TIME_PERSPECTIVES.filter((p) => !exclude.has(p.id) && ratioInBand(p, totalHours));
1926
+ if (candidates.length === 0) {
1927
+ candidates = TIME_PERSPECTIVES.filter((p) => !exclude.has(p.id));
1928
+ }
1929
+ if (candidates.length === 0) return TIME_PERSPECTIVES[0] ?? null;
1930
+ const otherCategories = candidates.filter((p) => p.category !== options.lastCategory);
1931
+ const pool = otherCategories.length > 0 ? otherCategories : candidates;
1932
+ const seed = options.seed ?? Date.now();
1933
+ return pool[Math.abs(seed) % pool.length] ?? null;
1934
+ }
1935
+ function formatRatio(ratio) {
1936
+ if (ratio >= 100) return Math.round(ratio).toString();
1937
+ if (ratio >= 10) return ratio.toFixed(0);
1938
+ if (ratio >= 1) return ratio.toFixed(1);
1939
+ return ratio.toFixed(2);
1940
+ }
1941
+ function formatPct(pct) {
1942
+ if (pct >= 10) return Math.round(pct).toString();
1943
+ if (pct >= 1) return pct.toFixed(1);
1944
+ return pct.toFixed(2);
1945
+ }
1946
+ function formatPerspectiveLine(perspective, totalHours) {
1947
+ const ratio = totalHours / perspective.reference_hours;
1948
+ const pct = ratio * 100;
1949
+ return perspective.template.replace("{ratio}", formatRatio(ratio)).replace("{pct}", formatPct(pct)).replace("{label}", perspective.label);
1950
+ }
1951
+ var TIME_PERSPECTIVES;
1952
+ var init_time_perspectives = __esm({
1953
+ "src/whimsy/time-perspectives.ts"() {
1954
+ "use strict";
1955
+ TIME_PERSPECTIVES = [
1956
+ { id: "dsotm", category: "music", reference_hours: 0.74, label: "Dark Side of the Moon", template: "\u2248 {ratio}\xD7 through {label}", min_ratio: 1, max_ratio: 200 },
1957
+ { id: "rush_2112", category: "music", reference_hours: 0.33, label: "2112", template: "\u2248 {ratio}\xD7 through {label}", min_ratio: 2, max_ratio: 200 },
1958
+ { id: "bohemian_rhapsody", category: "music", reference_hours: 0.1, label: "Bohemian Rhapsody", template: "\u2248 {ratio}\xD7 through {label}", min_ratio: 5, max_ratio: 500 },
1959
+ { id: "stairway", category: "music", reference_hours: 0.13, label: "Stairway to Heaven", template: "\u2248 {ratio}\xD7 through {label}", min_ratio: 5, max_ratio: 400 },
1960
+ { id: "podcast_binge", category: "music", reference_hours: 0.75, label: "hour-long podcast episodes", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 2, max_ratio: 300 },
1961
+ { id: "abbey_road", category: "music", reference_hours: 0.8, label: "Abbey Road", template: "\u2248 {ratio}\xD7 through {label}", min_ratio: 1, max_ratio: 200 },
1962
+ { id: "iron_maiden_set", category: "music", reference_hours: 2, label: "an Iron Maiden marathon set", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 0.5, max_ratio: 50 },
1963
+ { id: "festival_set", category: "music", reference_hours: 1.5, label: "main-stage festival sets", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 1, max_ratio: 100 },
1964
+ { id: "jazz_club", category: "music", reference_hours: 3, label: "late-night jazz sets", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 0.5, max_ratio: 80 },
1965
+ { id: "ring_cycle", category: "music", reference_hours: 15, label: "Wagner's Ring Cycle", template: "\u2248 {pct}% of {label}", min_ratio: 0.1, max_ratio: 2 },
1966
+ { id: "shrek", category: "film", reference_hours: 1.5, label: "Shrek (the first one)", template: "\u2248 {ratio}\xD7 watching {label}", min_ratio: 1, max_ratio: 150 },
1967
+ { id: "blockbuster", category: "film", reference_hours: 2.1, label: "average blockbusters", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 1, max_ratio: 100 },
1968
+ { id: "dune_two", category: "film", reference_hours: 2.75, label: "Dune: Part Two", template: "\u2248 {ratio}\xD7 in theater for {label}", min_ratio: 1, max_ratio: 100 },
1969
+ { id: "scorsese", category: "film", reference_hours: 3.5, label: "Goodfellas", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 1, max_ratio: 80 },
1970
+ { id: "godfather", category: "film", reference_hours: 6.5, label: "the Godfather saga", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 0.5, max_ratio: 20 },
1971
+ { id: "lotr_extended", category: "film", reference_hours: 11.4, label: "the LOTR extended trilogy", template: "Longer than all of {label}", min_ratio: 1, max_ratio: 50 },
1972
+ { id: "cooking_brisket", category: "film", reference_hours: 12, label: "low-and-slow brisket cooks", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 0.3, max_ratio: 30 },
1973
+ { id: "the_office", category: "film", reference_hours: 68, label: "The Office (full series)", template: "\u2248 {ratio}\xD7 bingeing {label}", min_ratio: 5, max_ratio: 200 },
1974
+ { id: "marvel_marathon", category: "film", reference_hours: 50, label: "an MCU Phase One marathon", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 0.3, max_ratio: 30 },
1975
+ { id: "around_world", category: "film", reference_hours: 1920, label: "Around the World in 80 Days (fictionally)", template: "\u2248 {pct}% of {label}", min_ratio: 0.3, max_ratio: 1 },
1976
+ { id: "soccer_match", category: "sports", reference_hours: 1.75, label: "Premier League matches", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 1, max_ratio: 150 },
1977
+ { id: "marathon", category: "sports", reference_hours: 2, label: "marathons at world-record pace", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 1, max_ratio: 80 },
1978
+ { id: "baseball_game", category: "sports", reference_hours: 3, label: "nine-inning baseball games", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 0.5, max_ratio: 80 },
1979
+ { id: "superbowl", category: "sports", reference_hours: 3.5, label: "Super Bowls", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 0.5, max_ratio: 80 },
1980
+ { id: "nfl_game", category: "sports", reference_hours: 3.25, label: "NFL games (with commercials)", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 0.5, max_ratio: 50 },
1981
+ { id: "wimbledon", category: "sports", reference_hours: 5, label: "Wimbledon finals", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 0.3, max_ratio: 30 },
1982
+ { id: "tour_stage", category: "sports", reference_hours: 4.5, label: "Tour de France stages", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 0.5, max_ratio: 60 },
1983
+ { id: "olympics", category: "sports", reference_hours: 250, label: "Summer Olympics broadcast hours", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 2, max_ratio: 10 },
1984
+ { id: "moon_light", category: "cosmos", reference_hours: 1.3 / 3600, label: "a beam of light Earth \u2192 Moon", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 1e3, max_ratio: 1e6 },
1985
+ { id: "iss_orbit", category: "cosmos", reference_hours: 1.5, label: "ISS orbits", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 2, max_ratio: 200 },
1986
+ { id: "light_sun", category: "cosmos", reference_hours: 8.3, label: "solar light crossing to Earth", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 0.5, max_ratio: 200 },
1987
+ { id: "sleep_cycle", category: "cosmos", reference_hours: 8, label: "full nights of sleep", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 0.5, max_ratio: 150 },
1988
+ { id: "red_eye", category: "cosmos", reference_hours: 5.5, label: "transcontinental red-eyes", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 0.5, max_ratio: 100 },
1989
+ { id: "mayfly", category: "cosmos", reference_hours: 24, label: "a mayfly's entire adult life", template: "\u2248 {pct}% of {label}", min_ratio: 0.1, max_ratio: 2 },
1990
+ { id: "earth_rotation", category: "cosmos", reference_hours: 24, label: "Earth rotations", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 0.1, max_ratio: 50 },
1991
+ { id: "jupiter_storm", category: "cosmos", reference_hours: 150, label: "Jupiter's Great Red Spot rotation", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 0.3, max_ratio: 15 },
1992
+ { id: "lunar_month", category: "cosmos", reference_hours: 708, label: "a lunar cycle", template: "\u2248 {pct}% of {label}", min_ratio: 0.05, max_ratio: 2 },
1993
+ { id: "mars_transit", category: "cosmos", reference_hours: 5110, label: "a one-way Mars transit (optimistic)", template: "\u2248 {pct}% of {label}", min_ratio: 1e-3, max_ratio: 5 },
1994
+ { id: "calendar_year", category: "cosmos", reference_hours: 8760, label: "all the hours in a calendar year", template: "\u2248 {pct}% of {label}", min_ratio: 0.05, max_ratio: 0.2 },
1995
+ { id: "standup", category: "gtm", reference_hours: 0.25, label: "daily standups", template: "\u2248 {ratio}\xD7 skipped {label}", min_ratio: 4, max_ratio: 500 },
1996
+ { id: "quick_sync", category: "gtm", reference_hours: 0.5, label: "avoided 'quick syncs'", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 2, max_ratio: 200 },
1997
+ { id: "pipeline_review", category: "gtm", reference_hours: 1, label: "weekly pipeline reviews", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 1, max_ratio: 200 },
1998
+ { id: "forecast_call", category: "gtm", reference_hours: 1.5, label: "forecast calls", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 1, max_ratio: 150 },
1999
+ { id: "pivot_spiral", category: "gtm", reference_hours: 2, label: "spreadsheet pivot-table spirals", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 1, max_ratio: 100 },
2000
+ { id: "win_loss", category: "gtm", reference_hours: 4, label: "win/loss interview blocks", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 0.5, max_ratio: 50 },
2001
+ { id: "crm_cleanup", category: "gtm", reference_hours: 6, label: "CRM hygiene sprints", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 0.5, max_ratio: 40 },
2002
+ { id: "qbr_prep", category: "gtm", reference_hours: 8, label: "QBR prep blocks", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 0.3, max_ratio: 20 },
2003
+ { id: "board_deck", category: "gtm", reference_hours: 12, label: "board deck builds", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 0.3, max_ratio: 30 },
2004
+ { id: "semester", category: "gtm", reference_hours: 400, label: "a college semester of analyst coverage", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 1, max_ratio: 5 },
2005
+ { id: "business_year", category: "gtm", reference_hours: 2e3, label: "a full-time analyst year", template: "\u2248 {pct}% of {label}", min_ratio: 0.2, max_ratio: 1 }
2006
+ ];
2007
+ }
2008
+ });
2009
+
2010
+ // src/whimsy/time-bank-whimsy.ts
2011
+ function formatHours(h) {
2012
+ if (h < 1) return `${Math.round(h * 60)}m`;
2013
+ if (h < 10) return `${h.toFixed(1)}h`;
2014
+ return `${Math.round(h)}h`;
2015
+ }
2016
+ function randomNearMilestoneGoodbye(hoursSaved, hoursToNext, nextTitle) {
2017
+ const pool = NEAR_MILESTONE_GOODBYES;
2018
+ const fn = pool[Math.floor(Math.random() * pool.length)] ?? pool[0];
2019
+ return fn(hoursSaved, hoursToNext, nextTitle);
2020
+ }
2021
+ var NEAR_MILESTONE_GOODBYES;
2022
+ var init_time_bank_whimsy = __esm({
2023
+ "src/whimsy/time-bank-whimsy.ts"() {
2024
+ "use strict";
2025
+ NEAR_MILESTONE_GOODBYES = [
2026
+ (saved, toGo, next) => `${formatHours(saved)} saved \u2014 ${formatHours(toGo)} from ${next}. Almost there.`,
2027
+ (saved, toGo, next) => `${formatHours(saved)} in the bank. One more push hits ${next}.`,
2028
+ (saved, _toGo, next) => `You're at ${formatHours(saved)}. ${next} is right around the corner.`,
2029
+ (saved, toGo, next) => `${formatHours(toGo)} to ${next}. You've already banked ${formatHours(saved)}.`,
2030
+ (saved, _toGo, next) => `Close \u2014 ${formatHours(saved)} saved and ${next} is within reach.`
2031
+ ];
2032
+ }
2033
+ });
2034
+
2035
+ // src/whimsy/perspective-rotation.ts
2036
+ function perspectiveRotationDue(state, now2 = Date.now()) {
2037
+ const perspectiveId = state.perspective_id ?? state.last_perspective_id;
2038
+ if (!perspectiveId) return true;
2039
+ const rotatedAt = state.perspective_rotated_at ? Date.parse(state.perspective_rotated_at) : 0;
2040
+ const minutesAtRotation = state.perspective_minutes_at_rotation ?? 0;
2041
+ const creditedSince = state.total_minutes_saved - minutesAtRotation;
2042
+ const msSince = rotatedAt > 0 ? now2 - rotatedAt : PERSPECTIVE_ROTATE_CALENDAR_MS;
2043
+ return creditedSince >= PERSPECTIVE_ROTATE_CREDIT_MINUTES || msSince >= PERSPECTIVE_ROTATE_CALENDAR_MS;
2044
+ }
2045
+ function rotationSeed(state) {
2046
+ const epoch = state.perspective_minutes_at_rotation ?? state.total_minutes_saved;
2047
+ const count = state.perspective_rotation_count ?? 0;
2048
+ return epoch * 31 + count * 17;
2049
+ }
2050
+ function bumpRecentPerspectiveIds(recent, id) {
2051
+ const next = [...(recent ?? []).filter((x) => x !== id), id];
2052
+ if (next.length > PERSPECTIVE_EXCLUDE_RECENT) {
2053
+ next.splice(0, next.length - PERSPECTIVE_EXCLUDE_RECENT);
2054
+ }
2055
+ return next;
2056
+ }
2057
+ var PERSPECTIVE_ROTATE_CREDIT_MINUTES, PERSPECTIVE_ROTATE_CALENDAR_MS, PERSPECTIVE_EXCLUDE_RECENT;
2058
+ var init_perspective_rotation = __esm({
2059
+ "src/whimsy/perspective-rotation.ts"() {
2060
+ "use strict";
2061
+ PERSPECTIVE_ROTATE_CREDIT_MINUTES = 180;
2062
+ PERSPECTIVE_ROTATE_CALENDAR_MS = 7 * 24 * 60 * 60 * 1e3;
2063
+ PERSPECTIVE_EXCLUDE_RECENT = 6;
2064
+ }
2065
+ });
2066
+
2067
+ // src/whimsy/usage-stats.ts
2068
+ var usage_stats_exports = {};
2069
+ __export(usage_stats_exports, {
2070
+ buildUsageSummary: () => buildUsageSummary,
2071
+ getUsageStats: () => getUsageStats,
2072
+ isoWeekKey: () => isoWeekKey2,
2073
+ recordLlmUsage: () => recordLlmUsage,
2074
+ recordSessionClosed: () => recordSessionClosed,
2075
+ recordUsageFromCredit: () => recordUsageFromCredit
2076
+ });
2077
+ function emptyUsage() {
2078
+ return {
2079
+ sessions_closed: 0,
2080
+ diagnoses: 0,
2081
+ metrics_runs: 0,
2082
+ deliverables: 0,
2083
+ nl_exchanges: 0,
2084
+ llm_calls: 0,
2085
+ input_tokens: 0,
2086
+ output_tokens: 0,
2087
+ weekly: []
2088
+ };
2089
+ }
2090
+ function ensureUsage(state) {
2091
+ return state.usage ?? emptyUsage();
2092
+ }
2093
+ function isoWeekKey2(d = /* @__PURE__ */ new Date()) {
2094
+ const date = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
2095
+ const day = date.getUTCDay() || 7;
2096
+ date.setUTCDate(date.getUTCDate() + 4 - day);
2097
+ const yearStart = new Date(Date.UTC(date.getUTCFullYear(), 0, 1));
2098
+ const weekNo = Math.ceil(((date.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
2099
+ return `${date.getUTCFullYear()}-W${String(weekNo).padStart(2, "0")}`;
2100
+ }
2101
+ function bumpWeekly2(weekly, patch) {
2102
+ const week = patch.week ?? isoWeekKey2();
2103
+ const idx = weekly.findIndex((w) => w.week === week);
2104
+ const row = idx >= 0 ? { ...weekly[idx] } : { week, minutes_saved: 0, llm_calls: 0, actions: 0 };
2105
+ if (patch.minutes_saved) row.minutes_saved += patch.minutes_saved;
2106
+ if (patch.llm_calls) row.llm_calls += patch.llm_calls;
2107
+ if (patch.actions) row.actions += patch.actions;
2108
+ const next = idx >= 0 ? weekly.map((w, i) => i === idx ? row : w) : [...weekly, row];
2109
+ if (next.length > WEEKLY_CAP) next.splice(0, next.length - WEEKLY_CAP);
2110
+ return next;
2111
+ }
2112
+ function touchUsage(state, patch) {
2113
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
2114
+ const usage = ensureUsage(state);
2115
+ return {
2116
+ ...state,
2117
+ usage: {
2118
+ ...usage,
2119
+ ...patch,
2120
+ first_active_at: usage.first_active_at ?? now2,
2121
+ last_active_at: now2,
2122
+ weekly: patch.weekly ?? usage.weekly
2123
+ }
2124
+ };
2125
+ }
2126
+ function recordUsageFromCredit(action, minutes) {
2127
+ if (minutes <= 0) return;
2128
+ let state = loadProgress();
2129
+ const usage = ensureUsage(state);
2130
+ const weekly = bumpWeekly2(usage.weekly, { minutes_saved: minutes, actions: 1 });
2131
+ const counters = { weekly };
2132
+ if (action === "diagnose" || action === "diagnose_findings") counters.diagnoses = usage.diagnoses + 1;
2133
+ if (action === "metrics" || action === "metrics_findings") counters.metrics_runs = usage.metrics_runs + 1;
2134
+ if (action === "deliverable" || action === "deliverable_deck") counters.deliverables = usage.deliverables + 1;
2135
+ if (action === "nl_answer") counters.nl_exchanges = usage.nl_exchanges + 1;
2136
+ state = touchUsage(state, counters);
2137
+ saveProgress(state);
2138
+ }
2139
+ function recordSessionClosed() {
2140
+ let state = loadProgress();
2141
+ const usage = ensureUsage(state);
2142
+ state = touchUsage(state, {
2143
+ sessions_closed: usage.sessions_closed + 1,
2144
+ weekly: bumpWeekly2(usage.weekly, { actions: 1 })
2145
+ });
2146
+ saveProgress(state);
2147
+ }
2148
+ function recordLlmUsage(tokenUsage) {
2149
+ let state = loadProgress();
2150
+ const usage = ensureUsage(state);
2151
+ const weekly = bumpWeekly2(usage.weekly, { llm_calls: 1 });
2152
+ state = touchUsage(state, {
2153
+ llm_calls: usage.llm_calls + 1,
2154
+ input_tokens: usage.input_tokens + (tokenUsage?.input_tokens ?? 0),
2155
+ output_tokens: usage.output_tokens + (tokenUsage?.output_tokens ?? 0),
2156
+ weekly
2157
+ });
2158
+ saveProgress(state);
2159
+ }
2160
+ function getUsageStats() {
2161
+ return ensureUsage(loadProgress());
2162
+ }
2163
+ function buildUsageSummary(sessionCounts, totalHours, milestonesUnlocked, milestoneTotal) {
2164
+ return {
2165
+ usage: getUsageStats(),
2166
+ total_sessions_on_disk: sessionCounts.total,
2167
+ sessions_with_work: sessionCounts.withWork,
2168
+ total_hours_saved: totalHours,
2169
+ milestones_unlocked: milestonesUnlocked,
2170
+ milestone_total: milestoneTotal
2171
+ };
2172
+ }
2173
+ var WEEKLY_CAP;
2174
+ var init_usage_stats = __esm({
2175
+ "src/whimsy/usage-stats.ts"() {
2176
+ "use strict";
2177
+ init_progress();
2178
+ WEEKLY_CAP = 52;
2179
+ }
2180
+ });
2181
+
2182
+ // src/whimsy/time-bank.ts
2183
+ var time_bank_exports = {};
2184
+ __export(time_bank_exports, {
2185
+ creditDeliverable: () => creditDeliverable,
2186
+ creditDiagnoseComplete: () => creditDiagnoseComplete,
2187
+ creditGapCompute: () => creditGapCompute,
2188
+ creditMetricsComplete: () => creditMetricsComplete,
2189
+ creditNlAnswer: () => creditNlAnswer,
2190
+ creditOnboardComplete: () => creditOnboardComplete,
2191
+ creditSessionDeliverableWrapup: () => creditSessionDeliverableWrapup,
2192
+ formatHoursLabel: () => formatHoursLabel,
2193
+ getTimeBankSummary: () => getTimeBankSummary,
2194
+ isNearNextMilestone: () => isNearNextMilestone,
2195
+ loadTimeBankState: () => loadTimeBankState,
2196
+ pickGoodbyeWithTimeBank: () => pickGoodbyeWithTimeBank,
2197
+ printTimeBankCelebration: () => printTimeBankCelebration,
2198
+ recordTimeCredit: () => recordTimeCredit
2199
+ });
2200
+ import chalk2 from "chalk";
2201
+ function actionKey(action, ctx, suffix) {
2202
+ const sessionScoped = /* @__PURE__ */ new Set([
2203
+ "gap_compute",
2204
+ "diagnose",
2205
+ "diagnose_findings",
2206
+ "metrics",
2207
+ "metrics_findings",
2208
+ "deliverable",
2209
+ "deliverable_deck",
2210
+ "session_deliverable_wrapup",
2211
+ "nl_answer"
2212
+ ]);
2213
+ if (sessionScoped.has(action) && ctx?.sessionId) {
2214
+ return suffix ? `${action}:${ctx.sessionId}:${suffix}` : `${action}:${ctx.sessionId}`;
2215
+ }
2216
+ return action;
2217
+ }
2218
+ function shouldSkip(ctx) {
2219
+ return !ctx || ctx.oneShot;
2220
+ }
2221
+ function recordTimeCredit(action, ctx, opts) {
2222
+ if (shouldSkip(ctx)) return null;
2223
+ const minutes = ACTION_MINUTES[action];
2224
+ if (!minutes || minutes <= 0) return null;
2225
+ const key = actionKey(action, ctx, opts?.suffix);
2226
+ let state = loadProgress();
2227
+ if (hasCreditAction(state, key)) {
2228
+ return { credited_minutes: 0, new_milestones: [], total_minutes: state.total_minutes_saved };
2229
+ }
2230
+ const previousMinutes = state.total_minutes_saved;
2231
+ const credit = {
2232
+ action: key,
2233
+ minutes,
2234
+ at: (/* @__PURE__ */ new Date()).toISOString(),
2235
+ session_id: ctx?.sessionId
2236
+ };
2237
+ state = appendCredit(state, credit);
2238
+ const unlocked = newlyUnlockedMilestones(
2239
+ previousMinutes,
2240
+ state.total_minutes_saved,
2241
+ state.milestones_unlocked
2242
+ );
2243
+ if (unlocked.length > 0) {
2244
+ state = {
2245
+ ...state,
2246
+ milestones_unlocked: [...state.milestones_unlocked, ...unlocked.map((m) => m.id)]
2247
+ };
2248
+ }
2249
+ saveProgress(state);
2250
+ if (minutes > 0) {
2251
+ recordUsageFromCredit(action, minutes);
2252
+ state = maybeRotatePerspective(state, state.total_minutes_saved / 60);
2253
+ saveProgress(state);
2254
+ }
2255
+ if (!opts?.silent && unlocked.length > 0) {
2256
+ for (const m of unlocked) {
2257
+ printTimeBankCelebration(m, state.total_minutes_saved);
2258
+ }
2259
+ }
2260
+ return {
2261
+ credited_minutes: minutes,
2262
+ new_milestones: unlocked,
2263
+ total_minutes: state.total_minutes_saved
2264
+ };
2265
+ }
2266
+ function creditGapCompute(ctx) {
2267
+ recordTimeCredit("gap_compute", ctx);
2268
+ if (!hasCreditAction(loadProgress(), "gap_compute_first_ever")) {
2269
+ recordTimeCredit("gap_compute_first_ever", ctx);
2270
+ }
2271
+ }
2272
+ function creditDiagnoseComplete(ctx, withFindings) {
2273
+ recordTimeCredit("diagnose", ctx);
2274
+ if (withFindings) {
2275
+ recordTimeCredit("diagnose_findings", ctx);
2276
+ }
2277
+ }
2278
+ function creditMetricsComplete(ctx, withFindings) {
2279
+ recordTimeCredit("metrics", ctx);
2280
+ if (withFindings) {
2281
+ recordTimeCredit("metrics_findings", ctx);
2282
+ }
2283
+ }
2284
+ function creditDeliverable(ctx, target) {
2285
+ recordTimeCredit("deliverable", ctx);
2286
+ if (target === "deck") {
2287
+ recordTimeCredit("deliverable_deck", ctx);
2288
+ }
2289
+ }
2290
+ function creditNlAnswer(ctx, exchangeIndex) {
2291
+ recordTimeCredit("nl_answer", ctx, { suffix: String(exchangeIndex) });
2292
+ }
2293
+ function creditOnboardComplete(ctx) {
2294
+ recordTimeCredit("onboard", ctx);
2295
+ }
2296
+ function creditSessionDeliverableWrapup(ctx) {
2297
+ recordTimeCredit("session_deliverable_wrapup", ctx);
2298
+ }
2299
+ function activePerspectiveId(state) {
2300
+ return state.perspective_id ?? state.last_perspective_id;
2301
+ }
2302
+ function maybeRotatePerspective(state, totalHours) {
2303
+ const currentId = activePerspectiveId(state);
2304
+ const current = currentId ? getPerspectiveById(currentId) : void 0;
2305
+ const staleBand = current && !ratioInBand2(current, totalHours);
2306
+ if (!perspectiveRotationDue(state) && current && !staleBand) {
2307
+ return state;
2308
+ }
2309
+ const lastCategory = current?.category;
2310
+ const picked = pickPerspective(totalHours, {
2311
+ excludeIds: state.recent_perspective_ids ?? [],
2312
+ lastCategory,
2313
+ seed: rotationSeed(state)
2314
+ });
2315
+ if (!picked) return state;
2316
+ return {
2317
+ ...state,
2318
+ perspective_id: picked.id,
2319
+ last_perspective_id: picked.id,
2320
+ perspective_rotated_at: (/* @__PURE__ */ new Date()).toISOString(),
2321
+ perspective_minutes_at_rotation: state.total_minutes_saved,
2322
+ perspective_rotation_count: (state.perspective_rotation_count ?? 0) + 1,
2323
+ recent_perspective_ids: bumpRecentPerspectiveIds(state.recent_perspective_ids, picked.id)
2324
+ };
2325
+ }
2326
+ function ratioInBand2(perspective, totalHours) {
2327
+ const ratio = totalHours / perspective.reference_hours;
2328
+ const min = perspective.min_ratio ?? 0.3;
2329
+ const max = perspective.max_ratio ?? 300;
2330
+ return ratio >= min && ratio <= max;
2331
+ }
2332
+ function getTimeBankSummary() {
2333
+ let state = loadProgress();
2334
+ const total_minutes = state.total_minutes_saved;
2335
+ const total_hours = total_minutes / 60;
2336
+ if (total_minutes > 0) {
2337
+ state = maybeRotatePerspective(state, total_hours);
2338
+ saveProgress(state);
2339
+ }
2340
+ const next = nextMilestone(total_hours, state.milestones_unlocked);
2341
+ let progress_pct = 100;
2342
+ if (next) {
2343
+ const prevMilestone = state.milestones_unlocked.length > 0 ? getMilestoneById(state.milestones_unlocked[state.milestones_unlocked.length - 1]) : void 0;
2344
+ const prevHours = prevMilestone?.hours ?? 0;
2345
+ const span = next.hours - prevHours;
2346
+ progress_pct = span > 0 ? Math.min(100, (total_hours - prevHours) / span * 100) : 0;
2347
+ }
2348
+ const perspectiveId = activePerspectiveId(state);
2349
+ const perspective = perspectiveId ? getPerspectiveById(perspectiveId) : null;
2350
+ const perspective_line = perspective ? formatPerspectiveLine(perspective, total_hours) : null;
2351
+ return {
2352
+ total_hours,
2353
+ total_minutes,
2354
+ next_milestone: next,
2355
+ progress_pct,
2356
+ perspective_line
2357
+ };
2358
+ }
2359
+ function printTimeBankCelebration(milestone, totalMinutes) {
2360
+ const totalHours = totalMinutes / 60;
2361
+ const state = loadProgress();
2362
+ const perspective = pickPerspective(totalHours, {
2363
+ excludeIds: state.recent_perspective_ids ?? [],
2364
+ seed: rotationSeed(state) + 1
2365
+ });
2366
+ console.log();
2367
+ console.log(" " + paint("accent", `\u2726 ${milestone.title}`) + chalk2.dim(` \u2014 ${formatHoursLabel(totalHours)} saved`));
2368
+ console.log(" " + chalk2.dim(milestone.message));
2369
+ if (perspective) {
2370
+ console.log(" " + chalk2.dim.italic(formatPerspectiveLine(perspective, totalHours)));
2371
+ }
2372
+ console.log();
2373
+ }
2374
+ function formatHoursLabel(hours) {
2375
+ if (hours < 1) return `${Math.round(hours * 60)}m`;
2376
+ if (hours < 10) return `${hours.toFixed(1)}h`;
2377
+ if (hours >= 1e3) return `${Math.round(hours).toLocaleString("en-US")}h`;
2378
+ return `${Math.round(hours)}h`;
2379
+ }
2380
+ function isNearNextMilestone(threshold = 0.15) {
2381
+ const state = loadProgress();
2382
+ if (state.total_minutes_saved <= 0) return false;
2383
+ const totalHours = state.total_minutes_saved / 60;
2384
+ const next = nextMilestone(totalHours, state.milestones_unlocked);
2385
+ if (!next) return false;
2386
+ const prev = state.milestones_unlocked.map((id) => getMilestoneById(id)).filter((m) => !!m).sort((a, b) => b.hours - a.hours)[0];
2387
+ const prevHours = prev?.hours ?? 0;
2388
+ const span = next.hours - prevHours;
2389
+ if (span <= 0) return false;
2390
+ const progress = (totalHours - prevHours) / span;
2391
+ return progress >= 1 - threshold;
2392
+ }
2393
+ function pickGoodbyeWithTimeBank() {
2394
+ if (Math.random() > 0.25) return null;
2395
+ if (!isNearNextMilestone()) return null;
2396
+ const state = loadProgress();
2397
+ const totalHours = state.total_minutes_saved / 60;
2398
+ const next = nextMilestone(totalHours, state.milestones_unlocked);
2399
+ if (!next) return null;
2400
+ const hoursToNext = Math.max(0, next.hours - totalHours);
2401
+ return randomNearMilestoneGoodbye(totalHours, hoursToNext, next.title);
2402
+ }
2403
+ function loadTimeBankState() {
2404
+ return loadProgress();
2405
+ }
2406
+ var ACTION_MINUTES;
2407
+ var init_time_bank = __esm({
2408
+ "src/whimsy/time-bank.ts"() {
2409
+ "use strict";
2410
+ init_progress();
2411
+ init_theme();
2412
+ init_time_milestones();
2413
+ init_time_perspectives();
2414
+ init_time_bank_whimsy();
2415
+ init_perspective_rotation();
2416
+ init_usage_stats();
2417
+ ACTION_MINUTES = {
2418
+ gap_compute: 30,
2419
+ gap_compute_first_ever: 30,
2420
+ diagnose: 180,
2421
+ diagnose_findings: 60,
2422
+ metrics: 120,
2423
+ metrics_findings: 60,
2424
+ deliverable: 240,
2425
+ deliverable_deck: 120,
2426
+ nl_answer: 15,
2427
+ onboard: 30,
2428
+ session_deliverable_wrapup: 30
2429
+ };
2430
+ }
2431
+ });
2432
+
1271
2433
  // src/cli/context.ts
1272
2434
  var context_exports = {};
1273
2435
  __export(context_exports, {
@@ -1304,10 +2466,10 @@ __export(context_exports, {
1304
2466
  saveSessionState: () => saveSessionState,
1305
2467
  setPrimaryLens: () => setPrimaryLens
1306
2468
  });
1307
- import { basename, join as join2, resolve as resolve2, sep } from "path";
1308
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync, readFileSync, readdirSync, statSync, rmSync as rmSync2 } from "fs";
1309
- import { homedir } from "os";
1310
- import { randomUUID as randomUUID2 } from "crypto";
2469
+ import { basename, join as join6, resolve as resolve3, sep } from "path";
2470
+ import { existsSync as existsSync6, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5, readFileSync as readFileSync5, readdirSync, statSync, rmSync as rmSync2 } from "fs";
2471
+ import { homedir as homedir2 } from "os";
2472
+ import { randomUUID as randomUUID3 } from "crypto";
1311
2473
  function isAnalysisReady(ctx) {
1312
2474
  if (ctx.stage !== "analyzed" || ctx.analysis.completed.length === 0) return false;
1313
2475
  if (!ctx.dataset) return false;
@@ -1315,29 +2477,29 @@ function isAnalysisReady(ctx) {
1315
2477
  return Object.values(counts).some((n) => n > 0);
1316
2478
  }
1317
2479
  function ntrpHomeDir() {
1318
- return process.env.NTRP_HOME ? resolve2(process.env.NTRP_HOME) : join2(homedir(), ".ntrp");
2480
+ return process.env.NTRP_HOME ? resolve3(process.env.NTRP_HOME) : join6(homedir2(), ".ntrp");
1319
2481
  }
1320
2482
  function getSessionsDir() {
1321
- const dir = join2(ntrpHomeDir(), "sessions");
1322
- if (!existsSync2(dir)) {
1323
- mkdirSync2(dir, { recursive: true });
2483
+ const dir = join6(ntrpHomeDir(), "sessions");
2484
+ if (!existsSync6(dir)) {
2485
+ mkdirSync5(dir, { recursive: true });
1324
2486
  }
1325
2487
  return dir;
1326
2488
  }
1327
2489
  function getDatasetsDir() {
1328
- const dir = join2(ntrpHomeDir(), "datasets");
1329
- if (!existsSync2(dir)) {
1330
- mkdirSync2(dir, { recursive: true });
2490
+ const dir = join6(ntrpHomeDir(), "datasets");
2491
+ if (!existsSync6(dir)) {
2492
+ mkdirSync5(dir, { recursive: true });
1331
2493
  }
1332
2494
  return dir;
1333
2495
  }
1334
2496
  function datasetPathForSession(id) {
1335
- return join2(getDatasetsDir(), `${id}.duckdb`);
2497
+ return join6(getDatasetsDir(), `${id}.duckdb`);
1336
2498
  }
1337
2499
  function makeSessionId() {
1338
2500
  const now2 = /* @__PURE__ */ new Date();
1339
2501
  const date = now2.toISOString().slice(0, 10);
1340
- const uuid2 = randomUUID2().slice(0, 4);
2502
+ const uuid2 = randomUUID3().slice(0, 4);
1341
2503
  return `${date}-${uuid2}`;
1342
2504
  }
1343
2505
  function isValidSessionId(id) {
@@ -1345,14 +2507,14 @@ function isValidSessionId(id) {
1345
2507
  }
1346
2508
  function sessionPathForId(id) {
1347
2509
  if (!isValidSessionId(id)) return null;
1348
- const dir = resolve2(getSessionsDir());
1349
- const filePath = resolve2(dir, `${id}.json`);
2510
+ const dir = resolve3(getSessionsDir());
2511
+ const filePath = resolve3(dir, `${id}.json`);
1350
2512
  if (filePath !== dir && !filePath.startsWith(dir + sep)) return null;
1351
2513
  return filePath;
1352
2514
  }
1353
2515
  function initContext(oneShot, execution) {
1354
2516
  const sessionId = makeSessionId();
1355
- const sessionFile = join2(getSessionsDir(), `${sessionId}.json`);
2517
+ const sessionFile = join6(getSessionsDir(), `${sessionId}.json`);
1356
2518
  return {
1357
2519
  sessionId,
1358
2520
  sessionFile,
@@ -1464,14 +2626,14 @@ function recordMessage(ctx, role, content) {
1464
2626
  ctx.messages.push(msg);
1465
2627
  if (ctx.oneShot) return;
1466
2628
  try {
1467
- writeFileSync(ctx.sessionFile, JSON.stringify(buildSessionFile(ctx), null, 2) + "\n");
2629
+ writeFileSync5(ctx.sessionFile, JSON.stringify(buildSessionFile(ctx), null, 2) + "\n");
1468
2630
  } catch {
1469
2631
  }
1470
2632
  }
1471
2633
  function saveSessionState(ctx) {
1472
2634
  if (ctx.oneShot) return;
1473
2635
  try {
1474
- writeFileSync(ctx.sessionFile, JSON.stringify(buildSessionFile(ctx), null, 2) + "\n");
2636
+ writeFileSync5(ctx.sessionFile, JSON.stringify(buildSessionFile(ctx), null, 2) + "\n");
1475
2637
  } catch {
1476
2638
  }
1477
2639
  }
@@ -1481,7 +2643,7 @@ function getLastActivityRelative() {
1481
2643
  try {
1482
2644
  for (const name of readdirSync(dir)) {
1483
2645
  if (!name.endsWith(".json")) continue;
1484
- const m = statSync(join2(dir, name)).mtimeMs;
2646
+ const m = statSync(join6(dir, name)).mtimeMs;
1485
2647
  if (m > mostRecent) mostRecent = m;
1486
2648
  }
1487
2649
  } catch {
@@ -1507,7 +2669,7 @@ function loadSessionFile(id) {
1507
2669
  const filePath = sessionPathForId(id);
1508
2670
  if (!filePath) return null;
1509
2671
  try {
1510
- const raw = readFileSync(filePath, "utf-8");
2672
+ const raw = readFileSync5(filePath, "utf-8");
1511
2673
  const session = JSON.parse(raw);
1512
2674
  if (session.thread?.length) {
1513
2675
  session.thread = normalizeThread(session.thread);
@@ -1522,7 +2684,7 @@ function listSessions(opts) {
1522
2684
  const entries = [];
1523
2685
  try {
1524
2686
  const files = readdirSync(dir).filter((name) => name.endsWith(".json")).map((name) => {
1525
- const filePath = join2(dir, name);
2687
+ const filePath = join6(dir, name);
1526
2688
  return { name, filePath, mtime: statSync(filePath).mtimeMs };
1527
2689
  }).sort((a, b) => b.mtime - a.mtime);
1528
2690
  const filesToRead = opts?.limit ? files.slice(0, opts.limit) : files;
@@ -1530,7 +2692,7 @@ function listSessions(opts) {
1530
2692
  const id = basename(name, ".json");
1531
2693
  if (!isValidSessionId(id)) continue;
1532
2694
  try {
1533
- const raw = readFileSync(filePath, "utf-8");
2695
+ const raw = readFileSync5(filePath, "utf-8");
1534
2696
  const session = JSON.parse(raw);
1535
2697
  entries.push({
1536
2698
  id,
@@ -1591,7 +2753,7 @@ async function closeAllActiveSessions(ctx) {
1591
2753
  skipped.push(s.id);
1592
2754
  continue;
1593
2755
  }
1594
- writeFileSync(filePath, JSON.stringify(file, null, 2) + "\n");
2756
+ writeFileSync5(filePath, JSON.stringify(file, null, 2) + "\n");
1595
2757
  closed.push(s.id);
1596
2758
  }
1597
2759
  const currentActive = active.some((s) => s.id === ctx.sessionId);
@@ -1630,7 +2792,7 @@ async function rotateToFreshSession(ctx) {
1630
2792
  const newId = makeSessionId();
1631
2793
  resetContextForSwitch(ctx, {
1632
2794
  sessionId: newId,
1633
- sessionFile: join2(getSessionsDir(), `${newId}.json`),
2795
+ sessionFile: join6(getSessionsDir(), `${newId}.json`),
1634
2796
  messages: [],
1635
2797
  stage: "new",
1636
2798
  analysis: defaultSessionAnalysis(),
@@ -1659,6 +2821,12 @@ async function finalizeSession(ctx, stage) {
1659
2821
  }
1660
2822
  return void 0;
1661
2823
  }
2824
+ if (ctx.deliverables.length > 0) {
2825
+ const { creditSessionDeliverableWrapup: creditSessionDeliverableWrapup2 } = await Promise.resolve().then(() => (init_time_bank(), time_bank_exports));
2826
+ creditSessionDeliverableWrapup2(ctx);
2827
+ }
2828
+ const { recordSessionClosed: recordSessionClosed2 } = await Promise.resolve().then(() => (init_usage_stats(), usage_stats_exports));
2829
+ recordSessionClosed2();
1662
2830
  const file = {
1663
2831
  id: ctx.sessionId,
1664
2832
  created_at: ctx.messages[0]?.at ?? endedAt,
@@ -1695,7 +2863,7 @@ async function finalizeSession(ctx, stage) {
1695
2863
  file.llm = ctx.llm;
1696
2864
  }
1697
2865
  try {
1698
- writeFileSync(ctx.sessionFile, JSON.stringify(file, null, 2) + "\n");
2866
+ writeFileSync5(ctx.sessionFile, JSON.stringify(file, null, 2) + "\n");
1699
2867
  } catch {
1700
2868
  }
1701
2869
  return void 0;
@@ -1923,7 +3091,14 @@ async function anthropicComplete(apiKey, model, req) {
1923
3091
  ...req.tools && req.tools.length > 0 ? { tools: toAnthropicTools(req.tools) } : {},
1924
3092
  messages: toAnthropicMessages(req.messages)
1925
3093
  });
1926
- return parseResponse(response.content);
3094
+ const parsed = parseResponse(response.content);
3095
+ if (response.usage) {
3096
+ parsed.token_usage = {
3097
+ input_tokens: response.usage.input_tokens,
3098
+ output_tokens: response.usage.output_tokens
3099
+ };
3100
+ }
3101
+ return parsed;
1927
3102
  } catch (err) {
1928
3103
  throw mapAnthropicError(err, provider);
1929
3104
  }
@@ -2038,7 +3213,14 @@ async function openaiComplete(apiKey, model, req) {
2038
3213
  if (!choice?.message) {
2039
3214
  throw new Error("OpenAI returned no message");
2040
3215
  }
2041
- return parseResponse2(choice.message);
3216
+ const parsed = parseResponse2(choice.message);
3217
+ if (response.usage) {
3218
+ parsed.token_usage = {
3219
+ input_tokens: response.usage.prompt_tokens ?? 0,
3220
+ output_tokens: response.usage.completion_tokens ?? 0
3221
+ };
3222
+ }
3223
+ return parsed;
2042
3224
  } catch (err) {
2043
3225
  if (err instanceof OpenAI.APIError) {
2044
3226
  throw mapOpenAiError(err, provider);
@@ -2098,270 +3280,103 @@ function resolveRetiredModel(id) {
2098
3280
  function cheapestActiveInTier(provider, tier) {
2099
3281
  const candidates = ENTRIES.filter(
2100
3282
  (e) => e.provider === provider && e.tier === tier && e.status === "active"
2101
- );
2102
- if (candidates.length === 0) return void 0;
2103
- return candidates.sort((a, b) => a.relative_cost - b.relative_cost)[0];
2104
- }
2105
- function getTierDefault(provider, tier) {
2106
- const entry = cheapestActiveInTier(provider, tier);
2107
- if (!entry) {
2108
- throw new Error(`No active ${tier}-tier model for provider ${provider} in catalog`);
2109
- }
2110
- return entry;
2111
- }
2112
- function resolveModel(provider, tier, override) {
2113
- if (override) {
2114
- const entry = byId.get(override);
2115
- if (!entry) return resolveRetiredModel(override);
2116
- if (entry.status === "retired") return resolveRetiredModel(override);
2117
- return entry.id;
2118
- }
2119
- return getTierDefault(provider, tier).id;
2120
- }
2121
- function formatModelLabel(provider, modelId) {
2122
- const entry = byId.get(modelId);
2123
- return entry ? `${provider}/${entry.display_name}` : `${provider}/${modelId}`;
2124
- }
2125
- var ENTRIES, byId;
2126
- var init_catalog = __esm({
2127
- "src/ai/llm/catalog.ts"() {
2128
- "use strict";
2129
- ENTRIES = [
2130
- {
2131
- id: "claude-opus-4-6",
2132
- provider: "anthropic",
2133
- tier: "high",
2134
- status: "active",
2135
- successor_id: null,
2136
- supports_tools: true,
2137
- max_context_tokens: 2e5,
2138
- display_name: "Claude Opus 4.6",
2139
- relative_cost: 3
2140
- },
2141
- {
2142
- id: "claude-sonnet-4-5-20250929",
2143
- provider: "anthropic",
2144
- tier: "medium",
2145
- status: "active",
2146
- successor_id: null,
2147
- supports_tools: true,
2148
- max_context_tokens: 2e5,
2149
- display_name: "Claude Sonnet 4.5",
2150
- relative_cost: 2
2151
- },
2152
- {
2153
- id: "claude-haiku-4-5-20251001",
2154
- provider: "anthropic",
2155
- tier: "low",
2156
- status: "active",
2157
- successor_id: null,
2158
- supports_tools: true,
2159
- max_context_tokens: 2e5,
2160
- display_name: "Claude Haiku 4.5",
2161
- relative_cost: 1
2162
- },
2163
- {
2164
- id: "gpt-4.1",
2165
- provider: "openai",
2166
- tier: "high",
2167
- status: "active",
2168
- successor_id: null,
2169
- supports_tools: true,
2170
- max_context_tokens: 1047576,
2171
- display_name: "GPT-4.1",
2172
- relative_cost: 3
2173
- },
2174
- {
2175
- id: "gpt-4.1-mini",
2176
- provider: "openai",
2177
- tier: "medium",
2178
- status: "active",
2179
- successor_id: null,
2180
- supports_tools: true,
2181
- max_context_tokens: 1047576,
2182
- display_name: "GPT-4.1 Mini",
2183
- relative_cost: 2
2184
- },
2185
- {
2186
- id: "gpt-4.1-nano",
2187
- provider: "openai",
2188
- tier: "low",
2189
- status: "active",
2190
- successor_id: null,
2191
- supports_tools: true,
2192
- max_context_tokens: 1047576,
2193
- display_name: "GPT-4.1 Nano",
2194
- relative_cost: 1
2195
- }
2196
- ];
2197
- byId = new Map(ENTRIES.map((e) => [e.id, e]));
2198
- }
2199
- });
2200
-
2201
- // src/config/store.ts
2202
- var store_exports = {};
2203
- __export(store_exports, {
2204
- deleteConfigValue: () => deleteConfigValue,
2205
- getConfigValue: () => getConfigValue,
2206
- getExportsDir: () => getExportsDir,
2207
- getKnowledgeDir: () => getKnowledgeDir,
2208
- getMemoryDir: () => getMemoryDir,
2209
- getStrategiesDir: () => getStrategiesDir,
2210
- getWinsDir: () => getWinsDir,
2211
- loadConfig: () => loadConfig,
2212
- ntrpHome: () => ntrpHome,
2213
- resetConfigCache: () => resetConfigCache,
2214
- saveConfig: () => saveConfig,
2215
- setConfigValue: () => setConfigValue
2216
- });
2217
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync3 } from "fs";
2218
- import { homedir as homedir2 } from "os";
2219
- import { join as join3, resolve as resolve3 } from "path";
2220
- function ntrpHome() {
2221
- return NTRP_DIR2;
2222
- }
2223
- function ensureDir2() {
2224
- if (!existsSync3(NTRP_DIR2)) {
2225
- mkdirSync3(NTRP_DIR2, { recursive: true });
2226
- }
2227
- }
2228
- function loadConfig() {
2229
- if (cachedConfig) return cachedConfig;
2230
- ensureDir2();
2231
- if (!existsSync3(CONFIG_PATH)) {
2232
- cachedConfig = {};
2233
- return cachedConfig;
2234
- }
2235
- try {
2236
- cachedConfig = JSON.parse(readFileSync2(CONFIG_PATH, "utf-8"));
2237
- } catch {
2238
- cachedConfig = {};
2239
- }
2240
- return cachedConfig;
2241
- }
2242
- function saveConfig(config) {
2243
- ensureDir2();
2244
- writeFileSync2(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n");
2245
- cachedConfig = config;
2246
- }
2247
- function resetConfigCache() {
2248
- cachedConfig = null;
2249
- }
2250
- function getConfigValue(key) {
2251
- if (key === "api-key") return loadConfig()["api-key"];
2252
- if (key === "license-key") return process.env.NTRP_LICENSE_KEY ?? loadConfig()["license-key"];
2253
- const config = loadConfig();
2254
- return config[key];
2255
- }
2256
- function setConfigValue(key, value) {
2257
- const config = loadConfig();
2258
- config[key] = value;
2259
- saveConfig(config);
2260
- }
2261
- function deleteConfigValue(key) {
2262
- const config = loadConfig();
2263
- delete config[key];
2264
- saveConfig(config);
2265
- }
2266
- function getExportsDir() {
2267
- const config = loadConfig();
2268
- const dir = resolve3(config["export-dir"] ?? join3(NTRP_DIR2, "exports"));
2269
- if (!existsSync3(dir)) {
2270
- mkdirSync3(dir, { recursive: true });
2271
- }
2272
- return dir;
2273
- }
2274
- function getStrategiesDir() {
2275
- const dir = join3(NTRP_DIR2, "strategies");
2276
- if (!existsSync3(dir)) {
2277
- mkdirSync3(dir, { recursive: true });
2278
- writeFileSync2(join3(dir, "README.md"), `# Strategies
2279
-
2280
- This directory holds your GTM strategy files. Each file describes a strategy you're executing.
2281
-
2282
- ## How to use
2283
-
2284
- 1. Create a markdown file for each active strategy (e.g., \`multi-thread-q2.md\`)
2285
- 2. Describe the goal, target segment, and success criteria
2286
- 3. Reference playbook plays that support this strategy
2287
- 4. After diagnosis, check if vital signs improved in the targeted area
2288
-
2289
- ## Example
2290
-
2291
- \`\`\`markdown
2292
- # Multi-Thread Enterprise Deals \u2014 Q2
2293
-
2294
- **Goal:** Reduce single-threaded deals from 65% to under 30%
2295
- **Segment:** Enterprise accounts > $100K
2296
- **Play:** Multi-Thread Your Deals
2297
- **Success metric:** Thread depth score > 70
2298
- \`\`\`
2299
- `);
2300
- }
2301
- return dir;
3283
+ );
3284
+ if (candidates.length === 0) return void 0;
3285
+ return candidates.sort((a, b) => a.relative_cost - b.relative_cost)[0];
2302
3286
  }
2303
- function getMemoryDir() {
2304
- const dir = join3(NTRP_DIR2, "memory");
2305
- if (!existsSync3(dir)) {
2306
- mkdirSync3(dir, { recursive: true });
3287
+ function getTierDefault(provider, tier) {
3288
+ const entry = cheapestActiveInTier(provider, tier);
3289
+ if (!entry) {
3290
+ throw new Error(`No active ${tier}-tier model for provider ${provider} in catalog`);
2307
3291
  }
2308
- return dir;
3292
+ return entry;
2309
3293
  }
2310
- function getKnowledgeDir() {
2311
- const dir = join3(NTRP_DIR2, "knowledge");
2312
- if (!existsSync3(dir)) {
2313
- mkdirSync3(dir, { recursive: true });
2314
- writeFileSync2(join3(dir, "README.md"), `# Knowledge Packs
2315
-
2316
- Drop case studies, GTM frameworks, benchmark reports, or playbooks here as
2317
- markdown, text, or PDF. NTRP ingests them with \`/knowledge add <file>\` and
2318
- references the most relevant passages during analysis \u2014 so the agent can learn
2319
- from work done outside this platform.
2320
-
2321
- ## How to use
2322
-
2323
- 1. Add a file: \`/knowledge add ~/Downloads/plg-benchmarks-2026.pdf\`
2324
- 2. List what's indexed: \`/knowledge list\`
2325
- 3. Ask a question \u2014 relevant passages are pulled in automatically.
2326
- `);
3294
+ function resolveModel(provider, tier, override) {
3295
+ if (override) {
3296
+ const entry = byId.get(override);
3297
+ if (!entry) return resolveRetiredModel(override);
3298
+ if (entry.status === "retired") return resolveRetiredModel(override);
3299
+ return entry.id;
2327
3300
  }
2328
- return dir;
3301
+ return getTierDefault(provider, tier).id;
2329
3302
  }
2330
- function getWinsDir() {
2331
- const dir = join3(NTRP_DIR2, "wins");
2332
- if (!existsSync3(dir)) {
2333
- mkdirSync3(dir, { recursive: true });
2334
- writeFileSync2(join3(dir, "README.md"), `# Wins
2335
-
2336
- This directory logs outcomes when a strategy or play succeeds. Each win creates a record that future diagnoses can reference.
2337
-
2338
- ## How to use
2339
-
2340
- 1. After executing a play, log the result here (e.g., \`2026-04-clean-pipeline.md\`)
2341
- 2. Include: what you did, what changed, before/after scores
2342
- 3. Future AI findings will reference wins to track improvement over time
2343
-
2344
- ## Example
2345
-
2346
- \`\`\`markdown
2347
- # Pipeline Cleanup \u2014 April 2026
2348
-
2349
- **Play:** Clean Dead Pipeline
2350
- **Before:** Freshness 29/100, $3.1M stale pipeline
2351
- **After:** Freshness 72/100, removed 45 zombie deals
2352
- **Impact:** Forecast accuracy improved from 62% to 84%
2353
- \`\`\`
2354
- `);
2355
- }
2356
- return dir;
3303
+ function formatModelLabel(provider, modelId) {
3304
+ const entry = byId.get(modelId);
3305
+ return entry ? `${provider}/${entry.display_name}` : `${provider}/${modelId}`;
2357
3306
  }
2358
- var NTRP_DIR2, CONFIG_PATH, cachedConfig;
2359
- var init_store = __esm({
2360
- "src/config/store.ts"() {
3307
+ var ENTRIES, byId;
3308
+ var init_catalog = __esm({
3309
+ "src/ai/llm/catalog.ts"() {
2361
3310
  "use strict";
2362
- NTRP_DIR2 = process.env.NTRP_HOME ? resolve3(process.env.NTRP_HOME) : join3(homedir2(), ".ntrp");
2363
- CONFIG_PATH = join3(NTRP_DIR2, "config.json");
2364
- cachedConfig = null;
3311
+ ENTRIES = [
3312
+ {
3313
+ id: "claude-opus-4-6",
3314
+ provider: "anthropic",
3315
+ tier: "high",
3316
+ status: "active",
3317
+ successor_id: null,
3318
+ supports_tools: true,
3319
+ max_context_tokens: 2e5,
3320
+ display_name: "Claude Opus 4.6",
3321
+ relative_cost: 3
3322
+ },
3323
+ {
3324
+ id: "claude-sonnet-4-5-20250929",
3325
+ provider: "anthropic",
3326
+ tier: "medium",
3327
+ status: "active",
3328
+ successor_id: null,
3329
+ supports_tools: true,
3330
+ max_context_tokens: 2e5,
3331
+ display_name: "Claude Sonnet 4.5",
3332
+ relative_cost: 2
3333
+ },
3334
+ {
3335
+ id: "claude-haiku-4-5-20251001",
3336
+ provider: "anthropic",
3337
+ tier: "low",
3338
+ status: "active",
3339
+ successor_id: null,
3340
+ supports_tools: true,
3341
+ max_context_tokens: 2e5,
3342
+ display_name: "Claude Haiku 4.5",
3343
+ relative_cost: 1
3344
+ },
3345
+ {
3346
+ id: "gpt-4.1",
3347
+ provider: "openai",
3348
+ tier: "high",
3349
+ status: "active",
3350
+ successor_id: null,
3351
+ supports_tools: true,
3352
+ max_context_tokens: 1047576,
3353
+ display_name: "GPT-4.1",
3354
+ relative_cost: 3
3355
+ },
3356
+ {
3357
+ id: "gpt-4.1-mini",
3358
+ provider: "openai",
3359
+ tier: "medium",
3360
+ status: "active",
3361
+ successor_id: null,
3362
+ supports_tools: true,
3363
+ max_context_tokens: 1047576,
3364
+ display_name: "GPT-4.1 Mini",
3365
+ relative_cost: 2
3366
+ },
3367
+ {
3368
+ id: "gpt-4.1-nano",
3369
+ provider: "openai",
3370
+ tier: "low",
3371
+ status: "active",
3372
+ successor_id: null,
3373
+ supports_tools: true,
3374
+ max_context_tokens: 1047576,
3375
+ display_name: "GPT-4.1 Nano",
3376
+ relative_cost: 1
3377
+ }
3378
+ ];
3379
+ byId = new Map(ENTRIES.map((e) => [e.id, e]));
2365
3380
  }
2366
3381
  });
2367
3382
 
@@ -2626,8 +3641,10 @@ async function completeWithFailover(req, opts = {}) {
2626
3641
  const meta = {
2627
3642
  provider_used: provider,
2628
3643
  model_used: model,
3644
+ ...response.token_usage ?? {},
2629
3645
  ...failoverFrom ? { failover: true, failover_from: failoverFrom } : {}
2630
3646
  };
3647
+ recordLlmUsage(response.token_usage);
2631
3648
  return { response, meta };
2632
3649
  } catch (err) {
2633
3650
  const llmErr = err;
@@ -2640,8 +3657,10 @@ async function completeWithFailover(req, opts = {}) {
2640
3657
  const meta = {
2641
3658
  provider_used: provider,
2642
3659
  model_used: model,
3660
+ ...response.token_usage ?? {},
2643
3661
  ...failoverFrom ? { failover: true, failover_from: failoverFrom } : {}
2644
3662
  };
3663
+ recordLlmUsage(response.token_usage);
2645
3664
  return { response, meta };
2646
3665
  } catch (retryErr) {
2647
3666
  const retryLlm = retryErr;
@@ -2688,9 +3707,13 @@ async function* streamWithFailover(req, opts = {}) {
2688
3707
  yield event;
2689
3708
  }
2690
3709
  }
3710
+ const estimatedOut = Math.ceil(fullText.length / 4);
3711
+ recordLlmUsage({ input_tokens: 0, output_tokens: estimatedOut });
2691
3712
  const meta = {
2692
3713
  provider_used: provider,
2693
3714
  model_used: model,
3715
+ input_tokens: 0,
3716
+ output_tokens: estimatedOut,
2694
3717
  ...failoverFrom ? { failover: true, failover_from: failoverFrom } : {}
2695
3718
  };
2696
3719
  yield {
@@ -2723,6 +3746,7 @@ async function* streamWithFailover(req, opts = {}) {
2723
3746
  var init_failover = __esm({
2724
3747
  "src/ai/llm/failover.ts"() {
2725
3748
  "use strict";
3749
+ init_usage_stats();
2726
3750
  init_anthropic();
2727
3751
  init_openai();
2728
3752
  init_catalog();
@@ -3135,9 +4159,9 @@ var init_tool_schemas = __esm({
3135
4159
  });
3136
4160
 
3137
4161
  // src/ai/privacy.ts
3138
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, appendFileSync } from "fs";
4162
+ import { existsSync as existsSync7, mkdirSync as mkdirSync6, appendFileSync } from "fs";
3139
4163
  import { homedir as homedir3 } from "os";
3140
- import { join as join4 } from "path";
4164
+ import { join as join7 } from "path";
3141
4165
  function stripPII(obj) {
3142
4166
  if (obj === null || obj === void 0) return obj;
3143
4167
  if (typeof obj !== "object") return obj;
@@ -3152,14 +4176,14 @@ function stripPII(obj) {
3152
4176
  return out;
3153
4177
  }
3154
4178
  function ensureAuditDir() {
3155
- if (!existsSync4(AUDIT_DIR)) {
3156
- mkdirSync4(AUDIT_DIR, { recursive: true });
4179
+ if (!existsSync7(AUDIT_DIR)) {
4180
+ mkdirSync6(AUDIT_DIR, { recursive: true });
3157
4181
  }
3158
4182
  }
3159
4183
  function logToolCall(entry) {
3160
4184
  ensureAuditDir();
3161
4185
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
3162
- const path = join4(AUDIT_DIR, `agentic-${date}.jsonl`);
4186
+ const path = join7(AUDIT_DIR, `agentic-${date}.jsonl`);
3163
4187
  appendFileSync(path, JSON.stringify(entry) + "\n");
3164
4188
  }
3165
4189
  var PII_FIELDS, AUDIT_DIR;
@@ -3183,7 +4207,7 @@ var init_privacy = __esm({
3183
4207
  "raw_data",
3184
4208
  "metadata"
3185
4209
  ]);
3186
- AUDIT_DIR = join4(homedir3(), ".ntrp", "audit");
4210
+ AUDIT_DIR = join7(homedir3(), ".ntrp", "audit");
3187
4211
  }
3188
4212
  });
3189
4213
 
@@ -3410,16 +4434,16 @@ var init_metrics_benchmarks = __esm({
3410
4434
  });
3411
4435
 
3412
4436
  // src/config/profile.ts
3413
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync5 } from "fs";
3414
- import { join as join5 } from "path";
4437
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, existsSync as existsSync8, mkdirSync as mkdirSync7 } from "fs";
4438
+ import { join as join8 } from "path";
3415
4439
  function isProfileConfigured(profile = loadProfile()) {
3416
4440
  if (!profile) return false;
3417
4441
  return profile.company_name.trim().length > 0;
3418
4442
  }
3419
4443
  function loadProfile() {
3420
- if (!existsSync5(PROFILE_PATH)) return null;
4444
+ if (!existsSync8(PROFILE_PATH)) return null;
3421
4445
  try {
3422
- const parsed = JSON.parse(readFileSync3(PROFILE_PATH, "utf-8"));
4446
+ const parsed = JSON.parse(readFileSync6(PROFILE_PATH, "utf-8"));
3423
4447
  if (!parsed || typeof parsed !== "object") return null;
3424
4448
  return parsed;
3425
4449
  } catch {
@@ -3432,7 +4456,7 @@ var init_profile = __esm({
3432
4456
  "use strict";
3433
4457
  init_store();
3434
4458
  NTRP_DIR3 = ntrpHome();
3435
- PROFILE_PATH = join5(NTRP_DIR3, "profile.json");
4459
+ PROFILE_PATH = join8(NTRP_DIR3, "profile.json");
3436
4460
  }
3437
4461
  });
3438
4462
 
@@ -5673,76 +6697,8 @@ var init_insights = __esm({
5673
6697
  }
5674
6698
  });
5675
6699
 
5676
- // src/ui/theme.ts
5677
- import chalk from "chalk";
5678
- function paint(token, text) {
5679
- if (token === "dim") return chalk.dim(text);
5680
- return chalk.hex(TOKENS[token])(text);
5681
- }
5682
- function bold(text) {
5683
- return chalk.bold(text);
5684
- }
5685
- function badge(label, tone = "muted") {
5686
- const normalized = ` ${label.toUpperCase()} `;
5687
- switch (tone) {
5688
- case "success":
5689
- return chalk.hex(TOKENS.success)(normalized);
5690
- case "warning":
5691
- return chalk.hex(TOKENS.warning)(normalized);
5692
- case "error":
5693
- return chalk.hex(TOKENS.error)(normalized);
5694
- case "info":
5695
- return chalk.hex(TOKENS.info)(normalized);
5696
- case "accent":
5697
- return chalk.hex(TOKENS.accent)(normalized);
5698
- case "muted":
5699
- return chalk.dim(normalized);
5700
- }
5701
- }
5702
- function sectionHeading(label) {
5703
- return `${paint("accent", "\u25B8")} ${paint("accent", bold(label))}`;
5704
- }
5705
- function actionHint(label, command, detail) {
5706
- const suffix = detail ? chalk.dim(` ${detail}`) : "";
5707
- return `${chalk.dim(label)} ${paint("accent", command)}${suffix}`;
5708
- }
5709
- function scoreBar(score, status, width = 14) {
5710
- const filled = Math.round(score / 100 * width);
5711
- const color = chalk.hex(STATUS_COLORS[status]);
5712
- let filledPart = "";
5713
- for (let i = 0; i < filled; i++) {
5714
- filledPart += i % 2 === 0 ? "\u2588" : "\u2593";
5715
- }
5716
- const emptyPart = "\u2591".repeat(width - filled);
5717
- return color(filledPart) + chalk.dim(emptyPart);
5718
- }
5719
- var STATUS_COLORS, TOKENS;
5720
- var init_theme = __esm({
5721
- "src/ui/theme.ts"() {
5722
- "use strict";
5723
- init_formatters();
5724
- STATUS_COLORS = {
5725
- green: "#22c55e",
5726
- yellow: "#eab308",
5727
- red: "#ef4444"
5728
- };
5729
- TOKENS = {
5730
- accent: "#14b8a6",
5731
- accentBright: "#2dd4bf",
5732
- border: "#334155",
5733
- borderMuted: "#1e293b",
5734
- dim: "#64748b",
5735
- text: "#e2e8f0",
5736
- error: "#ef4444",
5737
- warning: "#eab308",
5738
- success: "#22c55e",
5739
- info: "#3b82f6"
5740
- };
5741
- }
5742
- });
5743
-
5744
6700
  // src/conversation/phase.ts
5745
- import chalk2 from "chalk";
6701
+ import chalk3 from "chalk";
5746
6702
  function sessionHasData(ctx) {
5747
6703
  const counts = ctx.dataset?.counts ?? {};
5748
6704
  return Object.values(counts).some((n) => (n ?? 0) > 0);
@@ -6037,12 +6993,12 @@ var init_repl_globals = __esm({
6037
6993
  // src/cli/prompts.ts
6038
6994
  import { createInterface } from "readline/promises";
6039
6995
  import { clearLine, cursorTo } from "readline";
6040
- import chalk3 from "chalk";
6996
+ import chalk4 from "chalk";
6041
6997
  function marker() {
6042
6998
  return paint("accent", "ntrp \u203A ");
6043
6999
  }
6044
7000
  function secretPromptLine(question) {
6045
- return ` ${paint("accent", "\u25B8")} ${bold(question)} ${chalk3.dim("(hidden \u2014 paste once, Enter)")} `;
7001
+ return ` ${paint("accent", "\u25B8")} ${bold(question)} ${chalk4.dim("(hidden \u2014 paste once, Enter)")} `;
6046
7002
  }
6047
7003
  function stripTerminalArtifacts(input) {
6048
7004
  return input.replace(/\x1b\[[0-9;]*[a-zA-Z~]/g, "").replace(/\x1b\][^\x07]*(\x07|\x1b\\)/g, "").replace(/\x1b\[200~/g, "").replace(/\x1b\[201~/g, "");
@@ -6050,7 +7006,7 @@ function stripTerminalArtifacts(input) {
6050
7006
  function renderQuestion(question, defaultValue) {
6051
7007
  const base = ` ${marker()}${bold(question)}`;
6052
7008
  if (defaultValue !== void 0 && defaultValue !== "") {
6053
- return `${base} ${chalk3.dim(`[${defaultValue}]`)} `;
7009
+ return `${base} ${chalk4.dim(`[${defaultValue}]`)} `;
6054
7010
  }
6055
7011
  return `${base} `;
6056
7012
  }
@@ -6075,7 +7031,7 @@ function createPromptSession(existing, ctx) {
6075
7031
  const raw = (await rl.question(renderQuestion(question))).trim();
6076
7032
  assertNotGlobalReplCommand(raw);
6077
7033
  if (raw) return raw;
6078
- console.log(" " + chalk3.red("This one is required."));
7034
+ console.log(" " + chalk4.red("This one is required."));
6079
7035
  }
6080
7036
  }
6081
7037
  async function confirm(question, defaultYes = false) {
@@ -6093,13 +7049,13 @@ function createPromptSession(existing, ctx) {
6093
7049
  const defaultIdx = opts.default ? choices.findIndex((c) => c.value === opts.default) : -1;
6094
7050
  choices.forEach((c, i) => {
6095
7051
  const num = paint("accent", `${i + 1}.`);
6096
- const active = i === defaultIdx ? chalk3.dim(" \u2190 default") : "";
7052
+ const active = i === defaultIdx ? chalk4.dim(" \u2190 default") : "";
6097
7053
  console.log(` ${num} ${c.label}${active}`);
6098
- if (c.description) console.log(` ${chalk3.dim(c.description)}`);
7054
+ if (c.description) console.log(` ${chalk4.dim(c.description)}`);
6099
7055
  });
6100
7056
  const defaultLabel = defaultIdx >= 0 ? String(defaultIdx + 1) : void 0;
6101
7057
  console.log();
6102
- console.log(" " + chalk3.dim("\u2500".repeat(40)));
7058
+ console.log(" " + chalk4.dim("\u2500".repeat(40)));
6103
7059
  for (; ; ) {
6104
7060
  const raw = (await rl.question(renderQuestion(`Your pick [1-${choices.length}]`, defaultLabel))).trim();
6105
7061
  assertNotGlobalReplCommand(raw);
@@ -6108,7 +7064,7 @@ function createPromptSession(existing, ctx) {
6108
7064
  if (Number.isInteger(n) && n >= 1 && n <= choices.length) {
6109
7065
  return choices[n - 1].value;
6110
7066
  }
6111
- console.log(" " + chalk3.red(`Enter a number from 1 to ${choices.length}.`));
7067
+ console.log(" " + chalk4.red(`Enter a number from 1 to ${choices.length}.`));
6112
7068
  }
6113
7069
  }
6114
7070
  async function askMulti(question, options) {
@@ -6118,7 +7074,7 @@ function createPromptSession(existing, ctx) {
6118
7074
  options.forEach((o, i) => {
6119
7075
  const num = paint("accent", `${i + 1}.`);
6120
7076
  console.log(` ${num} ${o.label}`);
6121
- if (o.description) console.log(` ${chalk3.dim(o.description)}`);
7077
+ if (o.description) console.log(` ${chalk4.dim(o.description)}`);
6122
7078
  });
6123
7079
  const hint = `Choose [1-${options.length}], type your own, or enter to skip`;
6124
7080
  const raw = (await rl.question(renderQuestion(hint))).trim();
@@ -6221,20 +7177,20 @@ function createPromptSession(existing, ctx) {
6221
7177
  for (; ; ) {
6222
7178
  const value = await readMaskedLine(secretPromptLine(question), maskChar);
6223
7179
  if (!value) {
6224
- console.log(" " + chalk3.red("This one is required."));
7180
+ console.log(" " + chalk4.red("This one is required."));
6225
7181
  continue;
6226
7182
  }
6227
7183
  if (opts.confirm === false) return value;
6228
7184
  const preview = value.length <= 14 ? `${value.slice(0, 4)}\u2026` : `${value.slice(0, 10)}\u2026`;
6229
- console.log(" " + chalk3.dim(`Captured ${value.length} characters (${preview})`));
7185
+ console.log(" " + chalk4.dim(`Captured ${value.length} characters (${preview})`));
6230
7186
  const ok = await confirm("Save this key?", false);
6231
7187
  if (ok) return value;
6232
- console.log(" " + chalk3.dim("Try again \u2014 paste the key once, then Enter."));
7188
+ console.log(" " + chalk4.dim("Try again \u2014 paste the key once, then Enter."));
6233
7189
  }
6234
7190
  }
6235
7191
  async function askPressEnter(message) {
6236
7192
  await rl.question(
6237
- ` ${paint("accent", "\u25B8")} ${bold(message)} ${chalk3.dim("(Enter)")} `
7193
+ ` ${paint("accent", "\u25B8")} ${bold(message)} ${chalk4.dim("(Enter)")} `
6238
7194
  );
6239
7195
  }
6240
7196
  return {
@@ -6262,30 +7218,30 @@ var init_prompts = __esm({
6262
7218
  });
6263
7219
 
6264
7220
  // src/conversation/gap-card.ts
6265
- import chalk4 from "chalk";
7221
+ import chalk5 from "chalk";
6266
7222
  function printGapCard(audit) {
6267
7223
  console.log();
6268
- console.log(" " + chalk4.bold("Data check"));
7224
+ console.log(" " + chalk5.bold("Data check"));
6269
7225
  if (audit.satisfied.length > 0) {
6270
7226
  for (const item of audit.satisfied) {
6271
- console.log(" " + chalk4.green("\u2713") + " " + chalk4.dim(`${item.label}: ${item.detail}`));
7227
+ console.log(" " + chalk5.green("\u2713") + " " + chalk5.dim(`${item.label}: ${item.detail}`));
6272
7228
  }
6273
7229
  }
6274
7230
  for (const item of audit.missing) {
6275
- console.log(" " + chalk4.red("\u2717") + " " + item.label + chalk4.dim(` \u2014 ${item.why}`));
6276
- console.log(" " + chalk4.dim(item.suggestion));
7231
+ console.log(" " + chalk5.red("\u2717") + " " + item.label + chalk5.dim(` \u2014 ${item.why}`));
7232
+ console.log(" " + chalk5.dim(item.suggestion));
6277
7233
  }
6278
7234
  for (const item of audit.optional) {
6279
- console.log(" " + chalk4.yellow("~") + " " + chalk4.dim(`${item.label}: ${item.detail}`));
7235
+ console.log(" " + chalk5.yellow("~") + " " + chalk5.dim(`${item.label}: ${item.detail}`));
6280
7236
  }
6281
7237
  console.log();
6282
7238
  if (audit.can_compute) {
6283
7239
  console.log(
6284
- " " + chalk4.dim("Ready to compute \u2014 say ") + chalk4.cyan('"go ahead"') + chalk4.dim(" or ") + chalk4.cyan('"run analysis"')
7240
+ " " + chalk5.dim("Ready to compute \u2014 say ") + chalk5.cyan('"go ahead"') + chalk5.dim(" or ") + chalk5.cyan('"run analysis"')
6285
7241
  );
6286
7242
  } else if (audit.missing.length > 0) {
6287
7243
  console.log(
6288
- " " + chalk4.dim("Load data (paste a CSV path or say ") + chalk4.cyan("use demo data") + chalk4.dim(")")
7244
+ " " + chalk5.dim("Load data (paste a CSV path or say ") + chalk5.cyan("use demo data") + chalk5.dim(")")
6289
7245
  );
6290
7246
  }
6291
7247
  console.log();
@@ -6297,7 +7253,7 @@ var init_gap_card = __esm({
6297
7253
  });
6298
7254
 
6299
7255
  // src/metrics/companion.ts
6300
- import chalk5 from "chalk";
7256
+ import chalk6 from "chalk";
6301
7257
  function getCompanionRecommendation(input) {
6302
7258
  const { analysis, coverage, opportunityCount, activityCount, sourceType } = input;
6303
7259
  const completed = new Set(analysis.completed);
@@ -6317,14 +7273,14 @@ function getCompanionRecommendation(input) {
6317
7273
  function printAnalysisComplete(lens) {
6318
7274
  const label = lens === "revenue_metrics" ? "SaaS metrics" : "Pipeline health";
6319
7275
  console.log(
6320
- " " + chalk5.green(`\u2713 ${label} ready`) + chalk5.dim(" \u2014 type a question below ") + paint("accent", "(no slash needed)")
7276
+ " " + chalk6.green(`\u2713 ${label} ready`) + chalk6.dim(" \u2014 type a question below ") + paint("accent", "(no slash needed)")
6321
7277
  );
6322
7278
  }
6323
7279
  function printCompanionBanner(invoked, primary) {
6324
7280
  if (invoked === "diagnose" && primary === "revenue_metrics") {
6325
7281
  console.log();
6326
7282
  console.log(
6327
- " " + chalk5.dim("Other view \u2014 ") + chalk5.bold("pipeline health") + chalk5.dim(" (this session started with SaaS metrics)")
7283
+ " " + chalk6.dim("Other view \u2014 ") + chalk6.bold("pipeline health") + chalk6.dim(" (this session started with SaaS metrics)")
6328
7284
  );
6329
7285
  console.log();
6330
7286
  return;
@@ -6332,7 +7288,7 @@ function printCompanionBanner(invoked, primary) {
6332
7288
  if (invoked === "metrics" && primary === "gtm_health") {
6333
7289
  console.log();
6334
7290
  console.log(
6335
- " " + chalk5.dim("Other view \u2014 ") + chalk5.bold("SaaS metrics") + chalk5.dim(" (this session started with pipeline health)")
7291
+ " " + chalk6.dim("Other view \u2014 ") + chalk6.bold("SaaS metrics") + chalk6.dim(" (this session started with pipeline health)")
6336
7292
  );
6337
7293
  console.log();
6338
7294
  }
@@ -6342,7 +7298,7 @@ function printCompanionFooter(ctx, companion, options = { justCompleted: "gtm_he
6342
7298
  const completed = new Set(ctx.analysis.completed);
6343
7299
  printAnalysisComplete(justCompleted);
6344
7300
  console.log();
6345
- console.log(" " + chalk5.bold("Try asking"));
7301
+ console.log(" " + chalk6.bold("Try asking"));
6346
7302
  const defaults = justCompleted === "revenue_metrics" ? [
6347
7303
  "Why is NRR showing 100%?",
6348
7304
  "Which deals drove ARR this quarter?",
@@ -6354,7 +7310,7 @@ function printCompanionFooter(ctx, companion, options = { justCompleted: "gtm_he
6354
7310
  ];
6355
7311
  const asks = (suggestedAsks?.length ? suggestedAsks : defaults).slice(0, 3);
6356
7312
  for (const q of asks) {
6357
- console.log(chalk5.dim(` "${q}"`));
7313
+ console.log(chalk6.dim(` "${q}"`));
6358
7314
  }
6359
7315
  console.log();
6360
7316
  const extras = [];
@@ -6369,7 +7325,7 @@ function printCompanionFooter(ctx, companion, options = { justCompleted: "gtm_he
6369
7325
  } else {
6370
7326
  extras.push(`${paint("accent", "/diagnose --findings")} for AI depth`);
6371
7327
  }
6372
- console.log(" " + chalk5.dim("Also: ") + chalk5.dim(extras.join(" \xB7 ")));
7328
+ console.log(" " + chalk6.dim("Also: ") + chalk6.dim(extras.join(" \xB7 ")));
6373
7329
  console.log();
6374
7330
  }
6375
7331
  async function resolveCompanionRecommendation(ctx) {
@@ -6413,16 +7369,16 @@ var init_complete = __esm({
6413
7369
  });
6414
7370
 
6415
7371
  // src/data/playbook.ts
6416
- import { existsSync as existsSync6, readFileSync as readFileSync4, appendFileSync as appendFileSync2 } from "fs";
6417
- import { join as join6 } from "path";
7372
+ import { existsSync as existsSync9, readFileSync as readFileSync7, appendFileSync as appendFileSync2 } from "fs";
7373
+ import { join as join9 } from "path";
6418
7374
  function playsPath() {
6419
- return join6(getMemoryDir(), PLAYS_FILE);
7375
+ return join9(getMemoryDir(), PLAYS_FILE);
6420
7376
  }
6421
7377
  function getCustomPlays() {
6422
7378
  const path = playsPath();
6423
- if (!existsSync6(path)) return [];
7379
+ if (!existsSync9(path)) return [];
6424
7380
  const out = [];
6425
- for (const line of readFileSync4(path, "utf-8").split("\n")) {
7381
+ for (const line of readFileSync7(path, "utf-8").split("\n")) {
6426
7382
  const trimmed = line.trim();
6427
7383
  if (!trimmed) continue;
6428
7384
  try {
@@ -6974,7 +7930,7 @@ var init_layout = __esm({
6974
7930
  });
6975
7931
 
6976
7932
  // src/ui/markdown.ts
6977
- import chalk6 from "chalk";
7933
+ import chalk7 from "chalk";
6978
7934
  import Table from "cli-table3";
6979
7935
  function renderMarkdown(text, opts = {}) {
6980
7936
  const indent = opts.indent ?? 2;
@@ -7110,13 +8066,13 @@ function renderBlock(block, width) {
7110
8066
  case "paragraph":
7111
8067
  return wrapWords(inline(block.text), width).join("\n");
7112
8068
  case "heading": {
7113
- const styled = chalk6.bold(inline(block.text));
8069
+ const styled = chalk7.bold(inline(block.text));
7114
8070
  const barWidth = Math.min(width, Math.max(8, visibleWidth(styled)));
7115
8071
  return `${styled}
7116
- ${chalk6.dim(hr(barWidth))}`;
8072
+ ${chalk7.dim(hr(barWidth))}`;
7117
8073
  }
7118
8074
  case "hr":
7119
- return chalk6.dim(hr(width));
8075
+ return chalk7.dim(hr(width));
7120
8076
  case "ul":
7121
8077
  return block.items.map((item) => {
7122
8078
  const lines = wrapWords(inline(item), Math.max(1, width - 4));
@@ -7134,10 +8090,10 @@ ${chalk6.dim(hr(barWidth))}`;
7134
8090
  }
7135
8091
  case "blockquote": {
7136
8092
  const lines = wrapWords(inline(block.text), Math.max(1, width - 2));
7137
- return lines.map((l) => chalk6.dim("\u2502 ") + chalk6.italic(l)).join("\n");
8093
+ return lines.map((l) => chalk7.dim("\u2502 ") + chalk7.italic(l)).join("\n");
7138
8094
  }
7139
8095
  case "code":
7140
- return block.lines.map((l) => chalk6.cyan(` ${l}`)).join("\n");
8096
+ return block.lines.map((l) => chalk7.cyan(` ${l}`)).join("\n");
7141
8097
  case "table":
7142
8098
  return renderTable(block.header, block.rows, width);
7143
8099
  }
@@ -7178,11 +8134,11 @@ function inline(text) {
7178
8134
  codeSpans.push(code);
7179
8135
  return `\0CODE${idx}\0`;
7180
8136
  });
7181
- out = out.replace(/\*\*([^*\n]+?)\*\*/g, (_m, inner) => chalk6.bold(inner));
7182
- out = out.replace(/(^|[^*\w])\*([^*\n]+?)\*(?!\*)/g, (_m, pre, inner) => `${pre}${chalk6.italic(inner)}`);
7183
- out = out.replace(/(^|[^_\w])_([^_\n]+?)_(?!\w)/g, (_m, pre, inner) => `${pre}${chalk6.italic(inner)}`);
8137
+ out = out.replace(/\*\*([^*\n]+?)\*\*/g, (_m, inner) => chalk7.bold(inner));
8138
+ out = out.replace(/(^|[^*\w])\*([^*\n]+?)\*(?!\*)/g, (_m, pre, inner) => `${pre}${chalk7.italic(inner)}`);
8139
+ out = out.replace(/(^|[^_\w])_([^_\n]+?)_(?!\w)/g, (_m, pre, inner) => `${pre}${chalk7.italic(inner)}`);
7184
8140
  out = out.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
7185
- out = out.replace(/\u0000CODE(\d+)\u0000/g, (_m, idx) => chalk6.cyan(codeSpans[Number(idx)]));
8141
+ out = out.replace(/\u0000CODE(\d+)\u0000/g, (_m, idx) => chalk7.cyan(codeSpans[Number(idx)]));
7186
8142
  return out;
7187
8143
  }
7188
8144
  var init_markdown = __esm({
@@ -7193,7 +8149,7 @@ var init_markdown = __esm({
7193
8149
  });
7194
8150
 
7195
8151
  // src/output/llm-attribution.ts
7196
- import chalk7 from "chalk";
8152
+ import chalk8 from "chalk";
7197
8153
  function formatLlmAttribution(meta) {
7198
8154
  if (!meta.model_used) return null;
7199
8155
  const provider = meta.provider_used ?? "anthropic";
@@ -7205,7 +8161,7 @@ function formatLlmAttribution(meta) {
7205
8161
  }
7206
8162
  function printLlmAttribution(meta) {
7207
8163
  const line = formatLlmAttribution(meta);
7208
- if (line) console.log(chalk7.dim(` ${line}`));
8164
+ if (line) console.log(chalk8.dim(` ${line}`));
7209
8165
  }
7210
8166
  var init_llm_attribution = __esm({
7211
8167
  "src/output/llm-attribution.ts"() {
@@ -7215,7 +8171,7 @@ var init_llm_attribution = __esm({
7215
8171
  });
7216
8172
 
7217
8173
  // src/output/terminal.ts
7218
- import chalk8 from "chalk";
8174
+ import chalk9 from "chalk";
7219
8175
  import ora from "ora";
7220
8176
  import Table2 from "cli-table3";
7221
8177
  function centerPad(text, width) {
@@ -7227,11 +8183,11 @@ function centerPad(text, width) {
7227
8183
  function statusColor(status) {
7228
8184
  switch (status) {
7229
8185
  case "green":
7230
- return chalk8.green;
8186
+ return chalk9.green;
7231
8187
  case "yellow":
7232
- return chalk8.yellow;
8188
+ return chalk9.yellow;
7233
8189
  case "red":
7234
- return chalk8.red;
8190
+ return chalk9.red;
7235
8191
  }
7236
8192
  }
7237
8193
  function statusDot(status) {
@@ -7248,12 +8204,12 @@ function statusBadge(status) {
7248
8204
  }
7249
8205
  }
7250
8206
  function printHeading(label, detail) {
7251
- console.log(` ${sectionHeading(label)}${detail ? chalk8.dim(` ${detail}`) : ""}`);
8207
+ console.log(` ${sectionHeading(label)}${detail ? chalk9.dim(` ${detail}`) : ""}`);
7252
8208
  }
7253
8209
  function printResultCard(title, rows) {
7254
8210
  const width = 70;
7255
8211
  const inner = width - 4;
7256
- const border = chalk8.dim;
8212
+ const border = chalk9.dim;
7257
8213
  console.log();
7258
8214
  console.log(` ${border(`\u256D${"\u2500".repeat(width - 2)}\u256E`)}`);
7259
8215
  console.log(` ${border("\u2502 ")}${padRight(sectionHeading(title), inner)}${border(" \u2502")}`);
@@ -7269,17 +8225,17 @@ function printVitalSignRow(vs) {
7269
8225
  const label = VITAL_SIGN_LABELS[vs.vital_sign].padEnd(18);
7270
8226
  const bar = scoreBar(vs.score, vs.status);
7271
8227
  const score = String(Math.round(vs.score)).padStart(4);
7272
- const impact = vs.dollar_value != null && vs.dollar_value > 0 ? `${chalk8.green(formatCurrency(vs.dollar_value))} ${chalk8.dim(vs.dollar_label ?? "")}` : chalk8.dim("\u2014");
7273
- console.log(` ${dot} ${label} ${bar} ${chalk8.bold(score)} ${chalk8.dim("\u2502")} ${impact}`);
8228
+ const impact = vs.dollar_value != null && vs.dollar_value > 0 ? `${chalk9.green(formatCurrency(vs.dollar_value))} ${chalk9.dim(vs.dollar_label ?? "")}` : chalk9.dim("\u2014");
8229
+ console.log(` ${dot} ${label} ${bar} ${chalk9.bold(score)} ${chalk9.dim("\u2502")} ${impact}`);
7274
8230
  }
7275
8231
  function printHealthSummary(result, _pipelineMetrics) {
7276
- const scoreStr = `${chalk8.bold(String(Math.round(result.overall_score)))}${chalk8.dim("/100")}`;
7277
- const impact = result.total_value_at_risk != null && result.total_value_at_risk > 0 ? `${chalk8.green(formatCurrency(result.total_value_at_risk))} ${chalk8.dim("total at risk")}` : chalk8.dim("No dollar-weighted risk detected");
8232
+ const scoreStr = `${chalk9.bold(String(Math.round(result.overall_score)))}${chalk9.dim("/100")}`;
8233
+ const impact = result.total_value_at_risk != null && result.total_value_at_risk > 0 ? `${chalk9.green(formatCurrency(result.total_value_at_risk))} ${chalk9.dim("total at risk")}` : chalk9.dim("No dollar-weighted risk detected");
7278
8234
  const next = result.overall_status === "red" ? actionHint("Next:", "/playbook", "review recommended plays") : result.overall_status === "yellow" ? actionHint("Next:", "/diagnose --deep", "investigate the weak signal") : actionHint("Next:", "/report", "export the clean snapshot");
7279
8235
  printResultCard("Overall Health", [
7280
- `${chalk8.dim("Score")} ${scoreStr} ${statusBadge(result.overall_status)}`,
7281
- `${chalk8.dim("Gated by")} ${paint("accent", VITAL_SIGN_LABELS[result.gating_vital_sign])}`,
7282
- `${chalk8.dim("Revenue")} ${impact}`,
8236
+ `${chalk9.dim("Score")} ${scoreStr} ${statusBadge(result.overall_status)}`,
8237
+ `${chalk9.dim("Gated by")} ${paint("accent", VITAL_SIGN_LABELS[result.gating_vital_sign])}`,
8238
+ `${chalk9.dim("Revenue")} ${impact}`,
7283
8239
  next
7284
8240
  ]);
7285
8241
  }
@@ -7300,8 +8256,8 @@ function printSegmentSummary(segments) {
7300
8256
  const dot = statusDot(seg.result.overall_status);
7301
8257
  const name = seg.segment.name.padEnd(24);
7302
8258
  const score = String(Math.round(seg.result.overall_score)).padStart(4);
7303
- const gating = chalk8.dim(VITAL_SIGN_LABELS[seg.result.gating_vital_sign]);
7304
- console.log(` ${dot} ${name} ${chalk8.bold(score)} ${chalk8.dim("\u2502")} ${gating}`);
8259
+ const gating = chalk9.dim(VITAL_SIGN_LABELS[seg.result.gating_vital_sign]);
8260
+ console.log(` ${dot} ${name} ${chalk9.bold(score)} ${chalk9.dim("\u2502")} ${gating}`);
7305
8261
  }
7306
8262
  console.log();
7307
8263
  }
@@ -7325,7 +8281,7 @@ function printTopProblems(segments, limit = 7) {
7325
8281
  if (problems.length === 0) {
7326
8282
  printHeading("Top Problems");
7327
8283
  console.log();
7328
- console.log(" " + chalk8.dim("No dollar-weighted problems found across segments."));
8284
+ console.log(" " + chalk9.dim("No dollar-weighted problems found across segments."));
7329
8285
  console.log();
7330
8286
  return;
7331
8287
  }
@@ -7338,45 +8294,45 @@ function printTopProblems(segments, limit = 7) {
7338
8294
  const labelW = Math.max("".length, ...top.map((p) => p.dollarLabel.length));
7339
8295
  const impactW = dollarW + 2 + labelW;
7340
8296
  console.log(
7341
- ` ${sectionHeading("Top Problems")}` + chalk8.dim(` (${top.length} of ${problems.length})`)
8297
+ ` ${sectionHeading("Top Problems")}` + chalk9.dim(` (${top.length} of ${problems.length})`)
7342
8298
  );
7343
8299
  console.log();
7344
8300
  const segColW = 2 + segW;
7345
8301
  const hSeg = centerPad("Segment", segColW);
7346
8302
  const hVital = centerPad("Vital Sign", vitalW);
7347
8303
  const hImpact = centerPad("Revenue Impact", Math.max(impactW, "Revenue Impact".length));
7348
- console.log(` ${chalk8.dim(hSeg)} ${chalk8.dim(hVital)} ${chalk8.dim(hImpact)}`);
8304
+ console.log(` ${chalk9.dim(hSeg)} ${chalk9.dim(hVital)} ${chalk9.dim(hImpact)}`);
7349
8305
  console.log();
7350
8306
  for (let i = 0; i < top.length; i++) {
7351
8307
  const p = top[i];
7352
8308
  const dot = statusDot(p.status);
7353
8309
  const seg = p.segment.padEnd(segW);
7354
8310
  const vital = p.vitalSignLabel.padEnd(vitalW);
7355
- const dollar = chalk8.green(dollarStrs[i].padStart(dollarW));
7356
- const label = chalk8.dim(p.dollarLabel);
8311
+ const dollar = chalk9.green(dollarStrs[i].padStart(dollarW));
8312
+ const label = chalk9.dim(p.dollarLabel);
7357
8313
  console.log(` ${dot} ${seg} ${vital} ${dollar} ${label}`);
7358
8314
  }
7359
8315
  if (problems.length > top.length) {
7360
8316
  console.log();
7361
- console.log(` ${chalk8.dim("Run /diagnose --segment <name> to drill in")}`);
8317
+ console.log(` ${chalk9.dim("Run /diagnose --segment <name> to drill in")}`);
7362
8318
  }
7363
8319
  console.log();
7364
8320
  }
7365
8321
  function printFindings(findings) {
7366
8322
  if (findings.length === 0) {
7367
- console.log(chalk8.dim(" No findings generated."));
8323
+ console.log(chalk9.dim(" No findings generated."));
7368
8324
  return;
7369
8325
  }
7370
8326
  for (const finding of findings) {
7371
- const sevColor = finding.severity === "critical" ? chalk8.red : finding.severity === "warning" ? chalk8.yellow : chalk8.blue;
8327
+ const sevColor = finding.severity === "critical" ? chalk9.red : finding.severity === "warning" ? chalk9.yellow : chalk9.blue;
7372
8328
  const dot = sevColor("\u25CF");
7373
- const dollarTag = finding.dollar_value != null && finding.dollar_value > 0 ? ` ${chalk8.dim("\xB7")} ${chalk8.green(formatDollarValue(finding.dollar_value))}` : "";
7374
- console.log(` ${dot} ${chalk8.bold(finding.segment)}${dollarTag}`);
8329
+ const dollarTag = finding.dollar_value != null && finding.dollar_value > 0 ? ` ${chalk9.dim("\xB7")} ${chalk9.green(formatDollarValue(finding.dollar_value))}` : "";
8330
+ console.log(` ${dot} ${chalk9.bold(finding.segment)}${dollarTag}`);
7375
8331
  printMarkdown(finding.finding, { indent: 2 });
7376
8332
  if (finding.recommended_plays && finding.recommended_plays.length > 0) {
7377
8333
  for (const play of finding.recommended_plays) {
7378
8334
  console.log(
7379
- ` ${chalk8.dim("Consider:")} ${paint("accent", play.play_name)} ${chalk8.dim("\u2192")} ${chalk8.dim("/playbook " + play.play_id)}`
8335
+ ` ${chalk9.dim("Consider:")} ${paint("accent", play.play_name)} ${chalk9.dim("\u2192")} ${chalk9.dim("/playbook " + play.play_id)}`
7380
8336
  );
7381
8337
  }
7382
8338
  }
@@ -7385,7 +8341,7 @@ function printFindings(findings) {
7385
8341
  }
7386
8342
  function printEntityCounts(counts) {
7387
8343
  const table = new Table2({
7388
- head: [chalk8.dim("Entity"), chalk8.dim("Count")],
8344
+ head: [chalk9.dim("Entity"), chalk9.dim("Count")],
7389
8345
  colWidths: [20, 12],
7390
8346
  style: { head: [], border: [] }
7391
8347
  });
@@ -7400,19 +8356,19 @@ function printSegmentDetail(seg, aggregate) {
7400
8356
  console.log();
7401
8357
  printHeading(seg.segment.name);
7402
8358
  console.log(
7403
- ` ${statusDot(seg.result.overall_status)} ${color(chalk8.bold(formatScore(seg.result.overall_score)))}${chalk8.dim("/100")} ${chalk8.dim("Gated by:")} ${paint("accent", VITAL_SIGN_LABELS[seg.result.gating_vital_sign])}`
8359
+ ` ${statusDot(seg.result.overall_status)} ${color(chalk9.bold(formatScore(seg.result.overall_score)))}${chalk9.dim("/100")} ${chalk9.dim("Gated by:")} ${paint("accent", VITAL_SIGN_LABELS[seg.result.gating_vital_sign])}`
7404
8360
  );
7405
8361
  console.log();
7406
8362
  for (const vs of seg.result.vital_signs) {
7407
8363
  const aggVs = aggregate.vital_signs.find((a) => a.vital_sign === vs.vital_sign);
7408
8364
  const delta = aggVs ? vs.score - aggVs.score : 0;
7409
- const deltaStr = delta >= 0 ? chalk8.green(`+${Math.round(delta)}`) : chalk8.red(`${Math.round(delta)}`);
8365
+ const deltaStr = delta >= 0 ? chalk9.green(`+${Math.round(delta)}`) : chalk9.red(`${Math.round(delta)}`);
7410
8366
  const dot = statusDot(vs.status);
7411
8367
  const label = VITAL_SIGN_LABELS[vs.vital_sign].padEnd(18);
7412
8368
  const bar = scoreBar(vs.score, vs.status);
7413
8369
  const score = String(Math.round(vs.score)).padStart(4);
7414
- const impact = vs.dollar_value != null && vs.dollar_value > 0 ? `${chalk8.green(formatCurrency(vs.dollar_value))} ${chalk8.dim(vs.dollar_label ?? "")}` : chalk8.dim("\u2014");
7415
- console.log(` ${dot} ${label} ${bar} ${chalk8.bold(score)} ${deltaStr.padStart(12)} ${chalk8.dim("\u2502")} ${impact}`);
8370
+ const impact = vs.dollar_value != null && vs.dollar_value > 0 ? `${chalk9.green(formatCurrency(vs.dollar_value))} ${chalk9.dim(vs.dollar_label ?? "")}` : chalk9.dim("\u2014");
8371
+ console.log(` ${dot} ${label} ${bar} ${chalk9.bold(score)} ${deltaStr.padStart(12)} ${chalk9.dim("\u2502")} ${impact}`);
7416
8372
  }
7417
8373
  console.log();
7418
8374
  }
@@ -7496,7 +8452,7 @@ async function renderDiagnoseStream(options) {
7496
8452
  }
7497
8453
  findingsSpinner.stop();
7498
8454
  console.log(
7499
- chalk8.dim(` Investigating... called ${toolCalls} tools across ${iterations} iterations`)
8455
+ chalk9.dim(` Investigating... called ${toolCalls} tools across ${iterations} iterations`)
7500
8456
  );
7501
8457
  console.log();
7502
8458
  if (storeFindings) {
@@ -7518,7 +8474,7 @@ async function renderDiagnoseStream(options) {
7518
8474
  });
7519
8475
  } catch (err) {
7520
8476
  findingsSpinner.fail(deep ? "Agentic investigation failed" : "AI findings failed");
7521
- console.error(chalk8.dim(String(err)));
8477
+ console.error(chalk9.dim(String(err)));
7522
8478
  }
7523
8479
  }
7524
8480
  return { fullResult, findings: collectedFindings };
@@ -7526,13 +8482,13 @@ async function renderDiagnoseStream(options) {
7526
8482
  function metricStatusDot(status) {
7527
8483
  switch (status) {
7528
8484
  case "green":
7529
- return chalk8.green("\u25CF");
8485
+ return chalk9.green("\u25CF");
7530
8486
  case "yellow":
7531
- return chalk8.yellow("\u25CF");
8487
+ return chalk9.yellow("\u25CF");
7532
8488
  case "red":
7533
- return chalk8.red("\u25CF");
8489
+ return chalk9.red("\u25CF");
7534
8490
  case "neutral":
7535
- return chalk8.dim("\u25CB");
8491
+ return chalk9.dim("\u25CB");
7536
8492
  }
7537
8493
  }
7538
8494
  function printMetricsTable(metrics, groupOrder) {
@@ -7542,16 +8498,16 @@ function printMetricsTable(metrics, groupOrder) {
7542
8498
  printHeading(group);
7543
8499
  console.log();
7544
8500
  for (const m of groupMetrics) {
7545
- const dot = m.unavailable_reason ? chalk8.dim("\u25CB") : metricStatusDot(m.status);
8501
+ const dot = m.unavailable_reason ? chalk9.dim("\u25CB") : metricStatusDot(m.status);
7546
8502
  const label = m.label.padEnd(28);
7547
- const valueStr = m.unavailable_reason ? chalk8.dim("--") : chalk8.bold(m.formatted);
7548
- const confTag = m.confidence != null && m.confidence < 80 && m.confidence_label ? chalk8.yellow(` ${m.confidence_label} (${m.confidence})`) : m.confidence != null && m.confidence_label === "high" ? chalk8.dim(" \u2713") : "";
7549
- const note = m.unavailable_reason ? chalk8.dim(m.unavailable_reason) : m.benchmark_note ? chalk8.dim(m.benchmark_note) : "";
8503
+ const valueStr = m.unavailable_reason ? chalk9.dim("--") : chalk9.bold(m.formatted);
8504
+ const confTag = m.confidence != null && m.confidence < 80 && m.confidence_label ? chalk9.yellow(` ${m.confidence_label} (${m.confidence})`) : m.confidence != null && m.confidence_label === "high" ? chalk9.dim(" \u2713") : "";
8505
+ const note = m.unavailable_reason ? chalk9.dim(m.unavailable_reason) : m.benchmark_note ? chalk9.dim(m.benchmark_note) : "";
7550
8506
  console.log(` ${dot} ${label} ${valueStr}${confTag}${note ? " " + note : ""}`);
7551
8507
  if (m.reliability_gate?.requirements?.length && (m.confidence ?? 100) < 80) {
7552
8508
  const gate = m.reliability_gate.requirements[0];
7553
8509
  if (gate) {
7554
- console.log(chalk8.dim(` \u2514 Gate: ${gate}`));
8510
+ console.log(chalk9.dim(` \u2514 Gate: ${gate}`));
7555
8511
  }
7556
8512
  }
7557
8513
  }
@@ -7575,15 +8531,15 @@ __export(metrics_report_exports, {
7575
8531
  printMetricsNextSteps: () => printMetricsNextSteps,
7576
8532
  renderMetricsReport: () => renderMetricsReport
7577
8533
  });
7578
- import chalk9 from "chalk";
8534
+ import chalk10 from "chalk";
7579
8535
  function renderMetricsReport(ctx, metrics, coverage, sourceType, options = {}) {
7580
8536
  const title = options.title ?? "SaaS Metrics Analysis";
7581
8537
  const tier = coverageTier(coverage);
7582
8538
  const deterministic = buildDeterministicInsights(metrics, coverage, sourceType);
7583
8539
  const headline = pickHeadlineInsight(deterministic);
7584
8540
  console.log();
7585
- console.log(chalk9.bold(` ${title}`));
7586
- console.log(" " + chalk9.dim(formatCoverageHeader(sourceType, coverage)));
8541
+ console.log(chalk10.bold(` ${title}`));
8542
+ console.log(" " + chalk10.dim(formatCoverageHeader(sourceType, coverage)));
7587
8543
  console.log();
7588
8544
  printDataQualityPanel(coverage, sourceType, tier);
7589
8545
  if (options.snapshot && coverage.distinct_quarters >= 2) {
@@ -7591,7 +8547,7 @@ function renderMetricsReport(ctx, metrics, coverage, sourceType, options = {}) {
7591
8547
  }
7592
8548
  if (headline) {
7593
8549
  console.log(" " + paint("warning", "\u25B8 Headline"));
7594
- console.log(" " + chalk9.white(wrapInsight(headline)));
8550
+ console.log(" " + chalk10.white(wrapInsight(headline)));
7595
8551
  console.log();
7596
8552
  }
7597
8553
  printMetricsTable(metrics, GROUP_ORDER);
@@ -7599,9 +8555,9 @@ function renderMetricsReport(ctx, metrics, coverage, sourceType, options = {}) {
7599
8555
  console.log(" " + bold("Pattern checks"));
7600
8556
  console.log();
7601
8557
  for (const insight of deterministic.slice(0, 5)) {
7602
- const dot = insight.severity === "warning" ? chalk9.yellow("\u25CF") : insight.severity === "critical" ? chalk9.red("\u25CF") : chalk9.dim("\u25CB");
8558
+ const dot = insight.severity === "warning" ? chalk10.yellow("\u25CF") : insight.severity === "critical" ? chalk10.red("\u25CF") : chalk10.dim("\u25CB");
7603
8559
  if (insight.headline) continue;
7604
- console.log(` ${dot} ${chalk9.dim(wrapInsight(insight.message))}`);
8560
+ console.log(` ${dot} ${chalk10.dim(wrapInsight(insight.message))}`);
7605
8561
  }
7606
8562
  console.log();
7607
8563
  }
@@ -7630,7 +8586,7 @@ function printDataQualityPanel(coverage, sourceType, tier) {
7630
8586
  rows.push(["Ledger", "not loaded \u2014 retention inferred from CRM"]);
7631
8587
  }
7632
8588
  for (const [label, value] of rows) {
7633
- console.log(` ${chalk9.dim(String(label).padEnd(14))} ${value}`);
8589
+ console.log(` ${chalk10.dim(String(label).padEnd(14))} ${value}`);
7634
8590
  }
7635
8591
  console.log();
7636
8592
  }
@@ -7641,10 +8597,10 @@ function printCloseTrend(snapshot, cadence) {
7641
8597
  console.log(" " + bold(`Close trend (${cadence})`));
7642
8598
  console.log();
7643
8599
  for (const b of recent) {
7644
- const newStr = b.new_arr > 0 ? chalk9.dim(` new $${formatShort(b.new_arr)}`) : "";
7645
- const expStr = b.expansion_arr > 0 ? chalk9.dim(` exp $${formatShort(b.expansion_arr)}`) : "";
8600
+ const newStr = b.new_arr > 0 ? chalk10.dim(` new $${formatShort(b.new_arr)}`) : "";
8601
+ const expStr = b.expansion_arr > 0 ? chalk10.dim(` exp $${formatShort(b.expansion_arr)}`) : "";
7646
8602
  console.log(
7647
- ` ${chalk9.dim(b.period.padEnd(8))} ${chalk9.bold("$" + formatShort(b.closed_won_total))}${newStr}${expStr} ${chalk9.dim(`(${b.closed_won_count} deals)`)}`
8603
+ ` ${chalk10.dim(b.period.padEnd(8))} ${chalk10.bold("$" + formatShort(b.closed_won_total))}${newStr}${expStr} ${chalk10.dim(`(${b.closed_won_count} deals)`)}`
7648
8604
  );
7649
8605
  }
7650
8606
  console.log();
@@ -8156,7 +9112,7 @@ var diagnose_exports = {};
8156
9112
  __export(diagnose_exports, {
8157
9113
  handler: () => handler
8158
9114
  });
8159
- import chalk10 from "chalk";
9115
+ import chalk11 from "chalk";
8160
9116
  import ora2 from "ora";
8161
9117
  async function handler(args, ctx) {
8162
9118
  await hydrateAnalysisFromPersistedState(ctx);
@@ -8185,9 +9141,9 @@ async function handler(args, ctx) {
8185
9141
  }
8186
9142
  if (options.findings && !canUseReplAi(ctx)) {
8187
9143
  console.log();
8188
- console.log(" " + chalk10.red("AI findings run only in the interactive REPL."));
8189
- console.log(" " + chalk10.dim("Vital signs compute without a key \u2014 omit --findings for numbers only."));
8190
- console.log(" " + chalk10.dim("Start with ") + paint("accent", "ntrp") + chalk10.dim(", set ") + paint("accent", "/config set api-key") + chalk10.dim(" or openai-api-key, then /diagnose --findings."));
9144
+ console.log(" " + chalk11.red("AI findings run only in the interactive REPL."));
9145
+ console.log(" " + chalk11.dim("Vital signs compute without a key \u2014 omit --findings for numbers only."));
9146
+ console.log(" " + chalk11.dim("Start with ") + paint("accent", "ntrp") + chalk11.dim(", set ") + paint("accent", "/config set api-key") + chalk11.dim(" or openai-api-key, then /diagnose --findings."));
8191
9147
  console.log();
8192
9148
  return;
8193
9149
  }
@@ -8213,8 +9169,12 @@ async function handler(args, ctx) {
8213
9169
  const companion = await resolveCompanionRecommendation(ctx);
8214
9170
  printCompanionFooter(ctx, companion, { justCompleted: "gtm_health" });
8215
9171
  }
9172
+ if (!ctx.skipTimeBankDiagnoseCredit) {
9173
+ creditDiagnoseComplete(ctx, options.findings);
9174
+ }
9175
+ ctx.skipTimeBankDiagnoseCredit = false;
8216
9176
  if (ctx.oneShot && options.findings) {
8217
- console.log(chalk10.dim(" For follow-up questions, run `ntrp` and ask in plain English."));
9177
+ console.log(chalk11.dim(" For follow-up questions, run `ntrp` and ask in plain English."));
8218
9178
  console.log();
8219
9179
  }
8220
9180
  return summary;
@@ -8271,7 +9231,7 @@ async function runDiagnose(options, ctx) {
8271
9231
  });
8272
9232
  return buildDiagnoseSummary(fullResult.aggregate, findings);
8273
9233
  } catch (err) {
8274
- console.error(chalk10.red(String(err)));
9234
+ console.error(chalk11.red(String(err)));
8275
9235
  process.exit(1);
8276
9236
  }
8277
9237
  }
@@ -8283,7 +9243,7 @@ async function runSegmentDiagnose(options) {
8283
9243
  spinner.succeed("Diagnosis complete");
8284
9244
  } catch (err) {
8285
9245
  spinner.fail("Diagnosis failed");
8286
- console.error(chalk10.red(String(err)));
9246
+ console.error(chalk11.red(String(err)));
8287
9247
  process.exit(1);
8288
9248
  }
8289
9249
  const needle = options.segment.toLowerCase();
@@ -8292,19 +9252,19 @@ async function runSegmentDiagnose(options) {
8292
9252
  const subs = result.segments.filter((s) => s.segment.name.toLowerCase().includes(needle));
8293
9253
  if (subs.length === 1) match = subs[0];
8294
9254
  else if (subs.length > 1) {
8295
- console.error(chalk10.yellow(`
9255
+ console.error(chalk11.yellow(`
8296
9256
  "${options.segment}" matches multiple segments:`));
8297
- for (const s of subs) console.log(chalk10.dim(` - ${s.segment.name}`));
9257
+ for (const s of subs) console.log(chalk11.dim(` - ${s.segment.name}`));
8298
9258
  console.log();
8299
9259
  return;
8300
9260
  }
8301
9261
  }
8302
9262
  if (!match) {
8303
- console.error(chalk10.red(`
9263
+ console.error(chalk11.red(`
8304
9264
  No segment matching "${options.segment}".`));
8305
9265
  if (result.segments.length > 0) {
8306
- console.log(chalk10.dim(" Available segments:"));
8307
- for (const s of result.segments) console.log(chalk10.dim(` - ${s.segment.name}`));
9266
+ console.log(chalk11.dim(" Available segments:"));
9267
+ for (const s of result.segments) console.log(chalk11.dim(` - ${s.segment.name}`));
8308
9268
  }
8309
9269
  console.log();
8310
9270
  return;
@@ -8353,6 +9313,7 @@ var init_diagnose = __esm({
8353
9313
  init_serialize();
8354
9314
  init_theme();
8355
9315
  init_companion();
9316
+ init_time_bank();
8356
9317
  }
8357
9318
  });
8358
9319
 
@@ -8363,7 +9324,7 @@ __export(compute_exports2, {
8363
9324
  runConversationCompute: () => runConversationCompute
8364
9325
  });
8365
9326
  import ora3 from "ora";
8366
- import chalk11 from "chalk";
9327
+ import chalk12 from "chalk";
8367
9328
  async function runConversationCompute(ctx) {
8368
9329
  const lens = ctx.scope?.primary_lens ?? ctx.analysis.primary;
8369
9330
  ctx.computeInProgress = true;
@@ -8386,6 +9347,8 @@ async function runConversationCompute(ctx) {
8386
9347
  ctx.snapshot.computeResult = null;
8387
9348
  invalidateGapAudit(ctx);
8388
9349
  saveSessionState(ctx);
9350
+ creditGapCompute(ctx);
9351
+ creditMetricsComplete(ctx, false);
8389
9352
  renderMetricsReport2(ctx, result.metrics.aggregate.metrics, result.coverage, result.data_source_type, {
8390
9353
  snapshot: result.snapshot,
8391
9354
  findings: result.findings,
@@ -8399,6 +9362,8 @@ async function runConversationCompute(ctx) {
8399
9362
  }
8400
9363
  }
8401
9364
  const { handler: diagnose } = await Promise.resolve().then(() => (init_diagnose(), diagnose_exports));
9365
+ ctx.skipTimeBankDiagnoseCredit = true;
9366
+ creditGapCompute(ctx);
8402
9367
  const summary = await diagnose([], ctx);
8403
9368
  markLensCompleted(ctx, "gtm_health");
8404
9369
  ctx.stage = "analyzed";
@@ -8409,7 +9374,7 @@ async function runConversationCompute(ctx) {
8409
9374
  printCompanionFooter(ctx, companion, { justCompleted: "gtm_health" });
8410
9375
  return typeof summary === "string" ? summary : "Health analysis ready";
8411
9376
  } catch (err) {
8412
- console.error(" " + chalk11.red(String(err.message ?? err)));
9377
+ console.error(" " + chalk12.red(String(err.message ?? err)));
8413
9378
  return;
8414
9379
  } finally {
8415
9380
  ctx.computeInProgress = false;
@@ -8425,16 +9390,17 @@ var init_compute2 = __esm({
8425
9390
  init_context2();
8426
9391
  init_gap_audit();
8427
9392
  init_companion();
9393
+ init_time_bank();
8428
9394
  }
8429
9395
  });
8430
9396
 
8431
9397
  // src/config/demo.ts
8432
- import chalk12 from "chalk";
9398
+ import chalk13 from "chalk";
8433
9399
  function printDemoDisabled() {
8434
9400
  console.log();
8435
- console.log(" " + chalk12.red(DEMO_DISABLED_MESSAGE));
9401
+ console.log(" " + chalk13.red(DEMO_DISABLED_MESSAGE));
8436
9402
  console.log(
8437
- " " + chalk12.dim("Re-enable with ") + paint("accent", "/config set demo-enabled true") + chalk12.dim(".")
9403
+ " " + chalk13.dim("Re-enable with ") + paint("accent", "/config set demo-enabled true") + chalk13.dim(".")
8438
9404
  );
8439
9405
  console.log();
8440
9406
  }
@@ -11182,18 +12148,18 @@ var init_generator = __esm({
11182
12148
  });
11183
12149
 
11184
12150
  // src/demo/taxonomy-cache.ts
11185
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync7, mkdirSync as mkdirSync6, unlinkSync } from "fs";
12151
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, existsSync as existsSync10, mkdirSync as mkdirSync8, unlinkSync as unlinkSync3 } from "fs";
11186
12152
  import { homedir as homedir4 } from "os";
11187
- import { join as join7 } from "path";
11188
- function ensureDir3() {
11189
- if (!existsSync7(NTRP_DIR4)) {
11190
- mkdirSync6(NTRP_DIR4, { recursive: true });
12153
+ import { join as join10 } from "path";
12154
+ function ensureDir5() {
12155
+ if (!existsSync10(NTRP_DIR4)) {
12156
+ mkdirSync8(NTRP_DIR4, { recursive: true });
11191
12157
  }
11192
12158
  }
11193
12159
  function loadCachedTaxonomy(profile) {
11194
- if (!existsSync7(TAXONOMY_PATH)) return null;
12160
+ if (!existsSync10(TAXONOMY_PATH)) return null;
11195
12161
  try {
11196
- const parsed = JSON.parse(readFileSync5(TAXONOMY_PATH, "utf-8"));
12162
+ const parsed = JSON.parse(readFileSync8(TAXONOMY_PATH, "utf-8"));
11197
12163
  if (!parsed || typeof parsed !== "object") return null;
11198
12164
  if (parsed.profile_updated_at !== profile.updated_at) return null;
11199
12165
  return parsed;
@@ -11202,15 +12168,15 @@ function loadCachedTaxonomy(profile) {
11202
12168
  }
11203
12169
  }
11204
12170
  function saveCachedTaxonomy(taxonomy) {
11205
- ensureDir3();
11206
- writeFileSync4(TAXONOMY_PATH, JSON.stringify(taxonomy, null, 2) + "\n");
12171
+ ensureDir5();
12172
+ writeFileSync7(TAXONOMY_PATH, JSON.stringify(taxonomy, null, 2) + "\n");
11207
12173
  }
11208
12174
  var NTRP_DIR4, TAXONOMY_PATH;
11209
12175
  var init_taxonomy_cache = __esm({
11210
12176
  "src/demo/taxonomy-cache.ts"() {
11211
12177
  "use strict";
11212
- NTRP_DIR4 = join7(homedir4(), ".ntrp");
11213
- TAXONOMY_PATH = join7(NTRP_DIR4, "demo-taxonomy.json");
12178
+ NTRP_DIR4 = join10(homedir4(), ".ntrp");
12179
+ TAXONOMY_PATH = join10(NTRP_DIR4, "demo-taxonomy.json");
11214
12180
  }
11215
12181
  });
11216
12182
 
@@ -11446,16 +12412,16 @@ var generate_exports = {};
11446
12412
  __export(generate_exports, {
11447
12413
  handler: () => handler2
11448
12414
  });
11449
- import chalk13 from "chalk";
12415
+ import chalk14 from "chalk";
11450
12416
  import ora4 from "ora";
11451
12417
  async function handler2(args, ctx) {
11452
12418
  const { flags } = parseArgs(args, ["list-scenarios", "regen-taxonomy"]);
11453
12419
  const quiet = ctx.execution.quiet;
11454
12420
  if (getBool(flags, "list-scenarios")) {
11455
- console.log(chalk13.bold("\n Available Scenarios:\n"));
12421
+ console.log(chalk14.bold("\n Available Scenarios:\n"));
11456
12422
  for (const s of SCENARIO_LIST) {
11457
- console.log(` ${chalk13.cyan(s.key.padEnd(20))} ${s.label}`);
11458
- console.log(` ${chalk13.dim(" ".repeat(20))} ${s.description}
12423
+ console.log(` ${chalk14.cyan(s.key.padEnd(20))} ${s.label}`);
12424
+ console.log(` ${chalk14.dim(" ".repeat(20))} ${s.description}
11459
12425
  `);
11460
12426
  }
11461
12427
  return true;
@@ -11465,9 +12431,9 @@ async function handler2(args, ctx) {
11465
12431
  const skipProfile = getFalse(flags, "profile");
11466
12432
  if (!isProfileConfigured(profile) && !skipProfile) {
11467
12433
  console.error();
11468
- console.error(" " + chalk13.red("No company profile found."));
11469
- console.error(" " + chalk13.dim("Run ") + paint("accent", "/onboard") + chalk13.dim(" first for a richer demo,"));
11470
- console.error(" " + chalk13.dim("or pass ") + paint("accent", "--no-profile") + chalk13.dim(" to skip."));
12434
+ console.error(" " + chalk14.red("No company profile found."));
12435
+ console.error(" " + chalk14.dim("Run ") + paint("accent", "/onboard") + chalk14.dim(" first for a richer demo,"));
12436
+ console.error(" " + chalk14.dim("or pass ") + paint("accent", "--no-profile") + chalk14.dim(" to skip."));
11471
12437
  console.error();
11472
12438
  markFailure(ctx);
11473
12439
  return false;
@@ -11475,8 +12441,8 @@ async function handler2(args, ctx) {
11475
12441
  const explicitScenario = getString(flags, "scenario", "s");
11476
12442
  const resolvedScenario = resolveScenarioInput(explicitScenario);
11477
12443
  if (resolvedScenario === null) {
11478
- console.error(chalk13.red(` Unknown scenario: ${explicitScenario}`));
11479
- console.log(chalk13.dim(` Valid: ${SCENARIO_LIST.map((s) => s.key).join(", ")}`));
12444
+ console.error(chalk14.red(` Unknown scenario: ${explicitScenario}`));
12445
+ console.log(chalk14.dim(` Valid: ${SCENARIO_LIST.map((s) => s.key).join(", ")}`));
11480
12446
  markFailure(ctx);
11481
12447
  return false;
11482
12448
  }
@@ -11490,7 +12456,7 @@ async function handler2(args, ctx) {
11490
12456
  const s = getScenario(scenario);
11491
12457
  console.log();
11492
12458
  console.log(" " + paint("accent", "\u2713 Scenario: ") + bold(s.label));
11493
- console.log(" " + chalk13.dim(s.story));
12459
+ console.log(" " + chalk14.dim(s.story));
11494
12460
  console.log();
11495
12461
  }
11496
12462
  const spinner = quiet ? null : ora4({ text: "Initializing database...", discardStdin: false }).start();
@@ -11516,17 +12482,17 @@ async function handler2(args, ctx) {
11516
12482
  const result = await generateDemoData(config);
11517
12483
  if (result.mode === "direct") {
11518
12484
  if (spinner) {
11519
- spinner.succeed(`Generated demo data for "${chalk13.cyan(scenario)}" scenario`);
12485
+ spinner.succeed(`Generated demo data for "${chalk14.cyan(scenario)}" scenario`);
11520
12486
  console.log();
11521
12487
  printEntityCounts(result.counts);
11522
12488
  }
11523
12489
  if (!quiet && ctx.analysis.primary !== "revenue_metrics") {
11524
- console.log(chalk13.dim("\n Run /diagnose to compute vital signs.\n"));
12490
+ console.log(chalk14.dim("\n Run /diagnose to compute vital signs.\n"));
11525
12491
  }
11526
12492
  }
11527
12493
  } catch (err) {
11528
12494
  if (spinner) spinner.fail("Generation failed");
11529
- console.error(chalk13.red(String(err)));
12495
+ console.error(chalk14.red(String(err)));
11530
12496
  markFailure(ctx);
11531
12497
  return false;
11532
12498
  }
@@ -11551,7 +12517,7 @@ async function loadOrBuildTaxonomy(profile, forceRegen, ctx) {
11551
12517
  return taxonomy;
11552
12518
  } catch (err) {
11553
12519
  spinner.fail("Couldn't build market taxonomy \u2014 using generic data pools");
11554
- console.log(" " + chalk13.dim(String(err.message ?? err)));
12520
+ console.log(" " + chalk14.dim(String(err.message ?? err)));
11555
12521
  return void 0;
11556
12522
  }
11557
12523
  }
@@ -11641,9 +12607,9 @@ var ingest_exports = {};
11641
12607
  __export(ingest_exports, {
11642
12608
  handler: () => handler3
11643
12609
  });
11644
- import chalk14 from "chalk";
12610
+ import chalk15 from "chalk";
11645
12611
  import ora5 from "ora";
11646
- import { readFileSync as readFileSync6, existsSync as existsSync8 } from "fs";
12612
+ import { readFileSync as readFileSync9, existsSync as existsSync11 } from "fs";
11647
12613
  import { basename as basename2 } from "path";
11648
12614
  async function handler3(args, ctx) {
11649
12615
  const { positional, flags } = parseArgs(args, [
@@ -11663,21 +12629,21 @@ async function handler3(args, ctx) {
11663
12629
  const source = getString(flags, "source", "s") ?? "salesforce";
11664
12630
  const skipResolve = getBool(flags, "skip-resolve");
11665
12631
  if (!file) {
11666
- console.error(chalk14.red(" Usage: /ingest <file> [--source salesforce|hubspot|outreach]"));
11667
- console.error(chalk14.dim(" /ingest --demo [--scenario <name>]"));
12632
+ console.error(chalk15.red(" Usage: /ingest <file> [--source salesforce|hubspot|outreach]"));
12633
+ console.error(chalk15.dim(" /ingest --demo [--scenario <name>]"));
11668
12634
  process.exit(1);
11669
12635
  }
11670
- if (!existsSync8(file)) {
11671
- console.error(chalk14.red(` File not found: ${file}`));
12636
+ if (!existsSync11(file)) {
12637
+ console.error(chalk15.red(` File not found: ${file}`));
11672
12638
  process.exit(1);
11673
12639
  }
11674
12640
  const profile = loadProfile();
11675
12641
  const skipProfile = getFalse(flags, "profile");
11676
12642
  if (!profile && !skipProfile) {
11677
12643
  console.error();
11678
- console.error(" " + chalk14.red("No company profile found."));
11679
- console.error(" " + chalk14.dim("Run ") + paint("accent", "/onboard") + chalk14.dim(" first for better column mapping,"));
11680
- console.error(" " + chalk14.dim("or pass ") + paint("accent", "--no-profile") + chalk14.dim(" to skip."));
12644
+ console.error(" " + chalk15.red("No company profile found."));
12645
+ console.error(" " + chalk15.dim("Run ") + paint("accent", "/onboard") + chalk15.dim(" first for better column mapping,"));
12646
+ console.error(" " + chalk15.dim("or pass ") + paint("accent", "--no-profile") + chalk15.dim(" to skip."));
11681
12647
  console.error();
11682
12648
  process.exit(1);
11683
12649
  }
@@ -11685,7 +12651,7 @@ async function handler3(args, ctx) {
11685
12651
  try {
11686
12652
  await initSchema();
11687
12653
  spinner.text = "Parsing CSV...";
11688
- const content = readFileSync6(file, "utf-8");
12654
+ const content = readFileSync9(file, "utf-8");
11689
12655
  const { rows, headers } = parseCSV(content);
11690
12656
  if (rows.length === 0) {
11691
12657
  spinner.fail("CSV is empty");
@@ -11709,22 +12675,22 @@ async function handler3(args, ctx) {
11709
12675
  row_count: result2.imported
11710
12676
  });
11711
12677
  spinner.succeed(
11712
- `Imported ${chalk14.bold(result2.imported.toString())} revenue events from ${chalk14.dim(basename2(file))}`
12678
+ `Imported ${chalk15.bold(result2.imported.toString())} revenue events from ${chalk15.dim(basename2(file))}`
11713
12679
  );
11714
12680
  if (result2.errors.length > 0) {
11715
- console.log(chalk14.yellow(` ${result2.errors.length} rows skipped`));
12681
+ console.log(chalk15.yellow(` ${result2.errors.length} rows skipped`));
11716
12682
  }
11717
12683
  if (ctx.analysis) {
11718
12684
  ctx.analysis.data_source_type = "revenue_ledger";
11719
12685
  }
11720
- console.log(chalk14.dim(" Run ") + chalk14.cyan("/metrics") + chalk14.dim(" for SaaS metrics with ledger-backed retention."));
12686
+ console.log(chalk15.dim(" Run ") + chalk15.cyan("/metrics") + chalk15.dim(" for SaaS metrics with ledger-backed retention."));
11721
12687
  return `${result2.imported} revenue events from ${basename2(file)}`;
11722
12688
  }
11723
12689
  spinner.text = "Detecting entity type...";
11724
12690
  const detection = detectEntityType(headers, source);
11725
12691
  if (!detection) {
11726
12692
  spinner.fail(`Could not auto-detect entity type for source: ${source}`);
11727
- console.log(chalk14.dim(" Headers found: " + headers.join(", ")));
12693
+ console.log(chalk15.dim(" Headers found: " + headers.join(", ")));
11728
12694
  process.exit(1);
11729
12695
  }
11730
12696
  spinner.text = `Importing ${rows.length} ${detection.entityType} rows...`;
@@ -11747,15 +12713,15 @@ async function handler3(args, ctx) {
11747
12713
  row_count: result.imported
11748
12714
  });
11749
12715
  spinner.succeed(
11750
- `Imported ${chalk14.bold(result.imported.toString())} ${detection.entityType} from ${chalk14.dim(basename2(file))} (${source})`
12716
+ `Imported ${chalk15.bold(result.imported.toString())} ${detection.entityType} from ${chalk15.dim(basename2(file))} (${source})`
11751
12717
  );
11752
12718
  if (result.errors.length > 0) {
11753
- console.log(chalk14.yellow(` ${result.errors.length} rows skipped`));
12719
+ console.log(chalk15.yellow(` ${result.errors.length} rows skipped`));
11754
12720
  for (const err of result.errors.slice(0, 3)) {
11755
- console.log(chalk14.dim(` - ${err}`));
12721
+ console.log(chalk15.dim(` - ${err}`));
11756
12722
  }
11757
12723
  if (result.errors.length > 3) {
11758
- console.log(chalk14.dim(` ... and ${result.errors.length - 3} more`));
12724
+ console.log(chalk15.dim(` ... and ${result.errors.length - 3} more`));
11759
12725
  }
11760
12726
  }
11761
12727
  if (!skipResolve) {
@@ -11772,7 +12738,7 @@ async function handler3(args, ctx) {
11772
12738
  return `${result.imported} ${detection.entityType} from ${basename2(file)}`;
11773
12739
  } catch (err) {
11774
12740
  spinner.fail("Import failed");
11775
- console.error(chalk14.red(String(err)));
12741
+ console.error(chalk15.red(String(err)));
11776
12742
  process.exit(1);
11777
12743
  }
11778
12744
  }
@@ -11801,10 +12767,10 @@ __export(ingest_chat_exports, {
11801
12767
  loadDemoFromChat: () => loadDemoFromChat,
11802
12768
  looksLikeFilePath: () => looksLikeFilePath
11803
12769
  });
11804
- import { existsSync as existsSync9 } from "fs";
12770
+ import { existsSync as existsSync12 } from "fs";
11805
12771
  import { basename as basename3, resolve as resolve4 } from "path";
11806
12772
  import { homedir as homedir5 } from "os";
11807
- import chalk15 from "chalk";
12773
+ import chalk16 from "chalk";
11808
12774
  function extractFilePath(input) {
11809
12775
  const trimmed = input.trim();
11810
12776
  const patterns = [
@@ -11821,11 +12787,11 @@ function extractFilePath(input) {
11821
12787
  const m = trimmed.match(re);
11822
12788
  if (m?.[1]) {
11823
12789
  const p = expandPath(m[1]);
11824
- if (existsSync9(p)) return p;
12790
+ if (existsSync12(p)) return p;
11825
12791
  }
11826
12792
  if (!m?.[1] && re.test(trimmed) && trimmed.toLowerCase().endsWith(".csv")) {
11827
12793
  const p = expandPath(trimmed.replace(/^["']|["']$/g, ""));
11828
- if (existsSync9(p)) return p;
12794
+ if (existsSync12(p)) return p;
11829
12795
  }
11830
12796
  }
11831
12797
  return null;
@@ -11839,7 +12805,7 @@ function looksLikeFilePath(input) {
11839
12805
  }
11840
12806
  async function ingestFromChat(ctx, filePath) {
11841
12807
  if (!ctx.rl) {
11842
- console.log(" " + chalk15.red("Ingest confirm requires interactive mode."));
12808
+ console.log(" " + chalk16.red("Ingest confirm requires interactive mode."));
11843
12809
  return false;
11844
12810
  }
11845
12811
  const name = basename3(filePath);
@@ -11847,7 +12813,7 @@ async function ingestFromChat(ctx, filePath) {
11847
12813
  try {
11848
12814
  const ok = await prompts.confirm(`Ingest ${name} as CRM export?`, true);
11849
12815
  if (!ok) {
11850
- console.log(" " + chalk15.dim("Ingest cancelled."));
12816
+ console.log(" " + chalk16.dim("Ingest cancelled."));
11851
12817
  return false;
11852
12818
  }
11853
12819
  } finally {
@@ -11855,12 +12821,12 @@ async function ingestFromChat(ctx, filePath) {
11855
12821
  }
11856
12822
  const { handler: ingest } = await Promise.resolve().then(() => (init_ingest(), ingest_exports));
11857
12823
  const { detectEntityType: detectEntityType2 } = await Promise.resolve().then(() => (init_csv_detect(), csv_detect_exports));
11858
- const { readFileSync: readFileSync10 } = await import("fs");
12824
+ const { readFileSync: readFileSync13 } = await import("fs");
11859
12825
  const { parseCSV: parseCSV2 } = await Promise.resolve().then(() => (init_csv_parse(), csv_parse_exports));
11860
12826
  const { getStoredApiKey } = await Promise.resolve().then(() => (init_repl_api(), repl_api_exports));
11861
12827
  let headerCheckFailed = false;
11862
12828
  try {
11863
- const raw = readFileSync10(filePath, "utf-8");
12829
+ const raw = readFileSync13(filePath, "utf-8");
11864
12830
  const { headers } = parseCSV2(raw);
11865
12831
  const detected = detectEntityType2(headers, "unknown");
11866
12832
  if (!detected) headerCheckFailed = true;
@@ -11875,7 +12841,7 @@ async function ingestFromChat(ctx, filePath) {
11875
12841
  false
11876
12842
  );
11877
12843
  if (useAi) {
11878
- console.log(" " + chalk15.dim("AI column mapping is not wired to ingest yet \u2014 trying standard ingest."));
12844
+ console.log(" " + chalk16.dim("AI column mapping is not wired to ingest yet \u2014 trying standard ingest."));
11879
12845
  }
11880
12846
  } finally {
11881
12847
  prompts2.close();
@@ -11902,7 +12868,7 @@ async function ingestFromChat(ctx, filePath) {
11902
12868
  invalidateGapAudit(ctx);
11903
12869
  saveSessionState(ctx);
11904
12870
  console.log();
11905
- console.log(" " + paint("accent", "\u2713 Data loaded") + chalk15.dim(` \u2014 ${name}`));
12871
+ console.log(" " + paint("accent", "\u2713 Data loaded") + chalk16.dim(` \u2014 ${name}`));
11906
12872
  recordMessage(ctx, "user", `[ingested ${name}]`);
11907
12873
  recordMessage(ctx, "agent", `Loaded ${name}. Checking what we can analyze\u2026`);
11908
12874
  const audit = await refreshGapAudit(ctx);
@@ -13083,24 +14049,24 @@ var init_embeddings = __esm({
13083
14049
 
13084
14050
  // src/strategies/readers.ts
13085
14051
  import { createHash } from "crypto";
13086
- import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
14052
+ import { existsSync as existsSync13, readFileSync as readFileSync10 } from "fs";
13087
14053
  import { extname, resolve as resolve5 } from "path";
13088
14054
  import { parse as parseYaml } from "yaml";
13089
14055
  import { PDFParse } from "pdf-parse";
13090
14056
  async function readStrategyFile(pathOrDash) {
13091
14057
  if (pathOrDash === "-") {
13092
- const text2 = readFileSync7(0, "utf-8");
14058
+ const text2 = readFileSync10(0, "utf-8");
13093
14059
  return createDocument("stdin", null, text2, {});
13094
14060
  }
13095
14061
  const sourcePath = resolve5(pathOrDash);
13096
- if (!existsSync10(sourcePath)) {
14062
+ if (!existsSync13(sourcePath)) {
13097
14063
  throw new NtrpError("strategy_file_not_found", `Strategy file not found: ${pathOrDash}`, 2 /* Usage */);
13098
14064
  }
13099
14065
  const ext = extname(sourcePath).toLowerCase();
13100
14066
  if (ext === ".pdf") {
13101
14067
  return readPdf(sourcePath);
13102
14068
  }
13103
- const text = readFileSync7(sourcePath, "utf-8");
14069
+ const text = readFileSync10(sourcePath, "utf-8");
13104
14070
  if (ext === ".yaml" || ext === ".yml") {
13105
14071
  const structured = parseStructuredYaml(text);
13106
14072
  return createDocument("yaml", sourcePath, text, structured);
@@ -13115,7 +14081,7 @@ function readStrategyText(text) {
13115
14081
  return createDocument("text", null, text, {});
13116
14082
  }
13117
14083
  async function readPdf(sourcePath) {
13118
- const data = readFileSync7(sourcePath);
14084
+ const data = readFileSync10(sourcePath);
13119
14085
  const parser = new PDFParse({ data });
13120
14086
  try {
13121
14087
  const result = await parser.getText();
@@ -13304,15 +14270,15 @@ JSON SHAPE:
13304
14270
  });
13305
14271
 
13306
14272
  // src/strategies/library.ts
13307
- import { writeFileSync as writeFileSync5 } from "fs";
13308
- import { join as join9 } from "path";
14273
+ import { writeFileSync as writeFileSync8 } from "fs";
14274
+ import { join as join12 } from "path";
13309
14275
  import { stringify as stringifyYaml } from "yaml";
13310
14276
  function strategyLibraryPath(slug) {
13311
- return join9(getStrategiesDir(), `${slug}.md`);
14277
+ return join12(getStrategiesDir(), `${slug}.md`);
13312
14278
  }
13313
14279
  function writeStrategyMarkdown(strategy) {
13314
14280
  const path = strategyLibraryPath(strategy.slug);
13315
- writeFileSync5(path, renderStrategyMarkdown(strategy), "utf-8");
14281
+ writeFileSync8(path, renderStrategyMarkdown(strategy), "utf-8");
13316
14282
  return path;
13317
14283
  }
13318
14284
  function renderStrategyMarkdown(strategy) {
@@ -13386,7 +14352,7 @@ var init_library = __esm({
13386
14352
  // src/strategies/connectors.ts
13387
14353
  import { readdirSync as readdirSync3, statSync as statSync2 } from "fs";
13388
14354
  import { homedir as homedir6 } from "os";
13389
- import { basename as basename4, extname as extname2, join as join10, relative, resolve as resolve6, sep as sep2 } from "path";
14355
+ import { basename as basename4, extname as extname2, join as join13, relative, resolve as resolve6, sep as sep2 } from "path";
13390
14356
  function createLocalFolderConnector(options) {
13391
14357
  const rootPath = resolveUserPath(options.rootPath);
13392
14358
  const name = options.name ?? (basename4(rootPath) || "local");
@@ -13429,7 +14395,7 @@ function createLocalFolderConnector(options) {
13429
14395
  }
13430
14396
  function walkLocalFolder(rootPath, currentPath, refs, opts) {
13431
14397
  for (const entry of readdirSync3(currentPath, { withFileTypes: true })) {
13432
- const absolutePath = join10(currentPath, entry.name);
14398
+ const absolutePath = join13(currentPath, entry.name);
13433
14399
  const relativePath = normalizePath(relative(rootPath, absolutePath));
13434
14400
  if (entry.isDirectory()) {
13435
14401
  if (shouldSkipDirectory(entry.name) || matchesAny(relativePath, opts.excludePatterns)) continue;
@@ -13493,7 +14459,7 @@ function normalizePath(path) {
13493
14459
  }
13494
14460
  function resolveUserPath(path) {
13495
14461
  if (path === "~") return homedir6();
13496
- if (path.startsWith("~/")) return join10(homedir6(), path.slice(2));
14462
+ if (path.startsWith("~/")) return join13(homedir6(), path.slice(2));
13497
14463
  return resolve6(path);
13498
14464
  }
13499
14465
  var DEFAULT_MAX_FILES, DEFAULT_MAX_BYTES, SUPPORTED_EXTENSIONS, DEFAULT_EXCLUDED_DIRS;
@@ -13704,9 +14670,9 @@ init_divergence();
13704
14670
 
13705
14671
  // src/memory/store.ts
13706
14672
  init_store();
13707
- import { existsSync as existsSync12, readFileSync as readFileSync9, appendFileSync as appendFileSync4, readdirSync as readdirSync4, writeFileSync as writeFileSync6 } from "fs";
13708
- import { join as join11 } from "path";
13709
- import { randomUUID as randomUUID4 } from "crypto";
14673
+ import { existsSync as existsSync15, readFileSync as readFileSync12, appendFileSync as appendFileSync4, readdirSync as readdirSync4, writeFileSync as writeFileSync9 } from "fs";
14674
+ import { join as join14 } from "path";
14675
+ import { randomUUID as randomUUID5 } from "crypto";
13710
14676
 
13711
14677
  // src/memory/retrieval.ts
13712
14678
  var STOPWORDS = /* @__PURE__ */ new Set([
@@ -13835,18 +14801,18 @@ async function rankByRelevance(query, items, topK) {
13835
14801
  // src/memory/knowledge.ts
13836
14802
  init_store();
13837
14803
  init_readers();
13838
- import { existsSync as existsSync11, readFileSync as readFileSync8, appendFileSync as appendFileSync3, readdirSync as readdirSync2 } from "fs";
13839
- import { join as join8 } from "path";
13840
- import { randomUUID as randomUUID3 } from "crypto";
14804
+ import { existsSync as existsSync14, readFileSync as readFileSync11, appendFileSync as appendFileSync3, readdirSync as readdirSync2 } from "fs";
14805
+ import { join as join11 } from "path";
14806
+ import { randomUUID as randomUUID4 } from "crypto";
13841
14807
  var KNOWLEDGE_FILE = "knowledge.jsonl";
13842
14808
  function knowledgePath() {
13843
- return join8(getMemoryDir(), KNOWLEDGE_FILE);
14809
+ return join11(getMemoryDir(), KNOWLEDGE_FILE);
13844
14810
  }
13845
14811
  function loadKnowledgeChunks() {
13846
14812
  const path = knowledgePath();
13847
- if (!existsSync11(path)) return [];
14813
+ if (!existsSync14(path)) return [];
13848
14814
  const out = [];
13849
- for (const line of readFileSync8(path, "utf-8").split("\n")) {
14815
+ for (const line of readFileSync11(path, "utf-8").split("\n")) {
13850
14816
  const trimmed = line.trim();
13851
14817
  if (!trimmed) continue;
13852
14818
  try {
@@ -13861,13 +14827,13 @@ function loadKnowledgeChunks() {
13861
14827
  var FACTS_FILE = "facts.jsonl";
13862
14828
  var LEDGER_FILE = "ledger.jsonl";
13863
14829
  function memPath(file) {
13864
- return join11(getMemoryDir(), file);
14830
+ return join14(getMemoryDir(), file);
13865
14831
  }
13866
14832
  function readJsonl(file) {
13867
14833
  const path = memPath(file);
13868
- if (!existsSync12(path)) return [];
14834
+ if (!existsSync15(path)) return [];
13869
14835
  const out = [];
13870
- for (const line of readFileSync9(path, "utf-8").split("\n")) {
14836
+ for (const line of readFileSync12(path, "utf-8").split("\n")) {
13871
14837
  const trimmed = line.trim();
13872
14838
  if (!trimmed) continue;
13873
14839
  try {
@@ -13902,7 +14868,7 @@ function loadWinSnippets() {
13902
14868
  const out = [];
13903
14869
  for (const name of readdirSync4(dir)) {
13904
14870
  if (!name.endsWith(".md") || name.toLowerCase() === "readme.md") continue;
13905
- const raw = readFileSync9(join11(dir, name), "utf-8");
14871
+ const raw = readFileSync12(join14(dir, name), "utf-8");
13906
14872
  const title = raw.match(/^#\s+(.+)$/m)?.[1]?.trim() ?? name.replace(/\.md$/, "");
13907
14873
  const body = raw.replace(/^#.*$/m, "").replace(/\s+/g, " ").trim().slice(0, 300);
13908
14874
  out.push({ id: `win:${name}`, title, text: `${title}. ${body}` });