Last active
April 5, 2026 04:09
-
-
Save connorshea/f2a19e955bcc54e50b33afc7e598bf97 to your computer and use it in GitHub Desktop.
Claude bash script to enforce vite-plus usage by Claude Code via hooks, requires `jq` being installed (and vp, obviously). Put it in `.claude/hooks/enforce-vp.sh` and `.claude/settings.json` in your repo.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # .claude/hooks/enforce-vp-config.sh | |
| # Block creation/editing of standalone oxlint/oxfmt config files. | |
| # NOTE: MAKE SURE TO USE `chmod +x` ON THIS FILE SO IT IS EXECUTABLE! | |
| INPUT=$(cat) | |
| FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty') | |
| BASENAME=$(basename "$FILE_PATH" 2>/dev/null) | |
| case "$BASENAME" in | |
| .oxlintrc.json|.oxlintrc.jsonc|oxlint.config.ts|.oxfmtrc.json|.oxfmtrc.jsonc|oxfmt.config.ts) | |
| jq -n '{ | |
| hookSpecificOutput: { | |
| hookEventName: "PreToolUse", | |
| permissionDecision: "deny", | |
| permissionDecisionReason: "Do not create standalone oxlint/oxfmt config files. Configure linting and formatting in `vite.config.ts` instead." | |
| } | |
| }' | |
| exit 0 | |
| ;; | |
| esac | |
| exit 0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| # .claude/hooks/enforce-vp.sh | |
| # NOTE: MAKE SURE TO USE `chmod +x` ON THIS FILE SO IT IS EXECUTABLE! | |
| INPUT=$(cat) | |
| COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command') | |
| # --- Specific rules with tailored guidance (checked first) --- | |
| # Block "npx oxlint" / "pnpx oxlint" / "<pm> dlx/exec/run oxlint" → tell Claude to use vp lint | |
| if echo "$COMMAND" | grep -qE '\b(npx|pnpx) oxlint\b|\b(npm|pnpm|yarn) (dlx|exec|run) oxlint\b'; then | |
| jq -n '{ | |
| hookSpecificOutput: { | |
| hookEventName: "PreToolUse", | |
| permissionDecision: "deny", | |
| permissionDecisionReason: "Do not run oxlint through a package manager. Use `vp lint` instead." | |
| } | |
| }' | |
| exit 0 | |
| fi | |
| # Block "npx oxfmt" / "pnpx oxfmt" / "<pm> dlx/exec/run oxfmt" → tell Claude to use vp fmt | |
| if echo "$COMMAND" | grep -qE '\b(npx|pnpx) oxfmt\b|\b(npm|pnpm|yarn) (dlx|exec|run) oxfmt\b'; then | |
| jq -n '{ | |
| hookSpecificOutput: { | |
| hookEventName: "PreToolUse", | |
| permissionDecision: "deny", | |
| permissionDecisionReason: "Do not run oxfmt through a package manager. Use `vp fmt` instead." | |
| } | |
| }' | |
| exit 0 | |
| fi | |
| # Block "<pm> run <script>" → tell Claude to use vp <script> | |
| if echo "$COMMAND" | grep -qE '\b(npm|pnpm|yarn) run \S+'; then | |
| SCRIPT=$(echo "$COMMAND" | grep -oE '\b(npm|pnpm|yarn) run \S+' | head -1 | sed -E 's/(npm|pnpm|yarn) run //') | |
| jq -n --arg s "$SCRIPT" '{ | |
| hookSpecificOutput: { | |
| hookEventName: "PreToolUse", | |
| permissionDecision: "deny", | |
| permissionDecisionReason: ("Do not use a package manager directly. Use `vp run " + $s + "` instead.") | |
| } | |
| }' | |
| exit 0 | |
| fi | |
| # Block "<pm> test" → tell Claude to use vp test | |
| if echo "$COMMAND" | grep -qE '\b(npm|pnpm|yarn) test\b'; then | |
| jq -n '{ | |
| hookSpecificOutput: { | |
| hookEventName: "PreToolUse", | |
| permissionDecision: "deny", | |
| permissionDecisionReason: "Do not use a package manager directly. Use `vp test` instead." | |
| } | |
| }' | |
| exit 0 | |
| fi | |
| # Block "npx" / "pnpx" / "<pm> dlx" / "<pm> exec" → tell Claude to use vp dlx | |
| if echo "$COMMAND" | grep -qE '\b(npx|pnpx)\b|\b(npm|pnpm|yarn) (dlx|exec)\b'; then | |
| jq -n '{ | |
| hookSpecificOutput: { | |
| hookEventName: "PreToolUse", | |
| permissionDecision: "deny", | |
| permissionDecisionReason: "Do not use npx/pnpx/dlx/exec. Use `vp dlx` or `vp exec` instead." | |
| } | |
| }' | |
| exit 0 | |
| fi | |
| # --- Direct tool invocations replaced by vp --- | |
| # Block "vitest" → tell Claude to use vp test | |
| if echo "$COMMAND" | grep -qE '\bvitest\b'; then | |
| jq -n '{ | |
| hookSpecificOutput: { | |
| hookEventName: "PreToolUse", | |
| permissionDecision: "deny", | |
| permissionDecisionReason: "Do not run vitest directly. Use `vp test` instead." | |
| } | |
| }' | |
| exit 0 | |
| fi | |
| # Block "oxlint" → tell Claude to use vp lint | |
| if echo "$COMMAND" | grep -qE '\boxlint\b'; then | |
| jq -n '{ | |
| hookSpecificOutput: { | |
| hookEventName: "PreToolUse", | |
| permissionDecision: "deny", | |
| permissionDecisionReason: "Do not run oxlint directly. Use `vp lint` instead." | |
| } | |
| }' | |
| exit 0 | |
| fi | |
| # Block "oxfmt" → tell Claude to use vp fmt | |
| if echo "$COMMAND" | grep -qE '\boxfmt\b'; then | |
| jq -n '{ | |
| hookSpecificOutput: { | |
| hookEventName: "PreToolUse", | |
| permissionDecision: "deny", | |
| permissionDecisionReason: "Do not run oxfmt directly. Use `vp fmt` instead." | |
| } | |
| }' | |
| exit 0 | |
| fi | |
| # --- Catch-all: block any remaining npm/pnpm/yarn usage --- | |
| if echo "$COMMAND" | grep -qE '\bnpm\b'; then | |
| jq -n '{ | |
| hookSpecificOutput: { | |
| hookEventName: "PreToolUse", | |
| permissionDecision: "deny", | |
| permissionDecisionReason: "Do not use npm. Use the equivalent `vp` command instead (e.g. `vp install`, `vp add`, `vp remove`, `vp update`). You can also use `vp pm` to forward a command to the package manager." | |
| } | |
| }' | |
| exit 0 | |
| fi | |
| if echo "$COMMAND" | grep -qE '\bpnpm\b'; then | |
| jq -n '{ | |
| hookSpecificOutput: { | |
| hookEventName: "PreToolUse", | |
| permissionDecision: "deny", | |
| permissionDecisionReason: "Do not use pnpm. Use the equivalent `vp` command instead (e.g. `vp install`, `vp add`, `vp remove`, `vp update`). You can also use `vp pm` to forward a command to the package manager." | |
| } | |
| }' | |
| exit 0 | |
| fi | |
| if echo "$COMMAND" | grep -qE '\byarn\b'; then | |
| jq -n '{ | |
| hookSpecificOutput: { | |
| hookEventName: "PreToolUse", | |
| permissionDecision: "deny", | |
| permissionDecisionReason: "Do not use yarn. Use the equivalent `vp` command instead (e.g. `vp install`, `vp add`, `vp remove`, `vp update`). You can also use `vp pm` to forward a command to the package manager." | |
| } | |
| }' | |
| exit 0 | |
| fi | |
| # --- Everything else is fine --- | |
| exit 0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "$schema": "https://json.schemastore.org/claude-code-settings.json", | |
| "hooks": { | |
| "PreToolUse": [ | |
| { | |
| "matcher": "Bash", | |
| "hooks": [ | |
| { | |
| "type": "command", | |
| "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/enforce-vp.sh", | |
| "timeout": 2 | |
| } | |
| ] | |
| }, | |
| { | |
| "matcher": "Write|Edit", | |
| "hooks": [ | |
| { | |
| "type": "command", | |
| "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/enforce-vp-config.sh", | |
| "timeout": 2 | |
| } | |
| ] | |
| } | |
| ] | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment