Skip to content

Commit 8bef7fd

Browse files
authored
Merge pull request #2 from merefield/agent/add-stopwatch-and-versioning
Add live token stopwatch and versioning
2 parents 8fe17b1 + 4d3ed90 commit 8bef7fd

17 files changed

Lines changed: 1758 additions & 25 deletions

‎README.md‎

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ It works particularly well in:
5454
- Available earned reset credits when present.
5555
- Online, refreshing, stale-data, and error states.
5656
- A countdown to the next automatic refresh.
57+
- An optional Stopwatch view that measures token activity observed across local
58+
Codex sessions between Go and Stop, with a live 30-second bar chart.
5759

5860
Codexometer does not assume that every account has the same windows. Some
5961
accounts expose a shorter rolling window and a weekly window; plans and backend
@@ -124,6 +126,12 @@ Codexometer deliberately does not:
124126
- send credentials to another service;
125127
- invoke a model merely to discover quota information.
126128

129+
The Stopwatch additionally reads locally persisted Codex rollout files under
130+
`$CODEX_HOME/sessions` (normally `~/.codex/sessions`). It scans only for
131+
`token_count` telemetry records and decodes only their token totals and
132+
timestamps; message text, reasoning, commands, tool results, and credentials are
133+
ignored and never retained by Codexometer.
134+
127135
If you use a nonstandard Codex executable, pass it explicitly:
128136

