# Quickstart

A hook is an event, some conditions, an action, and a verdict Claude Code has to obey. In the next five minutes you'll scaffold a project, write a hook that blocks a dangerous command, and watch its inline tests pass.


# 1. Scaffold

From your repo root:

``` bash
uvx capt-hook init
```

`init` creates `.claude/hooks/` with a starter hook, wires Claude Code's settings to dispatch every hook event through capt-hook, and registers the plugin so the authoring skills load from it (nothing is copied into your repo). Prefer `uv tool install capt-hook` if you want the bare `capt-hook` on your PATH, or `uv add capt-hook` only if you're consuming it as a library -- every command here uses the `uvx` form.


# 2. Read a hook

`init` scaffolds `.claude/hooks/example.py` with a tour of the primitives -- skim it later. First, the shape of a hook, using the gate from the front page:

``` python
from captain_hook import TouchedFile, UsedSkill, gate

# A Stop gate: before the agent finishes, block if it edited UI files without doing a visual review.
gate(
    # the one-line reason shown to the agent when the gate fires
    "You edited UI files. Open them with agent-browser and verify they render before finishing.",
    # fires only if UI files changed
    only_if=[TouchedFile("**/src/routes/**", "**/src/components/**")],
    # already reviewed this session -> don't block
    skip_if=[UsedSkill("agent-browser", scope="session")],
)
```

Read it top to bottom: the event is the agent trying to finish, `only_if` scopes it to sessions that touched UI files, `skip_if` stands it down once a visual review happened, and the message is what the agent sees when the gate holds.


# 3. Write your own

Create `.claude/hooks/safety.py`:

``` python
# .claude/hooks/safety.py
from captain_hook import Allow, Block, Input, block_command

block_command(
    ["git", "push", "--force"],
    reason="Force-pushing rewrites shared history",
    hint="Use `git push --force-with-lease` instead",
    tests={
        Input(command="git push --force"): Block(),
        Input(command="git push origin main"): Allow(),
    },
)
```

One declaration, and the block covers the whole command family -- chained after `&&`, buried in a pipeline, even mentioned in quotes. The `tests` dict is the hook's contract: each [Input](../../reference/testing.md#captain_hook.Input) is a synthetic event, each value the verdict you expect.


# 4. Test it

``` bash
uvx capt-hook test
```

    PASS  safety.py::block_command  git push --force → Block
    PASS  safety.py::block_command  git push origin main → Allow
    5 tests: 5 passed, 0 failed, 0 errors, 0 skipped

That's the whole loop: declare, test, done. The next `git push --force` an agent attempts dies at `PreToolUse` with your reason and hint; LLM-backed hooks run their tests against recorded verdicts the same way.


# Where next

- [The tutorial](../../docs/tutorial/index.md) -- the same machinery, interactive in your browser.
- [Inspect and rewrite commands](../../docs/guide/commands.md) -- `evt.command`, precision vs recall, rewrites.
- [Testing](../../docs/guide/testing.md) -- strict verdicts, fixtures, and running `capt-hook test --json` in CI.
- [Under the hood](../../docs/guide/how-it-works.md) -- how a hook actually runs, from stdin JSON to verdict.
