Skip to content

Installation

Requirements

  • Python 3.12 or later
  • uv is required. The uvx runner ships with uv and is how every captain-hook command is invoked.

Run without installing (default)

The fastest way to use captain-hook — no project dependency needed:

uvx capt-hook init

uvx fetches capt-hook into a throwaway environment, runs the CLI, and discards it. Every command works the same way — just prefix it with uvx:

uvx capt-hook test
uvx capt-hook generate-settings

Run from your project root and --hooks defaults to .claude/hooks. This is the headline path: you never add captain-hook to pyproject.toml and never manage a venv yourself.

Add as a project dependency

Only if you want captain-hook pinned in your project's lockfile (e.g. to vendor it for offline CI):

uv add capt-hook

Then the commands drop the uvx prefix:

capt-hook init

Verify installation

uvx capt-hook test

This runs the inline tests on the scaffolded example hook. If you added captain-hook as a dependency, you can also check the import directly:

python -c "import captain_hook; print(captain_hook.__name__)"

Project layout

capt-hook init produces:

my-project/
├── .claude/
│   ├── hooks/
│   │   ├── conf.py       # Settings (optional)
│   │   └── example.py    # Starter hook
│   └── settings.local.json   # Captain-hook wires itself in here

You don't need this exact layout — capt-hook --hooks <any-dir> test works on any directory of hooks. The default just lines up with what Claude Code looks for.

Dependencies

capt-hook bundles:

  • pydantic / pydantic-settings — typed settings and data models
  • tree-sitter / tree-sitter-bash — bash command parsing for CommandLine
  • spacy — NLP-based signal matching and echo detection
  • wn — WordNet synonym expansion for NLP signals
  • funcy — functional utilities
  • orjsonl — fast JSONL reading for transcripts

The spaCy English model is not auto-downloaded — see Troubleshooting.

CI integration

Run inline hook tests in CI so scaffold edits cannot ship broken expectations. capt-hook test --json emits one JSON object per test (id, status, expected, reason) and exits non-zero on the first failure.

# .github/workflows/hooks.yml
name: hooks

on:
  pull_request:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v5
      - run: uvx capt-hook test

setup-uv installs uv (which provides Python 3.12+ and uvx), so there's no separate Python or install step. Drop --json in if a downstream reporter consumes the output.