Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@
# AIUSAGE_CODEX_DIR=~/.codex
# AIUSAGE_OPENCODE_DIR=~/.local/share/opencode

# Seconds between rescans [120]
# AIUSAGE_SCAN_INTERVAL=120

# Address and port [127.0.0.1:4747]
# HOST=127.0.0.1
# PORT=4747
14 changes: 7 additions & 7 deletions .github/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,20 @@
## About

Ever wondered where all your tokens are going? Or how much your AI usage is valued at?
This usage dashboard reads your local Claude Code, Codex, Open Code transcripts and builds a beautiful dashboard breaking down your full historical usage.
This usage dashboard reads your local Claude Code, Codex and OpenCode transcripts and builds a beautiful dashboard breaking down your full historical usage.

Everything happens locally, so your data never leaves your device.
Everything happens locally, so your transcripts never leave your device.
Stats are cached in a local SQLite DB, so that you get full history even after Claude and Codex have pruned old transcripts.
Cost estimates come from current published API rates.

The dashboard shows:

- a calendar heatmap of every day since your records start
- daily totals, stacked by tool, model and project
- a calendar heatmap of the past year
- daily totals, stacked by tool or model
- the split between fresh input, output, cache reads and cache writes
- which projects, AI providers and models the your tokens went to
- which projects, AI providers and models your tokens went to
- Anthropic and OpenAI 5-hour and 7-day windows
- every session, with full token usage breakdown
- your recent sessions, with full token usage breakdown

---

