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