背景
- Claude CodeはCLAUDE.mdに書いていたとしても、結構忘れがちです
- 毎回Claude Codeに自分で指摘するのは疲れます...
- 条件をトリガーに何かを必ず実行させる仕組み、hookがClaude Codeにはあります
- hookのinputはjsonが渡ってくるので、
jqなどで加工すればokかつ簡単に書けるので便利ではある - しかし、hookは簡単に読みにくいものになってしまう
- 例えば
Stopのhook(処理が終わったらntfy経由で通知させる)だと以下のようなめっちゃ長いものが簡単にできてしまう - 入力のjsonの複数の要素を使い回したい場合、一度tmpファイルとして出力しないといけない
jqのフィルタを複数回書かないといけない- jsonの中の文字列なので、yamlのように適度に改行しながら記述することができない
- 例えば
{ "hooks": { "Stop": [ { "matcher": "", "hooks": [ { "type": "command", "command": "transcript_path=$(jq -r '.transcript_path') && cat \"${transcript_path}\" | jq -s 'reverse | map(select(.type == \"assistant\" and .message.content[0].type == \"text\")) | .[0].message.content[0]' > /tmp/cc_ntfy.json && ntfy publish --markdown --title 'Claude Code' \"$(cat /tmp/cc_ntfy.json | jq -r '.text')\"" } ] } ] }, }
解決策: cchookを作った
さすがにしんどいなと思ったので、hookをもっと簡単に書けるツールを自作しました。
以下のような感じで読みやすく、かつjq likeなテンプレートを使って制御できます。「ファイルの拡張子が.goで」とか「このファイルが存在したら」といった頻出の表現はconditionsでさっと書けるようにしているので、何の設定かが一目で分かるようにしてます。
Stop: - actions: - type: command command: > MESSAGE=$(cat '{.transcript_path}' | jq -rs 'reverse | map(select(.type == "assistant" and .message.content[0].type == "text")) | .[0].message.content[0].text') ntfy publish --markdown --title 'Claude Code Complete' "$MESSAGE" PreToolUse: - matcher: "Bash" conditions: - type: command_starts_with value: "python" actions: - type: output message: "pythonは使わず`uv`を代わりに使いましょう" - matcher: "WebFetch" conditions: - type: url_starts_with value: "https://github.com" actions: - type: output message: "WebFetchではなく、`gh`コマンド経由で情報を取得しましょう" PostToolUse: - matcher: "Write|Edit|MultiEdit" conditions: - type: file_extension value: ".go" actions: - type: command command: "gofmt -w {.tool_input.file_path}" - matcher: "Write|Edit|MultiEdit" conditions: - type: file_exists value: ".pre-commit-config.yaml" actions: - type: command command: "pre-commit run --files {.tool_input.file_path}"
さっそく使ってみてるけど、普通に便利なので是非使ってみてね。