| 1 | import type { DocsHooksDict } from "../types"; |
| 2 | |
| 3 | /** |
| 4 | * English reference dictionary for `app/[locale]/docs/hooks/page.tsx` |
| 5 | * ("Run commands on events"). Checked against docs/HOOKS.md — the |
| 6 | * authoritative event-by-event contract (15 events, steering events, |
| 7 | * conditions, environment variables, project-hook approval). |
| 8 | */ |
| 9 | export const docsHooks: DocsHooksDict = { |
| 10 | metaTitle: "Run commands on events · Codewhale Docs", |
| 11 | metaDescription: |
| 12 | "Run your own scripts when a session starts, before a tool call, when a turn ends, or when Codewhale is waiting for you — to add context, enforce a rule, or get notified.", |
| 13 | bodyClassName: "text-ink-soft leading-relaxed", |
| 14 | title: "Run commands on events", |
| 15 | lede: |
| 16 | "Hooks run a command of yours at set moments in a Codewhale session. Use them to block a risky command, add context to a message, log what happened, or get told when Codewhale is waiting for you.", |
| 17 | sections: [ |
| 18 | { |
| 19 | id: "first-hook", |
| 20 | title: "Add your first hook", |
| 21 | blocks: [ |
| 22 | { p: "Hooks live in `~/.codewhale/config.toml`. This one prints a line whenever a session starts:" }, |
| 23 | { |
| 24 | code: `[hooks] |
| 25 | enabled = true |
| 26 | |
| 27 | [[hooks.hooks]] |
| 28 | name = "announce" |
| 29 | event = "session_start" |
| 30 | command = "echo 'Codewhale session started'"`, |
| 31 | lang: "config.toml", |
| 32 | }, |
| 33 | { |
| 34 | p: "Start a session, then run `/hooks` to see every configured hook, whether hooks are on, and any entry that was rejected. `/hooks events` lists the event names.", |
| 35 | }, |
| 36 | ], |
| 37 | }, |
| 38 | { |
| 39 | id: "gate", |
| 40 | title: "Block a command before it runs", |
| 41 | blocks: [ |
| 42 | { |
| 43 | p: "A `tool_call_before` hook sees each tool call before it executes and can allow it, deny it, or force an approval prompt. This one refuses force-pushes. Save the script and make it executable:", |
| 44 | }, |
| 45 | { |
| 46 | code: `#!/bin/sh |
| 47 | # ~/.codewhale/hooks/no-force-push.sh |
| 48 | case "$DEEPSEEK_TOOL_ARGS" in |
| 49 | *"push --force"*|*"push -f"*) |
| 50 | echo '{"decision": "deny", "reason": "Force-push is blocked by a hook."}' ;; |
| 51 | esac |
| 52 | exit 0`, |
| 53 | lang: "no-force-push.sh", |
| 54 | }, |
| 55 | { |
| 56 | code: `[[hooks.hooks]] |
| 57 | name = "no-force-push" |
| 58 | event = "tool_call_before" |
| 59 | command = "~/.codewhale/hooks/no-force-push.sh" |
| 60 | condition = { type = "tool_name", name = "bash" }`, |
| 61 | lang: "config.toml", |
| 62 | }, |
| 63 | { |
| 64 | p: "The hook reads the call from environment variables and answers with JSON on standard output: `allow`, `deny`, or `ask`, plus an optional `reason`, a rewritten input (`updatedInput`), or extra context for the model (`additionalContext`). Exit code 2 always denies. When several hooks answer, deny beats ask, and ask beats allow.", |
| 65 | }, |
| 66 | { |
| 67 | note: "`ask` forces a prompt in Ask and Auto-Review. Full Access never shows approval prompts, so there `ask` does not add one.", |
| 68 | }, |
| 69 | ], |
| 70 | }, |
| 71 | { |
| 72 | id: "events", |
| 73 | title: "Choose the moment", |
| 74 | blocks: [ |
| 75 | { |
| 76 | p: "Three events can change what happens next. The rest only observe; their output is ignored and a failure is a warning.", |
| 77 | }, |
| 78 | { |
| 79 | rows: [ |
| 80 | ["message_submit", "Before your message reaches the model. Can replace the text or block it."], |
| 81 | ["tool_call_before", "Before each tool call. Can allow, deny, ask, rewrite the input, or add context."], |
| 82 | ["shell_env", "Before each shell command. Can add environment variables."], |
| 83 | ["session_start / session_end", "When a session opens or closes cleanly."], |
| 84 | ["turn_end", "After a turn finishes, with its status, duration, and token usage."], |
| 85 | ["tool_call_after", "After each tool result, with its exit code when there is one."], |
| 86 | ["waiting_for_user", "When Codewhale starts waiting for an approval, an answer, or a paused goal."], |
| 87 | ["session_idle / session_busy", "When the session settles or starts working again."], |
| 88 | ["session_error / on_error", "When a turn fails for good, or on any error or failed tool."], |
| 89 | ["mode_change", "When you switch between Plan, Work, and Operate."], |
| 90 | ["subagent_spawn / subagent_complete", "When a sub-agent starts or finishes."], |
| 91 | ], |
| 92 | codeTerms: true, |
| 93 | }, |
| 94 | { |
| 95 | p: "A `condition` narrows when a hook fires: by tool name (with `*` globs), tool category, mode, or exit code, and combinations with `all` and `any`. A condition that can never match its event is rejected when the config loads, so a gate you think is armed never sits silently inert.", |
| 96 | }, |
| 97 | ], |
| 98 | }, |
| 99 | { |
| 100 | id: "options", |
| 101 | title: "Set timeouts and failure behavior", |
| 102 | blocks: [ |
| 103 | { |
| 104 | rows: [ |
| 105 | ["timeout_secs", "How long the hook may run. Default 30."], |
| 106 | ["continue_on_error", "`true` (default): a failing hook only warns. `false`: the failure blocks."], |
| 107 | ["background", "`true` runs the hook as an observer only; it cannot block or rewrite."], |
| 108 | ["working_dir", "Under `[hooks]`: where hooks run. Default: the session's workspace."], |
| 109 | ], |
| 110 | codeTerms: true, |
| 111 | }, |
| 112 | { |
| 113 | note: "`[hooks] default_timeout_secs` replaces every hook's own `timeout_secs`, not just the missing ones. Leave it unset if you want per-hook timeouts.", |
| 114 | }, |
| 115 | ], |
| 116 | }, |
| 117 | { |
| 118 | id: "project", |
| 119 | title: "Use hooks a repository ships", |
| 120 | blocks: [ |
| 121 | { |
| 122 | p: "A repository can include hooks in `.codewhale/hooks.toml`. Because they run commands on your machine, they load only after you trust the workspace and approve that exact file:", |
| 123 | }, |
| 124 | { code: "/hooks review\n/hooks approve <digest>\n/hooks revoke", lang: "Codewhale" }, |
| 125 | { |
| 126 | p: "`/hooks review` shows the commands and a digest of the file; approving that digest enables those exact bytes from the next session. Any change to the file needs a new approval. Review the scripts the commands call, too.", |
| 127 | }, |
| 128 | ], |
| 129 | }, |
| 130 | { |
| 131 | id: "headless", |
| 132 | title: "Use hooks in scripts and CI", |
| 133 | blocks: [ |
| 134 | { |
| 135 | p: "Hooks run in the interactive session. `codewhale exec` fires none by default; add `--hooks` to fire `tool_call_before` and `shell_env`. With no one to answer, an `ask` becomes a deny.", |
| 136 | }, |
| 137 | { code: 'codewhale exec --auto --hooks "run the test suite and fix the first failure"', lang: "Terminal" }, |
| 138 | ], |
| 139 | }, |
| 140 | ], |
| 141 | next: [ |
| 142 | { |
| 143 | href: "/docs/modes", |
| 144 | label: "Set modes and approvals", |
| 145 | note: "How hook decisions combine with Ask, Auto-Review, and Full Access.", |
| 146 | }, |
| 147 | { |
| 148 | href: "/docs/mcp", |
| 149 | label: "Connect tools with MCP", |
| 150 | note: "Gate MCP tools with the same `tool_call_before` hooks.", |
| 151 | }, |
| 152 | { |
| 153 | href: "/docs/configuration", |
| 154 | label: "Change settings", |
| 155 | note: "Where `config.toml` lives and what a project may override.", |
| 156 | }, |
| 157 | ], |
| 158 | sourceNote: |
| 159 | "Source documents: docs/HOOKS.md (authoritative), docs/CONFIGURATION.md · Update docs-map.ts when changing.", |
| 160 | }; |
| 161 |