Skip to content

Latest commit

Β 

History

187 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

claudeops-tui

Local TUI to track Claude Code usage, costs, and tasks. Single binary, no daemon, no SaaS.

Shows real subscription % from Anthropic's /api/oauth/usage endpoint β€” not estimates against guessed plan limits like every other tool out there.

What it does

  • Parses ~/.claude/projects/*.jsonl incrementally (fsnotify + persisted byte offsets)
  • Also ingests Codex rollouts (~/.codex/sessions) and opencode's SQLite database when they are present, auto-detected on first run
  • Stores events in local SQLite (modernc.org/sqlite, no CGO)
  • Computes per-event cost in € using a four-class token breakdown (input, output, cache_read, cache_create) β€” collapsing them ruins the math
  • Calls Anthropic's undocumented GET /api/oauth/usage for the real session/weekly/per-model usage that Claude Code's own /usage command uses, with OAuth token refresh
  • Tracks live quota for other services too (Codex, Copilot, Gemini, plus your own HTTP providers) β€” see docs/providers.md
  • Tracks tasks via claudeops task start "name" and attributes every event ingested while the task is active to it (time-window based, across all sessions β€” see docs/limitations.md)
  • Session drill-down β€” navigate into any session to see per-model costs, hourly activity, token breakdown with cache hit ratio, and duration
  • Daily drill-down β€” browse daily aggregates with hourly charts and per-model breakdown
  • Insights engine β€” 5 computed insights: cache efficiency, model mix, cost trend, session efficiency, peak hours
  • Classroom β€” a live grid of your currently-running Claude Code sessions, working or waiting for input
  • MCP server β€” expose all data to Claude Code, opencode, or any MCP client for conversational analysis
  • OTLP export β€” push your metrics to an OpenTelemetry endpoint, and manage Claude Code's own OTel env vars
  • Renders one consolidated Bubbletea dashboard with 8 tabs

Architecture

graph LR
    A["~/.claude/projects/*.jsonl"] -->|fsnotify + offsets| B[Collector]
    A2["~/.codex/sessions/**"] -->|fsnotify + offsets| B
    A3["opencode.db"] -->|5s poll + watermark| B
    B -->|parse + cost calc| C[(SQLite WAL)]
    D["Anthropic /api/oauth/usage"] -->|OAuth + 5min cache| E[Usage Client]
    C --> F[TUI Dashboard]
    E --> F
    C -->|read-only| G[MCP Server]
    G -->|stdio| H[Claude Code / opencode / Cursor]
    F --> I["8 tabs: Dashboard, Sessions, Projects,\nModels, Tasks, Insights, Classroom, Settings"]
Loading

Navigation flow

stateDiagram-v2
    [*] --> Normal : launch
    Normal --> DayBrowse : enter (Dashboard tab)
    DayBrowse --> DayDetail : enter on day
    DayDetail --> DayBrowse : esc
    DayBrowse --> Normal : esc

    Normal --> SessionBrowse : enter (Sessions tab)
    SessionBrowse --> SessionDetail : enter on session
    SessionDetail --> SessionBrowse : esc
    SessionBrowse --> Normal : esc

    Normal --> Normal : tab / 1-8 (switch tabs)
Loading

Install

Prebuilt binary (no Go required)

Every release ships signed-by-checksum archives for Linux, macOS and Windows on both amd64 and arm64. Grab one from Releases, or:

# Linux / macOS β€” resolves the latest release and the archive for your platform
VERSION=$(curl -fsSL https://api.github.com/repos/fullfran/claudeops-tui/releases/latest \
  | sed -n 's/.*"tag_name": *"v\([^"]*\)".*/\1/p')
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')
curl -fsSL "https://github.com/fullfran/claudeops-tui/releases/download/v${VERSION}/claudeops_${VERSION}_${OS}_${ARCH}.tar.gz" \
  | tar -xz claudeops
sudo install -m 0755 claudeops /usr/local/bin/claudeops

Verify what you downloaded against the release's checksums.txt:

sha256sum -c checksums.txt --ignore-missing

With Go

go install github.com/fullfran/claudeops-tui/cmd/claudeops@latest

Update

Either way you installed it:

claudeops update

The command works out which kind of installation it is and updates accordingly:

Installation What claudeops update does
go install re-runs go install, which is the thing that owns that binary
Downloaded archive downloads the release for your platform, verifies it against the release's checksums.txt, and replaces the running binary in place

claudeops update --check reports the published version and which of the two it would use, without changing anything.

If the binary lives somewhere you cannot write β€” /usr/local/bin usually belongs to root β€” the update stops and says so rather than escalating privileges on its own. Re-run it with sudo, or keep claudeops in a directory you own.

Manual update, for a Go installation:

go install github.com/fullfran/claudeops-tui/cmd/claudeops@latest

