open-standards/joulehook/joulehook-rs/INSTALL.md
Transaction Science 4759874771
All checks were successful
standards workspaces build + test / jouleclaw-rs (pull_request) Successful in 2m3s
standards workspaces build + test / aac-rs (pull_request) Successful in 1m42s
standards workspaces build + test / joule-code-rs (pull_request) Successful in 46s
standards workspaces build + test / joulecontract-rs (pull_request) Successful in 33s
standards workspaces build + test / map-rs (pull_request) Successful in 29s
standards workspaces build + test / mesh-rs (pull_request) Successful in 34s
standards workspaces build + test / openpay (pull_request) Successful in 1m23s
jouleclaw public surface must stay documented / Public-surface audit (jouleclaw-rs) (pull_request) Successful in 1m1s
no-vanity-metrics / Scan committed Markdown for vanity metrics (pull_request) Successful in 5s
no-vanity-metrics / WAI public text names no comparison (pull_request) Successful in 6s
no-vanity-metrics / WAI public text names no company or product (pull_request) Successful in 6s
standards workspaces build + test / arl-rs (pull_request) Successful in 1m14s
standards workspaces build + test / eoc-rs (pull_request) Successful in 2m0s
standards workspaces build + test / jcp-rs (pull_request) Successful in 1m39s
standards workspaces build + test / joule-ui-rs (pull_request) Successful in 1m11s
standards workspaces build + test / joulehook-rs (pull_request) Successful in 1m12s
standards workspaces build + test / proof-rs (pull_request) Successful in 1m18s
standards workspaces build + test / sense-rs (pull_request) Successful in 1m13s
standards workspaces build + test / sandbox-rs (pull_request) Successful in 2m20s
workflow hygiene / every workflow supersedes its own queue (pull_request) Successful in 5s
standards workspaces build + test / smart-byte-rs (pull_request) Successful in 4m2s
standards workspaces build + test / jouledesk-rs (pull_request) Successful in 4m38s
standards workspaces build + test / macOS energy backends (pull_request) Successful in 7m8s
jouledesk, joulehook: the docs say a figure carries its meter's grade, not "measured"
After the branch graded these figures by how they were obtained, the
docs still called them measured: jouledesk's channel example printed
"the teammate's measured contribution", Joule::energy_summary's doc and
SPEC §6 said the contribution "is *measured*", the room example sealed
an Estimator figure and closed "at its measured energy" (each turn
"metered {est} µJ" for a scripted estimate), and joulehook INSTALL.md
promised the Mesh proxy as "the only *measured* + *capped* option" that
"returns the measured energy".

Each now says the figure carries the grade of the meter that produced it
(a hardware reading only where a live counter read the work, else an
estimate graded as one), that a rollup is no better than the weakest
figure it sums, and the room example calls its figures estimates.
Text only; jouledesk and jouledesk-github tests pass unchanged.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-10 18:10:28 -04:00

105 lines
4.5 KiB
Markdown

# 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
```bash
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`
```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_TOOLS` is 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`. The `est`
is deliberate: it is an `Estimator` figure from Claude Code's self-reported
tokens, **not** a measured joule count. For a metered figure, put the Mesh
proxy (`mesh-openai`) on the model path via `ANTHROPIC_BASE_URL` (the §4 Proxy
meter) — then the joule figure is the Mesh host's meter's, at that meter's grade
(a hardware reading only where it read one), and the joule ceiling is
*enforced*, not just observed.
## 3. Verify
```bash
# 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 *metered* + *capped* option)
The statusline is an honest **estimate** from Claude Code's self-reported tokens. To
get a **metered** joule figure — at the grade of the meter that produced it, a hardware
reading only where the Mesh host's meter read one — 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_url` at a running `mesh-openai` server. Mesh serves under
`metadata.joule_ceiling_uj` and returns the served energy in `usage.energy`, graded
by the meter that produced it (`usage.energy.provenance`), as 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's `ServeRequest` — 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.
```bash
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 *metered*, at the grade of the meter that produced it.
## 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).