npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@bglocation/tune-context

v2.5.0

Published

Context-efficiency configurator for Claude Code — token-saving doctrine packaged as skills, plus a tune-context configurator that scaffolds a lean CLAUDE.md, doctrine skills, subagents, settings, hooks and MCP wiring.

Readme

tune-context

Context-efficiency configurator for Claude Code — the doctrine (token-saving working methods) packaged as skills, plus a tune-context configurator that scaffolds a cost-optimal, high-quality setup (lean CLAUDE.md + skills + subagents

  • settings + hooks + MCP).

Self-contained — zero dependency on any host repo. Ships two ways: a Claude Code plugin and an npm CLI (tune-context init), both under @bglocation. Incubated inside the code-search-mcp project, now extracted to its own repository.

tune-context/
├── .claude-plugin/plugin.json   # Claude Code plugin manifest
├── skills/            # token-efficiency, caveman, cdd, phase-workflow, tune-context (+ templates/)
├── agents/            # explore-cheap, reviewer (model right-sizing)
├── hooks/             # PreCompact state-doc, SessionStart re-inject, adoption log, context reminder + hooks.json
├── bin/ + cli/        # `tune-context init` — deterministic detect → generate → verify
├── eval/              # adoption-report.mjs (the rag-usage analog), prompt-metrics.mjs, pricing.mjs, arm.mjs, analyze.mjs, verdict.mjs, report.mjs
└── scripts/           # verify-pack, smoke-init, sync-tune-context (dev/maintenance)

Quick start

Node ≥ 18, no build step. From the repo you want to configure:

# Configure this repo — deterministic, no Claude session needed
npx -p @bglocation/tune-context tune-context init

The -p <pkg> tune-context form is deliberate: the package is scoped (@bglocation/…) and its bin (tune-context) doesn't match the scope, so a bare npx @bglocation/tune-context can fail to resolve the command. -p installs the package and names the bin explicitly.

That single command detects the stack + MCP servers, writes a lean CLAUDE.md, syncs the doctrine skills/agents/hooks into ~/.claude/, merges hooks and permissions, and prints a verification report. It's idempotent — safe to re-run.

Safe to re-run means exactly this: the managed block in CLAUDE.md holds only what detection produced and is rewritten every time; everything below the closing marker is yours and is never touched, including the ## Architecture and ## Conventions that bite sections the tool seeds there once. Write your own notes below the marker, not inside the block.

After upgrading the package, re-run init. npm i -g updates the package, not the hooks an earlier run copied into ~/.claude/hooks/ — settings.json points at those copies, so a release that changes hooks goes live only after another init. To check without changing anything:

tune-context verify        # read-only: is what's deployed still what ships?

Install it globally if you'll use it across repos:

npm install -g @bglocation/tune-context
tune-context init            # in any repo

The command above also installs the /tune-context skill into ~/.claude/skills/, so you can type it in a Claude session straight away — it does the same thing plus wiki wiring and richer MCP detection. Installing the plugin instead gets you the skill without the npm install, and updates through /plugin marketplace update:

/plugin marketplace add https://gitlab.com/bglocation/tune-context.git
/plugin install tune-context@bglocation
/reload-plugins

Then run /tune-context in the repo you want to configure. (The .git suffix matters — see Install & run.)

The section below breaks down all three entry points and when to use each.

Install & run

Three entry points, same doctrine. Pick whichever fits how you work:

1. /tune-context skill (recommended). Type /tune-context in a Claude Code session, in the repo you want to configure. It detects the stack, MCP servers and existing config, then generates a lean project CLAUDE.md, wires the doctrine skills, and merges hooks + permissions — idempotently, never clobbering hand-written content. This is the full-fidelity path (it also handles wiki wiring and semantic-search detection).

Either channel gives you the skill. The plugin installs it, and so does tune-context init — the CLI copies skills/tune-context/ into ~/.claude/skills/ along with the doctrine skills. So a plain npm i -g gives you both entry points, and so does the plugin (it puts bin/tune-context on PATH while enabled). Claude never reaches for it on its own: the skill is marked disable-model-invocation, so it runs only when you type it.

2. CLI tune-context init. The deterministic subset of the skill — same managed-block rules, same never-clobber guarantee, no model in the loop. Good for CI or scripted setup:

npx -p @bglocation/tune-context tune-context init           # runs in the current repo
npx -p @bglocation/tune-context tune-context init --cwd .    # explicit target dir
# from a checkout (dev):
node bin/tune-context.mjs init --cwd /path/to/your/repo