If the Go proxy is still serving the previous commit for a few minutes, retry with:

GOPROXY=direct go install github.com/fullfran/claudeops-tui/cmd/claudeops@latest

If claudeops is still "command not found" after installation, your Go bin directory is probably not on PATH yet:

export PATH="$(go env GOPATH)/bin:$PATH"

Add that line to your shell config (~/.bashrc, ~/.zshrc, etc.), reload the shell, and verify:

which claudeops
claudeops version

Or build locally:

git clone https://github.com/fullfran/claudeops-tui
cd claudeops-tui
CGO_ENABLED=0 go build -o claudeops ./cmd/claudeops
./claudeops

Upgrading from an earlier version? Read docs/upgrading.md β€” CODEX_HOME semantics changed and Codex users should re-ingest.

Usage

claudeops                        # launch the TUI dashboard (default)
claudeops mcp                    # start MCP server (stdio, for Claude Code / opencode)
claudeops task start "refactor parser"
claudeops task stop
claudeops task list
claudeops ingest                 # one-shot ingest of existing source files
claudeops reingest [--yes]       # rebuild the event store from source files
claudeops update                 # update the installed CLI when safe
claudeops update --check         # report what is published, install nothing
claudeops hooks install          # register Claude Code hooks for live session state
claudeops hooks uninstall        # remove claudeops hooks from settings.json
claudeops hooks status           # show which hooks are registered
claudeops hooks handle           # handle a hook event on stdin (invoked by Claude Code)
claudeops push [--dry-run] [--since RFC3339]   # push metrics to an OTLP endpoint
claudeops otel-config apply      # write Claude Code OTel env vars to settings.json
claudeops statusline             # one-line usage summary for a terminal status bar
claudeops otel-config status     # show the OTel telemetry configuration
claudeops otel-config remove     # remove the OTel telemetry configuration
claudeops version
claudeops help

reingest clears the derived event store (events, sessions, projects, offsets, watermarks) and rebuilds it from the source files. Tasks and config are kept. It asks for confirmation unless you pass --yes.

Live session state (hooks)

claudeops hooks install registers claudeops for Claude Code's SessionStart, UserPromptSubmit, Stop and SessionEnd events in ~/.claude/settings.json. Each event writes a small sidecar into ~/.claudeops/live/, which the Classroom tab uses to show whether a session is working or waiting for you. Without the hooks, Classroom still works but falls back to a file-mtime heuristic, which cannot tell "waiting for input" from "just finished".

OTLP export (push, otel-config)

claudeops push sends aggregated metrics to the OTLP HTTP endpoint configured under [export] in config.toml. Use --dry-run to print the payload instead of sending it, and --since to override the window start.

claudeops otel-config apply writes Claude Code's own telemetry env vars (CLAUDE_CODE_ENABLE_TELEMETRY, OTEL_*) into ~/.claude/settings.json so Claude Code exports directly to the same endpoint; status shows them and remove deletes only the keys claudeops manages. apply requires [export.claude_otel] enabled = true.

Keyboard shortcuts

Press ? inside the TUI for the full keybinding reference. Highlights:

Key Action
1–8 switch tab (Dashboard, Sessions, Projects, Models, Tasks, Insights, Classroom, Settings)
tab / shift+tab, h / l, ← / β†’ cycle tabs
enter browse daily breakdown (Dashboard) / browse sessions (Sessions) / drill into detail
j / k, ↑ / ↓ navigate lists (day browser, session browser, settings) / scroll
p switch subscription focus on the Dashboard (All / Claude / Codex / …)
space toggle setting (Settings tab)
enter (Settings) toggle a bool, edit a string inline, or run a β–Ί action
esc go back one level (detail β†’ browse β†’ tab)
n / S new task / stop task
r force refresh
? help overlay
q quit (goes back one level inside drill-downs)

Session drill-down

From the Sessions tab, press enter to open the session browser. Use j/k to navigate sessions β€” a preview card shows cost, events, tokens, and duration. Press enter again to see the full detail view:

  • Per-model cost breakdown with percentage of total
  • Hourly activity chart showing when cost was incurred
  • Token breakdown β€” input, output, cache read, cache create
  • Cache hit ratio β€” how effectively the session used prompt caching
  • Duration β€” first to last event timestamps

Insights tab

Press 6 to see computed insights about your usage patterns:

Insight What it detects Severity
Cache Efficiency Low prompt cache reuse across sessions Warn <20%, Tip 20-40%
Model Mix Over-reliance on a single (expensive) model Tip if >70% on one model
Cost Trend Week-over-week spending changes Warn if >50% increase
Session Efficiency Short sessions costing more per token (cold context rebuilds) Tip if 2x+ more expensive
Peak Hours When you spend the most Info (top 3 hours)

