ccusage-tracker 0.1.2 → 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.
Files changed (3) hide show
  1. package/README.md +5 -4
  2. package/dist/index.js +42 -10
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  CLI for [ccusage-tracker](https://github.com/ericcai0814/ccusage-tracker) — a self-hosted Claude Code usage tracker for teams.
4
4
 
5
- Install Claude Code SessionStart/SessionEnd hooks that report token usage to a self-hosted tracker server. Runs on macOS, Linux, and Windows.
5
+ Install Claude Code SessionStart/SessionEnd/Stop hooks that report token usage to a self-hosted tracker server. Runs on macOS, Linux, and Windows.
6
6
 
7
7
  ## Quick start
8
8
 
@@ -24,12 +24,13 @@ After installation, the binary is also available as `tracker` (if installed glob
24
24
 
25
25
  ## What it does
26
26
 
27
- `setup` writes a config file to `~/.config/ccusage-tracker/config.json` and adds two hooks to your Claude Code `~/.claude/settings.json`:
27
+ `setup` writes a config file to `~/.config/ccusage-tracker/config.json` and adds three hooks to your Claude Code `~/.claude/settings.json`:
28
28
 
29
29
  - **SessionStart** — records the model at session start
30
- - **SessionEnd** — POSTs token usage (and session metrics in 0.2.1+) to your team's tracker server
30
+ - **Stop** — primary reporting path: POSTs token usage + session metrics after each assistant turn, throttled to once per 5 minutes
31
+ - **SessionEnd** — backup path: same payload at session exit, in case Stop missed the last window
31
32
 
32
- Hook scripts are downloaded from your tracker server, so they always match the server version.
33
+ Hook scripts are downloaded from your tracker server, so they always match the server version. Re-running `setup` migrates old (no `--mode`) commands and unquoted tracker hook paths in-place — no manual cleanup needed.
33
34
 
34
35
  ## Requirements
35
36
 
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  // src/commands/setup.ts
4
4
  import { createInterface } from "node:readline";
5
+ import { spawnSync } from "node:child_process";
5
6
 
6
7
  // src/config.ts
7
8
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
@@ -36,21 +37,28 @@ function writeConfig(config) {
36
37
  import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, copyFileSync, mkdirSync as mkdirSync2 } from "node:fs";
37
38
  import { join as join2 } from "node:path";
38
39
  import { homedir as homedir2 } from "node:os";
39
- var HOOK_TIMEOUT_SEC = 25;
40
+ var HOOK_TIMEOUT_SEC = 45;
40
41
  function getClaudeSettingsPath() {
41
42
  return join2(homedir2(), ".claude", "settings.json");
42
43
  }
43
44
  function sessionEndScriptPath() {
44
45
  return join2(homedir2(), ".config", "ccusage-tracker", "session-end.mjs");
45
46
  }
47
+ function quoteScriptPath(path) {
48
+ return `"${path}"`;
49
+ }
50
+ function buildHookCommand(scriptPath, mode) {
51
+ const command = "node " + quoteScriptPath(scriptPath);
52
+ return mode ? `${command} --mode=${mode}` : command;
53
+ }
46
54
  function getHookCommand() {
47
- return "node " + sessionEndScriptPath() + " --mode=session-end";
55
+ return buildHookCommand(sessionEndScriptPath(), "session-end");
48
56
  }
49
57
  function getStopHookCommand() {
50
- return "node " + sessionEndScriptPath() + " --mode=stop";
58
+ return buildHookCommand(sessionEndScriptPath(), "stop");
51
59
  }
52
60
  function getStartHookCommand() {
53
- return "node " + join2(homedir2(), ".config", "ccusage-tracker", "session-start.mjs");
61
+ return buildHookCommand(join2(homedir2(), ".config", "ccusage-tracker", "session-start.mjs"));
54
62
  }
55
63
  function isCcusageTrackerHook(command) {
56
64
  return !!command && command.includes("ccusage-tracker");
@@ -179,8 +187,7 @@ async function defaultCheckServer(serverUrl) {
179
187
  }
180
188
  function defaultCheckCcusage() {
181
189
  try {
182
- Bun.spawnSync(["ccusage", "--version"]);
183
- return true;
190
+ return spawnSync("ccusage --version", { encoding: "utf8", shell: true, timeout: 1e4 }).status === 0;
184
191
  } catch {
185
192
  return false;
186
193
  }
@@ -363,6 +370,7 @@ async function reportCommand(args) {
363
370
 
364
371
  // src/commands/status.ts
365
372
  import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
373
+ import { spawnSync as spawnSync2 } from "node:child_process";
366
374
  import { join as join3, dirname } from "node:path";
367
375
  async function statusCommand() {
368
376
  const configPath = getConfigPath();
@@ -408,12 +416,36 @@ Hook: ${hookInstalled ? "installed" : "not installed"}`);
408
416
  } else {
409
417
  console.log("Buffer: none");
410
418
  }
419
+ const configDir = dirname(configPath);
420
+ const lastErrorPath = join3(configDir, "last-error.txt");
421
+ if (existsSync3(lastErrorPath)) {
422
+ console.log(`
423
+ Last upload: FAILED - ${readFileSync3(lastErrorPath, "utf-8").trim()}`);
424
+ console.log(" 用量目前並未上報。請量測 ccusage 耗時,若接近 25s 需再放寬 timeout:");
425
+ console.log(" time ccusage daily --json --since $(date +%Y%m%d)");
426
+ } else {
427
+ console.log(`
428
+ Last upload: no recorded failure`);
429
+ }
430
+ const lastFlushPath = join3(configDir, "last-flush.txt");
431
+ if (existsSync3(lastFlushPath)) {
432
+ const ts = parseInt(readFileSync3(lastFlushPath, "utf-8").trim(), 10);
433
+ if (!isNaN(ts)) {
434
+ const ageMin = Math.floor((Date.now() - ts) / 60000);
435
+ console.log(`Last hook run: ${new Date(ts).toISOString()} (${ageMin} 分鐘前)`);
436
+ }
437
+ }
438
+ const probe = probeCcusage();
439
+ console.log(probe ? `ccusage: installed (${probe})` : "ccusage: not found (install with: npx ccusage@latest)");
440
+ }
441
+ function probeCcusage() {
411
442
  try {
412
- const result = Bun.spawnSync(["ccusage", "--version"]);
413
- const version = new TextDecoder().decode(result.stdout).trim();
414
- console.log(`ccusage: installed (${version || "version unknown"})`);
443
+ const r = spawnSync2("ccusage --version", { encoding: "utf8", shell: true, timeout: 1e4 });
444
+ if (r.status !== 0 || !r.stdout)
445
+ return null;
446
+ return r.stdout.trim() || "version unknown";
415
447
  } catch {
416
- console.log("ccusage: not found (install with: npx ccusage@latest)");
448
+ return null;
417
449
  }
418
450
  }
419
451
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccusage-tracker",
3
- "version": "0.1.2",
3
+ "version": "0.1.5",
4
4
  "description": "CLI for ccusage-tracker — install Claude Code SessionStart/SessionEnd/Stop hooks to report token usage to a self-hosted team tracker.",
5
5
  "type": "module",
6
6
  "bin": {