{"id":51172382,"url":"https://github.com/openwong2kim/wlog","last_synced_at":"2026-06-27T01:31:07.245Z","repository":{"id":366475297,"uuid":"1276370775","full_name":"openwong2kim/wlog","owner":"openwong2kim","description":"See your Claude Code session's tokens, cost \u0026 tool decisions without standing up Grafana — a single-binary, local, zero-config observability dashboard with an embedded web UI and a native desktop app.","archived":false,"fork":false,"pushed_at":"2026-06-22T01:26:57.000Z","size":1225,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-22T03:14:53.048Z","etag":null,"topics":["anthropic","claude","claude-code","cli","cost-tracking","dashboard","developer-tools","golang","llm","monitoring","observability","opentelemetry","otlp","self-hosted","sqlite","tauri","telemetry","token-usage"],"latest_commit_sha":null,"homepage":"https://github.com/openwong2kim/wlog/releases/latest","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/openwong2kim.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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-21T22:34:37.000Z","updated_at":"2026-06-22T01:52:02.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/openwong2kim/wlog","commit_stats":null,"previous_names":["openwong2kim/wlog"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/openwong2kim/wlog","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openwong2kim%2Fwlog","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openwong2kim%2Fwlog/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openwong2kim%2Fwlog/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openwong2kim%2Fwlog/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/openwong2kim","download_url":"https://codeload.github.com/openwong2kim/wlog/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openwong2kim%2Fwlog/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34839004,"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-26T02:00:06.560Z","response_time":106,"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":["anthropic","claude","claude-code","cli","cost-tracking","dashboard","developer-tools","golang","llm","monitoring","observability","opentelemetry","otlp","self-hosted","sqlite","tauri","telemetry","token-usage"],"created_at":"2026-06-27T01:31:03.339Z","updated_at":"2026-06-27T01:31:07.233Z","avatar_url":"https://github.com/openwong2kim.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/screenshots/logo.png\" alt=\"wlog\" width=\"96\" height=\"96\"\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003ewlog\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cb\u003eSee what your Claude Code session is actually doing — tokens, cost, and tool decisions — without standing up Grafana.\u003c/b\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  Single static binary · reads \u003ccode\u003e~/.claude/projects\u003c/code\u003e · zero-config · local-only · embedded web UI\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/openwong2kim/wlog/releases/latest\"\u003e\u003cimg src=\"https://img.shields.io/github/v/release/openwong2kim/wlog?sort=semver\u0026color=2ea043\" alt=\"Latest release\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://github.com/openwong2kim/wlog/actions/workflows/ci.yml\"\u003e\u003cimg src=\"https://github.com/openwong2kim/wlog/actions/workflows/ci.yml/badge.svg\" alt=\"CI\"\u003e\u003c/a\u003e\n  \u003ca href=\"https://pkg.go.dev/github.com/openwong2kim/wlog\"\u003e\u003cimg src=\"https://pkg.go.dev/badge/github.com/openwong2kim/wlog.svg\" alt=\"Go reference\"\u003e\u003c/a\u003e\n  \u003ca href=\"LICENSE\"\u003e\u003cimg src=\"https://img.shields.io/badge/license-MIT-blue\" alt=\"MIT license\"\u003e\u003c/a\u003e\n  \u003cimg src=\"https://img.shields.io/badge/platform-linux%20%C2%B7%20macOS%20%C2%B7%20windows-lightgrey\" alt=\"Platforms\"\u003e\n\u003c/p\u003e\n\n---\n\n`wlog` reads the transcript files Claude Code already writes, normalizes everything locally, and serves an embedded web UI. No Docker, no daemon, no external database, no SaaS. Run `wlog` and your past sessions — cost, tokens, and command history — are there immediately, with **zero configuration**. Turn on OpenTelemetry (OTLP) when you also want live updates and tool-decision data.\n\n\u003e Your Claude Code already writes its history to disk and already speaks OTel. You just need something to read it — without `docker-compose`.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/screenshots/wlog-demo.gif\" alt=\"wlog demo — cycling through the Now, Sessions, Cost, Daily, Tools, and Timeline screens\" width=\"900\"\u003e\n\u003c/p\u003e\n\u003cp align=\"center\"\u003e\u003ci\u003eOne local binary, six screens: a live Now tail, cost \u0026amp; tokens, a GitHub-style Daily usage heatmap, tool accept/reject, and a per-session timeline.\u003c/i\u003e\u003c/p\u003e\n\n---\n\n## Highlights\n\n- 🟢 **Zero-config** — reads `~/.claude/projects` transcripts; your past sessions appear the moment you run it\n- 📊 **Six screens** — Now (live), Sessions, Cost \u0026 Tokens, **Daily** usage heatmap, Tools, Timeline\n- 🟩 **Daily \"잔디\"** — a GitHub-style contribution grid of tokens (or cost) per day\n- 🔍 **Filters everywhere** — shared period (7d · 30d · 90d · 1y · All); per-model \u0026 hour/day on Cost\n- 🖥️ **Desktop app** — a native Tauri app that bundles `wlog` as a sidecar: one window, no terminal\n- 🔒 **Local-only** — no Docker, no SaaS, no external DB; prompt collection is opt-in\n- 📡 **Optional OTLP** — add live updates and tool-decision data when you want them\n- 📦 **Single static binary** — pure-Go (incl. SQLite), ~26 MB, copy-and-run\n\n---\n\n## Why wlog\n\nThe usual ways to look at Claude Code telemetry are heavy:\n\n- **SaaS backends** (SigNoz Cloud, Datadog, Honeycomb) — your token, cost, and tool-decision data leaves your machine, and the onboarding/pricing is overkill for one developer.\n- **Self-hosted stacks** — almost every guide is `Collector → Prometheus + Loki → Grafana`: four containers, four ports, and a \"don't forget to start it every morning\" footnote.\n\nwlog sells simplicity as a feature. It is local-only, runs only when you run it, and goes from install to a live dashboard in about five minutes.\n\n### Two data sources, one local store\n\n| Source | Setup | What it gives you |\n|---|---|---|\n| **Transcript JSONL** (`~/.claude/projects/**/*.jsonl`) | None — on by default | Past sessions immediately: cost, tokens, model, cache hits, bash/MCP runs, repo \u0026 branch. Zero-config. |\n| **OTLP** | One env block (opt-in) | Live updates as a session runs, plus `tool_decision` data (accept/reject and the source: config / hook / user). |\n\nStart with **just the JSONL source** to see everything you've already done. Add **OTLP** when you want the live tail and the tool-decision drill-downs. The two sources are de-duplicated against each other, so running both never double-counts cost.\n\n---\n\n## Screens\n\nEvery view shares a **period filter** (`7d · 30d · 90d · 1y · All`) that scopes the whole dashboard, and the design is deliberately text- and whitespace-forward: monospace numbers, a single accent color, minimal motion, no AI-slop ornamentation.\n\n### Now — your live session\n\nCurrent session cost (max emphasis), input/output tokens, active time, the in-progress tool call, and a live event tail (SSE). Calm 2-second tick — no spinners, no count-up.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/screenshots/wlog-now.png\" alt=\"Now screen: live session cost card, active tool, live event tail\" width=\"860\"\u003e\n\u003c/p\u003e\n\n### Daily — usage heatmap\n\nOne square per calendar day, shaded by how much you spent. Toggle between **tokens** and **cost**, scope it with the period filter, hover any day for the exact figure. Folded to your **local** calendar day (no UTC drift).\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/screenshots/wlog-daily.png\" alt=\"Daily heatmap with tokens/cost toggle and period filter\" width=\"640\"\u003e\n\u003c/p\u003e\n\n### Cost \u0026 Tokens\n\nCumulative-spend step line, a `cache_hit_ratio` card with estimated savings, an input/output token series, and a per-model table. Filter by **period**, **hour/day bucket**, and **model**.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/screenshots/wlog-cost.png\" alt=\"Cost screen: cumulative spend chart, cache hit ratio, per-model table, model/bucket filters\" width=\"860\"\u003e\n\u003c/p\u003e\n\n### Sessions\n\nOne row per session: start time, cost, duration, tool accept rate, total tokens, model, and a source badge (`events` / `metrics` / `mixed`). Search by id/date, sort by time/cost/tokens, click a row to open its Timeline.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/screenshots/wlog-sessions.png\" alt=\"Sessions table: per-session cost, duration, accept rate, tokens, model\" width=\"860\"\u003e\n\u003c/p\u003e\n\n### Tools — decisions\n\n\"Auto-approved: N\" headline, accept/reject counts and reject rate, a per-source table (config / hook / user_*), and the top rejection hotspots.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/screenshots/wlog-tools.png\" alt=\"Tools screen: auto-approved headline, accept/reject by source, rejection hotspots\" width=\"860\"\u003e\n\u003c/p\u003e\n\n### Timeline — per-session lifecycle\n\nA vertical lifecycle for one session: prompt → api_request → tool calls → decisions → results, with tool nodes indented under the request that triggered them. Click a node to expand it inline.\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"docs/screenshots/wlog-timeline.png\" alt=\"Timeline: prompt, api_requests, tool decisions and results in order\" width=\"860\"\u003e\n\u003c/p\u003e\n\n\u003e Screenshots use synthetic demo data (`tools/replay`), not real usage.\n\n---\n\n## Desktop app\n\n`wlog` also ships as a **native desktop app** (built with [Tauri](https://tauri.app)). It bundles the `wlog` binary as a sidecar: launching the app starts the local server and opens the UI in one window, and closing the window shuts the server down cleanly — no orphaned processes, no terminal needed.\n\n```bash\ncd desktop\nnpm install\nnpm run build      # produces an installer under src-tauri/target/release/bundle/\n```\n\nThe installer (`.msi` / `-setup.exe` on Windows) is a normal per-user install. Requirements: a Rust toolchain, and on Windows the WebView2 runtime (preinstalled on Windows 11). The desktop app reads the same local store as the CLI, so your past sessions show up the moment it opens.\n\n---\n\n## Install\n\n### go install\n\n```bash\ngo install github.com/openwong2kim/wlog/cmd/wlog@latest\n```\n\n### GitHub Releases\n\nDownload a prebuilt static binary for your OS/arch from the\n[Releases page](https://github.com/openwong2kim/wlog/releases), unpack it, and put\n`wlog` on your `PATH`. Builds are published for linux, macOS, and Windows on\namd64 and arm64, with checksums and an SBOM.\n\n\u003e A Homebrew tap and Scoop bucket are planned.\n\n---\n\n## Start in 5 minutes\n\n### Stage 1 — zero-config: see your past sessions now\n\n```bash\nwlog\n```\n\nwlog scans `~/.claude/projects` for the transcript files Claude Code already wrote, the web UI starts, and the browser opens. If you've used Claude Code before, **your past sessions are already there** — no env vars, no restart. The startup line tells you the ports:\n\n```\nwlog v0.1.0  otlp=grpc://127.0.0.1:4317,http://127.0.0.1:4318  ui=http://127.0.0.1:7890\n```\n\nCost, tokens, models, cache-hit ratios, and bash/MCP command history are populated from the transcripts on disk.\n\n\u003e Prefer the terminal? `wlog last` prints a one-shot summary of your most recent session without opening anything — see [`wlog last`](#wlog-last).\n\n### Stage 2 — optional: add live updates + tool decisions\n\nThe transcript files don't include Claude Code's permission decisions (accept/reject and the source), and they're only written as a session progresses. Turn on OTLP to get **live updates** and the **tool-decision** data that powers the Tools screen.\n\nThe fastest way is one command — it merges the OTel env block, the status line, and a SessionEnd summary hook into `~/.claude/settings.json` (preserving your other settings, with a `.bak` backup):\n\n```bash\nwlog setup              # then restart Claude Code\nwlog setup --status     # verify it took effect (read-only)\nwlog setup --uninstall  # undo anytime\n```\n\nPrefer to do it by hand? `wlog --print-claude-setup` prints the exact JSON `env` block (and a bash form) to paste into `~/.claude/settings.json`.\n\nThen run Claude Code, make a request, and switch back to the **Now** tab — the first `api_request` reaches wlog within a few seconds.\n\n\u003e Want prompt text and detailed tool arguments too? Those are opt-in on the Claude Code side (`OTEL_LOG_USER_PROMPTS=1`). See [Privacy](#privacy) before enabling — they get stored in your local DB.\n\n\u003e **Tip:** keep a live status line in Claude Code itself with [`wlog statusline`](#wlog-statusline), so you see your running cost without switching to the browser.\n\n---\n\n## CLI\n\n```\nwlog                              run the receiver + UI, auto-open the browser\nwlog last [--session id] [--n N]  print a one-shot summary of the latest session\nwlog statusline [--install]       emit one status line for Claude Code's status bar\nwlog setup [--status|--uninstall] auto-configure Claude Code (OTel + statusline + hook)\nwlog tail [--session id]          stream live events to the terminal (no UI)\nwlog export --session \u003cid\u003e [...]  export a session to a file or stdout\nwlog version                      print version information\nwlog --print-claude-setup         print the Claude Code OTel env snippet and exit\n```\n\n### Flags\n\n| Flag | Default | Description |\n|---|---|---|\n| `--otlp-grpc-port` | `4317` | OTLP/gRPC listener port. |\n| `--otlp-http-port` | `4318` | OTLP/HTTP listener port. |\n| `--ui-port` | `0` (auto) | Web UI port; `0` picks a free port automatically. |\n| `--listen` | `127.0.0.1` | Bind address. |\n| `--unsafe` | `false` | Allow a non-loopback bind without auth. |\n| `--auth-token` | _(none)_ | Auth token required for non-loopback access. |\n| `--db` | platform data dir | SQLite database path. |\n| `--memory` | `false` | Use an in-memory database (nothing persisted). |\n| `--retention` | `90d` | Data retention window (e.g. `90d`, `72h`). |\n| `--max-sessions` | `1000` | Max retained sessions (oldest pruned first). |\n| `--max-recv-bytes` | `16MB` | Max size of a single OTLP message. |\n| `--no-open` | `false` | Do not auto-open the browser. |\n| `--no-store-prompts` | `false` | Do not persist prompts / tool arguments. |\n| `--no-jsonl` | `false` | Disable zero-config Claude Code transcript (JSONL) ingestion. |\n| `--claude-dir` | `~/.claude/projects` | Directory of Claude Code transcripts to scan. |\n| `--print-claude-setup` | `false` | Print the Claude Code OTel env snippet and exit. |\n\nEvery flag has a `WLOG_*` environment-variable equivalent (e.g. `WLOG_OTLP_GRPC_PORT`, `WLOG_UI_PORT`, `WLOG_LISTEN`, `WLOG_DB`, `WLOG_RETENTION`, `WLOG_NO_STORE_PROMPTS`, `WLOG_NO_JSONL`, `WLOG_CLAUDE_DIR`). Explicit flags always win over the environment.\n\n### `wlog last`\n\nPrint a one-shot summary of your latest session — cost, tokens, tool accept/reject counts, and recent command history — and exit. It is read-only: it does **not** start the receiver or the web UI, so it works whether or not a `wlog` server is running.\n\n```bash\nwlog last                      # summarize the latest session\nwlog last --session 0a1b2c3d   # summarize a specific session\nwlog last --n 20               # list the last 20 commands (default: 10)\n```\n\n```\nsession 0a1b2c3d  18:42–19:05 (23m)  cost=$0.42  in=128.4K out=12.1K  cache_hit=71%\ntools  accept 34  reject 2  auto-approved 28\n  ✕ reject  Bash (source=user_reject)\nbash (last 10)\n  18:43:07  ok   npm ci\n  19:02:54  fail go build ./cmd/wlog\n```\n\n### `wlog statusline`\n\nShow your current session's cost, tokens, cache-hit ratio, rejections, and the running tool directly in Claude Code's status bar:\n\n```\n$0.42 · in 128.4K/out 12.1K · cache 71% · ⊘2 reject · Bash\n```\n\nInstall it (idempotent; backs up `settings.json` first):\n\n```bash\nwlog statusline --install\n```\n\nIt reads the store directly and needs **no running `wlog` server** — it works from the zero-config transcript data alone, and never fails the status bar (blank line + exit 0 on any error).\n\n---\n\n## Privacy\n\nwlog is **local-only**. Data never leaves your machine, and it makes no outbound connections.\n\n- **Prompts and tool arguments are not collected by default.** They reach wlog only if you opt in on the Claude Code side (`OTEL_LOG_USER_PROMPTS=1`). wlog stores only what it receives.\n- **`--no-store-prompts`** drops prompt/argument payloads even when Claude Code sends them.\n- The UI shows a **three-stage privacy banner**: detailed-logging-off (default), a one-time amber warning when logging is on, and a persistent red banner on a non-loopback bind.\n- The database file is created with `0600` permissions, and non-loopback binds require `--unsafe` or `--auth-token` (with a Host-header check to block DNS rebinding).\n\n---\n\n## How it works\n\n```\nClaude Code ──OTLP gRPC :4317 / HTTP :4318──▶  receiver  ─┐\n                                                          │\n~/.claude/projects/**/*.jsonl ──scan + watch──▶ jsonl.Parse ─┤   wlog (single process)\n                                                          │\n                       otel.Parse ──▶ delta normalization ─┤\n                                                          ▼\n        single-writer (dedup) ──▶ SQLite (WAL) + series_state ──▶ live bus (SSE)\n                                                          │\n              embedded web UI (go:embed) ──▶ http://127.0.0.1:\u003cauto\u003e\n```\n\n**Two ingest paths, one store.** On startup wlog scans `~/.claude/projects` for Claude Code's transcript JSONL and watches for new lines; in parallel OTLP arrives over gRPC/HTTP (all three signals) through a bounded, backpressuring queue, and counters are normalized from cumulative/delta temporality into per-interval deltas. When the same `api_request` arrives from both sources, a shared de-duplication key collapses it to one row, so cost is never double-counted. A single writer goroutine serializes all SQLite writes; reads use a separate WAL pool. The UI is compiled into the binary via `go:embed`.\n\n**Where your data lives:**\n\n| Platform | Default database path |\n|---|---|\n| Windows | `%LOCALAPPDATA%\\wlog\\wlog.db` |\n| macOS | `~/Library/Application Support/wlog/wlog.db` |\n| Linux / other | `${XDG_DATA_HOME:-~/.local/share}/wlog/wlog.db` |\n\nOverride with `--db PATH`, or use `--memory` to keep everything in RAM.\n\n---\n\n## Build from source\n\nRequirements: Go 1.26+, Node 22+, and always `CGO_ENABLED=0` (the SQLite driver is pure Go).\n\n```bash\n# 1. Build the embedded UI\ncd ui \u0026\u0026 npm ci \u0026\u0026 npm run build      # outputs into ../internal/web/dist\n\n# 2. Build the static binary (from the repo root)\ncd .. \u0026\u0026 CGO_ENABLED=0 go build \\\n  -ldflags \"-s -w -X github.com/openwong2kim/wlog/internal/version.Version=0.1.0\" \\\n  -o wlog ./cmd/wlog\n```\n\nThe result is a single static binary (~26 MB) with the UI embedded — copy it anywhere and run it. A `Taskfile.yml` is included (`task build`, `task run`, `task test`, `task cross`).\n\nTo regenerate the demo screenshots: run a demo instance (`wlog --db demo.db --no-jsonl --ui-port 8898 --otlp-grpc-port 14317 --otlp-http-port 14318`), inject synthetic data (`replay --endpoint grpc://127.0.0.1:14317 --spread-days 70`), then capture each route.\n\n---\n\n## License\n\n[MIT](LICENSE) © 2026 openwong2kim\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopenwong2kim%2Fwlog","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fopenwong2kim%2Fwlog","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopenwong2kim%2Fwlog/lists"}