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
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]>
105 lines
4.5 KiB
Markdown
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).
|