oc2token 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/LICENSE +21 -0
- package/README.md +399 -0
- package/dist/src/accounting/index.d.ts +1 -0
- package/dist/src/accounting/index.js +2 -0
- package/dist/src/accounting/index.js.map +1 -0
- package/dist/src/accounting/reducer.d.ts +21 -0
- package/dist/src/accounting/reducer.js +105 -0
- package/dist/src/accounting/reducer.js.map +1 -0
- package/dist/src/antigravity/discovery.d.ts +18 -0
- package/dist/src/antigravity/discovery.js +95 -0
- package/dist/src/antigravity/discovery.js.map +1 -0
- package/dist/src/antigravity/doctor.d.ts +6 -0
- package/dist/src/antigravity/doctor.js +78 -0
- package/dist/src/antigravity/doctor.js.map +1 -0
- package/dist/src/antigravity/index.d.ts +3 -0
- package/dist/src/antigravity/index.js +4 -0
- package/dist/src/antigravity/index.js.map +1 -0
- package/dist/src/antigravity/proto.d.ts +73 -0
- package/dist/src/antigravity/proto.js +259 -0
- package/dist/src/antigravity/proto.js.map +1 -0
- package/dist/src/antigravity/scanner.d.ts +8 -0
- package/dist/src/antigravity/scanner.js +552 -0
- package/dist/src/antigravity/scanner.js.map +1 -0
- package/dist/src/application.d.ts +73 -0
- package/dist/src/application.js +657 -0
- package/dist/src/application.js.map +1 -0
- package/dist/src/cache/filesystem.d.ts +8 -0
- package/dist/src/cache/filesystem.js +70 -0
- package/dist/src/cache/filesystem.js.map +1 -0
- package/dist/src/cache/index.d.ts +5 -0
- package/dist/src/cache/index.js +6 -0
- package/dist/src/cache/index.js.map +1 -0
- package/dist/src/cache/lock.d.ts +40 -0
- package/dist/src/cache/lock.js +227 -0
- package/dist/src/cache/lock.js.map +1 -0
- package/dist/src/cache/schema.d.ts +45 -0
- package/dist/src/cache/schema.js +308 -0
- package/dist/src/cache/schema.js.map +1 -0
- package/dist/src/cache/store.d.ts +46 -0
- package/dist/src/cache/store.js +396 -0
- package/dist/src/cache/store.js.map +1 -0
- package/dist/src/cache/types.d.ts +130 -0
- package/dist/src/cache/types.js +6 -0
- package/dist/src/cache/types.js.map +1 -0
- package/dist/src/cli.d.ts +8 -0
- package/dist/src/cli.js +874 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/codex/discovery.d.ts +6 -0
- package/dist/src/codex/discovery.js +88 -0
- package/dist/src/codex/discovery.js.map +1 -0
- package/dist/src/codex/doctor.d.ts +6 -0
- package/dist/src/codex/doctor.js +52 -0
- package/dist/src/codex/doctor.js.map +1 -0
- package/dist/src/codex/index.d.ts +3 -0
- package/dist/src/codex/index.js +3 -0
- package/dist/src/codex/index.js.map +1 -0
- package/dist/src/codex/scanner.d.ts +6 -0
- package/dist/src/codex/scanner.js +487 -0
- package/dist/src/codex/scanner.js.map +1 -0
- package/dist/src/codex/types.d.ts +31 -0
- package/dist/src/codex/types.js +5 -0
- package/dist/src/codex/types.js.map +1 -0
- package/dist/src/collector/collector.d.ts +15 -0
- package/dist/src/collector/collector.js +597 -0
- package/dist/src/collector/collector.js.map +1 -0
- package/dist/src/collector/index.d.ts +3 -0
- package/dist/src/collector/index.js +3 -0
- package/dist/src/collector/index.js.map +1 -0
- package/dist/src/collector/types.d.ts +55 -0
- package/dist/src/collector/types.js +18 -0
- package/dist/src/collector/types.js.map +1 -0
- package/dist/src/dashboard/render/ansi.d.ts +53 -0
- package/dist/src/dashboard/render/ansi.js +106 -0
- package/dist/src/dashboard/render/ansi.js.map +1 -0
- package/dist/src/dashboard/render/dashboard.d.ts +13 -0
- package/dist/src/dashboard/render/dashboard.js +750 -0
- package/dist/src/dashboard/render/dashboard.js.map +1 -0
- package/dist/src/dashboard/render/format.d.ts +23 -0
- package/dist/src/dashboard/render/format.js +169 -0
- package/dist/src/dashboard/render/format.js.map +1 -0
- package/dist/src/dashboard/render/redraw.d.ts +13 -0
- package/dist/src/dashboard/render/redraw.js +24 -0
- package/dist/src/dashboard/render/redraw.js.map +1 -0
- package/dist/src/dashboard/render/types.d.ts +83 -0
- package/dist/src/dashboard/render/types.js +528 -0
- package/dist/src/dashboard/render/types.js.map +1 -0
- package/dist/src/dashboard/scheduler/index.d.ts +1 -0
- package/dist/src/dashboard/scheduler/index.js +2 -0
- package/dist/src/dashboard/scheduler/index.js.map +1 -0
- package/dist/src/dashboard/scheduler/refresh-scheduler.d.ts +46 -0
- package/dist/src/dashboard/scheduler/refresh-scheduler.js +137 -0
- package/dist/src/dashboard/scheduler/refresh-scheduler.js.map +1 -0
- package/dist/src/dashboard/settings/index.d.ts +25 -0
- package/dist/src/dashboard/settings/index.js +162 -0
- package/dist/src/dashboard/settings/index.js.map +1 -0
- package/dist/src/dashboard/state/index.d.ts +2 -0
- package/dist/src/dashboard/state/index.js +3 -0
- package/dist/src/dashboard/state/index.js.map +1 -0
- package/dist/src/dashboard/state/refresh-coordinator.d.ts +72 -0
- package/dist/src/dashboard/state/refresh-coordinator.js +346 -0
- package/dist/src/dashboard/state/refresh-coordinator.js.map +1 -0
- package/dist/src/dashboard/state/types.d.ts +68 -0
- package/dist/src/dashboard/state/types.js +2 -0
- package/dist/src/dashboard/state/types.js.map +1 -0
- package/dist/src/domain/contracts.d.ts +219 -0
- package/dist/src/domain/contracts.js +2 -0
- package/dist/src/domain/contracts.js.map +1 -0
- package/dist/src/domain/errors.d.ts +25 -0
- package/dist/src/domain/errors.js +65 -0
- package/dist/src/domain/errors.js.map +1 -0
- package/dist/src/domain/index.d.ts +5 -0
- package/dist/src/domain/index.js +6 -0
- package/dist/src/domain/index.js.map +1 -0
- package/dist/src/domain/records.d.ts +59 -0
- package/dist/src/domain/records.js +141 -0
- package/dist/src/domain/records.js.map +1 -0
- package/dist/src/domain/tokens.d.ts +51 -0
- package/dist/src/domain/tokens.js +93 -0
- package/dist/src/domain/tokens.js.map +1 -0
- package/dist/src/domain/windows.d.ts +59 -0
- package/dist/src/domain/windows.js +272 -0
- package/dist/src/domain/windows.js.map +1 -0
- package/dist/src/index.d.ts +9 -0
- package/dist/src/index.js +9 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/opencode/adapter.d.ts +47 -0
- package/dist/src/opencode/adapter.js +118 -0
- package/dist/src/opencode/adapter.js.map +1 -0
- package/dist/src/opencode/client.d.ts +64 -0
- package/dist/src/opencode/client.js +118 -0
- package/dist/src/opencode/client.js.map +1 -0
- package/dist/src/opencode/doctor.d.ts +28 -0
- package/dist/src/opencode/doctor.js +82 -0
- package/dist/src/opencode/doctor.js.map +1 -0
- package/dist/src/opencode/index.d.ts +5 -0
- package/dist/src/opencode/index.js +6 -0
- package/dist/src/opencode/index.js.map +1 -0
- package/dist/src/opencode/stats.d.ts +8 -0
- package/dist/src/opencode/stats.js +174 -0
- package/dist/src/opencode/stats.js.map +1 -0
- package/dist/src/opencode/transport.d.ts +54 -0
- package/dist/src/opencode/transport.js +621 -0
- package/dist/src/opencode/transport.js.map +1 -0
- package/dist/src/output/index.d.ts +20 -0
- package/dist/src/output/index.js +46 -0
- package/dist/src/output/index.js.map +1 -0
- package/dist/src/output/json.d.ts +72 -0
- package/dist/src/output/json.js +133 -0
- package/dist/src/output/json.js.map +1 -0
- package/dist/src/output/table.d.ts +4 -0
- package/dist/src/output/table.js +81 -0
- package/dist/src/output/table.js.map +1 -0
- package/dist/src/pricing/index.d.ts +1 -0
- package/dist/src/pricing/index.js +2 -0
- package/dist/src/pricing/index.js.map +1 -0
- package/dist/src/pricing/pricing.d.ts +19 -0
- package/dist/src/pricing/pricing.js +441 -0
- package/dist/src/pricing/pricing.js.map +1 -0
- package/package.json +27 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kaan Yaren
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/screenshots/dashboard.svg" width="100%" alt="OC2Token dashboard — day view with three window cards, trend sparkline and breakdown tables" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">OC2Token</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>macOS-first terminal dashboard for OpenCode 2 token usage</strong><br/>
|
|
9
|
+
Unified, privacy-preserving telemetry for <code>opencode</code> · <code>codex</code> · <code>antigravity</code>
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
<p align="center">
|
|
13
|
+
<a href="https://www.npmjs.com/package/oc2token"><img src="https://img.shields.io/npm/v/oc2token?style=flat-square&color=8B5CF6&label=npm" alt="npm version" /></a>
|
|
14
|
+
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/license-MIT-ff8700?style=flat-square" alt="MIT license" /></a>
|
|
15
|
+
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-%3E%3D20-00d7ff?style=flat-square" alt="node >=20" /></a>
|
|
16
|
+
<a href="#platform"><img src="https://img.shields.io/badge/platform-macOS%20%E2%80%A2%20Linux-lightgrey?style=flat-square" alt="macOS • Linux" /></a>
|
|
17
|
+
<a href="https://github.com/kaanyaren/OC2Token/issues"><img src="https://img.shields.io/github/issues/kaanyaren/OC2Token?style=flat-square&color=9a8fc2" alt="issues" /></a>
|
|
18
|
+
</p>
|
|
19
|
+
|
|
20
|
+
<p align="center">
|
|
21
|
+
<code>npx oc2token</code> · <code>oc2token day</code> · <code>oc2token --json</code> · <code>oc2token doctor</code>
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
### Why OC2Token?
|
|
27
|
+
|
|
28
|
+
OpenCode 2 records rich token telemetry locally, but the raw message stream is noisy and per-provider. **OC2Token** is a single, fast, terminal-native console that:
|
|
29
|
+
|
|
30
|
+
- aggregates **all three providers** (`opencode`, `codex`, `antigravity`) into one unified report
|
|
31
|
+
- shows three exact windows — **last 60 minutes · today · this week** — with local-day and ISO-week semantics
|
|
32
|
+
- visualises a **sparkline trend**, per-model / per-provider / per-project breakdowns, and coverage status without ever persisting prompts or keys
|
|
33
|
+
- stays instantly usable over pipes (`--json`, `--format table`) for CI and scripting
|
|
34
|
+
|
|
35
|
+
> `recorded_total = input + output + reasoning + cacheRead + cacheWrite` — an explicit accounting sum, **not** a billing or quota total.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## ✨ Highlights
|
|
40
|
+
|
|
41
|
+
- **Unified multi-provider** — one snapshot, one schema, three colours (purple · orange · cyan)
|
|
42
|
+
- **Three exact windows** rendered side-by-side, responsive down to 20 columns
|
|
43
|
+
- **CodeBurn-inspired palette** — violet structure, orange focus, cyan inputs; `--no-color` / `NO_COLOR` safe
|
|
44
|
+
- **Trend sparkline** per window, not one row per bucket
|
|
45
|
+
- **Privacy by design** — only normalized token counters are cached; no prompts, tool inputs, or session titles are stored
|
|
46
|
+
- **Resilient collection** — paginated assistant-message aggregation with retry, dedup by `tokenRevision`, fallback when the filtered stats API ignores the range
|
|
47
|
+
- **Scriptable** — stable JSON `schemaVersion: 3`, deterministic table output, exit codes `0` / `1` / `3`
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 📸 Preview
|
|
52
|
+
|
|
53
|
+
<table>
|
|
54
|
+
<tr>
|
|
55
|
+
<td width="50%">
|
|
56
|
+
|
|
57
|
+
**Dashboard — day (100 cols)**
|
|
58
|
+
<br/>Three cards + inline provider stack + trend
|
|
59
|
+
|
|
60
|
+
<img src="assets/screenshots/dashboard.svg" alt="Dashboard day view" />
|
|
61
|
+
|
|
62
|
+
</td>
|
|
63
|
+
<td width="50%">
|
|
64
|
+
|
|
65
|
+
**Settings overlay — `s`**
|
|
66
|
+
<br/>Toggle providers, scrub refresh interval
|
|
67
|
+
|
|
68
|
+
<img src="assets/screenshots/dashboard-settings.svg" alt="Settings overlay" />
|
|
69
|
+
|
|
70
|
+
</td>
|
|
71
|
+
</tr>
|
|
72
|
+
<tr>
|
|
73
|
+
<td>
|
|
74
|
+
|
|
75
|
+
**Projects — `p`**
|
|
76
|
+
<br/>Per-project totals scoped to the selected window
|
|
77
|
+
|
|
78
|
+
<img src="assets/screenshots/dashboard-projects.svg" alt="Projects overlay" />
|
|
79
|
+
|
|
80
|
+
</td>
|
|
81
|
+
<td>
|
|
82
|
+
|
|
83
|
+
**Narrow — 60 cols**
|
|
84
|
+
<br/>Stacks vertically, never wraps
|
|
85
|
+
|
|
86
|
+
<img src="assets/screenshots/dashboard-narrow.svg" alt="Narrow 60-col layout" />
|
|
87
|
+
|
|
88
|
+
</td>
|
|
89
|
+
</tr>
|
|
90
|
+
</table>
|
|
91
|
+
|
|
92
|
+
<details>
|
|
93
|
+
<summary>More screenshots</summary>
|
|
94
|
+
|
|
95
|
+
| View | Screenshot |
|
|
96
|
+
|------|------------|
|
|
97
|
+
| Hour (`1` / `oc2token hour`) |  |
|
|
98
|
+
| Help (`?`) |  |
|
|
99
|
+
| Table pipe (`--format table`) |  |
|
|
100
|
+
|
|
101
|
+
</details>
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 📦 Installation
|
|
106
|
+
|
|
107
|
+
### npm / npx (recommended)
|
|
108
|
+
|
|
109
|
+
```sh
|
|
110
|
+
npm install -g oc2token
|
|
111
|
+
# or without installing
|
|
112
|
+
npx oc2token
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Requires **Node.js ≥ 20**. The OpenCode 2 local service must be reachable; OC2Token talks to it via `@opencode-ai/client` (beta API subject to change).
|
|
116
|
+
|
|
117
|
+
### From source
|
|
118
|
+
|
|
119
|
+
```sh
|
|
120
|
+
git clone https://github.com/kaanyaren/OC2Token.git
|
|
121
|
+
cd OC2Token
|
|
122
|
+
npm install
|
|
123
|
+
npm run build
|
|
124
|
+
npm link # or: node dist/src/cli.js --help
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Verify
|
|
128
|
+
|
|
129
|
+
```sh
|
|
130
|
+
oc2token doctor # human-readable health checks
|
|
131
|
+
oc2token doctor --json # machine-readable
|
|
132
|
+
oc2token --help
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## 🚀 Quick start
|
|
138
|
+
|
|
139
|
+
```sh
|
|
140
|
+
oc2token # interactive dashboard, refreshes every 5 min
|
|
141
|
+
oc2token hour # start on the rolling last-60-minutes card
|
|
142
|
+
oc2token day # start on today's local-calendar day
|
|
143
|
+
oc2token week # start on the ISO week (Mon–Sun)
|
|
144
|
+
|
|
145
|
+
oc2token --once --json # one-shot JSON for scripting
|
|
146
|
+
oc2token --format table # deterministic plain table (great for pipes)
|
|
147
|
+
oc2token --refresh 0 # manual-only; press r to refresh
|
|
148
|
+
oc2token --timezone Europe/Istanbul # override the local zone
|
|
149
|
+
oc2token --no-color # or NO_COLOR=1 for plain output
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## 🎮 Interactive dashboard
|
|
155
|
+
|
|
156
|
+
| Key / Click | Action |
|
|
157
|
+
|-------------|--------|
|
|
158
|
+
| `r` / `R` | Refresh now |
|
|
159
|
+
| `1` / `2` / `3` · **click top card** | Select **hour · day · week** |
|
|
160
|
+
| `Tab` / `←` `→` / `↑` `↓` | Cycle windows |
|
|
161
|
+
| `p` | Projects panel |
|
|
162
|
+
| `s` | Settings — toggle providers, adjust refresh |
|
|
163
|
+
| `?` | Help |
|
|
164
|
+
| `q` / `Ctrl+C` | Quit |
|
|
165
|
+
|
|
166
|
+
Settings persist under the cache directory (`~/.cache/oc2token` on macOS/Linux or `$XDG_CACHE_HOME`). At least one provider must stay enabled.
|
|
167
|
+
|
|
168
|
+
**Theme.** Purple carries structure, orange carries activity and focus, cyan carries inputs. Respects `NO_COLOR` and `--no-color`.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## 🖥️ CLI reference
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
oc2token 0.1.0
|
|
176
|
+
|
|
177
|
+
Usage:
|
|
178
|
+
oc2token [hour|day|week]
|
|
179
|
+
oc2token [options]
|
|
180
|
+
oc2token doctor [--source <provider>] [--json]
|
|
181
|
+
|
|
182
|
+
Options:
|
|
183
|
+
--once Collect once and exit
|
|
184
|
+
--json Emit stable JSON and exit
|
|
185
|
+
--format <mode> auto, dashboard, table, or json
|
|
186
|
+
--refresh <seconds> Auto-refresh cadence; 0 is manual-only (60–14400)
|
|
187
|
+
--timezone <IANA> Zone for local day / ISO week boundaries
|
|
188
|
+
--project <id> Restrict collection to an OpenCode project
|
|
189
|
+
--cache-dir <path> Override the normalized metadata cache directory
|
|
190
|
+
--source <provider> Filter providers: opencode, codex, antigravity, or all (repeatable)
|
|
191
|
+
--no-color Disable ANSI colors
|
|
192
|
+
-h, --help Show help
|
|
193
|
+
-v, --version Show version
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Exit codes
|
|
197
|
+
|
|
198
|
+
| Code | Meaning |
|
|
199
|
+
|------|---------|
|
|
200
|
+
| `0` | Success, complete coverage |
|
|
201
|
+
| `1` | Connection / validation / usage error |
|
|
202
|
+
| `3` | Partial results — JSON emitted but coverage incomplete |
|
|
203
|
+
|
|
204
|
+
### Filtering providers
|
|
205
|
+
|
|
206
|
+
```sh
|
|
207
|
+
oc2token --source codex # only Codex
|
|
208
|
+
oc2token --source opencode --source codex # two providers
|
|
209
|
+
oc2token --source all # reset filter (default)
|
|
210
|
+
oc2token doctor --source antigravity --json
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Combine with `--once` / `--json` for scripting:
|
|
214
|
+
|
|
215
|
+
```sh
|
|
216
|
+
oc2token --once --json --source codex | jq .totals.day.recorded_total
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## 🔌 Providers
|
|
222
|
+
|
|
223
|
+
| Provider | Source | Discovery |
|
|
224
|
+
|----------|--------|-----------|
|
|
225
|
+
| **opencode** | OpenCode 2 local service (`@opencode-ai/client`) | session/message pagination with `tokenRevision` dedup |
|
|
226
|
+
| **codex** | `~/.codex/sessions` rollouts (JSONL) | file scan + parse |
|
|
227
|
+
| **antigravity** | Antigravity SQLite + session JSON | proto + scanner |
|
|
228
|
+
|
|
229
|
+
All three emit the same `UsageRecord` shape and are merged by `Aggregator → CachedUsageSource → Dashboard`. `doctor` reports each independently:
|
|
230
|
+
|
|
231
|
+
```sh
|
|
232
|
+
oc2token doctor
|
|
233
|
+
# oc2token doctor: OK
|
|
234
|
+
# ✓ opencode: OpenCode 2 service OK
|
|
235
|
+
# ✓ codex: 14 sessions scanned
|
|
236
|
+
# ✓ antigravity: 3 projects indexed
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## 📊 Output formats
|
|
242
|
+
|
|
243
|
+
### Table (pipes & logs)
|
|
244
|
+
|
|
245
|
+
No ANSI, deterministic — safe for `grep`, `awk`, `less`:
|
|
246
|
+
|
|
247
|
+
```sh
|
|
248
|
+
oc2token --format table
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
<p align="center"><img src="assets/screenshots/table.svg" width="100%" alt="Table output" /></p>
|
|
252
|
+
|
|
253
|
+
### JSON (`schemaVersion: 3`)
|
|
254
|
+
|
|
255
|
+
```sh
|
|
256
|
+
oc2token --json | jq .
|
|
257
|
+
oc2token --once --json > snapshot.json
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Emits **all three windows**, `totalsByProvider`/`totalsByProject`, trends, and coverage in one stable contract:
|
|
261
|
+
|
|
262
|
+
```json
|
|
263
|
+
{
|
|
264
|
+
"schemaVersion": 3,
|
|
265
|
+
"source": "unified",
|
|
266
|
+
"version": "0.1.0",
|
|
267
|
+
"windows": { "hour": { "kind": "hour", "from": "…", "to": "…", "label": "last 60 minutes" }, "day": {}, "week": {} },
|
|
268
|
+
"totals": { "hour": { "recorded_total": 15582 }, "day": {}, "week": {} },
|
|
269
|
+
"providersByWindow": { "day": [ { "name": "openai", "totals": {} } ] },
|
|
270
|
+
"totalsByProvider": { "day": { "openai": { "recorded_total": 21785 } } },
|
|
271
|
+
"coverage": { "complete": true, "sessionsScanned": 14, "sessionsDiscovered": 14 }
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
Use `--format json` for pretty-printed JSON without exiting interactive mode fallback.
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## ⚙️ Configuration
|
|
280
|
+
|
|
281
|
+
| Option | CLI | Env / Persisted |
|
|
282
|
+
|--------|-----|-----------------|
|
|
283
|
+
| Timezone | `--timezone Europe/Berlin` | Local system zone by default; validated as IANA |
|
|
284
|
+
| Refresh | `--refresh 300` (60–14400, `0` manual) | Persisted via Settings panel (`s`) |
|
|
285
|
+
| Cache dir | `--cache-dir /tmp/oc2` | `~/.cache/oc2token` / `$XDG_CACHE_HOME/oc2token` |
|
|
286
|
+
| Project filter | `--project my-project` | Scopes OpenCode collection only |
|
|
287
|
+
| Providers | `--source codex` | Persisted via Settings panel |
|
|
288
|
+
| Color | `--no-color` | `NO_COLOR=1` disables all ANSI |
|
|
289
|
+
|
|
290
|
+
A corrupt or future cache is ignored — the live service is always the authority; the cache is an optimization.
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## 🔒 Privacy
|
|
295
|
+
|
|
296
|
+
OC2Token stores **only normalized usage metadata**:
|
|
297
|
+
|
|
298
|
+
- `input`, `output`, `reasoning`, `cacheRead`, `cacheWrite`, `recorded_total`, model / provider / project names, timestamps
|
|
299
|
+
|
|
300
|
+
It **does not** persist:
|
|
301
|
+
|
|
302
|
+
- prompts, tool input/output, API keys, session titles, message bodies
|
|
303
|
+
|
|
304
|
+
All collection is local-first; no network calls besides the loopback OpenCode service.
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
## 🧭 How it works
|
|
309
|
+
|
|
310
|
+
```
|
|
311
|
+
┌─────────────┐ ┌─────────────┐ ┌──────────────┐
|
|
312
|
+
│ opencode │ │ codex │ │ antigravity │
|
|
313
|
+
│ transport │ │ scanner │ │ scanner │
|
|
314
|
+
└──────┬──────┘ └──────┬──────┘ └──────┬───────┘
|
|
315
|
+
│ │ │
|
|
316
|
+
└────────────────┼────────────────┘
|
|
317
|
+
▼
|
|
318
|
+
┌─────────────┐
|
|
319
|
+
│ Aggregator │ dedup by sessionID/messageID + tokenRevision
|
|
320
|
+
└──────┬──────┘
|
|
321
|
+
▼
|
|
322
|
+
┌──────────────┐ file lock · schema 1 · atomic writes
|
|
323
|
+
│ CachedUsage │
|
|
324
|
+
└──────┬──────┘
|
|
325
|
+
▼
|
|
326
|
+
┌───────────────────┐
|
|
327
|
+
│ dashboard / json │ normalize → renderDashboard | renderTable | renderJSON
|
|
328
|
+
└───────────────────┘
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
- Windows are derived with `Intl.DateTimeFormat` — including DST-aware local-day resolution.
|
|
332
|
+
- Filtered stats responses are range-validated; if the service ignores the range, OC2Token falls back to full pagination rather than mislabelling all-time usage as filtered.
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## 🛠️ Development
|
|
337
|
+
|
|
338
|
+
```sh
|
|
339
|
+
npm install
|
|
340
|
+
npm test # build + node --test
|
|
341
|
+
npm run typecheck
|
|
342
|
+
npm run pack:check # dry-run npm pack
|
|
343
|
+
npm run build
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
Project layout:
|
|
347
|
+
|
|
348
|
+
```
|
|
349
|
+
src/
|
|
350
|
+
accounting/ reducer for normalized records
|
|
351
|
+
collector/ parallel/serial message collection
|
|
352
|
+
opencode/ transport & doctor
|
|
353
|
+
codex/ rollout discovery & scanner
|
|
354
|
+
antigravity/ sqlite + proto + scanner
|
|
355
|
+
cache/ filesystem lock & store (schema 1)
|
|
356
|
+
dashboard/ render, state, settings
|
|
357
|
+
domain/ windows, tokens, records, contracts
|
|
358
|
+
output/ dashboard / table / json renderers
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
**Testing.** The suite uses `node:test` with a serial oracle vs parallel aggregator equivalence check, ANSI-gating invariants, and window-boundary coverage.
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## 🤝 Contributing
|
|
366
|
+
|
|
367
|
+
Contributions are welcome — bug reports, new provider adapters, render polish, docs.
|
|
368
|
+
|
|
369
|
+
1. Fork & create a feature branch
|
|
370
|
+
2. Add tests for new behaviour (`npm test`)
|
|
371
|
+
3. Run `npm run typecheck`
|
|
372
|
+
4. Open a PR with a clear description and screenshots for UI changes
|
|
373
|
+
|
|
374
|
+
Please read the [MIT License](LICENSE). By contributing you agree to license your contributions under the same terms.
|
|
375
|
+
|
|
376
|
+
> Be kind. This project follows the spirit of the [Contributor Covenant](https://www.contributor-covenant.org/).
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
## 🙏 Acknowledgments
|
|
381
|
+
|
|
382
|
+
- [OpenCode 2](https://github.com/sst/opencode) for the local token telemetry API
|
|
383
|
+
- The CodeBurn palette inspiration (violet · orange · cyan)
|
|
384
|
+
- Built with TypeScript, `node:test`, and a lot of terminal love
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## 📄 License
|
|
389
|
+
|
|
390
|
+
MIT © 2026 [Kaan Yaren](https://github.com/kaanyaren)
|
|
391
|
+
|
|
392
|
+
See [LICENSE](LICENSE) for details.
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
<p align="center">
|
|
397
|
+
<sub>OC2Token is not affiliated with OpenCode. <code>recorded_total</code> is an accounting sum — not a billing total.</sub><br/>
|
|
398
|
+
<sub><a href="https://github.com/kaanyaren/OC2Token">github.com/kaanyaren/OC2Token</a> · <code>npm i -g oc2token</code></sub>
|
|
399
|
+
</p>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { UsageRecordReducer, compareFreshness, reduceUsageRecords, sumRecordComponents, } from "./reducer.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/accounting/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type UsageTotals } from "../domain/index.js";
|
|
2
|
+
import { type UsageRecord, type UsageWindow } from "../domain/index.js";
|
|
3
|
+
/**
|
|
4
|
+
* A deterministic, synchronous reducer for usage records.
|
|
5
|
+
*
|
|
6
|
+
* Workers never mutate this reducer while they are reading pages. Callers can
|
|
7
|
+
* feed records in any completion order and the winner is selected by observed
|
|
8
|
+
* time, revision, and finally completeness, rather than arrival order.
|
|
9
|
+
*/
|
|
10
|
+
export declare class UsageRecordReducer {
|
|
11
|
+
private readonly byKey;
|
|
12
|
+
upsert(record: UsageRecord): boolean;
|
|
13
|
+
upsertMany(records: ReadonlyArray<UsageRecord>): void;
|
|
14
|
+
get size(): number;
|
|
15
|
+
get(key: string): UsageRecord | undefined;
|
|
16
|
+
records(): ReadonlyArray<UsageRecord>;
|
|
17
|
+
totals(window: UsageWindow, includeProvisional?: boolean): UsageTotals;
|
|
18
|
+
}
|
|
19
|
+
export declare function compareFreshness(left: UsageRecord, right: UsageRecord): number;
|
|
20
|
+
export declare function reduceUsageRecords(records: ReadonlyArray<UsageRecord>): ReadonlyArray<UsageRecord>;
|
|
21
|
+
export declare function sumRecordComponents(records: ReadonlyArray<UsageRecord>): UsageTotals;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { parseTokenComponents, toUsageTotals, } from "../domain/index.js";
|
|
2
|
+
import { sumUsageRecords, } from "../domain/index.js";
|
|
3
|
+
const COMPONENTS = ["input", "output", "reasoning", "cacheRead", "cacheWrite"];
|
|
4
|
+
function compareRevision(left, right) {
|
|
5
|
+
if (left === right) {
|
|
6
|
+
return 0;
|
|
7
|
+
}
|
|
8
|
+
// Explicit numeric revisions are common in test transports and are the only
|
|
9
|
+
// safe way to compare revisions such as 9 and 10 lexically.
|
|
10
|
+
const leftNumber = /(?:^|:)(?:v)?(\d+)$/.exec(left)?.[1];
|
|
11
|
+
const rightNumber = /(?:^|:)(?:v)?(\d+)$/.exec(right)?.[1];
|
|
12
|
+
if (leftNumber !== undefined && rightNumber !== undefined) {
|
|
13
|
+
const difference = Number(leftNumber) - Number(rightNumber);
|
|
14
|
+
if (difference !== 0) {
|
|
15
|
+
return difference;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const leftScan = /^scan:(\d+):/.exec(left)?.[1];
|
|
19
|
+
const rightScan = /^scan:(\d+):/.exec(right)?.[1];
|
|
20
|
+
if (leftScan !== undefined && rightScan !== undefined) {
|
|
21
|
+
const difference = Number(leftScan) - Number(rightScan);
|
|
22
|
+
if (difference !== 0)
|
|
23
|
+
return difference;
|
|
24
|
+
}
|
|
25
|
+
return left < right ? -1 : 1;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* A deterministic, synchronous reducer for usage records.
|
|
29
|
+
*
|
|
30
|
+
* Workers never mutate this reducer while they are reading pages. Callers can
|
|
31
|
+
* feed records in any completion order and the winner is selected by observed
|
|
32
|
+
* time, revision, and finally completeness, rather than arrival order.
|
|
33
|
+
*/
|
|
34
|
+
export class UsageRecordReducer {
|
|
35
|
+
byKey = new Map();
|
|
36
|
+
upsert(record) {
|
|
37
|
+
const previous = this.byKey.get(record.key);
|
|
38
|
+
if (previous === undefined || compareFreshness(record, previous) > 0) {
|
|
39
|
+
this.byKey.set(record.key, record);
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
upsertMany(records) {
|
|
45
|
+
for (const record of records) {
|
|
46
|
+
this.upsert(record);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
get size() {
|
|
50
|
+
return this.byKey.size;
|
|
51
|
+
}
|
|
52
|
+
get(key) {
|
|
53
|
+
return this.byKey.get(key);
|
|
54
|
+
}
|
|
55
|
+
records() {
|
|
56
|
+
return [...this.byKey.values()].sort((left, right) => left.key.localeCompare(right.key));
|
|
57
|
+
}
|
|
58
|
+
totals(window, includeProvisional = false) {
|
|
59
|
+
return sumUsageRecords(this.records(), window, { includeProvisional });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export function compareFreshness(left, right) {
|
|
63
|
+
const observedDifference = left.observedAt.getTime() - right.observedAt.getTime();
|
|
64
|
+
if (observedDifference !== 0) {
|
|
65
|
+
return observedDifference;
|
|
66
|
+
}
|
|
67
|
+
const revisionDifference = compareRevision(left.tokenRevision, right.tokenRevision);
|
|
68
|
+
if (revisionDifference !== 0) {
|
|
69
|
+
return revisionDifference;
|
|
70
|
+
}
|
|
71
|
+
// A final observation supersedes a provisional observation at the same
|
|
72
|
+
// revision. This matters when a streaming message settles in one refresh.
|
|
73
|
+
if (left.completeness !== right.completeness) {
|
|
74
|
+
return left.completeness === "final" ? 1 : -1;
|
|
75
|
+
}
|
|
76
|
+
// Identical revisions should be byte-equivalent for accounting purposes. A
|
|
77
|
+
// stable key tie-break keeps the comparator total without using arrival order.
|
|
78
|
+
return left.key.localeCompare(right.key);
|
|
79
|
+
}
|
|
80
|
+
export function reduceUsageRecords(records) {
|
|
81
|
+
const reducer = new UsageRecordReducer();
|
|
82
|
+
reducer.upsertMany(records);
|
|
83
|
+
return reducer.records();
|
|
84
|
+
}
|
|
85
|
+
export function sumRecordComponents(records) {
|
|
86
|
+
const totals = {
|
|
87
|
+
input: 0,
|
|
88
|
+
output: 0,
|
|
89
|
+
reasoning: 0,
|
|
90
|
+
cacheRead: 0,
|
|
91
|
+
cacheWrite: 0,
|
|
92
|
+
};
|
|
93
|
+
for (const record of records) {
|
|
94
|
+
const normalized = parseTokenComponents(record);
|
|
95
|
+
for (const component of COMPONENTS) {
|
|
96
|
+
const next = totals[component] + normalized[component];
|
|
97
|
+
if (!Number.isSafeInteger(next)) {
|
|
98
|
+
throw new RangeError(`Summing ${component} exceeds the safe integer range`);
|
|
99
|
+
}
|
|
100
|
+
totals[component] = next;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return toUsageTotals(totals);
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=reducer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reducer.js","sourceRoot":"","sources":["../../../src/accounting/reducer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare function defaultAntigravityHome(): string;
|
|
2
|
+
/**
|
|
3
|
+
* Scan Antigravity SQLite conversation databases.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors codeburn `conversationRoots()` (dist/main.js:1674) scanning:
|
|
6
|
+
* - ~/.gemini/antigravity/conversations
|
|
7
|
+
* - ~/.gemini/antigravity-cli/conversations
|
|
8
|
+
* - ~/.gemini/antigravity-cli/implicit
|
|
9
|
+
* - ~/.gemini/antigravity-ide/conversations
|
|
10
|
+
* - ~/.gemini/antigravity-ide/implicit
|
|
11
|
+
*
|
|
12
|
+
* Only `*.db` files are returned; `-shm`/`-wal` sidecars are ignored
|
|
13
|
+
* (they do not end with `.db`). Supports `ANTIGRAVITY_HOME` override via
|
|
14
|
+
* `resolveAntigravityHome` or explicit `root` parameter.
|
|
15
|
+
* For test fixtures `ANTIGRAVITY_HOME` may point directly to a directory
|
|
16
|
+
* containing `*.db` files — that directory is scanned as well.
|
|
17
|
+
*/
|
|
18
|
+
export declare function discoverAntigravityDatabases(root?: string): string[];
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { homedir } from "node:os";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { readdirSync } from "node:fs";
|
|
4
|
+
export function defaultAntigravityHome() {
|
|
5
|
+
return join(homedir(), ".gemini");
|
|
6
|
+
}
|
|
7
|
+
function resolveAntigravityHome(root) {
|
|
8
|
+
if (root !== undefined) {
|
|
9
|
+
return root;
|
|
10
|
+
}
|
|
11
|
+
const env = process.env.ANTIGRAVITY_HOME;
|
|
12
|
+
if (typeof env === "string" && env.trim().length > 0) {
|
|
13
|
+
return env;
|
|
14
|
+
}
|
|
15
|
+
return defaultAntigravityHome();
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Scan Antigravity SQLite conversation databases.
|
|
19
|
+
*
|
|
20
|
+
* Mirrors codeburn `conversationRoots()` (dist/main.js:1674) scanning:
|
|
21
|
+
* - ~/.gemini/antigravity/conversations
|
|
22
|
+
* - ~/.gemini/antigravity-cli/conversations
|
|
23
|
+
* - ~/.gemini/antigravity-cli/implicit
|
|
24
|
+
* - ~/.gemini/antigravity-ide/conversations
|
|
25
|
+
* - ~/.gemini/antigravity-ide/implicit
|
|
26
|
+
*
|
|
27
|
+
* Only `*.db` files are returned; `-shm`/`-wal` sidecars are ignored
|
|
28
|
+
* (they do not end with `.db`). Supports `ANTIGRAVITY_HOME` override via
|
|
29
|
+
* `resolveAntigravityHome` or explicit `root` parameter.
|
|
30
|
+
* For test fixtures `ANTIGRAVITY_HOME` may point directly to a directory
|
|
31
|
+
* containing `*.db` files — that directory is scanned as well.
|
|
32
|
+
*/
|
|
33
|
+
export function discoverAntigravityDatabases(root) {
|
|
34
|
+
const base = resolveAntigravityHome(root);
|
|
35
|
+
const seen = new Set();
|
|
36
|
+
const result = [];
|
|
37
|
+
const tryDir = (dir) => {
|
|
38
|
+
let entries;
|
|
39
|
+
try {
|
|
40
|
+
entries = readdirSync(dir);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
for (const file of entries) {
|
|
46
|
+
// Only *.db — ignore -shm/-wal sidecars and .pb files
|
|
47
|
+
if (!file.toLowerCase().endsWith(".db")) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
// Extra guard for sidecars that might be named *.db-shm / *.db-wal
|
|
51
|
+
const lower = file.toLowerCase();
|
|
52
|
+
if (lower.endsWith("-shm") || lower.endsWith("-wal") || lower.endsWith(".db-shm") || lower.endsWith(".db-wal")) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const full = join(dir, file);
|
|
56
|
+
if (seen.has(full)) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
seen.add(full);
|
|
60
|
+
result.push(full);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
// Direct base scan — handles ANTIGRAVITY_HOME pointing to a conversations dir
|
|
64
|
+
// or a test fixture containing .db files flat.
|
|
65
|
+
tryDir(base);
|
|
66
|
+
// Known relative paths (codeburn conversationRoots) relative to homedir base.
|
|
67
|
+
// We probe both interpretations of `base`:
|
|
68
|
+
// - base is homedir (e.g. /Users/you) -> join(base, ".gemini", ...)
|
|
69
|
+
// - base is .gemini dir (e.g. /Users/you/.gemini) -> join(base, "antigravity", ...)
|
|
70
|
+
const relativeFromHomedir = [
|
|
71
|
+
join(".gemini", "antigravity", "conversations"),
|
|
72
|
+
join(".gemini", "antigravity-cli", "conversations"),
|
|
73
|
+
join(".gemini", "antigravity-cli", "implicit"),
|
|
74
|
+
join(".gemini", "antigravity-ide", "conversations"),
|
|
75
|
+
join(".gemini", "antigravity-ide", "implicit"),
|
|
76
|
+
];
|
|
77
|
+
const relativeFromGemini = [
|
|
78
|
+
join("antigravity", "conversations"),
|
|
79
|
+
join("antigravity-cli", "conversations"),
|
|
80
|
+
join("antigravity-cli", "implicit"),
|
|
81
|
+
join("antigravity-ide", "conversations"),
|
|
82
|
+
join("antigravity-ide", "implicit"),
|
|
83
|
+
];
|
|
84
|
+
for (const rel of relativeFromHomedir) {
|
|
85
|
+
tryDir(join(base, rel));
|
|
86
|
+
}
|
|
87
|
+
for (const rel of relativeFromGemini) {
|
|
88
|
+
tryDir(join(base, rel));
|
|
89
|
+
}
|
|
90
|
+
// Also handle case where base is already a .gemini/antigravity-cli/conversations path's parent
|
|
91
|
+
// by not duplicating — seen set handles it.
|
|
92
|
+
result.sort();
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=discovery.js.map
|