117 lines
3.4 KiB
Python
Executable File
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()
|