1# Chapter 14: tools, three scripts and one rule about versions 2 3A repository collects commands the way a kitchen drawer collects takeaway 4menus: one in the README, a slightly different one in a git hook, a third in 5somebody's shell history. jevhooks keeps them in one place instead. Every 6job has a name in [mise.toml](../mise.toml), and the README, the git hooks 7and you all call it by that name: `mise run check`, `mise run mod`, 8`mise run daemon`. [mise](https://mise.jdx.dev) installs the tools those 9tasks need as well, at the versions written in the same file, for one user 10and without root. 11 12Most tasks are a line or two and live in `mise.toml` itself. The three 13that needed an `if` are scripts, here. 14 15- **[with-secrets.sh](with-secrets.sh)** runs a command with Jev's key in 16 its environment. The key is *declared* in [fnox.toml](../fnox.toml), with 17 no value and no provider. If you have [fnox](https://fnox.jdx.dev) set up 18 to supply `TYPESAFE_API_KEY` (from age, from a password manager), the 19 command runs under `fnox exec`; if not, it simply runs, and the key must 20 already be in the environment. Either way nothing reads a secret from a 21 file in this repository, because none is ever written to one. 22- **[check-versions.sh](check-versions.sh)** fails when a version written 23 somewhere other than `mise.toml` disagrees with it. There is one such 24 place today: [hk.pkl](../hk.pkl) has to name the hk release it was 25 written for. It also fails if `flake.nix` starts naming a toolchain. 26- **[screenshots.sh](screenshots.sh)** takes the pictures in this guide, 27 and nobody draws them. It starts a real Claude Code session in a 28 terminal you never see (a detached [tmux](https://github.com/tmux/tmux)), 29 with the plugin loaded and a daemon of its own. It types a prompt, waits 30 for a particular line to appear on the screen, and hands the screen, 31 colours and all, to [freeze](https://github.com/charmbracelet/freeze), 32 which writes it out as an SVG. Four scenes, four pictures. 33 34> **Aside: how do you photograph a wait?** The picture of a command 35> waiting for memory needs a machine that is short of memory, on cue. The 36> script does not go and fill your RAM. Its daemon is configured with 37> `available_memory_command = "cat <a file>"` (chapter 11), and the 38> script writes `1000` into the file before the scene and `64000` after 39> it. The daemon believes the file, the build waits, the shutter clicks, 40> and the build is let through. 41 42> **Aside: why the pictures are SVG.** A screenshot as pixels is drawn 43> with one font, and the first version of these had little boxes where 44> Claude Code's symbols should be, because that font did not have them. 45> An SVG keeps the screen as text and names a list of fonts, so your 46> browser takes each character from the first font that has it. The 47> files are also about 4 KB each, and you can select the text. 48 49> **Aside: why a version check at all?** Because the alternative is a 50> comment saying "remember to change both". The hooks file cannot read 51> `mise.toml`, so the number is written twice, and a number written twice 52> is two numbers the moment someone updates one. The check turns that 53> into a failed commit instead of a puzzling afternoon. 54 55> **Aside: where is nix?** `flake.nix` is still at the top, for a machine 56> that uses it: its shell holds mise itself and a C compiler, which is the 57> one thing a downloaded toolchain does not bring (Rust build scripts link 58> with it, even for wasm). It installs nothing else and pins nothing else. 59 60> **Try it.** `mise tasks` prints every task with its description. Then 61> `mise run check-versions` (it needs no Rust), and, to watch 62> it earn its keep, change `hk = "2.5.0"` in `mise.toml` to another number 63> and run it again. 64 65## For the people who maintain it 66 67| Task | What it runs | 68| --- | --- | 69| `submodules` | `git submodule update --init --recursive` | 70| `hooks:install` | `hk install`: the hooks in `hk.pkl` | 71| `mod` | cargo for wasm32, `wasm-opt -Oz`, `wasm2js -O2`, into `plugin/hooks/jevhooks.js` (chapter 9) | 72| `daemon` | a release build of `jevhooks`, copied into `plugin/bin/` (chapter 11) | 73| `test` | `cargo test --workspace` | 74| `test:live` | The one test that asks the real Jev, through `tools/with-secrets.sh` (needs the key and the network) | 75| `clippy` | `cargo clippy --workspace` | 76| `check-versions` | `tools/check-versions.sh` | 77| `screenshots` | `tools/screenshots.sh` through `tools/with-secrets.sh`: redraws the four pictures from a real session (needs the key, a signed-in `claude`, a built daemon; a few cents) | 78| `plugin:validate`, `plugin:test` | `claude plugin validate plugin`, `claude plugin test plugin` (`CLAUDE` names another binary) | 79| `check` | `check-versions`, `test`, `mod`, `daemon`, then `plugin:validate` | 80| `serve` | `tools/with-secrets.sh plugin/bin/jevhooks serve` | 81| `status` | `plugin/bin/jevhooks status` | 82 83### In this folder 84 85| Path | What | 86| --- | --- | 87| [with-secrets.sh](with-secrets.sh) | Runs its arguments under `fnox exec` when fnox resolves the key, plainly otherwise. `JEVHOOKS_NO_FNOX=1` forces the plain path. | 88| [check-versions.sh](check-versions.sh) | POSIX tools only; reads `mise.toml`'s `key = "value"` lines. | 89| [screenshots.sh](screenshots.sh) | The four scenes, in the order they are taken; each `wait_for` fails the run, and prints the screen, when its line never appears. Writes `plugin/hooks/band.svg`, `plugin/hooks/ask.svg`, `crates/jevhooks-daemon/src/waiting.svg` and `crates/jevhooks-daemon/src/model.svg`. | 90 91← Previous: [Chapter 13, third-party/](../third-party/) · Up: [jevhooks](../)