Build LinkedIn thought leadership with algorithmic understanding, strategic consistency, and AI-assisted content creation. Updated for the January 2026 360Brew algorithm change. 16 agents, 25 commands, 6 skills, 9 hooks, 24 reference docs. Personal data sanitized: voice samples generalized to template, high-engagement posts cleared, region-specific references replaced with placeholders. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
90 lines
3 KiB
Python
Executable file
90 lines
3 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
"""Compile hooks.template.json + prompt .md files into hooks.json.
|
|
|
|
Usage:
|
|
python3 hooks/scripts/compile-hooks.py # Generate hooks.json
|
|
python3 hooks/scripts/compile-hooks.py --check # Verify hooks.json is up to date
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
HOOKS_DIR = Path(__file__).resolve().parent.parent
|
|
TEMPLATE = HOOKS_DIR / "hooks.template.json"
|
|
OUTPUT = HOOKS_DIR / "hooks.json"
|
|
PROMPTS_DIR = HOOKS_DIR / "prompts"
|
|
|
|
|
|
def load_prompt(filename: str) -> str:
|
|
"""Load a prompt .md file and return its content as a string."""
|
|
path = PROMPTS_DIR / filename
|
|
if not path.exists():
|
|
print(f"ERROR: Prompt file not found: {path}", file=sys.stderr)
|
|
sys.exit(1)
|
|
content = path.read_text(encoding="utf-8")
|
|
if not content.strip():
|
|
print(f"ERROR: Prompt file is empty: {path}", file=sys.stderr)
|
|
sys.exit(1)
|
|
return content.rstrip("\n")
|
|
|
|
|
|
def resolve_prompts(obj):
|
|
"""Recursively walk JSON and replace prompt_file with inline prompt."""
|
|
if isinstance(obj, dict):
|
|
if "prompt_file" in obj:
|
|
if obj.get("type") != "prompt":
|
|
print(
|
|
f"ERROR: prompt_file used on non-prompt hook type: {obj.get('type')}",
|
|
file=sys.stderr,
|
|
)
|
|
sys.exit(1)
|
|
filename = obj.pop("prompt_file")
|
|
obj["prompt"] = load_prompt(filename)
|
|
return {k: resolve_prompts(v) for k, v in obj.items()}
|
|
if isinstance(obj, list):
|
|
return [resolve_prompts(item) for item in obj]
|
|
return obj
|
|
|
|
|
|
def compile_hooks() -> str:
|
|
"""Read template, resolve prompts, return JSON string."""
|
|
if not TEMPLATE.exists():
|
|
print(f"ERROR: Template not found: {TEMPLATE}", file=sys.stderr)
|
|
sys.exit(1)
|
|
template = json.loads(TEMPLATE.read_text(encoding="utf-8"))
|
|
resolved = resolve_prompts(template)
|
|
# Strip any top-level keys except "hooks" — Claude Code requires only "hooks"
|
|
invalid_keys = [k for k in resolved if k != "hooks"]
|
|
for k in invalid_keys:
|
|
print(f"WARNING: Stripping invalid top-level key '{k}' from output", file=sys.stderr)
|
|
del resolved[k]
|
|
return json.dumps(resolved, indent=2, ensure_ascii=False) + "\n"
|
|
|
|
|
|
def main():
|
|
check_mode = "--check" in sys.argv
|
|
compiled = compile_hooks()
|
|
|
|
if check_mode:
|
|
if not OUTPUT.exists():
|
|
print(f"ERROR: {OUTPUT} does not exist", file=sys.stderr)
|
|
sys.exit(1)
|
|
current = OUTPUT.read_text(encoding="utf-8")
|
|
if current == compiled:
|
|
print("OK: hooks.json is up to date")
|
|
sys.exit(0)
|
|
else:
|
|
print(
|
|
"DRIFT DETECTED: hooks.json does not match compiled output.\n"
|
|
"Run: python3 hooks/scripts/compile-hooks.py",
|
|
file=sys.stderr,
|
|
)
|
|
sys.exit(1)
|
|
|
|
OUTPUT.write_text(compiled, encoding="utf-8")
|
|
print(f"Compiled {OUTPUT.relative_to(HOOKS_DIR.parent)}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|