kerf-cli 0.1.3 → 0.1.5

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 CHANGED
@@ -576,10 +576,13 @@ function Dashboard({ sessionFilePath, interval }) {
576
576
  const timer = setInterval(refresh, interval);
577
577
  return () => clearInterval(timer);
578
578
  }, [sessionFilePath, interval]);
579
- useInput((input) => {
580
- if (input === "q") exit();
581
- if (input === "b") setShowBudget((prev) => !prev);
582
- });
579
+ useInput(
580
+ (input) => {
581
+ if (input === "q") exit();
582
+ if (input === "b") setShowBudget((prev) => !prev);
583
+ },
584
+ { isActive: process.stdin.isTTY ?? false }
585
+ );
583
586
  if (!session || session.messages.length === 0) {
584
587
  return /* @__PURE__ */ jsx3(Box3, { paddingX: 1, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Waiting for session data..." }) });
585
588
  }
@@ -659,6 +662,12 @@ function registerWatchCommand(program2) {
659
662
  );
660
663
  process.exit(0);
661
664
  }
665
+ if (!process.stdin.isTTY) {
666
+ console.log(
667
+ "kerf-cli watch requires an interactive terminal (TTY). Run it directly in a terminal tab, not piped."
668
+ );
669
+ process.exit(1);
670
+ }
662
671
  const { waitUntilExit } = render(
663
672
  React2.createElement(Dashboard, { sessionFilePath, interval })
664
673
  );
@@ -670,6 +679,7 @@ function registerWatchCommand(program2) {
670
679
  import React3 from "react";
671
680
  import { render as render2 } from "ink";
672
681
  import { glob as glob2 } from "glob";
682
+ import chalk from "chalk";
673
683
 
674
684
  // src/core/estimator.ts
675
685
  import { glob } from "glob";
@@ -859,17 +869,42 @@ function registerEstimateCommand(program2) {
859
869
  }
860
870
  if (opts.compare) {
861
871
  const models = ["sonnet", "opus", "haiku"];
862
- for (const model of models) {
863
- const estimate2 = await estimateTaskCost(task, { model, files, cwd: process.cwd() });
864
- if (opts.json) {
872
+ if (opts.json) {
873
+ for (const model of models) {
874
+ const estimate2 = await estimateTaskCost(task, { model, files, cwd: process.cwd() });
865
875
  console.log(JSON.stringify(estimate2, null, 2));
866
- } else {
867
- const { waitUntilExit: waitUntilExit2 } = render2(
868
- React3.createElement(EstimateCard, { task, estimate: estimate2 })
869
- );
870
- await waitUntilExit2();
871
876
  }
877
+ return;
872
878
  }
879
+ const estimates = await Promise.all(
880
+ models.map(async (model) => ({
881
+ model,
882
+ estimate: await estimateTaskCost(task, { model, files, cwd: process.cwd() })
883
+ }))
884
+ );
885
+ console.log(chalk.bold.cyan(`
886
+ kerf-cli estimate: '${task}'
887
+ `));
888
+ console.log(
889
+ ` ${"Model".padEnd(10)} ${"Turns".padEnd(14)} ${"Low".padEnd(12)} ${"Expected".padEnd(12)} ${"High".padEnd(12)}`
890
+ );
891
+ console.log(" " + "-".repeat(58));
892
+ for (const { model, estimate: estimate2 } of estimates) {
893
+ const turns = `${estimate2.estimatedTurns.low}-${estimate2.estimatedTurns.high}`;
894
+ console.log(
895
+ ` ${model.padEnd(10)} ${turns.padEnd(14)} ${chalk.green(estimate2.estimatedCost.low.padEnd(12))} ${chalk.yellow(estimate2.estimatedCost.expected.padEnd(12))} ${chalk.red(estimate2.estimatedCost.high.padEnd(12))}`
896
+ );
897
+ }
898
+ console.log();
899
+ const cheapest = estimates[2];
900
+ const priciest = estimates[1];
901
+ console.log(
902
+ chalk.dim(` Cheapest: ${cheapest.model} at ${cheapest.estimate.estimatedCost.expected}`)
903
+ );
904
+ console.log(
905
+ chalk.dim(` Priciest: ${priciest.model} at ${priciest.estimate.estimatedCost.expected}`)
906
+ );
907
+ console.log();
873
908
  return;
874
909
  }
875
910
  const estimate = await estimateTaskCost(task, {
@@ -889,7 +924,7 @@ function registerEstimateCommand(program2) {
889
924
  }
890
925
 
891
926
  // src/cli/commands/budget.ts
892
- import chalk from "chalk";
927
+ import chalk2 from "chalk";
893
928
 
894
929
  // src/core/budgetManager.ts
895
930
  import dayjs3 from "dayjs";
@@ -1095,12 +1130,12 @@ function registerBudgetCommand(program2) {
1095
1130
  const projectPath = opts.project || process.cwd();
1096
1131
  const amountNum = parseFloat(amount);
1097
1132
  if (isNaN(amountNum) || amountNum <= 0) {
1098
- console.log(chalk.red("Budget amount must be a positive number."));
1133
+ console.log(chalk2.red("Budget amount must be a positive number."));
1099
1134
  process.exit(1);
1100
1135
  }
1101
1136
  manager.setBudget(projectPath, amountNum, opts.period);
1102
1137
  console.log(
1103
- chalk.green(`Budget set: ${formatCost(amountNum)}/${opts.period} for ${projectPath}`)
1138
+ chalk2.green(`Budget set: ${formatCost(amountNum)}/${opts.period} for ${projectPath}`)
1104
1139
  );
1105
1140
  manager.close();
1106
1141
  });
@@ -1123,14 +1158,14 @@ function registerBudgetCommand(program2) {
1123
1158
  const filled = Math.round(Math.min(pct, 100) / 100 * barWidth);
1124
1159
  const empty = barWidth - filled;
1125
1160
  const color = pct < 50 ? "green" : pct < 80 ? "yellow" : "red";
1126
- const barColor = color === "green" ? chalk.green : color === "yellow" ? chalk.yellow : chalk.red;
1127
- console.log(chalk.bold.cyan("\n kerf-cli budget\n"));
1161
+ const barColor = color === "green" ? chalk2.green : color === "yellow" ? chalk2.yellow : chalk2.red;
1162
+ console.log(chalk2.bold.cyan("\n kerf-cli budget\n"));
1128
1163
  console.log(` Period: ${status.period} (${status.periodStart.slice(0, 10)} to ${status.periodEnd.slice(0, 10)})`);
1129
1164
  console.log(` Budget: ${formatCost(status.budget)}`);
1130
1165
  console.log(` Spent: ${barColor(formatCost(status.spent))}`);
1131
1166
  console.log(` ${barColor("[" + "\u2588".repeat(filled) + "\u2591".repeat(empty) + "]")} ${pct.toFixed(1)}%`);
1132
1167
  if (status.isOverBudget) {
1133
- console.log(chalk.red.bold(`
1168
+ console.log(chalk2.red.bold(`
1134
1169
  OVER BUDGET by ${formatCost(status.spent - status.budget)}`));
1135
1170
  }
1136
1171
  console.log();
@@ -1144,12 +1179,12 @@ function registerBudgetCommand(program2) {
1144
1179
  manager.close();
1145
1180
  return;
1146
1181
  }
1147
- console.log(chalk.bold.cyan("\n kerf-cli budget list\n"));
1182
+ console.log(chalk2.bold.cyan("\n kerf-cli budget list\n"));
1148
1183
  for (const p of projects) {
1149
1184
  const budgetStr = p.budget ? `${formatCost(p.budget)}/${p.period}` : "no budget";
1150
1185
  const spentStr = p.spent > 0 ? ` (spent: ${formatCost(p.spent)})` : "";
1151
- console.log(` ${chalk.bold(p.name)} \u2014 ${budgetStr}${spentStr}`);
1152
- console.log(chalk.dim(` ${p.path}`));
1186
+ console.log(` ${chalk2.bold(p.name)} \u2014 ${budgetStr}${spentStr}`);
1187
+ console.log(chalk2.dim(` ${p.path}`));
1153
1188
  }
1154
1189
  console.log();
1155
1190
  manager.close();
@@ -1159,7 +1194,7 @@ function registerBudgetCommand(program2) {
1159
1194
  const projectPath = opts.project || process.cwd();
1160
1195
  const removed = manager.removeBudget(projectPath);
1161
1196
  if (removed) {
1162
- console.log(chalk.green("Budget removed."));
1197
+ console.log(chalk2.green("Budget removed."));
1163
1198
  } else {
1164
1199
  console.log("No budget found for this project.");
1165
1200
  }
@@ -1168,7 +1203,7 @@ function registerBudgetCommand(program2) {
1168
1203
  }
1169
1204
 
1170
1205
  // src/cli/commands/audit.ts
1171
- import chalk2 from "chalk";
1206
+ import chalk3 from "chalk";
1172
1207
 
1173
1208
  // src/audit/ghostTokens.ts
1174
1209
  function calculateGrade(percentUsable) {
@@ -1431,14 +1466,14 @@ function registerAuditCommand(program2) {
1431
1466
  console.log(JSON.stringify(result, null, 2));
1432
1467
  return;
1433
1468
  }
1434
- const gradeColor = result.grade === "A" ? chalk2.green : result.grade === "B" ? chalk2.yellow : chalk2.red;
1435
- console.log(chalk2.bold.cyan("\n kerf-cli audit report\n"));
1469
+ const gradeColor = result.grade === "A" ? chalk3.green : result.grade === "B" ? chalk3.yellow : chalk3.red;
1470
+ console.log(chalk3.bold.cyan("\n kerf-cli audit report\n"));
1436
1471
  console.log(
1437
1472
  ` Context Window Health: ${gradeColor.bold(result.grade)} (${result.contextOverhead.percentUsable.toFixed(0)}% usable)
1438
1473
  `
1439
1474
  );
1440
1475
  if (!opts.claudeMdOnly) {
1441
- console.log(chalk2.bold(" Ghost Token Breakdown:"));
1476
+ console.log(chalk3.bold(" Ghost Token Breakdown:"));
1442
1477
  const oh = result.contextOverhead;
1443
1478
  const fmt = (label, tokens) => {
1444
1479
  const pct = (tokens / CONTEXT_WINDOW_SIZE * 100).toFixed(1);
@@ -1458,27 +1493,27 @@ function registerAuditCommand(program2) {
1458
1493
  }
1459
1494
  if (!opts.mcpOnly && result.claudeMdAnalysis) {
1460
1495
  const cma = result.claudeMdAnalysis;
1461
- console.log(chalk2.bold(" CLAUDE.md Analysis:"));
1496
+ console.log(chalk3.bold(" CLAUDE.md Analysis:"));
1462
1497
  console.log(
1463
- ` Lines: ${cma.totalLines}${cma.isOverLineLimit ? chalk2.yellow(" (over 200 limit)") : ""}`
1498
+ ` Lines: ${cma.totalLines}${cma.isOverLineLimit ? chalk3.yellow(" (over 200 limit)") : ""}`
1464
1499
  );
1465
1500
  console.log(` Tokens: ${cma.totalTokens.toLocaleString()}`);
1466
1501
  console.log(
1467
- ` Critical rules in dead zone: ${cma.criticalRulesInDeadZone > 0 ? chalk2.red(String(cma.criticalRulesInDeadZone)) : "0"}`
1502
+ ` Critical rules in dead zone: ${cma.criticalRulesInDeadZone > 0 ? chalk3.red(String(cma.criticalRulesInDeadZone)) : "0"}`
1468
1503
  );
1469
1504
  if (opts.claudeMdOnly) {
1470
1505
  console.log();
1471
- console.log(chalk2.bold(" Sections:"));
1506
+ console.log(chalk3.bold(" Sections:"));
1472
1507
  for (const section of cma.sections) {
1473
- const zone = section.attentionZone === "low-middle" ? chalk2.red(" [dead zone]") : chalk2.green(" [high attention]");
1474
- const critical = section.hasCriticalRules ? chalk2.yellow(" *critical rules*") : "";
1508
+ const zone = section.attentionZone === "low-middle" ? chalk3.red(" [dead zone]") : chalk3.green(" [high attention]");
1509
+ const critical = section.hasCriticalRules ? chalk3.yellow(" *critical rules*") : "";
1475
1510
  console.log(
1476
1511
  ` ${section.title.padEnd(30)} ${String(section.tokens).padStart(5)} tokens L${section.lineStart}-${section.lineEnd}${zone}${critical}`
1477
1512
  );
1478
1513
  }
1479
1514
  if (cma.suggestedReorder.length > 0) {
1480
1515
  console.log();
1481
- console.log(chalk2.bold(" Suggested section order:"));
1516
+ console.log(chalk3.bold(" Suggested section order:"));
1482
1517
  cma.suggestedReorder.forEach((title, i) => {
1483
1518
  console.log(` ${i + 1}. ${title}`);
1484
1519
  });
@@ -1486,42 +1521,42 @@ function registerAuditCommand(program2) {
1486
1521
  }
1487
1522
  console.log();
1488
1523
  } else if (!opts.mcpOnly && !result.claudeMdAnalysis) {
1489
- console.log(chalk2.yellow(" No CLAUDE.md found in current directory or git root.\n"));
1524
+ console.log(chalk3.yellow(" No CLAUDE.md found in current directory or git root.\n"));
1490
1525
  }
1491
1526
  if (opts.mcpOnly && result.mcpServers.length > 0) {
1492
- console.log(chalk2.bold(" MCP Servers:"));
1527
+ console.log(chalk3.bold(" MCP Servers:"));
1493
1528
  for (const server of result.mcpServers) {
1494
- const heavy = server.isHeavy ? chalk2.red(" [heavy]") : "";
1529
+ const heavy = server.isHeavy ? chalk3.red(" [heavy]") : "";
1495
1530
  console.log(
1496
1531
  ` ${server.name.padEnd(20)} ${String(server.toolCount).padStart(3)} tools ${server.estimatedTokens.toLocaleString().padStart(6)} tokens${heavy}`
1497
1532
  );
1498
1533
  }
1499
1534
  console.log();
1500
1535
  } else if (opts.mcpOnly && result.mcpServers.length === 0) {
1501
- console.log(chalk2.dim(" No MCP servers configured.\n"));
1536
+ console.log(chalk3.dim(" No MCP servers configured.\n"));
1502
1537
  }
1503
1538
  if (result.recommendations.length > 0) {
1504
1539
  const filteredRecs = opts.claudeMdOnly ? result.recommendations.filter((r) => r.category === "claude-md") : opts.mcpOnly ? result.recommendations.filter((r) => r.category === "mcp") : result.recommendations;
1505
1540
  if (filteredRecs.length > 0) {
1506
- console.log(chalk2.bold(" Recommendations:"));
1541
+ console.log(chalk3.bold(" Recommendations:"));
1507
1542
  filteredRecs.forEach((rec, i) => {
1508
- const priorityColor = rec.priority === "high" ? chalk2.red : rec.priority === "medium" ? chalk2.yellow : chalk2.dim;
1543
+ const priorityColor = rec.priority === "high" ? chalk3.red : rec.priority === "medium" ? chalk3.yellow : chalk3.dim;
1509
1544
  console.log(
1510
1545
  ` ${i + 1}. ${priorityColor(`[${rec.priority.toUpperCase()}]`)} ${rec.action}`
1511
1546
  );
1512
- console.log(chalk2.dim(` Impact: ${rec.impact}`));
1547
+ console.log(chalk3.dim(` Impact: ${rec.impact}`));
1513
1548
  });
1514
1549
  }
1515
1550
  }
1516
1551
  console.log();
1517
1552
  if (opts.fix) {
1518
- console.log(chalk2.yellow(" --fix: Auto-fix is not yet implemented. Coming in v0.2.0.\n"));
1553
+ console.log(chalk3.yellow(" --fix: Auto-fix is not yet implemented. Coming in v0.2.0.\n"));
1519
1554
  }
1520
1555
  });
1521
1556
  }
1522
1557
 
1523
1558
  // src/cli/commands/report.ts
1524
- import chalk3 from "chalk";
1559
+ import chalk4 from "chalk";
1525
1560
  import dayjs4 from "dayjs";
1526
1561
  function registerReportCommand(program2) {
1527
1562
  program2.command("report").description("Historical cost reports").option("--period <period>", "Time period (today|week|month|all)", "today").option("-p, --project <path>", "Filter to specific project").option("--model", "Show per-model breakdown").option("--sessions", "Show per-session breakdown").option("--csv", "Export as CSV").option("--json", "Export as JSON").action(async (opts) => {
@@ -1613,10 +1648,10 @@ function registerReportCommand(program2) {
1613
1648
  return;
1614
1649
  }
1615
1650
  const periodLabel = opts.period === "today" ? now.format("ddd, MMM D, YYYY") : opts.period;
1616
- console.log(chalk3.bold.cyan(`
1651
+ console.log(chalk4.bold.cyan(`
1617
1652
  kerf-cli report -- ${periodLabel}
1618
1653
  `));
1619
- console.log(` Total Cost: ${chalk3.bold(formatCost(totalCost))}`);
1654
+ console.log(` Total Cost: ${chalk4.bold(formatCost(totalCost))}`);
1620
1655
  console.log(` Total Tokens: ${formatTokens(totalInput)} in / ${formatTokens(totalOutput)} out`);
1621
1656
  console.log(` Cache Hit Rate: ${cacheHitRate.toFixed(1)}%`);
1622
1657
  console.log(` Sessions: ${sessionSummaries.length}`);
@@ -1629,7 +1664,7 @@ function registerReportCommand(program2) {
1629
1664
  existing.sessions++;
1630
1665
  byModel.set(s.model, existing);
1631
1666
  }
1632
- console.log(chalk3.bold(" Model Breakdown:"));
1667
+ console.log(chalk4.bold(" Model Breakdown:"));
1633
1668
  for (const [model, data] of byModel) {
1634
1669
  const pct = totalCost > 0 ? (data.cost / totalCost * 100).toFixed(1) : "0";
1635
1670
  const shortModel = model.replace("claude-", "").replace(/-20\d{6}$/, "");
@@ -1640,7 +1675,7 @@ function registerReportCommand(program2) {
1640
1675
  console.log();
1641
1676
  }
1642
1677
  if (opts.sessions) {
1643
- console.log(chalk3.bold(" Session Breakdown:"));
1678
+ console.log(chalk4.bold(" Session Breakdown:"));
1644
1679
  for (const s of sessionSummaries.sort((a, b) => b.cost - a.cost)) {
1645
1680
  console.log(` ${s.id.slice(0, 12)} ${formatCost(s.cost)} ${s.messages} msgs [${s.model}]`);
1646
1681
  }
@@ -1648,7 +1683,7 @@ function registerReportCommand(program2) {
1648
1683
  }
1649
1684
  const hourly = aggregateCosts(allMessages, "hour");
1650
1685
  if (hourly.length > 1) {
1651
- console.log(chalk3.bold(" Hourly:"));
1686
+ console.log(chalk4.bold(" Hourly:"));
1652
1687
  const maxCost = Math.max(...hourly.map((h) => h.totalCost));
1653
1688
  for (const h of hourly.slice(-8)) {
1654
1689
  const barLen = maxCost > 0 ? Math.round(h.totalCost / maxCost * 12) : 0;
@@ -1661,39 +1696,39 @@ function registerReportCommand(program2) {
1661
1696
  }
1662
1697
 
1663
1698
  // src/cli/commands/init.ts
1664
- import chalk4 from "chalk";
1699
+ import chalk5 from "chalk";
1665
1700
  import { mkdirSync as mkdirSync2, existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync, copyFileSync } from "node:fs";
1666
1701
  import { join as join6, dirname as dirname2 } from "node:path";
1667
1702
  import { homedir as homedir4 } from "node:os";
1668
1703
  function registerInitCommand(program2) {
1669
1704
  program2.command("init").description("Set up kerf-cli for the current project").option("--global", "Install hooks globally").option("--hooks-only", "Only install hooks").option("--no-hooks", "Skip hook installation").option("--force", "Skip confirmation prompts").action(async (opts) => {
1670
- console.log(chalk4.bold.cyan("\n Welcome to kerf-cli!\n"));
1705
+ console.log(chalk5.bold.cyan("\n Welcome to kerf-cli!\n"));
1671
1706
  console.log(" Setting up cost intelligence for Claude Code...\n");
1672
1707
  const kerfDir = join6(homedir4(), ".kerf");
1673
1708
  if (!existsSync5(kerfDir)) {
1674
1709
  mkdirSync2(kerfDir, { recursive: true });
1675
- console.log(chalk4.green(" Created ~/.kerf/"));
1710
+ console.log(chalk5.green(" Created ~/.kerf/"));
1676
1711
  }
1677
1712
  if (!opts.hooksOnly) {
1678
1713
  try {
1679
1714
  const db = initDatabase();
1680
1715
  runMigrations(db);
1681
1716
  db.close();
1682
- console.log(chalk4.green(" Created ~/.kerf/kerf.db"));
1717
+ console.log(chalk5.green(" Created ~/.kerf/kerf.db"));
1683
1718
  } catch (err) {
1684
- console.log(chalk4.red(` Failed to create database: ${err}`));
1719
+ console.log(chalk5.red(` Failed to create database: ${err}`));
1685
1720
  }
1686
1721
  }
1687
1722
  try {
1688
1723
  const { execSync: execSync2 } = await import("node:child_process");
1689
1724
  try {
1690
1725
  execSync2("which rtk", { stdio: "ignore" });
1691
- console.log(chalk4.green(" Detected RTK (command compression) -- compatible!"));
1726
+ console.log(chalk5.green(" Detected RTK (command compression) -- compatible!"));
1692
1727
  } catch {
1693
1728
  }
1694
1729
  try {
1695
1730
  execSync2("which ccusage", { stdio: "ignore" });
1696
- console.log(chalk4.green(" Detected ccusage -- will import historical data"));
1731
+ console.log(chalk5.green(" Detected ccusage -- will import historical data"));
1697
1732
  } catch {
1698
1733
  }
1699
1734
  } catch {
@@ -1707,21 +1742,21 @@ function registerInitCommand(program2) {
1707
1742
  Hooks will be added to ${opts.global ? "~/.claude" : ".claude"}/settings.json`);
1708
1743
  try {
1709
1744
  installHooks(settingsPath);
1710
- console.log(chalk4.green("\n Hooks installed"));
1745
+ console.log(chalk5.green("\n Hooks installed"));
1711
1746
  } catch (err) {
1712
- console.log(chalk4.yellow(`
1747
+ console.log(chalk5.yellow(`
1713
1748
  Skipped hook installation: ${err}`));
1714
1749
  }
1715
1750
  }
1716
- console.log(chalk4.bold("\n Recommended settings for your setup:"));
1717
- console.log(chalk4.dim(" Add to .claude/settings.json or ~/.claude/settings.json:"));
1718
- console.log(chalk4.dim(JSON.stringify({
1751
+ console.log(chalk5.bold("\n Recommended settings for your setup:"));
1752
+ console.log(chalk5.dim(" Add to .claude/settings.json or ~/.claude/settings.json:"));
1753
+ console.log(chalk5.dim(JSON.stringify({
1719
1754
  env: {
1720
1755
  MAX_THINKING_TOKENS: "10000",
1721
1756
  CLAUDE_AUTOCOMPACT_PCT_OVERRIDE: "50"
1722
1757
  }
1723
1758
  }, null, 4).split("\n").map((l) => " " + l).join("\n")));
1724
- console.log(chalk4.bold.cyan("\n Run 'kerf-cli watch' to start the live dashboard!\n"));
1759
+ console.log(chalk5.bold.cyan("\n Run 'kerf-cli watch' to start the live dashboard!\n"));
1725
1760
  });
1726
1761
  }
1727
1762
  function installHooks(settingsPath) {
@@ -1748,7 +1783,7 @@ function installHooks(settingsPath) {
1748
1783
 
1749
1784
  // src/cli/index.ts
1750
1785
  var program = new Command();
1751
- program.name("kerf-cli").version("0.1.0").description("Cost intelligence for Claude Code. Know before you spend.");
1786
+ program.name("kerf-cli").version("0.1.5").description("Cost intelligence for Claude Code. Know before you spend.");
1752
1787
  registerWatchCommand(program);
1753
1788
  registerEstimateCommand(program);
1754
1789
  registerBudgetCommand(program);