129137
```sh
@@ -137,6 +145,8 @@ codexometer --codex /path/to/codex
137145
| `t` | Cycle color themes |
138146
| `s` | Cycle meter styles |
139147
| `r` | Refresh quota data immediately |
148+
| `g` | Go: start the Stopwatch recorder (Stopwatch view only) |
149+
| `p` | Stop and take a final up-to-date reading (Stopwatch view only) |
140150
| `q` | Quit |
141151
| `Esc` | Quit |
142152
| `Ctrl+C` | Quit |
@@ -173,6 +183,14 @@ Press `s` to cycle:
173183
and whose dark segment shows consumed capacity, labelled from Empty to Full;
174184
one full-width tank appears per row. Its reset-cycle comparison also drains
175185
backward and aligns exactly with the tank's first and last inner cells.
186+
5. **Stopwatch** — establishes a zero baseline across locally active Codex
187+
sessions when you press Go. A large readout follows newly appended token
188+
telemetry once per second and shows total observed tokens, elapsed time, and
189+
average rate; clickable Go and Stop controls sit beside it. The full-width
190+
plot adds one thin vertical block bar every 30 seconds for tokens observed in
191+
that interval. New bars enter on the right and push older bars left, and the Y axis
192+
automatically expands or contracts to fit the visible samples. Stop performs
193+
an immediate final local read instead of relying on the latest graph sample.
176194

177195
The layout responds to both terminal dimensions and the number of rate limits
178196
returned by Codex. Header, status, errors, footer, and meter grid divide the
@@ -193,6 +211,23 @@ receives its card's remaining width and height, and resizing the terminal
193211
immediately reflows and rescales it. The underlying values and reset information
194212
never change with presentation.
195213

214+
The Stopwatch is deliberately separate from the percentage gauges: no token
215+
ceiling is exposed for those quota windows, so a percentage-based bar would be
216+
misleading. It follows the local token telemetry underlying Codex's live
217+
[`thread/tokenUsage/updated`](https://developers.openai.com/codex/app-server)
218+
data, rather than the delayed account activity
219+
summary. A separate process cannot subscribe to another Codex process's
220+
app-server connection, so Codexometer incrementally observes the equivalent
221+
`token_count` records written to local rollout files.
222+
223+
This is local activity telemetry, not account-wide billing data. It can combine
224+
multiple sessions using the same local `CODEX_HOME`, but it cannot see Codex
225+
activity on another computer, in a different Codex home, or in a cloud session
226+
that is not writing locally. Token totals normally appear when Codex emits usage
227+
for a model response, not token-by-token while a response is streaming. Raw token
228+
counts also do not reveal or reproduce the backend's quota-weighting rules, so
229+
they should not be converted directly into the percentage gauges.
230+
196231
## Options
197232

198233
```text
@@ -201,7 +236,7 @@ never change with presentation.
201236
--demo use simulated quota data
202237
--inline render inline instead of using the alternate screen
203238
--refresh DURATION refresh interval (default: 1m)
204-
--version print the version and exit
239+
-v, --version print the version and exit
205240
```
206241

207242
Examples:
@@ -236,6 +271,26 @@ GOOS=windows GOARCH=amd64 go build -trimpath -o dist/codexometer-windows-amd64.e
236271
The destination machine needs Codex installed and logged in, but it does not
237272
need Go or Codexometer's source dependencies.
238273

274+
## Versioning
275+
276+
Codexometer follows semantic versioning, beginning with `v0.1.0`. The Git tag is
277+
the release source of truth. Go automatically embeds that tag in binaries built
278+
with `go install github.com/merefield/codexometer@v0.1.0`; direct source builds
279+
fall back to the maintained value in `internal/version/version.go`.
280+
281+
Both forms report the embedded version and exit without starting the interface:
282+
283+
```sh
284+
codexometer -v
285+
codexometer --version
286+
```
287+
288+
Release automation can override the source-build fallback without editing code:
289+
290+
```sh
291+
go build -ldflags="-s -w -X github.com/merefield/codexometer/internal/version.Fallback=0.1.0" .
292+
```
293+
239294
## How refresh works
240295

241296
At startup and on each refresh, Codexometer:
@@ -251,6 +306,12 @@ interval. Pressing `r` refreshes immediately. If a refresh fails after valid
251306
data has already been displayed, Codexometer retains the last snapshot and
252307
marks it as stale instead of blanking the dashboard.
253308

309+
While Stopwatch is running it checks appended local token telemetry once per
310+
second and rolls the observed deltas into 30-second graph buckets. These reads do
311+
not contact OpenAI or invoke a model. Pressing Stop performs one immediate final
312+
local read and forces complete session discovery, including Codex sessions
313+
resumed from older rollout directories.
314+
254315
## Troubleshooting
255316

256317
### `Codex CLI not found`
@@ -282,6 +343,17 @@ Codexometer adapts its header and meter widths, but rich gauges need enough
282343
rows to display every quota window. Increase the pane height or return to the
283344
compact default bar style with `s`.
284345

346+
### Stopwatch remains at zero
347+
348+
The Stopwatch observes rollout telemetry under the same `CODEX_HOME` visible to
349+
the Codexometer process. Confirm that the Codex session doing work is local and
350+
uses that home. A native Windows Codex session and a native Windows Codexometer
351+
normally share the same user profile; WSL and native Windows have different
352+
homes unless `CODEX_HOME` is deliberately shared. Cloud activity and sessions on
353+
other machines are not visible. Usage is generally appended after a model
354+
response reports its token totals, so a currently streaming response may not
355+
appear until its next telemetry event.
356+
285357
## Development
286358

287359
Format, test, and vet the project:
@@ -304,6 +376,7 @@ Codexometer uses:
304376
- Bubble Tea for the terminal event loop
305377
- Lip Gloss for adaptive ANSI styling
306378
- Codex app-server JSON-RPC for authenticated quota data
379+
- Local Codex rollout `token_count` records for live Stopwatch telemetry
307380

308381
## License
309382

‎internal/codex/client.go‎

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,33 @@ import (
1212
"strings"
1313
"sync"
1414
"time"
15+
16+
"github.com/merefield/codexometer/internal/version"
1517
)
1618

1719
const requestTimeout = 15 * time.Second
1820

1921
// Client fetches the current quota snapshot through Codex's stable app-server API.
2022
type Client struct {
21-
Binary string
23+
Binary string
24+
LiveUsage *LiveUsageReader
25+
}
26+
27+
// FetchTokenUsage reads live telemetry from locally observed Codex sessions.
28+
func (c Client) FetchTokenUsage(ctx context.Context) (LiveUsageSnapshot, error) {
29+
if c.LiveUsage == nil {
30+
return LiveUsageSnapshot{}, errors.New("local Codex session telemetry is not configured")
31+
}
32+
return c.LiveUsage.FetchTokenUsage(ctx)
33+
}
34+
35+
// FetchTokenUsageFresh forces complete local rollout discovery for a final
36+
// Stopwatch reading.
37+
func (c Client) FetchTokenUsageFresh(ctx context.Context) (LiveUsageSnapshot, error) {
38+
if c.LiveUsage == nil {
39+
return LiveUsageSnapshot{}, errors.New("local Codex session telemetry is not configured")
40+
}
41+
return c.LiveUsage.FetchTokenUsageFresh(ctx)
2242
}
2343

2444
type rpcError struct {
@@ -83,7 +103,7 @@ func (c Client) Fetch(ctx context.Context) (Snapshot, error) {
83103
"clientInfo": map[string]string{
84104
"name": "codexometer",
85105
"title": "Codexometer",
86-
"version": Version,
106+
"version": version.Current(),
87107
},
88108
},
89109
}); err != nil {

0 commit comments

Comments
 (0)