Skip to main content

A local, read-only CLI that analyzes your coding agent's session logs and turns that analysis into skills, slash commands, and prompts.

Project description

warden

crates.io PyPI MIT licence MSRV 1.87 CI

A local, read-only CLI that analyzes how you use your coding agent harness — Claude Code today, Codex and Cursor next — and turns that analysis into something the harness can act on: skills, slash commands, prompts.

Two halves, in order. First warden reads the harness's own session logs and normalizes them into a store you own, so questions like where do the tokens go, which files are expensive, which tools fail have answers instead of guesses. Then it reads that store back the other way: the work you repeat by hand is exactly the work that should have been a skill, and warden is the thing that can see the repetition because it is the only thing holding the whole history.

$ warden report projects --since 7d
PROJECT                     SESSIONS      IN     OUT  CACHE R  EST. COST
adept-impl                         7  105.5k  505.8k  176.92M   $50.83 ~
adept                             32   24.6k  680.6k  126.36M   $25.71 ~
cli-tracing-spec                   1   10.8k  108.9k   29.53M          –
make-workspace-publishable         4    8.0k  196.3k   27.86M    $3.51 ~
adept-python-packaging             1    8.6k  119.2k   22.50M    $2.65 ~
adept-create-spec                  2     260   67.0k   13.24M          –
backlog-issue-links                4    2.4k   92.4k   11.87M    $1.96 ~
warden                             1     295   57.3k   10.81M          –
backlog-upskill                    1     120   31.0k    4.06M          –
assets                             1     106   23.0k    3.60M    $0.68 ~
agents-md-rename                   1      56   13.4k    1.45M          –
logo                               1      15    2.0k   355.6k          –
                                                             ~ estimated

warden is not a harness. It never calls a model provider, never needs an API key, and contains no network code — not as a setting, but as an absence. It reads logs, normalizes them into a JSONL store you own, and answers questions about them. Everything it produces for a harness — a SKILL.md draft, a report, a JSON envelope — is text on stdout that you or your agent decide what to do with.

The in that table is deliberate: it means this source cannot tell me, not zero. warden doctor explains every one of them.

Install

The crate publishes as warden-cli (warden was already taken on crates.io) but the installed command is warden either way:

cargo install warden-cli   # needs a Rust toolchain
# or
uv tool install warden-cli # no Rust toolchain required
# or
uvx --from warden-cli warden # run without installing (bare `uvx warden-cli`
                              # won't work: uvx requires the command name to
                              # match the package name, and it's `warden`)

warden doctor              # what can warden see?
warden ingest              # read the logs, append new events
warden report projects --since 7d

From source: cargo install --path . (or cargo build --release && install target/release/warden ~/.local/bin/).

Nothing else is required. warden creates ~/.warden/ (mode 0700) on first write and reads ~/.claude/projects strictly read-only.

Commands

command what it does
warden ingest Scan sources from the last cursor, normalize, append. Idempotent — event ids are content-derived, so re-running (or interrupting and re-running) changes nothing. Runs implicitly before any report unless --no-ingest.
warden report <name> One of the named reports (below).
warden query --group-by <dims> Ad-hoc rollup over project, model, agent, day, …
warden suggest Prompts you have sent more than once, byte-for-byte.
warden suggest --draft <id> Print a SKILL.md draft for one of them, to stdout only; conflicts with --json.
warden watch --oneline One status-bar line of burn rate, then exit.
warden doctor Why is this number empty?
warden purge --prompts Delete stored prompt text.

Reports

report answers
summary totals for the period, by day
projects tokens and est. cost per project
models usage split by model
sessions longest and most expensive sessions
files attributed tokens per file
tools tool call frequency and failure rate
compare this period vs. the previous one

report files is attribution, not measurement: no log records "this file cost N tokens", so warden splits a turn's cost evenly across the files its tool calls touched. The output is labelled attributed and --json carries "method": "even-split" so you can tell.

Global flags

--json · --since <7d|24h|2w|2026-01-01> · --project <name> · --data-dir <path> · --no-ingest · --no-sidechain

Sidechain (subagent) events are included by default. They are real spend, but they are counted in no per-session figure your agent shows you, so warden's totals read higher than the number in your terminal. --no-sidechain excludes them; the reports say which side of that line they are on.

warden suggest

One detector, deliberately: exact-duplicate prompts, matched on text_hash. No embeddings, no fuzzy matching, no false positives.

$ warden suggest --since 14d
1015 repeated prompts found, showing the 20 most repeated — --json has them all  # stderr

