-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinstall.sh
More file actions
executable file
·91 lines (78 loc) · 3.32 KB
/
Copy pathinstall.sh
File metadata and controls
executable file
·91 lines (78 loc) · 3.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#!/usr/bin/env bash
#
# install.sh — install the cooperative file-lock hooks into Claude Code.
#
# Idempotent. Safe to re-run (it replaces only its own hook entries and never
# duplicates them). Honors:
# CLAUDE_HOOKS_DIR (default: ~/.claude/hooks)
# CLAUDE_SETTINGS (default: ~/.claude/settings.json)
set -euo pipefail
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
HOOK_DIR="${CLAUDE_HOOKS_DIR:-$HOME/.claude/hooks}"
SETTINGS="${CLAUDE_SETTINGS:-$HOME/.claude/settings.json}"
command -v python3 >/dev/null 2>&1 || { echo "ERROR: python3 (3.10+) is required." >&2; exit 1; }
# 1) Copy the three hook scripts (must live in the same dir for the import).
mkdir -p "$HOOK_DIR"
cp "$HERE/hooks/file_lock_core.py" "$HERE/hooks/read_file_lock.py" \
"$HERE/hooks/write_file_lock.py" "$HOOK_DIR/"
chmod +x "$HOOK_DIR/file_lock_core.py" "$HOOK_DIR/read_file_lock.py" \
"$HOOK_DIR/write_file_lock.py"
echo "✓ hooks installed -> $HOOK_DIR"
# 2) Merge the hook wiring into settings.json (preserving everything else).
python3 - "$SETTINGS" "$HOOK_DIR" <<'PY'
import json, os, sys
settings_path, hook_dir = sys.argv[1], sys.argv[2]
read_cmd = f'python3 "{hook_dir}/read_file_lock.py"'
write_cmd = f'python3 "{hook_dir}/write_file_lock.py"'
try:
with open(settings_path, encoding="utf-8") as fh:
data = json.load(fh)
except FileNotFoundError:
data = {}
except json.JSONDecodeError as exc:
sys.exit(f"ERROR: {settings_path} is not valid JSON ({exc}); fix it and re-run.")
if not isinstance(data, dict):
sys.exit(f"ERROR: {settings_path} top-level is not a JSON object.")
def entry(matcher, cmd):
return {"matcher": matcher, "hooks": [
{"type": "command", "command": cmd, "timeout": 600}]}
WANTED = {
"PreToolUse": [entry("Read", read_cmd),
entry("Edit|Write|MultiEdit|NotebookEdit", write_cmd)],
"PostToolUse": [entry("Read", read_cmd),
entry("Edit|Write|MultiEdit|NotebookEdit", write_cmd)],
}
def is_ours(e):
for h in (e.get("hooks") or []):
c = str(h.get("command", ""))
if "read_file_lock.py" in c or "write_file_lock.py" in c:
return True
return False
hooks = data.setdefault("hooks", {})
if not isinstance(hooks, dict):
sys.exit("ERROR: settings.json 'hooks' is not an object.")
for event, entries in WANTED.items():
arr = hooks.setdefault(event, [])
if not isinstance(arr, list):
sys.exit(f"ERROR: settings.json hooks.{event} is not an array.")
arr[:] = [e for e in arr if not (isinstance(e, dict) and is_ours(e))]
arr.extend(entries)
os.makedirs(os.path.dirname(os.path.abspath(settings_path)), exist_ok=True)
tmp = settings_path + ".tmp"
with open(tmp, "w", encoding="utf-8") as fh:
json.dump(data, fh, indent=2)
fh.write("\n")
os.replace(tmp, settings_path)
print(f"✓ hooks wired into {settings_path}")
PY
# 3) Global gitignore so lock artifacts never land in any repo.
EXCL="$(git config --global --get core.excludesFile 2>/dev/null || true)"
EXCL="${EXCL:-${XDG_CONFIG_HOME:-$HOME/.config}/git/ignore}"
EXCL="${EXCL/#\~/$HOME}"
mkdir -p "$(dirname "$EXCL")"; touch "$EXCL"
if ! grep -qxF '*.agentlock' "$EXCL"; then
printf '%s\n%s\n' '*.agentlock' '*.agentlock.mutex' >> "$EXCL"
fi
echo "✓ global gitignore -> $EXCL"
echo
echo "Done. Start a NEW Claude Code session (or restart) to load the hooks."