Files

59 lines
2.0 KiB
Markdown

# hooklib
A small **library of composable Claude Code hooks**. Each hook is a drop-in
script; a shared router dispatches one event to all of them. Adding a hook is
just dropping an executable script into the matching directory — no wiring.
## Layout
```
hooks/
hooks.json # registers the router per event (currently PreToolUse:Bash)
dispatch.sh # router: feeds the event JSON to every hooks/<event>.d/*.sh
pretooluse.d/
10-nix-deploy-output.sh
```
### How dispatch works
`dispatch.sh <Event>` reads the event JSON from stdin once and replays it to
each script in `hooks/<event>.d/` (lexical order). A sub-hook that wants to act
prints a hook JSON response to stdout and exits 0; the **first** sub-hook to
emit non-empty output wins and the router forwards it verbatim. If nobody
speaks up, the router is silent (Claude Code treats that as "allow").
Scripts are numbered (`10-`, `20-`, …) to make ordering explicit.
## Hooks
### `nix-deploy-output` (PreToolUse / Bash)
**Problem:** `nix run .#deploy` (deploy-rs) shows **empty output** in Claude
Code. deploy-rs and nix render progress as an ANSI spinner on a TTY and log to
stderr; captured non-interactively, that collapses to a blank.
**Fix:** the command must fold stderr into stdout and defeat TTY detection so
the tools emit plain, line-buffered logs. The canonical form is:
```
nix run .#deploy 2>&1 | cat
```
The hook inspects any `nix run .#deploy…` command. If it is already in the
output-safe form (`2>&1` **and** a pipe into `cat`/`tee`/`less`) it passes
through untouched. Otherwise it **denies** the call and returns the corrected
command (preserving any `--` flags / host target) for Claude to run instead.
## Adding another hook
Drop `hooks/<event>.d/NN-name.sh` (executable) that reads the event JSON on
stdin and prints a hook response only when it wants to intervene. If the event
isn't `PreToolUse`, add it to `hooks/hooks.json` pointing at
`dispatch.sh <Event>`.
## Install (local dev)
```
claude plugin install hooklib@oleks-local
```