ID        COUNT  LAST     PROJECTS                                                SUGGESTION                                     PROMPT
17eaf8bd     86  59m ago  adept, adept-impl, adept-python-packaging, +7 more      → draft skill: simplify-4-cleanup-agents       "`/simplify → 4 cleanup agents in parallel → apply the fixes` Yo…"
319df741     10  9d ago   adept, fradragsjagt                                     → save as a slash command                      "Now update the @docs/BACKLOG.md"
abd268ed      6  1d ago   adept, backlog-issue-links, make-workspace-publishable  → draft skill: base-directory-this-skill       "Base directory for this skill: /home/mathias/.claude/skills/pla…"
246674b1      6  1d ago   adept, adept-impl, make-workspace-publishable           → draft skill: base-directory-this-skill       "Base directory for this skill: /home/mathias/.claude/skills/cod…"

The headline (1015 repeated prompts found, …) goes to stderr, unconditionally — including in --json mode — so stdout stays the table alone, or a single parseable JSON document; pipe stdout to a file and you get the table or the envelope, never the headline.

warden suggest --draft 17eaf8bd prints a SKILL.md to stdout. It writes nothing. Redirect it yourself if you want the file — staged writing with a diff and a confirmation is a v0.2 feature, and until it exists warden will not put a file in your repo. --draft conflicts with --json — a parse error, not a silent ignore — because the draft itself is the payload and there is no envelope to put it in.

Ids come from the prompt's hash, so they are stable across runs and machines. Groups that are only the client's own transcript furniture — slash-command expansions, compaction notices, interrupt markers — are set aside and counted in the notes rather than suggested at you.

Feeding the analysis back to the harness

suggest is the first of these, not the only one intended. The store already holds what the rest need — tool call sequences, failure rates, which files a project keeps returning to — and each future detector is the same shape: read the store, find a pattern that a skill or a command would have collapsed, print a draft.

Because every command takes --json, a harness can drive this itself. An agent running warden suggest --json --since 30d, picking a group, and running warden suggest --draft <id> gets a SKILL.md on stdout with no network hop, no provider call, and nothing written to disk until it decides to write it. The division is deliberate: warden supplies evidence about your usage, the harness supplies the judgement about what to build from it.

What warden will not do is decide for you. It has no notion of a "good" skill and does not rank one repetition as more worth automating than another beyond counting it, because that judgement depends on context only you and your agent have.

warden watch

$ warden watch --oneline
warden · 34.93M tok/hr · session 33m25s · 11.31M this session

Built for a tmux status line. Only --oneline ships in 0.1.0; the streaming form says so rather than pretending.

The store is the API

Everything warden knows lives in append-only JSONL under ~/.warden/. No database, no driver, no migration story to get wrong.

~/.warden/
  events/2026-08.jsonl     # normalized events, partitioned by UTC month
  prompts/2026-08.jsonl    # prompt text, separated so it can be disabled
  state/ingest.jsonl       # per-source cursors, append-only, last-wins
  config.toml

One event per line:

{
  "v": 1,
  "id": "9f2c…",
  "ts": 1754300000000,
  "agent": "claude-code",
  "provider": "anthropic",
  "model": "claude-sonnet-4-6",
  "project": "acme-api",
  "session_id": "…",
  "turn_id": "…",
  "role": "assistant",
  "input_tok": 412,
  "output_tok": 1180,
  "cache_read_tok": 84210,
  "cache_write_tok": 0,
  "stop_reason": "end_turn",
  "cost_est": 0.0412,
  "is_sidechain": false,
  "tool_calls": [{ "tool_name": "Read", "tool_target": "src/db/migrate.rs" }]
}

prompts/ holds {"event_id", "text", "text_hash"}; text_hash is always written, so duplicate detection works even with text storage off.

This shape is a committed surface:

  • Fields may be added freely. Readers must tolerate unknown fields.
  • Fields are never renamed, retyped, or removed without bumping v.
  • A field an adapter cannot populate is absent, never 0. duration_ms is missing from Claude Code's logs, so warden omits it rather than lying.
  • Cache tokens stay separate from input tokens. In an agentic loop cache reads dominate volume at a fraction of the price; collapsing them makes every cost figure wrong.
  • cost_est is an estimate, always labelled ~, always computed from your config rather than prices baked into the binary.
  • Appends are line-atomic. A reader that catches a torn final line skips it.

Every command also takes --json, which wraps rows in a versioned envelope:

{
  "warden_version": "0.1.0",
  "record_version": 1,
  "report": "projects",
  "period": { "from": "…", "to": "…" },
  "rows": [  ],
  "notes": ["cost figures are estimates"]
}

Pin on record_version. Read notes — that is where warden explains numbers you might not be able to reconcile.

…which means you don't need warden

The whole point of JSONL is that the data outlives the tool. warden has no report for "when in the day do I burn money", but the store answers it anyway:

