ai-usage-analyzer 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 +184 -0
- package/bin/ai-usage.js +100 -0
- package/package.json +52 -0
- package/src/aggregate.js +117 -0
- package/src/detectors.js +300 -0
- package/src/loaders.js +249 -0
- package/src/render.js +544 -0
package/README.md
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# ai-usage-analyzer
|
|
2
|
+
|
|
3
|
+
TUI analyzer for local AI coding agent token consumption. Auto-detects
|
|
4
|
+
**Claude Code**, **Codex**, **OpenCode**, **MimoCode**, **GitHub Copilot**,
|
|
5
|
+
**Antigravity**, and **Gemini CLI** data directories — no hardcoded paths.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
╭──────────────────────────────────────────────────────╮
|
|
9
|
+
│ ◆ AI TOKEN ANALYZER ◆ │
|
|
10
|
+
│ │
|
|
11
|
+
│ 411 sessions • 1.56B total tokens • $29.40 USD │
|
|
12
|
+
│ range: 2026-04-01 → 2026-06-27 │
|
|
13
|
+
╰──────────────────────────────────────────────────────╯
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
Requires **Node.js ≥ 22.5** (for built-in `node:sqlite`).
|
|
19
|
+
|
|
20
|
+
Pick whichever fits your workflow:
|
|
21
|
+
|
|
22
|
+
### Option 1: `npx` from GitHub (zero install, fastest to try)
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx -y github:adetxt/ai-usage-analyzer
|
|
26
|
+
# or with a specific ref
|
|
27
|
+
npx -y github:adetxt/ai-usage-analyzer#v0.1.0
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
No clone, no `node_modules`, no global install. npx downloads the repo
|
|
31
|
+
on first run and caches it. Subsequent runs are instant.
|
|
32
|
+
|
|
33
|
+
### Option 2: Clone + install (best for development / contribution)
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
git clone https://github.com/adetxt/ai-usage-analyzer.git
|
|
37
|
+
cd ai-usage-analyzer
|
|
38
|
+
pnpm install
|
|
39
|
+
pnpm link --global # exposes the `ai-usage` command globally
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
> Uses `pnpm` (declared in the `packageManager` field). `pnpm-lock.yaml`
|
|
43
|
+
> is committed as the source of truth for reproducible installs.
|
|
44
|
+
|
|
45
|
+
### Option 3: Install globally from GitHub (no clone)
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pnpm add -g git+https://github.com/adetxt/ai-usage-analyzer.git
|
|
49
|
+
# or with npm
|
|
50
|
+
npm install -g git+https://github.com/adetxt/ai-usage-analyzer.git
|
|
51
|
+
# then
|
|
52
|
+
ai-usage
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Option 4: From npm registry (when published)
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npx -y ai-usage-analyzer
|
|
59
|
+
# or
|
|
60
|
+
pnpm add -g ai-usage-analyzer
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
To publish your own copy: `npm login && npm publish --access public`
|
|
64
|
+
(requires the `ai-usage-analyzer` name to be available on npmjs.com,
|
|
65
|
+
or use a scoped name like `@yourname/ai-usage-analyzer` and update
|
|
66
|
+
the `name` field in `package.json` first).
|
|
67
|
+
|
|
68
|
+
### Verify install
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
ai-usage --help
|
|
72
|
+
# or via npx
|
|
73
|
+
npx -y github:adetxt/ai-usage-analyzer --help
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Usage
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
ai-usage # default TUI
|
|
80
|
+
ai-usage --top 10 # show top 10 heaviest sessions
|
|
81
|
+
ai-usage --json # machine-readable JSON output
|
|
82
|
+
ai-usage --help
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Environment overrides
|
|
86
|
+
|
|
87
|
+
If a tool's data lives outside the default location, override its base path:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
export CLAUDE_HOME=/custom/path/to/.claude
|
|
91
|
+
export CODEX_HOME=/custom/path/to/.codex
|
|
92
|
+
export OPENCODE_HOME=/custom/path/to/.local/share/opencode
|
|
93
|
+
export MIMOCODE_HOME=/custom/path/to/.local/share/mimocode
|
|
94
|
+
export COPILOT_HOME=/custom/path/to/.copilot
|
|
95
|
+
export ANTIGRAVITY_HOME=/custom/path/to/Antigravity
|
|
96
|
+
export GEMINI_HOME=/custom/path/to/.gemini
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Or pass all at once via JSON:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
export AI_USAGE_PATHS_JSON='{"codex":"/data/codex","opencode":"/data/oc.db"}'
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Supported tools
|
|
106
|
+
|
|
107
|
+
| Tool | Path | Token data | Source format |
|
|
108
|
+
|---|---|---|---|
|
|
109
|
+
| Claude Code | `~/.claude/transcripts` | presence only | `ses_*.jsonl` |
|
|
110
|
+
| Codex | `~/.codex/sessions/YYYY/MM/DD/` | **yes** | `rollout-*.jsonl` `token_count` events |
|
|
111
|
+
| OpenCode | `~/.local/share/opencode/opencode.db` | **yes** (+cost) | SQLite |
|
|
112
|
+
| MimoCode | `~/.local/share/mimocode/mimocode.db` | **yes** (+cost, when present) | SQLite |
|
|
113
|
+
| GitHub Copilot | `~/.copilot/session-state/` | presence only | `events.jsonl` |
|
|
114
|
+
| Antigravity | `~/Library/Application Support/Antigravity` | presence only | dir scan |
|
|
115
|
+
| Gemini CLI | `~/.gemini/antigravity/conversations` | presence only | `*.pb` protobuf |
|
|
116
|
+
|
|
117
|
+
## Token breakdown
|
|
118
|
+
|
|
119
|
+
For tools that record token data, the analyzer shows the full breakdown:
|
|
120
|
+
|
|
121
|
+
- **Input** — prompt tokens sent to the model
|
|
122
|
+
- **Output** — completion tokens generated by the model
|
|
123
|
+
- **Cache Read** — prompt tokens served from the provider's cache (cheap)
|
|
124
|
+
- **Cache Write** — prompt tokens cached for future use (OpenCode only)
|
|
125
|
+
- **Reasoning** — extended thinking / chain-of-thought tokens
|
|
126
|
+
|
|
127
|
+
The TUI is adaptive: at terminal widths below 110 columns it drops
|
|
128
|
+
non-essential columns; at 110+ it shows the full breakdown with project
|
|
129
|
+
paths, per-tool token columns, and longer distribution bars.
|
|
130
|
+
|
|
131
|
+
## Architecture
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
src/
|
|
135
|
+
├── detectors.js auto-path discovery (env → well-known locations)
|
|
136
|
+
├── loaders.js SQLite + JSONL parsers → unified session record
|
|
137
|
+
├── aggregate.js per-project / per-week / per-month grouping
|
|
138
|
+
├── render.js TUI (chalk + cli-table3 + boxen + gradient-string)
|
|
139
|
+
bin/
|
|
140
|
+
└── ai-usage.js entry point
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The loader produces a unified record shape:
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
{
|
|
147
|
+
tool, sessionId, project, title, week, month, ts,
|
|
148
|
+
tokensInput, tokensOutput, tokensCacheRead, tokensCacheWrite,
|
|
149
|
+
tokensReasoning, tokensTotal, cost, model
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The aggregator then groups by project / week / month, and the renderer
|
|
154
|
+
turns each grouping into a colored table with a distribution bar.
|
|
155
|
+
|
|
156
|
+
## Output modes
|
|
157
|
+
|
|
158
|
+
### TUI (default)
|
|
159
|
+
|
|
160
|
+
Color-coded, boxed tables with bar charts. Adapts to terminal width.
|
|
161
|
+
|
|
162
|
+
### JSON (`--json`)
|
|
163
|
+
|
|
164
|
+
Single JSON object with detection results, summary, raw sessions, and errors.
|
|
165
|
+
Suitable for piping into `jq` or feeding a dashboard.
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
ai-usage --json | jq '.summary'
|
|
169
|
+
# {
|
|
170
|
+
# "n": 411,
|
|
171
|
+
# "tokensTotal": 1558000000,
|
|
172
|
+
# "tokensInput": 420900000,
|
|
173
|
+
# "tokensOutput": 4450000,
|
|
174
|
+
# "tokensCacheRead": 1130000000,
|
|
175
|
+
# "tokensCacheWrite": 49000,
|
|
176
|
+
# "tokensReasoning": 2210000,
|
|
177
|
+
# "cost": 29.4,
|
|
178
|
+
# "avg": 3790000
|
|
179
|
+
# }
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## License
|
|
183
|
+
|
|
184
|
+
MIT
|
package/bin/ai-usage.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// AI Usage Analyzer - TUI for local AI coding agent token consumption
|
|
3
|
+
// Auto-detects: Claude Code, Codex, OpenCode, MimoCode, Copilot, Antigravity, Gemini
|
|
4
|
+
|
|
5
|
+
import { detectAll } from '../src/detectors.js';
|
|
6
|
+
import { loadAll, dateRange } from '../src/loaders.js';
|
|
7
|
+
import { overall } from '../src/aggregate.js';
|
|
8
|
+
import {
|
|
9
|
+
renderHeader, renderDetections, renderOverview,
|
|
10
|
+
renderPerProject, renderPerMonth, renderPerWeek,
|
|
11
|
+
renderTopSessions, renderNotes,
|
|
12
|
+
} from '../src/render.js';
|
|
13
|
+
|
|
14
|
+
const args = process.argv.slice(2);
|
|
15
|
+
const flags = new Set(args.filter(a => a.startsWith('--')));
|
|
16
|
+
const showHelp = flags.has('--help') || flags.has('-h');
|
|
17
|
+
const jsonOut = flags.has('--json');
|
|
18
|
+
const topN = (() => {
|
|
19
|
+
const i = args.indexOf('--top');
|
|
20
|
+
return i >= 0 ? parseInt(args[i + 1], 10) || 5 : 5;
|
|
21
|
+
})();
|
|
22
|
+
|
|
23
|
+
if (showHelp) {
|
|
24
|
+
console.log(`
|
|
25
|
+
ai-usage-analyzer — local AI coding agent token consumption TUI
|
|
26
|
+
|
|
27
|
+
Usage:
|
|
28
|
+
ai-usage [options]
|
|
29
|
+
|
|
30
|
+
Options:
|
|
31
|
+
--top N Show top N heaviest sessions (default: 5)
|
|
32
|
+
--json Output machine-readable JSON instead of TUI
|
|
33
|
+
-h, --help Show this help
|
|
34
|
+
|
|
35
|
+
Environment overrides (per-tool data path):
|
|
36
|
+
CLAUDE_HOME, CODEX_HOME, OPENCODE_HOME, MIMOCODE_HOME,
|
|
37
|
+
COPILOT_HOME, ANTIGRAVITY_HOME, GEMINI_HOME,
|
|
38
|
+
AI_USAGE_PATHS_JSON='{"codex":"/custom/path",...}'
|
|
39
|
+
|
|
40
|
+
Supported tools:
|
|
41
|
+
• Claude Code — ~/.claude/transcripts (presence only)
|
|
42
|
+
• Codex — ~/.codex/sessions (tokens from token_count events)
|
|
43
|
+
• OpenCode — ~/.local/share/opencode/opencode.db (tokens + cost)
|
|
44
|
+
• MimoCode — ~/.local/share/mimocode/mimocode.db (tokens + cost)
|
|
45
|
+
• GitHub Copilot — ~/.copilot/session-state (presence only)
|
|
46
|
+
• Antigravity — ~/Library/Application Support/Antigravity (presence only)
|
|
47
|
+
• Gemini CLI — ~/.gemini/antigravity/conversations (presence only)
|
|
48
|
+
`);
|
|
49
|
+
process.exit(0);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function main() {
|
|
53
|
+
const t0 = Date.now();
|
|
54
|
+
const detections = detectAll();
|
|
55
|
+
const { records, errors } = await loadAll(detections);
|
|
56
|
+
const range = dateRange(records);
|
|
57
|
+
const tot = overall(records);
|
|
58
|
+
|
|
59
|
+
if (jsonOut) {
|
|
60
|
+
const out = {
|
|
61
|
+
detections,
|
|
62
|
+
summary: tot,
|
|
63
|
+
dateRange: range,
|
|
64
|
+
sessions: records,
|
|
65
|
+
errors,
|
|
66
|
+
generatedAt: new Date().toISOString(),
|
|
67
|
+
durationMs: Date.now() - t0,
|
|
68
|
+
};
|
|
69
|
+
console.log(JSON.stringify(out, null, 2));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// TUI render
|
|
74
|
+
const sections = [
|
|
75
|
+
renderHeader({
|
|
76
|
+
totalSessions: records.length,
|
|
77
|
+
totalTokens: tot.tokensTotal,
|
|
78
|
+
totalCost: tot.cost,
|
|
79
|
+
dateRange: range,
|
|
80
|
+
}),
|
|
81
|
+
renderDetections(detections),
|
|
82
|
+
];
|
|
83
|
+
if (records.length > 0) {
|
|
84
|
+
sections.push(
|
|
85
|
+
renderOverview(records, detections),
|
|
86
|
+
renderPerProject(records),
|
|
87
|
+
renderPerMonth(records),
|
|
88
|
+
renderPerWeek(records),
|
|
89
|
+
renderTopSessions(records, topN),
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
sections.push(renderNotes(detections, errors));
|
|
93
|
+
|
|
94
|
+
console.log(sections.join('\n\n'));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
main().catch(err => {
|
|
98
|
+
console.error('Fatal error:', err);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ai-usage-analyzer",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TUI analyzer for local AI coding agent token usage. Auto-detects Claude Code, Codex, OpenCode, MimoCode, Copilot, Antigravity, and Gemini.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"packageManager": "pnpm@10.33.0",
|
|
7
|
+
"bin": {
|
|
8
|
+
"ai-usage": "bin/ai-usage.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bin",
|
|
12
|
+
"src",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"start": "node bin/ai-usage.js",
|
|
17
|
+
"test": "node --test test/*.test.js"
|
|
18
|
+
},
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=22.5.0",
|
|
21
|
+
"pnpm": ">=10.0.0"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"ai",
|
|
25
|
+
"tokens",
|
|
26
|
+
"claude-code",
|
|
27
|
+
"codex",
|
|
28
|
+
"opencode",
|
|
29
|
+
"mimocode",
|
|
30
|
+
"copilot",
|
|
31
|
+
"antigravity",
|
|
32
|
+
"gemini",
|
|
33
|
+
"tui",
|
|
34
|
+
"cli"
|
|
35
|
+
],
|
|
36
|
+
"author": "ade",
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "git+https://github.com/adetxt/ai-usage-analyzer.git"
|
|
41
|
+
},
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/adetxt/ai-usage-analyzer/issues"
|
|
44
|
+
},
|
|
45
|
+
"homepage": "https://github.com/adetxt/ai-usage-analyzer#readme",
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"boxen": "^7.1.1",
|
|
48
|
+
"chalk": "^5.3.0",
|
|
49
|
+
"cli-table3": "^0.6.5",
|
|
50
|
+
"gradient-string": "^2.0.2"
|
|
51
|
+
}
|
|
52
|
+
}
|
package/src/aggregate.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// Group session records by various dimensions and compute stats.
|
|
2
|
+
|
|
3
|
+
const MONTH_NAMES = {
|
|
4
|
+
'01': 'Jan', '02': 'Feb', '03': 'Mar', '04': 'Apr',
|
|
5
|
+
'05': 'Mei', '06': 'Jun', '07': 'Jul', '08': 'Agu',
|
|
6
|
+
'09': 'Sep', '10': 'Okt', '11': 'Nov', '12': 'Des',
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
function sum(arr, key) {
|
|
10
|
+
return arr.reduce((a, b) => a + (b[key] || 0), 0);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function avg(arr, key) {
|
|
14
|
+
return arr.length ? sum(arr, key) / arr.length : 0;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function groupBy(records, keyFn) {
|
|
18
|
+
const out = new Map();
|
|
19
|
+
for (const r of records) {
|
|
20
|
+
const k = keyFn(r);
|
|
21
|
+
if (!out.has(k)) out.set(k, []);
|
|
22
|
+
out.get(k).push(r);
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function summarize(records) {
|
|
28
|
+
return {
|
|
29
|
+
n: records.length,
|
|
30
|
+
tokensTotal: sum(records, 'tokensTotal'),
|
|
31
|
+
tokensInput: sum(records, 'tokensInput'),
|
|
32
|
+
tokensOutput: sum(records, 'tokensOutput'),
|
|
33
|
+
tokensCacheRead: sum(records, 'tokensCacheRead'),
|
|
34
|
+
tokensCacheWrite: sum(records, 'tokensCacheWrite'),
|
|
35
|
+
tokensReasoning: sum(records, 'tokensReasoning'),
|
|
36
|
+
cost: sum(records, 'cost'),
|
|
37
|
+
avg: avg(records, 'tokensTotal'),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function perProject(records) {
|
|
42
|
+
const m = groupBy(records, r => `${r.tool}\u0001${r.project}`);
|
|
43
|
+
const out = [];
|
|
44
|
+
for (const [k, arr] of m) {
|
|
45
|
+
const [tool, project] = k.split('\u0001');
|
|
46
|
+
const s = summarize(arr);
|
|
47
|
+
out.push({ tool, project, ...s });
|
|
48
|
+
}
|
|
49
|
+
return out.sort((a, b) => b.tokensTotal - a.tokensTotal);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function perMonth(records) {
|
|
53
|
+
const m = groupBy(records, r => r.month);
|
|
54
|
+
const out = [];
|
|
55
|
+
for (const [month, arr] of m) {
|
|
56
|
+
const byTool = {};
|
|
57
|
+
for (const r of arr) {
|
|
58
|
+
byTool[r.tool] = (byTool[r.tool] || 0) + r.tokensTotal;
|
|
59
|
+
}
|
|
60
|
+
out.push({ month, ...summarize(arr), byTool });
|
|
61
|
+
}
|
|
62
|
+
return out.sort((a, b) => a.month.localeCompare(b.month));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function perWeek(records) {
|
|
66
|
+
const m = groupBy(records, r => r.week);
|
|
67
|
+
const out = [];
|
|
68
|
+
for (const [week, arr] of m) {
|
|
69
|
+
const byTool = {};
|
|
70
|
+
for (const r of arr) {
|
|
71
|
+
byTool[r.tool] = (byTool[r.tool] || 0) + r.tokensTotal;
|
|
72
|
+
}
|
|
73
|
+
out.push({ week, ...summarize(arr), byTool });
|
|
74
|
+
}
|
|
75
|
+
return out.sort((a, b) => a.week.localeCompare(b.week));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function perTool(records) {
|
|
79
|
+
const m = groupBy(records, r => r.tool);
|
|
80
|
+
const out = [];
|
|
81
|
+
for (const [tool, arr] of m) {
|
|
82
|
+
out.push({ tool, ...summarize(arr) });
|
|
83
|
+
}
|
|
84
|
+
return out.sort((a, b) => b.tokensTotal - a.tokensTotal);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function overall(records) {
|
|
88
|
+
return summarize(records);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function topSessions(records, n = 5) {
|
|
92
|
+
return [...records]
|
|
93
|
+
.sort((a, b) => b.tokensTotal - a.tokensTotal)
|
|
94
|
+
.slice(0, n);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function tokenBreakdown(s) {
|
|
98
|
+
// Returns { input, output, cacheRead, cacheWrite, reasoning, total, ratios }
|
|
99
|
+
const total = s.tokensTotal || 1;
|
|
100
|
+
return {
|
|
101
|
+
input: s.tokensInput,
|
|
102
|
+
output: s.tokensOutput,
|
|
103
|
+
cacheRead: s.tokensCacheRead,
|
|
104
|
+
cacheWrite: s.tokensCacheWrite,
|
|
105
|
+
reasoning: s.tokensReasoning,
|
|
106
|
+
total: s.tokensTotal,
|
|
107
|
+
ratios: {
|
|
108
|
+
input: s.tokensInput / total,
|
|
109
|
+
output: s.tokensOutput / total,
|
|
110
|
+
cacheRead: s.tokensCacheRead / total,
|
|
111
|
+
cacheWrite: s.tokensCacheWrite / total,
|
|
112
|
+
reasoning: s.tokensReasoning / total,
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export { MONTH_NAMES };
|