It prints what it detected, what it synced/merged, and a verification report; a second run is a no-op.

Two flags are worth knowing:

  • --cwd <dir> — configure another repo instead of the current directory.
  • --force-claude-md — destructive, opt-in, and normally unnecessary. If a CLAUDE.md still has hand-written content inside the managed block (the layout used before 2.2.0), init refuses to regenerate it and says so — "hand-written content sits inside the managed block — regenerating would erase it". The fix is to move that prose below the closing marker, where the tool never reaches. This flag regenerates anyway and discards it; use it only when there is nothing in there worth keeping.

/tune-context vs tune-context init

They are two different kinds of thing. The skill is a procedure Claude follows, so it can look at your repo and decide; the CLI is code that runs, so it does exactly what it was written to do, every time. Same doctrine, same managed-block rules, same never-clobber guarantee.

| | /tune-context (skill) | tune-context init (CLI) | |---|---|---| | Where you run it | Claude Code chat | terminal, incl. CI | | Needs a Claude session | yes | no — plain Node ≥ 18 | | Costs tokens | yes | no | | Two runs, same repo | judgment may vary | byte-identical | | Project CLAUDE.md, doctrine skills, hooks, permissions | ✅ | ✅ | | Merge .mcp.json — add a semantic-search server if none was detected | ✅ | ❌ reads it, never writes it | | Wiki lever — RAG segment vs its own MCP vs a plain pointer | ✅ | ❌ not detected, not wired |

Both write the same block into the same files, so mixing them is safe: script the CLI where you want repeatability, and run the skill when you want the two judgment steps it alone can do.

3. Plugin install. Load the plugin so the skills, agents and hooks are available in every session. The best UX is the marketplace — install once, get updates with /plugin marketplace update:

/plugin marketplace add https://gitlab.com/bglocation/tune-context.git
/plugin install tune-context@bglocation

The .git suffix is required: without it Claude Code treats the URL as a direct link to a hosted marketplace.json instead of cloning the repo, and the add fails with Invalid marketplace schema. Same commands work non-interactively as claude plugin marketplace add … / claude plugin install … — useful where the /plugin panel isn't available (e.g. the VS Code extension).

Or point Claude at a directory directly (no marketplace):

claude --plugin-dir "$(npm root -g)/@bglocation/tune-context"   # global npm install
claude --plugin-dir /path/to/tune-context                       # or a local checkout

The plugin manifest (.claude-plugin/plugin.json) and hook wiring (hooks/hooks.json, via ${CLAUDE_PLUGIN_ROOT}) register the four hook events automatically; bin/tune-context is added to PATH while the plugin is enabled. The marketplace catalog is .claude-plugin/marketplace.json.

CLAUDE_CONFIG_DIR

By default the CLI reads and writes Claude config under ~/.claude (and ~/.claude.json). If you set Claude Code's CLAUDE_CONFIG_DIR, tune-context init honors it consistently — everything user-level moves under the override:

| What | Default | With CLAUDE_CONFIG_DIR=/x | |---|---|---| | doctrine skills / agents / hook scripts | ~/.claude/… | /x/… | | settings.json (hooks + gating) | ~/.claude/settings.json | /x/settings.json | | MCP-server detection (.claude.json) | ~/.claude.json | /x/.claude.json | | registered hook command paths | $HOME/.claude/hooks/… | /x/hooks/… | | hook state (PreCompact snapshot, adoption log) | ~/.claude/tune-context/… | /x/tune-context/… |

Without the override the generated settings.json keeps the portable $HOME/.claude/hooks/… form (expanded when the hook fires, so it travels between machines). Under the override it's baked to the literal /x/hooks/… — where the scripts actually land. Useful for shared accounts, containers, and test isolation.

Keeping context lean while you work

Setup is the cheap half. The recurring half is shedding context you no longer need — and that stays a manual, deliberate act, because only you know when a piece of work is actually finished.

Every turn re-sends the whole conversation as input. A session that has been running for hours pays for its early exploration on every later request, long after that detail stopped being useful. So:

| When | Do | Why | |---|---|---| | A work thread closes (feature done, bug fixed) | /compact | Keeps the conclusions, drops the raw tool output that led to them | | Switching to unrelated work | /clear | Nothing from the old task is worth carrying — /compact would still keep a summary of it | | Long session, no natural break yet | /compact anyway | Waiting for auto-compact means paying full price right up to the limit |

tune-context softens the cost of compacting rather than replacing it: its PreCompact hook snapshots your branch, dirty files and recent commits, and the SessionStart(compact) hook injects that back afterwards — so the mechanical state survives even though the transcript doesn't. Judgment calls and open threads do not survive; write those down before compacting if they matter.

The reminder is automatic

Claude cannot see its own context usage — nothing in a turn tells the model how many tokens it is re-sending, and Claude Code has no timer or context-threshold hook event. So context-reminder.mjs (registered under UserPromptSubmit) supplies the missing measurement: it reads the tail of the session transcript, sums the tokens actually re-sent, and once that passes 120,000 it injects a short note suggesting /compact.

  • The note goes to Claude, not to your screen. Hooks can only reach you through the model — a systemMessage on this event was measured and does not render — so what you actually see is Claude bringing it up in its reply.
  • The model picks the moment — at first. The hook can measure size, but only Claude can see whether you're mid-refactor, so the first note tells it to stay quiet mid-task and raise it at the next natural break. That deference runs out: see the ladder below. A session that never reaches a "natural break" is exactly the one paying the most for the wait.
  • Three notes per crossing at most — 120,000 / 180,000 / 240,000. A note that lands mid-task can be correctly absorbed and then forgotten. Measured on one session: it fired at 166k, Claude stayed silent, and nothing spoke again until 346k. Each rung halves that blind spot, and each one is louder than the last: at 120k Claude is told to wait for a natural break, at 180k to raise it at the next pause anyway, at 240k to say it this turn whatever it is doing. There is deliberately no fourth — the reminder costs tokens on the prompt it appears in.
  • Raise it for a large context window: export TUNE_CONTEXT_REMIND_TOKENS=400000. 120k is ~60% of a standard 200k window; the transcript doesn't record the window size, so this can't be auto-scaled. The whole ladder scales with the override — the rungs are always 1×, 1.5× and 2× the threshold (400k/600k/800k here).
  • Turn it off: delete the UserPromptSubmit entry from settings.json.

Depth lives in the phase-workflow and token-efficiency skills (both installed by tune-context init), not here.

Measuring adoption (does the doctrine actually get used?)

hooks/adoption-log.mjs logs one JSONL line per event to ~/.claude/tune-context/adoption.jsonl by default (under CLAUDE_CONFIG_DIR, the override's tune-context/adoption.jsonl — see above), and eval/adoption-report.mjs turns that into a report — code search (semantic RAG vs the Grep tool, with shell-plumbing greps kept out of the RAG share), doctrine skills, subagents, and re-read churn — closing with an earned verdict: withheld below a PO-frozen floor of distinct sessions, and never a false ✗ for a lever the log can't see (method-level doctrine leaves no tool call at all). It's registered under two Claude Code hook events — PostToolUse (every tool call) and SubagentStart (subagent spawns, which aren't regular tools).

"RAG installed but ignored" vs "no RAG here" is the one gap the log alone cannot close, and it happens to be the finding the report most wants — so it asks the config, per project, and only as far as the config can honestly answer. A ~ RAG likely available, not reached for line appears when that project's .mcp.json declares a semantic-search server, no settings file rejects it, and the file is older than every session logged there. Everything else stays · unknown: config written during or after those sessions says nothing about them, and user-scope servers (~/.claude.json) can't be dated at all, since Claude Code rewrites that file continuously. It is never a ✗ — config names servers, not their tools, so the line claims the lever was probably within reach, not that it existed. It also answers to the same frozen session floor as the rest of the verdict, applied per project: "available and never reached for" is a claim about absence, and one logged session cannot establish absence.

Using both the plugin and tune-context init (CLI) on the same repo registers the hook twice — once per channel — so every event logs twice. This is harmless (same script, same log, just doubled counts) and the report detects it: past a measured share of near-duplicate pairs it withholds the re-read-churn verdict instead of printing a number the doubling would have faked.

Turn it on

  1. Install the hook. Either run /tune-context in your repo (it copies the script to ~/.claude/hooks/ and merges the hook entries into settings.json for you), or do it by hand:

    • copy hooks/adoption-log.mjs and hooks/config-dir.mjs (it imports the latter for the log path) → ~/.claude/hooks/ (needs Node ≥ 18 on PATH);
    • merge this into your settings.json — ~/.claude/settings.json for every project, or a repo's .claude/settings.json for just that one (if the file doesn't exist yet, this can be the whole thing):
      {
        "hooks": {
          "PostToolUse": [
            {
              "matcher": "*",
              "hooks": [
                { "type": "command", "command": "node \"$HOME/.claude/hooks/adoption-log.mjs\"" }
              ]
            }
          ],
          "SubagentStart": [
            {
              "matcher": "*",
              "hooks": [
                { "type": "command", "command": "node \"$HOME/.claude/hooks/adoption-log.mjs\"" }
              ]
            }
          ]
        }
      }
      Using CLAUDE_CONFIG_DIR? Replace $HOME/.claude/hooks with the literal override path instead — $HOME is not expanded there (see CLAUDE_CONFIG_DIR above).
  2. No reload needed. Claude Code watches the settings files, so the new hooks go live within a few seconds of saving — you do not need to reload VSCode, restart the extension, or start a new session. Only if nothing shows up after a few seconds, restart the session to force a reload. Check what's active with the /hooks command.

  3. Just work. Every tool call and subagent run now appends a line to ~/.claude/tune-context/adoption.jsonl by default (created on first write; under CLAUDE_CONFIG_DIR it's the override's tune-context/adoption.jsonl instead).

  4. Read the report any time, from this checkout (or the installed package):

    node eval/adoption-report.mjs            # default log location
    node eval/adoption-report.mjs --log <path>

Stop / opt out: delete the PostToolUse + SubagentStart entries from settings.json (watcher picks that up too). The log stays local under ~/.claude/tune-context/ (or the CLAUDE_CONFIG_DIR override) and records only tool names, file paths, and skill/agent names — never full Bash command strings.

Honest scope: this is a behavioral proxy, not a bill. The hook log carries no tokens, so dollars come from prompt-metrics below (or from OTEL, which is Anthropic's own figure); and caveman is an output style with no tool call, so it can't be counted this way. Doctrine applied as method — narrow reads, contracts-first edits, delegation — is only partly visible too: no Skill call, but its trace shows up in the report's edit:read ratio, so the verdict never reads zero Skill calls as "unused."

Per-prompt metrics from transcripts (eval/prompt-metrics.mjs)

The adoption report reads our hook log. prompt-metrics reads the transcripts Claude Code writes itself (<config dir>/projects/<slug>/<session>.jsonl) and emits one JSONL row per prompt — time, tokens split by cache class, model, effort, tools, delegations, and cost in dollars (eval/pricing.mjs):

node eval/prompt-metrics.mjs                              # every transcript
node eval/prompt-metrics.mjs --transcript <path.jsonl>    # one session
node eval/prompt-metrics.mjs --dir <dir> > prompts.jsonl  # JSONL on stdout

Rows go to stdout, a run summary to stderr — so a pipe stays clean. Two things that source makes possible and a hook cannot: it works for past sessions, and it works when tune-context is switched off (a disabled hook records nothing, and that is exactly the arm a comparison needs).

Honest scope, printed on every run:

  • list_cost_usd is API list price, not your bill. A Pro/Max subscription charges a flat fee and consumes plan limits; nothing here is billed per token. The figure answers "what would this work have cost through the API" — which is the right yardstick for comparing two arms, and the wrong number to reconcile against a statement. The field is named so the qualifier travels with the data.
  • Repeated entries are skipped, and that changed every figure. A transcript writes some entries twice — 11 296 of 36 463 local ones (31%) repeat a uuid already written in the same file, 6 739 of them assistant turns with a usage block, the copies identical apart from a cosmetic slug. Summing both charged 45% of all cache-read tokens twice: run totals from before 2026-08-02 are inflated ~1.97×. The skip is scoped to one file (no uuid is shared between files) and the count is printed on every run.
  • Cache-aware, because a naive token sum is off by ~10×. Cache reads cost 0.1× and writes 1.25×/2×; the local run carries 4.18 billion cache-read tokens against 521 k plain input ones — 99.9% of the figure is cache and output. The total never appears without its per-class split, and a model the table doesn't know yields null plus an UNPRICED count, never a zero.
  • Prices are data, dated, and expire. eval/pricing.mjs holds one table taken from the published pricing page with its retrieval date, storing base input/output only and deriving the three cache columns from the documented 1.25×/2×/0.1× multipliers — a test asserts the derivation reproduces every published row. Service tier (batch −50%), fast mode, and inference_geo (us +10%) are applied; the claude-sonnet-5 introductory price ends in the table on 2026-08-31. Rows are priced at the prompt's own timestamp, so re-running the report later yields the same figure.
  • Subagent cost is measured from below, not captured. Subagent turns never reach the parent transcript (zero sidechain entries over 34 local ones), but the result of an Agent call carries its own usage and resolvedModel — so each delegation is priced with the same table and rides on the row as delegations[], deduplicated by agentId because the entry is written twice. That figure is a lower bound (cache_read grows linearly with the subagent's tool calls, which looks like its last turn rather than a sum) and is never added to the row's own cost. agent_spawns is still counted next to it: the spawns that left no priced result are cost there is no figure for at all.
  • Queue wait is not work. queued_s (submission → first assistant entry) is reported apart from work_s, because a prompt sent while the agent is busy waits in a queue and that is the user's time. max_gap_s exposes the same thing inside a turn — a tool call blocked on a permission prompt lands in work_s otherwise.
  • Model and effort change mid-session (measured), so both ride on every row, with a per-model token split when a single prompt used more than one.
  • Entries that can't be attributed to a prompt are counted and reported, never silently dropped. Same for the two ways the figure can be too low: usage.iterations reporting more than the usage we price from (3 turns locally), and web searches, which are billed per request ($10/1000) rather than per token and so sit outside the token total. Web fetch is free.

The other arm (tune-context experiment)

A cost figure with nothing to compare it against is a number, not a measurement. This switch turns the doctrine off so the same work can be run twice — tc={on,off} × rag={on,off} — and records which arm was in force when:

tune-context experiment status                              # what is set, and what the config actually shows
tune-context experiment set --tc off --rag off --note "pair 1"
tune-context experiment restore                             # byte for byte, or a refusal
  • tc off is the full arm (PO decision, EPIC-009): hook registrations in settings.json, the managed block in the project CLAUDE.md, the doctrine skills, the agents. Four interventions in one variable on purpose — at small n the biggest effect is the easiest to detect; splitting them is the next experiment.
  • rag off means this project's .mcp.json, and nothing else. The user scope (~/.claude.json) is rewritten by Claude Code while it runs, so the switch never touches it — status says so out loud instead of letting you assume RAG is off globally. Check claude mcp list before a measured run.
  • Reversibility comes before the change. Every touched path is fingerprinted and copied aside before it is modified, so a set that dies halfway is still undoable. restore compares each path against what set left there and refuses if anything changed in between, naming the file and the difference (first differing line for a text file, changed/added/removed files for a directory, and where the saved copy is) — the working session between the two commands is exactly when you might add a permission or write below the CLAUDE.md marker, and silently erasing that is worse than refusing.
  • The ledger is the source of truth (<config dir>/tune-context/experiment.jsonl, append-only). An analyzer assigns a session to an arm by timestamp; detecting the arm from a transcript is a cross-check, never the verdict. Every set appends — including one that changes nothing, because a run whose ledger is silent at its start cannot be assigned to an arm at all.
  • The arm belongs to the session. settings.json and MCP servers are read at startup, so it applies from the next Claude Code start; flipping it mid-session produces a mixed session, which the analysis throws away.
  • tune-context verify reads the arm and reports "off" rather than a broken install — and stays RED if the arm did not fully land. init refuses to run while an arm is set: it would re-install what the arm parked and leave restore with nothing it recognises.

The measurement protocol that uses this — paired runs on exp/<task>/<arm> branches, frozen prompt script, verdict gates — is COST_EVALUATION_PROCEDURE.md.

Per-arm aggregate (eval/analyze.mjs)

Rows are data, not an answer. analyze turns a stream of them into one aggregate per arm — and stops one step short of ruling which arm won, because the thresholds that would do the ruling are frozen by the PO, not by the model:

node eval/prompt-metrics.mjs > rows.jsonl
node eval/analyze.mjs --rows rows.jsonl > aggregate.json   # summary on stderr
  • Two populations, never one number. The paired measurement (branch exp/<task>/<arm>, everything else held constant) and ordinary daily work (attributed by the ledger, nothing held constant) come out as two separate objects with different shapes. There is no total above them and no list of pools to fold over — adding them has to be written out by hand and meant.
  • Discards are the output too. Every row lands either in an arm or in a named counter, and rows_in = rows_used + rows_rejected is asserted by a test. A session whose rows disagree about the arm is dropped whole — settings.json and MCP servers load at startup, so a session that switched arms was never in one — and a branch that mimics exp/<task>/<arm> without matching it (exp/task-089, exp/) goes to a counter, never to the nearest plausible arm.
  • Median and spread, not the mean alone. One 44-hour turn or one prompt that read a 200 k-token file moves a mean of six and leaves the median untouched; the gap between the two is itself the finding, so both are reported with p25/p75, IQR and the extremes.
  • An unpriced row leaves the cost, not the sample. It stays in n_prompts, sits outside the total, and the pool says cost_complete: false with the model that caused it named.
  • pass_order per pair. The second implementation of a task is cheaper for a reason that has nothing to do with the arm, and it pushes the same way as the effect being looked for. The per-pair, per-arm cost is carried so the two can be separated arithmetically (procedure §11b).

The report (eval/report.mjs)

One HTML file — data embedded as JSON, charts as inline SVG, no network and no build step, so it opens from file:// on a machine with nothing installed:

node eval/analyze.mjs --rows rows.jsonl > aggregate.json
node eval/report.mjs --aggregate aggregate.json --out report.html
node eval/report.mjs --aggregate aggregate.json --rejected-runs journal.json --out report.html

--rejected-runs carries what the run journal recorded as §7 invalidations ([{ session, task, arm, prompts, reason }]). Omitting it is not the same as passing []: the first says nobody asked the journal, and the verdict is held for exactly that reason — a rejection share whose denominator is unknown is a quietly dropped sample wearing a passing gate.

  • A withheld verdict is the page, not a footnote. When a gate holds, the arm-comparison chart is not drawn at all — not greyed out, not captioned. Two bars side by side assert "this is a comparison" more loudly than any caption can withdraw it. The per-pair costs stay in a table: those are what a repeat run is planned from.
  • The order effect is printed the same size as the arm effect. A page that shows one large and the other in small print has already made the claim §11b refuses.
  • Tokens never appear without their five classes, the two pools are never added, and the honesty counters — rejected rows and why, sessions dropped for switching arm mid-flight, the delegation cost that is a floor and not a total — are a section of their own rather than a footer.

Getting real cost (OTEL) — outside tune-context

tune-context does not set this up for you (deliberate — see TASK-062: it needs infra this tool stays zero-infra without). prompt-metrics above computes cost from a public price list, which is an independent calculation, not Anthropic's invoice — OTEL is the way to check it against the real one:

export CLAUDE_CODE_ENABLE_TELEMETRY=1     # off by default
export OTEL_METRICS_EXPORTER=prometheus   # NOT console — console is CLI-only and
                                           # conflicts with the SDK/message stream
                                           # a VSCode-extension session may use
export OTEL_METRIC_EXPORT_INTERVAL=1000   # default 60000ms; short sessions can
                                           # exit before ever exporting

Start a new Claude Code session with those set (must be inherited at launch — e.g. exported in the shell profile before opening VSCode, not just typed into an in-editor terminal), work for a bit, then:

curl -s http://localhost:9464/metrics | grep claude_code

Look for claude_code.cost.usage (USD, carries session.id) and claude_code.token.usage (token counts). No collector needed just to see the numbers — curl against the local Prometheus endpoint is enough; wire a real Prometheus/Grafana at that endpoint only if you want history instead of a point-in-time read. Traces (OTEL_TRACES_EXPORTER=otlp) need an extra CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1 flag and aren't needed just for cost.

Design principles

  • Minimal always-on, depth on-demand: thin CLAUDE.md points at skills; skill bodies load only when invoked (progressive disclosure).
  • @import is not a cost lever — it is inlined in full. Reduce via skills.
  • Right-size the model per role (subagent model:).
  • Caveman targets output (uncached, ~4–5× input); skills/subagents target the window.

Local dogfooding

node scripts/sync-tune-context.mjs mirrors skills/, agents/ and hooks/ into ~/.claude/ (hooks get chmod 755). Source of truth stays here. Skills take effect on the next Claude Code session; hooks are hot-reloaded by the settings file watcher (once their settings.json entries exist).

Related — the @bglocation toolbelt

  • @bglocation/code-search-mcp — semantic code index (RAG) + MCP server. The strongest lever tune-context detects and wires: search by meaning instead of grep, read only the returned spans. tune-context runs fine without it, but recommends it when no semantic-search MCP is present.
  • bglocation.dev — the rest of the toolbelt.