Skip to content

Instantly share code, notes, and snippets.

@connorshea
Last active April 5, 2026 04:09
Show Gist options
  • Select an option

  • Save connorshea/f2a19e955bcc54e50b33afc7e598bf97 to your computer and use it in GitHub Desktop.

Select an option

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.
#!/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
#!/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
{
"$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