{"id":51205394,"url":"https://github.com/jamesdbartlett3/drachometer","last_synced_at":"2026-06-28T03:01:02.122Z","repository":{"id":363672267,"uuid":"1264386118","full_name":"JamesDBartlett3/drachometer","owner":"JamesDBartlett3","description":"A Claude Code hook that logs every turn and tool call to a local SQLite database, with a browser dashboard for exploring token usage, costs, and cache efficiency. Features model-aware pricing, multi-sort tables, date filtering, live SSE refresh, and rich charts. No API keys or external services.","archived":false,"fork":false,"pushed_at":"2026-06-26T18:09:53.000Z","size":322,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-26T20:08:58.274Z","etag":null,"topics":["ai","claude","claude-code","llm","token-usage"],"latest_commit_sha":null,"homepage":"","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JamesDBartlett3.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-09T20:51:11.000Z","updated_at":"2026-06-26T18:09:58.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/JamesDBartlett3/drachometer","commit_stats":null,"previous_names":["jamesdbartlett3/claude-code-token-usage-dashboard","jamesdbartlett3/drachometer"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/JamesDBartlett3/drachometer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JamesDBartlett3%2Fdrachometer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JamesDBartlett3%2Fdrachometer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JamesDBartlett3%2Fdrachometer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JamesDBartlett3%2Fdrachometer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JamesDBartlett3","download_url":"https://codeload.github.com/JamesDBartlett3/drachometer/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JamesDBartlett3%2Fdrachometer/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34875362,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-28T02:00:05.809Z","response_time":54,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","claude","claude-code","llm","token-usage"],"created_at":"2026-06-28T03:00:59.983Z","updated_at":"2026-06-28T03:01:02.115Z","avatar_url":"https://github.com/JamesDBartlett3.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Drachometer\n\n![Coin logo](coin.svg)\n\nDrachometer: \"drachma\" (ancient Greek currency) + \"meter\" = a token usage meter for Claude Code.\n\nA Claude Code hook that logs every turn and tool call to a local SQLite database, with a browser dashboard for exploring token usage, costs, and cache efficiency. Features model-aware pricing, multi-sort tables, date filtering, live SSE refresh, and rich charts. No API keys or external services.\n\n![Dashboard](https://img.shields.io/badge/dashboard-localhost:9873-c87533)\n\n## One-Line Install (Windows PowerShell)\n\n```powershell\nirm https://raw.githubusercontent.com/JamesDBartlett3/drachometer/main/drachometer-install.ps1 | iex\n```\n\n## One-Line Install (Mac, Linux, and WSL2)\n\n```bash\ncurl -fsSL https://raw.githubusercontent.com/JamesDBartlett3/drachometer/main/drachometer-install.sh | bash\n```\n\nThese bootstrap scripts resolve the latest published release via the GitHub Releases API, download the single packaged zip artifact to a temporary directory, extract it, and then run the existing installer. The installer finds your Python interpreter, copies hook scripts to `~/.claude/hooks/drachometer/`, registers them in `~/.claude/settings.json`, creates the database, and runs a smoke test.\n\n## Customizing the Install\n\nThe bootstrap scripts accept environment variables so you can point them at a fork, a private mirror, or a locally built zip:\n\n| Variable                   | Default                                               | Purpose                                                             |\n| -------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------- |\n| `DRACHOMETER_REPO`         | `JamesDBartlett3/drachometer`                         | GitHub `owner/repo` used to resolve releases                        |\n| `DRACHOMETER_RELEASES_API` | `https://api.github.com/repos/\u003cREPO\u003e/releases/latest` | Full URL for the GitHub Releases API call                           |\n| `DRACHOMETER_ASSET_NAME`   | `drachometer.zip`                                     | Filename of the release asset to download                           |\n| `DRACHOMETER_ARCHIVE_URL`  | _(not set)_                                           | When set, skips the API lookup and downloads from this URL directly |\n\nBoth scripts also accept `file://` paths and plain local filesystem paths for `ARCHIVE_URL`, which lets you install completely offline from a locally built zip:\n\n```powershell\n# Windows — install from a local zip\n$env:DRACHOMETER_ARCHIVE_URL = \"file://C:/path/to/drachometer.zip\"\nirm https://raw.githubusercontent.com/JamesDBartlett3/drachometer/main/drachometer-install.ps1 | iex\n```\n\n```bash\n# Mac/Linux/WSL2 — install from a local zip\nDRACHOMETER_ARCHIVE_URL=\"file:///path/to/drachometer.zip\" \\\n  curl -fsSL https://raw.githubusercontent.com/JamesDBartlett3/drachometer/main/drachometer-install.sh | bash\n```\n\n## Quick Start (Windows zip)\n\n1. Extract the zip\n2. Double-click **drachometer-install.bat**\n3. Open **[http://localhost:9873/drachometer-dashboard.html](http://localhost:9873/drachometer-dashboard.html)**\n\nThat's it. Usage is logged automatically from that point on.\n\n\u003e The installer finds your Python interpreter, copies hook scripts to `~/.claude/hooks/drachometer/`, registers them in `~/.claude/settings.json`, creates the database, and runs a smoke test. Any existing hooks are left in place.\n\n\u003cimg width=\"3679\" height=\"1912\" alt=\"image\" src=\"https://github.com/user-attachments/assets/57220cd7-8097-4d57-ab61-546ab50af504\" /\u003e\n\n## Dashboard Features\n\n### KPIs\n\n- **Total Cost** with daily average\n- **Total Input Context** with cache hit percentage\n- **Output Tokens** with uncached input count\n- **Turns** with tool call count\n- **Sessions** with average turns per session\n- **Avg Cost / Session** with per-turn average\n\n### Charts\n\n- **Daily Cost Breakdown** — waterfall chart showing cost by category (uncached input, output, cache read, cache create) with a running total\n- **Cost by Day** — line chart with one line per model tier (Opus, Sonnet, Haiku)\n- **Top Tools** — horizontal bar chart of most-used tools\n- **Cache Hit Rate** — line chart of daily prompt cache efficiency\n\n### Tables\n\n- **Sessions** — cost, tokens, model, directory, and branch per session\n- **Recent Turns** — last 50 turns with full token breakdown\n\n### Interactive Features\n\n- **Date range slicer** — preset buttons (All, Today, 7d, 30d, 90d) and a Flatpickr date range picker in the header; selection persists across refreshes\n- **Hourly drill-down** — when a single date is selected, time-based charts automatically switch from daily to hourly granularity\n- **Multi-sort tables** — click any column header to sort; Ctrl+click to add secondary/tertiary sort columns (▲/▼ indicators with subscript priority)\n- **Rich chart tooltips** — hover over any chart element to see cost and full token breakdown (uncached input, output, cache read, cache create)\n- **Live refresh** — the dashboard auto-updates via SSE when the database changes (no manual reload needed)\n- **Release update notice** — the dashboard checks GitHub Releases and shows a banner when a newer semver release is available\n- **Info tooltips** — hover over any card's info icon for an explanation of how to read that visual\n- **Local timezone** — all dates and times display in your browser's timezone\n- **Model-aware pricing** — costs are calculated per-row using each turn's actual model (Opus, Sonnet, or Haiku pricing)\n\n## What Gets Logged\n\nEach **turn** (one assistant response) records:\n\n- Token counts: uncached input, output, cache read, cache creation\n- Model relationship (`model_id`) to the `models` dimension table\n- Working directory and git branch\n- Stop reason\n- Timestamp (UTC)\n\nEach **tool call** records:\n\n- Tool name and input\n- Exit code and errors\n- Linked back to the parent turn\n\nAll data is extracted from Claude Code's transcript files — no API keys or external services required.\n\nEach **model** row in the dimension table stores:\n\n- Model key from transcript data (e.g. `claude-opus-4-20250115`)\n- Model name\n- Model version\n- Model provider\n- Token pricing (input, output, cache read, cache creation)\n\n## How It Works\n\nThe installer registers two Claude Code hooks:\n\n- **Stop** — fires after each assistant turn. Reads the transcript file to extract token usage, model, and stop reason. Upserts model metadata into the `models` table, then writes the turn row.\n- **PostToolUse** — fires after each tool call. Writes a row to the `tool_calls` table.\n\nOn first run, the hook also starts a lightweight HTTP server (port 9873) that serves the dashboard and reads directly from the live database.\nThe installer now detects the installed version and runs migrations automatically before files are copied.\nWhen the installer finds models with missing metadata during migration, it prompts for any missing name/version/provider/pricing values.\n\n## Automatic Migration Mechanism\n\nDuring install/upgrade, `drachometer-install.py`:\n\n1. Reads the installed app version from `~/.claude/hooks/drachometer/drachometer-version.json` (defaults to `0.0.0` when not present).\n2. Applies migration steps for older versions while preserving existing data.\n   - Database filename/path migration for known legacy locations.\n   - Hook settings migration for HTTP server behavior changes (removes legacy direct `drachometer-serve-dashboard.py` hooks).\n   - Database schema migration/backfill via normal DB initialization.\n3. Copies the latest files and writes the new version metadata.\n\nThe SQL migration file is still available for manual use when needed:\n\n`migrations/001_migrate_to_model_dimension.sql`\n\nThat script performs the model-dimension migration in one transaction by:\n\n1. Creates `models` if it does not exist.\n2. Adds `turns.model_id` as a foreign key to `models(id)`.\n3. Inserts one `models` row per distinct non-empty legacy `turns.model` value.\n4. Backfills `turns.model_id` by joining legacy `turns.model` values to `models.model_key`.\n5. Creates `idx_turns_model_id` for query performance.\n\n### Full schema diagrams\n\nBefore migration (legacy schema):\n\n```text\nturns\n├─ id                    INTEGER PRIMARY KEY AUTOINCREMENT\n├─ session_id            TEXT NOT NULL\n├─ turn_id               TEXT NOT NULL\n├─ recorded_at           TEXT NOT NULL\n├─ stop_reason           TEXT\n├─ input_tokens          INTEGER NOT NULL DEFAULT 0\n├─ output_tokens         INTEGER NOT NULL DEFAULT 0\n├─ cache_read_tokens     INTEGER NOT NULL DEFAULT 0\n├─ cache_creation_tokens INTEGER NOT NULL DEFAULT 0\n├─ cwd                   TEXT\n├─ git_branch            TEXT\n└─ model                 TEXT\n   UNIQUE(session_id, turn_id)\n\ntool_calls\n├─ id          INTEGER PRIMARY KEY AUTOINCREMENT\n├─ turn_pk     INTEGER REFERENCES turns(id) ON DELETE CASCADE\n├─ session_id  TEXT NOT NULL\n├─ turn_id     TEXT NOT NULL\n├─ recorded_at TEXT NOT NULL\n├─ tool_name   TEXT\n├─ tool_input  TEXT\n├─ exit_code   INTEGER\n└─ error       TEXT\n```\n\nAfter migration (new schema):\n\n```text\nmodels\n├─ id                            INTEGER PRIMARY KEY AUTOINCREMENT\n├─ model_key                     TEXT NOT NULL UNIQUE\n├─ model_name                    TEXT\n├─ model_version                 TEXT\n├─ model_provider                TEXT\n├─ input_price_per_mtok          REAL\n├─ output_price_per_mtok         REAL\n├─ cache_read_price_per_mtok     REAL\n└─ cache_creation_price_per_mtok REAL\n\nturns\n├─ id                    INTEGER PRIMARY KEY AUTOINCREMENT\n├─ session_id            TEXT NOT NULL\n├─ turn_id               TEXT NOT NULL\n├─ recorded_at           TEXT NOT NULL\n├─ stop_reason           TEXT\n├─ input_tokens          INTEGER NOT NULL DEFAULT 0\n├─ output_tokens         INTEGER NOT NULL DEFAULT 0\n├─ cache_read_tokens     INTEGER NOT NULL DEFAULT 0\n├─ cache_creation_tokens INTEGER NOT NULL DEFAULT 0\n├─ cwd                   TEXT\n├─ git_branch            TEXT\n├─ model                 TEXT\n└─ model_id              INTEGER REFERENCES models(id)\n   UNIQUE(session_id, turn_id)\n\ntool_calls\n├─ id          INTEGER PRIMARY KEY AUTOINCREMENT\n├─ turn_pk     INTEGER REFERENCES turns(id) ON DELETE CASCADE\n├─ session_id  TEXT NOT NULL\n├─ turn_id     TEXT NOT NULL\n├─ recorded_at TEXT NOT NULL\n├─ tool_name   TEXT\n├─ tool_input  TEXT\n├─ exit_code   INTEGER\n└─ error       TEXT\n\nschema_migrations\n└─ version TEXT PRIMARY KEY\n\nRelationships\n├─ tool_calls.turn_pk -\u003e turns.id\n└─ turns.model_id -\u003e models.id\n```\n\nRecommended manual upgrade procedure (if you run SQL migration directly):\n\n1. Stop Claude Code so no writes occur during migration.\n2. Back up the database:\n   - `cp ~/.claude/drachometer.db ~/.claude/drachometer.db.bak`\n3. Run the migration script:\n   - `sqlite3 ~/.claude/drachometer.db \u003c migrations/001_migrate_to_model_dimension.sql`\n4. Verify migration results:\n   - `SELECT COUNT(*) FROM turns WHERE model IS NOT NULL AND TRIM(model) \u003c\u003e '' AND model_id IS NULL;` (should be `0`)\n   - `SELECT COUNT(*) FROM turns t LEFT JOIN models m ON m.id = t.model_id WHERE t.model_id IS NOT NULL AND m.id IS NULL;` (should be `0`)\n5. Start Claude Code again.\n\n## Model Pricing\n\nAll per-token pricing lives in one place — [`drachometer-pricing.json`](drachometer-pricing.json) — and is expressed in dollars per million tokens per model tier:\n\n```json\n{\n  \"tiers\": {\n    \"opus\": {\n      \"input\": 5,\n      \"output\": 25,\n      \"cache_read\": 0.5,\n      \"cache_create\": 6.25\n    },\n    \"sonnet\": {\n      \"input\": 3,\n      \"output\": 15,\n      \"cache_read\": 0.3,\n      \"cache_create\": 3.75\n    },\n    \"haiku\": {\n      \"input\": 1,\n      \"output\": 5,\n      \"cache_read\": 0.1,\n      \"cache_create\": 1.25\n    }\n  }\n}\n```\n\nThe tier is inferred from the model key (e.g. `claude-sonnet-4-6` → Sonnet). The same file is read by all three components, so they never disagree:\n\n- The **dashboard** fetches the latest `drachometer-pricing.json` from the repo (GitHub raw) on load, falling back to the installed copy, then to a built-in table if offline.\n- The **hook** reads its installed copy when it first records a model, storing that model's prices in the `models` table.\n- The **installer** reads it when creating model rows during install/migration.\n\nA turn's cost uses the model's stored per-row pricing when present (so you can hand-edit a model's prices in the `models` table), otherwise its tier's pricing. A model key that matches no known tier is costed at `$0.00` (an honest \"unknown\") rather than being silently assumed to be a particular tier.\n\n### Automatic pricing updates\n\nAnthropic does not publish a pricing REST API, so [`.github/workflows/update-pricing.yml`](.github/workflows/update-pricing.yml) runs [`scripts/drachometer-update-pricing.py`](scripts/drachometer-update-pricing.py) on a weekly schedule (and on demand) to scrape the published pricing and commit any changes to `drachometer-pricing.json`.\n\nEvery tier is **optional**: if a model disappears from the pricing page (e.g. a withdrawn model), its last-known price is preserved rather than wiped. The scraper is also **fail-loud**: if it can't parse _any_ pricing at all — which means the page format or URL changed — it exits non-zero and writes nothing, so the workflow run fails (notifying maintainers) while the last-good `drachometer-pricing.json` stays in effect.\n\nThe `fable` tier is fully wired through the dashboard, hook, and installer, so when Claude Fable pricing is published it is picked up automatically with no code or schema change.\n\n\u003e The autonomous commit needs the repository's Actions token to allow writes — enable **Settings → Actions → General → Workflow permissions → \"Read and write permissions\"**.\n\n## Data Retention\n\nYou can automatically purge old records by setting a retention window (in days):\n\n- Add `\"token_usage_retention_days\": 30` to `~/.claude/settings.json`, or\n- Set `TOKEN_USAGE_RETENTION_DAYS=30` in the environment where Claude Code runs.\n\nWhen configured, the hook deletes `turns` and `tool_calls` rows older than the retention window each time it runs.\n\n## Mesh Replication (LAN/VM)\n\n\u003e **Status:** Phase 1 (MVP). Opt-in and off by default — single-node users are unaffected.\n\nMesh replication lets several machines on the same trusted network share one combined view of your Claude Code usage. Each node keeps its own local database and they converge toward the union of everyone's history.\n\n### Scope and safety\n\n- **LAN/VM networks only.** This is designed for a home/lab network or a set of VMs. It is **out of scope** for the public internet/WAN.\n- **Not a security boundary.** There is no authentication and no TLS. The mesh identifier exists to prevent _accidental_ cross-merges between unrelated meshes that happen to share a LAN (coworkers, roommates) — not to keep data private. **Do not expose the mesh port to the internet.** Restrict it with your firewall to the LAN/VM subnet.\n\n### How it works\n\n- Every local write to `turns`/`tool_calls` also appends an immutable event to an append-only `oplog`, keyed by a **content hash** so applying the same event twice is a no-op.\n- Nodes gossip over plain HTTP using **pull-based anti-entropy**: a node compares per-origin event counts with each peer and fetches only the events it is missing. No broker and no third-party dependencies.\n- Because each Claude Code session runs on a single machine, `session_id` partitions writes by node, so merging is a conflict-free **union**. The rare case of the same turn re-logged is resolved last-writer-wins by timestamp.\n\n### Enabling it\n\nCreate a new mesh on the first node (during install or any time afterward):\n\n```powershell\n# during install\npython drachometer-install.py --enable-mesh --mesh-name home\n\n# or later, from the installed location\npython \"$HOME/.claude/hooks/drachometer/drachometer_mesh.py\" init --name home\n```\n\nThis prints a **mesh id** like `home-a1b2c3d4`. Join it from another node:\n\n```powershell\npython drachometer-install.py --join-mesh home-a1b2c3d4 --peer 192.168.1.10:9874\n# or later:\npython \"$HOME/.claude/hooks/drachometer/drachometer_mesh.py\" join home-a1b2c3d4 --peer 192.168.1.10:9874\n```\n\nOptions: `--mesh-port` (default `9874`), `--advertise HOST` (the address peers use to reach this node; auto-detected if omitted), and repeatable `--peer HOST:PORT` seeds. The mesh server starts automatically with the dashboard server.\n\nJoining a node that already has history is safe: its existing rows are **backfilled** into the oplog and replicate to peers, while peers' history flows back — so two independently-created clients converge to the union.\n\n### Maintenance\n\n```powershell\npython \"$HOME/.claude/hooks/drachometer/drachometer_mesh.py\" status          # config, oplog counts, peer reachability\npython \"$HOME/.claude/hooks/drachometer/drachometer_mesh.py\" import OTHER.db  # merge an offline database file\npython \"$HOME/.claude/hooks/drachometer/drachometer_mesh.py\" compact --dry-run  # preview retention-based oplog compaction\npython \"$HOME/.claude/hooks/drachometer/drachometer_mesh.py\" migrate         # sync mesh schema metadata after upgrades\npython \"$HOME/.claude/hooks/drachometer/drachometer_mesh.py\" disable          # stop replicating (history preserved)\n```\n\n### Phase 2 hardening\n\nThe mesh config file (`~/.claude/drachometer-mesh.json`) now supports operational tuning such as:\n\n- `log_level` (`debug`, `info`, `warning`, `error`)\n- `max_retries` and `retry_backoff_seconds` for transient peer failures\n- `retention_days` and `retention_keep_per_origin` for safe oplog compaction\n- `compress_payloads` to reduce the size of replication traffic over the LAN\n\nThe status command also reports basic health signals (peer reachability, replication lag, dedupe rate, conflict rate, and failed sync attempts) so you can spot unhealthy peers before they drift too far apart.\n\n### Firewall\n\nAllow inbound TCP on the mesh port (default `9874`) **only from your LAN/VM subnet**. Example (Windows PowerShell, adjust the subnet):\n\n```powershell\nNew-NetFirewallRule -DisplayName \"Drachometer mesh\" -Direction Inbound -Protocol TCP `\n  -LocalPort 9874 -RemoteAddress 192.168.1.0/24 -Action Allow\n```\n\n### Recovery\n\nA new or long-offline node converges automatically on the next gossip round — its first anti-entropy pass pulls the full event history (the bootstrap snapshot), and subsequent rounds catch up incrementally. If a node's database is lost, reinstall, re-`join` the mesh with the same mesh id, and it will re-pull everyone's history. Diagnostics are written to `~/.claude/drachometer-mesh.log`.\n\n## Files\n\n```\ndrachometer-install.bat             # Double-click installer (Windows zip)\ndrachometer-install.ps1             # Network installer bootstrap (Windows PowerShell)\ndrachometer-install.py              # Installer script\ndrachometer-install.sh              # Network installer bootstrap (Mac/Linux/WSL2)\nhooks/drachometer-log-usage.py      # Hook script (Stop + PostToolUse events)\ndrachometer-serve-dashboard.py         # Dashboard server (auto-launched by hook)\ndrachometer_mesh.py                 # Mesh replication library + CLI (opt-in, LAN/VM)\ndrachometer-dashboard.html             # Browser dashboard (sql.js + Chart.js)\ndrachometer-pricing.json            # Per-tier model pricing (single source of truth)\ndrachometer-version.json # App version + GitHub release metadata\ncoin.svg                # Logo / favicon artwork\nscripts/drachometer-update-pricing.py             # Scrapes Anthropic pricing -\u003e drachometer-pricing.json\n.github/workflows/release-package.yml # Publishes the release zip asset\n.github/workflows/update-pricing.yml  # Weekly pricing refresh (commits drachometer-pricing.json)\n```\n\n## Installed Locations\n\nAfter install, the source folder can be deleted. Everything runs from:\n\n```\n~/.claude/hooks/drachometer/drachometer-log-usage.py    # Hook script\n~/.claude/hooks/drachometer/drachometer-serve-dashboard.py # Dashboard server\n~/.claude/hooks/drachometer/drachometer_mesh.py         # Mesh replication library + CLI\n~/.claude/hooks/drachometer/drachometer-dashboard.html     # Dashboard\n~/.claude/hooks/drachometer/drachometer-pricing.json    # Per-tier model pricing\n~/.claude/hooks/drachometer/drachometer-version.json    # Installed version metadata\n~/.claude/drachometer.db          # SQLite database\n~/.claude/drachometer-mesh.json   # Mesh config (only if mesh is enabled)\n~/.claude/drachometer-mesh.log    # Mesh diagnostics (only if mesh is enabled)\n~/.claude/settings.json           # Hook registrations (merged, not replaced)\n```\n\n## Viewing the Dashboard\n\nOpen **[http://localhost:9873/drachometer-dashboard.html](http://localhost:9873/drachometer-dashboard.html)** in your browser.\n\nThe server starts automatically on your first Claude Code session after install. If the server isn't running, just start any Claude Code session and it will launch.\n\nYou can also open `drachometer-dashboard.html` directly in a browser and drag-and-drop the database file (`~/.claude/drachometer.db`) onto it.\n\n## Uninstalling\n\n1. Remove the `Stop` and `PostToolUse` entries that reference `drachometer-log-usage.py` from `~/.claude/settings.json`\n2. Delete the installed directory and database:\n   ```bash\n   # Mac / Linux / WSL2\n   rm -rf ~/.claude/hooks/drachometer/\n   rm ~/.claude/drachometer.db\n   ```\n   ```powershell\n   # Windows PowerShell\n   Remove-Item -Recurse -Force \"$HOME/.claude/hooks/drachometer/\"\n   Remove-Item \"$HOME/.claude/drachometer.db\"\n   ```\n3. If you enabled mesh replication, also remove its config and log:\n   ```bash\n   # Mac / Linux / WSL2\n   rm -f ~/.claude/drachometer-mesh.json ~/.claude/drachometer-mesh.log\n   ```\n   ```powershell\n   # Windows PowerShell\n   Remove-Item -Force \"$HOME/.claude/drachometer-mesh.json\", \"$HOME/.claude/drachometer-mesh.log\" -ErrorAction SilentlyContinue\n   ```\n\n## Requirements\n\n- Python 3.10+\n- Claude Code\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesdbartlett3%2Fdrachometer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjamesdbartlett3%2Fdrachometer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesdbartlett3%2Fdrachometer/lists"}