Expand Down Expand Up @@ -115,7 +115,7 @@ Follow [Development](#development) below, then `npm run build && npm start`.

## Development

You'll need [Node](https://nodejs.org/) 22.12 or newer, plus [Git](https://git-scm.com/).
You'll need [Node](https://nodejs.org/) 22.17 or newer, plus [Git](https://git-scm.com/).
It's a [SvelteKit](https://svelte.dev/docs/kit) app and the build is self-contained so there's no runtime dependencies at all.

```bash
Expand Down
2 changes: 1 addition & 1 deletion docker/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ services:
token-usage-dashboard:
image: notaflightrisk/token-usage-dashboard:latest
ports:
- '8080:8080'
- '127.0.0.1:8080:8080'
volumes:
- ${HOME}/.claude:/home/node/.claude:ro
- ${HOME}/.codex:/home/node/.codex:ro
Expand Down
3 changes: 1 addition & 2 deletions src/lib/logos/marks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,5 @@ export const MARKS: Record<string, string> = {
'M20.998 10.949H24v3.102h-3v3.028h-1.487V20H18v-2.921h-1.487V20H15v-2.921H9V20H7.488v-2.921H6V20H4.487v-2.921H3V14.05H0V10.95h3V5h17.998v5.949zM6 10.949h1.488V8.102H6v2.847zm10.51 0H18V8.102h-1.49v2.847z',
codex:
'M8.086.457a6.105 6.105 0 013.046-.415c1.333.153 2.521.72 3.564 1.7a.117.117 0 00.107.029c1.408-.346 2.762-.224 4.061.366l.063.03.154.076c1.357.703 2.33 1.77 2.918 3.198.278.679.418 1.388.421 2.126a5.655 5.655 0 01-.18 1.631.167.167 0 00.04.155 5.982 5.982 0 011.578 2.891c.385 1.901-.01 3.615-1.183 5.14l-.182.22a6.063 6.063 0 01-2.934 1.851.162.162 0 00-.108.102c-.255.736-.511 1.364-.987 1.992-1.199 1.582-2.962 2.462-4.948 2.451-1.583-.008-2.986-.587-4.21-1.736a.145.145 0 00-.14-.032c-.518.167-1.04.191-1.604.185a5.924 5.924 0 01-2.595-.622 6.058 6.058 0 01-2.146-1.781c-.203-.269-.404-.522-.551-.821a7.74 7.74 0 01-.495-1.283 6.11 6.11 0 01-.017-3.064.166.166 0 00.008-.074.115.115 0 00-.037-.064 5.958 5.958 0 01-1.38-2.202 5.196 5.196 0 01-.333-1.589 6.915 6.915 0 01.188-2.132c.45-1.484 1.309-2.648 2.577-3.493.282-.188.55-.334.802-.438.286-.12.573-.22.861-.304a.129.129 0 00.087-.087A6.016 6.016 0 015.635 2.31C6.315 1.464 7.132.846 8.086.457zm-.804 7.85a.848.848 0 00-1.473.842l1.694 2.965-1.688 2.848a.849.849 0 001.46.864l1.94-3.272a.849.849 0 00.007-.854l-1.94-3.393zm5.446 6.24a.849.849 0 000 1.695h4.848a.849.849 0 000-1.696h-4.848z',
opencode: 'M22 24H2V0h20zM17 4.8H7v14.4h10z',
'gemini-cli': GEMINI
opencode: 'M22 24H2V0h20zM17 4.8H7v14.4h10z'
};
20 changes: 4 additions & 16 deletions src/lib/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,25 +61,13 @@ export type Tool = {
id: string;
label: string;
colour: string;
home: string;
};

export const TOOLS: Tool[] = [
{
id: 'claude-code',
label: 'Claude Code',
colour: '#d97757',
home: 'https://claude.com/claude-code'
},
{ id: 'codex', label: 'Codex', colour: '#10a37f', home: 'https://developers.openai.com/codex' },
{ id: 'opencode', label: 'OpenCode', colour: '#f5a524', home: 'https://opencode.ai' },
{
id: 'gemini-cli',
label: 'Gemini CLI',
colour: '#4285f4',
home: 'https://github.com/google-gemini/gemini-cli'
}
{ id: 'claude-code', label: 'Claude Code', colour: '#d97757' },
{ id: 'codex', label: 'Codex', colour: '#10a37f' },
{ id: 'opencode', label: 'OpenCode', colour: '#f5a524' }
];

export const toolMeta = (id: string) =>
TOOLS.find((tool) => tool.id === id) ?? { id, label: id, colour: '#8b93a7', home: '' };
TOOLS.find((tool) => tool.id === id) ?? { id, label: id, colour: '#8b93a7' };
5 changes: 1 addition & 4 deletions src/lib/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,5 @@ export const config = {
dbPath: env('AIUSAGE_DB', join(dataDir, 'usage.db')),
claudeDir: env('AIUSAGE_CLAUDE_DIR', join(home, '.claude')),
codexDir: env('AIUSAGE_CODEX_DIR', join(home, '.codex')),
openCodeDir: env('AIUSAGE_OPENCODE_DIR', join(dataHome, 'opencode')),
geminiDir: env('AIUSAGE_GEMINI_DIR', join(home, '.gemini')),
openRouterKey: env('OPENROUTER_API_KEY', ''),
scanIntervalMs: Number(env('AIUSAGE_SCAN_INTERVAL', '120')) * 1000
openCodeDir: env('AIUSAGE_OPENCODE_DIR', join(dataHome, 'opencode'))
};
1 change: 0 additions & 1 deletion src/lib/server/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,6 @@ function rollup(filters: Filters): Usage {
filters,
totals: totals(filters, overrides),
previous: totals(previous, overrides),
days: Math.max(1, Math.round(span / 86_400_000)),
byTool: grouped('tool', filters, overrides),
byModel,
byProject: grouped('project', filters, overrides),
Expand Down
4 changes: 2 additions & 2 deletions src/lib/server/scan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ async function scanSource(source: Source): Promise<SourceStat> {
let size = 0;
let mtime = 0;
try {
const info = await lstatOf(path);
const info = await statOf(path);
size = info.size;
mtime = info.mtime;
} catch {
Expand Down Expand Up @@ -184,7 +184,7 @@ const pad = (value: number) => String(value).padStart(2, '0');
export const localDay = (at: Date) =>
`${at.getFullYear()}-${pad(at.getMonth() + 1)}-${pad(at.getDate())}`;

async function lstatOf(path: string) {
async function statOf(path: string) {
const info = await stat(path);
return { size: info.size, mtime: Math.round(info.mtimeMs) };
}
Expand Down
2 changes: 0 additions & 2 deletions src/lib/server/sources/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,3 @@ import { opencode } from './opencode';
import type { Source } from './types';

export const SOURCES: Source[] = [claudeCode, codex, opencode];

export const sourceById = (id: string) => SOURCES.find((source) => source.id === id);
1 change: 0 additions & 1 deletion src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ export type Usage = {
filters: Filters;
totals: Slice;
previous: Slice;
days: number;
byTool: Slice[];
byModel: Slice[];
byProject: Slice[];
Expand Down
5 changes: 4 additions & 1 deletion src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,10 @@
</div>

<footer>
<span>Reads the transcripts already on this machine. Nothing is sent anywhere.</span>
<span>
Reads the transcripts already on this machine.
{saved.claudeWindows ? 'Only the plan windows check leaves it.' : 'Nothing is sent anywhere.'}
</span>
<span>
{#if usage.oldest}Records from {new Date(usage.oldest).toLocaleDateString('en-GB')} ·{/if}
Scanned {relative(usage.scannedAt)}
Expand Down
Loading