@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.
@@ -1272,989 +1272,1990 @@ CREATE INDEX IF NOT EXISTS idx_health_readings_batch ON health_readings(upload_b
1272
1272
  }
1273
1273
  });
1274
1274
 
1275
- // src/cli/context.ts
1276
- var context_exports = {};
1277
- __export(context_exports, {
1278
- buildAnalysisBlock: () => buildAnalysisBlock,
1279
- buildSwitchContext: () => buildSwitchContext,
1280
- closeAllActiveSessions: () => closeAllActiveSessions,
1281
- closeSession: () => closeSession,
1282
- datasetPathForSession: () => datasetPathForSession,
1283
- defaultSessionAnalysis: () => defaultSessionAnalysis,
1284
- endSession: () => endSession,
1285
- findSessionByName: () => findSessionByName,
1286
- getActiveSessions: () => getActiveSessions,
1287
- getDatasetsDir: () => getDatasetsDir,
1288
- getLastActivityRelative: () => getLastActivityRelative,
1289
- getLastWorkedSession: () => getLastWorkedSession,
1290
- getRecentSessions: () => getRecentSessions,
1291
- getSessionsDir: () => getSessionsDir,
1292
- getUnfinishedSessions: () => getUnfinishedSessions,
1293
- hydrateAnalysisFromPersistedState: () => hydrateAnalysisFromPersistedState,
1294
- initContext: () => initContext,
1295
- initHeadlessAgentContext: () => initHeadlessAgentContext,
1296
- isAnalysisReady: () => isAnalysisReady,
1297
- isSessionInProgress: () => isSessionInProgress,
1298
- lensBadgeLabel: () => lensBadgeLabel,
1299
- listSessions: () => listSessions,
1300
- loadSessionFile: () => loadSessionFile,
1301
- makeSessionId: () => makeSessionId,
1302
- markLensCompleted: () => markLensCompleted,
1303
- prefersMetricsFirstContext: () => prefersMetricsFirstContext,
1304
- recordMessage: () => recordMessage,
1305
- resetContextForSwitch: () => resetContextForSwitch,
1306
- resolveSessionByToken: () => resolveSessionByToken,
1307
- rotateToFreshSession: () => rotateToFreshSession,
1308
- saveSessionState: () => saveSessionState,
1309
- setPrimaryLens: () => setPrimaryLens
1275
+ // src/config/store.ts
1276
+ var store_exports = {};
1277
+ __export(store_exports, {
1278
+ deleteConfigValue: () => deleteConfigValue,
1279
+ getConfigValue: () => getConfigValue,
1280
+ getExportsDir: () => getExportsDir,
1281
+ getKnowledgeDir: () => getKnowledgeDir,
1282
+ getMemoryDir: () => getMemoryDir,
1283
+ getStrategiesDir: () => getStrategiesDir,
1284
+ getWinsDir: () => getWinsDir,
1285
+ loadConfig: () => loadConfig,
1286
+ ntrpHome: () => ntrpHome,
1287
+ resetConfigCache: () => resetConfigCache,
1288
+ saveConfig: () => saveConfig,
1289
+ setConfigValue: () => setConfigValue
1310
1290
  });
1311
- import { basename, join as join2, resolve as resolve2, sep } from "path";
1312
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync, readFileSync, readdirSync, statSync, rmSync as rmSync2 } from "fs";
1291
+ import { readFileSync, writeFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
1313
1292
  import { homedir } from "os";
1314
- import { randomUUID as randomUUID2 } from "crypto";
1315
- function isAnalysisReady(ctx) {
1316
- if (ctx.stage !== "analyzed" || ctx.analysis.completed.length === 0) return false;
1317
- if (!ctx.dataset) return false;
1318
- const counts = ctx.dataset.counts ?? {};
1319
- return Object.values(counts).some((n) => n > 0);
1293
+ import { join as join2, resolve as resolve2 } from "path";
1294
+ function ntrpHome() {
1295
+ return NTRP_DIR2;
1320
1296
  }
1321
- function ntrpHomeDir() {
1322
- return process.env.NTRP_HOME ? resolve2(process.env.NTRP_HOME) : join2(homedir(), ".ntrp");
1297
+ function ensureDir2() {
1298
+ if (!existsSync2(NTRP_DIR2)) {
1299
+ mkdirSync2(NTRP_DIR2, { recursive: true });
1300
+ }
1323
1301
  }
1324
- function getSessionsDir() {
1325
- const dir = join2(ntrpHomeDir(), "sessions");
1302
+ function loadConfig() {
1303
+ if (cachedConfig) return cachedConfig;
1304
+ ensureDir2();
1305
+ if (!existsSync2(CONFIG_PATH)) {
1306
+ cachedConfig = {};
1307
+ return cachedConfig;
1308
+ }
1309
+ try {
1310
+ cachedConfig = JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
1311
+ } catch {
1312
+ cachedConfig = {};
1313
+ }
1314
+ return cachedConfig;
1315
+ }
1316
+ function saveConfig(config) {
1317
+ ensureDir2();
1318
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n");
1319
+ cachedConfig = config;
1320
+ }
1321
+ function resetConfigCache() {
1322
+ cachedConfig = null;
1323
+ }
1324
+ function getConfigValue(key) {
1325
+ if (key === "api-key") return loadConfig()["api-key"];
1326
+ if (key === "license-key") return process.env.NTRP_LICENSE_KEY ?? loadConfig()["license-key"];
1327
+ const config = loadConfig();
1328
+ return config[key];
1329
+ }
1330
+ function setConfigValue(key, value) {
1331
+ const config = loadConfig();
1332
+ config[key] = value;
1333
+ saveConfig(config);
1334
+ }
1335
+ function deleteConfigValue(key) {
1336
+ const config = loadConfig();
1337
+ delete config[key];
1338
+ saveConfig(config);
1339
+ }
1340
+ function getExportsDir() {
1341
+ const config = loadConfig();
1342
+ const dir = resolve2(config["export-dir"] ?? join2(NTRP_DIR2, "exports"));
1326
1343
  if (!existsSync2(dir)) {
1327
1344
  mkdirSync2(dir, { recursive: true });
1328
1345
  }
1329
1346
  return dir;
1330
1347
  }
1331
- function getDatasetsDir() {
1332
- const dir = join2(ntrpHomeDir(), "datasets");
1348
+ function getStrategiesDir() {
1349
+ const dir = join2(NTRP_DIR2, "strategies");
1333
1350
  if (!existsSync2(dir)) {
1334
1351
  mkdirSync2(dir, { recursive: true });
1352
+ writeFileSync(join2(dir, "README.md"), `# Strategies
1353
+
1354
+ This directory holds your GTM strategy files. Each file describes a strategy you're executing.
1355
+
1356
+ ## How to use
1357
+
1358
+ 1. Create a markdown file for each active strategy (e.g., \`multi-thread-q2.md\`)
1359
+ 2. Describe the goal, target segment, and success criteria
1360
+ 3. Reference playbook plays that support this strategy
1361
+ 4. After diagnosis, check if vital signs improved in the targeted area
1362
+
1363
+ ## Example
1364
+
1365
+ \`\`\`markdown
1366
+ # Multi-Thread Enterprise Deals \u2014 Q2
1367
+
1368
+ **Goal:** Reduce single-threaded deals from 65% to under 30%
1369
+ **Segment:** Enterprise accounts > $100K
1370
+ **Play:** Multi-Thread Your Deals
1371
+ **Success metric:** Thread depth score > 70
1372
+ \`\`\`
1373
+ `);
1335
1374
  }
1336
1375
  return dir;
1337
1376
  }
1338
- function datasetPathForSession(id) {
1339
- return join2(getDatasetsDir(), `${id}.duckdb`);
1377
+ function getMemoryDir() {
1378
+ const dir = join2(NTRP_DIR2, "memory");
1379
+ if (!existsSync2(dir)) {
1380
+ mkdirSync2(dir, { recursive: true });
1381
+ }
1382
+ return dir;
1340
1383
  }
1341
- function makeSessionId() {
1342
- const now2 = /* @__PURE__ */ new Date();
1343
- const date = now2.toISOString().slice(0, 10);
1344
- const uuid2 = randomUUID2().slice(0, 4);
1345
- return `${date}-${uuid2}`;
1384
+ function getKnowledgeDir() {
1385
+ const dir = join2(NTRP_DIR2, "knowledge");
1386
+ if (!existsSync2(dir)) {
1387
+ mkdirSync2(dir, { recursive: true });
1388
+ writeFileSync(join2(dir, "README.md"), `# Knowledge Packs
1389
+
1390
+ Drop case studies, GTM frameworks, benchmark reports, or playbooks here as
1391
+ markdown, text, or PDF. NTRP ingests them with \`/knowledge add <file>\` and
1392
+ references the most relevant passages during analysis \u2014 so the agent can learn
1393
+ from work done outside this platform.
1394
+
1395
+ ## How to use
1396
+
1397
+ 1. Add a file: \`/knowledge add ~/Downloads/plg-benchmarks-2026.pdf\`
1398
+ 2. List what's indexed: \`/knowledge list\`
1399
+ 3. Ask a question \u2014 relevant passages are pulled in automatically.
1400
+ `);
1401
+ }
1402
+ return dir;
1346
1403
  }
1347
- function isValidSessionId(id) {
1348
- return SESSION_ID_RE.test(id);
1404
+ function getWinsDir() {
1405
+ const dir = join2(NTRP_DIR2, "wins");
1406
+ if (!existsSync2(dir)) {
1407
+ mkdirSync2(dir, { recursive: true });
1408
+ writeFileSync(join2(dir, "README.md"), `# Wins
1409
+
1410
+ This directory logs outcomes when a strategy or play succeeds. Each win creates a record that future diagnoses can reference.
1411
+
1412
+ ## How to use
1413
+
1414
+ 1. After executing a play, log the result here (e.g., \`2026-04-clean-pipeline.md\`)
1415
+ 2. Include: what you did, what changed, before/after scores
1416
+ 3. Future AI findings will reference wins to track improvement over time
1417
+
1418
+ ## Example
1419
+
1420
+ \`\`\`markdown
1421
+ # Pipeline Cleanup \u2014 April 2026
1422
+
1423
+ **Play:** Clean Dead Pipeline
1424
+ **Before:** Freshness 29/100, $3.1M stale pipeline
1425
+ **After:** Freshness 72/100, removed 45 zombie deals
1426
+ **Impact:** Forecast accuracy improved from 62% to 84%
1427
+ \`\`\`
1428
+ `);
1429
+ }
1430
+ return dir;
1349
1431
  }
1350
- function sessionPathForId(id) {
1351
- if (!isValidSessionId(id)) return null;
1352
- const dir = resolve2(getSessionsDir());
1353
- const filePath = resolve2(dir, `${id}.json`);
1354
- if (filePath !== dir && !filePath.startsWith(dir + sep)) return null;
1355
- return filePath;
1432
+ var NTRP_DIR2, CONFIG_PATH, cachedConfig;
1433
+ var init_store = __esm({
1434
+ "src/config/store.ts"() {
1435
+ "use strict";
1436
+ NTRP_DIR2 = process.env.NTRP_HOME ? resolve2(process.env.NTRP_HOME) : join2(homedir(), ".ntrp");
1437
+ CONFIG_PATH = join2(NTRP_DIR2, "config.json");
1438
+ cachedConfig = null;
1439
+ }
1440
+ });
1441
+
1442
+ // src/config/install.ts
1443
+ import { randomUUID as randomUUID2 } from "crypto";
1444
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
1445
+ import { join as join3 } from "path";
1446
+ function installPath() {
1447
+ return join3(ntrpHome(), "install.json");
1356
1448
  }
1357
- function initContext(oneShot, execution) {
1358
- const sessionId = makeSessionId();
1359
- const sessionFile = join2(getSessionsDir(), `${sessionId}.json`);
1360
- return {
1361
- sessionId,
1362
- sessionFile,
1363
- oneShot,
1364
- execution: buildExecutionOptions({
1365
- mode: oneShot ? "one_shot" : "interactive",
1366
- ...execution
1367
- }),
1368
- snapshot: { computeResult: null, divergences: [] },
1369
- messages: [],
1370
- conversation: [],
1371
- stage: "new",
1372
- deliverables: [],
1373
- analysis: defaultSessionAnalysis(),
1374
- wizardDepth: 0,
1375
- attachments: [],
1376
- deliverIntent: false,
1377
- computeInProgress: false
1378
- };
1449
+ function ensureDir3() {
1450
+ const dir = ntrpHome();
1451
+ if (!existsSync3(dir)) {
1452
+ mkdirSync3(dir, { recursive: true });
1453
+ }
1379
1454
  }
1380
- function buildSessionFile(ctx) {
1381
- const file = {
1382
- id: ctx.sessionId,
1383
- created_at: ctx.messages[0]?.at ?? (/* @__PURE__ */ new Date()).toISOString(),
1384
- messages: ctx.messages,
1385
- stage: ctx.stage
1455
+ function isValidInstall(value) {
1456
+ if (!value || typeof value !== "object") return false;
1457
+ const r = value;
1458
+ return r.schema_version === 1 && typeof r.install_id === "string" && r.install_id.length > 0 && typeof r.created_at === "string";
1459
+ }
1460
+ function ensureInstall() {
1461
+ if (cachedInstall) return cachedInstall;
1462
+ const path = installPath();
1463
+ if (existsSync3(path)) {
1464
+ try {
1465
+ const parsed = JSON.parse(readFileSync2(path, "utf-8"));
1466
+ if (isValidInstall(parsed)) {
1467
+ cachedInstall = parsed;
1468
+ return parsed;
1469
+ }
1470
+ } catch {
1471
+ }
1472
+ }
1473
+ const record = {
1474
+ schema_version: 1,
1475
+ install_id: randomUUID2(),
1476
+ created_at: (/* @__PURE__ */ new Date()).toISOString()
1386
1477
  };
1387
- if (ctx.sessionName) file.name = ctx.sessionName;
1388
- if (ctx.dataset) file.dataset = ctx.dataset;
1389
- if (ctx.deliverables.length > 0) file.deliverables = ctx.deliverables;
1390
- if (ctx.conversation.length > 0) file.thread = ctx.conversation;
1391
- if (ctx.resumedFromId) file.resumed_from = ctx.resumedFromId;
1392
- if (ctx.analysis) file.analysis = ctx.analysis;
1393
- if (ctx.scope) file.scope = ctx.scope;
1394
- if (ctx.attachments && ctx.attachments.length > 0) file.attachments = ctx.attachments;
1395
- if (ctx.llm && Object.keys(ctx.llm).length > 0) file.llm = ctx.llm;
1396
- return file;
1478
+ ensureDir3();
1479
+ writeFileSync2(path, JSON.stringify(record, null, 2) + "\n");
1480
+ cachedInstall = record;
1481
+ return record;
1397
1482
  }
1398
- function defaultSessionAnalysis(primary = "gtm_health") {
1399
- return { primary, completed: [] };
1483
+ function getInstallId() {
1484
+ return ensureInstall().install_id;
1400
1485
  }
1401
- function setPrimaryLens(ctx, lens) {
1402
- ctx.analysis = { ...ctx.analysis, primary: lens };
1486
+ var cachedInstall;
1487
+ var init_install = __esm({
1488
+ "src/config/install.ts"() {
1489
+ "use strict";
1490
+ init_store();
1491
+ cachedInstall = null;
1492
+ }
1493
+ });
1494
+
1495
+ // src/config/progress-migrate.ts
1496
+ import { existsSync as existsSync4, readFileSync as readFileSync3, renameSync, writeFileSync as writeFileSync3 } from "fs";
1497
+ import { join as join4 } from "path";
1498
+ function legacyStatePath() {
1499
+ return join4(ntrpHome(), "state.json");
1403
1500
  }
1404
- function markLensCompleted(ctx, lens) {
1405
- const completed = ctx.analysis.completed.includes(lens) ? ctx.analysis.completed : [...ctx.analysis.completed, lens];
1406
- ctx.analysis = { ...ctx.analysis, completed };
1501
+ function legacyStateBackupPath() {
1502
+ return join4(ntrpHome(), "state.json.bak");
1407
1503
  }
1408
- function lensBadgeLabel(analysis) {
1409
- if (!analysis) return "health";
1410
- const hasHealth = analysis.completed.includes("gtm_health") || analysis.primary === "gtm_health";
1411
- const hasMetrics = analysis.completed.includes("revenue_metrics") || analysis.primary === "revenue_metrics";
1412
- if (hasHealth && hasMetrics) return "both";
1413
- if (hasMetrics) return "metrics";
1414
- return "health";
1415
- }
1416
- function buildAnalysisBlock(ctx) {
1417
- return [
1418
- `Primary lens: ${ctx.analysis.primary}`,
1419
- `Completed: ${ctx.analysis.completed.join(", ") || "none"}`,
1420
- `Badge: ${lensBadgeLabel(ctx.analysis)}`,
1421
- ctx.analysis.coverage ? `Coverage: ${ctx.analysis.coverage.distinct_months} months, ${ctx.analysis.coverage.recommended_cadence} cadence` : null,
1422
- ctx.analysis.data_source_type ? `Data source: ${ctx.analysis.data_source_type}` : null
1423
- ].filter(Boolean).join("\n");
1504
+ function progressPath() {
1505
+ return join4(ntrpHome(), "progress.json");
1424
1506
  }
1425
- function prefersMetricsFirstContext(ctx) {
1426
- return ctx.analysis.primary === "revenue_metrics" && ctx.analysis.completed.includes("revenue_metrics") && !ctx.analysis.completed.includes("gtm_health");
1507
+ function isValidLegacyState(value) {
1508
+ if (!value || typeof value !== "object") return false;
1509
+ const s = value;
1510
+ return s.schema_version === 1 && typeof s.total_minutes_saved === "number" && Array.isArray(s.credits) && Array.isArray(s.milestones_unlocked);
1427
1511
  }
1428
- async function hydrateAnalysisFromPersistedState(ctx) {
1429
- const sessions = listSessions({ limit: 10 });
1430
- const withAnalysis = sessions.find(
1431
- (s) => s.analysis && (s.analysis.completed.length > 0 || s.analysis.primary !== "gtm_health" || s.stage === "analyzed")
1432
- );
1433
- if (withAnalysis?.analysis) {
1434
- ctx.analysis = {
1435
- ...defaultSessionAnalysis(withAnalysis.analysis.primary),
1436
- ...withAnalysis.analysis,
1437
- completed: [...withAnalysis.analysis.completed]
1512
+ function migrateLegacyStateIfNeeded(installId) {
1513
+ if (existsSync4(progressPath())) return null;
1514
+ const legacyPath = legacyStatePath();
1515
+ if (!existsSync4(legacyPath)) return null;
1516
+ try {
1517
+ const parsed = JSON.parse(readFileSync3(legacyPath, "utf-8"));
1518
+ if (!isValidLegacyState(parsed)) return null;
1519
+ const { schema_version: _v, ...rest } = parsed;
1520
+ const progress = {
1521
+ ...rest,
1522
+ schema_version: 2,
1523
+ install_id: installId
1438
1524
  };
1439
- if (withAnalysis.stage) ctx.stage = withAnalysis.stage;
1440
- if (withAnalysis.dataset) ctx.dataset = withAnalysis.dataset;
1441
- return;
1525
+ writeFileSync3(progressPath(), JSON.stringify(progress, null, 2) + "\n");
1526
+ try {
1527
+ renameSync(legacyPath, legacyStateBackupPath());
1528
+ } catch {
1529
+ }
1530
+ return progress;
1531
+ } catch {
1532
+ return null;
1442
1533
  }
1443
- const { loadLatestDiagnosis: loadLatestDiagnosis2, loadLatestMetricsAnalysis: loadLatestMetricsAnalysis2 } = await Promise.resolve().then(() => (init_queries(), queries_exports));
1444
- const [diagnosis, metrics] = await Promise.all([
1445
- loadLatestDiagnosis2(),
1446
- loadLatestMetricsAnalysis2()
1447
- ]);
1448
- const completed = [];
1449
- if (diagnosis) completed.push("gtm_health");
1450
- if (metrics?.metrics.length) completed.push("revenue_metrics");
1451
- if (completed.length === 0) return;
1452
- let primary = ctx.analysis.primary;
1453
- if (completed.includes("revenue_metrics") && !completed.includes("gtm_health")) {
1454
- primary = "revenue_metrics";
1455
- } else if (completed.includes("gtm_health") && !completed.includes("revenue_metrics")) {
1456
- primary = "gtm_health";
1534
+ }
1535
+ var init_progress_migrate = __esm({
1536
+ "src/config/progress-migrate.ts"() {
1537
+ "use strict";
1538
+ init_store();
1457
1539
  }
1458
- ctx.analysis = { ...ctx.analysis, primary, completed };
1459
- if (ctx.stage === "new") ctx.stage = "analyzed";
1540
+ });
1541
+
1542
+ // src/whimsy/usage-backfill.ts
1543
+ function isoWeekKey(d) {
1544
+ const date = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
1545
+ const day = date.getUTCDay() || 7;
1546
+ date.setUTCDate(date.getUTCDate() + 4 - day);
1547
+ const yearStart = new Date(Date.UTC(date.getUTCFullYear(), 0, 1));
1548
+ const weekNo = Math.ceil(((date.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
1549
+ return `${date.getUTCFullYear()}-W${String(weekNo).padStart(2, "0")}`;
1550
+ }
1551
+ function actionBase(action) {
1552
+ return action.split(":")[0] ?? action;
1553
+ }
1554
+ function bumpWeekly(weekly, week, patch) {
1555
+ const idx = weekly.findIndex((w) => w.week === week);
1556
+ const row = idx >= 0 ? { ...weekly[idx] } : { week, minutes_saved: 0, llm_calls: 0, actions: 0 };
1557
+ if (patch.minutes_saved) row.minutes_saved += patch.minutes_saved;
1558
+ if (patch.actions) row.actions += patch.actions;
1559
+ return idx >= 0 ? weekly.map((w, i) => i === idx ? row : w) : [...weekly, row];
1560
+ }
1561
+ function rebuildFromCredits(credits) {
1562
+ let diagnoses = 0;
1563
+ let metrics_runs = 0;
1564
+ let deliverables = 0;
1565
+ let nl_exchanges = 0;
1566
+ let weekly = [];
1567
+ let first_active_at;
1568
+ let last_active_at;
1569
+ for (const credit of credits) {
1570
+ if (!first_active_at || credit.at < first_active_at) first_active_at = credit.at;
1571
+ if (!last_active_at || credit.at > last_active_at) last_active_at = credit.at;
1572
+ const base = actionBase(credit.action);
1573
+ if (base === "diagnose" || base === "diagnose_findings") diagnoses++;
1574
+ if (base === "metrics" || base === "metrics_findings") metrics_runs++;
1575
+ if (base === "deliverable" || base === "deliverable_deck") deliverables++;
1576
+ if (base === "nl_answer") nl_exchanges++;
1577
+ const week = isoWeekKey(new Date(credit.at));
1578
+ weekly = bumpWeekly(weekly, week, { minutes_saved: credit.minutes, actions: 1 });
1579
+ }
1580
+ return {
1581
+ first_active_at,
1582
+ last_active_at,
1583
+ diagnoses,
1584
+ metrics_runs,
1585
+ deliverables,
1586
+ nl_exchanges,
1587
+ weekly
1588
+ };
1460
1589
  }
1461
- async function initHeadlessAgentContext() {
1462
- const ctx = initContext(true, { mode: "headless", output: "json" });
1463
- await hydrateAnalysisFromPersistedState(ctx);
1464
- return ctx;
1590
+ function mergeWeekly(existing, fromCredits) {
1591
+ const byWeek = /* @__PURE__ */ new Map();
1592
+ for (const row of fromCredits) {
1593
+ byWeek.set(row.week, { ...row });
1594
+ }
1595
+ for (const row of existing) {
1596
+ const prior = byWeek.get(row.week);
1597
+ if (prior) {
1598
+ byWeek.set(row.week, {
1599
+ week: row.week,
1600
+ minutes_saved: Math.max(prior.minutes_saved, row.minutes_saved),
1601
+ actions: Math.max(prior.actions, row.actions),
1602
+ llm_calls: row.llm_calls
1603
+ });
1604
+ } else {
1605
+ byWeek.set(row.week, { ...row });
1606
+ }
1607
+ }
1608
+ return [...byWeek.values()].sort((a, b) => a.week.localeCompare(b.week));
1609
+ }
1610
+ function migrateUsageIfNeeded(state) {
1611
+ if (state.credits.length === 0) return { state, changed: false };
1612
+ if (state.usage?.first_active_at) return { state, changed: false };
1613
+ const fromCredits = rebuildFromCredits(state.credits);
1614
+ const prior = state.usage;
1615
+ const usage = {
1616
+ sessions_closed: prior?.sessions_closed ?? 0,
1617
+ llm_calls: prior?.llm_calls ?? 0,
1618
+ input_tokens: prior?.input_tokens ?? 0,
1619
+ output_tokens: prior?.output_tokens ?? 0,
1620
+ ...fromCredits,
1621
+ weekly: mergeWeekly(prior?.weekly ?? [], fromCredits.weekly)
1622
+ };
1623
+ return { state: { ...state, usage }, changed: true };
1465
1624
  }
1466
- function recordMessage(ctx, role, content) {
1467
- const msg = { role, content, at: (/* @__PURE__ */ new Date()).toISOString() };
1468
- ctx.messages.push(msg);
1469
- if (ctx.oneShot) return;
1470
- try {
1471
- writeFileSync(ctx.sessionFile, JSON.stringify(buildSessionFile(ctx), null, 2) + "\n");
1472
- } catch {
1625
+ var init_usage_backfill = __esm({
1626
+ "src/whimsy/usage-backfill.ts"() {
1627
+ "use strict";
1473
1628
  }
1629
+ });
1630
+
1631
+ // src/config/progress.ts
1632
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
1633
+ import { join as join5 } from "path";
1634
+ function progressPath2() {
1635
+ return join5(ntrpHome(), "progress.json");
1474
1636
  }
1475
- function saveSessionState(ctx) {
1476
- if (ctx.oneShot) return;
1477
- try {
1478
- writeFileSync(ctx.sessionFile, JSON.stringify(buildSessionFile(ctx), null, 2) + "\n");
1479
- } catch {
1637
+ function ensureDir4() {
1638
+ const dir = ntrpHome();
1639
+ if (!existsSync5(dir)) {
1640
+ mkdirSync4(dir, { recursive: true });
1480
1641
  }
1481
1642
  }
1482
- function getLastActivityRelative() {
1483
- const dir = getSessionsDir();
1484
- let mostRecent = 0;
1485
- try {
1486
- for (const name of readdirSync(dir)) {
1487
- if (!name.endsWith(".json")) continue;
1488
- const m = statSync(join2(dir, name)).mtimeMs;
1489
- if (m > mostRecent) mostRecent = m;
1490
- }
1491
- } catch {
1492
- return null;
1493
- }
1494
- if (mostRecent === 0) return null;
1495
- return formatRelativeTime(new Date(mostRecent));
1643
+ function emptyProgress(installId) {
1644
+ return {
1645
+ schema_version: 2,
1646
+ install_id: installId,
1647
+ total_minutes_saved: 0,
1648
+ credits: [],
1649
+ milestones_unlocked: []
1650
+ };
1496
1651
  }
1497
- function formatRelativeTime(then) {
1498
- const diffMs = Date.now() - then.getTime();
1499
- if (diffMs < 0) return "just now";
1500
- const s = Math.floor(diffMs / 1e3);
1501
- if (s < 60) return "just now";
1502
- const m = Math.floor(s / 60);
1503
- if (m < 60) return `${m}m ago`;
1504
- const h = Math.floor(m / 60);
1505
- if (h < 24) return `${h}h ago`;
1506
- const d = Math.floor(h / 24);
1507
- if (d < 7) return `${d}d ago`;
1508
- return then.toISOString().slice(0, 10);
1652
+ function isValidProgress(value) {
1653
+ if (!value || typeof value !== "object") return false;
1654
+ const s = value;
1655
+ 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);
1656
+ }
1657
+ function reconcileInstallId(state) {
1658
+ const localId = getInstallId();
1659
+ if (state.install_id === localId) return { state, changed: false };
1660
+ if (!installMismatchWarned) {
1661
+ installMismatchWarned = true;
1662
+ console.warn(
1663
+ " progress.json install_id did not match this machine \u2014 rebound to local install."
1664
+ );
1665
+ }
1666
+ return { state: { ...state, install_id: localId }, changed: true };
1509
1667
  }
1510
- function loadSessionFile(id) {
1511
- const filePath = sessionPathForId(id);
1512
- if (!filePath) return null;
1668
+ function readProgressFile() {
1669
+ const path = progressPath2();
1670
+ if (!existsSync5(path)) return { state: null, changed: false };
1513
1671
  try {
1514
- const raw = readFileSync(filePath, "utf-8");
1515
- const session = JSON.parse(raw);
1516
- if (session.thread?.length) {
1517
- session.thread = normalizeThread(session.thread);
1518
- }
1519
- return session;
1672
+ const parsed = JSON.parse(readFileSync4(path, "utf-8"));
1673
+ if (!isValidProgress(parsed)) return { state: null, changed: false };
1674
+ return reconcileInstallId(parsed);
1520
1675
  } catch {
1521
- return null;
1676
+ return { state: null, changed: false };
1522
1677
  }
1523
1678
  }
1524
- function listSessions(opts) {
1525
- const dir = getSessionsDir();
1526
- const entries = [];
1527
- try {
1528
- const files = readdirSync(dir).filter((name) => name.endsWith(".json")).map((name) => {
1529
- const filePath = join2(dir, name);
1530
- return { name, filePath, mtime: statSync(filePath).mtimeMs };
1531
- }).sort((a, b) => b.mtime - a.mtime);
1532
- const filesToRead = opts?.limit ? files.slice(0, opts.limit) : files;
1533
- for (const { name, filePath, mtime } of filesToRead) {
1534
- const id = basename(name, ".json");
1535
- if (!isValidSessionId(id)) continue;
1536
- try {
1537
- const raw = readFileSync(filePath, "utf-8");
1538
- const session = JSON.parse(raw);
1539
- entries.push({
1540
- id,
1541
- created_at: session.created_at,
1542
- ended_at: session.ended_at,
1543
- exchange_count: session.exchange_count ?? Math.floor(session.messages.length / 2),
1544
- summary: session.summary,
1545
- name: session.name,
1546
- stage: session.stage,
1547
- dataset: session.dataset,
1548
- deliverables: session.deliverables,
1549
- analysis: session.analysis,
1550
- mtime
1551
- });
1552
- } catch {
1553
- }
1679
+ function loadProgress() {
1680
+ ensureInstall();
1681
+ const installId = getInstallId();
1682
+ let state = null;
1683
+ let changed = false;
1684
+ const fromFile = readProgressFile();
1685
+ if (fromFile.state) {
1686
+ state = fromFile.state;
1687
+ changed = fromFile.changed;
1688
+ }
1689
+ if (!state) {
1690
+ const migrated2 = migrateLegacyStateIfNeeded(installId);
1691
+ if (migrated2) {
1692
+ state = migrated2;
1693
+ changed = true;
1554
1694
  }
1555
- } catch {
1556
- return [];
1557
1695
  }
1558
- entries.sort((a, b) => b.mtime - a.mtime);
1559
- if (opts?.limit) return entries.slice(0, opts.limit);
1560
- return entries;
1561
- }
1562
- function getRecentSessions(n) {
1563
- return listSessions({ limit: n });
1564
- }
1565
- function getUnfinishedSessions(excludeId) {
1566
- return listSessions().filter(
1567
- (s) => s.id !== excludeId && s.stage === "analyzed" && (s.deliverables?.length ?? 0) === 0
1568
- );
1696
+ if (!state) {
1697
+ state = emptyProgress(installId);
1698
+ changed = true;
1699
+ }
1700
+ const { state: usageMigrated, changed: usageChanged } = migrateUsageIfNeeded(state);
1701
+ state = usageMigrated;
1702
+ if (usageChanged) changed = true;
1703
+ if (changed) saveProgress(state);
1704
+ return state;
1705
+ }
1706
+ function saveProgress(state) {
1707
+ ensureDir4();
1708
+ const next = {
1709
+ ...state,
1710
+ schema_version: 2,
1711
+ install_id: getInstallId()
1712
+ };
1713
+ writeFileSync4(progressPath2(), JSON.stringify(next, null, 2) + "\n");
1569
1714
  }
1570
- function isSessionInProgress(s) {
1571
- if (s.stage === "ended") return false;
1572
- if ((s.deliverables?.length ?? 0) > 0 || s.stage === "delivered") return false;
1573
- if (s.stage === "analyzed") return true;
1574
- return (s.exchange_count ?? 0) > 0 || !!s.dataset?.label;
1715
+ function appendCredit(state, credit) {
1716
+ const credits = [...state.credits, credit];
1717
+ if (credits.length > CREDIT_HISTORY_CAP) {
1718
+ credits.splice(0, credits.length - CREDIT_HISTORY_CAP);
1719
+ }
1720
+ return {
1721
+ ...state,
1722
+ total_minutes_saved: state.total_minutes_saved + credit.minutes,
1723
+ credits
1724
+ };
1575
1725
  }
1576
- function getActiveSessions() {
1577
- return listSessions().filter(isSessionInProgress);
1726
+ function hasCreditAction(state, action) {
1727
+ return state.credits.some((c) => c.action === action);
1578
1728
  }
1579
- async function closeAllActiveSessions(ctx) {
1580
- const active = getActiveSessions();
1581
- const closed = [];
1582
- const skipped = [];
1583
- const endedAt = (/* @__PURE__ */ new Date()).toISOString();
1584
- for (const s of active) {
1585
- if (s.id === ctx.sessionId) continue;
1586
- const file = loadSessionFile(s.id);
1587
- if (!file) {
1588
- skipped.push(s.id);
1589
- continue;
1590
- }
1591
- file.stage = "ended";
1592
- file.ended_at = endedAt;
1593
- const filePath = sessionPathForId(s.id);
1594
- if (!filePath) {
1595
- skipped.push(s.id);
1596
- continue;
1597
- }
1598
- writeFileSync(filePath, JSON.stringify(file, null, 2) + "\n");
1599
- closed.push(s.id);
1600
- }
1601
- const currentActive = active.some((s) => s.id === ctx.sessionId);
1602
- if (currentActive) {
1603
- const alreadyClosed = ctx.stage === "delivered" || ctx.stage === "ended";
1604
- const hasWork = ctx.stage === "analyzed" || !!ctx.dataset || ctx.messages.length > 0 || ctx.deliverables.length > 0;
1605
- if (!alreadyClosed && hasWork) {
1606
- await endSession(ctx);
1607
- if (!closed.includes(ctx.sessionId)) closed.push(ctx.sessionId);
1608
- } else if (!alreadyClosed && (ctx.messages.length > 0 || !!ctx.dataset?.label)) {
1609
- await endSession(ctx);
1610
- if (!closed.includes(ctx.sessionId)) closed.push(ctx.sessionId);
1611
- }
1729
+ var CREDIT_HISTORY_CAP, installMismatchWarned;
1730
+ var init_progress = __esm({
1731
+ "src/config/progress.ts"() {
1732
+ "use strict";
1733
+ init_install();
1734
+ init_progress_migrate();
1735
+ init_store();
1736
+ init_usage_backfill();
1737
+ CREDIT_HISTORY_CAP = 100;
1738
+ installMismatchWarned = false;
1612
1739
  }
1613
- await rotateToFreshSession(ctx);
1614
- const { initSchema: initSchema2 } = await Promise.resolve().then(() => (init_schema(), schema_exports));
1615
- await initSchema2();
1616
- return { closed, skipped };
1740
+ });
1741
+
1742
+ // src/ui/theme.ts
1743
+ import chalk from "chalk";
1744
+ function paint(token, text) {
1745
+ if (token === "dim") return chalk.dim(text);
1746
+ return chalk.hex(TOKENS[token])(text);
1617
1747
  }
1618
- function getLastWorkedSession() {
1619
- for (const s of listSessions()) {
1620
- if ((s.exchange_count ?? 0) > 0 || s.stage === "analyzed" || s.stage === "delivered" || !!s.dataset?.label) {
1621
- return s;
1622
- }
1748
+ function bold(text) {
1749
+ return chalk.bold(text);
1750
+ }
1751
+ function badge(label, tone = "muted") {
1752
+ const normalized = ` ${label.toUpperCase()} `;
1753
+ switch (tone) {
1754
+ case "success":
1755
+ return chalk.hex(TOKENS.success)(normalized);
1756
+ case "warning":
1757
+ return chalk.hex(TOKENS.warning)(normalized);
1758
+ case "error":
1759
+ return chalk.hex(TOKENS.error)(normalized);
1760
+ case "info":
1761
+ return chalk.hex(TOKENS.info)(normalized);
1762
+ case "accent":
1763
+ return chalk.hex(TOKENS.accent)(normalized);
1764
+ case "muted":
1765
+ return chalk.dim(normalized);
1623
1766
  }
1624
- return null;
1625
1767
  }
1626
- async function closeSession(ctx) {
1627
- return finalizeSession(ctx, ctx.stage);
1768
+ function sectionHeading(label) {
1769
+ return `${paint("accent", "\u25B8")} ${paint("accent", bold(label))}`;
1628
1770
  }
1629
- async function endSession(ctx) {
1630
- return finalizeSession(ctx, "ended");
1771
+ function actionHint(label, command, detail) {
1772
+ const suffix = detail ? chalk.dim(` ${detail}`) : "";
1773
+ return `${chalk.dim(label)} ${paint("accent", command)}${suffix}`;
1631
1774
  }
1632
- async function rotateToFreshSession(ctx) {
1633
- const { setActiveDbPath: setActiveDbPath2 } = await Promise.resolve().then(() => (init_connection(), connection_exports));
1634
- const newId = makeSessionId();
1635
- resetContextForSwitch(ctx, {
1636
- sessionId: newId,
1637
- sessionFile: join2(getSessionsDir(), `${newId}.json`),
1638
- messages: [],
1639
- stage: "new",
1640
- analysis: defaultSessionAnalysis(),
1641
- llm: void 0
1642
- });
1643
- ctx.datasetPath = datasetPathForSession(newId);
1644
- await setActiveDbPath2(ctx.datasetPath);
1775
+ function scoreBar(score, status, width = 14) {
1776
+ const filled = Math.round(score / 100 * width);
1777
+ const color = chalk.hex(STATUS_COLORS[status]);
1778
+ let filledPart = "";
1779
+ for (let i = 0; i < filled; i++) {
1780
+ filledPart += i % 2 === 0 ? "\u2588" : "\u2593";
1781
+ }
1782
+ const emptyPart = "\u2591".repeat(width - filled);
1783
+ return color(filledPart) + chalk.dim(emptyPart);
1645
1784
  }
1646
- async function finalizeSession(ctx, stage) {
1647
- if (ctx.oneShot) return void 0;
1648
- const exchangeCount = Math.floor(ctx.messages.length / 2);
1649
- const endedAt = (/* @__PURE__ */ new Date()).toISOString();
1650
- if (ctx.messages.length === 0 && ctx.stage === "new" && !ctx.dataset && ctx.deliverables.length === 0) {
1651
- if (ctx.datasetPath) {
1652
- try {
1653
- const { close: close2 } = await Promise.resolve().then(() => (init_connection(), connection_exports));
1654
- await close2();
1655
- } catch {
1656
- }
1657
- for (const path of [ctx.datasetPath, `${ctx.datasetPath}.wal`]) {
1658
- try {
1659
- rmSync2(path, { force: true });
1660
- } catch {
1661
- }
1662
- }
1785
+ var STATUS_COLORS, TOKENS;
1786
+ var init_theme = __esm({
1787
+ "src/ui/theme.ts"() {
1788
+ "use strict";
1789
+ init_formatters();
1790
+ STATUS_COLORS = {
1791
+ green: "#22c55e",
1792
+ yellow: "#eab308",
1793
+ red: "#ef4444"
1794
+ };
1795
+ TOKENS = {
1796
+ accent: "#14b8a6",
1797
+ accentBright: "#2dd4bf",
1798
+ border: "#334155",
1799
+ borderMuted: "#1e293b",
1800
+ dim: "#64748b",
1801
+ text: "#e2e8f0",
1802
+ error: "#ef4444",
1803
+ warning: "#eab308",
1804
+ success: "#22c55e",
1805
+ info: "#3b82f6"
1806
+ };
1807
+ }
1808
+ });
1809
+
1810
+ // src/whimsy/time-milestones.ts
1811
+ function getMilestoneById(id) {
1812
+ return TIME_MILESTONES.find((m) => m.id === id);
1813
+ }
1814
+ function nextMilestone(totalHours, unlocked) {
1815
+ for (const m of TIME_MILESTONES) {
1816
+ if (!unlocked.includes(m.id) && totalHours < m.hours) {
1817
+ return m;
1663
1818
  }
1664
- return void 0;
1665
1819
  }
1666
- const file = {
1667
- id: ctx.sessionId,
1668
- created_at: ctx.messages[0]?.at ?? endedAt,
1669
- messages: ctx.messages,
1670
- ended_at: endedAt,
1671
- exchange_count: exchangeCount,
1672
- stage
1673
- };
1674
- if (ctx.resumedFromId) {
1675
- file.resumed_from = ctx.resumedFromId;
1820
+ return null;
1821
+ }
1822
+ function newlyUnlockedMilestones(previousMinutes, newMinutes, unlocked) {
1823
+ const prevHours = previousMinutes / 60;
1824
+ const newHours = newMinutes / 60;
1825
+ return TIME_MILESTONES.filter(
1826
+ (m) => !unlocked.includes(m.id) && newHours >= m.hours && prevHours < m.hours
1827
+ );
1828
+ }
1829
+ var TIME_MILESTONES;
1830
+ var init_time_milestones = __esm({
1831
+ "src/whimsy/time-milestones.ts"() {
1832
+ "use strict";
1833
+ TIME_MILESTONES = [
1834
+ {
1835
+ id: "first_hour",
1836
+ hours: 1,
1837
+ title: "First hour back",
1838
+ message: "First hour back. That's one pipeline standup you didn't have to sit through."
1839
+ },
1840
+ {
1841
+ id: "half_day",
1842
+ hours: 4,
1843
+ title: "Half day",
1844
+ message: "4 hours saved \u2014 a half-day an analyst would've billed you for."
1845
+ },
1846
+ {
1847
+ id: "analyst_day",
1848
+ hours: 8,
1849
+ title: "Analyst day",
1850
+ message: "A full analyst day, reclaimed."
1851
+ },
1852
+ {
1853
+ id: "long_weekend",
1854
+ hours: 24,
1855
+ title: "Three days",
1856
+ message: "Three analyst days. You could've been in spreadsheets."
1857
+ },
1858
+ {
1859
+ id: "analyst_week",
1860
+ hours: 40,
1861
+ title: "Analyst week",
1862
+ message: "A week of analyst time. Your calendar thanks you."
1863
+ },
1864
+ {
1865
+ id: "analyst_fortnight",
1866
+ hours: 80,
1867
+ title: "Two weeks",
1868
+ message: "Two weeks of manual pipeline archaeology \u2014 skipped."
1869
+ },
1870
+ {
1871
+ id: "analyst_month",
1872
+ hours: 160,
1873
+ title: "Analyst month",
1874
+ message: "A month of analyst hours. That's a hiring conversation you didn't need."
1875
+ },
1876
+ {
1877
+ id: "quarter_fte",
1878
+ hours: 500,
1879
+ title: "Quarter FTE",
1880
+ message: "500 hours. That's a quarter of a full-time analyst year."
1881
+ },
1882
+ {
1883
+ id: "two_quarters",
1884
+ hours: 600,
1885
+ title: "Two quarters",
1886
+ message: "600 hours \u2014 half a fiscal year of analyst time, back in your calendar."
1887
+ },
1888
+ {
1889
+ id: "nine_months",
1890
+ hours: 720,
1891
+ title: "Nine months",
1892
+ message: "720 hours. Three quarters of a year \u2014 most teams never get this much outside help."
1893
+ },
1894
+ {
1895
+ id: "eleven_months",
1896
+ hours: 840,
1897
+ title: "Eleven months",
1898
+ message: "840 hours saved. You're one month shy of a full annual arc."
1899
+ },
1900
+ {
1901
+ id: "annual_arc",
1902
+ hours: 960,
1903
+ title: "Annual arc",
1904
+ message: "960 hours \u2014 a year of normal use, banked. The subscription paid for itself."
1905
+ },
1906
+ {
1907
+ id: "subscription_year",
1908
+ hours: 1100,
1909
+ title: "Subscription year",
1910
+ message: "1,100 hours. A full year plus wiggle room \u2014 even power users rarely climb higher."
1911
+ }
1912
+ ];
1676
1913
  }
1677
- if (ctx.sessionName) {
1678
- file.name = ctx.sessionName;
1914
+ });
1915
+
1916
+ // src/whimsy/time-perspectives.ts
1917
+ function getPerspectiveById(id) {
1918
+ return TIME_PERSPECTIVES.find((p) => p.id === id);
1919
+ }
1920
+ function ratioInBand(perspective, totalHours) {
1921
+ const ratio = totalHours / perspective.reference_hours;
1922
+ const min = perspective.min_ratio ?? 0.3;
1923
+ const max = perspective.max_ratio ?? 300;
1924
+ return ratio >= min && ratio <= max;
1925
+ }
1926
+ function pickPerspective(totalHours, options = {}) {
1927
+ if (totalHours <= 0) return null;
1928
+ const exclude = new Set(options.excludeIds ?? []);
1929
+ let candidates = TIME_PERSPECTIVES.filter((p) => !exclude.has(p.id) && ratioInBand(p, totalHours));
1930
+ if (candidates.length === 0) {
1931
+ candidates = TIME_PERSPECTIVES.filter((p) => !exclude.has(p.id));
1932
+ }
1933
+ if (candidates.length === 0) return TIME_PERSPECTIVES[0] ?? null;
1934
+ const otherCategories = candidates.filter((p) => p.category !== options.lastCategory);
1935
+ const pool = otherCategories.length > 0 ? otherCategories : candidates;
1936
+ const seed = options.seed ?? Date.now();
1937
+ return pool[Math.abs(seed) % pool.length] ?? null;
1938
+ }
1939
+ function formatRatio(ratio) {
1940
+ if (ratio >= 100) return Math.round(ratio).toString();
1941
+ if (ratio >= 10) return ratio.toFixed(0);
1942
+ if (ratio >= 1) return ratio.toFixed(1);
1943
+ return ratio.toFixed(2);
1944
+ }
1945
+ function formatPct(pct) {
1946
+ if (pct >= 10) return Math.round(pct).toString();
1947
+ if (pct >= 1) return pct.toFixed(1);
1948
+ return pct.toFixed(2);
1949
+ }
1950
+ function formatPerspectiveLine(perspective, totalHours) {
1951
+ const ratio = totalHours / perspective.reference_hours;
1952
+ const pct = ratio * 100;
1953
+ return perspective.template.replace("{ratio}", formatRatio(ratio)).replace("{pct}", formatPct(pct)).replace("{label}", perspective.label);
1954
+ }
1955
+ var TIME_PERSPECTIVES;
1956
+ var init_time_perspectives = __esm({
1957
+ "src/whimsy/time-perspectives.ts"() {
1958
+ "use strict";
1959
+ TIME_PERSPECTIVES = [
1960
+ { 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 },
1961
+ { id: "rush_2112", category: "music", reference_hours: 0.33, label: "2112", template: "\u2248 {ratio}\xD7 through {label}", min_ratio: 2, max_ratio: 200 },
1962
+ { id: "bohemian_rhapsody", category: "music", reference_hours: 0.1, label: "Bohemian Rhapsody", template: "\u2248 {ratio}\xD7 through {label}", min_ratio: 5, max_ratio: 500 },
1963
+ { id: "stairway", category: "music", reference_hours: 0.13, label: "Stairway to Heaven", template: "\u2248 {ratio}\xD7 through {label}", min_ratio: 5, max_ratio: 400 },
1964
+ { 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 },
1965
+ { id: "abbey_road", category: "music", reference_hours: 0.8, label: "Abbey Road", template: "\u2248 {ratio}\xD7 through {label}", min_ratio: 1, max_ratio: 200 },
1966
+ { 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 },
1967
+ { 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 },
1968
+ { 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 },
1969
+ { 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 },
1970
+ { 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 },
1971
+ { id: "blockbuster", category: "film", reference_hours: 2.1, label: "average blockbusters", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 1, max_ratio: 100 },
1972
+ { 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 },
1973
+ { id: "scorsese", category: "film", reference_hours: 3.5, label: "Goodfellas", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 1, max_ratio: 80 },
1974
+ { id: "godfather", category: "film", reference_hours: 6.5, label: "the Godfather saga", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 0.5, max_ratio: 20 },
1975
+ { 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 },
1976
+ { 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 },
1977
+ { 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 },
1978
+ { 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 },
1979
+ { 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 },
1980
+ { id: "soccer_match", category: "sports", reference_hours: 1.75, label: "Premier League matches", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 1, max_ratio: 150 },
1981
+ { id: "marathon", category: "sports", reference_hours: 2, label: "marathons at world-record pace", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 1, max_ratio: 80 },
1982
+ { 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 },
1983
+ { id: "superbowl", category: "sports", reference_hours: 3.5, label: "Super Bowls", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 0.5, max_ratio: 80 },
1984
+ { 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 },
1985
+ { id: "wimbledon", category: "sports", reference_hours: 5, label: "Wimbledon finals", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 0.3, max_ratio: 30 },
1986
+ { 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 },
1987
+ { id: "olympics", category: "sports", reference_hours: 250, label: "Summer Olympics broadcast hours", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 2, max_ratio: 10 },
1988
+ { 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 },
1989
+ { id: "iss_orbit", category: "cosmos", reference_hours: 1.5, label: "ISS orbits", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 2, max_ratio: 200 },
1990
+ { 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 },
1991
+ { 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 },
1992
+ { 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 },
1993
+ { 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 },
1994
+ { id: "earth_rotation", category: "cosmos", reference_hours: 24, label: "Earth rotations", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 0.1, max_ratio: 50 },
1995
+ { 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 },
1996
+ { id: "lunar_month", category: "cosmos", reference_hours: 708, label: "a lunar cycle", template: "\u2248 {pct}% of {label}", min_ratio: 0.05, max_ratio: 2 },
1997
+ { 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 },
1998
+ { 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 },
1999
+ { id: "standup", category: "gtm", reference_hours: 0.25, label: "daily standups", template: "\u2248 {ratio}\xD7 skipped {label}", min_ratio: 4, max_ratio: 500 },
2000
+ { id: "quick_sync", category: "gtm", reference_hours: 0.5, label: "avoided 'quick syncs'", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 2, max_ratio: 200 },
2001
+ { id: "pipeline_review", category: "gtm", reference_hours: 1, label: "weekly pipeline reviews", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 1, max_ratio: 200 },
2002
+ { id: "forecast_call", category: "gtm", reference_hours: 1.5, label: "forecast calls", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 1, max_ratio: 150 },
2003
+ { id: "pivot_spiral", category: "gtm", reference_hours: 2, label: "spreadsheet pivot-table spirals", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 1, max_ratio: 100 },
2004
+ { 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 },
2005
+ { id: "crm_cleanup", category: "gtm", reference_hours: 6, label: "CRM hygiene sprints", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 0.5, max_ratio: 40 },
2006
+ { id: "qbr_prep", category: "gtm", reference_hours: 8, label: "QBR prep blocks", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 0.3, max_ratio: 20 },
2007
+ { id: "board_deck", category: "gtm", reference_hours: 12, label: "board deck builds", template: "\u2248 {ratio}\xD7 {label}", min_ratio: 0.3, max_ratio: 30 },
2008
+ { 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 },
2009
+ { 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 }
2010
+ ];
1679
2011
  }
1680
- if (ctx.dataset) {
1681
- file.dataset = ctx.dataset;
2012
+ });
2013
+
2014
+ // src/whimsy/time-bank-whimsy.ts
2015
+ function formatHours(h) {
2016
+ if (h < 1) return `${Math.round(h * 60)}m`;
2017
+ if (h < 10) return `${h.toFixed(1)}h`;
2018
+ return `${Math.round(h)}h`;
2019
+ }
2020
+ function randomNearMilestoneGoodbye(hoursSaved, hoursToNext, nextTitle) {
2021
+ const pool = NEAR_MILESTONE_GOODBYES;
2022
+ const fn = pool[Math.floor(Math.random() * pool.length)] ?? pool[0];
2023
+ return fn(hoursSaved, hoursToNext, nextTitle);
2024
+ }
2025
+ var NEAR_MILESTONE_GOODBYES;
2026
+ var init_time_bank_whimsy = __esm({
2027
+ "src/whimsy/time-bank-whimsy.ts"() {
2028
+ "use strict";
2029
+ NEAR_MILESTONE_GOODBYES = [
2030
+ (saved, toGo, next) => `${formatHours(saved)} saved \u2014 ${formatHours(toGo)} from ${next}. Almost there.`,
2031
+ (saved, toGo, next) => `${formatHours(saved)} in the bank. One more push hits ${next}.`,
2032
+ (saved, _toGo, next) => `You're at ${formatHours(saved)}. ${next} is right around the corner.`,
2033
+ (saved, toGo, next) => `${formatHours(toGo)} to ${next}. You've already banked ${formatHours(saved)}.`,
2034
+ (saved, _toGo, next) => `Close \u2014 ${formatHours(saved)} saved and ${next} is within reach.`
2035
+ ];
1682
2036
  }
1683
- if (ctx.deliverables.length > 0) {
1684
- file.deliverables = ctx.deliverables;
2037
+ });
2038
+
2039
+ // src/whimsy/perspective-rotation.ts
2040
+ function perspectiveRotationDue(state, now2 = Date.now()) {
2041
+ const perspectiveId = state.perspective_id ?? state.last_perspective_id;
2042
+ if (!perspectiveId) return true;
2043
+ const rotatedAt = state.perspective_rotated_at ? Date.parse(state.perspective_rotated_at) : 0;
2044
+ const minutesAtRotation = state.perspective_minutes_at_rotation ?? 0;
2045
+ const creditedSince = state.total_minutes_saved - minutesAtRotation;
2046
+ const msSince = rotatedAt > 0 ? now2 - rotatedAt : PERSPECTIVE_ROTATE_CALENDAR_MS;
2047
+ return creditedSince >= PERSPECTIVE_ROTATE_CREDIT_MINUTES || msSince >= PERSPECTIVE_ROTATE_CALENDAR_MS;
2048
+ }
2049
+ function rotationSeed(state) {
2050
+ const epoch = state.perspective_minutes_at_rotation ?? state.total_minutes_saved;
2051
+ const count = state.perspective_rotation_count ?? 0;
2052
+ return epoch * 31 + count * 17;
2053
+ }
2054
+ function bumpRecentPerspectiveIds(recent, id) {
2055
+ const next = [...(recent ?? []).filter((x) => x !== id), id];
2056
+ if (next.length > PERSPECTIVE_EXCLUDE_RECENT) {
2057
+ next.splice(0, next.length - PERSPECTIVE_EXCLUDE_RECENT);
1685
2058
  }
1686
- if (ctx.conversation.length > 0) {
1687
- file.thread = ctx.conversation;
2059
+ return next;
2060
+ }
2061
+ var PERSPECTIVE_ROTATE_CREDIT_MINUTES, PERSPECTIVE_ROTATE_CALENDAR_MS, PERSPECTIVE_EXCLUDE_RECENT;
2062
+ var init_perspective_rotation = __esm({
2063
+ "src/whimsy/perspective-rotation.ts"() {
2064
+ "use strict";
2065
+ PERSPECTIVE_ROTATE_CREDIT_MINUTES = 180;
2066
+ PERSPECTIVE_ROTATE_CALENDAR_MS = 7 * 24 * 60 * 60 * 1e3;
2067
+ PERSPECTIVE_EXCLUDE_RECENT = 6;
1688
2068
  }
1689
- if (ctx.analysis) {
1690
- file.analysis = ctx.analysis;
2069
+ });
2070
+
2071
+ // src/whimsy/usage-stats.ts
2072
+ var usage_stats_exports = {};
2073
+ __export(usage_stats_exports, {
2074
+ buildUsageSummary: () => buildUsageSummary,
2075
+ getUsageStats: () => getUsageStats,
2076
+ isoWeekKey: () => isoWeekKey2,
2077
+ recordLlmUsage: () => recordLlmUsage,
2078
+ recordSessionClosed: () => recordSessionClosed,
2079
+ recordUsageFromCredit: () => recordUsageFromCredit
2080
+ });
2081
+ function emptyUsage() {
2082
+ return {
2083
+ sessions_closed: 0,
2084
+ diagnoses: 0,
2085
+ metrics_runs: 0,
2086
+ deliverables: 0,
2087
+ nl_exchanges: 0,
2088
+ llm_calls: 0,
2089
+ input_tokens: 0,
2090
+ output_tokens: 0,
2091
+ weekly: []
2092
+ };
2093
+ }
2094
+ function ensureUsage(state) {
2095
+ return state.usage ?? emptyUsage();
2096
+ }
2097
+ function isoWeekKey2(d = /* @__PURE__ */ new Date()) {
2098
+ const date = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
2099
+ const day = date.getUTCDay() || 7;
2100
+ date.setUTCDate(date.getUTCDate() + 4 - day);
2101
+ const yearStart = new Date(Date.UTC(date.getUTCFullYear(), 0, 1));
2102
+ const weekNo = Math.ceil(((date.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
2103
+ return `${date.getUTCFullYear()}-W${String(weekNo).padStart(2, "0")}`;
2104
+ }
2105
+ function bumpWeekly2(weekly, patch) {
2106
+ const week = patch.week ?? isoWeekKey2();
2107
+ const idx = weekly.findIndex((w) => w.week === week);
2108
+ const row = idx >= 0 ? { ...weekly[idx] } : { week, minutes_saved: 0, llm_calls: 0, actions: 0 };
2109
+ if (patch.minutes_saved) row.minutes_saved += patch.minutes_saved;
2110
+ if (patch.llm_calls) row.llm_calls += patch.llm_calls;
2111
+ if (patch.actions) row.actions += patch.actions;
2112
+ const next = idx >= 0 ? weekly.map((w, i) => i === idx ? row : w) : [...weekly, row];
2113
+ if (next.length > WEEKLY_CAP) next.splice(0, next.length - WEEKLY_CAP);
2114
+ return next;
2115
+ }
2116
+ function touchUsage(state, patch) {
2117
+ const now2 = (/* @__PURE__ */ new Date()).toISOString();
2118
+ const usage = ensureUsage(state);
2119
+ return {
2120
+ ...state,
2121
+ usage: {
2122
+ ...usage,
2123
+ ...patch,
2124
+ first_active_at: usage.first_active_at ?? now2,
2125
+ last_active_at: now2,
2126
+ weekly: patch.weekly ?? usage.weekly
2127
+ }
2128
+ };
2129
+ }
2130
+ function recordUsageFromCredit(action, minutes) {
2131
+ if (minutes <= 0) return;
2132
+ let state = loadProgress();
2133
+ const usage = ensureUsage(state);
2134
+ const weekly = bumpWeekly2(usage.weekly, { minutes_saved: minutes, actions: 1 });
2135
+ const counters = { weekly };
2136
+ if (action === "diagnose" || action === "diagnose_findings") counters.diagnoses = usage.diagnoses + 1;
2137
+ if (action === "metrics" || action === "metrics_findings") counters.metrics_runs = usage.metrics_runs + 1;
2138
+ if (action === "deliverable" || action === "deliverable_deck") counters.deliverables = usage.deliverables + 1;
2139
+ if (action === "nl_answer") counters.nl_exchanges = usage.nl_exchanges + 1;
2140
+ state = touchUsage(state, counters);
2141
+ saveProgress(state);
2142
+ }
2143
+ function recordSessionClosed() {
2144
+ let state = loadProgress();
2145
+ const usage = ensureUsage(state);
2146
+ state = touchUsage(state, {
2147
+ sessions_closed: usage.sessions_closed + 1,
2148
+ weekly: bumpWeekly2(usage.weekly, { actions: 1 })
2149
+ });
2150
+ saveProgress(state);
2151
+ }
2152
+ function recordLlmUsage(tokenUsage) {
2153
+ let state = loadProgress();
2154
+ const usage = ensureUsage(state);
2155
+ const weekly = bumpWeekly2(usage.weekly, { llm_calls: 1 });
2156
+ state = touchUsage(state, {
2157
+ llm_calls: usage.llm_calls + 1,
2158
+ input_tokens: usage.input_tokens + (tokenUsage?.input_tokens ?? 0),
2159
+ output_tokens: usage.output_tokens + (tokenUsage?.output_tokens ?? 0),
2160
+ weekly
2161
+ });
2162
+ saveProgress(state);
2163
+ }
2164
+ function getUsageStats() {
2165
+ return ensureUsage(loadProgress());
2166
+ }
2167
+ function buildUsageSummary(sessionCounts, totalHours, milestonesUnlocked, milestoneTotal) {
2168
+ return {
2169
+ usage: getUsageStats(),
2170
+ total_sessions_on_disk: sessionCounts.total,
2171
+ sessions_with_work: sessionCounts.withWork,
2172
+ total_hours_saved: totalHours,
2173
+ milestones_unlocked: milestonesUnlocked,
2174
+ milestone_total: milestoneTotal
2175
+ };
2176
+ }
2177
+ var WEEKLY_CAP;
2178
+ var init_usage_stats = __esm({
2179
+ "src/whimsy/usage-stats.ts"() {
2180
+ "use strict";
2181
+ init_progress();
2182
+ WEEKLY_CAP = 52;
2183
+ }
2184
+ });
2185
+
2186
+ // src/whimsy/time-bank.ts
2187
+ var time_bank_exports = {};
2188
+ __export(time_bank_exports, {
2189
+ creditDeliverable: () => creditDeliverable,
2190
+ creditDiagnoseComplete: () => creditDiagnoseComplete,
2191
+ creditGapCompute: () => creditGapCompute,
2192
+ creditMetricsComplete: () => creditMetricsComplete,
2193
+ creditNlAnswer: () => creditNlAnswer,
2194
+ creditOnboardComplete: () => creditOnboardComplete,
2195
+ creditSessionDeliverableWrapup: () => creditSessionDeliverableWrapup,
2196
+ formatHoursLabel: () => formatHoursLabel,
2197
+ getTimeBankSummary: () => getTimeBankSummary,
2198
+ isNearNextMilestone: () => isNearNextMilestone,
2199
+ loadTimeBankState: () => loadTimeBankState,
2200
+ pickGoodbyeWithTimeBank: () => pickGoodbyeWithTimeBank,
2201
+ printTimeBankCelebration: () => printTimeBankCelebration,
2202
+ recordTimeCredit: () => recordTimeCredit
2203
+ });
2204
+ import chalk2 from "chalk";
2205
+ function actionKey(action, ctx, suffix) {
2206
+ const sessionScoped = /* @__PURE__ */ new Set([
2207
+ "gap_compute",
2208
+ "diagnose",
2209
+ "diagnose_findings",
2210
+ "metrics",
2211
+ "metrics_findings",
2212
+ "deliverable",
2213
+ "deliverable_deck",
2214
+ "session_deliverable_wrapup",
2215
+ "nl_answer"
2216
+ ]);
2217
+ if (sessionScoped.has(action) && ctx?.sessionId) {
2218
+ return suffix ? `${action}:${ctx.sessionId}:${suffix}` : `${action}:${ctx.sessionId}`;
2219
+ }
2220
+ return action;
2221
+ }
2222
+ function shouldSkip(ctx) {
2223
+ return !ctx || ctx.oneShot;
2224
+ }
2225
+ function recordTimeCredit(action, ctx, opts) {
2226
+ if (shouldSkip(ctx)) return null;
2227
+ const minutes = ACTION_MINUTES[action];
2228
+ if (!minutes || minutes <= 0) return null;
2229
+ const key = actionKey(action, ctx, opts?.suffix);
2230
+ let state = loadProgress();
2231
+ if (hasCreditAction(state, key)) {
2232
+ return { credited_minutes: 0, new_milestones: [], total_minutes: state.total_minutes_saved };
2233
+ }
2234
+ const previousMinutes = state.total_minutes_saved;
2235
+ const credit = {
2236
+ action: key,
2237
+ minutes,
2238
+ at: (/* @__PURE__ */ new Date()).toISOString(),
2239
+ session_id: ctx?.sessionId
2240
+ };
2241
+ state = appendCredit(state, credit);
2242
+ const unlocked = newlyUnlockedMilestones(
2243
+ previousMinutes,
2244
+ state.total_minutes_saved,
2245
+ state.milestones_unlocked
2246
+ );
2247
+ if (unlocked.length > 0) {
2248
+ state = {
2249
+ ...state,
2250
+ milestones_unlocked: [...state.milestones_unlocked, ...unlocked.map((m) => m.id)]
2251
+ };
1691
2252
  }
1692
- if (ctx.scope) {
1693
- file.scope = ctx.scope;
2253
+ saveProgress(state);
2254
+ if (minutes > 0) {
2255
+ recordUsageFromCredit(action, minutes);
2256
+ state = maybeRotatePerspective(state, state.total_minutes_saved / 60);
2257
+ saveProgress(state);
1694
2258
  }
1695
- if (ctx.attachments && ctx.attachments.length > 0) {
1696
- file.attachments = ctx.attachments;
2259
+ if (!opts?.silent && unlocked.length > 0) {
2260
+ for (const m of unlocked) {
2261
+ printTimeBankCelebration(m, state.total_minutes_saved);
2262
+ }
1697
2263
  }
1698
- if (ctx.llm && Object.keys(ctx.llm).length > 0) {
1699
- file.llm = ctx.llm;
2264
+ return {
2265
+ credited_minutes: minutes,
2266
+ new_milestones: unlocked,
2267
+ total_minutes: state.total_minutes_saved
2268
+ };
2269
+ }
2270
+ function creditGapCompute(ctx) {
2271
+ recordTimeCredit("gap_compute", ctx);
2272
+ if (!hasCreditAction(loadProgress(), "gap_compute_first_ever")) {
2273
+ recordTimeCredit("gap_compute_first_ever", ctx);
1700
2274
  }
1701
- try {
1702
- writeFileSync(ctx.sessionFile, JSON.stringify(file, null, 2) + "\n");
1703
- } catch {
2275
+ }
2276
+ function creditDiagnoseComplete(ctx, withFindings) {
2277
+ recordTimeCredit("diagnose", ctx);
2278
+ if (withFindings) {
2279
+ recordTimeCredit("diagnose_findings", ctx);
1704
2280
  }
1705
- return void 0;
1706
2281
  }
1707
- function resolveSessionByToken(idArg, options) {
1708
- const printErrors = options?.printErrors !== false;
1709
- const all2 = listSessions();
1710
- const lower = idArg.toLowerCase();
1711
- let matches = all2.filter((s) => s.id === idArg);
1712
- if (matches.length === 0) matches = all2.filter((s) => s.name?.toLowerCase() === lower);
1713
- if (matches.length === 0 && idArg.length >= 4) {
1714
- matches = all2.filter((s) => s.id.endsWith(idArg));
2282
+ function creditMetricsComplete(ctx, withFindings) {
2283
+ recordTimeCredit("metrics", ctx);
2284
+ if (withFindings) {
2285
+ recordTimeCredit("metrics_findings", ctx);
1715
2286
  }
1716
- if (matches.length === 0) return void 0;
1717
- if (matches.length > 1) {
1718
- if (printErrors) {
1719
- console.log(
1720
- ` Ambiguous "${idArg}" \u2014 matches ${matches.length} sessions. Use a longer id.`
1721
- );
1722
- }
1723
- return null;
2287
+ }
2288
+ function creditDeliverable(ctx, target) {
2289
+ recordTimeCredit("deliverable", ctx);
2290
+ if (target === "deck") {
2291
+ recordTimeCredit("deliverable_deck", ctx);
1724
2292
  }
1725
- return matches[0];
1726
2293
  }
1727
- function findSessionByName(name) {
1728
- const lower = name.toLowerCase();
1729
- const all2 = listSessions();
1730
- return all2.find((s) => s.name?.toLowerCase() === lower) ?? null;
2294
+ function creditNlAnswer(ctx, exchangeIndex) {
2295
+ recordTimeCredit("nl_answer", ctx, { suffix: String(exchangeIndex) });
1731
2296
  }
1732
- function buildSwitchContext(session) {
1733
- const parts = [];
1734
- if (session.summary) parts.push(session.summary);
1735
- const userMsgs = session.messages.filter((m) => m.role === "user").slice(-3);
1736
- for (const m of userMsgs) {
1737
- parts.push(m.content.slice(0, 300));
2297
+ function creditOnboardComplete(ctx) {
2298
+ recordTimeCredit("onboard", ctx);
2299
+ }
2300
+ function creditSessionDeliverableWrapup(ctx) {
2301
+ recordTimeCredit("session_deliverable_wrapup", ctx);
2302
+ }
2303
+ function activePerspectiveId(state) {
2304
+ return state.perspective_id ?? state.last_perspective_id;
2305
+ }
2306
+ function maybeRotatePerspective(state, totalHours) {
2307
+ const currentId = activePerspectiveId(state);
2308
+ const current = currentId ? getPerspectiveById(currentId) : void 0;
2309
+ const staleBand = current && !ratioInBand2(current, totalHours);
2310
+ if (!perspectiveRotationDue(state) && current && !staleBand) {
2311
+ return state;
1738
2312
  }
1739
- return parts.join("\n");
2313
+ const lastCategory = current?.category;
2314
+ const picked = pickPerspective(totalHours, {
2315
+ excludeIds: state.recent_perspective_ids ?? [],
2316
+ lastCategory,
2317
+ seed: rotationSeed(state)
2318
+ });
2319
+ if (!picked) return state;
2320
+ return {
2321
+ ...state,
2322
+ perspective_id: picked.id,
2323
+ last_perspective_id: picked.id,
2324
+ perspective_rotated_at: (/* @__PURE__ */ new Date()).toISOString(),
2325
+ perspective_minutes_at_rotation: state.total_minutes_saved,
2326
+ perspective_rotation_count: (state.perspective_rotation_count ?? 0) + 1,
2327
+ recent_perspective_ids: bumpRecentPerspectiveIds(state.recent_perspective_ids, picked.id)
2328
+ };
1740
2329
  }
1741
- function resetContextForSwitch(ctx, opts) {
1742
- ctx.sessionId = opts.sessionId;
1743
- ctx.sessionFile = opts.sessionFile;
1744
- ctx.sessionName = opts.sessionName;
1745
- ctx.messages = opts.messages;
1746
- ctx.conversation = opts.conversation ?? [];
1747
- ctx.resumedFromId = opts.resumedFromId;
1748
- ctx.resumedSessionSummary = opts.resumedSessionSummary;
1749
- ctx.stage = opts.stage ?? "new";
1750
- ctx.dataset = opts.dataset;
1751
- ctx.deliverables = opts.deliverables ?? [];
1752
- ctx.analysis = opts.analysis ?? defaultSessionAnalysis();
1753
- ctx.scope = opts.scope;
1754
- ctx.attachments = opts.attachments ?? [];
1755
- ctx.llm = opts.llm;
1756
- ctx.gapAudit = void 0;
1757
- ctx.deliverIntent = false;
1758
- ctx.computeInProgress = false;
1759
- ctx.wizardDepth = 0;
1760
- ctx.snapshot = { computeResult: null, divergences: [] };
2330
+ function ratioInBand2(perspective, totalHours) {
2331
+ const ratio = totalHours / perspective.reference_hours;
2332
+ const min = perspective.min_ratio ?? 0.3;
2333
+ const max = perspective.max_ratio ?? 300;
2334
+ return ratio >= min && ratio <= max;
2335
+ }
2336
+ function getTimeBankSummary() {
2337
+ let state = loadProgress();
2338
+ const total_minutes = state.total_minutes_saved;
2339
+ const total_hours = total_minutes / 60;
2340
+ if (total_minutes > 0) {
2341
+ state = maybeRotatePerspective(state, total_hours);
2342
+ saveProgress(state);
2343
+ }
2344
+ const next = nextMilestone(total_hours, state.milestones_unlocked);
2345
+ let progress_pct = 100;
2346
+ if (next) {
2347
+ const prevMilestone = state.milestones_unlocked.length > 0 ? getMilestoneById(state.milestones_unlocked[state.milestones_unlocked.length - 1]) : void 0;
2348
+ const prevHours = prevMilestone?.hours ?? 0;
2349
+ const span = next.hours - prevHours;
2350
+ progress_pct = span > 0 ? Math.min(100, (total_hours - prevHours) / span * 100) : 0;
2351
+ }
2352
+ const perspectiveId = activePerspectiveId(state);
2353
+ const perspective = perspectiveId ? getPerspectiveById(perspectiveId) : null;
2354
+ const perspective_line = perspective ? formatPerspectiveLine(perspective, total_hours) : null;
2355
+ return {
2356
+ total_hours,
2357
+ total_minutes,
2358
+ next_milestone: next,
2359
+ progress_pct,
2360
+ perspective_line
2361
+ };
1761
2362
  }
1762
- var SESSION_ID_RE;
1763
- var init_context2 = __esm({
1764
- "src/cli/context.ts"() {
2363
+ function printTimeBankCelebration(milestone, totalMinutes) {
2364
+ const totalHours = totalMinutes / 60;
2365
+ const state = loadProgress();
2366
+ const perspective = pickPerspective(totalHours, {
2367
+ excludeIds: state.recent_perspective_ids ?? [],
2368
+ seed: rotationSeed(state) + 1
2369
+ });
2370
+ console.log();
2371
+ console.log(" " + paint("accent", `\u2726 ${milestone.title}`) + chalk2.dim(` \u2014 ${formatHoursLabel(totalHours)} saved`));
2372
+ console.log(" " + chalk2.dim(milestone.message));
2373
+ if (perspective) {
2374
+ console.log(" " + chalk2.dim.italic(formatPerspectiveLine(perspective, totalHours)));
2375
+ }
2376
+ console.log();
2377
+ }
2378
+ function formatHoursLabel(hours) {
2379
+ if (hours < 1) return `${Math.round(hours * 60)}m`;
2380
+ if (hours < 10) return `${hours.toFixed(1)}h`;
2381
+ if (hours >= 1e3) return `${Math.round(hours).toLocaleString("en-US")}h`;
2382
+ return `${Math.round(hours)}h`;
2383
+ }
2384
+ function isNearNextMilestone(threshold = 0.15) {
2385
+ const state = loadProgress();
2386
+ if (state.total_minutes_saved <= 0) return false;
2387
+ const totalHours = state.total_minutes_saved / 60;
2388
+ const next = nextMilestone(totalHours, state.milestones_unlocked);
2389
+ if (!next) return false;
2390
+ const prev = state.milestones_unlocked.map((id) => getMilestoneById(id)).filter((m) => !!m).sort((a, b) => b.hours - a.hours)[0];
2391
+ const prevHours = prev?.hours ?? 0;
2392
+ const span = next.hours - prevHours;
2393
+ if (span <= 0) return false;
2394
+ const progress = (totalHours - prevHours) / span;
2395
+ return progress >= 1 - threshold;
2396
+ }
2397
+ function pickGoodbyeWithTimeBank() {
2398
+ if (Math.random() > 0.25) return null;
2399
+ if (!isNearNextMilestone()) return null;
2400
+ const state = loadProgress();
2401
+ const totalHours = state.total_minutes_saved / 60;
2402
+ const next = nextMilestone(totalHours, state.milestones_unlocked);
2403
+ if (!next) return null;
2404
+ const hoursToNext = Math.max(0, next.hours - totalHours);
2405
+ return randomNearMilestoneGoodbye(totalHours, hoursToNext, next.title);
2406
+ }
2407
+ function loadTimeBankState() {
2408
+ return loadProgress();
2409
+ }
2410
+ var ACTION_MINUTES;
2411
+ var init_time_bank = __esm({
2412
+ "src/whimsy/time-bank.ts"() {
1765
2413
  "use strict";
1766
- init_thread_compat();
1767
- init_context();
1768
- SESSION_ID_RE = /^\d{4}-\d{2}-\d{2}-[a-f0-9]{4}$/i;
2414
+ init_progress();
2415
+ init_theme();
2416
+ init_time_milestones();
2417
+ init_time_perspectives();
2418
+ init_time_bank_whimsy();
2419
+ init_perspective_rotation();
2420
+ init_usage_stats();
2421
+ ACTION_MINUTES = {
2422
+ gap_compute: 30,
2423
+ gap_compute_first_ever: 30,
2424
+ diagnose: 180,
2425
+ diagnose_findings: 60,
2426
+ metrics: 120,
2427
+ metrics_findings: 60,
2428
+ deliverable: 240,
2429
+ deliverable_deck: 120,
2430
+ nl_answer: 15,
2431
+ onboard: 30,
2432
+ session_deliverable_wrapup: 30
2433
+ };
1769
2434
  }
1770
2435
  });
1771
2436
 
1772
- // src/pipeline/segments.ts
1773
- function resolveSegmentScopeFromSnapshot(segment, snapshot) {
1774
- const orgIds = [];
1775
- const peopleIds = [];
1776
- const oppIds = [];
1777
- if (segment.entity_type === "organizations") {
1778
- const filtered = filterEntities(snapshot.organizations, segment.filters);
1779
- const orgIdSet = new Set(filtered.map((o) => o.id));
1780
- orgIds.push(...orgIdSet);
1781
- for (const p of snapshot.people) {
1782
- if (p.organization_id && orgIdSet.has(p.organization_id)) {
1783
- peopleIds.push(p.id);
1784
- }
1785
- }
1786
- for (const o of snapshot.opportunities) {
1787
- if (o.organization_id && orgIdSet.has(o.organization_id)) {
1788
- oppIds.push(o.id);
1789
- }
1790
- }
1791
- } else if (segment.entity_type === "opportunities") {
1792
- const filtered = filterEntities(snapshot.opportunities, segment.filters);
1793
- oppIds.push(...filtered.map((o) => o.id));
1794
- const orgIdSet = /* @__PURE__ */ new Set();
1795
- for (const o of filtered) {
1796
- if (o.organization_id) orgIdSet.add(o.organization_id);
1797
- }
1798
- orgIds.push(...orgIdSet);
1799
- for (const p of snapshot.people) {
1800
- if (p.organization_id && orgIdSet.has(p.organization_id)) {
1801
- peopleIds.push(p.id);
1802
- }
2437
+ // src/cli/context.ts
2438
+ var context_exports = {};
2439
+ __export(context_exports, {
2440
+ buildAnalysisBlock: () => buildAnalysisBlock,
2441
+ buildSwitchContext: () => buildSwitchContext,
2442
+ closeAllActiveSessions: () => closeAllActiveSessions,
2443
+ closeSession: () => closeSession,
2444
+ datasetPathForSession: () => datasetPathForSession,
2445
+ defaultSessionAnalysis: () => defaultSessionAnalysis,
2446
+ endSession: () => endSession,
2447
+ findSessionByName: () => findSessionByName,
2448
+ getActiveSessions: () => getActiveSessions,
2449
+ getDatasetsDir: () => getDatasetsDir,
2450
+ getLastActivityRelative: () => getLastActivityRelative,
2451
+ getLastWorkedSession: () => getLastWorkedSession,
2452
+ getRecentSessions: () => getRecentSessions,
2453
+ getSessionsDir: () => getSessionsDir,
2454
+ getUnfinishedSessions: () => getUnfinishedSessions,
2455
+ hydrateAnalysisFromPersistedState: () => hydrateAnalysisFromPersistedState,
2456
+ initContext: () => initContext,
2457
+ initHeadlessAgentContext: () => initHeadlessAgentContext,
2458
+ isAnalysisReady: () => isAnalysisReady,
2459
+ isSessionInProgress: () => isSessionInProgress,
2460
+ lensBadgeLabel: () => lensBadgeLabel,
2461
+ listSessions: () => listSessions,
2462
+ loadSessionFile: () => loadSessionFile,
2463
+ makeSessionId: () => makeSessionId,
2464
+ markLensCompleted: () => markLensCompleted,
2465
+ prefersMetricsFirstContext: () => prefersMetricsFirstContext,
2466
+ recordMessage: () => recordMessage,
2467
+ resetContextForSwitch: () => resetContextForSwitch,
2468
+ resolveSessionByToken: () => resolveSessionByToken,
2469
+ rotateToFreshSession: () => rotateToFreshSession,
2470
+ saveSessionState: () => saveSessionState,
2471
+ setPrimaryLens: () => setPrimaryLens
2472
+ });
2473
+ import { basename, join as join6, resolve as resolve3, sep } from "path";
2474
+ import { existsSync as existsSync6, mkdirSync as mkdirSync5, writeFileSync as writeFileSync5, readFileSync as readFileSync5, readdirSync, statSync, rmSync as rmSync2 } from "fs";
2475
+ import { homedir as homedir2 } from "os";
2476
+ import { randomUUID as randomUUID3 } from "crypto";
2477
+ function isAnalysisReady(ctx) {
2478
+ if (ctx.stage !== "analyzed" || ctx.analysis.completed.length === 0) return false;
2479
+ if (!ctx.dataset) return false;
2480
+ const counts = ctx.dataset.counts ?? {};
2481
+ return Object.values(counts).some((n) => n > 0);
2482
+ }
2483
+ function ntrpHomeDir() {
2484
+ return process.env.NTRP_HOME ? resolve3(process.env.NTRP_HOME) : join6(homedir2(), ".ntrp");
2485
+ }
2486
+ function getSessionsDir() {
2487
+ const dir = join6(ntrpHomeDir(), "sessions");
2488
+ if (!existsSync6(dir)) {
2489
+ mkdirSync5(dir, { recursive: true });
2490
+ }
2491
+ return dir;
2492
+ }
2493
+ function getDatasetsDir() {
2494
+ const dir = join6(ntrpHomeDir(), "datasets");
2495
+ if (!existsSync6(dir)) {
2496
+ mkdirSync5(dir, { recursive: true });
2497
+ }
2498
+ return dir;
2499
+ }
2500
+ function datasetPathForSession(id) {
2501
+ return join6(getDatasetsDir(), `${id}.duckdb`);
2502
+ }
2503
+ function makeSessionId() {
2504
+ const now2 = /* @__PURE__ */ new Date();
2505
+ const date = now2.toISOString().slice(0, 10);
2506
+ const uuid2 = randomUUID3().slice(0, 4);
2507
+ return `${date}-${uuid2}`;
2508
+ }
2509
+ function isValidSessionId(id) {
2510
+ return SESSION_ID_RE.test(id);
2511
+ }
2512
+ function sessionPathForId(id) {
2513
+ if (!isValidSessionId(id)) return null;
2514
+ const dir = resolve3(getSessionsDir());
2515
+ const filePath = resolve3(dir, `${id}.json`);
2516
+ if (filePath !== dir && !filePath.startsWith(dir + sep)) return null;
2517
+ return filePath;
2518
+ }
2519
+ function initContext(oneShot, execution) {
2520
+ const sessionId = makeSessionId();
2521
+ const sessionFile = join6(getSessionsDir(), `${sessionId}.json`);
2522
+ return {
2523
+ sessionId,
2524
+ sessionFile,
2525
+ oneShot,
2526
+ execution: buildExecutionOptions({
2527
+ mode: oneShot ? "one_shot" : "interactive",
2528
+ ...execution
2529
+ }),
2530
+ snapshot: { computeResult: null, divergences: [] },
2531
+ messages: [],
2532
+ conversation: [],
2533
+ stage: "new",
2534
+ deliverables: [],
2535
+ analysis: defaultSessionAnalysis(),
2536
+ wizardDepth: 0,
2537
+ attachments: [],
2538
+ deliverIntent: false,
2539
+ computeInProgress: false
2540
+ };
2541
+ }
2542
+ function buildSessionFile(ctx) {
2543
+ const file = {
2544
+ id: ctx.sessionId,
2545
+ created_at: ctx.messages[0]?.at ?? (/* @__PURE__ */ new Date()).toISOString(),
2546
+ messages: ctx.messages,
2547
+ stage: ctx.stage
2548
+ };
2549
+ if (ctx.sessionName) file.name = ctx.sessionName;
2550
+ if (ctx.dataset) file.dataset = ctx.dataset;
2551
+ if (ctx.deliverables.length > 0) file.deliverables = ctx.deliverables;
2552
+ if (ctx.conversation.length > 0) file.thread = ctx.conversation;
2553
+ if (ctx.resumedFromId) file.resumed_from = ctx.resumedFromId;
2554
+ if (ctx.analysis) file.analysis = ctx.analysis;
2555
+ if (ctx.scope) file.scope = ctx.scope;
2556
+ if (ctx.attachments && ctx.attachments.length > 0) file.attachments = ctx.attachments;
2557
+ if (ctx.llm && Object.keys(ctx.llm).length > 0) file.llm = ctx.llm;
2558
+ return file;
2559
+ }
2560
+ function defaultSessionAnalysis(primary = "gtm_health") {
2561
+ return { primary, completed: [] };
2562
+ }
2563
+ function setPrimaryLens(ctx, lens) {
2564
+ ctx.analysis = { ...ctx.analysis, primary: lens };
2565
+ }
2566
+ function markLensCompleted(ctx, lens) {
2567
+ const completed = ctx.analysis.completed.includes(lens) ? ctx.analysis.completed : [...ctx.analysis.completed, lens];
2568
+ ctx.analysis = { ...ctx.analysis, completed };
2569
+ }
2570
+ function lensBadgeLabel(analysis) {
2571
+ if (!analysis) return "health";
2572
+ const hasHealth = analysis.completed.includes("gtm_health") || analysis.primary === "gtm_health";
2573
+ const hasMetrics = analysis.completed.includes("revenue_metrics") || analysis.primary === "revenue_metrics";
2574
+ if (hasHealth && hasMetrics) return "both";
2575
+ if (hasMetrics) return "metrics";
2576
+ return "health";
2577
+ }
2578
+ function buildAnalysisBlock(ctx) {
2579
+ return [
2580
+ `Primary lens: ${ctx.analysis.primary}`,
2581
+ `Completed: ${ctx.analysis.completed.join(", ") || "none"}`,
2582
+ `Badge: ${lensBadgeLabel(ctx.analysis)}`,
2583
+ ctx.analysis.coverage ? `Coverage: ${ctx.analysis.coverage.distinct_months} months, ${ctx.analysis.coverage.recommended_cadence} cadence` : null,
2584
+ ctx.analysis.data_source_type ? `Data source: ${ctx.analysis.data_source_type}` : null
2585
+ ].filter(Boolean).join("\n");
2586
+ }
2587
+ function prefersMetricsFirstContext(ctx) {
2588
+ return ctx.analysis.primary === "revenue_metrics" && ctx.analysis.completed.includes("revenue_metrics") && !ctx.analysis.completed.includes("gtm_health");
2589
+ }
2590
+ async function hydrateAnalysisFromPersistedState(ctx) {
2591
+ const sessions = listSessions({ limit: 10 });
2592
+ const withAnalysis = sessions.find(
2593
+ (s) => s.analysis && (s.analysis.completed.length > 0 || s.analysis.primary !== "gtm_health" || s.stage === "analyzed")
2594
+ );
2595
+ if (withAnalysis?.analysis) {
2596
+ ctx.analysis = {
2597
+ ...defaultSessionAnalysis(withAnalysis.analysis.primary),
2598
+ ...withAnalysis.analysis,
2599
+ completed: [...withAnalysis.analysis.completed]
2600
+ };
2601
+ if (withAnalysis.stage) ctx.stage = withAnalysis.stage;
2602
+ if (withAnalysis.dataset) ctx.dataset = withAnalysis.dataset;
2603
+ return;
2604
+ }
2605
+ const { loadLatestDiagnosis: loadLatestDiagnosis2, loadLatestMetricsAnalysis: loadLatestMetricsAnalysis2 } = await Promise.resolve().then(() => (init_queries(), queries_exports));
2606
+ const [diagnosis, metrics] = await Promise.all([
2607
+ loadLatestDiagnosis2(),
2608
+ loadLatestMetricsAnalysis2()
2609
+ ]);
2610
+ const completed = [];
2611
+ if (diagnosis) completed.push("gtm_health");
2612
+ if (metrics?.metrics.length) completed.push("revenue_metrics");
2613
+ if (completed.length === 0) return;
2614
+ let primary = ctx.analysis.primary;
2615
+ if (completed.includes("revenue_metrics") && !completed.includes("gtm_health")) {
2616
+ primary = "revenue_metrics";
2617
+ } else if (completed.includes("gtm_health") && !completed.includes("revenue_metrics")) {
2618
+ primary = "gtm_health";
2619
+ }
2620
+ ctx.analysis = { ...ctx.analysis, primary, completed };
2621
+ if (ctx.stage === "new") ctx.stage = "analyzed";
2622
+ }
2623
+ async function initHeadlessAgentContext() {
2624
+ const ctx = initContext(true, { mode: "headless", output: "json" });
2625
+ await hydrateAnalysisFromPersistedState(ctx);
2626
+ return ctx;
2627
+ }
2628
+ function recordMessage(ctx, role, content) {
2629
+ const msg = { role, content, at: (/* @__PURE__ */ new Date()).toISOString() };
2630
+ ctx.messages.push(msg);
2631
+ if (ctx.oneShot) return;
2632
+ try {
2633
+ writeFileSync5(ctx.sessionFile, JSON.stringify(buildSessionFile(ctx), null, 2) + "\n");
2634
+ } catch {
2635
+ }
2636
+ }
2637
+ function saveSessionState(ctx) {
2638
+ if (ctx.oneShot) return;
2639
+ try {
2640
+ writeFileSync5(ctx.sessionFile, JSON.stringify(buildSessionFile(ctx), null, 2) + "\n");
2641
+ } catch {
2642
+ }
2643
+ }
2644
+ function getLastActivityRelative() {
2645
+ const dir = getSessionsDir();
2646
+ let mostRecent = 0;
2647
+ try {
2648
+ for (const name of readdirSync(dir)) {
2649
+ if (!name.endsWith(".json")) continue;
2650
+ const m = statSync(join6(dir, name)).mtimeMs;
2651
+ if (m > mostRecent) mostRecent = m;
1803
2652
  }
1804
- } else if (segment.entity_type === "people") {
1805
- const filtered = filterEntities(snapshot.people, segment.filters);
1806
- peopleIds.push(...filtered.map((p) => p.id));
1807
- const orgIdSet = /* @__PURE__ */ new Set();
1808
- for (const p of filtered) {
1809
- if (p.organization_id) orgIdSet.add(p.organization_id);
2653
+ } catch {
2654
+ return null;
2655
+ }
2656
+ if (mostRecent === 0) return null;
2657
+ return formatRelativeTime(new Date(mostRecent));
2658
+ }
2659
+ function formatRelativeTime(then) {
2660
+ const diffMs = Date.now() - then.getTime();
2661
+ if (diffMs < 0) return "just now";
2662
+ const s = Math.floor(diffMs / 1e3);
2663
+ if (s < 60) return "just now";
2664
+ const m = Math.floor(s / 60);
2665
+ if (m < 60) return `${m}m ago`;
2666
+ const h = Math.floor(m / 60);
2667
+ if (h < 24) return `${h}h ago`;
2668
+ const d = Math.floor(h / 24);
2669
+ if (d < 7) return `${d}d ago`;
2670
+ return then.toISOString().slice(0, 10);
2671
+ }
2672
+ function loadSessionFile(id) {
2673
+ const filePath = sessionPathForId(id);
2674
+ if (!filePath) return null;
2675
+ try {
2676
+ const raw = readFileSync5(filePath, "utf-8");
2677
+ const session = JSON.parse(raw);
2678
+ if (session.thread?.length) {
2679
+ session.thread = normalizeThread(session.thread);
1810
2680
  }
1811
- orgIds.push(...orgIdSet);
1812
- for (const o of snapshot.opportunities) {
1813
- if (o.organization_id && orgIdSet.has(o.organization_id)) {
1814
- oppIds.push(o.id);
2681
+ return session;
2682
+ } catch {
2683
+ return null;
2684
+ }
2685
+ }
2686
+ function listSessions(opts) {
2687
+ const dir = getSessionsDir();
2688
+ const entries = [];
2689
+ try {
2690
+ const files = readdirSync(dir).filter((name) => name.endsWith(".json")).map((name) => {
2691
+ const filePath = join6(dir, name);
2692
+ return { name, filePath, mtime: statSync(filePath).mtimeMs };
2693
+ }).sort((a, b) => b.mtime - a.mtime);
2694
+ const filesToRead = opts?.limit ? files.slice(0, opts.limit) : files;
2695
+ for (const { name, filePath, mtime } of filesToRead) {
2696
+ const id = basename(name, ".json");
2697
+ if (!isValidSessionId(id)) continue;
2698
+ try {
2699
+ const raw = readFileSync5(filePath, "utf-8");
2700
+ const session = JSON.parse(raw);
2701
+ entries.push({
2702
+ id,
2703
+ created_at: session.created_at,
2704
+ ended_at: session.ended_at,
2705
+ exchange_count: session.exchange_count ?? Math.floor(session.messages.length / 2),
2706
+ summary: session.summary,
2707
+ name: session.name,
2708
+ stage: session.stage,
2709
+ dataset: session.dataset,
2710
+ deliverables: session.deliverables,
2711
+ analysis: session.analysis,
2712
+ mtime
2713
+ });
2714
+ } catch {
1815
2715
  }
1816
2716
  }
2717
+ } catch {
2718
+ return [];
1817
2719
  }
1818
- return { orgIds, peopleIds, oppIds };
2720
+ entries.sort((a, b) => b.mtime - a.mtime);
2721
+ if (opts?.limit) return entries.slice(0, opts.limit);
2722
+ return entries;
1819
2723
  }
1820
- function filterEntities(entities, filters) {
1821
- return entities.filter(
1822
- (entity) => filters.every((f) => matchFilter(entity, f))
2724
+ function getRecentSessions(n) {
2725
+ return listSessions({ limit: n });
2726
+ }
2727
+ function getUnfinishedSessions(excludeId) {
2728
+ return listSessions().filter(
2729
+ (s) => s.id !== excludeId && s.stage === "analyzed" && (s.deliverables?.length ?? 0) === 0
1823
2730
  );
1824
2731
  }
1825
- function getNestedValue(obj, path) {
1826
- const parts = path.split(".");
1827
- let current = obj;
1828
- for (const part of parts) {
1829
- if (current == null || typeof current !== "object") return void 0;
1830
- current = current[part];
2732
+ function isSessionInProgress(s) {
2733
+ if (s.stage === "ended") return false;
2734
+ if ((s.deliverables?.length ?? 0) > 0 || s.stage === "delivered") return false;
2735
+ if (s.stage === "analyzed") return true;
2736
+ return (s.exchange_count ?? 0) > 0 || !!s.dataset?.label;
2737
+ }
2738
+ function getActiveSessions() {
2739
+ return listSessions().filter(isSessionInProgress);
2740
+ }
2741
+ async function closeAllActiveSessions(ctx) {
2742
+ const active = getActiveSessions();
2743
+ const closed = [];
2744
+ const skipped = [];
2745
+ const endedAt = (/* @__PURE__ */ new Date()).toISOString();
2746
+ for (const s of active) {
2747
+ if (s.id === ctx.sessionId) continue;
2748
+ const file = loadSessionFile(s.id);
2749
+ if (!file) {
2750
+ skipped.push(s.id);
2751
+ continue;
2752
+ }
2753
+ file.stage = "ended";
2754
+ file.ended_at = endedAt;
2755
+ const filePath = sessionPathForId(s.id);
2756
+ if (!filePath) {
2757
+ skipped.push(s.id);
2758
+ continue;
2759
+ }
2760
+ writeFileSync5(filePath, JSON.stringify(file, null, 2) + "\n");
2761
+ closed.push(s.id);
1831
2762
  }
1832
- return current;
2763
+ const currentActive = active.some((s) => s.id === ctx.sessionId);
2764
+ if (currentActive) {
2765
+ const alreadyClosed = ctx.stage === "delivered" || ctx.stage === "ended";
2766
+ const hasWork = ctx.stage === "analyzed" || !!ctx.dataset || ctx.messages.length > 0 || ctx.deliverables.length > 0;
2767
+ if (!alreadyClosed && hasWork) {
2768
+ await endSession(ctx);
2769
+ if (!closed.includes(ctx.sessionId)) closed.push(ctx.sessionId);
2770
+ } else if (!alreadyClosed && (ctx.messages.length > 0 || !!ctx.dataset?.label)) {
2771
+ await endSession(ctx);
2772
+ if (!closed.includes(ctx.sessionId)) closed.push(ctx.sessionId);
2773
+ }
2774
+ }
2775
+ await rotateToFreshSession(ctx);
2776
+ const { initSchema: initSchema2 } = await Promise.resolve().then(() => (init_schema(), schema_exports));
2777
+ await initSchema2();
2778
+ return { closed, skipped };
1833
2779
  }
1834
- function matchFilter(entity, filter) {
1835
- const value = getNestedValue(entity, filter.field);
1836
- switch (filter.operator) {
1837
- case "equals":
1838
- return String(value) === String(filter.value);
1839
- case "not_equals":
1840
- return String(value) !== String(filter.value);
1841
- case "greater_than":
1842
- return Number(value) > Number(filter.value);
1843
- case "less_than":
1844
- return Number(value) < Number(filter.value);
1845
- case "contains":
1846
- return String(value).toLowerCase().includes(String(filter.value).toLowerCase());
1847
- case "in":
1848
- return Array.isArray(filter.value) && filter.value.includes(String(value));
1849
- default:
1850
- return false;
2780
+ function getLastWorkedSession() {
2781
+ for (const s of listSessions()) {
2782
+ if ((s.exchange_count ?? 0) > 0 || s.stage === "analyzed" || s.stage === "delivered" || !!s.dataset?.label) {
2783
+ return s;
2784
+ }
1851
2785
  }
2786
+ return null;
1852
2787
  }
1853
- async function autoGenerateSegments() {
1854
- await all("DELETE FROM segments WHERE is_auto_generated = true");
1855
- let count = 0;
1856
- const industries = await all(
1857
- `SELECT json_extract_string(metadata, '$.industry') as industry, COUNT(*) as cnt
1858
- FROM organizations
1859
- WHERE json_extract_string(metadata, '$.industry') IS NOT NULL
1860
- GROUP BY industry
1861
- HAVING cnt >= 5
1862
- ORDER BY cnt DESC`
1863
- );
1864
- for (const row of industries) {
1865
- await insertSegment({
1866
- name: `Industry: ${row.industry}`,
1867
- entity_type: "organizations",
1868
- filters: [{ field: "metadata.industry", operator: "equals", value: row.industry }],
1869
- is_auto_generated: true
1870
- });
1871
- count++;
2788
+ async function closeSession(ctx) {
2789
+ return finalizeSession(ctx, ctx.stage);
2790
+ }
2791
+ async function endSession(ctx) {
2792
+ return finalizeSession(ctx, "ended");
2793
+ }
2794
+ async function rotateToFreshSession(ctx) {
2795
+ const { setActiveDbPath: setActiveDbPath2 } = await Promise.resolve().then(() => (init_connection(), connection_exports));
2796
+ const newId = makeSessionId();
2797
+ resetContextForSwitch(ctx, {
2798
+ sessionId: newId,
2799
+ sessionFile: join6(getSessionsDir(), `${newId}.json`),
2800
+ messages: [],
2801
+ stage: "new",
2802
+ analysis: defaultSessionAnalysis(),
2803
+ llm: void 0
2804
+ });
2805
+ ctx.datasetPath = datasetPathForSession(newId);
2806
+ await setActiveDbPath2(ctx.datasetPath);
2807
+ }
2808
+ async function finalizeSession(ctx, stage) {
2809
+ if (ctx.oneShot) return void 0;
2810
+ const exchangeCount = Math.floor(ctx.messages.length / 2);
2811
+ const endedAt = (/* @__PURE__ */ new Date()).toISOString();
2812
+ if (ctx.messages.length === 0 && ctx.stage === "new" && !ctx.dataset && ctx.deliverables.length === 0) {
2813
+ if (ctx.datasetPath) {
2814
+ try {
2815
+ const { close: close2 } = await Promise.resolve().then(() => (init_connection(), connection_exports));
2816
+ await close2();
2817
+ } catch {
2818
+ }
2819
+ for (const path of [ctx.datasetPath, `${ctx.datasetPath}.wal`]) {
2820
+ try {
2821
+ rmSync2(path, { force: true });
2822
+ } catch {
2823
+ }
2824
+ }
2825
+ }
2826
+ return void 0;
1872
2827
  }
1873
- const sizes = await all(
1874
- `SELECT json_extract_string(metadata, '$.size') as size, COUNT(*) as cnt
1875
- FROM organizations
1876
- WHERE json_extract_string(metadata, '$.size') IS NOT NULL
1877
- GROUP BY size
1878
- HAVING cnt >= 5
1879
- ORDER BY cnt DESC`
1880
- );
1881
- for (const row of sizes) {
1882
- await insertSegment({
1883
- name: `Size: ${row.size}`,
1884
- entity_type: "organizations",
1885
- filters: [{ field: "metadata.size", operator: "equals", value: row.size }],
1886
- is_auto_generated: true
1887
- });
1888
- count++;
2828
+ if (ctx.deliverables.length > 0) {
2829
+ const { creditSessionDeliverableWrapup: creditSessionDeliverableWrapup2 } = await Promise.resolve().then(() => (init_time_bank(), time_bank_exports));
2830
+ creditSessionDeliverableWrapup2(ctx);
1889
2831
  }
1890
- const oppCount = await all("SELECT COUNT(*) as cnt FROM opportunities");
1891
- if ((oppCount[0]?.cnt ?? 0) >= 10) {
1892
- await insertSegment({
1893
- name: "Enterprise Deals (>$100K)",
1894
- entity_type: "opportunities",
1895
- filters: [{ field: "amount", operator: "greater_than", value: 1e5 }],
1896
- is_auto_generated: true
1897
- });
1898
- count++;
1899
- await insertSegment({
1900
- name: "SMB Deals (\u2264$100K)",
1901
- entity_type: "opportunities",
1902
- filters: [{ field: "amount", operator: "less_than", value: 100001 }],
1903
- is_auto_generated: true
1904
- });
1905
- count++;
2832
+ const { recordSessionClosed: recordSessionClosed2 } = await Promise.resolve().then(() => (init_usage_stats(), usage_stats_exports));
2833
+ recordSessionClosed2();
2834
+ const file = {
2835
+ id: ctx.sessionId,
2836
+ created_at: ctx.messages[0]?.at ?? endedAt,
2837
+ messages: ctx.messages,
2838
+ ended_at: endedAt,
2839
+ exchange_count: exchangeCount,
2840
+ stage
2841
+ };
2842
+ if (ctx.resumedFromId) {
2843
+ file.resumed_from = ctx.resumedFromId;
1906
2844
  }
1907
- const owners = await all(
1908
- `SELECT o.owner_id, p.canonical_name as owner_name, COUNT(*) as cnt
1909
- FROM opportunities o
1910
- JOIN people p ON o.owner_id = p.id
1911
- WHERE o.owner_id IS NOT NULL
1912
- GROUP BY o.owner_id, p.canonical_name
1913
- HAVING cnt >= 3
1914
- ORDER BY cnt DESC`
1915
- );
1916
- for (const row of owners) {
1917
- await insertSegment({
1918
- name: `Rep: ${row.owner_name}`,
1919
- entity_type: "opportunities",
1920
- filters: [{ field: "owner_id", operator: "equals", value: row.owner_id }],
1921
- is_auto_generated: true
1922
- });
1923
- count++;
2845
+ if (ctx.sessionName) {
2846
+ file.name = ctx.sessionName;
1924
2847
  }
1925
- return count;
1926
- }
1927
- var init_segments = __esm({
1928
- "src/pipeline/segments.ts"() {
1929
- "use strict";
1930
- init_connection();
1931
- init_queries();
2848
+ if (ctx.dataset) {
2849
+ file.dataset = ctx.dataset;
2850
+ }
2851
+ if (ctx.deliverables.length > 0) {
2852
+ file.deliverables = ctx.deliverables;
2853
+ }
2854
+ if (ctx.conversation.length > 0) {
2855
+ file.thread = ctx.conversation;
2856
+ }
2857
+ if (ctx.analysis) {
2858
+ file.analysis = ctx.analysis;
2859
+ }
2860
+ if (ctx.scope) {
2861
+ file.scope = ctx.scope;
2862
+ }
2863
+ if (ctx.attachments && ctx.attachments.length > 0) {
2864
+ file.attachments = ctx.attachments;
2865
+ }
2866
+ if (ctx.llm && Object.keys(ctx.llm).length > 0) {
2867
+ file.llm = ctx.llm;
2868
+ }
2869
+ try {
2870
+ writeFileSync5(ctx.sessionFile, JSON.stringify(file, null, 2) + "\n");
2871
+ } catch {
1932
2872
  }
1933
- });
1934
-
1935
- // src/pipeline/divergence.ts
1936
- function detectDivergences(aggregate, segments) {
1937
- const divergences = [];
1938
- const SCORE_THRESHOLD = 15;
1939
- const aggMap = new Map(aggregate.vital_signs.map((v) => [v.vital_sign, v]));
1940
- for (const seg of segments) {
1941
- for (const vs of seg.result.vital_signs) {
1942
- const agg = aggMap.get(vs.vital_sign);
1943
- if (!agg) continue;
1944
- const delta = vs.score - agg.score;
1945
- const statusDiffers = vs.status !== agg.status;
1946
- const scoreDiverges = Math.abs(delta) >= SCORE_THRESHOLD;
1947
- if (statusDiffers || scoreDiverges) {
1948
- divergences.push({
1949
- segmentId: seg.segmentId,
1950
- segmentName: seg.segmentName,
1951
- vitalSign: vs.vital_sign,
1952
- segmentScore: vs.score,
1953
- aggregateScore: agg.score,
1954
- delta,
1955
- segmentStatus: vs.status,
1956
- aggregateStatus: agg.status
1957
- });
1958
- }
2873
+ return void 0;
2874
+ }
2875
+ function resolveSessionByToken(idArg, options) {
2876
+ const printErrors = options?.printErrors !== false;
2877
+ const all2 = listSessions();
2878
+ const lower = idArg.toLowerCase();
2879
+ let matches = all2.filter((s) => s.id === idArg);
2880
+ if (matches.length === 0) matches = all2.filter((s) => s.name?.toLowerCase() === lower);
2881
+ if (matches.length === 0 && idArg.length >= 4) {
2882
+ matches = all2.filter((s) => s.id.endsWith(idArg));
2883
+ }
2884
+ if (matches.length === 0) return void 0;
2885
+ if (matches.length > 1) {
2886
+ if (printErrors) {
2887
+ console.log(
2888
+ ` Ambiguous "${idArg}" \u2014 matches ${matches.length} sessions. Use a longer id.`
2889
+ );
1959
2890
  }
2891
+ return null;
1960
2892
  }
1961
- divergences.sort((a, b) => Math.abs(b.delta) - Math.abs(a.delta));
1962
- return { divergences };
2893
+ return matches[0];
1963
2894
  }
1964
- var init_divergence = __esm({
1965
- "src/pipeline/divergence.ts"() {
1966
- "use strict";
2895
+ function findSessionByName(name) {
2896
+ const lower = name.toLowerCase();
2897
+ const all2 = listSessions();
2898
+ return all2.find((s) => s.name?.toLowerCase() === lower) ?? null;
2899
+ }
2900
+ function buildSwitchContext(session) {
2901
+ const parts = [];
2902
+ if (session.summary) parts.push(session.summary);
2903
+ const userMsgs = session.messages.filter((m) => m.role === "user").slice(-3);
2904
+ for (const m of userMsgs) {
2905
+ parts.push(m.content.slice(0, 300));
1967
2906
  }
1968
- });
1969
-
1970
- // src/baselines/defaults.ts
1971
- var DEFAULT_THRESHOLDS;
1972
- var init_defaults = __esm({
1973
- "src/baselines/defaults.ts"() {
2907
+ return parts.join("\n");
2908
+ }
2909
+ function resetContextForSwitch(ctx, opts) {
2910
+ ctx.sessionId = opts.sessionId;
2911
+ ctx.sessionFile = opts.sessionFile;
2912
+ ctx.sessionName = opts.sessionName;
2913
+ ctx.messages = opts.messages;
2914
+ ctx.conversation = opts.conversation ?? [];
2915
+ ctx.resumedFromId = opts.resumedFromId;
2916
+ ctx.resumedSessionSummary = opts.resumedSessionSummary;
2917
+ ctx.stage = opts.stage ?? "new";
2918
+ ctx.dataset = opts.dataset;
2919
+ ctx.deliverables = opts.deliverables ?? [];
2920
+ ctx.analysis = opts.analysis ?? defaultSessionAnalysis();
2921
+ ctx.scope = opts.scope;
2922
+ ctx.attachments = opts.attachments ?? [];
2923
+ ctx.llm = opts.llm;
2924
+ ctx.gapAudit = void 0;
2925
+ ctx.deliverIntent = false;
2926
+ ctx.computeInProgress = false;
2927
+ ctx.wizardDepth = 0;
2928
+ ctx.snapshot = { computeResult: null, divergences: [] };
2929
+ }
2930
+ var SESSION_ID_RE;
2931
+ var init_context2 = __esm({
2932
+ "src/cli/context.ts"() {
1974
2933
  "use strict";
1975
- DEFAULT_THRESHOLDS = {
1976
- freshness: {
1977
- people_window_days: 90,
1978
- org_window_days: 90,
1979
- opp_window_days: 30,
1980
- red_below: 60,
1981
- green_above: 80,
1982
- weights: { people: 0.35, organizations: 0.3, opportunities: 0.35 }
1983
- },
1984
- flow_rate: {
1985
- green_days: 45,
1986
- yellow_days: 90,
1987
- stuck_days: 60,
1988
- max_days: 120
1989
- },
1990
- drop_rate: {
1991
- marketing_systems: ["hubspot"],
1992
- sales_systems: ["salesforce"],
1993
- recency_days: 30,
1994
- red_below: 60,
1995
- green_above: 80,
1996
- weights: { cross_system: 0.6, abandoned: 0.4 }
1997
- },
1998
- signal_to_noise: {
1999
- lookback_days: 90,
2000
- red_below: 40,
2001
- green_above: 65
2002
- },
2003
- thread_depth: {
2004
- activity_window_days: 90,
2005
- multi_thread_threshold: 2,
2006
- red_below: 40,
2007
- green_above: 65
2008
- }
2009
- };
2934
+ init_thread_compat();
2935
+ init_context();
2936
+ SESSION_ID_RE = /^\d{4}-\d{2}-\d{2}-[a-f0-9]{4}$/i;
2010
2937
  }
2011
2938
  });
2012
2939
 
2013
- // src/baselines/profile-presets.ts
2014
- var PROFILE_PRESETS;
2015
- var init_profile_presets = __esm({
2016
- "src/baselines/profile-presets.ts"() {
2017
- "use strict";
2018
- PROFILE_PRESETS = {
2019
- plg: {
2020
- freshness: {
2021
- people_window_days: 60,
2022
- org_window_days: 60,
2023
- opp_window_days: 21
2024
- },
2025
- flow_rate: {
2026
- green_days: 21,
2027
- yellow_days: 45,
2028
- stuck_days: 30,
2029
- max_days: 60
2030
- },
2031
- signal_to_noise: {
2032
- lookback_days: 60,
2033
- red_below: 25,
2034
- green_above: 50
2035
- },
2036
- thread_depth: {
2037
- activity_window_days: 60,
2038
- red_below: 30,
2039
- green_above: 55
2040
- }
2041
- },
2042
- smb_velocity: {
2043
- freshness: {
2044
- people_window_days: 60,
2045
- org_window_days: 60
2046
- },
2047
- flow_rate: {
2048
- green_days: 30,
2049
- yellow_days: 60,
2050
- stuck_days: 45,
2051
- max_days: 90
2052
- },
2053
- signal_to_noise: {
2054
- lookback_days: 60
2055
- },
2056
- thread_depth: {
2057
- activity_window_days: 60
2058
- }
2059
- },
2060
- mid_market: {
2061
- flow_rate: {
2062
- green_days: 60,
2063
- yellow_days: 120,
2064
- stuck_days: 75,
2065
- max_days: 150
2066
- }
2067
- },
2068
- enterprise: {
2069
- freshness: {
2070
- people_window_days: 120,
2071
- org_window_days: 120,
2072
- opp_window_days: 45
2073
- },
2074
- flow_rate: {
2075
- green_days: 90,
2076
- yellow_days: 180,
2077
- stuck_days: 90,
2078
- max_days: 240
2079
- },
2080
- signal_to_noise: {
2081
- lookback_days: 120
2082
- },
2083
- thread_depth: {
2084
- activity_window_days: 120,
2085
- multi_thread_threshold: 3,
2086
- red_below: 50,
2087
- green_above: 75
2088
- }
2940
+ // src/pipeline/segments.ts
2941
+ function resolveSegmentScopeFromSnapshot(segment, snapshot) {
2942
+ const orgIds = [];
2943
+ const peopleIds = [];
2944
+ const oppIds = [];
2945
+ if (segment.entity_type === "organizations") {
2946
+ const filtered = filterEntities(snapshot.organizations, segment.filters);
2947
+ const orgIdSet = new Set(filtered.map((o) => o.id));
2948
+ orgIds.push(...orgIdSet);
2949
+ for (const p of snapshot.people) {
2950
+ if (p.organization_id && orgIdSet.has(p.organization_id)) {
2951
+ peopleIds.push(p.id);
2089
2952
  }
2090
- };
2953
+ }
2954
+ for (const o of snapshot.opportunities) {
2955
+ if (o.organization_id && orgIdSet.has(o.organization_id)) {
2956
+ oppIds.push(o.id);
2957
+ }
2958
+ }
2959
+ } else if (segment.entity_type === "opportunities") {
2960
+ const filtered = filterEntities(snapshot.opportunities, segment.filters);
2961
+ oppIds.push(...filtered.map((o) => o.id));
2962
+ const orgIdSet = /* @__PURE__ */ new Set();
2963
+ for (const o of filtered) {
2964
+ if (o.organization_id) orgIdSet.add(o.organization_id);
2965
+ }
2966
+ orgIds.push(...orgIdSet);
2967
+ for (const p of snapshot.people) {
2968
+ if (p.organization_id && orgIdSet.has(p.organization_id)) {
2969
+ peopleIds.push(p.id);
2970
+ }
2971
+ }
2972
+ } else if (segment.entity_type === "people") {
2973
+ const filtered = filterEntities(snapshot.people, segment.filters);
2974
+ peopleIds.push(...filtered.map((p) => p.id));
2975
+ const orgIdSet = /* @__PURE__ */ new Set();
2976
+ for (const p of filtered) {
2977
+ if (p.organization_id) orgIdSet.add(p.organization_id);
2978
+ }
2979
+ orgIds.push(...orgIdSet);
2980
+ for (const o of snapshot.opportunities) {
2981
+ if (o.organization_id && orgIdSet.has(o.organization_id)) {
2982
+ oppIds.push(o.id);
2983
+ }
2984
+ }
2985
+ }
2986
+ return { orgIds, peopleIds, oppIds };
2987
+ }
2988
+ function filterEntities(entities, filters) {
2989
+ return entities.filter(
2990
+ (entity) => filters.every((f) => matchFilter(entity, f))
2991
+ );
2992
+ }
2993
+ function getNestedValue(obj, path) {
2994
+ const parts = path.split(".");
2995
+ let current = obj;
2996
+ for (const part of parts) {
2997
+ if (current == null || typeof current !== "object") return void 0;
2998
+ current = current[part];
2091
2999
  }
2092
- });
2093
-
2094
- // src/config/store.ts
2095
- var store_exports = {};
2096
- __export(store_exports, {
2097
- deleteConfigValue: () => deleteConfigValue,
2098
- getConfigValue: () => getConfigValue,
2099
- getExportsDir: () => getExportsDir,
2100
- getKnowledgeDir: () => getKnowledgeDir,
2101
- getMemoryDir: () => getMemoryDir,
2102
- getStrategiesDir: () => getStrategiesDir,
2103
- getWinsDir: () => getWinsDir,
2104
- loadConfig: () => loadConfig,
2105
- ntrpHome: () => ntrpHome,
2106
- resetConfigCache: () => resetConfigCache,
2107
- saveConfig: () => saveConfig,
2108
- setConfigValue: () => setConfigValue
2109
- });
2110
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync3 } from "fs";
2111
- import { homedir as homedir2 } from "os";
2112
- import { join as join3, resolve as resolve3 } from "path";
2113
- function ntrpHome() {
2114
- return NTRP_DIR2;
3000
+ return current;
2115
3001
  }
2116
- function ensureDir2() {
2117
- if (!existsSync3(NTRP_DIR2)) {
2118
- mkdirSync3(NTRP_DIR2, { recursive: true });
3002
+ function matchFilter(entity, filter) {
3003
+ const value = getNestedValue(entity, filter.field);
3004
+ switch (filter.operator) {
3005
+ case "equals":
3006
+ return String(value) === String(filter.value);
3007
+ case "not_equals":
3008
+ return String(value) !== String(filter.value);
3009
+ case "greater_than":
3010
+ return Number(value) > Number(filter.value);
3011
+ case "less_than":
3012
+ return Number(value) < Number(filter.value);
3013
+ case "contains":
3014
+ return String(value).toLowerCase().includes(String(filter.value).toLowerCase());
3015
+ case "in":
3016
+ return Array.isArray(filter.value) && filter.value.includes(String(value));
3017
+ default:
3018
+ return false;
2119
3019
  }
2120
3020
  }
2121
- function loadConfig() {
2122
- if (cachedConfig) return cachedConfig;
2123
- ensureDir2();
2124
- if (!existsSync3(CONFIG_PATH)) {
2125
- cachedConfig = {};
2126
- return cachedConfig;
3021
+ async function autoGenerateSegments() {
3022
+ await all("DELETE FROM segments WHERE is_auto_generated = true");
3023
+ let count = 0;
3024
+ const industries = await all(
3025
+ `SELECT json_extract_string(metadata, '$.industry') as industry, COUNT(*) as cnt
3026
+ FROM organizations
3027
+ WHERE json_extract_string(metadata, '$.industry') IS NOT NULL
3028
+ GROUP BY industry
3029
+ HAVING cnt >= 5
3030
+ ORDER BY cnt DESC`
3031
+ );
3032
+ for (const row of industries) {
3033
+ await insertSegment({
3034
+ name: `Industry: ${row.industry}`,
3035
+ entity_type: "organizations",
3036
+ filters: [{ field: "metadata.industry", operator: "equals", value: row.industry }],
3037
+ is_auto_generated: true
3038
+ });
3039
+ count++;
2127
3040
  }
2128
- try {
2129
- cachedConfig = JSON.parse(readFileSync2(CONFIG_PATH, "utf-8"));
2130
- } catch {
2131
- cachedConfig = {};
3041
+ const sizes = await all(
3042
+ `SELECT json_extract_string(metadata, '$.size') as size, COUNT(*) as cnt
3043
+ FROM organizations
3044
+ WHERE json_extract_string(metadata, '$.size') IS NOT NULL
3045
+ GROUP BY size
3046
+ HAVING cnt >= 5
3047
+ ORDER BY cnt DESC`
3048
+ );
3049
+ for (const row of sizes) {
3050
+ await insertSegment({
3051
+ name: `Size: ${row.size}`,
3052
+ entity_type: "organizations",
3053
+ filters: [{ field: "metadata.size", operator: "equals", value: row.size }],
3054
+ is_auto_generated: true
3055
+ });
3056
+ count++;
2132
3057
  }
2133
- return cachedConfig;
2134
- }
2135
- function saveConfig(config) {
2136
- ensureDir2();
2137
- writeFileSync2(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n");
2138
- cachedConfig = config;
2139
- }
2140
- function resetConfigCache() {
2141
- cachedConfig = null;
2142
- }
2143
- function getConfigValue(key) {
2144
- if (key === "api-key") return loadConfig()["api-key"];
2145
- if (key === "license-key") return process.env.NTRP_LICENSE_KEY ?? loadConfig()["license-key"];
2146
- const config = loadConfig();
2147
- return config[key];
2148
- }
2149
- function setConfigValue(key, value) {
2150
- const config = loadConfig();
2151
- config[key] = value;
2152
- saveConfig(config);
2153
- }
2154
- function deleteConfigValue(key) {
2155
- const config = loadConfig();
2156
- delete config[key];
2157
- saveConfig(config);
2158
- }
2159
- function getExportsDir() {
2160
- const config = loadConfig();
2161
- const dir = resolve3(config["export-dir"] ?? join3(NTRP_DIR2, "exports"));
2162
- if (!existsSync3(dir)) {
2163
- mkdirSync3(dir, { recursive: true });
3058
+ const oppCount = await all("SELECT COUNT(*) as cnt FROM opportunities");
3059
+ if ((oppCount[0]?.cnt ?? 0) >= 10) {
3060
+ await insertSegment({
3061
+ name: "Enterprise Deals (>$100K)",
3062
+ entity_type: "opportunities",
3063
+ filters: [{ field: "amount", operator: "greater_than", value: 1e5 }],
3064
+ is_auto_generated: true
3065
+ });
3066
+ count++;
3067
+ await insertSegment({
3068
+ name: "SMB Deals (\u2264$100K)",
3069
+ entity_type: "opportunities",
3070
+ filters: [{ field: "amount", operator: "less_than", value: 100001 }],
3071
+ is_auto_generated: true
3072
+ });
3073
+ count++;
2164
3074
  }
2165
- return dir;
2166
- }
2167
- function getStrategiesDir() {
2168
- const dir = join3(NTRP_DIR2, "strategies");
2169
- if (!existsSync3(dir)) {
2170
- mkdirSync3(dir, { recursive: true });
2171
- writeFileSync2(join3(dir, "README.md"), `# Strategies
2172
-
2173
- This directory holds your GTM strategy files. Each file describes a strategy you're executing.
2174
-
2175
- ## How to use
2176
-
2177
- 1. Create a markdown file for each active strategy (e.g., \`multi-thread-q2.md\`)
2178
- 2. Describe the goal, target segment, and success criteria
2179
- 3. Reference playbook plays that support this strategy
2180
- 4. After diagnosis, check if vital signs improved in the targeted area
2181
-
2182
- ## Example
2183
-
2184
- \`\`\`markdown
2185
- # Multi-Thread Enterprise Deals \u2014 Q2
2186
-
2187
- **Goal:** Reduce single-threaded deals from 65% to under 30%
2188
- **Segment:** Enterprise accounts > $100K
2189
- **Play:** Multi-Thread Your Deals
2190
- **Success metric:** Thread depth score > 70
2191
- \`\`\`
2192
- `);
3075
+ const owners = await all(
3076
+ `SELECT o.owner_id, p.canonical_name as owner_name, COUNT(*) as cnt
3077
+ FROM opportunities o
3078
+ JOIN people p ON o.owner_id = p.id
3079
+ WHERE o.owner_id IS NOT NULL
3080
+ GROUP BY o.owner_id, p.canonical_name
3081
+ HAVING cnt >= 3
3082
+ ORDER BY cnt DESC`
3083
+ );
3084
+ for (const row of owners) {
3085
+ await insertSegment({
3086
+ name: `Rep: ${row.owner_name}`,
3087
+ entity_type: "opportunities",
3088
+ filters: [{ field: "owner_id", operator: "equals", value: row.owner_id }],
3089
+ is_auto_generated: true
3090
+ });
3091
+ count++;
2193
3092
  }
2194
- return dir;
3093
+ return count;
2195
3094
  }
2196
- function getMemoryDir() {
2197
- const dir = join3(NTRP_DIR2, "memory");
2198
- if (!existsSync3(dir)) {
2199
- mkdirSync3(dir, { recursive: true });
3095
+ var init_segments = __esm({
3096
+ "src/pipeline/segments.ts"() {
3097
+ "use strict";
3098
+ init_connection();
3099
+ init_queries();
2200
3100
  }
2201
- return dir;
2202
- }
2203
- function getKnowledgeDir() {
2204
- const dir = join3(NTRP_DIR2, "knowledge");
2205
- if (!existsSync3(dir)) {
2206
- mkdirSync3(dir, { recursive: true });
2207
- writeFileSync2(join3(dir, "README.md"), `# Knowledge Packs
2208
-
2209
- Drop case studies, GTM frameworks, benchmark reports, or playbooks here as
2210
- markdown, text, or PDF. NTRP ingests them with \`/knowledge add <file>\` and
2211
- references the most relevant passages during analysis \u2014 so the agent can learn
2212
- from work done outside this platform.
2213
-
2214
- ## How to use
3101
+ });
2215
3102
 
2216
- 1. Add a file: \`/knowledge add ~/Downloads/plg-benchmarks-2026.pdf\`
2217
- 2. List what's indexed: \`/knowledge list\`
2218
- 3. Ask a question \u2014 relevant passages are pulled in automatically.
2219
- `);
3103
+ // src/pipeline/divergence.ts
3104
+ function detectDivergences(aggregate, segments) {
3105
+ const divergences = [];
3106
+ const SCORE_THRESHOLD = 15;
3107
+ const aggMap = new Map(aggregate.vital_signs.map((v) => [v.vital_sign, v]));
3108
+ for (const seg of segments) {
3109
+ for (const vs of seg.result.vital_signs) {
3110
+ const agg = aggMap.get(vs.vital_sign);
3111
+ if (!agg) continue;
3112
+ const delta = vs.score - agg.score;
3113
+ const statusDiffers = vs.status !== agg.status;
3114
+ const scoreDiverges = Math.abs(delta) >= SCORE_THRESHOLD;
3115
+ if (statusDiffers || scoreDiverges) {
3116
+ divergences.push({
3117
+ segmentId: seg.segmentId,
3118
+ segmentName: seg.segmentName,
3119
+ vitalSign: vs.vital_sign,
3120
+ segmentScore: vs.score,
3121
+ aggregateScore: agg.score,
3122
+ delta,
3123
+ segmentStatus: vs.status,
3124
+ aggregateStatus: agg.status
3125
+ });
3126
+ }
3127
+ }
2220
3128
  }
2221
- return dir;
3129
+ divergences.sort((a, b) => Math.abs(b.delta) - Math.abs(a.delta));
3130
+ return { divergences };
2222
3131
  }
2223
- function getWinsDir() {
2224
- const dir = join3(NTRP_DIR2, "wins");
2225
- if (!existsSync3(dir)) {
2226
- mkdirSync3(dir, { recursive: true });
2227
- writeFileSync2(join3(dir, "README.md"), `# Wins
2228
-
2229
- This directory logs outcomes when a strategy or play succeeds. Each win creates a record that future diagnoses can reference.
2230
-
2231
- ## How to use
2232
-
2233
- 1. After executing a play, log the result here (e.g., \`2026-04-clean-pipeline.md\`)
2234
- 2. Include: what you did, what changed, before/after scores
2235
- 3. Future AI findings will reference wins to track improvement over time
2236
-
2237
- ## Example
2238
-
2239
- \`\`\`markdown
2240
- # Pipeline Cleanup \u2014 April 2026
3132
+ var init_divergence = __esm({
3133
+ "src/pipeline/divergence.ts"() {
3134
+ "use strict";
3135
+ }
3136
+ });
2241
3137
 
2242
- **Play:** Clean Dead Pipeline
2243
- **Before:** Freshness 29/100, $3.1M stale pipeline
2244
- **After:** Freshness 72/100, removed 45 zombie deals
2245
- **Impact:** Forecast accuracy improved from 62% to 84%
2246
- \`\`\`
2247
- `);
3138
+ // src/baselines/defaults.ts
3139
+ var DEFAULT_THRESHOLDS;
3140
+ var init_defaults = __esm({
3141
+ "src/baselines/defaults.ts"() {
3142
+ "use strict";
3143
+ DEFAULT_THRESHOLDS = {
3144
+ freshness: {
3145
+ people_window_days: 90,
3146
+ org_window_days: 90,
3147
+ opp_window_days: 30,
3148
+ red_below: 60,
3149
+ green_above: 80,
3150
+ weights: { people: 0.35, organizations: 0.3, opportunities: 0.35 }
3151
+ },
3152
+ flow_rate: {
3153
+ green_days: 45,
3154
+ yellow_days: 90,
3155
+ stuck_days: 60,
3156
+ max_days: 120
3157
+ },
3158
+ drop_rate: {
3159
+ marketing_systems: ["hubspot"],
3160
+ sales_systems: ["salesforce"],
3161
+ recency_days: 30,
3162
+ red_below: 60,
3163
+ green_above: 80,
3164
+ weights: { cross_system: 0.6, abandoned: 0.4 }
3165
+ },
3166
+ signal_to_noise: {
3167
+ lookback_days: 90,
3168
+ red_below: 40,
3169
+ green_above: 65
3170
+ },
3171
+ thread_depth: {
3172
+ activity_window_days: 90,
3173
+ multi_thread_threshold: 2,
3174
+ red_below: 40,
3175
+ green_above: 65
3176
+ }
3177
+ };
2248
3178
  }
2249
- return dir;
2250
- }
2251
- var NTRP_DIR2, CONFIG_PATH, cachedConfig;
2252
- var init_store = __esm({
2253
- "src/config/store.ts"() {
3179
+ });
3180
+
3181
+ // src/baselines/profile-presets.ts
3182
+ var PROFILE_PRESETS;
3183
+ var init_profile_presets = __esm({
3184
+ "src/baselines/profile-presets.ts"() {
2254
3185
  "use strict";
2255
- NTRP_DIR2 = process.env.NTRP_HOME ? resolve3(process.env.NTRP_HOME) : join3(homedir2(), ".ntrp");
2256
- CONFIG_PATH = join3(NTRP_DIR2, "config.json");
2257
- cachedConfig = null;
3186
+ PROFILE_PRESETS = {
3187
+ plg: {
3188
+ freshness: {
3189
+ people_window_days: 60,
3190
+ org_window_days: 60,
3191
+ opp_window_days: 21
3192
+ },
3193
+ flow_rate: {
3194
+ green_days: 21,
3195
+ yellow_days: 45,
3196
+ stuck_days: 30,
3197
+ max_days: 60
3198
+ },
3199
+ signal_to_noise: {
3200
+ lookback_days: 60,
3201
+ red_below: 25,
3202
+ green_above: 50
3203
+ },
3204
+ thread_depth: {
3205
+ activity_window_days: 60,
3206
+ red_below: 30,
3207
+ green_above: 55
3208
+ }
3209
+ },
3210
+ smb_velocity: {
3211
+ freshness: {
3212
+ people_window_days: 60,
3213
+ org_window_days: 60
3214
+ },
3215
+ flow_rate: {
3216
+ green_days: 30,
3217
+ yellow_days: 60,
3218
+ stuck_days: 45,
3219
+ max_days: 90
3220
+ },
3221
+ signal_to_noise: {
3222
+ lookback_days: 60
3223
+ },
3224
+ thread_depth: {
3225
+ activity_window_days: 60
3226
+ }
3227
+ },
3228
+ mid_market: {
3229
+ flow_rate: {
3230
+ green_days: 60,
3231
+ yellow_days: 120,
3232
+ stuck_days: 75,
3233
+ max_days: 150
3234
+ }
3235
+ },
3236
+ enterprise: {
3237
+ freshness: {
3238
+ people_window_days: 120,
3239
+ org_window_days: 120,
3240
+ opp_window_days: 45
3241
+ },
3242
+ flow_rate: {
3243
+ green_days: 90,
3244
+ yellow_days: 180,
3245
+ stuck_days: 90,
3246
+ max_days: 240
3247
+ },
3248
+ signal_to_noise: {
3249
+ lookback_days: 120
3250
+ },
3251
+ thread_depth: {
3252
+ activity_window_days: 120,
3253
+ multi_thread_threshold: 3,
3254
+ red_below: 50,
3255
+ green_above: 75
3256
+ }
3257
+ }
3258
+ };
2258
3259
  }
2259
3260
  });
2260
3261
 
@@ -3348,7 +4349,14 @@ async function anthropicComplete(apiKey, model, req) {
3348
4349
  ...req.tools && req.tools.length > 0 ? { tools: toAnthropicTools(req.tools) } : {},
3349
4350
  messages: toAnthropicMessages(req.messages)
3350
4351
  });
3351
- return parseResponse(response.content);
4352
+ const parsed = parseResponse(response.content);
4353
+ if (response.usage) {
4354
+ parsed.token_usage = {
4355
+ input_tokens: response.usage.input_tokens,
4356
+ output_tokens: response.usage.output_tokens
4357
+ };
4358
+ }
4359
+ return parsed;
3352
4360
  } catch (err) {
3353
4361
  throw mapAnthropicError(err, provider);
3354
4362
  }
@@ -3463,7 +4471,14 @@ async function openaiComplete(apiKey, model, req) {
3463
4471
  if (!choice?.message) {
3464
4472
  throw new Error("OpenAI returned no message");
3465
4473
  }
3466
- return parseResponse2(choice.message);
4474
+ const parsed = parseResponse2(choice.message);
4475
+ if (response.usage) {
4476
+ parsed.token_usage = {
4477
+ input_tokens: response.usage.prompt_tokens ?? 0,
4478
+ output_tokens: response.usage.completion_tokens ?? 0
4479
+ };
4480
+ }
4481
+ return parsed;
3467
4482
  } catch (err) {
3468
4483
  if (err instanceof OpenAI.APIError) {
3469
4484
  throw mapOpenAiError(err, provider);
@@ -3776,8 +4791,10 @@ async function completeWithFailover(req, opts = {}) {
3776
4791
  const meta = {
3777
4792
  provider_used: provider,
3778
4793
  model_used: model,
4794
+ ...response.token_usage ?? {},
3779
4795
  ...failoverFrom ? { failover: true, failover_from: failoverFrom } : {}
3780
4796
  };
4797
+ recordLlmUsage(response.token_usage);
3781
4798
  return { response, meta };
3782
4799
  } catch (err) {
3783
4800
  const llmErr = err;
@@ -3790,8 +4807,10 @@ async function completeWithFailover(req, opts = {}) {
3790
4807
  const meta = {
3791
4808
  provider_used: provider,
3792
4809
  model_used: model,
4810
+ ...response.token_usage ?? {},
3793
4811
  ...failoverFrom ? { failover: true, failover_from: failoverFrom } : {}
3794
4812
  };
4813
+ recordLlmUsage(response.token_usage);
3795
4814
  return { response, meta };
3796
4815
  } catch (retryErr) {
3797
4816
  const retryLlm = retryErr;
@@ -3838,9 +4857,13 @@ async function* streamWithFailover(req, opts = {}) {
3838
4857
  yield event;
3839
4858
  }
3840
4859
  }
4860
+ const estimatedOut = Math.ceil(fullText.length / 4);
4861
+ recordLlmUsage({ input_tokens: 0, output_tokens: estimatedOut });
3841
4862
  const meta = {
3842
4863
  provider_used: provider,
3843
4864
  model_used: model,
4865
+ input_tokens: 0,
4866
+ output_tokens: estimatedOut,
3844
4867
  ...failoverFrom ? { failover: true, failover_from: failoverFrom } : {}
3845
4868
  };
3846
4869
  yield {
@@ -3873,6 +4896,7 @@ async function* streamWithFailover(req, opts = {}) {
3873
4896
  var init_failover = __esm({
3874
4897
  "src/ai/llm/failover.ts"() {
3875
4898
  "use strict";
4899
+ init_usage_stats();
3876
4900
  init_anthropic();
3877
4901
  init_openai();
3878
4902
  init_catalog();
@@ -3882,22 +4906,22 @@ var init_failover = __esm({
3882
4906
  });
3883
4907
 
3884
4908
  // src/config/profile.ts
3885
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync4 } from "fs";
3886
- import { join as join4 } from "path";
4909
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, existsSync as existsSync7, mkdirSync as mkdirSync6 } from "fs";
4910
+ import { join as join7 } from "path";
3887
4911
  function profilePath() {
3888
4912
  return PROFILE_PATH;
3889
4913
  }
3890
4914
  function profileExists() {
3891
- return existsSync4(PROFILE_PATH);
4915
+ return existsSync7(PROFILE_PATH);
3892
4916
  }
3893
4917
  function isProfileConfigured(profile = loadProfile()) {
3894
4918
  if (!profile) return false;
3895
4919
  return profile.company_name.trim().length > 0;
3896
4920
  }
3897
4921
  function loadProfile() {
3898
- if (!existsSync4(PROFILE_PATH)) return null;
4922
+ if (!existsSync7(PROFILE_PATH)) return null;
3899
4923
  try {
3900
- const parsed = JSON.parse(readFileSync3(PROFILE_PATH, "utf-8"));
4924
+ const parsed = JSON.parse(readFileSync6(PROFILE_PATH, "utf-8"));
3901
4925
  if (!parsed || typeof parsed !== "object") return null;
3902
4926
  return parsed;
3903
4927
  } catch {
@@ -3910,21 +4934,21 @@ var init_profile = __esm({
3910
4934
  "use strict";
3911
4935
  init_store();
3912
4936
  NTRP_DIR3 = ntrpHome();
3913
- PROFILE_PATH = join4(NTRP_DIR3, "profile.json");
4937
+ PROFILE_PATH = join7(NTRP_DIR3, "profile.json");
3914
4938
  }
3915
4939
  });
3916
4940
 
3917
4941
  // src/data/playbook.ts
3918
- import { existsSync as existsSync5, readFileSync as readFileSync4, appendFileSync } from "fs";
3919
- import { join as join5 } from "path";
4942
+ import { existsSync as existsSync8, readFileSync as readFileSync7, appendFileSync } from "fs";
4943
+ import { join as join8 } from "path";
3920
4944
  function playsPath() {
3921
- return join5(getMemoryDir(), PLAYS_FILE);
4945
+ return join8(getMemoryDir(), PLAYS_FILE);
3922
4946
  }
3923
4947
  function getCustomPlays() {
3924
4948
  const path = playsPath();
3925
- if (!existsSync5(path)) return [];
4949
+ if (!existsSync8(path)) return [];
3926
4950
  const out = [];
3927
- for (const line of readFileSync4(path, "utf-8").split("\n")) {
4951
+ for (const line of readFileSync7(path, "utf-8").split("\n")) {
3928
4952
  const trimmed = line.trim();
3929
4953
  if (!trimmed) continue;
3930
4954
  try {
@@ -4672,9 +5696,9 @@ var init_tool_schemas = __esm({
4672
5696
  });
4673
5697
 
4674
5698
  // src/ai/privacy.ts
4675
- import { existsSync as existsSync6, mkdirSync as mkdirSync5, appendFileSync as appendFileSync2 } from "fs";
5699
+ import { existsSync as existsSync9, mkdirSync as mkdirSync7, appendFileSync as appendFileSync2 } from "fs";
4676
5700
  import { homedir as homedir3 } from "os";
4677
- import { join as join6 } from "path";
5701
+ import { join as join9 } from "path";
4678
5702
  function stripPII(obj) {
4679
5703
  if (obj === null || obj === void 0) return obj;
4680
5704
  if (typeof obj !== "object") return obj;
@@ -4689,14 +5713,14 @@ function stripPII(obj) {
4689
5713
  return out;
4690
5714
  }
4691
5715
  function ensureAuditDir() {
4692
- if (!existsSync6(AUDIT_DIR)) {
4693
- mkdirSync5(AUDIT_DIR, { recursive: true });
5716
+ if (!existsSync9(AUDIT_DIR)) {
5717
+ mkdirSync7(AUDIT_DIR, { recursive: true });
4694
5718
  }
4695
5719
  }
4696
5720
  function logToolCall(entry) {
4697
5721
  ensureAuditDir();
4698
5722
  const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
4699
- const path = join6(AUDIT_DIR, `agentic-${date}.jsonl`);
5723
+ const path = join9(AUDIT_DIR, `agentic-${date}.jsonl`);
4700
5724
  appendFileSync2(path, JSON.stringify(entry) + "\n");
4701
5725
  }
4702
5726
  var PII_FIELDS, AUDIT_DIR;
@@ -4720,7 +5744,7 @@ var init_privacy = __esm({
4720
5744
  "raw_data",
4721
5745
  "metadata"
4722
5746
  ]);
4723
- AUDIT_DIR = join6(homedir3(), ".ntrp", "audit");
5747
+ AUDIT_DIR = join9(homedir3(), ".ntrp", "audit");
4724
5748
  }
4725
5749
  });
4726
5750
 
@@ -6153,76 +7177,8 @@ var init_insights = __esm({
6153
7177
  }
6154
7178
  });
6155
7179
 
6156
- // src/ui/theme.ts
6157
- import chalk from "chalk";
6158
- function paint(token, text) {
6159
- if (token === "dim") return chalk.dim(text);
6160
- return chalk.hex(TOKENS[token])(text);
6161
- }
6162
- function bold(text) {
6163
- return chalk.bold(text);
6164
- }
6165
- function badge(label, tone = "muted") {
6166
- const normalized = ` ${label.toUpperCase()} `;
6167
- switch (tone) {
6168
- case "success":
6169
- return chalk.hex(TOKENS.success)(normalized);
6170
- case "warning":
6171
- return chalk.hex(TOKENS.warning)(normalized);
6172
- case "error":
6173
- return chalk.hex(TOKENS.error)(normalized);
6174
- case "info":
6175
- return chalk.hex(TOKENS.info)(normalized);
6176
- case "accent":
6177
- return chalk.hex(TOKENS.accent)(normalized);
6178
- case "muted":
6179
- return chalk.dim(normalized);
6180
- }
6181
- }
6182
- function sectionHeading(label) {
6183
- return `${paint("accent", "\u25B8")} ${paint("accent", bold(label))}`;
6184
- }
6185
- function actionHint(label, command, detail) {
6186
- const suffix = detail ? chalk.dim(` ${detail}`) : "";
6187
- return `${chalk.dim(label)} ${paint("accent", command)}${suffix}`;
6188
- }
6189
- function scoreBar(score, status, width = 14) {
6190
- const filled = Math.round(score / 100 * width);
6191
- const color = chalk.hex(STATUS_COLORS[status]);
6192
- let filledPart = "";
6193
- for (let i = 0; i < filled; i++) {
6194
- filledPart += i % 2 === 0 ? "\u2588" : "\u2593";
6195
- }
6196
- const emptyPart = "\u2591".repeat(width - filled);
6197
- return color(filledPart) + chalk.dim(emptyPart);
6198
- }
6199
- var STATUS_COLORS, TOKENS;
6200
- var init_theme = __esm({
6201
- "src/ui/theme.ts"() {
6202
- "use strict";
6203
- init_formatters();
6204
- STATUS_COLORS = {
6205
- green: "#22c55e",
6206
- yellow: "#eab308",
6207
- red: "#ef4444"
6208
- };
6209
- TOKENS = {
6210
- accent: "#14b8a6",
6211
- accentBright: "#2dd4bf",
6212
- border: "#334155",
6213
- borderMuted: "#1e293b",
6214
- dim: "#64748b",
6215
- text: "#e2e8f0",
6216
- error: "#ef4444",
6217
- warning: "#eab308",
6218
- success: "#22c55e",
6219
- info: "#3b82f6"
6220
- };
6221
- }
6222
- });
6223
-
6224
7180
  // src/conversation/phase.ts
6225
- import chalk2 from "chalk";
7181
+ import chalk3 from "chalk";
6226
7182
  function sessionHasData(ctx) {
6227
7183
  const counts = ctx.dataset?.counts ?? {};
6228
7184
  return Object.values(counts).some((n) => (n ?? 0) > 0);
@@ -6517,12 +7473,12 @@ var init_repl_globals = __esm({
6517
7473
  // src/cli/prompts.ts
6518
7474
  import { createInterface } from "readline/promises";
6519
7475
  import { clearLine, cursorTo } from "readline";
6520
- import chalk3 from "chalk";
7476
+ import chalk4 from "chalk";
6521
7477
  function marker() {
6522
7478
  return paint("accent", "ntrp \u203A ");
6523
7479
  }
6524
7480
  function secretPromptLine(question) {
6525
- return ` ${paint("accent", "\u25B8")} ${bold(question)} ${chalk3.dim("(hidden \u2014 paste once, Enter)")} `;
7481
+ return ` ${paint("accent", "\u25B8")} ${bold(question)} ${chalk4.dim("(hidden \u2014 paste once, Enter)")} `;
6526
7482
  }
6527
7483
  function stripTerminalArtifacts(input) {
6528
7484
  return input.replace(/\x1b\[[0-9;]*[a-zA-Z~]/g, "").replace(/\x1b\][^\x07]*(\x07|\x1b\\)/g, "").replace(/\x1b\[200~/g, "").replace(/\x1b\[201~/g, "");
@@ -6530,7 +7486,7 @@ function stripTerminalArtifacts(input) {
6530
7486
  function renderQuestion(question, defaultValue) {
6531
7487
  const base = ` ${marker()}${bold(question)}`;
6532
7488
  if (defaultValue !== void 0 && defaultValue !== "") {
6533
- return `${base} ${chalk3.dim(`[${defaultValue}]`)} `;
7489
+ return `${base} ${chalk4.dim(`[${defaultValue}]`)} `;
6534
7490
  }
6535
7491
  return `${base} `;
6536
7492
  }
@@ -6555,7 +7511,7 @@ function createPromptSession(existing, ctx) {
6555
7511
  const raw = (await rl2.question(renderQuestion(question))).trim();
6556
7512
  assertNotGlobalReplCommand(raw);
6557
7513
  if (raw) return raw;
6558
- console.log(" " + chalk3.red("This one is required."));
7514
+ console.log(" " + chalk4.red("This one is required."));
6559
7515
  }
6560
7516
  }
6561
7517
  async function confirm(question, defaultYes = false) {
@@ -6573,13 +7529,13 @@ function createPromptSession(existing, ctx) {
6573
7529
  const defaultIdx = opts.default ? choices.findIndex((c) => c.value === opts.default) : -1;
6574
7530
  choices.forEach((c, i) => {
6575
7531
  const num = paint("accent", `${i + 1}.`);
6576
- const active = i === defaultIdx ? chalk3.dim(" \u2190 default") : "";
7532
+ const active = i === defaultIdx ? chalk4.dim(" \u2190 default") : "";
6577
7533
  console.log(` ${num} ${c.label}${active}`);
6578
- if (c.description) console.log(` ${chalk3.dim(c.description)}`);
7534
+ if (c.description) console.log(` ${chalk4.dim(c.description)}`);
6579
7535
  });
6580
7536
  const defaultLabel = defaultIdx >= 0 ? String(defaultIdx + 1) : void 0;
6581
7537
  console.log();
6582
- console.log(" " + chalk3.dim("\u2500".repeat(40)));
7538
+ console.log(" " + chalk4.dim("\u2500".repeat(40)));
6583
7539
  for (; ; ) {
6584
7540
  const raw = (await rl2.question(renderQuestion(`Your pick [1-${choices.length}]`, defaultLabel))).trim();
6585
7541
  assertNotGlobalReplCommand(raw);
@@ -6588,7 +7544,7 @@ function createPromptSession(existing, ctx) {
6588
7544
  if (Number.isInteger(n) && n >= 1 && n <= choices.length) {
6589
7545
  return choices[n - 1].value;
6590
7546
  }
6591
- console.log(" " + chalk3.red(`Enter a number from 1 to ${choices.length}.`));
7547
+ console.log(" " + chalk4.red(`Enter a number from 1 to ${choices.length}.`));
6592
7548
  }
6593
7549
  }
6594
7550
  async function askMulti(question, options) {
@@ -6598,7 +7554,7 @@ function createPromptSession(existing, ctx) {
6598
7554
  options.forEach((o, i) => {
6599
7555
  const num = paint("accent", `${i + 1}.`);
6600
7556
  console.log(` ${num} ${o.label}`);
6601
- if (o.description) console.log(` ${chalk3.dim(o.description)}`);
7557
+ if (o.description) console.log(` ${chalk4.dim(o.description)}`);
6602
7558
  });
6603
7559
  const hint = `Choose [1-${options.length}], type your own, or enter to skip`;
6604
7560
  const raw = (await rl2.question(renderQuestion(hint))).trim();
@@ -6701,20 +7657,20 @@ function createPromptSession(existing, ctx) {
6701
7657
  for (; ; ) {
6702
7658
  const value = await readMaskedLine(secretPromptLine(question), maskChar);
6703
7659
  if (!value) {
6704
- console.log(" " + chalk3.red("This one is required."));
7660
+ console.log(" " + chalk4.red("This one is required."));
6705
7661
  continue;
6706
7662
  }
6707
7663
  if (opts.confirm === false) return value;
6708
7664
  const preview = value.length <= 14 ? `${value.slice(0, 4)}\u2026` : `${value.slice(0, 10)}\u2026`;
6709
- console.log(" " + chalk3.dim(`Captured ${value.length} characters (${preview})`));
7665
+ console.log(" " + chalk4.dim(`Captured ${value.length} characters (${preview})`));
6710
7666
  const ok = await confirm("Save this key?", false);
6711
7667
  if (ok) return value;
6712
- console.log(" " + chalk3.dim("Try again \u2014 paste the key once, then Enter."));
7668
+ console.log(" " + chalk4.dim("Try again \u2014 paste the key once, then Enter."));
6713
7669
  }
6714
7670
  }
6715
7671
  async function askPressEnter(message) {
6716
7672
  await rl2.question(
6717
- ` ${paint("accent", "\u25B8")} ${bold(message)} ${chalk3.dim("(Enter)")} `
7673
+ ` ${paint("accent", "\u25B8")} ${bold(message)} ${chalk4.dim("(Enter)")} `
6718
7674
  );
6719
7675
  }
6720
7676
  return {
@@ -6742,30 +7698,30 @@ var init_prompts = __esm({
6742
7698
  });
6743
7699
 
6744
7700
  // src/conversation/gap-card.ts
6745
- import chalk4 from "chalk";
7701
+ import chalk5 from "chalk";
6746
7702
  function printGapCard(audit) {
6747
7703
  console.log();
6748
- console.log(" " + chalk4.bold("Data check"));
7704
+ console.log(" " + chalk5.bold("Data check"));
6749
7705
  if (audit.satisfied.length > 0) {
6750
7706
  for (const item of audit.satisfied) {
6751
- console.log(" " + chalk4.green("\u2713") + " " + chalk4.dim(`${item.label}: ${item.detail}`));
7707
+ console.log(" " + chalk5.green("\u2713") + " " + chalk5.dim(`${item.label}: ${item.detail}`));
6752
7708
  }
6753
7709
  }
6754
7710
  for (const item of audit.missing) {
6755
- console.log(" " + chalk4.red("\u2717") + " " + item.label + chalk4.dim(` \u2014 ${item.why}`));
6756
- console.log(" " + chalk4.dim(item.suggestion));
7711
+ console.log(" " + chalk5.red("\u2717") + " " + item.label + chalk5.dim(` \u2014 ${item.why}`));
7712
+ console.log(" " + chalk5.dim(item.suggestion));
6757
7713
  }
6758
7714
  for (const item of audit.optional) {
6759
- console.log(" " + chalk4.yellow("~") + " " + chalk4.dim(`${item.label}: ${item.detail}`));
7715
+ console.log(" " + chalk5.yellow("~") + " " + chalk5.dim(`${item.label}: ${item.detail}`));
6760
7716
  }
6761
7717
  console.log();
6762
7718
  if (audit.can_compute) {
6763
7719
  console.log(
6764
- " " + chalk4.dim("Ready to compute \u2014 say ") + chalk4.cyan('"go ahead"') + chalk4.dim(" or ") + chalk4.cyan('"run analysis"')
7720
+ " " + chalk5.dim("Ready to compute \u2014 say ") + chalk5.cyan('"go ahead"') + chalk5.dim(" or ") + chalk5.cyan('"run analysis"')
6765
7721
  );
6766
7722
  } else if (audit.missing.length > 0) {
6767
7723
  console.log(
6768
- " " + chalk4.dim("Load data (paste a CSV path or say ") + chalk4.cyan("use demo data") + chalk4.dim(")")
7724
+ " " + chalk5.dim("Load data (paste a CSV path or say ") + chalk5.cyan("use demo data") + chalk5.dim(")")
6769
7725
  );
6770
7726
  }
6771
7727
  console.log();
@@ -6777,7 +7733,7 @@ var init_gap_card = __esm({
6777
7733
  });
6778
7734
 
6779
7735
  // src/metrics/companion.ts
6780
- import chalk5 from "chalk";
7736
+ import chalk6 from "chalk";
6781
7737
  function getCompanionRecommendation(input) {
6782
7738
  const { analysis, coverage, opportunityCount, activityCount, sourceType } = input;
6783
7739
  const completed = new Set(analysis.completed);
@@ -6797,14 +7753,14 @@ function getCompanionRecommendation(input) {
6797
7753
  function printAnalysisComplete(lens) {
6798
7754
  const label = lens === "revenue_metrics" ? "SaaS metrics" : "Pipeline health";
6799
7755
  console.log(
6800
- " " + chalk5.green(`\u2713 ${label} ready`) + chalk5.dim(" \u2014 type a question below ") + paint("accent", "(no slash needed)")
7756
+ " " + chalk6.green(`\u2713 ${label} ready`) + chalk6.dim(" \u2014 type a question below ") + paint("accent", "(no slash needed)")
6801
7757
  );
6802
7758
  }
6803
7759
  function printCompanionBanner(invoked, primary) {
6804
7760
  if (invoked === "diagnose" && primary === "revenue_metrics") {
6805
7761
  console.log();
6806
7762
  console.log(
6807
- " " + chalk5.dim("Other view \u2014 ") + chalk5.bold("pipeline health") + chalk5.dim(" (this session started with SaaS metrics)")
7763
+ " " + chalk6.dim("Other view \u2014 ") + chalk6.bold("pipeline health") + chalk6.dim(" (this session started with SaaS metrics)")
6808
7764
  );
6809
7765
  console.log();
6810
7766
  return;
@@ -6812,7 +7768,7 @@ function printCompanionBanner(invoked, primary) {
6812
7768
  if (invoked === "metrics" && primary === "gtm_health") {
6813
7769
  console.log();
6814
7770
  console.log(
6815
- " " + chalk5.dim("Other view \u2014 ") + chalk5.bold("SaaS metrics") + chalk5.dim(" (this session started with pipeline health)")
7771
+ " " + chalk6.dim("Other view \u2014 ") + chalk6.bold("SaaS metrics") + chalk6.dim(" (this session started with pipeline health)")
6816
7772
  );
6817
7773
  console.log();
6818
7774
  }
@@ -6822,7 +7778,7 @@ function printCompanionFooter(ctx, companion, options = { justCompleted: "gtm_he
6822
7778
  const completed = new Set(ctx.analysis.completed);
6823
7779
  printAnalysisComplete(justCompleted);
6824
7780
  console.log();
6825
- console.log(" " + chalk5.bold("Try asking"));
7781
+ console.log(" " + chalk6.bold("Try asking"));
6826
7782
  const defaults = justCompleted === "revenue_metrics" ? [
6827
7783
  "Why is NRR showing 100%?",
6828
7784
  "Which deals drove ARR this quarter?",
@@ -6834,7 +7790,7 @@ function printCompanionFooter(ctx, companion, options = { justCompleted: "gtm_he
6834
7790
  ];
6835
7791
  const asks = (suggestedAsks?.length ? suggestedAsks : defaults).slice(0, 3);
6836
7792
  for (const q of asks) {
6837
- console.log(chalk5.dim(` "${q}"`));
7793
+ console.log(chalk6.dim(` "${q}"`));
6838
7794
  }
6839
7795
  console.log();
6840
7796
  const extras = [];
@@ -6849,7 +7805,7 @@ function printCompanionFooter(ctx, companion, options = { justCompleted: "gtm_he
6849
7805
  } else {
6850
7806
  extras.push(`${paint("accent", "/diagnose --findings")} for AI depth`);
6851
7807
  }
6852
- console.log(" " + chalk5.dim("Also: ") + chalk5.dim(extras.join(" \xB7 ")));
7808
+ console.log(" " + chalk6.dim("Also: ") + chalk6.dim(extras.join(" \xB7 ")));
6853
7809
  console.log();
6854
7810
  }
6855
7811
  async function resolveCompanionRecommendation(ctx) {
@@ -7151,7 +8107,7 @@ var init_layout = __esm({
7151
8107
  });
7152
8108
 
7153
8109
  // src/ui/markdown.ts
7154
- import chalk6 from "chalk";
8110
+ import chalk7 from "chalk";
7155
8111
  import Table from "cli-table3";
7156
8112
  function renderMarkdown(text, opts = {}) {
7157
8113
  const indent = opts.indent ?? 2;
@@ -7287,13 +8243,13 @@ function renderBlock(block, width) {
7287
8243
  case "paragraph":
7288
8244
  return wrapWords(inline(block.text), width).join("\n");
7289
8245
  case "heading": {
7290
- const styled = chalk6.bold(inline(block.text));
8246
+ const styled = chalk7.bold(inline(block.text));
7291
8247
  const barWidth = Math.min(width, Math.max(8, visibleWidth(styled)));
7292
8248
  return `${styled}
7293
- ${chalk6.dim(hr(barWidth))}`;
8249
+ ${chalk7.dim(hr(barWidth))}`;
7294
8250
  }
7295
8251
  case "hr":
7296
- return chalk6.dim(hr(width));
8252
+ return chalk7.dim(hr(width));
7297
8253
  case "ul":
7298
8254
  return block.items.map((item) => {
7299
8255
  const lines = wrapWords(inline(item), Math.max(1, width - 4));
@@ -7311,10 +8267,10 @@ ${chalk6.dim(hr(barWidth))}`;
7311
8267
  }
7312
8268
  case "blockquote": {
7313
8269
  const lines = wrapWords(inline(block.text), Math.max(1, width - 2));
7314
- return lines.map((l) => chalk6.dim("\u2502 ") + chalk6.italic(l)).join("\n");
8270
+ return lines.map((l) => chalk7.dim("\u2502 ") + chalk7.italic(l)).join("\n");
7315
8271
  }
7316
8272
  case "code":
7317
- return block.lines.map((l) => chalk6.cyan(` ${l}`)).join("\n");
8273
+ return block.lines.map((l) => chalk7.cyan(` ${l}`)).join("\n");
7318
8274
  case "table":
7319
8275
  return renderTable(block.header, block.rows, width);
7320
8276
  }
@@ -7355,11 +8311,11 @@ function inline(text) {
7355
8311
  codeSpans.push(code);
7356
8312
  return `\0CODE${idx}\0`;
7357
8313
  });
7358
- out = out.replace(/\*\*([^*\n]+?)\*\*/g, (_m, inner) => chalk6.bold(inner));
7359
- out = out.replace(/(^|[^*\w])\*([^*\n]+?)\*(?!\*)/g, (_m, pre, inner) => `${pre}${chalk6.italic(inner)}`);
7360
- out = out.replace(/(^|[^_\w])_([^_\n]+?)_(?!\w)/g, (_m, pre, inner) => `${pre}${chalk6.italic(inner)}`);
8314
+ out = out.replace(/\*\*([^*\n]+?)\*\*/g, (_m, inner) => chalk7.bold(inner));
8315
+ out = out.replace(/(^|[^*\w])\*([^*\n]+?)\*(?!\*)/g, (_m, pre, inner) => `${pre}${chalk7.italic(inner)}`);
8316
+ out = out.replace(/(^|[^_\w])_([^_\n]+?)_(?!\w)/g, (_m, pre, inner) => `${pre}${chalk7.italic(inner)}`);
7361
8317
  out = out.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1");
7362
- out = out.replace(/\u0000CODE(\d+)\u0000/g, (_m, idx) => chalk6.cyan(codeSpans[Number(idx)]));
8318
+ out = out.replace(/\u0000CODE(\d+)\u0000/g, (_m, idx) => chalk7.cyan(codeSpans[Number(idx)]));
7363
8319
  return out;
7364
8320
  }
7365
8321
  var init_markdown = __esm({
@@ -7370,7 +8326,7 @@ var init_markdown = __esm({
7370
8326
  });
7371
8327
 
7372
8328
  // src/output/llm-attribution.ts
7373
- import chalk7 from "chalk";
8329
+ import chalk8 from "chalk";
7374
8330
  function formatLlmAttribution(meta) {
7375
8331
  if (!meta.model_used) return null;
7376
8332
  const provider = meta.provider_used ?? "anthropic";
@@ -7382,7 +8338,7 @@ function formatLlmAttribution(meta) {
7382
8338
  }
7383
8339
  function printLlmAttribution(meta) {
7384
8340
  const line = formatLlmAttribution(meta);
7385
- if (line) console.log(chalk7.dim(` ${line}`));
8341
+ if (line) console.log(chalk8.dim(` ${line}`));
7386
8342
  }
7387
8343
  var init_llm_attribution = __esm({
7388
8344
  "src/output/llm-attribution.ts"() {
@@ -7392,7 +8348,7 @@ var init_llm_attribution = __esm({
7392
8348
  });
7393
8349
 
7394
8350
  // src/output/terminal.ts
7395
- import chalk8 from "chalk";
8351
+ import chalk9 from "chalk";
7396
8352
  import ora from "ora";
7397
8353
  import Table2 from "cli-table3";
7398
8354
  function centerPad(text, width) {
@@ -7404,11 +8360,11 @@ function centerPad(text, width) {
7404
8360
  function statusColor(status) {
7405
8361
  switch (status) {
7406
8362
  case "green":
7407
- return chalk8.green;
8363
+ return chalk9.green;
7408
8364
  case "yellow":
7409
- return chalk8.yellow;
8365
+ return chalk9.yellow;
7410
8366
  case "red":
7411
- return chalk8.red;
8367
+ return chalk9.red;
7412
8368
  }
7413
8369
  }
7414
8370
  function statusDot(status) {
@@ -7425,12 +8381,12 @@ function statusBadge(status) {
7425
8381
  }
7426
8382
  }
7427
8383
  function printHeading(label, detail) {
7428
- console.log(` ${sectionHeading(label)}${detail ? chalk8.dim(` ${detail}`) : ""}`);
8384
+ console.log(` ${sectionHeading(label)}${detail ? chalk9.dim(` ${detail}`) : ""}`);
7429
8385
  }
7430
8386
  function printResultCard(title, rows) {
7431
8387
  const width = 70;
7432
8388
  const inner = width - 4;
7433
- const border = chalk8.dim;
8389
+ const border = chalk9.dim;
7434
8390
  console.log();
7435
8391
  console.log(` ${border(`\u256D${"\u2500".repeat(width - 2)}\u256E`)}`);
7436
8392
  console.log(` ${border("\u2502 ")}${padRight(sectionHeading(title), inner)}${border(" \u2502")}`);
@@ -7446,17 +8402,17 @@ function printVitalSignRow(vs) {
7446
8402
  const label = VITAL_SIGN_LABELS[vs.vital_sign].padEnd(18);
7447
8403
  const bar = scoreBar(vs.score, vs.status);
7448
8404
  const score = String(Math.round(vs.score)).padStart(4);
7449
- const impact = vs.dollar_value != null && vs.dollar_value > 0 ? `${chalk8.green(formatCurrency(vs.dollar_value))} ${chalk8.dim(vs.dollar_label ?? "")}` : chalk8.dim("\u2014");
7450
- console.log(` ${dot} ${label} ${bar} ${chalk8.bold(score)} ${chalk8.dim("\u2502")} ${impact}`);
8405
+ const impact = vs.dollar_value != null && vs.dollar_value > 0 ? `${chalk9.green(formatCurrency(vs.dollar_value))} ${chalk9.dim(vs.dollar_label ?? "")}` : chalk9.dim("\u2014");
8406
+ console.log(` ${dot} ${label} ${bar} ${chalk9.bold(score)} ${chalk9.dim("\u2502")} ${impact}`);
7451
8407
  }
7452
8408
  function printHealthSummary(result, _pipelineMetrics) {
7453
- const scoreStr = `${chalk8.bold(String(Math.round(result.overall_score)))}${chalk8.dim("/100")}`;
7454
- 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");
8409
+ const scoreStr = `${chalk9.bold(String(Math.round(result.overall_score)))}${chalk9.dim("/100")}`;
8410
+ 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");
7455
8411
  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");
7456
8412
  printResultCard("Overall Health", [
7457
- `${chalk8.dim("Score")} ${scoreStr} ${statusBadge(result.overall_status)}`,
7458
- `${chalk8.dim("Gated by")} ${paint("accent", VITAL_SIGN_LABELS[result.gating_vital_sign])}`,
7459
- `${chalk8.dim("Revenue")} ${impact}`,
8413
+ `${chalk9.dim("Score")} ${scoreStr} ${statusBadge(result.overall_status)}`,
8414
+ `${chalk9.dim("Gated by")} ${paint("accent", VITAL_SIGN_LABELS[result.gating_vital_sign])}`,
8415
+ `${chalk9.dim("Revenue")} ${impact}`,
7460
8416
  next
7461
8417
  ]);
7462
8418
  }
@@ -7477,8 +8433,8 @@ function printSegmentSummary(segments) {
7477
8433
  const dot = statusDot(seg.result.overall_status);
7478
8434
  const name = seg.segment.name.padEnd(24);
7479
8435
  const score = String(Math.round(seg.result.overall_score)).padStart(4);
7480
- const gating = chalk8.dim(VITAL_SIGN_LABELS[seg.result.gating_vital_sign]);
7481
- console.log(` ${dot} ${name} ${chalk8.bold(score)} ${chalk8.dim("\u2502")} ${gating}`);
8436
+ const gating = chalk9.dim(VITAL_SIGN_LABELS[seg.result.gating_vital_sign]);
8437
+ console.log(` ${dot} ${name} ${chalk9.bold(score)} ${chalk9.dim("\u2502")} ${gating}`);
7482
8438
  }
7483
8439
  console.log();
7484
8440
  }
@@ -7502,7 +8458,7 @@ function printTopProblems(segments, limit = 7) {
7502
8458
  if (problems.length === 0) {
7503
8459
  printHeading("Top Problems");
7504
8460
  console.log();
7505
- console.log(" " + chalk8.dim("No dollar-weighted problems found across segments."));
8461
+ console.log(" " + chalk9.dim("No dollar-weighted problems found across segments."));
7506
8462
  console.log();
7507
8463
  return;
7508
8464
  }
@@ -7515,45 +8471,45 @@ function printTopProblems(segments, limit = 7) {
7515
8471
  const labelW = Math.max("".length, ...top.map((p) => p.dollarLabel.length));
7516
8472
  const impactW = dollarW + 2 + labelW;
7517
8473
  console.log(
7518
- ` ${sectionHeading("Top Problems")}` + chalk8.dim(` (${top.length} of ${problems.length})`)
8474
+ ` ${sectionHeading("Top Problems")}` + chalk9.dim(` (${top.length} of ${problems.length})`)
7519
8475
  );
7520
8476
  console.log();
7521
8477
  const segColW = 2 + segW;
7522
8478
  const hSeg = centerPad("Segment", segColW);
7523
8479
  const hVital = centerPad("Vital Sign", vitalW);
7524
8480
  const hImpact = centerPad("Revenue Impact", Math.max(impactW, "Revenue Impact".length));
7525
- console.log(` ${chalk8.dim(hSeg)} ${chalk8.dim(hVital)} ${chalk8.dim(hImpact)}`);
8481
+ console.log(` ${chalk9.dim(hSeg)} ${chalk9.dim(hVital)} ${chalk9.dim(hImpact)}`);
7526
8482
  console.log();
7527
8483
  for (let i = 0; i < top.length; i++) {
7528
8484
  const p = top[i];
7529
8485
  const dot = statusDot(p.status);
7530
8486
  const seg = p.segment.padEnd(segW);
7531
8487
  const vital = p.vitalSignLabel.padEnd(vitalW);
7532
- const dollar = chalk8.green(dollarStrs[i].padStart(dollarW));
7533
- const label = chalk8.dim(p.dollarLabel);
8488
+ const dollar = chalk9.green(dollarStrs[i].padStart(dollarW));
8489
+ const label = chalk9.dim(p.dollarLabel);
7534
8490
  console.log(` ${dot} ${seg} ${vital} ${dollar} ${label}`);
7535
8491
  }
7536
8492
  if (problems.length > top.length) {
7537
8493
  console.log();
7538
- console.log(` ${chalk8.dim("Run /diagnose --segment <name> to drill in")}`);
8494
+ console.log(` ${chalk9.dim("Run /diagnose --segment <name> to drill in")}`);
7539
8495
  }
7540
8496
  console.log();
7541
8497
  }
7542
8498
  function printFindings(findings) {
7543
8499
  if (findings.length === 0) {
7544
- console.log(chalk8.dim(" No findings generated."));
8500
+ console.log(chalk9.dim(" No findings generated."));
7545
8501
  return;
7546
8502
  }
7547
8503
  for (const finding of findings) {
7548
- const sevColor = finding.severity === "critical" ? chalk8.red : finding.severity === "warning" ? chalk8.yellow : chalk8.blue;
8504
+ const sevColor = finding.severity === "critical" ? chalk9.red : finding.severity === "warning" ? chalk9.yellow : chalk9.blue;
7549
8505
  const dot = sevColor("\u25CF");
7550
- const dollarTag = finding.dollar_value != null && finding.dollar_value > 0 ? ` ${chalk8.dim("\xB7")} ${chalk8.green(formatDollarValue(finding.dollar_value))}` : "";
7551
- console.log(` ${dot} ${chalk8.bold(finding.segment)}${dollarTag}`);
8506
+ const dollarTag = finding.dollar_value != null && finding.dollar_value > 0 ? ` ${chalk9.dim("\xB7")} ${chalk9.green(formatDollarValue(finding.dollar_value))}` : "";
8507
+ console.log(` ${dot} ${chalk9.bold(finding.segment)}${dollarTag}`);
7552
8508
  printMarkdown(finding.finding, { indent: 2 });
7553
8509
  if (finding.recommended_plays && finding.recommended_plays.length > 0) {
7554
8510
  for (const play of finding.recommended_plays) {
7555
8511
  console.log(
7556
- ` ${chalk8.dim("Consider:")} ${paint("accent", play.play_name)} ${chalk8.dim("\u2192")} ${chalk8.dim("/playbook " + play.play_id)}`
8512
+ ` ${chalk9.dim("Consider:")} ${paint("accent", play.play_name)} ${chalk9.dim("\u2192")} ${chalk9.dim("/playbook " + play.play_id)}`
7557
8513
  );
7558
8514
  }
7559
8515
  }
@@ -7562,7 +8518,7 @@ function printFindings(findings) {
7562
8518
  }
7563
8519
  function printEntityCounts(counts) {
7564
8520
  const table = new Table2({
7565
- head: [chalk8.dim("Entity"), chalk8.dim("Count")],
8521
+ head: [chalk9.dim("Entity"), chalk9.dim("Count")],
7566
8522
  colWidths: [20, 12],
7567
8523
  style: { head: [], border: [] }
7568
8524
  });
@@ -7577,19 +8533,19 @@ function printSegmentDetail(seg, aggregate) {
7577
8533
  console.log();
7578
8534
  printHeading(seg.segment.name);
7579
8535
  console.log(
7580
- ` ${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])}`
8536
+ ` ${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])}`
7581
8537
  );
7582
8538
  console.log();
7583
8539
  for (const vs of seg.result.vital_signs) {
7584
8540
  const aggVs = aggregate.vital_signs.find((a) => a.vital_sign === vs.vital_sign);
7585
8541
  const delta = aggVs ? vs.score - aggVs.score : 0;
7586
- const deltaStr = delta >= 0 ? chalk8.green(`+${Math.round(delta)}`) : chalk8.red(`${Math.round(delta)}`);
8542
+ const deltaStr = delta >= 0 ? chalk9.green(`+${Math.round(delta)}`) : chalk9.red(`${Math.round(delta)}`);
7587
8543
  const dot = statusDot(vs.status);
7588
8544
  const label = VITAL_SIGN_LABELS[vs.vital_sign].padEnd(18);
7589
8545
  const bar = scoreBar(vs.score, vs.status);
7590
8546
  const score = String(Math.round(vs.score)).padStart(4);
7591
- const impact = vs.dollar_value != null && vs.dollar_value > 0 ? `${chalk8.green(formatCurrency(vs.dollar_value))} ${chalk8.dim(vs.dollar_label ?? "")}` : chalk8.dim("\u2014");
7592
- console.log(` ${dot} ${label} ${bar} ${chalk8.bold(score)} ${deltaStr.padStart(12)} ${chalk8.dim("\u2502")} ${impact}`);
8547
+ const impact = vs.dollar_value != null && vs.dollar_value > 0 ? `${chalk9.green(formatCurrency(vs.dollar_value))} ${chalk9.dim(vs.dollar_label ?? "")}` : chalk9.dim("\u2014");
8548
+ console.log(` ${dot} ${label} ${bar} ${chalk9.bold(score)} ${deltaStr.padStart(12)} ${chalk9.dim("\u2502")} ${impact}`);
7593
8549
  }
7594
8550
  console.log();
7595
8551
  }
@@ -7673,7 +8629,7 @@ async function renderDiagnoseStream(options) {
7673
8629
  }
7674
8630
  findingsSpinner.stop();
7675
8631
  console.log(
7676
- chalk8.dim(` Investigating... called ${toolCalls} tools across ${iterations} iterations`)
8632
+ chalk9.dim(` Investigating... called ${toolCalls} tools across ${iterations} iterations`)
7677
8633
  );
7678
8634
  console.log();
7679
8635
  if (storeFindings) {
@@ -7695,7 +8651,7 @@ async function renderDiagnoseStream(options) {
7695
8651
  });
7696
8652
  } catch (err) {
7697
8653
  findingsSpinner.fail(deep ? "Agentic investigation failed" : "AI findings failed");
7698
- console.error(chalk8.dim(String(err)));
8654
+ console.error(chalk9.dim(String(err)));
7699
8655
  }
7700
8656
  }
7701
8657
  return { fullResult, findings: collectedFindings };
@@ -7703,13 +8659,13 @@ async function renderDiagnoseStream(options) {
7703
8659
  function metricStatusDot(status) {
7704
8660
  switch (status) {
7705
8661
  case "green":
7706
- return chalk8.green("\u25CF");
8662
+ return chalk9.green("\u25CF");
7707
8663
  case "yellow":
7708
- return chalk8.yellow("\u25CF");
8664
+ return chalk9.yellow("\u25CF");
7709
8665
  case "red":
7710
- return chalk8.red("\u25CF");
8666
+ return chalk9.red("\u25CF");
7711
8667
  case "neutral":
7712
- return chalk8.dim("\u25CB");
8668
+ return chalk9.dim("\u25CB");
7713
8669
  }
7714
8670
  }
7715
8671
  function printMetricsTable(metrics, groupOrder) {
@@ -7719,16 +8675,16 @@ function printMetricsTable(metrics, groupOrder) {
7719
8675
  printHeading(group);
7720
8676
  console.log();
7721
8677
  for (const m of groupMetrics) {
7722
- const dot = m.unavailable_reason ? chalk8.dim("\u25CB") : metricStatusDot(m.status);
8678
+ const dot = m.unavailable_reason ? chalk9.dim("\u25CB") : metricStatusDot(m.status);
7723
8679
  const label = m.label.padEnd(28);
7724
- const valueStr = m.unavailable_reason ? chalk8.dim("--") : chalk8.bold(m.formatted);
7725
- 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") : "";
7726
- const note = m.unavailable_reason ? chalk8.dim(m.unavailable_reason) : m.benchmark_note ? chalk8.dim(m.benchmark_note) : "";
8680
+ const valueStr = m.unavailable_reason ? chalk9.dim("--") : chalk9.bold(m.formatted);
8681
+ 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") : "";
8682
+ const note = m.unavailable_reason ? chalk9.dim(m.unavailable_reason) : m.benchmark_note ? chalk9.dim(m.benchmark_note) : "";
7727
8683
  console.log(` ${dot} ${label} ${valueStr}${confTag}${note ? " " + note : ""}`);
7728
8684
  if (m.reliability_gate?.requirements?.length && (m.confidence ?? 100) < 80) {
7729
8685
  const gate = m.reliability_gate.requirements[0];
7730
8686
  if (gate) {
7731
- console.log(chalk8.dim(` \u2514 Gate: ${gate}`));
8687
+ console.log(chalk9.dim(` \u2514 Gate: ${gate}`));
7732
8688
  }
7733
8689
  }
7734
8690
  }
@@ -7752,15 +8708,15 @@ __export(metrics_report_exports, {
7752
8708
  printMetricsNextSteps: () => printMetricsNextSteps,
7753
8709
  renderMetricsReport: () => renderMetricsReport
7754
8710
  });
7755
- import chalk9 from "chalk";
8711
+ import chalk10 from "chalk";
7756
8712
  function renderMetricsReport(ctx, metrics, coverage, sourceType, options = {}) {
7757
8713
  const title = options.title ?? "SaaS Metrics Analysis";
7758
8714
  const tier = coverageTier(coverage);
7759
8715
  const deterministic = buildDeterministicInsights(metrics, coverage, sourceType);
7760
8716
  const headline = pickHeadlineInsight(deterministic);
7761
8717
  console.log();
7762
- console.log(chalk9.bold(` ${title}`));
7763
- console.log(" " + chalk9.dim(formatCoverageHeader(sourceType, coverage)));
8718
+ console.log(chalk10.bold(` ${title}`));
8719
+ console.log(" " + chalk10.dim(formatCoverageHeader(sourceType, coverage)));
7764
8720
  console.log();
7765
8721
  printDataQualityPanel(coverage, sourceType, tier);
7766
8722
  if (options.snapshot && coverage.distinct_quarters >= 2) {
@@ -7768,7 +8724,7 @@ function renderMetricsReport(ctx, metrics, coverage, sourceType, options = {}) {
7768
8724
  }
7769
8725
  if (headline) {
7770
8726
  console.log(" " + paint("warning", "\u25B8 Headline"));
7771
- console.log(" " + chalk9.white(wrapInsight(headline)));
8727
+ console.log(" " + chalk10.white(wrapInsight(headline)));
7772
8728
  console.log();
7773
8729
  }
7774
8730
  printMetricsTable(metrics, GROUP_ORDER);
@@ -7776,9 +8732,9 @@ function renderMetricsReport(ctx, metrics, coverage, sourceType, options = {}) {
7776
8732
  console.log(" " + bold("Pattern checks"));
7777
8733
  console.log();
7778
8734
  for (const insight of deterministic.slice(0, 5)) {
7779
- const dot = insight.severity === "warning" ? chalk9.yellow("\u25CF") : insight.severity === "critical" ? chalk9.red("\u25CF") : chalk9.dim("\u25CB");
8735
+ const dot = insight.severity === "warning" ? chalk10.yellow("\u25CF") : insight.severity === "critical" ? chalk10.red("\u25CF") : chalk10.dim("\u25CB");
7780
8736
  if (insight.headline) continue;
7781
- console.log(` ${dot} ${chalk9.dim(wrapInsight(insight.message))}`);
8737
+ console.log(` ${dot} ${chalk10.dim(wrapInsight(insight.message))}`);
7782
8738
  }
7783
8739
  console.log();
7784
8740
  }
@@ -7807,7 +8763,7 @@ function printDataQualityPanel(coverage, sourceType, tier) {
7807
8763
  rows.push(["Ledger", "not loaded \u2014 retention inferred from CRM"]);
7808
8764
  }
7809
8765
  for (const [label, value] of rows) {
7810
- console.log(` ${chalk9.dim(String(label).padEnd(14))} ${value}`);
8766
+ console.log(` ${chalk10.dim(String(label).padEnd(14))} ${value}`);
7811
8767
  }
7812
8768
  console.log();
7813
8769
  }
@@ -7818,10 +8774,10 @@ function printCloseTrend(snapshot, cadence) {
7818
8774
  console.log(" " + bold(`Close trend (${cadence})`));
7819
8775
  console.log();
7820
8776
  for (const b of recent) {
7821
- const newStr = b.new_arr > 0 ? chalk9.dim(` new $${formatShort(b.new_arr)}`) : "";
7822
- const expStr = b.expansion_arr > 0 ? chalk9.dim(` exp $${formatShort(b.expansion_arr)}`) : "";
8777
+ const newStr = b.new_arr > 0 ? chalk10.dim(` new $${formatShort(b.new_arr)}`) : "";
8778
+ const expStr = b.expansion_arr > 0 ? chalk10.dim(` exp $${formatShort(b.expansion_arr)}`) : "";
7823
8779
  console.log(
7824
- ` ${chalk9.dim(b.period.padEnd(8))} ${chalk9.bold("$" + formatShort(b.closed_won_total))}${newStr}${expStr} ${chalk9.dim(`(${b.closed_won_count} deals)`)}`
8780
+ ` ${chalk10.dim(b.period.padEnd(8))} ${chalk10.bold("$" + formatShort(b.closed_won_total))}${newStr}${expStr} ${chalk10.dim(`(${b.closed_won_count} deals)`)}`
7825
8781
  );
7826
8782
  }
7827
8783
  console.log();
@@ -8083,7 +9039,7 @@ var diagnose_exports = {};
8083
9039
  __export(diagnose_exports, {
8084
9040
  handler: () => handler
8085
9041
  });
8086
- import chalk10 from "chalk";
9042
+ import chalk11 from "chalk";
8087
9043
  import ora2 from "ora";
8088
9044
  async function handler(args, ctx) {
8089
9045
  await hydrateAnalysisFromPersistedState(ctx);
@@ -8112,9 +9068,9 @@ async function handler(args, ctx) {
8112
9068
  }
8113
9069
  if (options.findings && !canUseReplAi(ctx)) {
8114
9070
  console.log();
8115
- console.log(" " + chalk10.red("AI findings run only in the interactive REPL."));
8116
- console.log(" " + chalk10.dim("Vital signs compute without a key \u2014 omit --findings for numbers only."));
8117
- 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."));
9071
+ console.log(" " + chalk11.red("AI findings run only in the interactive REPL."));
9072
+ console.log(" " + chalk11.dim("Vital signs compute without a key \u2014 omit --findings for numbers only."));
9073
+ 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."));
8118
9074
  console.log();
8119
9075
  return;
8120
9076
  }
@@ -8140,8 +9096,12 @@ async function handler(args, ctx) {
8140
9096
  const companion = await resolveCompanionRecommendation(ctx);
8141
9097
  printCompanionFooter(ctx, companion, { justCompleted: "gtm_health" });
8142
9098
  }
9099
+ if (!ctx.skipTimeBankDiagnoseCredit) {
9100
+ creditDiagnoseComplete(ctx, options.findings);
9101
+ }
9102
+ ctx.skipTimeBankDiagnoseCredit = false;
8143
9103
  if (ctx.oneShot && options.findings) {
8144
- console.log(chalk10.dim(" For follow-up questions, run `ntrp` and ask in plain English."));
9104
+ console.log(chalk11.dim(" For follow-up questions, run `ntrp` and ask in plain English."));
8145
9105
  console.log();
8146
9106
  }
8147
9107
  return summary;
@@ -8198,7 +9158,7 @@ async function runDiagnose(options, ctx) {
8198
9158
  });
8199
9159
  return buildDiagnoseSummary(fullResult.aggregate, findings);
8200
9160
  } catch (err) {
8201
- console.error(chalk10.red(String(err)));
9161
+ console.error(chalk11.red(String(err)));
8202
9162
  process.exit(1);
8203
9163
  }
8204
9164
  }
@@ -8210,7 +9170,7 @@ async function runSegmentDiagnose(options) {
8210
9170
  spinner.succeed("Diagnosis complete");
8211
9171
  } catch (err) {
8212
9172
  spinner.fail("Diagnosis failed");
8213
- console.error(chalk10.red(String(err)));
9173
+ console.error(chalk11.red(String(err)));
8214
9174
  process.exit(1);
8215
9175
  }
8216
9176
  const needle = options.segment.toLowerCase();
@@ -8219,19 +9179,19 @@ async function runSegmentDiagnose(options) {
8219
9179
  const subs = result.segments.filter((s) => s.segment.name.toLowerCase().includes(needle));
8220
9180
  if (subs.length === 1) match = subs[0];
8221
9181
  else if (subs.length > 1) {
8222
- console.error(chalk10.yellow(`
9182
+ console.error(chalk11.yellow(`
8223
9183
  "${options.segment}" matches multiple segments:`));
8224
- for (const s of subs) console.log(chalk10.dim(` - ${s.segment.name}`));
9184
+ for (const s of subs) console.log(chalk11.dim(` - ${s.segment.name}`));
8225
9185
  console.log();
8226
9186
  return;
8227
9187
  }
8228
9188
  }
8229
9189
  if (!match) {
8230
- console.error(chalk10.red(`
9190
+ console.error(chalk11.red(`
8231
9191
  No segment matching "${options.segment}".`));
8232
9192
  if (result.segments.length > 0) {
8233
- console.log(chalk10.dim(" Available segments:"));
8234
- for (const s of result.segments) console.log(chalk10.dim(` - ${s.segment.name}`));
9193
+ console.log(chalk11.dim(" Available segments:"));
9194
+ for (const s of result.segments) console.log(chalk11.dim(` - ${s.segment.name}`));
8235
9195
  }
8236
9196
  console.log();
8237
9197
  return;
@@ -8280,6 +9240,7 @@ var init_diagnose = __esm({
8280
9240
  init_serialize();
8281
9241
  init_theme();
8282
9242
  init_companion();
9243
+ init_time_bank();
8283
9244
  }
8284
9245
  });
8285
9246
 
@@ -8290,7 +9251,7 @@ __export(compute_exports2, {
8290
9251
  runConversationCompute: () => runConversationCompute
8291
9252
  });
8292
9253
  import ora3 from "ora";
8293
- import chalk11 from "chalk";
9254
+ import chalk12 from "chalk";
8294
9255
  async function runConversationCompute(ctx) {
8295
9256
  const lens = ctx.scope?.primary_lens ?? ctx.analysis.primary;
8296
9257
  ctx.computeInProgress = true;
@@ -8313,6 +9274,8 @@ async function runConversationCompute(ctx) {
8313
9274
  ctx.snapshot.computeResult = null;
8314
9275
  invalidateGapAudit(ctx);
8315
9276
  saveSessionState(ctx);
9277
+ creditGapCompute(ctx);
9278
+ creditMetricsComplete(ctx, false);
8316
9279
  renderMetricsReport2(ctx, result.metrics.aggregate.metrics, result.coverage, result.data_source_type, {
8317
9280
  snapshot: result.snapshot,
8318
9281
  findings: result.findings,
@@ -8326,6 +9289,8 @@ async function runConversationCompute(ctx) {
8326
9289
  }
8327
9290
  }
8328
9291
  const { handler: diagnose } = await Promise.resolve().then(() => (init_diagnose(), diagnose_exports));
9292
+ ctx.skipTimeBankDiagnoseCredit = true;
9293
+ creditGapCompute(ctx);
8329
9294
  const summary = await diagnose([], ctx);
8330
9295
  markLensCompleted(ctx, "gtm_health");
8331
9296
  ctx.stage = "analyzed";
@@ -8336,7 +9301,7 @@ async function runConversationCompute(ctx) {
8336
9301
  printCompanionFooter(ctx, companion, { justCompleted: "gtm_health" });
8337
9302
  return typeof summary === "string" ? summary : "Health analysis ready";
8338
9303
  } catch (err) {
8339
- console.error(" " + chalk11.red(String(err.message ?? err)));
9304
+ console.error(" " + chalk12.red(String(err.message ?? err)));
8340
9305
  return;
8341
9306
  } finally {
8342
9307
  ctx.computeInProgress = false;
@@ -8352,16 +9317,17 @@ var init_compute2 = __esm({
8352
9317
  init_context2();
8353
9318
  init_gap_audit();
8354
9319
  init_companion();
9320
+ init_time_bank();
8355
9321
  }
8356
9322
  });
8357
9323
 
8358
9324
  // src/config/demo.ts
8359
- import chalk12 from "chalk";
9325
+ import chalk13 from "chalk";
8360
9326
  function printDemoDisabled() {
8361
9327
  console.log();
8362
- console.log(" " + chalk12.red(DEMO_DISABLED_MESSAGE));
9328
+ console.log(" " + chalk13.red(DEMO_DISABLED_MESSAGE));
8363
9329
  console.log(
8364
- " " + chalk12.dim("Re-enable with ") + paint("accent", "/config set demo-enabled true") + chalk12.dim(".")
9330
+ " " + chalk13.dim("Re-enable with ") + paint("accent", "/config set demo-enabled true") + chalk13.dim(".")
8365
9331
  );
8366
9332
  console.log();
8367
9333
  }
@@ -11109,18 +12075,18 @@ var init_generator = __esm({
11109
12075
  });
11110
12076
 
11111
12077
  // src/demo/taxonomy-cache.ts
11112
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync7, mkdirSync as mkdirSync6, unlinkSync } from "fs";
12078
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, existsSync as existsSync10, mkdirSync as mkdirSync8, unlinkSync as unlinkSync3 } from "fs";
11113
12079
  import { homedir as homedir4 } from "os";
11114
- import { join as join7 } from "path";
11115
- function ensureDir3() {
11116
- if (!existsSync7(NTRP_DIR4)) {
11117
- mkdirSync6(NTRP_DIR4, { recursive: true });
12080
+ import { join as join10 } from "path";
12081
+ function ensureDir5() {
12082
+ if (!existsSync10(NTRP_DIR4)) {
12083
+ mkdirSync8(NTRP_DIR4, { recursive: true });
11118
12084
  }
11119
12085
  }
11120
12086
  function loadCachedTaxonomy(profile) {
11121
- if (!existsSync7(TAXONOMY_PATH)) return null;
12087
+ if (!existsSync10(TAXONOMY_PATH)) return null;
11122
12088
  try {
11123
- const parsed = JSON.parse(readFileSync5(TAXONOMY_PATH, "utf-8"));
12089
+ const parsed = JSON.parse(readFileSync8(TAXONOMY_PATH, "utf-8"));
11124
12090
  if (!parsed || typeof parsed !== "object") return null;
11125
12091
  if (parsed.profile_updated_at !== profile.updated_at) return null;
11126
12092
  return parsed;
@@ -11129,15 +12095,15 @@ function loadCachedTaxonomy(profile) {
11129
12095
  }
11130
12096
  }
11131
12097
  function saveCachedTaxonomy(taxonomy) {
11132
- ensureDir3();
11133
- writeFileSync4(TAXONOMY_PATH, JSON.stringify(taxonomy, null, 2) + "\n");
12098
+ ensureDir5();
12099
+ writeFileSync7(TAXONOMY_PATH, JSON.stringify(taxonomy, null, 2) + "\n");
11134
12100
  }
11135
12101
  var NTRP_DIR4, TAXONOMY_PATH;
11136
12102
  var init_taxonomy_cache = __esm({
11137
12103
  "src/demo/taxonomy-cache.ts"() {
11138
12104
  "use strict";
11139
- NTRP_DIR4 = join7(homedir4(), ".ntrp");
11140
- TAXONOMY_PATH = join7(NTRP_DIR4, "demo-taxonomy.json");
12105
+ NTRP_DIR4 = join10(homedir4(), ".ntrp");
12106
+ TAXONOMY_PATH = join10(NTRP_DIR4, "demo-taxonomy.json");
11141
12107
  }
11142
12108
  });
11143
12109
 
@@ -11373,16 +12339,16 @@ var generate_exports = {};
11373
12339
  __export(generate_exports, {
11374
12340
  handler: () => handler2
11375
12341
  });
11376
- import chalk13 from "chalk";
12342
+ import chalk14 from "chalk";
11377
12343
  import ora4 from "ora";
11378
12344
  async function handler2(args, ctx) {
11379
12345
  const { flags } = parseArgs(args, ["list-scenarios", "regen-taxonomy"]);
11380
12346
  const quiet = ctx.execution.quiet;
11381
12347
  if (getBool(flags, "list-scenarios")) {
11382
- console.log(chalk13.bold("\n Available Scenarios:\n"));
12348
+ console.log(chalk14.bold("\n Available Scenarios:\n"));
11383
12349
  for (const s of SCENARIO_LIST) {
11384
- console.log(` ${chalk13.cyan(s.key.padEnd(20))} ${s.label}`);
11385
- console.log(` ${chalk13.dim(" ".repeat(20))} ${s.description}
12350
+ console.log(` ${chalk14.cyan(s.key.padEnd(20))} ${s.label}`);
12351
+ console.log(` ${chalk14.dim(" ".repeat(20))} ${s.description}
11386
12352
  `);
11387
12353
  }
11388
12354
  return true;
@@ -11392,9 +12358,9 @@ async function handler2(args, ctx) {
11392
12358
  const skipProfile = getFalse(flags, "profile");
11393
12359
  if (!isProfileConfigured(profile) && !skipProfile) {
11394
12360
  console.error();
11395
- console.error(" " + chalk13.red("No company profile found."));
11396
- console.error(" " + chalk13.dim("Run ") + paint("accent", "/onboard") + chalk13.dim(" first for a richer demo,"));
11397
- console.error(" " + chalk13.dim("or pass ") + paint("accent", "--no-profile") + chalk13.dim(" to skip."));
12361
+ console.error(" " + chalk14.red("No company profile found."));
12362
+ console.error(" " + chalk14.dim("Run ") + paint("accent", "/onboard") + chalk14.dim(" first for a richer demo,"));
12363
+ console.error(" " + chalk14.dim("or pass ") + paint("accent", "--no-profile") + chalk14.dim(" to skip."));
11398
12364
  console.error();
11399
12365
  markFailure(ctx);
11400
12366
  return false;
@@ -11402,8 +12368,8 @@ async function handler2(args, ctx) {
11402
12368
  const explicitScenario = getString(flags, "scenario", "s");
11403
12369
  const resolvedScenario = resolveScenarioInput(explicitScenario);
11404
12370
  if (resolvedScenario === null) {
11405
- console.error(chalk13.red(` Unknown scenario: ${explicitScenario}`));
11406
- console.log(chalk13.dim(` Valid: ${SCENARIO_LIST.map((s) => s.key).join(", ")}`));
12371
+ console.error(chalk14.red(` Unknown scenario: ${explicitScenario}`));
12372
+ console.log(chalk14.dim(` Valid: ${SCENARIO_LIST.map((s) => s.key).join(", ")}`));
11407
12373
  markFailure(ctx);
11408
12374
  return false;
11409
12375
  }
@@ -11417,7 +12383,7 @@ async function handler2(args, ctx) {
11417
12383
  const s = getScenario(scenario);
11418
12384
  console.log();
11419
12385
  console.log(" " + paint("accent", "\u2713 Scenario: ") + bold(s.label));
11420
- console.log(" " + chalk13.dim(s.story));
12386
+ console.log(" " + chalk14.dim(s.story));
11421
12387
  console.log();
11422
12388
  }
11423
12389
  const spinner = quiet ? null : ora4({ text: "Initializing database...", discardStdin: false }).start();
@@ -11443,17 +12409,17 @@ async function handler2(args, ctx) {
11443
12409
  const result = await generateDemoData(config);
11444
12410
  if (result.mode === "direct") {
11445
12411
  if (spinner) {
11446
- spinner.succeed(`Generated demo data for "${chalk13.cyan(scenario)}" scenario`);
12412
+ spinner.succeed(`Generated demo data for "${chalk14.cyan(scenario)}" scenario`);
11447
12413
  console.log();
11448
12414
  printEntityCounts(result.counts);
11449
12415
  }
11450
12416
  if (!quiet && ctx.analysis.primary !== "revenue_metrics") {
11451
- console.log(chalk13.dim("\n Run /diagnose to compute vital signs.\n"));
12417
+ console.log(chalk14.dim("\n Run /diagnose to compute vital signs.\n"));
11452
12418
  }
11453
12419
  }
11454
12420
  } catch (err) {
11455
12421
  if (spinner) spinner.fail("Generation failed");
11456
- console.error(chalk13.red(String(err)));
12422
+ console.error(chalk14.red(String(err)));
11457
12423
  markFailure(ctx);
11458
12424
  return false;
11459
12425
  }
@@ -11478,7 +12444,7 @@ async function loadOrBuildTaxonomy(profile, forceRegen, ctx) {
11478
12444
  return taxonomy;
11479
12445
  } catch (err) {
11480
12446
  spinner.fail("Couldn't build market taxonomy \u2014 using generic data pools");
11481
- console.log(" " + chalk13.dim(String(err.message ?? err)));
12447
+ console.log(" " + chalk14.dim(String(err.message ?? err)));
11482
12448
  return void 0;
11483
12449
  }
11484
12450
  }
@@ -11568,9 +12534,9 @@ var ingest_exports = {};
11568
12534
  __export(ingest_exports, {
11569
12535
  handler: () => handler3
11570
12536
  });
11571
- import chalk14 from "chalk";
12537
+ import chalk15 from "chalk";
11572
12538
  import ora5 from "ora";
11573
- import { readFileSync as readFileSync6, existsSync as existsSync8 } from "fs";
12539
+ import { readFileSync as readFileSync9, existsSync as existsSync11 } from "fs";
11574
12540
  import { basename as basename2 } from "path";
11575
12541
  async function handler3(args, ctx) {
11576
12542
  const { positional, flags } = parseArgs(args, [
@@ -11590,21 +12556,21 @@ async function handler3(args, ctx) {
11590
12556
  const source = getString(flags, "source", "s") ?? "salesforce";
11591
12557
  const skipResolve = getBool(flags, "skip-resolve");
11592
12558
  if (!file) {
11593
- console.error(chalk14.red(" Usage: /ingest <file> [--source salesforce|hubspot|outreach]"));
11594
- console.error(chalk14.dim(" /ingest --demo [--scenario <name>]"));
12559
+ console.error(chalk15.red(" Usage: /ingest <file> [--source salesforce|hubspot|outreach]"));
12560
+ console.error(chalk15.dim(" /ingest --demo [--scenario <name>]"));
11595
12561
  process.exit(1);
11596
12562
  }
11597
- if (!existsSync8(file)) {
11598
- console.error(chalk14.red(` File not found: ${file}`));
12563
+ if (!existsSync11(file)) {
12564
+ console.error(chalk15.red(` File not found: ${file}`));
11599
12565
  process.exit(1);
11600
12566
  }
11601
12567
  const profile = loadProfile();
11602
12568
  const skipProfile = getFalse(flags, "profile");
11603
12569
  if (!profile && !skipProfile) {
11604
12570
  console.error();
11605
- console.error(" " + chalk14.red("No company profile found."));
11606
- console.error(" " + chalk14.dim("Run ") + paint("accent", "/onboard") + chalk14.dim(" first for better column mapping,"));
11607
- console.error(" " + chalk14.dim("or pass ") + paint("accent", "--no-profile") + chalk14.dim(" to skip."));
12571
+ console.error(" " + chalk15.red("No company profile found."));
12572
+ console.error(" " + chalk15.dim("Run ") + paint("accent", "/onboard") + chalk15.dim(" first for better column mapping,"));
12573
+ console.error(" " + chalk15.dim("or pass ") + paint("accent", "--no-profile") + chalk15.dim(" to skip."));
11608
12574
  console.error();
11609
12575
  process.exit(1);
11610
12576
  }
@@ -11612,7 +12578,7 @@ async function handler3(args, ctx) {
11612
12578
  try {
11613
12579
  await initSchema();
11614
12580
  spinner.text = "Parsing CSV...";
11615
- const content = readFileSync6(file, "utf-8");
12581
+ const content = readFileSync9(file, "utf-8");
11616
12582
  const { rows, headers } = parseCSV(content);
11617
12583
  if (rows.length === 0) {
11618
12584
  spinner.fail("CSV is empty");
@@ -11636,22 +12602,22 @@ async function handler3(args, ctx) {
11636
12602
  row_count: result2.imported
11637
12603
  });
11638
12604
  spinner.succeed(
11639
- `Imported ${chalk14.bold(result2.imported.toString())} revenue events from ${chalk14.dim(basename2(file))}`
12605
+ `Imported ${chalk15.bold(result2.imported.toString())} revenue events from ${chalk15.dim(basename2(file))}`
11640
12606
  );
11641
12607
  if (result2.errors.length > 0) {
11642
- console.log(chalk14.yellow(` ${result2.errors.length} rows skipped`));
12608
+ console.log(chalk15.yellow(` ${result2.errors.length} rows skipped`));
11643
12609
  }
11644
12610
  if (ctx.analysis) {
11645
12611
  ctx.analysis.data_source_type = "revenue_ledger";
11646
12612
  }
11647
- console.log(chalk14.dim(" Run ") + chalk14.cyan("/metrics") + chalk14.dim(" for SaaS metrics with ledger-backed retention."));
12613
+ console.log(chalk15.dim(" Run ") + chalk15.cyan("/metrics") + chalk15.dim(" for SaaS metrics with ledger-backed retention."));
11648
12614
  return `${result2.imported} revenue events from ${basename2(file)}`;
11649
12615
  }
11650
12616
  spinner.text = "Detecting entity type...";
11651
12617
  const detection = detectEntityType(headers, source);
11652
12618
  if (!detection) {
11653
12619
  spinner.fail(`Could not auto-detect entity type for source: ${source}`);
11654
- console.log(chalk14.dim(" Headers found: " + headers.join(", ")));
12620
+ console.log(chalk15.dim(" Headers found: " + headers.join(", ")));
11655
12621
  process.exit(1);
11656
12622
  }
11657
12623
  spinner.text = `Importing ${rows.length} ${detection.entityType} rows...`;
@@ -11674,15 +12640,15 @@ async function handler3(args, ctx) {
11674
12640
  row_count: result.imported
11675
12641
  });
11676
12642
  spinner.succeed(
11677
- `Imported ${chalk14.bold(result.imported.toString())} ${detection.entityType} from ${chalk14.dim(basename2(file))} (${source})`
12643
+ `Imported ${chalk15.bold(result.imported.toString())} ${detection.entityType} from ${chalk15.dim(basename2(file))} (${source})`
11678
12644
  );
11679
12645
  if (result.errors.length > 0) {
11680
- console.log(chalk14.yellow(` ${result.errors.length} rows skipped`));
12646
+ console.log(chalk15.yellow(` ${result.errors.length} rows skipped`));
11681
12647
  for (const err of result.errors.slice(0, 3)) {
11682
- console.log(chalk14.dim(` - ${err}`));
12648
+ console.log(chalk15.dim(` - ${err}`));
11683
12649
  }
11684
12650
  if (result.errors.length > 3) {
11685
- console.log(chalk14.dim(` ... and ${result.errors.length - 3} more`));
12651
+ console.log(chalk15.dim(` ... and ${result.errors.length - 3} more`));
11686
12652
  }
11687
12653
  }
11688
12654
  if (!skipResolve) {
@@ -11699,7 +12665,7 @@ async function handler3(args, ctx) {
11699
12665
  return `${result.imported} ${detection.entityType} from ${basename2(file)}`;
11700
12666
  } catch (err) {
11701
12667
  spinner.fail("Import failed");
11702
- console.error(chalk14.red(String(err)));
12668
+ console.error(chalk15.red(String(err)));
11703
12669
  process.exit(1);
11704
12670
  }
11705
12671
  }
@@ -11728,10 +12694,10 @@ __export(ingest_chat_exports, {
11728
12694
  loadDemoFromChat: () => loadDemoFromChat,
11729
12695
  looksLikeFilePath: () => looksLikeFilePath
11730
12696
  });
11731
- import { existsSync as existsSync9 } from "fs";
12697
+ import { existsSync as existsSync12 } from "fs";
11732
12698
  import { basename as basename3, resolve as resolve4 } from "path";
11733
12699
  import { homedir as homedir5 } from "os";
11734
- import chalk15 from "chalk";
12700
+ import chalk16 from "chalk";
11735
12701
  function extractFilePath(input) {
11736
12702
  const trimmed = input.trim();
11737
12703
  const patterns = [
@@ -11748,11 +12714,11 @@ function extractFilePath(input) {
11748
12714
  const m = trimmed.match(re);
11749
12715
  if (m?.[1]) {
11750
12716
  const p = expandPath(m[1]);
11751
- if (existsSync9(p)) return p;
12717
+ if (existsSync12(p)) return p;
11752
12718
  }
11753
12719
  if (!m?.[1] && re.test(trimmed) && trimmed.toLowerCase().endsWith(".csv")) {
11754
12720
  const p = expandPath(trimmed.replace(/^["']|["']$/g, ""));
11755
- if (existsSync9(p)) return p;
12721
+ if (existsSync12(p)) return p;
11756
12722
  }
11757
12723
  }
11758
12724
  return null;
@@ -11766,7 +12732,7 @@ function looksLikeFilePath(input) {
11766
12732
  }
11767
12733
  async function ingestFromChat(ctx, filePath) {
11768
12734
  if (!ctx.rl) {
11769
- console.log(" " + chalk15.red("Ingest confirm requires interactive mode."));
12735
+ console.log(" " + chalk16.red("Ingest confirm requires interactive mode."));
11770
12736
  return false;
11771
12737
  }
11772
12738
  const name = basename3(filePath);
@@ -11774,7 +12740,7 @@ async function ingestFromChat(ctx, filePath) {
11774
12740
  try {
11775
12741
  const ok = await prompts.confirm(`Ingest ${name} as CRM export?`, true);
11776
12742
  if (!ok) {
11777
- console.log(" " + chalk15.dim("Ingest cancelled."));
12743
+ console.log(" " + chalk16.dim("Ingest cancelled."));
11778
12744
  return false;
11779
12745
  }
11780
12746
  } finally {
@@ -11782,12 +12748,12 @@ async function ingestFromChat(ctx, filePath) {
11782
12748
  }
11783
12749
  const { handler: ingest } = await Promise.resolve().then(() => (init_ingest(), ingest_exports));
11784
12750
  const { detectEntityType: detectEntityType2 } = await Promise.resolve().then(() => (init_csv_detect(), csv_detect_exports));
11785
- const { readFileSync: readFileSync12 } = await import("fs");
12751
+ const { readFileSync: readFileSync15 } = await import("fs");
11786
12752
  const { parseCSV: parseCSV2 } = await Promise.resolve().then(() => (init_csv_parse(), csv_parse_exports));
11787
12753
  const { getStoredApiKey } = await Promise.resolve().then(() => (init_repl_api(), repl_api_exports));
11788
12754
  let headerCheckFailed = false;
11789
12755
  try {
11790
- const raw = readFileSync12(filePath, "utf-8");
12756
+ const raw = readFileSync15(filePath, "utf-8");
11791
12757
  const { headers } = parseCSV2(raw);
11792
12758
  const detected = detectEntityType2(headers, "unknown");
11793
12759
  if (!detected) headerCheckFailed = true;
@@ -11802,7 +12768,7 @@ async function ingestFromChat(ctx, filePath) {
11802
12768
  false
11803
12769
  );
11804
12770
  if (useAi) {
11805
- console.log(" " + chalk15.dim("AI column mapping is not wired to ingest yet \u2014 trying standard ingest."));
12771
+ console.log(" " + chalk16.dim("AI column mapping is not wired to ingest yet \u2014 trying standard ingest."));
11806
12772
  }
11807
12773
  } finally {
11808
12774
  prompts2.close();
@@ -11829,7 +12795,7 @@ async function ingestFromChat(ctx, filePath) {
11829
12795
  invalidateGapAudit(ctx);
11830
12796
  saveSessionState(ctx);
11831
12797
  console.log();
11832
- console.log(" " + paint("accent", "\u2713 Data loaded") + chalk15.dim(` \u2014 ${name}`));
12798
+ console.log(" " + paint("accent", "\u2713 Data loaded") + chalk16.dim(` \u2014 ${name}`));
11833
12799
  recordMessage(ctx, "user", `[ingested ${name}]`);
11834
12800
  recordMessage(ctx, "agent", `Loaded ${name}. Checking what we can analyze\u2026`);
11835
12801
  const audit = await refreshGapAudit(ctx);
@@ -13134,24 +14100,24 @@ JSON SHAPE:
13134
14100
 
13135
14101
  // src/strategies/readers.ts
13136
14102
  import { createHash } from "crypto";
13137
- import { existsSync as existsSync10, readFileSync as readFileSync7 } from "fs";
14103
+ import { existsSync as existsSync13, readFileSync as readFileSync10 } from "fs";
13138
14104
  import { extname, resolve as resolve5 } from "path";
13139
14105
  import { parse as parseYaml } from "yaml";
13140
14106
  import { PDFParse } from "pdf-parse";
13141
14107
  async function readStrategyFile(pathOrDash) {
13142
14108
  if (pathOrDash === "-") {
13143
- const text2 = readFileSync7(0, "utf-8");
14109
+ const text2 = readFileSync10(0, "utf-8");
13144
14110
  return createDocument("stdin", null, text2, {});
13145
14111
  }
13146
14112
  const sourcePath = resolve5(pathOrDash);
13147
- if (!existsSync10(sourcePath)) {
14113
+ if (!existsSync13(sourcePath)) {
13148
14114
  throw new NtrpError("strategy_file_not_found", `Strategy file not found: ${pathOrDash}`, 2 /* Usage */);
13149
14115
  }
13150
14116
  const ext = extname(sourcePath).toLowerCase();
13151
14117
  if (ext === ".pdf") {
13152
14118
  return readPdf(sourcePath);
13153
14119
  }
13154
- const text = readFileSync7(sourcePath, "utf-8");
14120
+ const text = readFileSync10(sourcePath, "utf-8");
13155
14121
  if (ext === ".yaml" || ext === ".yml") {
13156
14122
  const structured = parseStructuredYaml(text);
13157
14123
  return createDocument("yaml", sourcePath, text, structured);
@@ -13166,7 +14132,7 @@ function readStrategyText(text) {
13166
14132
  return createDocument("text", null, text, {});
13167
14133
  }
13168
14134
  async function readPdf(sourcePath) {
13169
- const data = readFileSync7(sourcePath);
14135
+ const data = readFileSync10(sourcePath);
13170
14136
  const parser = new PDFParse({ data });
13171
14137
  try {
13172
14138
  const result = await parser.getText();
@@ -13213,15 +14179,15 @@ var init_readers = __esm({
13213
14179
  });
13214
14180
 
13215
14181
  // src/strategies/library.ts
13216
- import { writeFileSync as writeFileSync5 } from "fs";
13217
- import { join as join8 } from "path";
14182
+ import { writeFileSync as writeFileSync8 } from "fs";
14183
+ import { join as join11 } from "path";
13218
14184
  import { stringify as stringifyYaml } from "yaml";
13219
14185
  function strategyLibraryPath(slug) {
13220
- return join8(getStrategiesDir(), `${slug}.md`);
14186
+ return join11(getStrategiesDir(), `${slug}.md`);
13221
14187
  }
13222
14188
  function writeStrategyMarkdown(strategy) {
13223
14189
  const path = strategyLibraryPath(strategy.slug);
13224
- writeFileSync5(path, renderStrategyMarkdown(strategy), "utf-8");
14190
+ writeFileSync8(path, renderStrategyMarkdown(strategy), "utf-8");
13225
14191
  return path;
13226
14192
  }
13227
14193
  function renderStrategyMarkdown(strategy) {
@@ -13295,7 +14261,7 @@ var init_library = __esm({
13295
14261
  // src/strategies/connectors.ts
13296
14262
  import { readdirSync as readdirSync2, statSync as statSync2 } from "fs";
13297
14263
  import { homedir as homedir6 } from "os";
13298
- import { basename as basename4, extname as extname2, join as join9, relative, resolve as resolve6, sep as sep2 } from "path";
14264
+ import { basename as basename4, extname as extname2, join as join12, relative, resolve as resolve6, sep as sep2 } from "path";
13299
14265
  function createLocalFolderConnector(options) {
13300
14266
  const rootPath = resolveUserPath(options.rootPath);
13301
14267
  const name = options.name ?? (basename4(rootPath) || "local");
@@ -13338,7 +14304,7 @@ function createLocalFolderConnector(options) {
13338
14304
  }
13339
14305
  function walkLocalFolder(rootPath, currentPath, refs, opts) {
13340
14306
  for (const entry of readdirSync2(currentPath, { withFileTypes: true })) {
13341
- const absolutePath = join9(currentPath, entry.name);
14307
+ const absolutePath = join12(currentPath, entry.name);
13342
14308
  const relativePath = normalizePath(relative(rootPath, absolutePath));
13343
14309
  if (entry.isDirectory()) {
13344
14310
  if (shouldSkipDirectory(entry.name) || matchesAny(relativePath, opts.excludePatterns)) continue;
@@ -13402,7 +14368,7 @@ function normalizePath(path) {
13402
14368
  }
13403
14369
  function resolveUserPath(path) {
13404
14370
  if (path === "~") return homedir6();
13405
- if (path.startsWith("~/")) return join9(homedir6(), path.slice(2));
14371
+ if (path.startsWith("~/")) return join12(homedir6(), path.slice(2));
13406
14372
  return resolve6(path);
13407
14373
  }
13408
14374
  var DEFAULT_MAX_FILES, DEFAULT_MAX_BYTES, SUPPORTED_EXTENSIONS, DEFAULT_EXCLUDED_DIRS;
@@ -13818,17 +14784,17 @@ var init_retrieval = __esm({
13818
14784
  });
13819
14785
 
13820
14786
  // src/memory/knowledge.ts
13821
- import { existsSync as existsSync11, readFileSync as readFileSync8, appendFileSync as appendFileSync3, readdirSync as readdirSync3 } from "fs";
13822
- import { join as join12 } from "path";
13823
- import { randomUUID as randomUUID3 } from "crypto";
14787
+ import { existsSync as existsSync14, readFileSync as readFileSync11, appendFileSync as appendFileSync3, readdirSync as readdirSync3 } from "fs";
14788
+ import { join as join15 } from "path";
14789
+ import { randomUUID as randomUUID4 } from "crypto";
13824
14790
  function knowledgePath() {
13825
- return join12(getMemoryDir(), KNOWLEDGE_FILE);
14791
+ return join15(getMemoryDir(), KNOWLEDGE_FILE);
13826
14792
  }
13827
14793
  function loadKnowledgeChunks() {
13828
14794
  const path = knowledgePath();
13829
- if (!existsSync11(path)) return [];
14795
+ if (!existsSync14(path)) return [];
13830
14796
  const out = [];
13831
- for (const line of readFileSync8(path, "utf-8").split("\n")) {
14797
+ for (const line of readFileSync11(path, "utf-8").split("\n")) {
13832
14798
  const trimmed = line.trim();
13833
14799
  if (!trimmed) continue;
13834
14800
  try {
@@ -13861,17 +14827,17 @@ __export(store_exports2, {
13861
14827
  rewriteJsonl: () => rewriteJsonl,
13862
14828
  scrubText: () => scrubText
13863
14829
  });
13864
- import { existsSync as existsSync12, readFileSync as readFileSync9, appendFileSync as appendFileSync4, readdirSync as readdirSync4, writeFileSync as writeFileSync7 } from "fs";
13865
- import { join as join13 } from "path";
13866
- import { randomUUID as randomUUID4 } from "crypto";
14830
+ import { existsSync as existsSync15, readFileSync as readFileSync12, appendFileSync as appendFileSync4, readdirSync as readdirSync4, writeFileSync as writeFileSync10 } from "fs";
14831
+ import { join as join16 } from "path";
14832
+ import { randomUUID as randomUUID5 } from "crypto";
13867
14833
  function memPath(file) {
13868
- return join13(getMemoryDir(), file);
14834
+ return join16(getMemoryDir(), file);
13869
14835
  }
13870
14836
  function readJsonl(file) {
13871
14837
  const path = memPath(file);
13872
- if (!existsSync12(path)) return [];
14838
+ if (!existsSync15(path)) return [];
13873
14839
  const out = [];
13874
- for (const line of readFileSync9(path, "utf-8").split("\n")) {
14840
+ for (const line of readFileSync12(path, "utf-8").split("\n")) {
13875
14841
  const trimmed = line.trim();
13876
14842
  if (!trimmed) continue;
13877
14843
  try {
@@ -13889,7 +14855,7 @@ function appendJsonl(file, obj) {
13889
14855
  }
13890
14856
  function rewriteJsonl(file, rows) {
13891
14857
  try {
13892
- writeFileSync7(memPath(file), rows.map((r) => JSON.stringify(r)).join("\n") + (rows.length ? "\n" : ""));
14858
+ writeFileSync10(memPath(file), rows.map((r) => JSON.stringify(r)).join("\n") + (rows.length ? "\n" : ""));
13893
14859
  } catch {
13894
14860
  }
13895
14861
  }
@@ -13898,7 +14864,7 @@ function scrubText(text) {
13898
14864
  }
13899
14865
  function addFact(input) {
13900
14866
  const fact = {
13901
- id: randomUUID4(),
14867
+ id: randomUUID5(),
13902
14868
  text: scrubText(input.text),
13903
14869
  kind: input.kind ?? "fact",
13904
14870
  source: input.source ?? "user",
@@ -13919,7 +14885,7 @@ function summarizeAnswer(answer) {
13919
14885
  }
13920
14886
  function recordAnalysis(input) {
13921
14887
  const entry = {
13922
- id: randomUUID4(),
14888
+ id: randomUUID5(),
13923
14889
  question: scrubText(input.question).slice(0, 300),
13924
14890
  summary: scrubText(summarizeAnswer(input.answer)),
13925
14891
  tools: input.tools,
@@ -13951,7 +14917,7 @@ function loadWinSnippets() {
13951
14917
  const out = [];
13952
14918
  for (const name of readdirSync4(dir)) {
13953
14919
  if (!name.endsWith(".md") || name.toLowerCase() === "readme.md") continue;
13954
- const raw = readFileSync9(join13(dir, name), "utf-8");
14920
+ const raw = readFileSync12(join16(dir, name), "utf-8");
13955
14921
  const title = raw.match(/^#\s+(.+)$/m)?.[1]?.trim() ?? name.replace(/\.md$/, "");
13956
14922
  const body = raw.replace(/^#.*$/m, "").replace(/\s+/g, " ").trim().slice(0, 300);
13957
14923
  out.push({ id: `win:${name}`, title, text: `${title}. ${body}` });
@@ -14125,7 +15091,7 @@ init_session_analysis();
14125
15091
  init_queries();
14126
15092
  init_diagnosis();
14127
15093
  init_strategy();
14128
- import { join as join11 } from "path";
15094
+ import { join as join14 } from "path";
14129
15095
 
14130
15096
  // src/services/publish.ts
14131
15097
  init_queries();
@@ -14290,8 +15256,8 @@ function buildSections(input) {
14290
15256
 
14291
15257
  // src/repositories/markdown.ts
14292
15258
  init_formatters();
14293
- import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync6 } from "fs";
14294
- import { basename as basename5, dirname as dirname2, join as join10, resolve as resolve7 } from "path";
15259
+ import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync9 } from "fs";
15260
+ import { basename as basename5, dirname as dirname2, join as join13, resolve as resolve7 } from "path";
14295
15261
  import { stringify as stringifyYaml2 } from "yaml";
14296
15262
  var markdownRepositoryAdapter = {
14297
15263
  kind: "markdown",
@@ -14313,12 +15279,12 @@ var markdownRepositoryAdapter = {
14313
15279
  write(pkg) {
14314
15280
  const root = getRootPath(pkg.target);
14315
15281
  const files = renderMarkdownFiles(pkg);
14316
- mkdirSync7(root, { recursive: true });
15282
+ mkdirSync9(root, { recursive: true });
14317
15283
  const written = [];
14318
15284
  for (const file of files) {
14319
- const absolutePath = join10(root, file.relativePath);
14320
- mkdirSync7(dirname2(absolutePath), { recursive: true });
14321
- writeFileSync6(absolutePath, file.contents, "utf-8");
15285
+ const absolutePath = join13(root, file.relativePath);
15286
+ mkdirSync9(dirname2(absolutePath), { recursive: true });
15287
+ writeFileSync9(absolutePath, file.contents, "utf-8");
14322
15288
  written.push(absolutePath);
14323
15289
  }
14324
15290
  return {
@@ -14634,7 +15600,7 @@ async function runSmokeProtocol(_input, ctx) {
14634
15600
  });
14635
15601
  const proposalResult = await proposeRepositoryExport({
14636
15602
  target: "markdown",
14637
- directory: join11(getExportsDir(), "repository-smoke"),
15603
+ directory: join14(getExportsDir(), "repository-smoke"),
14638
15604
  source: "smoke_protocol",
14639
15605
  modelOrFixture: "smoke-protocol-v1"
14640
15606
  });
@@ -14801,8 +15767,8 @@ init_repl_api();
14801
15767
  init_llm_config();
14802
15768
  init_store();
14803
15769
  init_profile();
14804
- import { existsSync as existsSync13, mkdirSync as mkdirSync8, readFileSync as readFileSync10, writeFileSync as writeFileSync8 } from "fs";
14805
- import { join as join14 } from "path";
15770
+ import { existsSync as existsSync16, mkdirSync as mkdirSync10, readFileSync as readFileSync13, writeFileSync as writeFileSync11 } from "fs";
15771
+ import { join as join17 } from "path";
14806
15772
 
14807
15773
  // src/license/verify.ts
14808
15774
  init_store();
@@ -14811,6 +15777,7 @@ import { createHmac } from "crypto";
14811
15777
  // src/license/trial-policy.ts
14812
15778
  var TRIAL_FULL_DAYS = 11;
14813
15779
  var TRIAL_GRACE_END_DAYS = 30;
15780
+ var TRIAL_ACTIVE_NUDGE_FROM_DAY = 8;
14814
15781
  function evaluateTrial(activatedAt, now2 = /* @__PURE__ */ new Date()) {
14815
15782
  const daysSince = Math.floor((now2.getTime() - activatedAt.getTime()) / 864e5);
14816
15783
  if (daysSince >= TRIAL_GRACE_END_DAYS) {
@@ -14818,6 +15785,7 @@ function evaluateTrial(activatedAt, now2 = /* @__PURE__ */ new Date()) {
14818
15785
  phase: "expired",
14819
15786
  daysSinceActivation: daysSince,
14820
15787
  daysUntilLockout: 0,
15788
+ trialDaysRemaining: 0,
14821
15789
  shouldNudge: false
14822
15790
  };
14823
15791
  }
@@ -14826,14 +15794,17 @@ function evaluateTrial(activatedAt, now2 = /* @__PURE__ */ new Date()) {
14826
15794
  phase: "grace",
14827
15795
  daysSinceActivation: daysSince,
14828
15796
  daysUntilLockout: TRIAL_GRACE_END_DAYS - daysSince,
15797
+ trialDaysRemaining: 0,
14829
15798
  shouldNudge: true
14830
15799
  };
14831
15800
  }
15801
+ const trialDaysRemaining = TRIAL_FULL_DAYS - daysSince;
14832
15802
  return {
14833
15803
  phase: "active",
14834
15804
  daysSinceActivation: daysSince,
14835
15805
  daysUntilLockout: TRIAL_GRACE_END_DAYS - daysSince,
14836
- shouldNudge: false
15806
+ trialDaysRemaining,
15807
+ shouldNudge: daysSince >= TRIAL_ACTIVE_NUDGE_FROM_DAY
14837
15808
  };
14838
15809
  }
14839
15810
  function formatTrialActiveMessage(daysSince) {
@@ -14930,7 +15901,8 @@ function applyTrialPolicy(result) {
14930
15901
  message: randomCutoffNudge(),
14931
15902
  trialPhase: "expired",
14932
15903
  shouldNudgeUpgrade: false,
14933
- daysUntilLockout: 0
15904
+ daysUntilLockout: 0,
15905
+ trialDaysRemaining: 0
14934
15906
  };
14935
15907
  }
14936
15908
  const message = trial.phase === "grace" ? `trial license (grace \u2014 ${trial.daysUntilLockout} day${trial.daysUntilLockout === 1 ? "" : "s"} until lockout)` : formatTrialActiveMessage(trial.daysSinceActivation);
@@ -14939,7 +15911,8 @@ function applyTrialPolicy(result) {
14939
15911
  message,
14940
15912
  trialPhase: trial.phase,
14941
15913
  shouldNudgeUpgrade: trial.shouldNudge,
14942
- daysUntilLockout: trial.daysUntilLockout
15914
+ daysUntilLockout: trial.daysUntilLockout,
15915
+ trialDaysRemaining: trial.trialDaysRemaining
14943
15916
  };
14944
15917
  }
14945
15918
  function storedLicenseProvider(key) {
@@ -14991,9 +15964,9 @@ function setupCheck() {
14991
15964
  const home = ntrpHome();
14992
15965
  let writable = false;
14993
15966
  try {
14994
- mkdirSync8(home, { recursive: true });
14995
- const probe = join14(home, ".write-check");
14996
- writeFileSync8(probe, "ok\n");
15967
+ mkdirSync10(home, { recursive: true });
15968
+ const probe = join17(home, ".write-check");
15969
+ writeFileSync11(probe, "ok\n");
14997
15970
  writable = true;
14998
15971
  } catch {
14999
15972
  writable = false;
@@ -15037,18 +16010,18 @@ init_serialize();
15037
16010
  init_errors2();
15038
16011
 
15039
16012
  // src/version.ts
15040
- import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
15041
- import { dirname as dirname3, join as join15 } from "path";
16013
+ import { existsSync as existsSync17, readFileSync as readFileSync14 } from "fs";
16014
+ import { dirname as dirname3, join as join18 } from "path";
15042
16015
  import { fileURLToPath } from "url";
15043
16016
  var cachedVersion;
15044
16017
  function getInstalledVersion() {
15045
16018
  if (cachedVersion) return cachedVersion;
15046
16019
  const start = dirname3(fileURLToPath(import.meta.url));
15047
16020
  for (const rel of ["../package.json", "../../package.json"]) {
15048
- const path = join15(start, rel);
15049
- if (!existsSync14(path)) continue;
16021
+ const path = join18(start, rel);
16022
+ if (!existsSync17(path)) continue;
15050
16023
  try {
15051
- const pkg = JSON.parse(readFileSync11(path, "utf-8"));
16024
+ const pkg = JSON.parse(readFileSync14(path, "utf-8"));
15052
16025
  if (typeof pkg.version === "string" && pkg.version.length > 0) {
15053
16026
  cachedVersion = pkg.version;
15054
16027
  return cachedVersion;