pretooluse.py 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. #!/usr/bin/env python3
  2. """PreToolUse hook executor for hookify plugin.
  3. This script is called by Claude Code before any tool executes.
  4. It reads .claude/hookify.*.local.md files and evaluates rules.
  5. """
  6. import os
  7. import sys
  8. import json
  9. # CRITICAL: Add plugin root to Python path for imports
  10. # We need to add the parent of the plugin directory so Python can find "hookify" package
  11. PLUGIN_ROOT = os.environ.get('CLAUDE_PLUGIN_ROOT')
  12. if PLUGIN_ROOT:
  13. # Add the parent directory of the plugin
  14. parent_dir = os.path.dirname(PLUGIN_ROOT)
  15. if parent_dir not in sys.path:
  16. sys.path.insert(0, parent_dir)
  17. # Also add PLUGIN_ROOT itself in case we have other scripts
  18. if PLUGIN_ROOT not in sys.path:
  19. sys.path.insert(0, PLUGIN_ROOT)
  20. try:
  21. from hookify.core.config_loader import load_rules
  22. from hookify.core.rule_engine import RuleEngine
  23. except ImportError as e:
  24. # If imports fail, allow operation and log error
  25. error_msg = {"systemMessage": f"Hookify import error: {e}"}
  26. print(json.dumps(error_msg), file=sys.stdout)
  27. sys.exit(0)
  28. def main():
  29. """Main entry point for PreToolUse hook."""
  30. try:
  31. # Read input from stdin
  32. input_data = json.load(sys.stdin)
  33. # Determine event type for filtering
  34. # For PreToolUse, we use tool_name to determine "bash" vs "file" event
  35. tool_name = input_data.get('tool_name', '')
  36. event = None
  37. if tool_name == 'Bash':
  38. event = 'bash'
  39. elif tool_name in ['Edit', 'Write', 'MultiEdit']:
  40. event = 'file'
  41. # Load rules
  42. rules = load_rules(event=event)
  43. # Evaluate rules
  44. engine = RuleEngine()
  45. result = engine.evaluate_rules(rules, input_data)
  46. # Always output JSON (even if empty)
  47. print(json.dumps(result), file=sys.stdout)
  48. except Exception as e:
  49. # On any error, allow the operation and log
  50. error_output = {
  51. "systemMessage": f"Hookify error: {str(e)}"
  52. }
  53. print(json.dumps(error_output), file=sys.stdout)
  54. finally:
  55. # ALWAYS exit 0 - never block operations due to hook errors
  56. sys.exit(0)
  57. if __name__ == '__main__':
  58. main()