@tokz/cli 0.2.4 → 0.2.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/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-A36XPBG6.js");
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
- console.log(await sl.statusline(input));
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-WEIYEE6M.js"),
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 sessionCost = input.cost?.total_cost_usd;
63
+ let calcSessionCost;
61
64
  for (const s of sessions) {
62
- for (const [model2, u] of Object.entries(s.dailyUsage[today] ?? {})) todayCost += costUsd(u, model2).total;
63
- if (sessionCost === void 0 && input.transcript_path && s.file === input.transcript_path) {
64
- sessionCost = Object.entries(s.usageByModel).reduce((sum, [m, u]) => sum + costUsd(u, m).total, 0);
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 model = input.model?.display_name ?? shortModel(input.model?.id ?? "?");
71
- const parts = [`\u{1F916} ${model}`];
72
- const costs = [
73
- sessionCost !== void 0 ? `${usd(sessionCost)} session` : void 0,
74
- `${usd(todayCost)} today`,
75
- active ? `${usd(active.costUsd)} block (${fmtMs(active.end - now)} left)` : void 0
76
- ].filter(Boolean);
77
- parts.push(`\u{1F4B0} ${costs.join(" / ")}`);
78
- if (rate) parts.push(`\u{1F525} ${usd(rate.costPerHour)}/hr`);
79
- const ctx = input.transcript_path ? await lastContextTokens(input.transcript_path) : void 0;
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 p = Math.round(ctx / CONTEXT_WINDOW * 100);
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokz/cli",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "Audit where your coding agent's context window and API dollars go.",
5
5
  "type": "module",
6
6
  "license": "MIT",