tokentracker

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 2, 2026 License: MIT Imports: 10 Imported by: 0

README

token-tracker

A small Go package that scans the local files LLM CLIs and desktop apps leave behind (Claude Code, Codex, ChatGPT, Gemini, ...) and reports how many tokens they've used — broken down by provider and by token type (input, output, cache creation, cache read). Same idea as ccusage, minus the pricing/cost calculations: this package only counts tokens.

It's meant to be imported into other Go programs (this repo's original motivation was a Wails desktop app that turns your local LLM usage into an arbitrary in-game resource), and ships a small CLI for manually exercising it.

Status

Provider Status
Claude Code Implemented — reads ~/.claude/projects/**/*.jsonl
Codex Stub — file format not yet confirmed against real sample files
ChatGPT Stub — desktop app may not persist local token usage at all
Gemini Stub — file format not yet confirmed against real sample files

Stubs satisfy the same Scanner interface and are registered, but return no entries until a real parser is written against an actual sample file.

Install

go get github.com/mattgrunwald/token-tracker

Usage

report, err := tokentracker.Scan()
if err != nil {
    // err may be non-nil even with usable results: one provider failing
    // doesn't stop the others, so check report.Entries too.
}

fmt.Println("total tokens:", report.Total())
fmt.Println("claude-code tokens:", report.ByProvider[tokentracker.ProviderClaudeCode].Total())

// Scope down to a single arbitrary day...
today := report.Day(time.Now())
fmt.Println("today:", today.Total())

// ...or bucket the whole history by day.
for date, day := range report.ByDay(time.Local) {
    fmt.Println(date, day.Total())
}

Scan() walks the filesystem, so call it once and slice the returned Report with Day, ByDay, or Filter for repeated queries instead of rescanning.

CLI

go run ./cmd/tokentracker              # all-time totals, one row per provider
go run ./cmd/tokentracker -day 2026-07-11   # totals for one arbitrary day
go run ./cmd/tokentracker -daily            # one row per day, all history

Development

Go version is pinned via mise (mise.toml). With eval "$(mise activate zsh)" in your shell profile, go resolves to the pinned version automatically in this directory.

go build ./...
go vet ./...
go test ./...

License

MIT

Documentation

Overview

Package tokentracker scans local machine files left behind by LLM CLIs and desktop apps (Claude Code, Codex, ChatGPT, Gemini, ...) and reports how many tokens they've used, broken down by provider and by token type.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Entry

type Entry struct {
	Provider Provider
	Model    string
	Time     time.Time
	Usage    TokenUsage
	Source   string // path to the file this entry was read from
}

Entry is a single usage record read from a provider's local log/history file.

type IncrementalScanner added in v0.2.0

type IncrementalScanner interface {
	Scanner
	ScanIncremental(cache *fileCache) ([]Entry, error)
}

IncrementalScanner is implemented by Scanners that can skip re-reading files unchanged since a previous scan, given a persistent per-file cache. A Watcher uses this when a registered Scanner implements it, falling back to plain Scan otherwise — which is exactly what a Scanner with nothing to cache (a stub, or one with no local files yet) wants anyway.

type Provider

type Provider string

Provider identifies which LLM tool produced a usage record.

const (
	ProviderClaudeCode Provider = "claude-code"
	ProviderCodex      Provider = "codex"
	ProviderChatGPT    Provider = "chatgpt"
	ProviderGemini     Provider = "gemini"
)

type Report

type Report struct {
	Entries     []Entry
	ByProvider  map[Provider]TokenUsage
	ByTokenType TokenUsage
}

Report aggregates token usage across all detected providers.

func Scan

func Scan() (Report, error)

Scan walks every registered provider's local usage files and returns the combined report, covering every entry ever recorded (not just one day). A provider failing to scan does not stop the others; their errors are joined and returned alongside whatever entries were recovered. Use Report.Day or Report.Filter to narrow the result down to a specific date.

func (Report) ByDay

func (r Report) ByDay(loc *time.Location) map[string]Report

ByDay buckets every entry into a Report per calendar day, keyed by "2006-01-02" using loc for day boundaries. Pass time.Local to bucket by your machine's local days, or time.UTC to match raw timestamps.

func (Report) Day

func (r Report) Day(t time.Time) Report

Day returns a Report scoped to entries whose timestamp falls on the same calendar day as t, using t's own location to decide day boundaries. Pass time.Now() for "today", or a time.Time constructed for the target date (e.g. time.Date(2026, 7, 15, 0, 0, 0, 0, time.Local)).

func (Report) Filter

func (r Report) Filter(keep func(Entry) bool) Report

Filter returns a new Report containing only the entries for which keep returns true. ByProvider and ByTokenType are recomputed from the kept entries.

func (Report) Total

func (r Report) Total() int64

Total returns the sum of every token, across every provider and type.

type Scanner

type Scanner interface {
	Provider() Provider
	Scan() ([]Entry, error)
}

Scanner locates and parses a specific LLM tool's local usage files.

type TokenType

type TokenType string

TokenType identifies a category of tokens within a single LLM exchange.

const (
	TokenInput         TokenType = "input"
	TokenOutput        TokenType = "output"
	TokenCacheCreation TokenType = "cache_creation"
	TokenCacheRead     TokenType = "cache_read"
)

type TokenUsage

type TokenUsage map[TokenType]int64

TokenUsage holds token counts keyed by TokenType.

func (TokenUsage) Add

func (u TokenUsage) Add(other TokenUsage) TokenUsage

Add returns a new TokenUsage with u and other's counts summed per type. Either receiver may be nil.

func (TokenUsage) Total

func (u TokenUsage) Total() int64

Total returns the sum of all token counts in u.

type Watcher added in v0.2.0

type Watcher struct {
	// contains filtered or unexported fields
}

Watcher wraps the registered Scanners with a persistent, in-memory, per-file cache so repeated Rescan calls over the life of a long-running process — a game or dashboard polling for fresh usage, unlike Scan's documented call-once-and-slice-the-result use — only pay the cost of re-reading files that actually changed since the last call.

A Watcher must be reused across calls (via NewWatcher, once) rather than recreated each time, or there's nothing for it to cache against. Its cache lives only in memory: nothing is persisted to disk, so the first Rescan after a process starts pays the same full-scan cost Scan always does.

func NewWatcher added in v0.2.0

func NewWatcher() *Watcher

NewWatcher creates a Watcher with an empty cache for each registered provider.

func (*Watcher) Rescan added in v0.2.0

func (w *Watcher) Rescan() (Report, error)

Rescan returns the same complete, correct Report Scan does, but reuses cached entries for any file whose size and modification time haven't changed since this Watcher's last Rescan (or construction, for the first call).

Directories

Path Synopsis
cmd
tokentracker command
Command tokentracker prints a table of local LLM token usage, for manually exercising the tokentracker package.
Command tokentracker prints a table of local LLM token usage, for manually exercising the tokentracker package.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL