@descope-int/claude-code-plugin-detoken
v0.10.0
Published
Privacy-safe Claude Code usage analytics and token-cost optimization.
Readme
detoken
See where your Claude Code tokens go. A Claude Code plugin + backend that analyzes how your team uses Claude Code — all from local session transcripts, with only aggregate metrics ever leaving the machine.
plugins/detoken/
├── (plugin) # hooks, the usage-analysis skill, the usage-analyst agent, and the uo.py engine
└── backend/ # Node/TypeScript ingest + dashboard service (Hono, Descope auth)The idea
- Measure — the plugin parses local session transcripts into privacy-safe
metrics (tokens, cost, tool/model mix, cache efficiency) and classifies each
session:
feature-dev,debugging,troubleshooting,wasting-tokens,exploration. Classification happens client-side; only the label + numbers leave the machine. - Report — on each turn end (throttled) and session end, a hook POSTs the report to the backend, which stores per-session rows and serves a dashboard of cost by user and by classification.
What it does
Analysis & reporting (no commands — just ask)
- Ask in plain language — "how much have I spent on Claude Code this week?",
"which sessions wasted tokens?" — and the
usage-analystagent parses your local transcripts off to the side and returns cost/token/tool/model breakdowns with a classification per session.
Hooks (automatic)
| Hook | Event | Behavior |
|------|-------|----------|
| session-greeting | SessionStart | Surfaces the previous session's spend for visibility. |
| report | Stop / SessionEnd | Flushes privacy-safe metrics + classification to the backend (throttled). |
Install
claude plugin marketplace add descope/claude-config
claude plugin install detoken@descope-dev-internalFor local iteration: claude --plugin-dir /path/to/plugins/detoken.
Configure (optional)
Local analysis works out of the box. Backend reporting is enabled only for approved
installs that receive DETOKEN_INGEST_SECRET through their managed environment;
the token is never shipped in this open-source plugin. Create a config file only
to override another default (point at a different backend or turn something off):
cp detoken.local.md.example ~/.claude/detoken.local.md
# e.g. change backend_url or set enabled: falseConfig is read from <project>/.claude/detoken.local.md then
~/.claude/detoken.local.md (project overrides global). backend_url can also
come from DETOKEN_BACKEND_URL. Distributors should inject the required bearer
token as DETOKEN_INGEST_SECRET, never commit it to a config file.
Hooks load at session start — restart Claude Code after changing config.
The engine
All logic lives in scripts/uo.py (Python stdlib only — no pip installs). Useful directly:
python3 scripts/uo.py metrics <transcript.jsonl> # privacy-safe usage metrics
python3 scripts/uo.py classify <transcript.jsonl> # session classification
python3 scripts/uo.py config [key] # resolved configurationBackend
The ingest + dashboard service lives in backend/ (Hono + libSQL/Turso, deploys
to Vercel). See backend/README.md. For local dev:
cd backend && npm install
INGEST_SHARED_SECRET=local-dev-token AUTH_DISABLED=true npm run devTo see your data locally instead of the team backend, point the plugin at the
local server — set backend_url: "http://localhost:8787" in
~/.claude/detoken.local.md (or DETOKEN_BACKEND_URL=http://localhost:8787) and
set DETOKEN_INGEST_SECRET=local-dev-token for the plugin process.
RTK integration
RTK (Rust Token Killer) is a separately-installed CLI that compresses prompts before they reach Claude. detoken is the measurement layer on top of it: it detects whether RTK is active, can optionally wire it into your global config, and reports RTK's token-savings to the backend so the dashboard shows fleet-wide savings and rollout coverage. detoken does not install or duplicate RTK.
What detoken does with RTK
- Checks whether RTK is installed and wired (
rtk init -ghas been run). - On each Stop/SessionEnd report, sends a privacy-safe
rtkblock: saved-token totals, command count, active flag, and RTK version. No command text or project paths leave the machine. - The backend aggregates these into fleet-wide totals visible on the dashboard (an "RTK savings" card and a coverage indicator showing what fraction of sessions have RTK active).
Privacy guarantee
Only aggregates leave the machine: saved-token totals, command counts, the active flag, and the RTK version string. The gain DB is read read-only. No command text, no project paths, no file contents.
Config keys
| Key | Default | What it does |
|-----|---------|--------------|
| rtk_enabled | true | Master toggle for the RTK health-check and gain reporting. Set false to skip RTK entirely. |
| rtk_db_path | "" | Override the path to RTK's history.db. Empty = auto-detect: config value, then RTK_DB_PATH env, then the macOS default ~/Library/Application Support/rtk/history.db. |
| rtk_auto_init | true | Run rtk init -g at SessionStart when RTK is installed but not yet wired. No-op when RTK is already active (no duplicate init). Set false to only surface a hint instead. |
Consent and the next-session caveat
rtk_auto_init defaults to true. detoken runs rtk init -g at SessionStart only
when RTK is installed but not yet wired; it is a no-op when RTK is already active
(so it never double-inits), and rtk init -g is itself idempotent. The wiring takes
effect the next session (Claude Code reads the wired config at startup). Set
rtk_auto_init: false to suppress auto-init and only show a one-line hint.
context-mode integration
context-mode keeps large tool outputs, logs, files, and docs out of Claude's active context while preserving searchable recall. detoken reports context-mode savings the same way it reports RTK savings: on each Stop/SessionEnd report it reads the current project's context-mode SQLite session DB read-only, sends only aggregate deltas, and advances a local per-project watermark only after a confirmed backend accept.
What detoken reports
- A privacy-safe
context_modeblock with active flag, version string, rawbytes_avoidedtotals, and command/event count. - The active flag is a deployment signal — the analog of RTK's "wired" — true when context-mode is installed on the machine or has recorded activity (which proves it's present even when install-detection misses an editor-plugin install). This keeps the dashboard's context-mode rollout coverage comparable to RTK's, so a fresh project on a context-mode machine is not under-counted. Actual impact is the saved-tokens / operations cards, not this flag.
- The plugin sends raw
bytes_avoided; the backend converts it to tokens (bytes_avoided // 4), so the conversion constant lives in one auditable place. - No project paths, event payloads, file contents, prompts, or raw command data leave the machine.
Config keys
| Key | Default | What it does |
|-----|---------|--------------|
| ctx_enabled | true | Master toggle for context-mode health-check and savings reporting. Set false to skip context-mode entirely. |
| ctx_db_path | "" | Override context-mode's sessions directory or a specific project .db. Empty = auto-detect: DETOKEN_CTX_DB_PATH, CONTEXT_MODE_DIR/sessions, ~/.config/opencode/context-mode/sessions, then ~/.claude/context-mode/sessions. |
Design notes
- Privacy first. No prompt/response text is ever stored or transmitted — only
counts, token totals, tool/model names, timestamps, a classification label, the
plugin's own version (
client_version, for upgrade-rollout monitoring), and auser_label(Claude account email / git / OS user, configurable) for per-user attribution. Reports carry a hashed cwd, never the path. - Never breaks a session. Every hook is a silent no-op when the plugin is
disabled, misconfigured, or missing
python3, and always exits 0. - Dependency-free engine. All client logic lives in
scripts/uo.py(Python stdlib only — no pip installs).
Requirements
python3(stdlib only).
Acknowledgements
The privacy-safe, low-overhead usage-analytics approach is informed by mksglu/context-mode.
