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 +83 -0
- package/dist/aggregator.d.ts +6 -0
- package/dist/cli.d.ts +8 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +460 -0
- package/dist/index.js.map +15 -0
- package/dist/loader.d.ts +6 -0
- package/dist/pricing.d.ts +7 -0
- package/dist/renderer.d.ts +5 -0
- package/dist/types.d.ts +56 -0
- package/package.json +64 -0
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
package/dist/index.d.ts
ADDED
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
|
+
}
|
package/dist/loader.d.ts
ADDED
|
@@ -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;
|
package/dist/types.d.ts
ADDED
|
@@ -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
|
+
}
|