jq -rs '
  map(select(.cost_est > 0))
  | group_by(.ts / 1000 | strftime("%H"))
  | map({hour: (.[0].ts / 1000 | strftime("%H")), cost: (map(.cost_est) | add)})
  | sort_by(.cost) | reverse | .[:5][]
  | "\(.hour):00  $\(.cost * 100 | round / 100)"
' ~/.warden/events/*.jsonl
11:00  $43.62
09:00  $41.99
15:00  $30.48
08:00  $28.83
20:00  $28.16

Timestamps are epoch milliseconds; strftime wants seconds, hence the / 1000. Everything in the store is UTC.

Config

~/.warden/config.toml, entirely optional:

[general]
data_dir = "~/.warden"
index_prompt_text = true

[sources.claude-code]
enabled = true
path = "~/.claude/projects"

# Per million tokens. User-editable because prices drift and models get
# deprecated — warden ships no prices of its own, and a model with no
# configured rate shows `–` rather than a fake $0.00.
[pricing.anthropic]
"claude-sonnet-4-6" = { input = 3.0, output = 15.0, cache_read = 0.3 }
"claude-opus-4-1"   = { input = 15.0, output = 75.0, cache_read = 1.5, cache_write = 18.75 }

Prices are applied when a report is built, not baked in at ingest, so adding a rate re-prices events that are already in the store — no re-ingest, and a row mixing priced and unpriced models is marked ~+ rather than reported as a total.

warden doctor names every model it saw with no configured price.

Privacy

warden reads the most sensitive data on the machine: proprietary source, business context, occasionally a credential someone pasted into a chat.

  • Local only. No telemetry, no network calls, no API key. Not a setting — there is no network code in the crate and no dependency capable of a request.

  • Read-only at the source. ~/.claude/projects is never written to.

  • Prompt text is separable. index_prompt_text = false stores only text_hash, so duplicate detection still works and no prompt text is ever written to disk. warden suggest then reports the repeats and says it cannot show you the wording, rather than showing you a blank.

  • warden purge --prompts deletes prompts/ outright. The store is otherwise append-only, so purge is one of the very few commands that removes anything: it requires --yes or an interactive confirmation, it touches nothing but prompts/, and it tells you exactly what it took.

    $ warden purge --prompts --yes --data-dir /tmp/warden-copy
    REMOVED                   FILES  PROMPT RECORDS  BYTES
    /tmp/warden-copy/prompts      2  5,638           14.3 MB
    
  • ~/.warden/ is created 0700. Plain-text JSONL on disk is more exposed than an opaque database file — it isn't encryption either way, but it is more obviously readable, so the permissions matter.

Status

0.1.0 — MVP. The analysis half is real; the feedback half is one detector deep.

One adapter (Claude Code), because normalizing a second harness only proves the event shape is right if the first one is already carrying real load. Codex and Cursor adapters, skill writing (staged, with a diff and a confirmation), an MCP server so a harness can query the store as a tool rather than by shelling out, and prompt clustering beyond exact matches are all deferred, roughly in that order.

Licence

MIT. See LICENSE.

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

warden_cli-0.1.0.tar.gz (108.5 kB view details)

Uploaded Source

Built Distributions

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

warden_cli-0.1.0-py3-none-win_amd64.whl (841.8 kB view details)

Uploaded Python 3Windows x86-64

warden_cli-0.1.0-py3-none-musllinux_1_2_x86_64.whl (975.8 kB view details)

Uploaded Python 3musllinux: musl 1.2+ x86-64

warden_cli-0.1.0-py3-none-musllinux_1_2_aarch64.whl (905.5 kB view details)

Uploaded Python 3musllinux: musl 1.2+ ARM64

warden_cli-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (934.4 kB view details)

Uploaded Python 3manylinux: glibc 2.17+ x86-64

warden_cli-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (862.8 kB view details)

Uploaded Python 3manylinux: glibc 2.17+ ARM64

warden_cli-0.1.0-py3-none-macosx_11_0_arm64.whl (853.5 kB view details)

Uploaded Python 3macOS 11.0+ ARM64

warden_cli-0.1.0-py3-none-macosx_10_12_x86_64.whl (898.2 kB view details)

Uploaded Python 3macOS 10.12+ x86-64

File details

Details for the file warden_cli-0.1.0.tar.gz.

File metadata

  • Download URL: warden_cli-0.1.0.tar.gz
  • Upload date:
  • Size: 108.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for warden_cli-0.1.0.tar.gz
Algorithm Hash digest
SHA256 c0f14ff85728dc6ca0d4285f7c50e2306fea94c9b31eb0eea6a9ef0b2ba7f40a
MD5 8b01bd96f45bae848eafd42fdfce975c
BLAKE2b-256 38e4b3f925e2b0c22044e5af12d4eb283dacd0c2d4680759a2d573fec00ab915

