opencode-usage 0.1.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.
package/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # opencode-usage
2
+
3
+ CLI tool for tracking [OpenCode](https://github.com/sst/opencode) AI coding assistant usage and costs.
4
+
5
+ ## Features
6
+
7
+ - Daily usage breakdown with token counts and estimated costs
8
+ - Provider breakdown (Anthropic, OpenAI, Google, etc.)
9
+ - Filter by provider or time range
10
+ - Model pricing for accurate cost estimation
11
+ - Terminal table output
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ # Run directly with npx (no install needed)
17
+ npx opencode-usage
18
+
19
+ # Or with bunx (faster)
20
+ bunx opencode-usage
21
+
22
+ # Or install globally
23
+ npm install -g opencode-usage
24
+ bun add -g opencode-usage
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ ```bash
30
+ # Show all usage data
31
+ opencode-usage
32
+
33
+ # Filter by provider
34
+ opencode-usage --provider anthropic
35
+ opencode-usage -p openai
36
+
37
+ # Show last N days
38
+ opencode-usage --days 30
39
+ opencode-usage -d 7
40
+
41
+ # Combine filters
42
+ opencode-usage --provider anthropic --days 7
43
+ ```
44
+
45
+ ## Output
46
+
47
+ ```
48
+ ┌────────────┬───────────────────────────────────┬────────────────┬──────────────┬────────────────┬────────────┐
49
+ │ Date │ Models │ Input │ Output │ Total Tokens │ Cost │
50
+ ├────────────┼───────────────────────────────────┼────────────────┼──────────────┼────────────────┼────────────┤
51
+ │ 2025-12-30 │ - claude-opus-4-5 │ 173,440,372 │ 691,955 │ 174,132,327 │ $167.42 │
52
+ │ │ - claude-sonnet-4-5 │ │ │ │ │
53
+ │ │ [anthropic] │ 161,029,288 │ 618,355 │ 161,647,643 │ $162.06 │
54
+ │ │ [openai] │ 7,109,638 │ 56,201 │ 7,165,839 │ $5.36 │
55
+ ├────────────┼───────────────────────────────────┼────────────────┼──────────────┼────────────────┼────────────┤
56
+ │ Total │ │ 395,521,798 │ 1,617,158 │ 397,138,956 │ $417.81 │
57
+ └────────────┴───────────────────────────────────┴────────────────┴──────────────┴────────────────┴────────────┘
58
+ ```
59
+
60
+ ## Supported Providers
61
+
62
+ - **Anthropic**: Claude Opus, Sonnet, Haiku (all versions)
63
+ - **OpenAI**: GPT-4o, GPT-5, O1, O3
64
+ - **Google**: Gemini 2.0, 2.5, 3.0
65
+ - **OpenCode hosted**: Free models (qwen3-coder, glm-4.7-free, etc.)
66
+
67
+ ## How It Works
68
+
69
+ This tool reads OpenCode session data from:
70
+
71
+ - Linux: `~/.local/share/opencode/storage/`
72
+ - macOS: `~/.local/share/opencode/storage/`
73
+ - Windows: `%LOCALAPPDATA%/opencode/storage/`
74
+
75
+ It aggregates token usage by day and calculates estimated costs based on current API pricing.
76
+
77
+ ## Note on Costs
78
+
79
+ If you're using OpenCode with a Claude Max/Pro subscription or OpenCode Zen credits, the actual cost to you is your subscription fee, not the API-equivalent cost shown here. The cost column shows what the equivalent API usage would cost for reference.
80
+
81
+ ## License
82
+
83
+ MIT
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Data aggregation functions
3
+ */
4
+ import type { DailyStats, MessageJson } from "./types.js";
5
+ export declare function aggregateByDate(messages: MessageJson[]): Map<string, DailyStats>;
6
+ export declare function filterByDays(dailyStats: Map<string, DailyStats>, days: number): Map<string, DailyStats>;
package/dist/cli.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * CLI argument parser using Node.js parseArgs (works with both Bun and Node.js)
3
+ */
4
+ export type CliArgs = {
5
+ provider?: string;
6
+ days?: number;
7
+ };
8
+ export declare function parseArgs(): CliArgs;
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * OpenCode Usage - CLI tool for tracking OpenCode AI usage and costs
4
+ *
5
+ * Usage:
6
+ * bunx opencode-usage
7
+ * bunx opencode-usage --provider anthropic
8
+ * bunx opencode-usage --days 30
9
+ */
10
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,460 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { parseArgs as nodeParseArgs } from "node:util";
5
+ function getArgs() {
6
+ if (typeof globalThis.Bun !== "undefined") {
7
+ return Bun.argv.slice(2);
8
+ }
9
+ return process.argv.slice(2);
10
+ }
11
+ function parseArgs() {
12
+ try {
13
+ const { values } = nodeParseArgs({
14
+ args: getArgs(),
15
+ options: {
16
+ provider: { type: "string", short: "p" },
17
+ days: { type: "string", short: "d" },
18
+ help: { type: "boolean", short: "h" }
19
+ },
20
+ strict: true
21
+ });
22
+ if (values.help) {
23
+ printHelp();
24
+ process.exit(0);
25
+ }
26
+ return {
27
+ provider: values.provider?.toLowerCase(),
28
+ days: values.days ? parseInt(values.days, 10) : undefined
29
+ };
30
+ } catch (error) {
31
+ if (error instanceof Error && error.message.includes("Unknown option")) {
32
+ console.error(`Error: ${error.message}`);
33
+ printHelp();
34
+ process.exit(1);
35
+ }
36
+ throw error;
37
+ }
38
+ }
39
+ function printHelp() {
40
+ console.log(`
41
+ opencode-usage - Track OpenCode AI coding assistant usage and costs
42
+
43
+ Usage:
44
+ bunx opencode-usage [options]
45
+
46
+ Options:
47
+ -p, --provider <name> Filter by provider (anthropic, openai, google, opencode)
48
+ -d, --days <n> Show only last N days
49
+ -h, --help Show this help message
50
+
51
+ Examples:
52
+ bunx opencode-usage
53
+ bunx opencode-usage --provider anthropic
54
+ bunx opencode-usage -p openai -d 30
55
+ `);
56
+ }
57
+
58
+ // src/loader.ts
59
+ import { readFileSync, readdirSync, statSync } from "node:fs";
60
+ import { homedir } from "node:os";
61
+ import { join } from "node:path";
62
+ var isBun = typeof globalThis.Bun !== "undefined";
63
+ function getOpenCodeStoragePath() {
64
+ const xdgDataHome = process.env.XDG_DATA_HOME ?? join(homedir(), ".local", "share");
65
+ return join(xdgDataHome, "opencode", "storage");
66
+ }
67
+ async function readJsonFile(filePath) {
68
+ if (isBun) {
69
+ return Bun.file(filePath).json();
70
+ }
71
+ const content = readFileSync(filePath, "utf-8");
72
+ return JSON.parse(content);
73
+ }
74
+ async function loadMessages(storagePath, providerFilter) {
75
+ const messagesDir = join(storagePath, "message");
76
+ const messages = [];
77
+ try {
78
+ const sessionDirs = readdirSync(messagesDir);
79
+ for (const sessionDir of sessionDirs) {
80
+ const sessionPath = join(messagesDir, sessionDir);
81
+ const stat = statSync(sessionPath);
82
+ if (!stat.isDirectory())
83
+ continue;
84
+ const messageFiles = readdirSync(sessionPath).filter((f) => f.endsWith(".json"));
85
+ for (const messageFile of messageFiles) {
86
+ try {
87
+ const messagePath = join(sessionPath, messageFile);
88
+ const msg = await readJsonFile(messagePath);
89
+ if (msg.role === "user")
90
+ continue;
91
+ if (!msg.tokens)
92
+ continue;
93
+ const providerId = msg.model?.providerID ?? msg.providerID ?? "unknown";
94
+ if (providerFilter && providerId.toLowerCase() !== providerFilter) {
95
+ continue;
96
+ }
97
+ messages.push(msg);
98
+ } catch {}
99
+ }
100
+ }
101
+ } catch (err) {
102
+ console.error(`Error reading messages directory: ${err}`);
103
+ }
104
+ return messages;
105
+ }
106
+
107
+ // src/pricing.ts
108
+ var MODEL_PRICING = {
109
+ "claude-opus-4-5": {
110
+ input: 5,
111
+ output: 25,
112
+ cacheWrite: 6.25,
113
+ cacheRead: 0.5
114
+ },
115
+ "claude-sonnet-4-5": {
116
+ input: 3,
117
+ output: 15,
118
+ cacheWrite: 3.75,
119
+ cacheRead: 0.3
120
+ },
121
+ "claude-haiku-4-5": {
122
+ input: 1,
123
+ output: 5,
124
+ cacheWrite: 1.25,
125
+ cacheRead: 0.1
126
+ },
127
+ "claude-opus-4": {
128
+ input: 15,
129
+ output: 75,
130
+ cacheWrite: 18.75,
131
+ cacheRead: 1.5
132
+ },
133
+ "claude-sonnet-4": {
134
+ input: 3,
135
+ output: 15,
136
+ cacheWrite: 3.75,
137
+ cacheRead: 0.3
138
+ },
139
+ "claude-opus-4-1": {
140
+ input: 15,
141
+ output: 75,
142
+ cacheWrite: 18.75,
143
+ cacheRead: 1.5
144
+ },
145
+ "claude-opus-3": {
146
+ input: 15,
147
+ output: 75,
148
+ cacheWrite: 18.75,
149
+ cacheRead: 1.5
150
+ },
151
+ "claude-haiku-3": {
152
+ input: 0.25,
153
+ output: 1.25,
154
+ cacheWrite: 0.3,
155
+ cacheRead: 0.03
156
+ },
157
+ "gpt-4o": {
158
+ input: 2.5,
159
+ output: 10,
160
+ cacheWrite: 0,
161
+ cacheRead: 0
162
+ },
163
+ "gpt-4o-mini": {
164
+ input: 0.15,
165
+ output: 0.6,
166
+ cacheWrite: 0,
167
+ cacheRead: 0
168
+ },
169
+ "gpt-4-turbo": {
170
+ input: 10,
171
+ output: 30,
172
+ cacheWrite: 0,
173
+ cacheRead: 0
174
+ },
175
+ "gpt-5": {
176
+ input: 5,
177
+ output: 15,
178
+ cacheWrite: 0,
179
+ cacheRead: 0
180
+ },
181
+ "gpt-5.2": {
182
+ input: 5,
183
+ output: 15,
184
+ cacheWrite: 0,
185
+ cacheRead: 0
186
+ },
187
+ o1: {
188
+ input: 15,
189
+ output: 60,
190
+ cacheWrite: 0,
191
+ cacheRead: 0
192
+ },
193
+ "o1-mini": {
194
+ input: 3,
195
+ output: 12,
196
+ cacheWrite: 0,
197
+ cacheRead: 0
198
+ },
199
+ "o1-pro": {
200
+ input: 150,
201
+ output: 600,
202
+ cacheWrite: 0,
203
+ cacheRead: 0
204
+ },
205
+ o3: {
206
+ input: 10,
207
+ output: 40,
208
+ cacheWrite: 0,
209
+ cacheRead: 0
210
+ },
211
+ "o3-mini": {
212
+ input: 1.1,
213
+ output: 4.4,
214
+ cacheWrite: 0,
215
+ cacheRead: 0
216
+ },
217
+ "gemini-2.0-flash": {
218
+ input: 0.1,
219
+ output: 0.4,
220
+ cacheWrite: 0,
221
+ cacheRead: 0
222
+ },
223
+ "gemini-2.5-pro": {
224
+ input: 1.25,
225
+ output: 10,
226
+ cacheWrite: 0,
227
+ cacheRead: 0
228
+ },
229
+ "gemini-2.5-flash": {
230
+ input: 0.15,
231
+ output: 0.6,
232
+ cacheWrite: 0,
233
+ cacheRead: 0
234
+ },
235
+ "gemini-3-flash-preview": {
236
+ input: 0.15,
237
+ output: 0.6,
238
+ cacheWrite: 0,
239
+ cacheRead: 0
240
+ },
241
+ "qwen3-coder": {
242
+ input: 0,
243
+ output: 0,
244
+ cacheWrite: 0,
245
+ cacheRead: 0
246
+ },
247
+ "glm-4.7-free": {
248
+ input: 0,
249
+ output: 0,
250
+ cacheWrite: 0,
251
+ cacheRead: 0
252
+ },
253
+ "minimax-m2.1-free": {
254
+ input: 0,
255
+ output: 0,
256
+ cacheWrite: 0,
257
+ cacheRead: 0
258
+ }
259
+ };
260
+ var DEFAULT_PRICING = {
261
+ input: 3,
262
+ output: 15,
263
+ cacheWrite: 3.75,
264
+ cacheRead: 0.3
265
+ };
266
+ function getModelPricing(modelId) {
267
+ const normalized = modelId.toLowerCase().replace(/_/g, "-");
268
+ if (MODEL_PRICING[normalized]) {
269
+ return MODEL_PRICING[normalized];
270
+ }
271
+ for (const [key, pricing] of Object.entries(MODEL_PRICING)) {
272
+ if (normalized.includes(key) || key.includes(normalized)) {
273
+ return pricing;
274
+ }
275
+ }
276
+ return DEFAULT_PRICING;
277
+ }
278
+ function calculateCost(tokens, modelId) {
279
+ const pricing = getModelPricing(modelId);
280
+ const inputCost = tokens.input / 1e6 * pricing.input;
281
+ const outputCost = tokens.output / 1e6 * pricing.output;
282
+ const cacheWriteCost = tokens.cache.write / 1e6 * pricing.cacheWrite;
283
+ const cacheReadCost = tokens.cache.read / 1e6 * pricing.cacheRead;
284
+ const reasoningCost = tokens.reasoning / 1e6 * pricing.output;
285
+ return inputCost + outputCost + cacheWriteCost + cacheReadCost + reasoningCost;
286
+ }
287
+
288
+ // src/aggregator.ts
289
+ function timestampToDate(timestamp) {
290
+ return new Date(timestamp).toISOString().split("T")[0];
291
+ }
292
+ function aggregateByDate(messages) {
293
+ const dailyStats = new Map;
294
+ for (const msg of messages) {
295
+ const timestamp = msg.time?.created ?? msg.time?.completed;
296
+ if (!timestamp)
297
+ continue;
298
+ const date = timestampToDate(timestamp);
299
+ const modelId = msg.model?.modelID ?? msg.modelID ?? "unknown";
300
+ const providerId = msg.model?.providerID ?? msg.providerID ?? "unknown";
301
+ const tokens = msg.tokens;
302
+ const msgCost = calculateCost(tokens, modelId);
303
+ let stats = dailyStats.get(date);
304
+ if (!stats) {
305
+ stats = {
306
+ date,
307
+ models: new Set,
308
+ providers: new Set,
309
+ providerStats: new Map,
310
+ input: 0,
311
+ output: 0,
312
+ cacheWrite: 0,
313
+ cacheRead: 0,
314
+ reasoning: 0,
315
+ cost: 0
316
+ };
317
+ dailyStats.set(date, stats);
318
+ }
319
+ stats.models.add(modelId);
320
+ stats.providers.add(providerId);
321
+ stats.input += tokens.input ?? 0;
322
+ stats.output += tokens.output ?? 0;
323
+ stats.cacheWrite += tokens.cache?.write ?? 0;
324
+ stats.cacheRead += tokens.cache?.read ?? 0;
325
+ stats.reasoning += tokens.reasoning ?? 0;
326
+ stats.cost += msgCost;
327
+ let providerStat = stats.providerStats.get(providerId);
328
+ if (!providerStat) {
329
+ providerStat = {
330
+ input: 0,
331
+ output: 0,
332
+ cacheWrite: 0,
333
+ cacheRead: 0,
334
+ reasoning: 0,
335
+ cost: 0,
336
+ models: new Set
337
+ };
338
+ stats.providerStats.set(providerId, providerStat);
339
+ }
340
+ providerStat.models.add(modelId);
341
+ providerStat.input += tokens.input ?? 0;
342
+ providerStat.output += tokens.output ?? 0;
343
+ providerStat.cacheWrite += tokens.cache?.write ?? 0;
344
+ providerStat.cacheRead += tokens.cache?.read ?? 0;
345
+ providerStat.reasoning += tokens.reasoning ?? 0;
346
+ providerStat.cost += msgCost;
347
+ }
348
+ return dailyStats;
349
+ }
350
+ function filterByDays(dailyStats, days) {
351
+ const cutoffDate = new Date;
352
+ cutoffDate.setDate(cutoffDate.getDate() - days);
353
+ const cutoffStr = cutoffDate.toISOString().split("T")[0];
354
+ const filtered = new Map;
355
+ for (const [date, stats] of dailyStats) {
356
+ if (date >= cutoffStr) {
357
+ filtered.set(date, stats);
358
+ }
359
+ }
360
+ return filtered;
361
+ }
362
+
363
+ // src/renderer.ts
364
+ function formatNumber(num) {
365
+ return num.toLocaleString("en-US");
366
+ }
367
+ function formatCost(cost) {
368
+ return `$${cost.toFixed(2)}`;
369
+ }
370
+ function padRight(str, len) {
371
+ return str.padEnd(len);
372
+ }
373
+ function padLeft(str, len) {
374
+ return str.padStart(len);
375
+ }
376
+ function renderTable(dailyStats) {
377
+ const sortedDates = Array.from(dailyStats.keys()).sort((a, b) => a.localeCompare(b));
378
+ if (sortedDates.length === 0) {
379
+ console.log(`
380
+ No usage data found.
381
+ `);
382
+ return;
383
+ }
384
+ const colDate = 12;
385
+ const colModels = 35;
386
+ const colInput = 16;
387
+ const colOutput = 14;
388
+ const colTotal = 16;
389
+ const colCost = 12;
390
+ const h = "─";
391
+ const v = "│";
392
+ const tl = "┌";
393
+ const tr = "┐";
394
+ const bl = "└";
395
+ const br = "┘";
396
+ const ml = "├";
397
+ const mr = "┤";
398
+ const mt = "┬";
399
+ const mb = "┴";
400
+ const mm = "┼";
401
+ const topLine = tl + h.repeat(colDate) + mt + h.repeat(colModels) + mt + h.repeat(colInput) + mt + h.repeat(colOutput) + mt + h.repeat(colTotal) + mt + h.repeat(colCost) + tr;
402
+ const midLine = ml + h.repeat(colDate) + mm + h.repeat(colModels) + mm + h.repeat(colInput) + mm + h.repeat(colOutput) + mm + h.repeat(colTotal) + mm + h.repeat(colCost) + mr;
403
+ const bottomLine = bl + h.repeat(colDate) + mb + h.repeat(colModels) + mb + h.repeat(colInput) + mb + h.repeat(colOutput) + mb + h.repeat(colTotal) + mb + h.repeat(colCost) + br;
404
+ const header = v + padRight(" Date", colDate) + v + padRight(" Models", colModels) + v + padLeft("Input ", colInput) + v + padLeft("Output ", colOutput) + v + padLeft("Total Tokens ", colTotal) + v + padLeft("Cost ", colCost) + v;
405
+ console.log(`
406
+ ` + topLine);
407
+ console.log(header);
408
+ console.log(midLine);
409
+ let totalInput = 0;
410
+ let totalOutput = 0;
411
+ let totalCost = 0;
412
+ for (const date of sortedDates) {
413
+ const stats = dailyStats.get(date);
414
+ const models = Array.from(stats.models).sort();
415
+ const combinedInput = stats.input + stats.cacheRead + stats.cacheWrite;
416
+ const totalTokens = combinedInput + stats.output;
417
+ totalInput += combinedInput;
418
+ totalOutput += stats.output;
419
+ totalCost += stats.cost;
420
+ const firstModel = models[0] ? `- ${models[0]}` : "";
421
+ console.log(v + padRight(` ${date}`, colDate) + v + padRight(` ${firstModel}`, colModels) + v + padLeft(`${formatNumber(combinedInput)} `, colInput) + v + padLeft(`${formatNumber(stats.output)} `, colOutput) + v + padLeft(`${formatNumber(totalTokens)} `, colTotal) + v + padLeft(`${formatCost(stats.cost)} `, colCost) + v);
422
+ for (let i = 1;i < models.length; i++) {
423
+ console.log(v + " ".repeat(colDate) + v + padRight(` - ${models[i]}`, colModels) + v + " ".repeat(colInput) + v + " ".repeat(colOutput) + v + " ".repeat(colTotal) + v + " ".repeat(colCost) + v);
424
+ }
425
+ const providers = Array.from(stats.providerStats.entries()).sort((a, b) => b[1].cost - a[1].cost);
426
+ for (const [providerId, providerStat] of providers) {
427
+ const providerInput = providerStat.input + providerStat.cacheRead + providerStat.cacheWrite;
428
+ const providerTokens = providerInput + providerStat.output;
429
+ console.log(v + " ".repeat(colDate) + v + padRight(` [${providerId}]`, colModels) + v + padLeft(`${formatNumber(providerInput)} `, colInput) + v + padLeft(`${formatNumber(providerStat.output)} `, colOutput) + v + padLeft(`${formatNumber(providerTokens)} `, colTotal) + v + padLeft(`${formatCost(providerStat.cost)} `, colCost) + v);
430
+ }
431
+ console.log(midLine);
432
+ }
433
+ const grandTotal = totalInput + totalOutput;
434
+ console.log(v + padRight(" Total", colDate) + v + " ".repeat(colModels) + v + padLeft(`${formatNumber(totalInput)} `, colInput) + v + padLeft(`${formatNumber(totalOutput)} `, colOutput) + v + padLeft(`${formatNumber(grandTotal)} `, colTotal) + v + padLeft(`${formatCost(totalCost)} `, colCost) + v);
435
+ console.log(bottomLine);
436
+ console.log();
437
+ }
438
+
439
+ // src/index.ts
440
+ async function main() {
441
+ const { provider, days } = parseArgs();
442
+ const storagePath = getOpenCodeStoragePath();
443
+ console.log(`
444
+ Loading OpenCode usage data from: ${storagePath}`);
445
+ if (provider) {
446
+ console.log(`Filtering: ${provider} provider only`);
447
+ }
448
+ const messages = await loadMessages(storagePath, provider);
449
+ console.log(`Found ${messages.length} assistant messages with token data`);
450
+ let dailyStats = aggregateByDate(messages);
451
+ if (days) {
452
+ dailyStats = filterByDays(dailyStats, days);
453
+ console.log(`Showing last ${days} days`);
454
+ }
455
+ renderTable(dailyStats);
456
+ }
457
+ main().catch(console.error);
458
+
459
+ //# debugId=CF9F8AC2D659ACE464756E2164756E21
460
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,15 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/cli.ts", "../src/loader.ts", "../src/pricing.ts", "../src/aggregator.ts", "../src/renderer.ts", "../src/index.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * CLI argument parser using Node.js parseArgs (works with both Bun and Node.js)\n */\n\nimport { parseArgs as nodeParseArgs } from \"node:util\";\n\nexport type CliArgs = {\n provider?: string;\n days?: number;\n};\n\n// Get CLI args - works with both Bun and Node.js\nfunction getArgs(): string[] {\n if (typeof globalThis.Bun !== \"undefined\") {\n return Bun.argv.slice(2);\n }\n return process.argv.slice(2);\n}\n\nexport function parseArgs(): CliArgs {\n try {\n const { values } = nodeParseArgs({\n args: getArgs(),\n options: {\n provider: { type: \"string\", short: \"p\" },\n days: { type: \"string\", short: \"d\" },\n help: { type: \"boolean\", short: \"h\" },\n },\n strict: true,\n });\n\n if (values.help) {\n printHelp();\n process.exit(0);\n }\n\n return {\n provider: values.provider?.toLowerCase(),\n days: values.days ? parseInt(values.days, 10) : undefined,\n };\n } catch (error) {\n if (error instanceof Error && error.message.includes(\"Unknown option\")) {\n console.error(`Error: ${error.message}`);\n printHelp();\n process.exit(1);\n }\n throw error;\n }\n}\n\nfunction printHelp(): void {\n console.log(`\nopencode-usage - Track OpenCode AI coding assistant usage and costs\n\nUsage:\n bunx opencode-usage [options]\n\nOptions:\n -p, --provider <name> Filter by provider (anthropic, openai, google, opencode)\n -d, --days <n> Show only last N days\n -h, --help Show this help message\n\nExamples:\n bunx opencode-usage\n bunx opencode-usage --provider anthropic\n bunx opencode-usage -p openai -d 30\n`);\n}\n",
6
+ "/**\n * OpenCode storage data loader - works with both Bun and Node.js\n */\n\nimport { readFileSync, readdirSync, statSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport type { MessageJson } from \"./types.js\";\n\n// Runtime detection\nconst isBun = typeof globalThis.Bun !== \"undefined\";\n\nexport function getOpenCodeStoragePath(): string {\n const xdgDataHome =\n process.env.XDG_DATA_HOME ?? join(homedir(), \".local\", \"share\");\n return join(xdgDataHome, \"opencode\", \"storage\");\n}\n\nasync function readJsonFile(filePath: string): Promise<MessageJson> {\n if (isBun) {\n return Bun.file(filePath).json() as Promise<MessageJson>;\n }\n const content = readFileSync(filePath, \"utf-8\");\n return JSON.parse(content) as MessageJson;\n}\n\nexport async function loadMessages(\n storagePath: string,\n providerFilter?: string\n): Promise<MessageJson[]> {\n const messagesDir = join(storagePath, \"message\");\n const messages: MessageJson[] = [];\n\n try {\n const sessionDirs = readdirSync(messagesDir);\n\n for (const sessionDir of sessionDirs) {\n const sessionPath = join(messagesDir, sessionDir);\n const stat = statSync(sessionPath);\n\n if (!stat.isDirectory()) continue;\n\n const messageFiles = readdirSync(sessionPath).filter((f) =>\n f.endsWith(\".json\")\n );\n\n for (const messageFile of messageFiles) {\n try {\n const messagePath = join(sessionPath, messageFile);\n const msg = await readJsonFile(messagePath);\n\n if (msg.role === \"user\") continue;\n if (!msg.tokens) continue;\n\n const providerId =\n msg.model?.providerID ?? msg.providerID ?? \"unknown\";\n\n if (providerFilter && providerId.toLowerCase() !== providerFilter) {\n continue;\n }\n\n messages.push(msg);\n } catch {\n // Skip invalid JSON files\n }\n }\n }\n } catch (err) {\n console.error(`Error reading messages directory: ${err}`);\n }\n\n return messages;\n}\n",
7
+ "/**\n * Model pricing configuration (per million tokens)\n */\n\nimport type { ModelPricing, TokenUsage } from \"./types\";\n\nexport const MODEL_PRICING: Record<string, ModelPricing> = {\n // Anthropic - Current Models\n \"claude-opus-4-5\": {\n input: 5,\n output: 25,\n cacheWrite: 6.25,\n cacheRead: 0.5,\n },\n \"claude-sonnet-4-5\": {\n input: 3,\n output: 15,\n cacheWrite: 3.75,\n cacheRead: 0.3,\n },\n \"claude-haiku-4-5\": {\n input: 1,\n output: 5,\n cacheWrite: 1.25,\n cacheRead: 0.1,\n },\n \"claude-opus-4\": {\n input: 15,\n output: 75,\n cacheWrite: 18.75,\n cacheRead: 1.5,\n },\n \"claude-sonnet-4\": {\n input: 3,\n output: 15,\n cacheWrite: 3.75,\n cacheRead: 0.3,\n },\n \"claude-opus-4-1\": {\n input: 15,\n output: 75,\n cacheWrite: 18.75,\n cacheRead: 1.5,\n },\n \"claude-opus-3\": {\n input: 15,\n output: 75,\n cacheWrite: 18.75,\n cacheRead: 1.5,\n },\n \"claude-haiku-3\": {\n input: 0.25,\n output: 1.25,\n cacheWrite: 0.3,\n cacheRead: 0.03,\n },\n\n // OpenAI Models\n \"gpt-4o\": {\n input: 2.5,\n output: 10,\n cacheWrite: 0,\n cacheRead: 0,\n },\n \"gpt-4o-mini\": {\n input: 0.15,\n output: 0.6,\n cacheWrite: 0,\n cacheRead: 0,\n },\n \"gpt-4-turbo\": {\n input: 10,\n output: 30,\n cacheWrite: 0,\n cacheRead: 0,\n },\n \"gpt-5\": {\n input: 5,\n output: 15,\n cacheWrite: 0,\n cacheRead: 0,\n },\n \"gpt-5.2\": {\n input: 5,\n output: 15,\n cacheWrite: 0,\n cacheRead: 0,\n },\n o1: {\n input: 15,\n output: 60,\n cacheWrite: 0,\n cacheRead: 0,\n },\n \"o1-mini\": {\n input: 3,\n output: 12,\n cacheWrite: 0,\n cacheRead: 0,\n },\n \"o1-pro\": {\n input: 150,\n output: 600,\n cacheWrite: 0,\n cacheRead: 0,\n },\n o3: {\n input: 10,\n output: 40,\n cacheWrite: 0,\n cacheRead: 0,\n },\n \"o3-mini\": {\n input: 1.1,\n output: 4.4,\n cacheWrite: 0,\n cacheRead: 0,\n },\n\n // Google Models\n \"gemini-2.0-flash\": {\n input: 0.1,\n output: 0.4,\n cacheWrite: 0,\n cacheRead: 0,\n },\n \"gemini-2.5-pro\": {\n input: 1.25,\n output: 10,\n cacheWrite: 0,\n cacheRead: 0,\n },\n \"gemini-2.5-flash\": {\n input: 0.15,\n output: 0.6,\n cacheWrite: 0,\n cacheRead: 0,\n },\n \"gemini-3-flash-preview\": {\n input: 0.15,\n output: 0.6,\n cacheWrite: 0,\n cacheRead: 0,\n },\n\n // Free/OpenCode hosted models\n \"qwen3-coder\": {\n input: 0,\n output: 0,\n cacheWrite: 0,\n cacheRead: 0,\n },\n \"glm-4.7-free\": {\n input: 0,\n output: 0,\n cacheWrite: 0,\n cacheRead: 0,\n },\n \"minimax-m2.1-free\": {\n input: 0,\n output: 0,\n cacheWrite: 0,\n cacheRead: 0,\n },\n};\n\nconst DEFAULT_PRICING: ModelPricing = {\n input: 3,\n output: 15,\n cacheWrite: 3.75,\n cacheRead: 0.3,\n};\n\nexport function getModelPricing(modelId: string): ModelPricing {\n const normalized = modelId.toLowerCase().replace(/_/g, \"-\");\n\n if (MODEL_PRICING[normalized]) {\n return MODEL_PRICING[normalized];\n }\n\n for (const [key, pricing] of Object.entries(MODEL_PRICING)) {\n if (normalized.includes(key) || key.includes(normalized)) {\n return pricing;\n }\n }\n\n return DEFAULT_PRICING;\n}\n\nexport function calculateCost(tokens: TokenUsage, modelId: string): number {\n const pricing = getModelPricing(modelId);\n\n const inputCost = (tokens.input / 1_000_000) * pricing.input;\n const outputCost = (tokens.output / 1_000_000) * pricing.output;\n const cacheWriteCost = (tokens.cache.write / 1_000_000) * pricing.cacheWrite;\n const cacheReadCost = (tokens.cache.read / 1_000_000) * pricing.cacheRead;\n const reasoningCost = (tokens.reasoning / 1_000_000) * pricing.output;\n\n return (\n inputCost + outputCost + cacheWriteCost + cacheReadCost + reasoningCost\n );\n}\n",
8
+ "/**\n * Data aggregation functions\n */\n\nimport type { DailyStats, MessageJson } from \"./types.js\";\nimport { calculateCost } from \"./pricing\";\n\nfunction timestampToDate(timestamp: number): string {\n return new Date(timestamp).toISOString().split(\"T\")[0];\n}\n\nexport function aggregateByDate(\n messages: MessageJson[]\n): Map<string, DailyStats> {\n const dailyStats = new Map<string, DailyStats>();\n\n for (const msg of messages) {\n const timestamp = msg.time?.created ?? msg.time?.completed;\n if (!timestamp) continue;\n\n const date = timestampToDate(timestamp);\n const modelId = msg.model?.modelID ?? msg.modelID ?? \"unknown\";\n const providerId = msg.model?.providerID ?? msg.providerID ?? \"unknown\";\n const tokens = msg.tokens!;\n const msgCost = calculateCost(tokens, modelId);\n\n let stats = dailyStats.get(date);\n if (!stats) {\n stats = {\n date,\n models: new Set(),\n providers: new Set(),\n providerStats: new Map(),\n input: 0,\n output: 0,\n cacheWrite: 0,\n cacheRead: 0,\n reasoning: 0,\n cost: 0,\n };\n dailyStats.set(date, stats);\n }\n\n // Update daily totals\n stats.models.add(modelId);\n stats.providers.add(providerId);\n stats.input += tokens.input ?? 0;\n stats.output += tokens.output ?? 0;\n stats.cacheWrite += tokens.cache?.write ?? 0;\n stats.cacheRead += tokens.cache?.read ?? 0;\n stats.reasoning += tokens.reasoning ?? 0;\n stats.cost += msgCost;\n\n // Update provider-specific stats\n let providerStat = stats.providerStats.get(providerId);\n if (!providerStat) {\n providerStat = {\n input: 0,\n output: 0,\n cacheWrite: 0,\n cacheRead: 0,\n reasoning: 0,\n cost: 0,\n models: new Set(),\n };\n stats.providerStats.set(providerId, providerStat);\n }\n providerStat.models.add(modelId);\n providerStat.input += tokens.input ?? 0;\n providerStat.output += tokens.output ?? 0;\n providerStat.cacheWrite += tokens.cache?.write ?? 0;\n providerStat.cacheRead += tokens.cache?.read ?? 0;\n providerStat.reasoning += tokens.reasoning ?? 0;\n providerStat.cost += msgCost;\n }\n\n return dailyStats;\n}\n\nexport function filterByDays(\n dailyStats: Map<string, DailyStats>,\n days: number\n): Map<string, DailyStats> {\n const cutoffDate = new Date();\n cutoffDate.setDate(cutoffDate.getDate() - days);\n const cutoffStr = cutoffDate.toISOString().split(\"T\")[0];\n\n const filtered = new Map<string, DailyStats>();\n for (const [date, stats] of dailyStats) {\n if (date >= cutoffStr) {\n filtered.set(date, stats);\n }\n }\n return filtered;\n}\n",
9
+ "/**\n * Terminal table renderer\n */\n\nimport type { DailyStats } from \"./types\";\n\nfunction formatNumber(num: number): string {\n return num.toLocaleString(\"en-US\");\n}\n\nfunction formatCost(cost: number): string {\n return `$${cost.toFixed(2)}`;\n}\n\nfunction padRight(str: string, len: number): string {\n return str.padEnd(len);\n}\n\nfunction padLeft(str: string, len: number): string {\n return str.padStart(len);\n}\n\nexport function renderTable(dailyStats: Map<string, DailyStats>): void {\n const sortedDates = Array.from(dailyStats.keys()).sort((a, b) =>\n a.localeCompare(b)\n );\n\n if (sortedDates.length === 0) {\n console.log(\"\\nNo usage data found.\\n\");\n return;\n }\n\n // Column widths\n const colDate = 12;\n const colModels = 35;\n const colInput = 16;\n const colOutput = 14;\n const colTotal = 16;\n const colCost = 12;\n\n // Border characters\n const h = \"\\u2500\";\n const v = \"\\u2502\";\n const tl = \"\\u250C\";\n const tr = \"\\u2510\";\n const bl = \"\\u2514\";\n const br = \"\\u2518\";\n const ml = \"\\u251C\";\n const mr = \"\\u2524\";\n const mt = \"\\u252C\";\n const mb = \"\\u2534\";\n const mm = \"\\u253C\";\n\n const topLine =\n tl +\n h.repeat(colDate) +\n mt +\n h.repeat(colModels) +\n mt +\n h.repeat(colInput) +\n mt +\n h.repeat(colOutput) +\n mt +\n h.repeat(colTotal) +\n mt +\n h.repeat(colCost) +\n tr;\n\n const midLine =\n ml +\n h.repeat(colDate) +\n mm +\n h.repeat(colModels) +\n mm +\n h.repeat(colInput) +\n mm +\n h.repeat(colOutput) +\n mm +\n h.repeat(colTotal) +\n mm +\n h.repeat(colCost) +\n mr;\n\n const bottomLine =\n bl +\n h.repeat(colDate) +\n mb +\n h.repeat(colModels) +\n mb +\n h.repeat(colInput) +\n mb +\n h.repeat(colOutput) +\n mb +\n h.repeat(colTotal) +\n mb +\n h.repeat(colCost) +\n br;\n\n const header =\n v +\n padRight(\" Date\", colDate) +\n v +\n padRight(\" Models\", colModels) +\n v +\n padLeft(\"Input \", colInput) +\n v +\n padLeft(\"Output \", colOutput) +\n v +\n padLeft(\"Total Tokens \", colTotal) +\n v +\n padLeft(\"Cost \", colCost) +\n v;\n\n console.log(\"\\n\" + topLine);\n console.log(header);\n console.log(midLine);\n\n let totalInput = 0;\n let totalOutput = 0;\n let totalCost = 0;\n\n for (const date of sortedDates) {\n const stats = dailyStats.get(date)!;\n const models = Array.from(stats.models).sort();\n\n const combinedInput = stats.input + stats.cacheRead + stats.cacheWrite;\n const totalTokens = combinedInput + stats.output;\n\n totalInput += combinedInput;\n totalOutput += stats.output;\n totalCost += stats.cost;\n\n const firstModel = models[0] ? `- ${models[0]}` : \"\";\n console.log(\n v +\n padRight(` ${date}`, colDate) +\n v +\n padRight(` ${firstModel}`, colModels) +\n v +\n padLeft(`${formatNumber(combinedInput)} `, colInput) +\n v +\n padLeft(`${formatNumber(stats.output)} `, colOutput) +\n v +\n padLeft(`${formatNumber(totalTokens)} `, colTotal) +\n v +\n padLeft(`${formatCost(stats.cost)} `, colCost) +\n v\n );\n\n for (let i = 1; i < models.length; i++) {\n console.log(\n v +\n \" \".repeat(colDate) +\n v +\n padRight(` - ${models[i]}`, colModels) +\n v +\n \" \".repeat(colInput) +\n v +\n \" \".repeat(colOutput) +\n v +\n \" \".repeat(colTotal) +\n v +\n \" \".repeat(colCost) +\n v\n );\n }\n\n const providers = Array.from(stats.providerStats.entries()).sort(\n (a, b) => b[1].cost - a[1].cost\n );\n\n for (const [providerId, providerStat] of providers) {\n const providerInput =\n providerStat.input + providerStat.cacheRead + providerStat.cacheWrite;\n const providerTokens = providerInput + providerStat.output;\n console.log(\n v +\n \" \".repeat(colDate) +\n v +\n padRight(` [${providerId}]`, colModels) +\n v +\n padLeft(`${formatNumber(providerInput)} `, colInput) +\n v +\n padLeft(`${formatNumber(providerStat.output)} `, colOutput) +\n v +\n padLeft(`${formatNumber(providerTokens)} `, colTotal) +\n v +\n padLeft(`${formatCost(providerStat.cost)} `, colCost) +\n v\n );\n }\n\n console.log(midLine);\n }\n\n const grandTotal = totalInput + totalOutput;\n console.log(\n v +\n padRight(\" Total\", colDate) +\n v +\n \" \".repeat(colModels) +\n v +\n padLeft(`${formatNumber(totalInput)} `, colInput) +\n v +\n padLeft(`${formatNumber(totalOutput)} `, colOutput) +\n v +\n padLeft(`${formatNumber(grandTotal)} `, colTotal) +\n v +\n padLeft(`${formatCost(totalCost)} `, colCost) +\n v\n );\n console.log(bottomLine);\n console.log();\n}\n",
10
+ "#!/usr/bin/env node\n/**\n * OpenCode Usage - CLI tool for tracking OpenCode AI usage and costs\n *\n * Usage:\n * bunx opencode-usage\n * bunx opencode-usage --provider anthropic\n * bunx opencode-usage --days 30\n */\n\nimport { parseArgs } from \"./cli.js\";\nimport { getOpenCodeStoragePath, loadMessages } from \"./loader.js\";\nimport { aggregateByDate, filterByDays } from \"./aggregator.js\";\nimport { renderTable } from \"./renderer.js\";\n\nasync function main(): Promise<void> {\n const { provider, days } = parseArgs();\n const storagePath = getOpenCodeStoragePath();\n\n console.log(`\\nLoading OpenCode usage data from: ${storagePath}`);\n if (provider) {\n console.log(`Filtering: ${provider} provider only`);\n }\n\n const messages = await loadMessages(storagePath, provider);\n console.log(`Found ${messages.length} assistant messages with token data`);\n\n let dailyStats = aggregateByDate(messages);\n\n if (days) {\n dailyStats = filterByDays(dailyStats, days);\n console.log(`Showing last ${days} days`);\n }\n\n renderTable(dailyStats);\n}\n\nmain().catch(console.error);\n"
11
+ ],
12
+ "mappings": "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",
13
+ "debugId": "CF9F8AC2D659ACE464756E2164756E21",
14
+ "names": []
15
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * OpenCode storage data loader - works with both Bun and Node.js
3
+ */
4
+ import type { MessageJson } from "./types.js";
5
+ export declare function getOpenCodeStoragePath(): string;
6
+ export declare function loadMessages(storagePath: string, providerFilter?: string): Promise<MessageJson[]>;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Model pricing configuration (per million tokens)
3
+ */
4
+ import type { ModelPricing, TokenUsage } from "./types";
5
+ export declare const MODEL_PRICING: Record<string, ModelPricing>;
6
+ export declare function getModelPricing(modelId: string): ModelPricing;
7
+ export declare function calculateCost(tokens: TokenUsage, modelId: string): number;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Terminal table renderer
3
+ */
4
+ import type { DailyStats } from "./types";
5
+ export declare function renderTable(dailyStats: Map<string, DailyStats>): void;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Type definitions for OpenCode usage stats
3
+ */
4
+ export type TokenUsage = {
5
+ input: number;
6
+ output: number;
7
+ reasoning: number;
8
+ cache: {
9
+ read: number;
10
+ write: number;
11
+ };
12
+ };
13
+ export type MessageJson = {
14
+ id: string;
15
+ sessionID: string;
16
+ role: "user" | "assistant";
17
+ model?: {
18
+ providerID: string;
19
+ modelID: string;
20
+ };
21
+ modelID?: string;
22
+ providerID?: string;
23
+ tokens?: TokenUsage;
24
+ cost?: number;
25
+ time?: {
26
+ created?: number;
27
+ completed?: number;
28
+ };
29
+ };
30
+ export type ProviderStats = {
31
+ input: number;
32
+ output: number;
33
+ cacheWrite: number;
34
+ cacheRead: number;
35
+ reasoning: number;
36
+ cost: number;
37
+ models: Set<string>;
38
+ };
39
+ export type DailyStats = {
40
+ date: string;
41
+ models: Set<string>;
42
+ providers: Set<string>;
43
+ providerStats: Map<string, ProviderStats>;
44
+ input: number;
45
+ output: number;
46
+ cacheWrite: number;
47
+ cacheRead: number;
48
+ reasoning: number;
49
+ cost: number;
50
+ };
51
+ export type ModelPricing = {
52
+ input: number;
53
+ output: number;
54
+ cacheWrite: number;
55
+ cacheRead: number;
56
+ };
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "opencode-usage",
3
+ "version": "0.1.0",
4
+ "description": "CLI tool for tracking OpenCode AI coding assistant usage and costs",
5
+ "keywords": [
6
+ "ai",
7
+ "anthropic",
8
+ "bun",
9
+ "claude",
10
+ "cli",
11
+ "cost",
12
+ "gpt",
13
+ "openai",
14
+ "opencode",
15
+ "tokens",
16
+ "tracking",
17
+ "usage"
18
+ ],
19
+ "homepage": "https://github.com/blogic-cz/opencode-usage#readme",
20
+ "bugs": {
21
+ "url": "https://github.com/blogic-cz/opencode-usage/issues"
22
+ },
23
+ "license": "MIT",
24
+ "author": "blogic-cz",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/blogic-cz/opencode-usage.git"
28
+ },
29
+ "bin": {
30
+ "opencode-usage": "dist/index.js"
31
+ },
32
+ "files": [
33
+ "dist"
34
+ ],
35
+ "type": "module",
36
+ "main": "./dist/index.js",
37
+ "module": "./dist/index.js",
38
+ "types": "./dist/index.d.ts",
39
+ "exports": {
40
+ ".": {
41
+ "types": "./dist/index.d.ts",
42
+ "import": "./dist/index.js"
43
+ }
44
+ },
45
+ "scripts": {
46
+ "build": "bun build ./src/index.ts --outdir ./dist --target node --format esm --sourcemap && bun run build:types",
47
+ "build:types": "tsc --emitDeclarationOnly --declaration --outDir dist",
48
+ "check": "bun check.ts",
49
+ "check:ci": "bun check.ts ci",
50
+ "ck:warmup": "bun run scripts/warmup-ck.ts",
51
+ "compile": "bun build --compile --minify ./src/index.ts --outfile opencode-usage",
52
+ "dev": "bun run ck:warmup & bun run ./src/index.ts",
53
+ "format": "bunx oxfmt",
54
+ "lint": "bunx oxlint -c ./.oxlintrc.json --deny-warnings",
55
+ "prepublishOnly": "bun run check && bun run build"
56
+ },
57
+ "devDependencies": {
58
+ "@types/bun": "latest",
59
+ "typescript": "^5.9.3"
60
+ },
61
+ "engines": {
62
+ "node": ">=18"
63
+ }
64
+ }