kerf-cli 0.1.2 → 0.1.4
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/README.md +2 -2
- package/USAGE.md +644 -0
- package/dist/index.js +161 -66
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
|
+
import { createRequire } from "node:module";
|
|
4
5
|
import { Command } from "commander";
|
|
5
6
|
|
|
6
7
|
// src/cli/commands/watch.ts
|
|
@@ -314,8 +315,11 @@ function getPeriodKey(timestamp, period) {
|
|
|
314
315
|
}
|
|
315
316
|
function formatPeriodLabel(key, period) {
|
|
316
317
|
switch (period) {
|
|
317
|
-
case "hour":
|
|
318
|
-
|
|
318
|
+
case "hour": {
|
|
319
|
+
const parts = key.split("-");
|
|
320
|
+
const dateStr = `${parts[0]}-${parts[1]}-${parts[2]}T${parts[3]}:00:00`;
|
|
321
|
+
return dayjs2(dateStr).format("MMM D, h A");
|
|
322
|
+
}
|
|
319
323
|
case "day":
|
|
320
324
|
return dayjs2(key).format("ddd, MMM D");
|
|
321
325
|
case "week":
|
|
@@ -419,8 +423,25 @@ function ContextBar({ used, total, overhead }) {
|
|
|
419
423
|
|
|
420
424
|
// src/core/tokenCounter.ts
|
|
421
425
|
import { readFileSync as readFileSync2, existsSync } from "node:fs";
|
|
426
|
+
import { execSync } from "node:child_process";
|
|
422
427
|
import { join as join3 } from "node:path";
|
|
423
428
|
import { homedir as homedir2 } from "node:os";
|
|
429
|
+
function findGitRootClaudeMd() {
|
|
430
|
+
try {
|
|
431
|
+
const gitRoot = execSync("git rev-parse --show-toplevel", {
|
|
432
|
+
encoding: "utf-8",
|
|
433
|
+
stdio: ["pipe", "pipe", "ignore"]
|
|
434
|
+
}).trim();
|
|
435
|
+
if (gitRoot && gitRoot !== process.cwd()) {
|
|
436
|
+
return [
|
|
437
|
+
join3(gitRoot, "CLAUDE.md"),
|
|
438
|
+
join3(gitRoot, ".claude", "CLAUDE.md")
|
|
439
|
+
];
|
|
440
|
+
}
|
|
441
|
+
} catch {
|
|
442
|
+
}
|
|
443
|
+
return [];
|
|
444
|
+
}
|
|
424
445
|
function estimateTokens(text) {
|
|
425
446
|
return Math.ceil(text.length / 3.5);
|
|
426
447
|
}
|
|
@@ -473,7 +494,9 @@ function parseClaudeMdSections(content) {
|
|
|
473
494
|
function analyzeClaudeMd(filePath) {
|
|
474
495
|
const paths = filePath ? [filePath] : [
|
|
475
496
|
join3(process.cwd(), "CLAUDE.md"),
|
|
476
|
-
join3(process.cwd(), ".claude", "CLAUDE.md")
|
|
497
|
+
join3(process.cwd(), ".claude", "CLAUDE.md"),
|
|
498
|
+
...findGitRootClaudeMd(),
|
|
499
|
+
join3(homedir2(), ".claude", "CLAUDE.md")
|
|
477
500
|
];
|
|
478
501
|
for (const p of paths) {
|
|
479
502
|
if (existsSync(p)) {
|
|
@@ -554,10 +577,13 @@ function Dashboard({ sessionFilePath, interval }) {
|
|
|
554
577
|
const timer = setInterval(refresh, interval);
|
|
555
578
|
return () => clearInterval(timer);
|
|
556
579
|
}, [sessionFilePath, interval]);
|
|
557
|
-
useInput(
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
580
|
+
useInput(
|
|
581
|
+
(input) => {
|
|
582
|
+
if (input === "q") exit();
|
|
583
|
+
if (input === "b") setShowBudget((prev) => !prev);
|
|
584
|
+
},
|
|
585
|
+
{ isActive: process.stdin.isTTY ?? false }
|
|
586
|
+
);
|
|
561
587
|
if (!session || session.messages.length === 0) {
|
|
562
588
|
return /* @__PURE__ */ jsx3(Box3, { paddingX: 1, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "Waiting for session data..." }) });
|
|
563
589
|
}
|
|
@@ -637,6 +663,12 @@ function registerWatchCommand(program2) {
|
|
|
637
663
|
);
|
|
638
664
|
process.exit(0);
|
|
639
665
|
}
|
|
666
|
+
if (!process.stdin.isTTY) {
|
|
667
|
+
console.log(
|
|
668
|
+
"kerf-cli watch requires an interactive terminal (TTY). Run it directly in a terminal tab, not piped."
|
|
669
|
+
);
|
|
670
|
+
process.exit(1);
|
|
671
|
+
}
|
|
640
672
|
const { waitUntilExit } = render(
|
|
641
673
|
React2.createElement(Dashboard, { sessionFilePath, interval })
|
|
642
674
|
);
|
|
@@ -648,6 +680,7 @@ function registerWatchCommand(program2) {
|
|
|
648
680
|
import React3 from "react";
|
|
649
681
|
import { render as render2 } from "ink";
|
|
650
682
|
import { glob as glob2 } from "glob";
|
|
683
|
+
import chalk from "chalk";
|
|
651
684
|
|
|
652
685
|
// src/core/estimator.ts
|
|
653
686
|
import { glob } from "glob";
|
|
@@ -674,8 +707,8 @@ async function estimateTaskCost(taskDescription, options = {}) {
|
|
|
674
707
|
let fileList = options.files ?? [];
|
|
675
708
|
if (fileList.length === 0) {
|
|
676
709
|
try {
|
|
677
|
-
const { execSync } = await import("node:child_process");
|
|
678
|
-
const output =
|
|
710
|
+
const { execSync: execSync2 } = await import("node:child_process");
|
|
711
|
+
const output = execSync2("git diff --name-only HEAD 2>/dev/null || git ls-files -m 2>/dev/null", {
|
|
679
712
|
cwd,
|
|
680
713
|
encoding: "utf-8"
|
|
681
714
|
});
|
|
@@ -837,17 +870,42 @@ function registerEstimateCommand(program2) {
|
|
|
837
870
|
}
|
|
838
871
|
if (opts.compare) {
|
|
839
872
|
const models = ["sonnet", "opus", "haiku"];
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
873
|
+
if (opts.json) {
|
|
874
|
+
for (const model of models) {
|
|
875
|
+
const estimate2 = await estimateTaskCost(task, { model, files, cwd: process.cwd() });
|
|
843
876
|
console.log(JSON.stringify(estimate2, null, 2));
|
|
844
|
-
} else {
|
|
845
|
-
const { waitUntilExit: waitUntilExit2 } = render2(
|
|
846
|
-
React3.createElement(EstimateCard, { task, estimate: estimate2 })
|
|
847
|
-
);
|
|
848
|
-
await waitUntilExit2();
|
|
849
877
|
}
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
const estimates = await Promise.all(
|
|
881
|
+
models.map(async (model) => ({
|
|
882
|
+
model,
|
|
883
|
+
estimate: await estimateTaskCost(task, { model, files, cwd: process.cwd() })
|
|
884
|
+
}))
|
|
885
|
+
);
|
|
886
|
+
console.log(chalk.bold.cyan(`
|
|
887
|
+
kerf-cli estimate: '${task}'
|
|
888
|
+
`));
|
|
889
|
+
console.log(
|
|
890
|
+
` ${"Model".padEnd(10)} ${"Turns".padEnd(14)} ${"Low".padEnd(12)} ${"Expected".padEnd(12)} ${"High".padEnd(12)}`
|
|
891
|
+
);
|
|
892
|
+
console.log(" " + "-".repeat(58));
|
|
893
|
+
for (const { model, estimate: estimate2 } of estimates) {
|
|
894
|
+
const turns = `${estimate2.estimatedTurns.low}-${estimate2.estimatedTurns.high}`;
|
|
895
|
+
console.log(
|
|
896
|
+
` ${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))}`
|
|
897
|
+
);
|
|
850
898
|
}
|
|
899
|
+
console.log();
|
|
900
|
+
const cheapest = estimates[2];
|
|
901
|
+
const priciest = estimates[1];
|
|
902
|
+
console.log(
|
|
903
|
+
chalk.dim(` Cheapest: ${cheapest.model} at ${cheapest.estimate.estimatedCost.expected}`)
|
|
904
|
+
);
|
|
905
|
+
console.log(
|
|
906
|
+
chalk.dim(` Priciest: ${priciest.model} at ${priciest.estimate.estimatedCost.expected}`)
|
|
907
|
+
);
|
|
908
|
+
console.log();
|
|
851
909
|
return;
|
|
852
910
|
}
|
|
853
911
|
const estimate = await estimateTaskCost(task, {
|
|
@@ -867,7 +925,7 @@ function registerEstimateCommand(program2) {
|
|
|
867
925
|
}
|
|
868
926
|
|
|
869
927
|
// src/cli/commands/budget.ts
|
|
870
|
-
import
|
|
928
|
+
import chalk2 from "chalk";
|
|
871
929
|
|
|
872
930
|
// src/core/budgetManager.ts
|
|
873
931
|
import dayjs3 from "dayjs";
|
|
@@ -1073,12 +1131,12 @@ function registerBudgetCommand(program2) {
|
|
|
1073
1131
|
const projectPath = opts.project || process.cwd();
|
|
1074
1132
|
const amountNum = parseFloat(amount);
|
|
1075
1133
|
if (isNaN(amountNum) || amountNum <= 0) {
|
|
1076
|
-
console.log(
|
|
1134
|
+
console.log(chalk2.red("Budget amount must be a positive number."));
|
|
1077
1135
|
process.exit(1);
|
|
1078
1136
|
}
|
|
1079
1137
|
manager.setBudget(projectPath, amountNum, opts.period);
|
|
1080
1138
|
console.log(
|
|
1081
|
-
|
|
1139
|
+
chalk2.green(`Budget set: ${formatCost(amountNum)}/${opts.period} for ${projectPath}`)
|
|
1082
1140
|
);
|
|
1083
1141
|
manager.close();
|
|
1084
1142
|
});
|
|
@@ -1101,14 +1159,14 @@ function registerBudgetCommand(program2) {
|
|
|
1101
1159
|
const filled = Math.round(Math.min(pct, 100) / 100 * barWidth);
|
|
1102
1160
|
const empty = barWidth - filled;
|
|
1103
1161
|
const color = pct < 50 ? "green" : pct < 80 ? "yellow" : "red";
|
|
1104
|
-
const barColor = color === "green" ?
|
|
1105
|
-
console.log(
|
|
1162
|
+
const barColor = color === "green" ? chalk2.green : color === "yellow" ? chalk2.yellow : chalk2.red;
|
|
1163
|
+
console.log(chalk2.bold.cyan("\n kerf-cli budget\n"));
|
|
1106
1164
|
console.log(` Period: ${status.period} (${status.periodStart.slice(0, 10)} to ${status.periodEnd.slice(0, 10)})`);
|
|
1107
1165
|
console.log(` Budget: ${formatCost(status.budget)}`);
|
|
1108
1166
|
console.log(` Spent: ${barColor(formatCost(status.spent))}`);
|
|
1109
1167
|
console.log(` ${barColor("[" + "\u2588".repeat(filled) + "\u2591".repeat(empty) + "]")} ${pct.toFixed(1)}%`);
|
|
1110
1168
|
if (status.isOverBudget) {
|
|
1111
|
-
console.log(
|
|
1169
|
+
console.log(chalk2.red.bold(`
|
|
1112
1170
|
OVER BUDGET by ${formatCost(status.spent - status.budget)}`));
|
|
1113
1171
|
}
|
|
1114
1172
|
console.log();
|
|
@@ -1122,12 +1180,12 @@ function registerBudgetCommand(program2) {
|
|
|
1122
1180
|
manager.close();
|
|
1123
1181
|
return;
|
|
1124
1182
|
}
|
|
1125
|
-
console.log(
|
|
1183
|
+
console.log(chalk2.bold.cyan("\n kerf-cli budget list\n"));
|
|
1126
1184
|
for (const p of projects) {
|
|
1127
1185
|
const budgetStr = p.budget ? `${formatCost(p.budget)}/${p.period}` : "no budget";
|
|
1128
1186
|
const spentStr = p.spent > 0 ? ` (spent: ${formatCost(p.spent)})` : "";
|
|
1129
|
-
console.log(` ${
|
|
1130
|
-
console.log(
|
|
1187
|
+
console.log(` ${chalk2.bold(p.name)} \u2014 ${budgetStr}${spentStr}`);
|
|
1188
|
+
console.log(chalk2.dim(` ${p.path}`));
|
|
1131
1189
|
}
|
|
1132
1190
|
console.log();
|
|
1133
1191
|
manager.close();
|
|
@@ -1137,7 +1195,7 @@ function registerBudgetCommand(program2) {
|
|
|
1137
1195
|
const projectPath = opts.project || process.cwd();
|
|
1138
1196
|
const removed = manager.removeBudget(projectPath);
|
|
1139
1197
|
if (removed) {
|
|
1140
|
-
console.log(
|
|
1198
|
+
console.log(chalk2.green("Budget removed."));
|
|
1141
1199
|
} else {
|
|
1142
1200
|
console.log("No budget found for this project.");
|
|
1143
1201
|
}
|
|
@@ -1146,7 +1204,7 @@ function registerBudgetCommand(program2) {
|
|
|
1146
1204
|
}
|
|
1147
1205
|
|
|
1148
1206
|
// src/cli/commands/audit.ts
|
|
1149
|
-
import
|
|
1207
|
+
import chalk3 from "chalk";
|
|
1150
1208
|
|
|
1151
1209
|
// src/audit/ghostTokens.ts
|
|
1152
1210
|
function calculateGrade(percentUsable) {
|
|
@@ -1409,14 +1467,14 @@ function registerAuditCommand(program2) {
|
|
|
1409
1467
|
console.log(JSON.stringify(result, null, 2));
|
|
1410
1468
|
return;
|
|
1411
1469
|
}
|
|
1412
|
-
const gradeColor = result.grade === "A" ?
|
|
1413
|
-
console.log(
|
|
1470
|
+
const gradeColor = result.grade === "A" ? chalk3.green : result.grade === "B" ? chalk3.yellow : chalk3.red;
|
|
1471
|
+
console.log(chalk3.bold.cyan("\n kerf-cli audit report\n"));
|
|
1414
1472
|
console.log(
|
|
1415
1473
|
` Context Window Health: ${gradeColor.bold(result.grade)} (${result.contextOverhead.percentUsable.toFixed(0)}% usable)
|
|
1416
1474
|
`
|
|
1417
1475
|
);
|
|
1418
|
-
if (!opts.
|
|
1419
|
-
console.log(
|
|
1476
|
+
if (!opts.claudeMdOnly) {
|
|
1477
|
+
console.log(chalk3.bold(" Ghost Token Breakdown:"));
|
|
1420
1478
|
const oh = result.contextOverhead;
|
|
1421
1479
|
const fmt = (label, tokens) => {
|
|
1422
1480
|
const pct = (tokens / CONTEXT_WINDOW_SIZE * 100).toFixed(1);
|
|
@@ -1436,35 +1494,70 @@ function registerAuditCommand(program2) {
|
|
|
1436
1494
|
}
|
|
1437
1495
|
if (!opts.mcpOnly && result.claudeMdAnalysis) {
|
|
1438
1496
|
const cma = result.claudeMdAnalysis;
|
|
1439
|
-
console.log(
|
|
1497
|
+
console.log(chalk3.bold(" CLAUDE.md Analysis:"));
|
|
1440
1498
|
console.log(
|
|
1441
|
-
` Lines: ${cma.totalLines}${cma.isOverLineLimit ?
|
|
1499
|
+
` Lines: ${cma.totalLines}${cma.isOverLineLimit ? chalk3.yellow(" (over 200 limit)") : ""}`
|
|
1442
1500
|
);
|
|
1443
1501
|
console.log(` Tokens: ${cma.totalTokens.toLocaleString()}`);
|
|
1444
1502
|
console.log(
|
|
1445
|
-
` Critical rules in dead zone: ${cma.criticalRulesInDeadZone > 0 ?
|
|
1503
|
+
` Critical rules in dead zone: ${cma.criticalRulesInDeadZone > 0 ? chalk3.red(String(cma.criticalRulesInDeadZone)) : "0"}`
|
|
1446
1504
|
);
|
|
1505
|
+
if (opts.claudeMdOnly) {
|
|
1506
|
+
console.log();
|
|
1507
|
+
console.log(chalk3.bold(" Sections:"));
|
|
1508
|
+
for (const section of cma.sections) {
|
|
1509
|
+
const zone = section.attentionZone === "low-middle" ? chalk3.red(" [dead zone]") : chalk3.green(" [high attention]");
|
|
1510
|
+
const critical = section.hasCriticalRules ? chalk3.yellow(" *critical rules*") : "";
|
|
1511
|
+
console.log(
|
|
1512
|
+
` ${section.title.padEnd(30)} ${String(section.tokens).padStart(5)} tokens L${section.lineStart}-${section.lineEnd}${zone}${critical}`
|
|
1513
|
+
);
|
|
1514
|
+
}
|
|
1515
|
+
if (cma.suggestedReorder.length > 0) {
|
|
1516
|
+
console.log();
|
|
1517
|
+
console.log(chalk3.bold(" Suggested section order:"));
|
|
1518
|
+
cma.suggestedReorder.forEach((title, i) => {
|
|
1519
|
+
console.log(` ${i + 1}. ${title}`);
|
|
1520
|
+
});
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1447
1523
|
console.log();
|
|
1524
|
+
} else if (!opts.mcpOnly && !result.claudeMdAnalysis) {
|
|
1525
|
+
console.log(chalk3.yellow(" No CLAUDE.md found in current directory or git root.\n"));
|
|
1448
1526
|
}
|
|
1449
|
-
if (result.
|
|
1450
|
-
console.log(
|
|
1451
|
-
result.
|
|
1452
|
-
const
|
|
1527
|
+
if (opts.mcpOnly && result.mcpServers.length > 0) {
|
|
1528
|
+
console.log(chalk3.bold(" MCP Servers:"));
|
|
1529
|
+
for (const server of result.mcpServers) {
|
|
1530
|
+
const heavy = server.isHeavy ? chalk3.red(" [heavy]") : "";
|
|
1453
1531
|
console.log(
|
|
1454
|
-
` ${
|
|
1532
|
+
` ${server.name.padEnd(20)} ${String(server.toolCount).padStart(3)} tools ${server.estimatedTokens.toLocaleString().padStart(6)} tokens${heavy}`
|
|
1455
1533
|
);
|
|
1456
|
-
|
|
1457
|
-
|
|
1534
|
+
}
|
|
1535
|
+
console.log();
|
|
1536
|
+
} else if (opts.mcpOnly && result.mcpServers.length === 0) {
|
|
1537
|
+
console.log(chalk3.dim(" No MCP servers configured.\n"));
|
|
1538
|
+
}
|
|
1539
|
+
if (result.recommendations.length > 0) {
|
|
1540
|
+
const filteredRecs = opts.claudeMdOnly ? result.recommendations.filter((r) => r.category === "claude-md") : opts.mcpOnly ? result.recommendations.filter((r) => r.category === "mcp") : result.recommendations;
|
|
1541
|
+
if (filteredRecs.length > 0) {
|
|
1542
|
+
console.log(chalk3.bold(" Recommendations:"));
|
|
1543
|
+
filteredRecs.forEach((rec, i) => {
|
|
1544
|
+
const priorityColor = rec.priority === "high" ? chalk3.red : rec.priority === "medium" ? chalk3.yellow : chalk3.dim;
|
|
1545
|
+
console.log(
|
|
1546
|
+
` ${i + 1}. ${priorityColor(`[${rec.priority.toUpperCase()}]`)} ${rec.action}`
|
|
1547
|
+
);
|
|
1548
|
+
console.log(chalk3.dim(` Impact: ${rec.impact}`));
|
|
1549
|
+
});
|
|
1550
|
+
}
|
|
1458
1551
|
}
|
|
1459
1552
|
console.log();
|
|
1460
1553
|
if (opts.fix) {
|
|
1461
|
-
console.log(
|
|
1554
|
+
console.log(chalk3.yellow(" --fix: Auto-fix is not yet implemented. Coming in v0.2.0.\n"));
|
|
1462
1555
|
}
|
|
1463
1556
|
});
|
|
1464
1557
|
}
|
|
1465
1558
|
|
|
1466
1559
|
// src/cli/commands/report.ts
|
|
1467
|
-
import
|
|
1560
|
+
import chalk4 from "chalk";
|
|
1468
1561
|
import dayjs4 from "dayjs";
|
|
1469
1562
|
function registerReportCommand(program2) {
|
|
1470
1563
|
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) => {
|
|
@@ -1556,10 +1649,10 @@ function registerReportCommand(program2) {
|
|
|
1556
1649
|
return;
|
|
1557
1650
|
}
|
|
1558
1651
|
const periodLabel = opts.period === "today" ? now.format("ddd, MMM D, YYYY") : opts.period;
|
|
1559
|
-
console.log(
|
|
1652
|
+
console.log(chalk4.bold.cyan(`
|
|
1560
1653
|
kerf-cli report -- ${periodLabel}
|
|
1561
1654
|
`));
|
|
1562
|
-
console.log(` Total Cost: ${
|
|
1655
|
+
console.log(` Total Cost: ${chalk4.bold(formatCost(totalCost))}`);
|
|
1563
1656
|
console.log(` Total Tokens: ${formatTokens(totalInput)} in / ${formatTokens(totalOutput)} out`);
|
|
1564
1657
|
console.log(` Cache Hit Rate: ${cacheHitRate.toFixed(1)}%`);
|
|
1565
1658
|
console.log(` Sessions: ${sessionSummaries.length}`);
|
|
@@ -1572,7 +1665,7 @@ function registerReportCommand(program2) {
|
|
|
1572
1665
|
existing.sessions++;
|
|
1573
1666
|
byModel.set(s.model, existing);
|
|
1574
1667
|
}
|
|
1575
|
-
console.log(
|
|
1668
|
+
console.log(chalk4.bold(" Model Breakdown:"));
|
|
1576
1669
|
for (const [model, data] of byModel) {
|
|
1577
1670
|
const pct = totalCost > 0 ? (data.cost / totalCost * 100).toFixed(1) : "0";
|
|
1578
1671
|
const shortModel = model.replace("claude-", "").replace(/-20\d{6}$/, "");
|
|
@@ -1583,7 +1676,7 @@ function registerReportCommand(program2) {
|
|
|
1583
1676
|
console.log();
|
|
1584
1677
|
}
|
|
1585
1678
|
if (opts.sessions) {
|
|
1586
|
-
console.log(
|
|
1679
|
+
console.log(chalk4.bold(" Session Breakdown:"));
|
|
1587
1680
|
for (const s of sessionSummaries.sort((a, b) => b.cost - a.cost)) {
|
|
1588
1681
|
console.log(` ${s.id.slice(0, 12)} ${formatCost(s.cost)} ${s.messages} msgs [${s.model}]`);
|
|
1589
1682
|
}
|
|
@@ -1591,7 +1684,7 @@ function registerReportCommand(program2) {
|
|
|
1591
1684
|
}
|
|
1592
1685
|
const hourly = aggregateCosts(allMessages, "hour");
|
|
1593
1686
|
if (hourly.length > 1) {
|
|
1594
|
-
console.log(
|
|
1687
|
+
console.log(chalk4.bold(" Hourly:"));
|
|
1595
1688
|
const maxCost = Math.max(...hourly.map((h) => h.totalCost));
|
|
1596
1689
|
for (const h of hourly.slice(-8)) {
|
|
1597
1690
|
const barLen = maxCost > 0 ? Math.round(h.totalCost / maxCost * 12) : 0;
|
|
@@ -1604,39 +1697,39 @@ function registerReportCommand(program2) {
|
|
|
1604
1697
|
}
|
|
1605
1698
|
|
|
1606
1699
|
// src/cli/commands/init.ts
|
|
1607
|
-
import
|
|
1700
|
+
import chalk5 from "chalk";
|
|
1608
1701
|
import { mkdirSync as mkdirSync2, existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync, copyFileSync } from "node:fs";
|
|
1609
1702
|
import { join as join6, dirname as dirname2 } from "node:path";
|
|
1610
1703
|
import { homedir as homedir4 } from "node:os";
|
|
1611
1704
|
function registerInitCommand(program2) {
|
|
1612
1705
|
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) => {
|
|
1613
|
-
console.log(
|
|
1706
|
+
console.log(chalk5.bold.cyan("\n Welcome to kerf-cli!\n"));
|
|
1614
1707
|
console.log(" Setting up cost intelligence for Claude Code...\n");
|
|
1615
1708
|
const kerfDir = join6(homedir4(), ".kerf");
|
|
1616
1709
|
if (!existsSync5(kerfDir)) {
|
|
1617
1710
|
mkdirSync2(kerfDir, { recursive: true });
|
|
1618
|
-
console.log(
|
|
1711
|
+
console.log(chalk5.green(" Created ~/.kerf/"));
|
|
1619
1712
|
}
|
|
1620
1713
|
if (!opts.hooksOnly) {
|
|
1621
1714
|
try {
|
|
1622
1715
|
const db = initDatabase();
|
|
1623
1716
|
runMigrations(db);
|
|
1624
1717
|
db.close();
|
|
1625
|
-
console.log(
|
|
1718
|
+
console.log(chalk5.green(" Created ~/.kerf/kerf.db"));
|
|
1626
1719
|
} catch (err) {
|
|
1627
|
-
console.log(
|
|
1720
|
+
console.log(chalk5.red(` Failed to create database: ${err}`));
|
|
1628
1721
|
}
|
|
1629
1722
|
}
|
|
1630
1723
|
try {
|
|
1631
|
-
const { execSync } = await import("node:child_process");
|
|
1724
|
+
const { execSync: execSync2 } = await import("node:child_process");
|
|
1632
1725
|
try {
|
|
1633
|
-
|
|
1634
|
-
console.log(
|
|
1726
|
+
execSync2("which rtk", { stdio: "ignore" });
|
|
1727
|
+
console.log(chalk5.green(" Detected RTK (command compression) -- compatible!"));
|
|
1635
1728
|
} catch {
|
|
1636
1729
|
}
|
|
1637
1730
|
try {
|
|
1638
|
-
|
|
1639
|
-
console.log(
|
|
1731
|
+
execSync2("which ccusage", { stdio: "ignore" });
|
|
1732
|
+
console.log(chalk5.green(" Detected ccusage -- will import historical data"));
|
|
1640
1733
|
} catch {
|
|
1641
1734
|
}
|
|
1642
1735
|
} catch {
|
|
@@ -1650,21 +1743,21 @@ function registerInitCommand(program2) {
|
|
|
1650
1743
|
Hooks will be added to ${opts.global ? "~/.claude" : ".claude"}/settings.json`);
|
|
1651
1744
|
try {
|
|
1652
1745
|
installHooks(settingsPath);
|
|
1653
|
-
console.log(
|
|
1746
|
+
console.log(chalk5.green("\n Hooks installed"));
|
|
1654
1747
|
} catch (err) {
|
|
1655
|
-
console.log(
|
|
1748
|
+
console.log(chalk5.yellow(`
|
|
1656
1749
|
Skipped hook installation: ${err}`));
|
|
1657
1750
|
}
|
|
1658
1751
|
}
|
|
1659
|
-
console.log(
|
|
1660
|
-
console.log(
|
|
1661
|
-
console.log(
|
|
1752
|
+
console.log(chalk5.bold("\n Recommended settings for your setup:"));
|
|
1753
|
+
console.log(chalk5.dim(" Add to .claude/settings.json or ~/.claude/settings.json:"));
|
|
1754
|
+
console.log(chalk5.dim(JSON.stringify({
|
|
1662
1755
|
env: {
|
|
1663
1756
|
MAX_THINKING_TOKENS: "10000",
|
|
1664
1757
|
CLAUDE_AUTOCOMPACT_PCT_OVERRIDE: "50"
|
|
1665
1758
|
}
|
|
1666
1759
|
}, null, 4).split("\n").map((l) => " " + l).join("\n")));
|
|
1667
|
-
console.log(
|
|
1760
|
+
console.log(chalk5.bold.cyan("\n Run 'kerf-cli watch' to start the live dashboard!\n"));
|
|
1668
1761
|
});
|
|
1669
1762
|
}
|
|
1670
1763
|
function installHooks(settingsPath) {
|
|
@@ -1690,8 +1783,10 @@ function installHooks(settingsPath) {
|
|
|
1690
1783
|
}
|
|
1691
1784
|
|
|
1692
1785
|
// src/cli/index.ts
|
|
1786
|
+
var require2 = createRequire(import.meta.url);
|
|
1787
|
+
var pkg = require2("../../package.json");
|
|
1693
1788
|
var program = new Command();
|
|
1694
|
-
program.name("kerf-cli").version(
|
|
1789
|
+
program.name("kerf-cli").version(pkg.version).description("Cost intelligence for Claude Code. Know before you spend.");
|
|
1695
1790
|
registerWatchCommand(program);
|
|
1696
1791
|
registerEstimateCommand(program);
|
|
1697
1792
|
registerBudgetCommand(program);
|