See more details on using hashes here.

Provenance

The following attestation bundles were made for warden_cli-0.1.0.tar.gz:

Publisher: release-pypi.yml on mathiasesn/warden

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file warden_cli-0.1.0-py3-none-win_amd64.whl.

File metadata

  • Download URL: warden_cli-0.1.0-py3-none-win_amd64.whl
  • Upload date:
  • Size: 841.8 kB
  • Tags: Python 3, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for warden_cli-0.1.0-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 1f6ff905adc6755ca543c0753bc77b458283d42b467f8bbafebf2f1d10c638cc
MD5 41f0a3d37b6b4a4309942a0da394fabd
BLAKE2b-256 e2261fb2a88ece5bcefc00e7308d7211586b0331a762cf5ad0ac04fcc428f02c

See more details on using hashes here.

Provenance

The following attestation bundles were made for warden_cli-0.1.0-py3-none-win_amd64.whl:

Publisher: release-pypi.yml on mathiasesn/warden

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file warden_cli-0.1.0-py3-none-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for warden_cli-0.1.0-py3-none-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 ff5cbad04843f8639a122117ba11099b5eefdd2f676c9c1b5ee3192ccf1f7825
MD5 2ab468132c355c72b76ad7ff88b94e99
BLAKE2b-256 85bb6098c1b34f5352c5c62d26697298e905a5a261271dcca6ea222f13adbe3d

See more details on using hashes here.

Provenance

The following attestation bundles were made for warden_cli-0.1.0-py3-none-musllinux_1_2_x86_64.whl:

Publisher: release-pypi.yml on mathiasesn/warden

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file warden_cli-0.1.0-py3-none-musllinux_1_2_aarch64.whl.

File metadata

File hashes

Hashes for warden_cli-0.1.0-py3-none-musllinux_1_2_aarch64.whl
Algorithm Hash digest
SHA256 5b1c2c5b633a248450e8ad4c0f27fa1f7310c83361846513aa12f62aedde4f7a
MD5 e57ab9fd79ccaf84f6f0def30e7106cf
BLAKE2b-256 f3821fd435a02352733de9039c1410d7c326d4783c5ac340c555fc3115f98f5a

See more details on using hashes here.

Provenance

The following attestation bundles were made for warden_cli-0.1.0-py3-none-musllinux_1_2_aarch64.whl:

Publisher: release-pypi.yml on mathiasesn/warden

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file warden_cli-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for warden_cli-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 cd90e5b41bf73dd58911b287c045b878a2c4349d96fe648b17516e15cef7bb54
MD5 3afbdf1584b952535ca80c1fecaeb1d4
BLAKE2b-256 bf212f902bbc7d1156f9b179b705023534399388f9ebd04077b011ffd655fc4f

See more details on using hashes here.

Provenance

The following attestation bundles were made for warden_cli-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: release-pypi.yml on mathiasesn/warden

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file warden_cli-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for warden_cli-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 b9ff5ae6ee724efff6b0fea44aea4411143935dff2b3642227f764f88867cbfc
MD5 caab91e85d05e78199f35889dc256118
BLAKE2b-256 b21b996f477f2c9f955ca292b40a2f58fde50e64931639b6e50d61beb5346bc4

See more details on using hashes here.

Provenance

The following attestation bundles were made for warden_cli-0.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:

Publisher: release-pypi.yml on mathiasesn/warden

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file warden_cli-0.1.0-py3-none-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for warden_cli-0.1.0-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 29fa2943d93a11930fa35982d29460ee432e0aaf81e71d820e2614dbb320311f
MD5 9b3c810c57d7e37ad2506b96dbc66b12
BLAKE2b-256 0b18263eb79ad93a6c92131e9a7ead9fa0795724d1dfe3e74995555fc03e0901

See more details on using hashes here.

Provenance

The following attestation bundles were made for warden_cli-0.1.0-py3-none-macosx_11_0_arm64.whl:

Publisher: release-pypi.yml on mathiasesn/warden

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file warden_cli-0.1.0-py3-none-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for warden_cli-0.1.0-py3-none-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 8550c7f4b39cf98116fd1ee218c24ac03573bda6f51667539fde0aedd7730a5e
MD5 7fe73612fec6c39507e6eb7159780c41
BLAKE2b-256 353f1dbb61ac1d740de8059002c926dfa2d7b7b0aa40b3ea98bac8388d8ae79b

See more details on using hashes here.

Provenance

The following attestation bundles were made for warden_cli-0.1.0-py3-none-macosx_10_12_x86_64.whl:

Publisher: release-pypi.yml on mathiasesn/warden

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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