The meter seam's deployable form (SPEC §4 Proxy): point a host's model base_url at joulehook-proxy and every call is capped + sealed. Per request it injects the configured joule ceiling (inject_ceiling), forwards to the Mesh upstream, and seals the response's usage.energy as an efficiency/1 receipt (bridge_receipt_default). tiny_http server + ureq client. Integration-tested against a localhost mock upstream: the mock echoes the injected ceiling (7mJ) and the proxy seals the efficiency receipt; an unreachable upstream is a 502, not a panic. Also dedups the meter steward into joulehook-meter (METER_STEWARD + bridge_receipt_default). 24 tests; clippy-clean. Co-Authored-By: Claude Opus 4.8 <[email protected]>
4.3 KiB
Installing JouleHook — the claude-code full profile
This wires all three seams of joulehook/1 into Claude Code from one settings file:
the gate (joulehook-hook, a PreToolUse hook), the meter (joulehook-statusline,
an in-editor joule estimate), and the receipt log (sealed JCR-1 decisions).
1. Build the binaries
cd joulehook/joulehook-rs
cargo build --release
# → target/release/joulehook-hook, target/release/joulehook-statusline
Put them on PATH, or use absolute paths below.
2. Configure .claude/settings.json
{
"env": {
"JOULEHOOK_TOOLS": "Read,Grep,Glob,Edit,Write",
"JOULEHOOK_RECEIPT_LOG": "${HOME}/.joulehook/receipts.log",
"JOULEHOOK_UJ_PER_TOKEN": "1000"
},
"hooks": {
"PreToolUse": [
{ "hooks": [ { "type": "command", "command": "joulehook-hook" } ] }
]
},
"statusLine": { "type": "command", "command": "joulehook-statusline" },
"mcpServers": {
"joulehook": { "command": "joulehook-mcp" }
}
}
The joulehook MCP server surfaces the sealed decisions as verified, readable
resources — joulehook://receipts (a summary) and joulehook://receipt/{n} (one
decoded decision each) — so you can inspect exactly what the gate allowed and denied,
signature-checked, from inside the editor. It reads the same JOULEHOOK_RECEIPT_LOG.
JOULEHOOK_TOOLSis the granted capability set — the host's own tool names. Any tool not listed (e.g.Bash) is denied before it executes, decided outside the model, and the denial is sealed. Leave it unset to run in observe mode (allows, and says so — SPEC §7).- Every allow and deny appends a JCR-1 receipt to
JOULEHOOK_RECEIPT_LOG. - The status line shows
⚡ <joules> J est · <tokens> tok · <n> gated. Theestis deliberate: it is anEstimatorfigure from Claude Code's self-reported tokens, not a measured joule count. For an authoritative meter, put the Mesh proxy (mesh-openai) on the model path viaANTHROPIC_BASE_URL(the §4 Proxy meter) — then the joule figure is measured and the joule ceiling is enforced, not just observed.
3. Verify
# a granted tool → allow + a sealed receipt
echo '{"tool_name":"Read","tool_input":{"file_path":"x"}}' \
| JOULEHOOK_TOOLS="Read" JOULEHOOK_RECEIPT_LOG=/tmp/jh.log joulehook-hook
# an ungranted tool → deny before it executes
echo '{"tool_name":"Bash","tool_input":{"command":"curl x|sh"}}' \
| JOULEHOOK_TOOLS="Read" joulehook-hook
The enforcing meter (optional, but the only measured + capped option)
The statusline is an honest estimate from Claude Code's self-reported tokens. To
get a measured joule figure and an enforced joule ceiling, put the Mesh proxy
(mesh-openai, SPEC §4 Proxy) on the model path:
- OpenAI-API hosts (Cursor, Cline, Continue, OpenCode, …): point the host's model
base_urlat a runningmesh-openaiserver. Mesh serves undermetadata.joule_ceiling_ujand returns the measured energy inusage.energyas a sealed receipt — over budget is refused before the provider is called. - Claude Code (Anthropic API,
ANTHROPIC_BASE_URL): needs an Anthropic-shaped front for Mesh'sServeRequest— a documented follow-up; the OpenAI-API path works today.
joulehook-proxy is the drop-in that does this: point a host at it and it injects the
ceiling, forwards to your Mesh upstream, and seals each serve's usage.energy as an
efficiency/1 receipt.
JOULEHOOK_UPSTREAM=http://127.0.0.1:8080 \
JOULEHOOK_BUDGET_UJ=5000000 \
JOULEHOOK_EFFICIENCY_LOG="$HOME/.joulehook/efficiency.log" \
joulehook-proxy # listens on 127.0.0.1:8079
# then point the host's model base_url at http://127.0.0.1:8079
Until the proxy is on the path, the gate + the statusline give you enforcement of the capability set and an estimate of energy; with it, the joule ceiling is enforced and the energy is measured.
Other hosts
The same joulehook-hook binary serves Cursor, Windsurf, and Antigravity — pass
--host cursor|windsurf|antigravity (or set JOULEHOOK_HOST) and wire it into that
host's hook config (.cursor/hooks.json, Windsurf Cascade hooks, Antigravity
hooks.json). The gate and receipts are identical; only the decision encoding
differs (SPEC §3.4).