@tokz/cli 0.2.4 → 0.2.6
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 +124 -98
- package/dist/{Root-WEIYEE6M.js → Root-TUEV5MJB.js} +983 -121
- package/dist/cli.js +6 -4
- package/dist/{statusline-A36XPBG6.js → statusline-EXFMQYFF.js} +37 -16
- package/package.json +14 -6
package/dist/cli.js
CHANGED
|
@@ -208,9 +208,9 @@ program.command("blocks").description("Claude usage grouped into rolling 5-hour
|
|
|
208
208
|
}
|
|
209
209
|
console.log(renderBlocksReport(blocks, { tokenLimit }));
|
|
210
210
|
});
|
|
211
|
-
program.command("statusline").description("compact usage line for Claude Code's statusLine hook (reads hook JSON on stdin)").argument("[action]", '"enable" writes the hook into ~/.claude/settings.json, "disable" removes it').action(async (action) => {
|
|
211
|
+
program.command("statusline").description("compact usage line for Claude Code's statusLine hook (reads hook JSON on stdin)").argument("[action]", '"enable" writes the hook into ~/.claude/settings.json, "disable" removes it').option("--cost-source <mode>", "session cost source: auto | cc | calc | both", "auto").action(async (action, opts) => {
|
|
212
212
|
const globals = applyGlobals();
|
|
213
|
-
const sl = await import("./statusline-
|
|
213
|
+
const sl = await import("./statusline-EXFMQYFF.js");
|
|
214
214
|
if (action === "enable" || action === "disable") {
|
|
215
215
|
try {
|
|
216
216
|
console.log(action === "enable" ? await sl.enableStatusline() : await sl.disableStatusline());
|
|
@@ -231,7 +231,9 @@ program.command("statusline").description("compact usage line for Claude Code's
|
|
|
231
231
|
input = JSON.parse(await sl.readStdin());
|
|
232
232
|
} catch {
|
|
233
233
|
}
|
|
234
|
-
|
|
234
|
+
const valid = ["auto", "cc", "calc", "both"];
|
|
235
|
+
const costSource = valid.includes(opts.costSource ?? "auto") ? opts.costSource ?? "auto" : "auto";
|
|
236
|
+
console.log(await sl.statusline(input, Date.now(), void 0, { costSource }));
|
|
235
237
|
});
|
|
236
238
|
program.action(async () => {
|
|
237
239
|
const globals = applyGlobals();
|
|
@@ -249,7 +251,7 @@ program.action(async () => {
|
|
|
249
251
|
const [{ render }, React, { Root }, { Fullscreen }] = await Promise.all([
|
|
250
252
|
import("ink"),
|
|
251
253
|
import("react"),
|
|
252
|
-
import("./Root-
|
|
254
|
+
import("./Root-TUEV5MJB.js"),
|
|
253
255
|
import("./Fullscreen-GK2ZYXHV.js")
|
|
254
256
|
]);
|
|
255
257
|
render(React.createElement(Fullscreen, null, React.createElement(Root)));
|
|
@@ -21,6 +21,8 @@ import { join } from "path";
|
|
|
21
21
|
import pc from "picocolors";
|
|
22
22
|
var CONTEXT_WINDOW = 2e5;
|
|
23
23
|
var LOOKBACK_MS = 6 * 60 * 60 * 1e3;
|
|
24
|
+
var BURN_MODERATE = 2e3;
|
|
25
|
+
var BURN_HIGH = 5e3;
|
|
24
26
|
async function lastContextTokens(transcriptPath) {
|
|
25
27
|
try {
|
|
26
28
|
const lines = (await readFile(transcriptPath, "utf8")).split("\n");
|
|
@@ -39,7 +41,8 @@ async function lastContextTokens(transcriptPath) {
|
|
|
39
41
|
}
|
|
40
42
|
return void 0;
|
|
41
43
|
}
|
|
42
|
-
async function statusline(input, now = Date.now(), home) {
|
|
44
|
+
async function statusline(input, now = Date.now(), home, opts = {}) {
|
|
45
|
+
const costSource = opts.costSource ?? "auto";
|
|
43
46
|
const files = await findTranscripts(void 0, home);
|
|
44
47
|
const recent = [];
|
|
45
48
|
await Promise.all(
|
|
@@ -57,33 +60,51 @@ async function statusline(input, now = Date.now(), home) {
|
|
|
57
60
|
const sessions = await Promise.all(recent.map((f) => parseTranscript(f, seen, seenTools, events)));
|
|
58
61
|
const today = dayKey(now);
|
|
59
62
|
let todayCost = 0;
|
|
60
|
-
let
|
|
63
|
+
let calcSessionCost;
|
|
61
64
|
for (const s of sessions) {
|
|
62
|
-
for (const [
|
|
63
|
-
if (
|
|
64
|
-
|
|
65
|
+
for (const [model, u] of Object.entries(s.dailyUsage[today] ?? {})) todayCost += costUsd(u, model).total;
|
|
66
|
+
if (input.transcript_path && s.file === input.transcript_path) {
|
|
67
|
+
calcSessionCost = Object.entries(s.usageByModel).reduce((sum, [m, u]) => sum + costUsd(u, m).total, 0);
|
|
65
68
|
}
|
|
66
69
|
}
|
|
70
|
+
const ccCost = input.cost?.total_cost_usd;
|
|
67
71
|
const blocks = buildBlocks(events, { now });
|
|
68
72
|
const active = blocks.find((b) => b.active);
|
|
69
73
|
const rate = active ? burnRate(active, now) : void 0;
|
|
70
|
-
const
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const ctx =
|
|
74
|
+
const modelName = input.model?.display_name ?? shortModel(input.model?.id ?? "?");
|
|
75
|
+
const effort = input.effort?.level;
|
|
76
|
+
const parts = [`\u{1F916} ${modelName}${effort ? ` (${effort})` : ""}`];
|
|
77
|
+
parts.push(`\u{1F4B0} ${moneySegment(costSource, ccCost, calcSessionCost, todayCost, active, now)}`);
|
|
78
|
+
if (rate) {
|
|
79
|
+
const tpm = rate.tokensPerMinute;
|
|
80
|
+
const label = `\u{1F525} ${usd(rate.costPerHour)}/hr`;
|
|
81
|
+
parts.push(tpm > BURN_HIGH ? pc.red(label) : tpm > BURN_MODERATE ? pc.yellow(label) : pc.green(label));
|
|
82
|
+
}
|
|
83
|
+
const ctx = await contextTokens(input);
|
|
80
84
|
if (ctx !== void 0) {
|
|
81
|
-
const
|
|
85
|
+
const window = input.context_window?.context_window_size ?? CONTEXT_WINDOW;
|
|
86
|
+
const p = Math.round(ctx / window * 100);
|
|
82
87
|
const colored = p >= 80 ? pc.red(`${p}%`) : p >= 50 ? pc.yellow(`${p}%`) : pc.green(`${p}%`);
|
|
83
88
|
parts.push(`\u{1F9E0} ${compact(ctx)} (${colored})`);
|
|
84
89
|
}
|
|
85
90
|
return parts.join(pc.dim(" | "));
|
|
86
91
|
}
|
|
92
|
+
function moneySegment(source, ccCost, calcCost, todayCost, active, now) {
|
|
93
|
+
let session;
|
|
94
|
+
if (source === "both") {
|
|
95
|
+
session = `(${usd(ccCost ?? 0)} cc / ${usd(calcCost ?? 0)} calc) session`;
|
|
96
|
+
} else {
|
|
97
|
+
const pick = source === "cc" ? ccCost : source === "calc" ? calcCost : ccCost ?? calcCost;
|
|
98
|
+
session = `${usd(pick ?? 0)} session`;
|
|
99
|
+
}
|
|
100
|
+
const block = active ? `${usd(active.costUsd)} block (${fmtMs(active.end - now)} left)` : "No active block";
|
|
101
|
+
return `${session} / ${usd(todayCost)} today / ${block}`;
|
|
102
|
+
}
|
|
103
|
+
async function contextTokens(input) {
|
|
104
|
+
const cw = input.context_window;
|
|
105
|
+
if (cw && typeof cw.total_input_tokens === "number") return cw.total_input_tokens;
|
|
106
|
+
return input.transcript_path ? lastContextTokens(input.transcript_path) : void 0;
|
|
107
|
+
}
|
|
87
108
|
function msSinceMidnight(now) {
|
|
88
109
|
const day = dayKey(now);
|
|
89
110
|
const midnight = Date.parse(`${day}T00:00:00Z`);
|
package/package.json
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokz/cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "Audit where your coding agent's context window and API dollars go.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
|
-
"engines": {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
"
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=20.9.0"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"tokz": "./dist/cli.js"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
15
|
+
],
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public"
|
|
18
|
+
},
|
|
11
19
|
"scripts": {
|
|
12
20
|
"build": "tsup",
|
|
13
21
|
"test": "vitest run",
|
|
@@ -24,7 +32,7 @@
|
|
|
24
32
|
"zod": "^3.24.0"
|
|
25
33
|
},
|
|
26
34
|
"devDependencies": {
|
|
27
|
-
"@types/node": "^
|
|
35
|
+
"@types/node": "^20.19.43",
|
|
28
36
|
"@types/react": "^18.3.12",
|
|
29
37
|
"ink-testing-library": "^4.0.0",
|
|
30
38
|
"tsup": "^8.3.0",
|