49 lines
2.0 KiB
Python
Executable File
49 lines
2.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""UserPromptExpansion hook: run a skill TUI-side, without a model turn.
|
|
|
|
Custom slash commands in Claude Code are prompts -- typing `/x` expands skills/x/SKILL.md
|
|
and sends it to the model. For a command that is really just a local action (flip a
|
|
setting, print a state) that round trip is waste. This hook gives any skill a TUI-side
|
|
implementation: if `~/.claude/skills/<command_name>/local.py` exists, it is executed with
|
|
the command's arguments, its output is shown to you, and the expansion is blocked (exit 2),
|
|
so nothing reaches the model. Skills without a local.py expand as usual (exit 0).
|
|
|
|
Order matters: the hook fires AFTER the skill body has been expanded, so any `!` inline
|
|
commands in that SKILL.md have already run by the time this executes. A skill with a
|
|
local.py must therefore not also apply its action via `!` -- it would happen twice.
|
|
|
|
Input (stdin JSON): command_name, command_args (list), expanded_prompt, cwd, ...
|
|
Fails open: any error here means exit 0 and the normal expansion goes ahead.
|
|
"""
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
|
|
|
|
def main():
|
|
try:
|
|
data = json.load(sys.stdin)
|
|
name = data.get("command_name") or ""
|
|
args = data.get("command_args") or []
|
|
if isinstance(args, str):
|
|
args = args.split()
|
|
if not name or "/" in name or name.startswith("."):
|
|
return
|
|
script = os.path.expanduser(f"~/.claude/skills/{name}/local.py")
|
|
if not os.path.isfile(script):
|
|
return
|
|
r = subprocess.run(
|
|
[sys.executable, script, *[str(a) for a in args]],
|
|
capture_output=True, text=True, timeout=20, cwd=data.get("cwd") or None,
|
|
)
|
|
except Exception:
|
|
return # fail open: let the skill expand normally
|
|
out = (r.stdout or "") + (r.stderr or "")
|
|
sys.stderr.write(out.rstrip("\n") + f"\n[/{name}: handled TUI-side by skills/{name}/local.py, no model turn]\n")
|
|
sys.exit(2)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|