Skip to content

Commit 6ec8934

Browse files
author
Tom Brandenburg
committed
Refactored agent commands
1 parent 4ed1672 commit 6ec8934

32 files changed

Lines changed: 5869 additions & 2472 deletions

.claude/PRPs/features/completed/add-prp-core-runner-skill.md

Lines changed: 535 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
#!/usr/bin/env -S uv run --script
2+
"""Invoke a Claude Code slash command from Python.
3+
4+
Usage:
5+
uv run .claude/PRPs/scripts/invoke_command.py prp-core-create "Add JWT authentication"
6+
uv run .claude/PRPs/scripts/invoke_command.py prp-core-execute my-feature --interactive
7+
uv run .claude/PRPs/scripts/invoke_command.py .claude/commands/prp-core/prp-core-pr.md "Add auth feature"
8+
"""
9+
10+
from __future__ import annotations
11+
12+
import argparse
13+
import subprocess
14+
import sys
15+
from pathlib import Path
16+
17+
ROOT = Path(__file__).resolve().parent.parent.parent.parent # project root
18+
19+
20+
def resolve_command_path(command: str) -> Path:
21+
"""Resolve command name or path to full .md file path.
22+
23+
Args:
24+
command: Either a command name (e.g., "prp-core-create") or full path
25+
26+
Returns:
27+
Path to the command .md file
28+
"""
29+
# If it's already a path, use it
30+
if command.endswith(".md"):
31+
path = Path(command)
32+
if path.is_absolute():
33+
return path
34+
return ROOT / path
35+
36+
# Otherwise, search in .claude/commands/
37+
commands_dir = ROOT / ".claude" / "commands"
38+
39+
# Try common locations
40+
search_paths = [
41+
commands_dir / f"{command}.md",
42+
commands_dir / "prp-core" / f"{command}.md",
43+
commands_dir / "prp-commands" / f"{command}.md",
44+
commands_dir / "development" / f"{command}.md",
45+
commands_dir / "code-quality" / f"{command}.md",
46+
]
47+
48+
for path in search_paths:
49+
if path.exists():
50+
return path
51+
52+
# Fallback: search recursively
53+
for md_file in commands_dir.rglob("*.md"):
54+
if md_file.stem == command:
55+
return md_file
56+
57+
sys.exit(f"Command not found: {command}")
58+
59+
60+
def strip_frontmatter(content: str) -> str:
61+
"""Remove YAML frontmatter from markdown content.
62+
63+
Frontmatter is delimited by --- at the start and end.
64+
"""
65+
if content.startswith("---\n"):
66+
# Find the closing ---
67+
end_marker = content.find("\n---\n", 4)
68+
if end_marker != -1:
69+
# Return content after frontmatter
70+
return content[end_marker + 5:].lstrip()
71+
return content
72+
73+
74+
def expand_template(template: str, arguments: str) -> str:
75+
"""Expand template with arguments.
76+
77+
Supports:
78+
- $ARGUMENTS: all arguments as single string
79+
- $1, $2, $3, etc.: individual positional arguments
80+
"""
81+
# Strip frontmatter first
82+
template = strip_frontmatter(template)
83+
84+
# Replace $ARGUMENTS with full argument string
85+
expanded = template.replace("$ARGUMENTS", arguments)
86+
87+
# Replace positional arguments
88+
args_list = arguments.split()
89+
for i, arg in enumerate(args_list, 1):
90+
expanded = expanded.replace(f"${i}", arg)
91+
92+
return expanded
93+
94+
95+
def invoke_command(
96+
command_path: Path,
97+
arguments: str = "",
98+
interactive: bool = False,
99+
output_format: str = "text",
100+
allowed_tools: str = "Edit,Bash,Write,Read,Glob,Grep,TodoWrite,WebFetch,WebSearch,Task",
101+
) -> None:
102+
"""Invoke a Claude Code slash command.
103+
104+
Args:
105+
command_path: Path to command .md file
106+
arguments: Arguments to pass to command
107+
interactive: Run in interactive mode
108+
output_format: Output format for headless mode (text, json, stream-json)
109+
allowed_tools: Comma-separated list of allowed tools
110+
"""
111+
# Read and expand template
112+
template = command_path.read_text()
113+
prompt = expand_template(template, arguments)
114+
115+
# Build command
116+
if interactive:
117+
# Interactive mode: pipe via stdin
118+
cmd = [
119+
"claude",
120+
"--allowedTools",
121+
allowed_tools,
122+
]
123+
subprocess.run(cmd, input=prompt.encode(), check=True)
124+
else:
125+
# Headless mode: use -p flag
126+
cmd = [
127+
"claude",
128+
"-p",
129+
prompt,
130+
"--allowedTools",
131+
allowed_tools,
132+
"--output-format",
133+
output_format,
134+
]
135+
subprocess.run(cmd, check=True)
136+
137+
138+
def main() -> None:
139+
parser = argparse.ArgumentParser(
140+
description="Invoke a Claude Code slash command",
141+
epilog="""
142+
Examples:
143+
%(prog)s prp-core-create "Add JWT authentication"
144+
%(prog)s prp-core-execute my-feature --interactive
145+
%(prog)s .claude/commands/prp-core/prp-core-pr.md "Add auth" --output-format json
146+
""",
147+
formatter_class=argparse.RawDescriptionHelpFormatter,
148+
)
149+
parser.add_argument(
150+
"command",
151+
help="Command name (e.g., 'prp-core-create') or path to .md file",
152+
)
153+
parser.add_argument(
154+
"arguments",
155+
nargs="?",
156+
default="",
157+
help="Arguments to pass to the command",
158+
)
159+
parser.add_argument(
160+
"--interactive",
161+
"-i",
162+
action="store_true",
163+
help="Run in interactive mode",
164+
)
165+
parser.add_argument(
166+
"--output-format",
167+
choices=["text", "json", "stream-json"],
168+
default="text",
169+
help="Output format for headless mode (default: text)",
170+
)
171+
parser.add_argument(
172+
"--allowed-tools",
173+
default="Edit,Bash,Write,Read,Glob,Grep,TodoWrite,WebFetch,WebSearch,Task",
174+
help="Comma-separated list of allowed tools",
175+
)
176+
177+
args = parser.parse_args()
178+
179+
# Resolve command path
180+
command_path = resolve_command_path(args.command)
181+
print(f"Invoking: {command_path.relative_to(ROOT)}", file=sys.stderr)
182+
if args.arguments:
183+
print(f"Arguments: {args.arguments}", file=sys.stderr)
184+
print(file=sys.stderr)
185+
186+
# Invoke command
187+
invoke_command(
188+
command_path=command_path,
189+
arguments=args.arguments,
190+
interactive=args.interactive,
191+
output_format=args.output_format,
192+
allowed_tools=args.allowed_tools,
193+
)
194+
195+
196+
if __name__ == "__main__":
197+
main()

0 commit comments

Comments
 (0)