Development
Hook
Hooks
What bernstein runs automatically, and when. A hook is a command Claude Code fires at a fixed moment, without you asking for it.
Install
$ npx -y skills add sipyourdrink-ltd/bernstein --agent claude-codeShips with bernstein. Installing the plugin gets these hooks.
Where it lives
- hooks/smart_approve.pyGitHub
Read the script
#!/usr/bin/env python3 """Claude Code PreToolUse hook - smart auto-approve for agent tool calls. Reads the tool-call payload from stdin (Claude Code hook JSON format), classifies the command, and exits with the appropriate code: Exit 0 - approve (tool call proceeds) Exit 1 - deny (tool call blocked; reason written to stdout) Exit 2 - ask (escalate to human; reason written to stdout) Usage in ~/.claude/settings.json or .claude/settings.json: { "hooks": { "PreToolUse": [ { "matcher": "Bash", "hooks": [ { "type": "command", "command": "python /path/to/bernstein/hooks/smart_approve.py" } ] } ] } } Or to cover all tools: { "hooks": { "PreToolUse": [ { "matcher": "*", "hooks": [ { "type": "command", "command": "python /path/to/bernstein/hooks/smart_approve.py" } ] } ] } } The hook can also be used standalone for testing: echo '{"tool_name": "Bash", "tool_input": {"command": "ls -la"}}' | python hooks/smart_approve.py echo $? # 0 - approved """ from __future__ import annotations import json import sys def main() -> int: """Read hook payload from stdin, classify, return exit code.""" try: raw = sys.stdin.read() if not raw.strip(): # No input - allow (edge case in some hook configurations) return 0 payload: dict[str, object] = json.loads(raw) except (json.JSONDecodeError, OSError) as exc: # Cannot parse โ escalate to human print(json.dumps({"decision": "ask", "reason": f"Hook parse error: {exc}"})) return 2 tool_name = str(payload.get("tool_name", "")) tool_input_raw = payload.get("tool_input", {}) tool_input: dict[str, object] = ( dict(tool_input_raw) # type: ignore[arg-type] if isinstance(tool_input_raw, dict) else {} ) # Import here so the hook works even when bernstein isn't on PYTHONPATH: # fall back to a copy of the logic if the package isn't importable. try: from bernstein.core.auto_approve import Decision, classify_tool_call result = classify_tool_call(tool_name, tool_input) except ImportError: # Fallback: approve everything if the package isn't installed. # This preserves the previous --dangerously-skip-permissions behaviour. print(json.dumps({"decision": "approve", "reason": "bernstein not installed; fallback approve"})) return 0 if result.decision == Decision.APPROVE: # Writing nothing to stdout is the standard "allow" signal. return 0 if result.decision == Decision.DENY: print(json.dumps({"decision": "block", "reason": result.reason})) return 1 # ASK - escalate to human print(json.dumps({"decision": "ask", "reason": result.reason})) return 2 if __name__ == "__main__": sys.exit(main())
Read the script before you install anything that runs on your machine. This is the one part of a plugin that acts without being asked.
Ships withbernstein
Deterministic orchestrator for CLI coding agents (Claude Code, Codex, Gemini CLI, +40 more). No model in the coordination loop, so parallel runs in per-task git worktrees replay byte-identically. Signed lineage plus an opt-in HMAC audit chain a reviewer checks offline, without rerunning it. Cluster mode, air-gap deploy. https://bernstein.run
Get the whole plugin
Stats
821
Stars
94
Forks
Active
Maintenance
Python
Language
Apache-2.0
License
58m ago
Last commit
4mo ago
Created
Repo: sipyourdrink-ltd/bernstein

