Files
cline--cline/sdk/examples/hooks/PreToolUse_InjectContext.py
2026-07-13 12:52:40 +08:00

117 lines
3.4 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Cline Hook: PreToolUse (Inject Context)
Injects file context and environment info before tool execution.
Copy to ~/.cline/hooks/PreToolUse.py and chmod +x
"""
import sys
import json
import os
import subprocess
from pathlib import Path
def get_git_branch() -> str | None:
"""Get current git branch."""
try:
result = subprocess.run(
["git", "branch", "--show-current"],
capture_output=True,
text=True,
timeout=2
)
branch = result.stdout.strip()
return branch if branch else None
except Exception:
return None
def get_node_version() -> str | None:
"""Get Node.js version."""
try:
result = subprocess.run(
["node", "--version"],
capture_output=True,
text=True,
timeout=2
)
return result.stdout.strip() if result.returncode == 0 else None
except Exception:
return None
def get_file_context(file_path: str) -> str | None:
"""Get context about related files."""
path = Path(file_path)
# For TypeScript files, check for test files
if path.suffix in [".ts", ".tsx"]:
test_file = path.with_stem(path.stem).with_suffix(".test.ts")
if test_file.exists():
return f"Associated test file exists: {test_file}"
# For package.json, mention lock files
if path.name == "package.json":
lock_files = []
for lock in ["package-lock.json", "yarn.lock", "pnpm-lock.yaml"]:
if (path.parent / lock).exists():
lock_files.append(lock)
if lock_files:
return f"Related lock files: {', '.join(lock_files)}"
# For config files, mention related configs
if path.name.endswith(".config.ts") or path.name.endswith(".config.js"):
basename = path.stem.replace(".config", "")
parent = path.parent
related = []
for ext in [".ts", ".js", ".json"]:
env_file = parent / f"{basename}.env{ext}"
if env_file.exists():
related.append(f"{env_file.name}")
if related:
return f"Related config files: {', '.join(related)}"
return None
def main():
try:
event = json.load(sys.stdin)
except json.JSONDecodeError as e:
print(json.dumps({"errorMessage": f"Failed to parse: {e}"}))
return
tool_call = event.get("tool_call", {})
tool_name = tool_call.get("name", "")
tool_input = tool_call.get("input", {})
# Inject file context when reading files
if tool_name == "read_files":
file_path = tool_input.get("filePath", "")
if file_path:
context = get_file_context(file_path)
if context:
print(json.dumps({"context": context}))
return
# Inject environment context for run_commands
if tool_name == "run_commands":
context_parts = []
node_version = get_node_version()
if node_version:
context_parts.append(f"node {node_version}")
git_branch = get_git_branch()
if git_branch:
context_parts.append(f"git branch: {git_branch}")
if context_parts:
context = "Environment: " + ", ".join(context_parts)
print(json.dumps({"context": context}))
return
# Allow other tools without modification
print(json.dumps({}))
if __name__ == "__main__":
main()