Skip to main content

Local-first CLI that collects Claude Code / Codex / Cursor agent session logs into SQLite and exposes pandas query helpers + starter notebooks.

Project description

thread-keeper

A local-first Python CLI that automatically collects the logs of how you work with AI coding agents (Claude Code, Codex, Cursor), normalizes them into one queryable SQLite store, and opens them in Jupyter — so you own your agent-usage data and can find where your instructions or setup are wasteful or inconsistent.

No web UI, no daemon, no cloud, no network calls. Just: collect → normalize → analyze.

The product name is thread-keeper. The PyPI / install distribution name is thk (uv tool install thk, uvx thk, pip install thk).

See docs/PRD.md for the full product spec and docs/contracts/ for the JSON Schemas of every shape that crosses a boundary.

Install

# From PyPI:
uv tool install thk
# or: pip install thk

# From a clone (editable):
git clone <this-repo>
cd thread-keeper
uv sync
# Put `thread-keeper` (and `thk`) on PATH for agent hooks (required before
# install-hooks if you want a bare `thread-keeper` name; install-hooks also
# writes an absolute path so SessionEnd works after `uv sync` alone).
uv tool install -e .

Requires Python ≥3.11 (pinned to 3.14 in .python-version) and uv.

Quickstart

# Backfill your ENTIRE existing history from Claude Code / Codex / Cursor —
# no "only since install" cutoff.
uv run thread-keeper collect --sweep

# See what's in the store.
uv run thread-keeper status

# Optional: write SessionEnd / notify hook configs (backs up existing files
# first). Commands use an absolute path to this install so they work even
# when `thread-keeper` is not on PATH. Revert with `uninstall-hooks`.
uv run thread-keeper install-hooks
# uv run thread-keeper uninstall-hooks
import threadkeeper as tk

sessions = tk.sessions()             # DataFrame + has_usage / cost_usd / token cols
sessions.loc[sessions["has_usage"]].sort_values("cost_usd", ascending=False)
tk.usage_long().groupby("model")["cost_usd"].sum()
sid = sessions.iloc[0]["id"]
messages = tk.messages(sid)          # DataFrame, one row per normalized event
tk.cost_of(sessions.iloc[0]["usage"])

API note (cost_of / cost_breakdown_of): these return None when usage is missing/empty or every model in the blob is unpriced (unknown ≠ $0; previously empty usage returned 0.0). Prefer sessions["cost_usd"] / filter has_usage, or handle None before arithmetic.

Starter analysis notebooks live in notebooks/ — see that folder's README for setup with Jupyter.

CLI

thread-keeper collect --source <claude-code|codex|cursor> --session-id <id> [--transcript <path>]
thread-keeper collect --source <claude-code|cursor> --from-stdin          # Claude Code / Cursor SessionEnd (JSON on stdin)
thread-keeper collect --source codex --from-notify-argv                   # Codex notify (idle-heuristic)
thread-keeper collect --sweep [--source <...>] [--claude-root <dir>] [--codex-root <dir>] [--cursor-root <dir>] [--host <label>]
thread-keeper install-hooks [--tool <claude-code|cursor|codex> ...]
thread-keeper uninstall-hooks [--tool <claude-code|cursor|codex> ...]
thread-keeper status
  • The fast path is what hooks invoke: either --session-id/--transcript, or --from-stdin (Claude Code / Cursor SessionEnd JSON payload), or --from-notify-argv (Codex notify, no session id — reparses the newest rollout). Always exits 0 (must never block the agent tool).
  • --sweep is the backstop + cold-start backfill: walks all three sources' log dirs and ingests anything changed since the last run, including your entire pre-existing history on a fresh store.
  • Source roots (--claude-root/--codex-root/--cursor-root, or the matching THREAD_KEEPER_CLAUDE_ROOT/_CODEX_ROOT/_CURSOR_ROOT env vars) plus --host <label> let you consolidate logs copied in from another laptop into the same store, correctly attributed by origin machine.

Configuration

Setting Flag Env var Default
Data dir THREAD_KEEPER_DATA_DIR ~/.thread-keeper/
Claude Code root --claude-root THREAD_KEEPER_CLAUDE_ROOT ~/.claude/projects
Codex root --codex-root THREAD_KEEPER_CODEX_ROOT ~/.codex/sessions
Cursor root --cursor-root THREAD_KEEPER_CURSOR_ROOT platform Cursor user dir

The SQLite store lives at <data-dir>/thread-keeper.db; install-hooks backs up any existing hook config it overwrites to <data-dir>/backups/.

Design invariants

  • Offline. No network calls anywhere, ever.
  • Read-only on foreign logs. Cursor's state.vscdb is always snapshot-copied (with its -wal/-shm files) before being read; the original is never opened read-write.
  • Whole-session invariant. A changed file is always re-parsed whole and replaces its prior session — never a partial/appended read.
  • Idempotent. Re-running any collect is a no-op net of row identity; a session rename and its origin host label both survive re-import.

See docs/PRD.md §6 for the full set of design principles.

Development

uv sync
uv run pytest
uv run thread-keeper --help

See CLAUDE.md for how this repo is meant to be extended (spec-driven, one Req at a time, contracts used verbatim).

Credits / prior art

thread-keeper is a from-scratch Python redraw of the offline log-extraction core of Chronicle by @chizhangucb, a JS/Electron "time machine" for AI coding-agent sessions. Chronicle already solved parsing Claude Code, Codex, and Cursor's session logs offline; thread-keeper ports that extraction logic (schema, parsers, pricing tables) to Python and drops everything else (the desktop UI, live streaming, replay, sharing) to leave a lightweight, notebook-first collector. Thanks to @chizhangucb and the Chronicle project for the open-source reference implementation this work stands on.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

thk-0.0.2.tar.gz (24.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

thk-0.0.2-py3-none-any.whl (31.4 kB view details)

Uploaded Python 3

File details

Details for the file thk-0.0.2.tar.gz.

File metadata

  • Download URL: thk-0.0.2.tar.gz
  • Upload date:
  • Size: 24.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.10 {"installer":{"name":"uv","version":"0.10.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for thk-0.0.2.tar.gz
Algorithm Hash digest
SHA256 af4aec321b7cd3382806773d6b98453b3cf654104ba1b6140254addf36c652df
MD5 83552f2330748b41e0c358fa739c1d84
BLAKE2b-256 89bc320dd31c282f5296a125f7c5665e67d895c9ddeefa38fa65fab0fa759af4

See more details on using hashes here.

File details

Details for the file thk-0.0.2-py3-none-any.whl.

File metadata

  • Download URL: thk-0.0.2-py3-none-any.whl
  • Upload date:
  • Size: 31.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.10 {"installer":{"name":"uv","version":"0.10.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for thk-0.0.2-py3-none-any.whl
Algorithm Hash digest
SHA256 5bfa2682737403995a737fe5e9e6ca4de62b6cb55cb9ee3a190eac23900fdb18
MD5 f5ce438ebcb926508afd5b8a8125377a
BLAKE2b-256 5f5d5212c1cec9176b63df46619fa0d2b31fabe9067cb90d495a28873ce01e37

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page