Each insight is toggleable in the Settings tab (8).

Classroom tab

Press 7 for a live grid of your currently-running Claude Code sessions β€” one "desk" per session, refreshed on the 2s tick:

  • ✨ working β€” the session is producing output
  • πŸ’€ waiting β€” the session is waiting for your input

Sessions are discovered from ~/.claude/projects file activity. Install the hooks (claudeops hooks install) for accurate state; without them the state is inferred from file mtimes alone.

MCP server

The MCP server exposes your usage data to Claude Code, opencode, Cursor, or any MCP-compatible client. This lets you ask questions about your usage conversationally:

"What project am I spending the most on this week?" "Am I using cache effectively?" "Show me my daily cost trend for the last month"

graph LR
    subgraph "claudeops mcp (stdio)"
        T1[claudeops_summary]
        T2[claudeops_sessions]
        T3[claudeops_session_detail]
        T4[claudeops_projects]
        T5[claudeops_models]
        T6[claudeops_daily]
        T7[claudeops_insights]
    end
    DB[(SQLite\nread-only)] --> T1 & T2 & T3 & T4 & T5 & T6 & T7
    T1 & T2 & T3 & T4 & T5 & T6 & T7 --> C[Claude Code / opencode]
Loading

Activate

# Register the MCP server with Claude Code
claude mcp add claudeops -- claudeops mcp

This tells Claude Code to launch claudeops mcp on demand. The server opens your SQLite database in read-only mode (safe to run alongside the TUI), answers queries via stdio, and exits when the connection closes. Zero background processes.

The Settings tab shows whether claudeops is currently registered in ~/.claude.json. It only reads that file β€” registration stays your call.

Deactivate

# Remove it β€” no more context token cost
claude mcp remove claudeops

When deactivated, the 7 tools disappear from Claude's context completely. Activate it only when you want to analyze your usage, then deactivate to save context tokens.

Available tools

Tool Description Params
claudeops_summary Cost and token aggregates period: today, 7d, 30d (required)
claudeops_sessions Sessions ranked by cost limit: 1-100 (default 20)
claudeops_session_detail Full session breakdown (models + hourly) session_id (required)
claudeops_projects Projects ranked by cost limit: 1-100 (default 20)
claudeops_models Per-model usage with cache ratios none
claudeops_daily Daily cost/events trend days: 1-90 (default 30)
claudeops_insights Computed insights from the Insights tab none

opencode

Add to ~/.config/opencode/opencode.json inside the "mcp" object:

{
  "mcp": {
    "claudeops": {
      "type": "local",
      "command": ["claudeops", "mcp"],
      "enabled": true
    }
  }
}

Set "enabled": false to deactivate without removing the entry.

Cursor / other MCP clients

Add to your MCP config file (e.g. ~/.cursor/mcp.json):

{
  "mcpServers": {
    "claudeops": {
      "command": "claudeops",
      "args": ["mcp"]
    }
  }
}

Remove the entry to deactivate.

Status bar

claudeops statusline prints the current quota as one line, for tmux, Zellij, a shell prompt, or anything else that can run a command. It ships with the CLI.

$ claudeops statusline
5h 6% Β· 7d 29%
set -g status-right "#(claudeops statusline --color) β”‚ %H:%M"

By default it follows the agent in the active pane β€” a Claude Code pane shows the Anthropic window, an opencode pane wired to OpenAI shows the Codex one β€” and it serves an on-disk cache so a bar redrawing every couple of seconds makes one request a minute rather than thirty.

--forecast adds a warning when a window is on course to run out before it resets, projected from observed utilisation rather than from spend:

$ claudeops statusline --forecast
5h 62% · 7d 29% ⚠5h out in 1h20m

Full reference, including the agent mapping, Zellij and shell prompts, and how to switch provider from a keybinding: docs/statusline.md.

There is also an opencode plugin that puts the same numbers beside the opencode prompt.

Three ways to read the same data, all backed by one shared cache:

Where How
tmux, Zellij, shell prompt claudeops statusline β€” see docs/statusline.md
beside the opencode prompt the TUI plugin
full detail claudeops β€” the dashboard

Files

Path Purpose
~/.claudeops/claudeops.db local SQLite store (WAL mode)
~/.claudeops/pricing.toml editable price table (seed shipped, edit when Anthropic changes prices)
~/.claudeops/config.toml dashboard widgets, thresholds, tab visibility, usage polling interval, export settings (auto-created on first run)
~/.claudeops/providers.toml optional user-defined quota providers (see docs/providers.md)
~/.claudeops/current-task.json sidecar for the active task
~/.claudeops/usage-cache.json statusline snapshot cache, atomic + 0600
~/.claudeops/live/ hook-written live session sidecars (Classroom tab)
~/.claude/projects/*.jsonl source data β€” read only
~/.codex/sessions/**/*.jsonl Codex source data β€” read only (override the parent dir with CODEX_HOME)
$XDG_DATA_HOME/opencode/opencode.db opencode source data β€” read only (default ~/.local/share; the conventional path is still probed as a fallback)
~/.claude/.credentials.json OAuth tokens β€” read always, written only during token refresh, atomic + 0600; locking uses the sidecar .credentials.json.lock
~/.claude/settings.json Claude Code settings β€” claudeops manages only its hook entries and OTel env vars

