clanker-stats 0.3.2 → 0.4.0

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 (2) hide show
  1. package/cli.js +22 -12
  2. package/package.json +1 -1
package/cli.js CHANGED
@@ -41,18 +41,19 @@ async function collectClaude() {
41
41
  async function collectCodex() {
42
42
  const counts = new Map()
43
43
  const dir = join(home, ".codex", "sessions")
44
- for (const path of await fg("*/*/*/*rollout-*.jsonl", { cwd: dir })) {
44
+ for (const path of await fg("**/*.jsonl", { cwd: dir })) {
45
45
  const text = await readFile(join(dir, path), "utf-8")
46
- let prevCumulative = -1
46
+ let prevCumulative = null
47
47
  for (const line of text.split("\n")) {
48
48
  if (!line.includes('"token_count"')) continue
49
49
  try {
50
50
  const obj = JSON.parse(line)
51
51
  if (obj.payload?.type !== "token_count") continue
52
- const cumulative = obj.payload.info?.total_token_usage?.total_tokens ?? -1
53
- if (cumulative === prevCumulative) continue
54
- prevCumulative = cumulative
55
- const tokens = obj.payload.info?.last_token_usage?.total_tokens
52
+ const cumTotal = obj.payload.info?.total_token_usage?.total_tokens
53
+ if (cumTotal != null && cumTotal === prevCumulative) continue
54
+ let tokens = obj.payload.info?.last_token_usage?.total_tokens
55
+ if (!tokens && cumTotal != null && prevCumulative != null) tokens = cumTotal - prevCumulative
56
+ if (cumTotal != null) prevCumulative = cumTotal
56
57
  if (tokens > 0 && obj.timestamp) add(counts, isoToDate(obj.timestamp), tokens)
57
58
  } catch {}
58
59
  }
@@ -62,10 +63,15 @@ async function collectCodex() {
62
63
 
63
64
  async function collectOpenCode() {
64
65
  const counts = new Map()
65
- const dir = join(home, ".local", "share", "opencode", "storage", "message")
66
- for (const path of await fg("ses_*/msg_*.json", { cwd: dir })) {
66
+ const dirs = [
67
+ join(home, ".local", "share", "opencode", "storage", "message"),
68
+ ...(process.platform === "darwin" ? [join(home, "Library", "Application Support", "opencode", "storage", "message")] : []),
69
+ ]
70
+ for (const dir of dirs)
71
+ for (const path of await fg("ses_*/msg_*.json", { cwd: dir, suppressErrors: true })) {
67
72
  try {
68
73
  const obj = JSON.parse(await readFile(join(dir, path), "utf-8"))
74
+ if (obj.role !== "assistant") continue
69
75
  const t = obj.tokens
70
76
  if (!t) continue
71
77
  const tokens = (t.input || 0) + (t.output || 0) + (t.reasoning || 0) +
@@ -79,12 +85,15 @@ async function collectOpenCode() {
79
85
  async function collectGemini() {
80
86
  const counts = new Map()
81
87
  const dir = join(home, ".gemini", "tmp")
82
- for (const path of await fg("*/chats/session-*.json", { cwd: dir })) {
88
+ for (const path of await fg("*/chats/*.json", { cwd: dir })) {
83
89
  try {
84
90
  const obj = JSON.parse(await readFile(join(dir, path), "utf-8"))
85
91
  if (!obj.messages) continue
86
92
  for (const msg of obj.messages) {
87
- if (msg.tokens?.total && msg.timestamp) add(counts, isoToDate(msg.timestamp), msg.tokens.total)
93
+ if (msg.type !== "gemini") continue
94
+ const t = msg.tokens
95
+ const tk = t?.total || ((t?.input || 0) + (t?.tool || 0) + (t?.output || 0) + (t?.thoughts || 0) + (t?.cached || 0))
96
+ if (tk > 0 && msg.timestamp) add(counts, isoToDate(msg.timestamp), tk)
88
97
  }
89
98
  } catch {}
90
99
  }
@@ -114,14 +123,15 @@ async function collectAmp() {
114
123
  async function collectPi() {
115
124
  const counts = new Map()
116
125
  const dir = join(home, ".pi", "agent", "sessions")
117
- for (const path of await fg("*/*.jsonl", { cwd: dir })) {
126
+ for (const path of await fg("**/*.jsonl", { cwd: dir })) {
118
127
  const text = await readFile(join(dir, path), "utf-8")
119
128
  for (const line of text.split("\n")) {
120
129
  if (!line.includes('"usage"')) continue
121
130
  try {
122
131
  const obj = JSON.parse(line)
123
132
  if (obj.type !== "message") continue
124
- const tokens = obj.message?.usage?.totalTokens
133
+ const u = obj.message?.usage || obj.usage
134
+ const tokens = u?.totalTokens || ((u?.input || 0) + (u?.output || 0) + (u?.reasoning || 0) + (u?.cacheRead || 0) + (u?.cacheWrite || 0))
125
135
  if (tokens > 0 && obj.timestamp) add(counts, isoToDate(obj.timestamp), tokens)
126
136
  } catch {}
127
137
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clanker-stats",
3
- "version": "0.3.2",
3
+ "version": "0.4.0",
4
4
  "description": "See how many tokens you've burned across AI coding tools",
5
5
  "type": "module",
6
6
  "bin": {