opencode-token-tracker 1.2.0 → 1.3.1
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 +60 -1
- package/dist/bin/opencode-tokens.js +285 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -94,6 +94,36 @@ Breakdown options (`--by`):
|
|
|
94
94
|
- `daily` - Show day-by-day breakdown
|
|
95
95
|
- `all` - Show all breakdowns
|
|
96
96
|
|
|
97
|
+
### Pricing & Config Commands
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# Show built-in pricing table
|
|
101
|
+
opencode-tokens pricing
|
|
102
|
+
|
|
103
|
+
# Show your used models and their pricing status
|
|
104
|
+
opencode-tokens models
|
|
105
|
+
|
|
106
|
+
# Show current config
|
|
107
|
+
opencode-tokens config
|
|
108
|
+
|
|
109
|
+
# Generate example config based on your usage
|
|
110
|
+
opencode-tokens config init
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Example `models` output:
|
|
114
|
+
```
|
|
115
|
+
Model Provider Msgs Pricing
|
|
116
|
+
------------------------ ---------------- -------- ------------
|
|
117
|
+
claude-opus-4.5 github-copilot 379 provider cfg
|
|
118
|
+
deepseek-chat deepseek 6 built-in
|
|
119
|
+
gpt-5.2 openai 18 built-in
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
This helps you understand:
|
|
123
|
+
- Which models/providers you're using
|
|
124
|
+
- Whether pricing is from built-in table, your config, or default fallback
|
|
125
|
+
- What to add to your config file
|
|
126
|
+
|
|
97
127
|
## Log Files
|
|
98
128
|
|
|
99
129
|
Token usage is logged to:
|
|
@@ -153,6 +183,35 @@ Create a config file at `~/.config/opencode/token-tracker.json`:
|
|
|
153
183
|
}
|
|
154
184
|
```
|
|
155
185
|
|
|
186
|
+
### Pricing Fields Explained
|
|
187
|
+
|
|
188
|
+
All prices are in **USD per 1 million tokens**:
|
|
189
|
+
|
|
190
|
+
| Field | Description | Example |
|
|
191
|
+
|-------|-------------|---------|
|
|
192
|
+
| `input` | Cost for input/prompt tokens | `15` = $15 per 1M tokens |
|
|
193
|
+
| `output` | Cost for output/completion tokens | `75` = $75 per 1M tokens |
|
|
194
|
+
| `cacheRead` | Cost for cached input tokens (optional) | `1.5` = $1.5 per 1M tokens |
|
|
195
|
+
| `cacheWrite` | Cost for cache write tokens (optional) | `18.75` = $18.75 per 1M tokens |
|
|
196
|
+
|
|
197
|
+
**How to find pricing for your model:**
|
|
198
|
+
|
|
199
|
+
1. Check the provider's official pricing page:
|
|
200
|
+
- [Anthropic Claude](https://www.anthropic.com/pricing)
|
|
201
|
+
- [OpenAI](https://openai.com/pricing)
|
|
202
|
+
- [DeepSeek](https://platform.deepseek.com/api-docs/pricing)
|
|
203
|
+
- [Google Gemini](https://ai.google.dev/pricing)
|
|
204
|
+
|
|
205
|
+
2. Or run `opencode-tokens pricing` to see built-in prices
|
|
206
|
+
|
|
207
|
+
**Common scenarios:**
|
|
208
|
+
|
|
209
|
+
| Scenario | Config |
|
|
210
|
+
|----------|--------|
|
|
211
|
+
| Subscription service (GitHub Copilot, Cursor) | `{ "input": 0, "output": 0 }` |
|
|
212
|
+
| Free/local model | `{ "input": 0, "output": 0 }` |
|
|
213
|
+
| Custom API with known pricing | Look up provider's pricing page |
|
|
214
|
+
|
|
156
215
|
### Pricing Override
|
|
157
216
|
|
|
158
217
|
Pricing is resolved in this order (first match wins):
|
|
@@ -177,7 +236,7 @@ If you're using GitHub Copilot or other subscription-based services, set their c
|
|
|
177
236
|
|
|
178
237
|
#### Example: Custom model pricing
|
|
179
238
|
|
|
180
|
-
Override or add pricing for specific models:
|
|
239
|
+
Override or add pricing for specific models (prices in USD per 1M tokens):
|
|
181
240
|
|
|
182
241
|
```json
|
|
183
242
|
{
|
|
@@ -1,8 +1,44 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { readFileSync, existsSync } from "fs";
|
|
2
|
+
import { readFileSync, existsSync, writeFileSync } from "fs";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import { homedir } from "os";
|
|
5
|
-
const
|
|
5
|
+
const CONFIG_DIR = join(homedir(), ".config", "opencode");
|
|
6
|
+
const CONFIG_FILE = join(CONFIG_DIR, "token-tracker.json");
|
|
7
|
+
const LOG_FILE = join(CONFIG_DIR, "logs", "token-tracker", "tokens.jsonl");
|
|
8
|
+
const BUILTIN_PRICING = {
|
|
9
|
+
// Anthropic Claude
|
|
10
|
+
"claude-opus-4.5": { input: 15, output: 75, cacheRead: 1.5, cacheWrite: 18.75 },
|
|
11
|
+
"claude-sonnet-4.5": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
12
|
+
"claude-sonnet-4": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
13
|
+
"claude-haiku-4.5": { input: 0.8, output: 4, cacheRead: 0.08, cacheWrite: 1 },
|
|
14
|
+
"claude-haiku-4": { input: 0.8, output: 4, cacheRead: 0.08, cacheWrite: 1 },
|
|
15
|
+
// OpenAI GPT
|
|
16
|
+
"gpt-5.2": { input: 2.5, output: 10 },
|
|
17
|
+
"gpt-5.2-codex": { input: 3, output: 12 },
|
|
18
|
+
"gpt-5.1": { input: 2, output: 8 },
|
|
19
|
+
"gpt-5": { input: 5, output: 15 },
|
|
20
|
+
"gpt-4.1": { input: 2, output: 8 },
|
|
21
|
+
"gpt-4.1-mini": { input: 0.4, output: 1.6 },
|
|
22
|
+
"gpt-4.1-nano": { input: 0.1, output: 0.4 },
|
|
23
|
+
"gpt-4o": { input: 2.5, output: 10 },
|
|
24
|
+
"gpt-4o-mini": { input: 0.15, output: 0.6 },
|
|
25
|
+
"o3": { input: 10, output: 40 },
|
|
26
|
+
"o3-mini": { input: 1.1, output: 4.4 },
|
|
27
|
+
"o1": { input: 15, output: 60 },
|
|
28
|
+
"o1-mini": { input: 1.1, output: 4.4 },
|
|
29
|
+
// DeepSeek
|
|
30
|
+
"deepseek-chat": { input: 0.14, output: 0.28, cacheRead: 0.014 },
|
|
31
|
+
"deepseek-reasoner": { input: 0.55, output: 2.19, cacheRead: 0.055 },
|
|
32
|
+
// Google Gemini
|
|
33
|
+
"gemini-3-pro": { input: 1.25, output: 5 },
|
|
34
|
+
"gemini-3-pro-preview": { input: 1.25, output: 5 },
|
|
35
|
+
"gemini-3-flash": { input: 0.1, output: 0.4 },
|
|
36
|
+
"gemini-2.5-pro": { input: 1.25, output: 5 },
|
|
37
|
+
"gemini-2.5-flash": { input: 0.075, output: 0.3 },
|
|
38
|
+
"gemini-2.0-flash": { input: 0.1, output: 0.4 },
|
|
39
|
+
// Fallback
|
|
40
|
+
"_default": { input: 1, output: 4 },
|
|
41
|
+
};
|
|
6
42
|
// ============================================================================
|
|
7
43
|
// Helpers
|
|
8
44
|
// ============================================================================
|
|
@@ -34,7 +70,7 @@ function getStartOfDay(date) {
|
|
|
34
70
|
function getStartOfWeek(date) {
|
|
35
71
|
const d = new Date(date);
|
|
36
72
|
const day = d.getDay();
|
|
37
|
-
const diff = d.getDate() - day + (day === 0 ? -6 : 1);
|
|
73
|
+
const diff = d.getDate() - day + (day === 0 ? -6 : 1);
|
|
38
74
|
d.setDate(diff);
|
|
39
75
|
d.setHours(0, 0, 0, 0);
|
|
40
76
|
return d.getTime();
|
|
@@ -63,7 +99,7 @@ function loadEntries(since) {
|
|
|
63
99
|
if (since && entry._ts < since)
|
|
64
100
|
continue;
|
|
65
101
|
if (!entry.input && !entry.output)
|
|
66
|
-
continue;
|
|
102
|
+
continue;
|
|
67
103
|
entries.push(entry);
|
|
68
104
|
}
|
|
69
105
|
catch {
|
|
@@ -72,6 +108,15 @@ function loadEntries(since) {
|
|
|
72
108
|
}
|
|
73
109
|
return entries;
|
|
74
110
|
}
|
|
111
|
+
function loadConfig() {
|
|
112
|
+
try {
|
|
113
|
+
if (existsSync(CONFIG_FILE)) {
|
|
114
|
+
return JSON.parse(readFileSync(CONFIG_FILE, "utf-8"));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch { }
|
|
118
|
+
return {};
|
|
119
|
+
}
|
|
75
120
|
// ============================================================================
|
|
76
121
|
// Stats Aggregation
|
|
77
122
|
// ============================================================================
|
|
@@ -129,13 +174,11 @@ function printSummary(title, stats) {
|
|
|
129
174
|
console.log();
|
|
130
175
|
}
|
|
131
176
|
function printTable(title, groups, labelHeader) {
|
|
132
|
-
// Sort by cost descending
|
|
133
177
|
const sorted = Array.from(groups.entries()).sort((a, b) => b[1].cost - a[1].cost);
|
|
134
178
|
if (sorted.length === 0) {
|
|
135
179
|
console.log(`\n No data for ${title}\n`);
|
|
136
180
|
return;
|
|
137
181
|
}
|
|
138
|
-
// Calculate column widths
|
|
139
182
|
const labelWidth = Math.max(labelHeader.length, ...sorted.map(([k]) => k.length));
|
|
140
183
|
const tokensWidth = 10;
|
|
141
184
|
const costWidth = 10;
|
|
@@ -143,10 +186,8 @@ function printTable(title, groups, labelHeader) {
|
|
|
143
186
|
console.log();
|
|
144
187
|
console.log(` ${title}`);
|
|
145
188
|
console.log(` ${"─".repeat(labelWidth + tokensWidth + costWidth + countWidth + 12)}`);
|
|
146
|
-
// Header
|
|
147
189
|
console.log(` ${padRight(labelHeader, labelWidth)} ${padLeft("Tokens", tokensWidth)} ${padLeft("Cost", costWidth)} ${padLeft("Msgs", countWidth)}`);
|
|
148
190
|
console.log(` ${"-".repeat(labelWidth)} ${"-".repeat(tokensWidth)} ${"-".repeat(costWidth)} ${"-".repeat(countWidth)}`);
|
|
149
|
-
// Rows
|
|
150
191
|
for (const [label, stats] of sorted) {
|
|
151
192
|
const totalTokens = stats.input + stats.output;
|
|
152
193
|
console.log(` ${padRight(label, labelWidth)} ${padLeft(formatTokens(totalTokens), tokensWidth)} ${padLeft(formatCost(stats.cost), costWidth)} ${padLeft(stats.count.toString(), countWidth)}`);
|
|
@@ -156,9 +197,8 @@ function printTable(title, groups, labelHeader) {
|
|
|
156
197
|
function printDailyBreakdown(entries) {
|
|
157
198
|
const byDay = groupBy(entries, (e) => {
|
|
158
199
|
const date = new Date(e._ts);
|
|
159
|
-
return date.toISOString().slice(0, 10);
|
|
200
|
+
return date.toISOString().slice(0, 10);
|
|
160
201
|
});
|
|
161
|
-
// Sort by date descending
|
|
162
202
|
const sorted = Array.from(byDay.entries()).sort((a, b) => b[0].localeCompare(a[0]));
|
|
163
203
|
if (sorted.length === 0) {
|
|
164
204
|
console.log("\n No data\n");
|
|
@@ -210,10 +250,8 @@ function cmdStats(period, breakdown) {
|
|
|
210
250
|
console.log(`\n No data for ${title.toLowerCase()}\n`);
|
|
211
251
|
return;
|
|
212
252
|
}
|
|
213
|
-
// Overall summary
|
|
214
253
|
const total = aggregateStats(entries);
|
|
215
254
|
printSummary(title, total);
|
|
216
|
-
// Breakdown
|
|
217
255
|
switch (breakdown) {
|
|
218
256
|
case "model":
|
|
219
257
|
printTable("By Model", groupBy(entries, (e) => e.model ?? "unknown"), "Model");
|
|
@@ -235,32 +273,237 @@ function cmdStats(period, breakdown) {
|
|
|
235
273
|
break;
|
|
236
274
|
}
|
|
237
275
|
}
|
|
276
|
+
function cmdPricing() {
|
|
277
|
+
const config = loadConfig();
|
|
278
|
+
console.log(`
|
|
279
|
+
Built-in Pricing Table (USD per 1M tokens)
|
|
280
|
+
══════════════════════════════════════════════════════════════════
|
|
281
|
+
`);
|
|
282
|
+
// Group by provider
|
|
283
|
+
const groups = {
|
|
284
|
+
"Anthropic Claude": ["claude-opus-4.5", "claude-sonnet-4.5", "claude-sonnet-4", "claude-haiku-4.5", "claude-haiku-4"],
|
|
285
|
+
"OpenAI": ["gpt-5.2", "gpt-5.2-codex", "gpt-5.1", "gpt-5", "gpt-4.1", "gpt-4.1-mini", "gpt-4.1-nano", "gpt-4o", "gpt-4o-mini", "o3", "o3-mini", "o1", "o1-mini"],
|
|
286
|
+
"DeepSeek": ["deepseek-chat", "deepseek-reasoner"],
|
|
287
|
+
"Google Gemini": ["gemini-3-pro", "gemini-3-pro-preview", "gemini-3-flash", "gemini-2.5-pro", "gemini-2.5-flash", "gemini-2.0-flash"],
|
|
288
|
+
};
|
|
289
|
+
const modelWidth = 20;
|
|
290
|
+
const priceWidth = 10;
|
|
291
|
+
for (const [group, models] of Object.entries(groups)) {
|
|
292
|
+
console.log(` ${group}`);
|
|
293
|
+
console.log(` ${"-".repeat(modelWidth + priceWidth * 4 + 12)}`);
|
|
294
|
+
console.log(` ${padRight("Model", modelWidth)} ${padLeft("Input", priceWidth)} ${padLeft("Output", priceWidth)} ${padLeft("CacheRd", priceWidth)} ${padLeft("CacheWr", priceWidth)}`);
|
|
295
|
+
for (const model of models) {
|
|
296
|
+
const p = BUILTIN_PRICING[model];
|
|
297
|
+
if (!p)
|
|
298
|
+
continue;
|
|
299
|
+
const overridden = config.models?.[model] ? " *" : "";
|
|
300
|
+
console.log(` ${padRight(model + overridden, modelWidth)} ${padLeft("$" + p.input.toString(), priceWidth)} ${padLeft("$" + p.output.toString(), priceWidth)} ${padLeft(p.cacheRead ? "$" + p.cacheRead.toString() : "-", priceWidth)} ${padLeft(p.cacheWrite ? "$" + p.cacheWrite.toString() : "-", priceWidth)}`);
|
|
301
|
+
}
|
|
302
|
+
console.log();
|
|
303
|
+
}
|
|
304
|
+
console.log(` Default (unknown models)`);
|
|
305
|
+
console.log(` ${"-".repeat(modelWidth + priceWidth * 4 + 12)}`);
|
|
306
|
+
const def = BUILTIN_PRICING["_default"];
|
|
307
|
+
console.log(` ${padRight("_default", modelWidth)} ${padLeft("$" + def.input.toString(), priceWidth)} ${padLeft("$" + def.output.toString(), priceWidth)} ${padLeft("-", priceWidth)} ${padLeft("-", priceWidth)}`);
|
|
308
|
+
console.log();
|
|
309
|
+
if (Object.keys(config.models || {}).length > 0) {
|
|
310
|
+
console.log(` * = overridden in config`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
function cmdModels() {
|
|
314
|
+
const entries = loadEntries();
|
|
315
|
+
if (entries.length === 0) {
|
|
316
|
+
console.log(`\n No usage data found. Start using OpenCode to collect data.\n`);
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
// Get unique model+provider combinations
|
|
320
|
+
const modelProviders = new Map();
|
|
321
|
+
for (const e of entries) {
|
|
322
|
+
const model = e.model ?? "unknown";
|
|
323
|
+
const provider = e.provider ?? "unknown";
|
|
324
|
+
const key = `${model}|${provider}`;
|
|
325
|
+
if (!modelProviders.has(key)) {
|
|
326
|
+
modelProviders.set(key, { provider, count: 0, lastUsed: 0 });
|
|
327
|
+
}
|
|
328
|
+
const info = modelProviders.get(key);
|
|
329
|
+
info.count++;
|
|
330
|
+
info.lastUsed = Math.max(info.lastUsed, e._ts);
|
|
331
|
+
}
|
|
332
|
+
// Sort by last used
|
|
333
|
+
const sorted = Array.from(modelProviders.entries())
|
|
334
|
+
.map(([key, info]) => ({ model: key.split("|")[0], ...info }))
|
|
335
|
+
.sort((a, b) => b.lastUsed - a.lastUsed);
|
|
336
|
+
const config = loadConfig();
|
|
337
|
+
console.log(`
|
|
338
|
+
Your Used Models
|
|
339
|
+
══════════════════════════════════════════════════════════════════
|
|
340
|
+
`);
|
|
341
|
+
const modelWidth = 24;
|
|
342
|
+
const providerWidth = 16;
|
|
343
|
+
const countWidth = 8;
|
|
344
|
+
const statusWidth = 12;
|
|
345
|
+
console.log(` ${padRight("Model", modelWidth)} ${padRight("Provider", providerWidth)} ${padLeft("Msgs", countWidth)} ${padRight("Pricing", statusWidth)}`);
|
|
346
|
+
console.log(` ${"-".repeat(modelWidth)} ${"-".repeat(providerWidth)} ${"-".repeat(countWidth)} ${"-".repeat(statusWidth)}`);
|
|
347
|
+
for (const { model, provider, count } of sorted) {
|
|
348
|
+
let status = "built-in";
|
|
349
|
+
if (config.providers?.[provider]) {
|
|
350
|
+
status = "provider cfg";
|
|
351
|
+
}
|
|
352
|
+
else if (config.models?.[model]) {
|
|
353
|
+
status = "model cfg";
|
|
354
|
+
}
|
|
355
|
+
else if (!BUILTIN_PRICING[model]) {
|
|
356
|
+
// Check partial match
|
|
357
|
+
const hasMatch = Object.keys(BUILTIN_PRICING).some(k => k !== "_default" && model.toLowerCase().includes(k.toLowerCase()));
|
|
358
|
+
status = hasMatch ? "built-in" : "default";
|
|
359
|
+
}
|
|
360
|
+
console.log(` ${padRight(model, modelWidth)} ${padRight(provider, providerWidth)} ${padLeft(count.toString(), countWidth)} ${padRight(status, statusWidth)}`);
|
|
361
|
+
}
|
|
362
|
+
console.log();
|
|
363
|
+
console.log(` Pricing status:`);
|
|
364
|
+
console.log(` built-in = using built-in pricing table`);
|
|
365
|
+
console.log(` provider cfg = overridden by providers config`);
|
|
366
|
+
console.log(` model cfg = overridden by models config`);
|
|
367
|
+
console.log(` default = unknown model, using $1/$4 per 1M tokens`);
|
|
368
|
+
console.log();
|
|
369
|
+
}
|
|
370
|
+
function cmdConfig(action) {
|
|
371
|
+
const config = loadConfig();
|
|
372
|
+
const entries = loadEntries();
|
|
373
|
+
if (action === "init" || action === "generate") {
|
|
374
|
+
// Get unique providers from logs
|
|
375
|
+
const providers = new Set();
|
|
376
|
+
const models = new Set();
|
|
377
|
+
for (const e of entries) {
|
|
378
|
+
if (e.provider)
|
|
379
|
+
providers.add(e.provider);
|
|
380
|
+
if (e.model)
|
|
381
|
+
models.add(e.model);
|
|
382
|
+
}
|
|
383
|
+
// Find providers/models without built-in pricing
|
|
384
|
+
const unknownModels = Array.from(models).filter(m => {
|
|
385
|
+
if (BUILTIN_PRICING[m])
|
|
386
|
+
return false;
|
|
387
|
+
const hasMatch = Object.keys(BUILTIN_PRICING).some(k => k !== "_default" && m.toLowerCase().includes(k.toLowerCase()));
|
|
388
|
+
return !hasMatch;
|
|
389
|
+
});
|
|
390
|
+
const exampleConfig = {
|
|
391
|
+
providers: {},
|
|
392
|
+
models: {},
|
|
393
|
+
toast: {
|
|
394
|
+
enabled: true,
|
|
395
|
+
duration: 3000,
|
|
396
|
+
showOnIdle: true,
|
|
397
|
+
},
|
|
398
|
+
};
|
|
399
|
+
// Add providers as comments/examples
|
|
400
|
+
for (const provider of providers) {
|
|
401
|
+
// Common free providers
|
|
402
|
+
if (provider.includes("copilot") || provider.includes("cursor") || provider.includes("free")) {
|
|
403
|
+
exampleConfig.providers[provider] = { input: 0, output: 0 };
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
// Add unknown models
|
|
407
|
+
for (const model of unknownModels) {
|
|
408
|
+
exampleConfig.models[model] = { input: 1, output: 4 };
|
|
409
|
+
}
|
|
410
|
+
// Print explanation first
|
|
411
|
+
console.log(`
|
|
412
|
+
Pricing Configuration Guide
|
|
413
|
+
══════════════════════════════════════════════════════════════════
|
|
414
|
+
|
|
415
|
+
All prices are in USD per 1 MILLION tokens.
|
|
416
|
+
|
|
417
|
+
Fields:
|
|
418
|
+
input Cost for input/prompt tokens sent to the model
|
|
419
|
+
output Cost for output/completion tokens from the model
|
|
420
|
+
cacheRead Cost for cached input tokens (optional, usually cheaper)
|
|
421
|
+
cacheWrite Cost for cache write tokens (optional)
|
|
422
|
+
|
|
423
|
+
Examples:
|
|
424
|
+
{ "input": 15, "output": 75 } = $15 per 1M input, $75 per 1M output
|
|
425
|
+
{ "input": 0, "output": 0 } = Free (subscription or local model)
|
|
426
|
+
|
|
427
|
+
Common scenarios:
|
|
428
|
+
- GitHub Copilot, Cursor, etc. → Set to 0 (subscription-based)
|
|
429
|
+
- Local/self-hosted models → Set to 0
|
|
430
|
+
- Direct API usage → Look up provider's pricing page
|
|
431
|
+
|
|
432
|
+
Where to find pricing:
|
|
433
|
+
- Anthropic: https://www.anthropic.com/pricing
|
|
434
|
+
- OpenAI: https://openai.com/pricing
|
|
435
|
+
- DeepSeek: https://platform.deepseek.com/api-docs/pricing
|
|
436
|
+
- Google: https://ai.google.dev/pricing
|
|
437
|
+
- Or run: opencode-tokens pricing
|
|
438
|
+
|
|
439
|
+
────────────────────────────────────────────────────────────────
|
|
440
|
+
Example config based on your usage:
|
|
441
|
+
`);
|
|
442
|
+
console.log(JSON.stringify(exampleConfig, null, 2));
|
|
443
|
+
if (action === "generate") {
|
|
444
|
+
const json = JSON.stringify(exampleConfig, null, 2);
|
|
445
|
+
writeFileSync(CONFIG_FILE, json);
|
|
446
|
+
console.log(`
|
|
447
|
+
Config file created: ${CONFIG_FILE}
|
|
448
|
+
`);
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
console.log(`
|
|
452
|
+
To create this config file, run:
|
|
453
|
+
opencode-tokens config generate
|
|
454
|
+
|
|
455
|
+
Or manually create: ${CONFIG_FILE}
|
|
456
|
+
`);
|
|
457
|
+
}
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
// Show current config
|
|
461
|
+
console.log(`
|
|
462
|
+
Current Configuration
|
|
463
|
+
══════════════════════════════════════════════════════════════════
|
|
464
|
+
|
|
465
|
+
Config file: ${CONFIG_FILE}
|
|
466
|
+
Status: ${existsSync(CONFIG_FILE) ? "exists" : "not found (using defaults)"}
|
|
467
|
+
`);
|
|
468
|
+
if (existsSync(CONFIG_FILE)) {
|
|
469
|
+
console.log(` Contents:`);
|
|
470
|
+
console.log(` ${"-".repeat(60)}`);
|
|
471
|
+
console.log(JSON.stringify(config, null, 2).split("\n").map(l => " " + l).join("\n"));
|
|
472
|
+
console.log();
|
|
473
|
+
}
|
|
474
|
+
console.log(` Commands:`);
|
|
475
|
+
console.log(` opencode-tokens config init Show example config with explanation`);
|
|
476
|
+
console.log(` opencode-tokens config generate Create config file`);
|
|
477
|
+
console.log();
|
|
478
|
+
}
|
|
238
479
|
function cmdHelp() {
|
|
239
480
|
console.log(`
|
|
240
481
|
opencode-tokens - Token usage statistics CLI
|
|
241
482
|
|
|
242
483
|
Usage:
|
|
243
|
-
opencode-tokens [
|
|
484
|
+
opencode-tokens [command] [options]
|
|
244
485
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
486
|
+
Commands:
|
|
487
|
+
(default) Show usage statistics
|
|
488
|
+
pricing Show built-in pricing table
|
|
489
|
+
models Show your used models and their pricing status
|
|
490
|
+
config Show/generate configuration
|
|
250
491
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
492
|
+
Statistics Options:
|
|
493
|
+
today Show today's usage
|
|
494
|
+
week Show this week's usage
|
|
495
|
+
month Show this month's usage
|
|
496
|
+
all Show all-time usage (default)
|
|
497
|
+
|
|
498
|
+
--by <type> Group by: model, agent, provider, daily, all
|
|
257
499
|
|
|
258
500
|
Examples:
|
|
259
501
|
opencode-tokens # All-time summary
|
|
260
502
|
opencode-tokens today # Today's summary
|
|
261
|
-
opencode-tokens week --by model # This week,
|
|
262
|
-
opencode-tokens
|
|
263
|
-
opencode-tokens
|
|
503
|
+
opencode-tokens week --by model # This week, by model
|
|
504
|
+
opencode-tokens pricing # Show pricing table
|
|
505
|
+
opencode-tokens models # Show your models
|
|
506
|
+
opencode-tokens config init # Generate example config
|
|
264
507
|
`);
|
|
265
508
|
}
|
|
266
509
|
// ============================================================================
|
|
@@ -268,11 +511,25 @@ function cmdHelp() {
|
|
|
268
511
|
// ============================================================================
|
|
269
512
|
function main() {
|
|
270
513
|
const args = process.argv.slice(2);
|
|
514
|
+
const command = args[0];
|
|
271
515
|
if (args.includes("--help") || args.includes("-h")) {
|
|
272
516
|
cmdHelp();
|
|
273
517
|
return;
|
|
274
518
|
}
|
|
275
|
-
//
|
|
519
|
+
// Handle subcommands
|
|
520
|
+
if (command === "pricing") {
|
|
521
|
+
cmdPricing();
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
if (command === "models") {
|
|
525
|
+
cmdModels();
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
if (command === "config") {
|
|
529
|
+
cmdConfig(args[1]);
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
// Parse stats arguments
|
|
276
533
|
let period = "all";
|
|
277
534
|
let breakdown;
|
|
278
535
|
for (let i = 0; i < args.length; i++) {
|
package/package.json
CHANGED