Configuration

~/.claudeops/config.toml is auto-created on first run. Every field falls back to its built-in default when missing, so you can delete anything you do not want to pin.

[dashboard]
show_subscription = true      # subscription % bars
show_today = true             # today's events / cost / tokens
show_top_sessions = true      # highest-cost sessions (7d)
show_top_projects = true      # highest-cost projects (7d)
show_active_task = true       # current task name + elapsed
show_sparkline_14d = true     # daily cost bar chart
show_per_model_today = true   # today's cost by model
show_burn_rate = true         # cost/hour from the last 4h
show_streak = true            # consecutive active days
show_avg_per_session = true   # today's average cost per session
show_cache_hit_ratio = true   # cache efficiency, inline on the Today card
show_tokens_per_euro = true   # inline on the Today card
show_max_day_30d = true       # most expensive day in 30d
show_vs_avg_7d = true         # daily spend vs the 7d average

[dashboard.thresholds]
daily_warn_eur = 20           # yellow threshold
daily_alert_eur = 50          # red threshold

[usage]
cache_ttl_seconds = 300       # how often to poll Anthropic's usage endpoint (default: 5min)

[tabs]                        # hide whole tabs; Dashboard, Classroom and
sessions = true               # Settings are always visible
projects = true
models = true
tasks = true
insights = true

[insights]
show_cache_efficiency = true
show_model_mix = true
show_cost_trend = true
show_session_efficiency = true
show_peak_hours = true

[keybindings]
command_palette = "ctrl+p"     # reserved; no command palette is implemented yet

[export]
enabled = false               # push metrics to an OTLP endpoint
user_name = ""                # your display name in dashboards
team_name = ""                # team label for grouping
endpoint = ""                 # OTLP HTTP endpoint URL (required when enabled)

[export.headers]              # extra headers sent with the push, e.g.
# Authorization = "Bearer …"

[export.claude_otel]
enabled = false               # manage Claude Code's own OTel env vars
include_user_prompts = false  # log user prompt content
include_tool_details = false  # log Bash commands and file paths

# Optional: pin the ingestion sources instead of auto-detecting them.
# An explicit [[sources]] list always wins.
[[sources]]
name = "claude"               # "claude" | "codex" | "opencode"
enabled = true
root = ""                     # empty uses the per-source default path
format = "jsonl"              # informational

Two keys are written by the config encoder but currently have no effect: [tabs] calendar and the whole [calendar] section. The calendar tab was never shipped β€” the 7th tab is Classroom. Leave them alone or delete them.

Roadmap

See epic #9 for the work pattern analysis roadmap:

graph TD
    P1["Phase 1: Session Drill-Down βœ…"]
    P2["Phase 2: Aggregate Insights βœ…"]
    P3["Phase 3: MCP Server βœ…"]
    P4["Phase 4: Active Logging"]

    P1 --> P2
    P2 --> P3
    P3 --> P4

    style P1 fill:#2d6a4f,color:#fff
    style P2 fill:#2d6a4f,color:#fff
    style P3 fill:#2d6a4f,color:#fff
Loading
Phase Status What
1. Session Drill-Down Done Navigate into sessions, see per-model costs, hourly charts, cache ratios
2. Aggregate Insights Done Cache efficiency, model mix, cost trend, session efficiency, peak hours
3. MCP Server Done 7 tools via claudeops mcp for conversational usage analysis
4. Active Logging Partial Hooks + Classroom track live session state; intent tagging and tool usage patterns are not implemented

Status

0.7.0. Multi-source ingestion (Claude, Codex, opencode), interactive drill-downs, computed insights, live Classroom, MCP server, and OTLP export.

Caveats

  • The /api/oauth/usage endpoint is undocumented. Anthropic can change or remove it without notice. ClaudeOps degrades gracefully ("subscription % unavailable") instead of faking numbers.
  • Pricing in TOML goes stale when Anthropic updates prices. Edit ~/.claudeops/pricing.toml.
  • The collector lives inside the TUI process. If the TUI is closed, live ingestion pauses β€” run claudeops ingest (e.g. from cron) to catch up without opening the dashboard. There is no daemon mode.

Documentation

License

MIT.

About

Local TUI to track Claude Code usage, costs, and tasks (Go + Bubbletea + SQLite)

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages