Initial commit
This commit is contained in:
501
skills/adw-bootstrap/reference/enhanced/adws/adw_chore_implement.py
Executable file
501
skills/adw-bootstrap/reference/enhanced/adws/adw_chore_implement.py
Executable file
@@ -0,0 +1,501 @@
|
||||
#!/usr/bin/env -S uv run --script
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# dependencies = [
|
||||
# "pydantic",
|
||||
# "python-dotenv",
|
||||
# "click",
|
||||
# "rich",
|
||||
# ]
|
||||
# ///
|
||||
"""
|
||||
Run chore planning and implementation workflow.
|
||||
|
||||
This script runs two slash commands in sequence:
|
||||
1. /chore - Creates a plan based on the prompt
|
||||
2. /implement - Implements the plan created by /chore
|
||||
|
||||
Usage:
|
||||
# Method 1: Direct execution (requires uv)
|
||||
./adws/adw_chore_implement.py "Add error handling to all API endpoints"
|
||||
|
||||
# Method 2: Using uv run
|
||||
uv run adws/adw_chore_implement.py "Refactor database connection logic"
|
||||
|
||||
Examples:
|
||||
# Run with specific model
|
||||
./adws/adw_chore_implement.py "Add logging to agent.py" --model opus
|
||||
|
||||
# Run from a different working directory
|
||||
./adws/adw_chore_implement.py "Update documentation" --working-dir /path/to/project
|
||||
|
||||
# Run with verbose output
|
||||
./adws/adw_chore_implement.py "Add tests" --verbose
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
import click
|
||||
from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
from rich.table import Table
|
||||
from rich.rule import Rule
|
||||
|
||||
# Add the adw_modules directory to the path so we can import agent
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "adw_modules"))
|
||||
|
||||
from agent import (
|
||||
AgentTemplateRequest,
|
||||
AgentPromptResponse,
|
||||
execute_template,
|
||||
generate_short_id,
|
||||
)
|
||||
|
||||
# Output file name constants
|
||||
OUTPUT_JSONL = "cc_raw_output.jsonl"
|
||||
OUTPUT_JSON = "cc_raw_output.json"
|
||||
FINAL_OBJECT_JSON = "cc_final_object.json"
|
||||
SUMMARY_JSON = "custom_summary_output.json"
|
||||
|
||||
|
||||
def extract_plan_path(output: str) -> str:
|
||||
"""Extract the plan file path from the chore command output.
|
||||
|
||||
Looks for patterns like:
|
||||
- specs/chore-12345678-update-readme.md
|
||||
- Created plan at: specs/chore-...
|
||||
- Plan file: specs/chore-...
|
||||
"""
|
||||
# Try multiple patterns to find the plan path
|
||||
patterns = [
|
||||
r"specs/chore-[a-zA-Z0-9\-]+\.md",
|
||||
r"Created plan at:\s*(specs/chore-[a-zA-Z0-9\-]+\.md)",
|
||||
r"Plan file:\s*(specs/chore-[a-zA-Z0-9\-]+\.md)",
|
||||
r"path.*?:\s*(specs/chore-[a-zA-Z0-9\-]+\.md)",
|
||||
]
|
||||
|
||||
for pattern in patterns:
|
||||
match = re.search(pattern, output, re.IGNORECASE | re.MULTILINE)
|
||||
if match:
|
||||
return match.group(1) if match.groups() else match.group(0)
|
||||
|
||||
# If no match found, raise an error
|
||||
raise ValueError("Could not find plan file path in chore output")
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.argument("prompt", required=True)
|
||||
@click.option(
|
||||
"--model",
|
||||
type=click.Choice(["sonnet", "opus", "haiku"]),
|
||||
default="sonnet",
|
||||
help="Claude model to use (sonnet=balanced, opus=max intelligence, haiku=fast & economical)",
|
||||
)
|
||||
@click.option(
|
||||
"--working-dir",
|
||||
type=click.Path(exists=True, file_okay=False, dir_okay=True, resolve_path=True),
|
||||
help="Working directory for command execution (default: current directory)",
|
||||
)
|
||||
def main(
|
||||
prompt: str,
|
||||
model: str,
|
||||
working_dir: str,
|
||||
):
|
||||
"""Run chore planning and implementation workflow."""
|
||||
console = Console()
|
||||
|
||||
# Generate a unique ID for this workflow
|
||||
adw_id = generate_short_id()
|
||||
|
||||
# Use current directory if no working directory specified
|
||||
if not working_dir:
|
||||
working_dir = os.getcwd()
|
||||
|
||||
# Set default agent names
|
||||
planner_name = "planner"
|
||||
builder_name = "builder"
|
||||
|
||||
console.print(
|
||||
Panel(
|
||||
f"[bold blue]ADW Chore & Implement Workflow[/bold blue]\n\n"
|
||||
f"[cyan]ADW ID:[/cyan] {adw_id}\n"
|
||||
f"[cyan]Model:[/cyan] {model}\n"
|
||||
f"[cyan]Working Dir:[/cyan] {working_dir}",
|
||||
title="[bold blue]🚀 Workflow Configuration[/bold blue]",
|
||||
border_style="blue",
|
||||
)
|
||||
)
|
||||
console.print()
|
||||
|
||||
# Phase 1: Run /chore command
|
||||
console.print(Rule("[bold yellow]Phase 1: Planning (/chore)[/bold yellow]"))
|
||||
console.print()
|
||||
|
||||
# Create the chore request
|
||||
chore_request = AgentTemplateRequest(
|
||||
agent_name=planner_name,
|
||||
slash_command="/chore",
|
||||
args=[adw_id, prompt],
|
||||
adw_id=adw_id,
|
||||
model=model,
|
||||
working_dir=working_dir,
|
||||
)
|
||||
|
||||
# Display chore execution info
|
||||
chore_info_table = Table(show_header=False, box=None, padding=(0, 1))
|
||||
chore_info_table.add_column(style="bold cyan")
|
||||
chore_info_table.add_column()
|
||||
|
||||
chore_info_table.add_row("ADW ID", adw_id)
|
||||
chore_info_table.add_row("ADW Name", "adw_chore_implement (planning)")
|
||||
chore_info_table.add_row("Command", "/chore")
|
||||
chore_info_table.add_row("Args", f'{adw_id} "{prompt}"')
|
||||
chore_info_table.add_row("Model", model)
|
||||
chore_info_table.add_row("Agent", planner_name)
|
||||
|
||||
console.print(
|
||||
Panel(
|
||||
chore_info_table,
|
||||
title="[bold blue]🚀 Chore Inputs[/bold blue]",
|
||||
border_style="blue",
|
||||
)
|
||||
)
|
||||
console.print()
|
||||
|
||||
plan_path = None
|
||||
|
||||
try:
|
||||
# Execute the chore command
|
||||
with console.status("[bold yellow]Creating plan...[/bold yellow]"):
|
||||
chore_response = execute_template(chore_request)
|
||||
|
||||
# Display the chore result
|
||||
if chore_response.success:
|
||||
# Success panel
|
||||
console.print(
|
||||
Panel(
|
||||
chore_response.output,
|
||||
title="[bold green]✅ Planning Success[/bold green]",
|
||||
border_style="green",
|
||||
padding=(1, 2),
|
||||
)
|
||||
)
|
||||
|
||||
# Extract the plan path from the output
|
||||
try:
|
||||
plan_path = extract_plan_path(chore_response.output)
|
||||
console.print(f"\n[bold cyan]Plan created at:[/bold cyan] {plan_path}")
|
||||
except ValueError as e:
|
||||
console.print(
|
||||
Panel(
|
||||
f"[bold red]Could not extract plan path: {str(e)}[/bold red]\n\n"
|
||||
"The chore command succeeded but the plan file path could not be found in the output.",
|
||||
title="[bold red]❌ Parse Error[/bold red]",
|
||||
border_style="red",
|
||||
)
|
||||
)
|
||||
sys.exit(3)
|
||||
|
||||
else:
|
||||
# Error panel
|
||||
console.print(
|
||||
Panel(
|
||||
chore_response.output,
|
||||
title="[bold red]❌ Planning Failed[/bold red]",
|
||||
border_style="red",
|
||||
padding=(1, 2),
|
||||
)
|
||||
)
|
||||
console.print(
|
||||
"\n[bold red]Workflow aborted: Planning phase failed[/bold red]"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
# Save chore phase summary
|
||||
chore_output_dir = f"./agents/{adw_id}/{planner_name}"
|
||||
chore_summary_path = f"{chore_output_dir}/{SUMMARY_JSON}"
|
||||
|
||||
with open(chore_summary_path, "w") as f:
|
||||
json.dump(
|
||||
{
|
||||
"phase": "planning",
|
||||
"adw_id": adw_id,
|
||||
"slash_command": "/chore",
|
||||
"args": [adw_id, prompt],
|
||||
"path_to_slash_command_prompt": ".claude/commands/chore.md",
|
||||
"model": model,
|
||||
"working_dir": working_dir,
|
||||
"success": chore_response.success,
|
||||
"session_id": chore_response.session_id,
|
||||
"retry_code": chore_response.retry_code,
|
||||
"output": chore_response.output,
|
||||
"plan_path": plan_path,
|
||||
},
|
||||
f,
|
||||
indent=2,
|
||||
)
|
||||
|
||||
# Show chore output files
|
||||
console.print()
|
||||
|
||||
# Files saved panel for chore phase
|
||||
chore_files_table = Table(show_header=True, box=None)
|
||||
chore_files_table.add_column("File Type", style="bold cyan")
|
||||
chore_files_table.add_column("Path", style="dim")
|
||||
chore_files_table.add_column("Description", style="italic")
|
||||
|
||||
chore_files_table.add_row(
|
||||
"JSONL Stream",
|
||||
f"{chore_output_dir}/{OUTPUT_JSONL}",
|
||||
"Raw streaming output from Claude Code",
|
||||
)
|
||||
chore_files_table.add_row(
|
||||
"JSON Array",
|
||||
f"{chore_output_dir}/{OUTPUT_JSON}",
|
||||
"All messages as a JSON array",
|
||||
)
|
||||
chore_files_table.add_row(
|
||||
"Final Object",
|
||||
f"{chore_output_dir}/{FINAL_OBJECT_JSON}",
|
||||
"Last message entry (final result)",
|
||||
)
|
||||
chore_files_table.add_row(
|
||||
"Summary",
|
||||
chore_summary_path,
|
||||
"High-level execution summary with metadata",
|
||||
)
|
||||
|
||||
console.print(
|
||||
Panel(
|
||||
chore_files_table,
|
||||
title="[bold blue]📄 Planning Output Files[/bold blue]",
|
||||
border_style="blue",
|
||||
)
|
||||
)
|
||||
|
||||
console.print()
|
||||
|
||||
# Phase 2: Run /implement command
|
||||
console.print(
|
||||
Rule("[bold yellow]Phase 2: Implementation (/implement)[/bold yellow]")
|
||||
)
|
||||
console.print()
|
||||
|
||||
# Create the implement request
|
||||
implement_request = AgentTemplateRequest(
|
||||
agent_name=builder_name,
|
||||
slash_command="/implement",
|
||||
args=[plan_path],
|
||||
adw_id=adw_id,
|
||||
model=model,
|
||||
working_dir=working_dir,
|
||||
)
|
||||
|
||||
# Display implement execution info
|
||||
implement_info_table = Table(show_header=False, box=None, padding=(0, 1))
|
||||
implement_info_table.add_column(style="bold cyan")
|
||||
implement_info_table.add_column()
|
||||
|
||||
implement_info_table.add_row("ADW ID", adw_id)
|
||||
implement_info_table.add_row("ADW Name", "adw_chore_implement (building)")
|
||||
implement_info_table.add_row("Command", "/implement")
|
||||
implement_info_table.add_row("Args", plan_path)
|
||||
implement_info_table.add_row("Model", model)
|
||||
implement_info_table.add_row("Agent", builder_name)
|
||||
|
||||
console.print(
|
||||
Panel(
|
||||
implement_info_table,
|
||||
title="[bold blue]🚀 Implement Inputs[/bold blue]",
|
||||
border_style="blue",
|
||||
)
|
||||
)
|
||||
console.print()
|
||||
|
||||
# Execute the implement command
|
||||
with console.status("[bold yellow]Implementing plan...[/bold yellow]"):
|
||||
implement_response = execute_template(implement_request)
|
||||
|
||||
# Display the implement result
|
||||
if implement_response.success:
|
||||
# Success panel
|
||||
console.print(
|
||||
Panel(
|
||||
implement_response.output,
|
||||
title="[bold green]✅ Implementation Success[/bold green]",
|
||||
border_style="green",
|
||||
padding=(1, 2),
|
||||
)
|
||||
)
|
||||
|
||||
if implement_response.session_id:
|
||||
console.print(
|
||||
f"\n[bold cyan]Session ID:[/bold cyan] {implement_response.session_id}"
|
||||
)
|
||||
else:
|
||||
# Error panel
|
||||
console.print(
|
||||
Panel(
|
||||
implement_response.output,
|
||||
title="[bold red]❌ Implementation Failed[/bold red]",
|
||||
border_style="red",
|
||||
padding=(1, 2),
|
||||
)
|
||||
)
|
||||
|
||||
# Save implement phase summary
|
||||
implement_output_dir = f"./agents/{adw_id}/{builder_name}"
|
||||
implement_summary_path = f"{implement_output_dir}/{SUMMARY_JSON}"
|
||||
|
||||
with open(implement_summary_path, "w") as f:
|
||||
json.dump(
|
||||
{
|
||||
"phase": "implementation",
|
||||
"adw_id": adw_id,
|
||||
"slash_command": "/implement",
|
||||
"args": [plan_path],
|
||||
"path_to_slash_command_prompt": ".claude/commands/implement.md",
|
||||
"model": model,
|
||||
"working_dir": working_dir,
|
||||
"success": implement_response.success,
|
||||
"session_id": implement_response.session_id,
|
||||
"retry_code": implement_response.retry_code,
|
||||
"output": implement_response.output,
|
||||
},
|
||||
f,
|
||||
indent=2,
|
||||
)
|
||||
|
||||
# Show implement output files
|
||||
console.print()
|
||||
|
||||
# Files saved panel for implement phase
|
||||
implement_files_table = Table(show_header=True, box=None)
|
||||
implement_files_table.add_column("File Type", style="bold cyan")
|
||||
implement_files_table.add_column("Path", style="dim")
|
||||
implement_files_table.add_column("Description", style="italic")
|
||||
|
||||
implement_files_table.add_row(
|
||||
"JSONL Stream",
|
||||
f"{implement_output_dir}/{OUTPUT_JSONL}",
|
||||
"Raw streaming output from Claude Code",
|
||||
)
|
||||
implement_files_table.add_row(
|
||||
"JSON Array",
|
||||
f"{implement_output_dir}/{OUTPUT_JSON}",
|
||||
"All messages as a JSON array",
|
||||
)
|
||||
implement_files_table.add_row(
|
||||
"Final Object",
|
||||
f"{implement_output_dir}/{FINAL_OBJECT_JSON}",
|
||||
"Last message entry (final result)",
|
||||
)
|
||||
implement_files_table.add_row(
|
||||
"Summary",
|
||||
implement_summary_path,
|
||||
"High-level execution summary with metadata",
|
||||
)
|
||||
|
||||
console.print(
|
||||
Panel(
|
||||
implement_files_table,
|
||||
title="[bold blue]📄 Implementation Output Files[/bold blue]",
|
||||
border_style="blue",
|
||||
)
|
||||
)
|
||||
|
||||
# Show workflow summary
|
||||
console.print()
|
||||
console.print(Rule("[bold blue]Workflow Summary[/bold blue]"))
|
||||
console.print()
|
||||
|
||||
summary_table = Table(show_header=True, box=None)
|
||||
summary_table.add_column("Phase", style="bold cyan")
|
||||
summary_table.add_column("Status", style="bold")
|
||||
summary_table.add_column("Output Directory", style="dim")
|
||||
|
||||
# Planning phase row
|
||||
planning_status = "✅ Success" if chore_response.success else "❌ Failed"
|
||||
summary_table.add_row(
|
||||
"Planning (/chore)",
|
||||
planning_status,
|
||||
f"./agents/{adw_id}/{planner_name}/",
|
||||
)
|
||||
|
||||
# Implementation phase row
|
||||
implement_status = "✅ Success" if implement_response.success else "❌ Failed"
|
||||
summary_table.add_row(
|
||||
"Implementation (/implement)",
|
||||
implement_status,
|
||||
f"./agents/{adw_id}/{builder_name}/",
|
||||
)
|
||||
|
||||
console.print(summary_table)
|
||||
|
||||
# Create overall workflow summary
|
||||
workflow_summary_path = f"./agents/{adw_id}/workflow_summary.json"
|
||||
os.makedirs(f"./agents/{adw_id}", exist_ok=True)
|
||||
|
||||
with open(workflow_summary_path, "w") as f:
|
||||
json.dump(
|
||||
{
|
||||
"workflow": "chore_implement",
|
||||
"adw_id": adw_id,
|
||||
"prompt": prompt,
|
||||
"model": model,
|
||||
"working_dir": working_dir,
|
||||
"plan_path": plan_path,
|
||||
"phases": {
|
||||
"planning": {
|
||||
"success": chore_response.success,
|
||||
"session_id": chore_response.session_id,
|
||||
"agent": planner_name,
|
||||
"output_dir": f"./agents/{adw_id}/{planner_name}/",
|
||||
},
|
||||
"implementation": {
|
||||
"success": implement_response.success,
|
||||
"session_id": implement_response.session_id,
|
||||
"agent": builder_name,
|
||||
"output_dir": f"./agents/{adw_id}/{builder_name}/",
|
||||
},
|
||||
},
|
||||
"overall_success": chore_response.success
|
||||
and implement_response.success,
|
||||
},
|
||||
f,
|
||||
indent=2,
|
||||
)
|
||||
|
||||
console.print(
|
||||
f"\n[bold cyan]Workflow summary:[/bold cyan] {workflow_summary_path}"
|
||||
)
|
||||
console.print()
|
||||
|
||||
# Exit with appropriate code
|
||||
if chore_response.success and implement_response.success:
|
||||
console.print(
|
||||
"[bold green]✅ Workflow completed successfully![/bold green]"
|
||||
)
|
||||
sys.exit(0)
|
||||
else:
|
||||
console.print(
|
||||
"[bold yellow]⚠️ Workflow completed with errors[/bold yellow]"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
except Exception as e:
|
||||
console.print(
|
||||
Panel(
|
||||
f"[bold red]{str(e)}[/bold red]",
|
||||
title="[bold red]❌ Unexpected Error[/bold red]",
|
||||
border_style="red",
|
||||
)
|
||||
)
|
||||
sys.exit(2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,436 @@
|
||||
"""
|
||||
Claude Code SDK - The SDK Way
|
||||
|
||||
This module demonstrates the idiomatic way to use the Claude Code Python SDK
|
||||
for programmatic agent interactions. It focuses on clean, type-safe patterns
|
||||
using the SDK's native abstractions.
|
||||
|
||||
Key Concepts:
|
||||
- Use `query()` for one-shot operations
|
||||
- Use `ClaudeSDKClient` for interactive sessions
|
||||
- Work directly with SDK message types
|
||||
- Leverage async/await for clean concurrency
|
||||
- Configure options for your use case
|
||||
|
||||
Example Usage:
|
||||
# Simple query
|
||||
async for message in query(prompt="What is 2 + 2?"):
|
||||
if isinstance(message, AssistantMessage):
|
||||
print(extract_text(message))
|
||||
|
||||
# With options
|
||||
options = ClaudeCodeOptions(
|
||||
model="claude-sonnet-4-20250514",
|
||||
allowed_tools=["Read", "Write"],
|
||||
permission_mode="bypassPermissions"
|
||||
)
|
||||
async for message in query(prompt="Create hello.py", options=options):
|
||||
process_message(message)
|
||||
|
||||
# Interactive session
|
||||
async with create_session() as client:
|
||||
await client.query("Debug this error")
|
||||
async for msg in client.receive_response():
|
||||
handle_message(msg)
|
||||
"""
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import AsyncIterator, Optional, List
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
# Import all SDK components we'll use
|
||||
from claude_code_sdk import (
|
||||
# Main functions
|
||||
query,
|
||||
ClaudeSDKClient,
|
||||
|
||||
# Configuration
|
||||
ClaudeCodeOptions,
|
||||
PermissionMode,
|
||||
|
||||
# Message types
|
||||
Message,
|
||||
AssistantMessage,
|
||||
UserMessage,
|
||||
SystemMessage,
|
||||
ResultMessage,
|
||||
|
||||
# Content blocks
|
||||
ContentBlock,
|
||||
TextBlock,
|
||||
ToolUseBlock,
|
||||
ToolResultBlock,
|
||||
|
||||
# Errors
|
||||
ClaudeSDKError,
|
||||
CLIConnectionError,
|
||||
CLINotFoundError,
|
||||
ProcessError,
|
||||
)
|
||||
|
||||
# Set up logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# UTILITY FUNCTIONS
|
||||
# ============================================================================
|
||||
|
||||
def extract_text(message: AssistantMessage) -> str:
|
||||
"""Extract all text content from an assistant message.
|
||||
|
||||
The SDK way: Work directly with typed message objects.
|
||||
|
||||
Args:
|
||||
message: AssistantMessage with content blocks
|
||||
|
||||
Returns:
|
||||
Concatenated text from all text blocks
|
||||
"""
|
||||
texts = []
|
||||
for block in message.content:
|
||||
if isinstance(block, TextBlock):
|
||||
texts.append(block.text)
|
||||
return "\n".join(texts)
|
||||
|
||||
|
||||
def extract_tool_uses(message: AssistantMessage) -> List[ToolUseBlock]:
|
||||
"""Extract all tool use blocks from an assistant message.
|
||||
|
||||
Args:
|
||||
message: AssistantMessage with content blocks
|
||||
|
||||
Returns:
|
||||
List of ToolUseBlock objects
|
||||
"""
|
||||
return [
|
||||
block for block in message.content
|
||||
if isinstance(block, ToolUseBlock)
|
||||
]
|
||||
|
||||
|
||||
def get_result_text(messages: List[Message]) -> str:
|
||||
"""Extract final result text from a list of messages.
|
||||
|
||||
Args:
|
||||
messages: List of messages from a query
|
||||
|
||||
Returns:
|
||||
Result text or assistant responses
|
||||
"""
|
||||
# First check for ResultMessage
|
||||
for msg in reversed(messages):
|
||||
if isinstance(msg, ResultMessage) and msg.result:
|
||||
return msg.result
|
||||
|
||||
# Otherwise collect assistant text
|
||||
texts = []
|
||||
for msg in messages:
|
||||
if isinstance(msg, AssistantMessage):
|
||||
text = extract_text(msg)
|
||||
if text:
|
||||
texts.append(text)
|
||||
|
||||
return "\n".join(texts)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# ONE-SHOT QUERIES (The Simple SDK Way)
|
||||
# ============================================================================
|
||||
|
||||
async def simple_query(prompt: str, model: str = "claude-sonnet-4-5-20250929") -> str:
|
||||
"""Simple one-shot query with text response.
|
||||
|
||||
The SDK way: Direct use of query() with minimal setup.
|
||||
|
||||
Args:
|
||||
prompt: What to ask Claude
|
||||
model: Which model to use
|
||||
|
||||
Returns:
|
||||
Text response from Claude
|
||||
|
||||
Example:
|
||||
response = await simple_query("What is 2 + 2?")
|
||||
print(response) # "4" or "2 + 2 equals 4"
|
||||
"""
|
||||
options = ClaudeCodeOptions(model=model)
|
||||
|
||||
texts = []
|
||||
async for message in query(prompt=prompt, options=options):
|
||||
if isinstance(message, AssistantMessage):
|
||||
text = extract_text(message)
|
||||
if text:
|
||||
texts.append(text)
|
||||
|
||||
return "\n".join(texts) if texts else "No response"
|
||||
|
||||
|
||||
async def query_with_tools(
|
||||
prompt: str,
|
||||
allowed_tools: List[str],
|
||||
working_dir: Optional[Path] = None
|
||||
) -> AsyncIterator[Message]:
|
||||
"""Query with specific tools enabled.
|
||||
|
||||
The SDK way: Configure options for your use case.
|
||||
|
||||
Args:
|
||||
prompt: What to ask Claude
|
||||
allowed_tools: List of tool names to allow
|
||||
working_dir: Optional working directory
|
||||
|
||||
Yields:
|
||||
SDK message objects
|
||||
|
||||
Example:
|
||||
async for msg in query_with_tools(
|
||||
"Create a Python script",
|
||||
allowed_tools=["Write", "Read"]
|
||||
):
|
||||
if isinstance(msg, AssistantMessage):
|
||||
for block in msg.content:
|
||||
if isinstance(block, ToolUseBlock):
|
||||
print(f"Using tool: {block.name}")
|
||||
"""
|
||||
options = ClaudeCodeOptions(
|
||||
allowed_tools=allowed_tools,
|
||||
cwd=str(working_dir) if working_dir else None,
|
||||
permission_mode="bypassPermissions" # For automated workflows
|
||||
)
|
||||
|
||||
async for message in query(prompt=prompt, options=options):
|
||||
yield message
|
||||
|
||||
|
||||
async def collect_query_response(
|
||||
prompt: str,
|
||||
options: Optional[ClaudeCodeOptions] = None
|
||||
) -> tuple[List[Message], Optional[ResultMessage]]:
|
||||
"""Collect all messages from a query.
|
||||
|
||||
The SDK way: Async iteration with type checking.
|
||||
|
||||
Args:
|
||||
prompt: What to ask Claude
|
||||
options: Optional configuration
|
||||
|
||||
Returns:
|
||||
Tuple of (all_messages, result_message)
|
||||
|
||||
Example:
|
||||
messages, result = await collect_query_response("List files")
|
||||
if result and not result.is_error:
|
||||
print("Success!")
|
||||
for msg in messages:
|
||||
process_message(msg)
|
||||
"""
|
||||
if options is None:
|
||||
options = ClaudeCodeOptions()
|
||||
|
||||
messages = []
|
||||
result = None
|
||||
|
||||
async for message in query(prompt=prompt, options=options):
|
||||
messages.append(message)
|
||||
if isinstance(message, ResultMessage):
|
||||
result = message
|
||||
|
||||
return messages, result
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# INTERACTIVE SESSIONS (The SDK Client Way)
|
||||
# ============================================================================
|
||||
|
||||
@asynccontextmanager
|
||||
async def create_session(
|
||||
model: str = "claude-sonnet-4-5-20250929",
|
||||
working_dir: Optional[Path] = None
|
||||
):
|
||||
"""Create an interactive session with Claude.
|
||||
|
||||
The SDK way: Use context managers for resource management.
|
||||
|
||||
Args:
|
||||
model: Which model to use
|
||||
working_dir: Optional working directory
|
||||
|
||||
Yields:
|
||||
Connected ClaudeSDKClient
|
||||
|
||||
Example:
|
||||
async with create_session() as client:
|
||||
await client.query("Hello")
|
||||
async for msg in client.receive_response():
|
||||
print(msg)
|
||||
"""
|
||||
options = ClaudeCodeOptions(
|
||||
model=model,
|
||||
cwd=str(working_dir) if working_dir else None,
|
||||
permission_mode="bypassPermissions"
|
||||
)
|
||||
|
||||
client = ClaudeSDKClient(options=options)
|
||||
await client.connect()
|
||||
|
||||
try:
|
||||
yield client
|
||||
finally:
|
||||
await client.disconnect()
|
||||
|
||||
|
||||
async def interactive_conversation(prompts: List[str]) -> List[Message]:
|
||||
"""Have an interactive conversation with Claude.
|
||||
|
||||
The SDK way: Bidirectional communication with the client.
|
||||
|
||||
Args:
|
||||
prompts: List of prompts to send in sequence
|
||||
|
||||
Returns:
|
||||
All messages from the conversation
|
||||
|
||||
Example:
|
||||
messages = await interactive_conversation([
|
||||
"What's the weather like?",
|
||||
"Tell me more about clouds",
|
||||
"How do they form?"
|
||||
])
|
||||
"""
|
||||
all_messages = []
|
||||
|
||||
async with create_session() as client:
|
||||
for prompt in prompts:
|
||||
# Send prompt
|
||||
await client.query(prompt)
|
||||
|
||||
# Collect response
|
||||
async for msg in client.receive_response():
|
||||
all_messages.append(msg)
|
||||
if isinstance(msg, ResultMessage):
|
||||
break
|
||||
|
||||
return all_messages
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# ERROR HANDLING (The SDK Way)
|
||||
# ============================================================================
|
||||
|
||||
async def safe_query(prompt: str) -> tuple[Optional[str], Optional[str]]:
|
||||
"""Query with comprehensive error handling.
|
||||
|
||||
The SDK way: Handle specific SDK exceptions.
|
||||
|
||||
Args:
|
||||
prompt: What to ask Claude
|
||||
|
||||
Returns:
|
||||
Tuple of (response_text, error_message)
|
||||
|
||||
Example:
|
||||
response, error = await safe_query("Help me debug this")
|
||||
if error:
|
||||
print(f"Error: {error}")
|
||||
else:
|
||||
print(f"Response: {response}")
|
||||
"""
|
||||
try:
|
||||
response = await simple_query(prompt)
|
||||
return response, None
|
||||
|
||||
except CLINotFoundError:
|
||||
return None, "Claude Code CLI not found. Install with: npm install -g @anthropic-ai/claude-code"
|
||||
|
||||
except CLIConnectionError as e:
|
||||
return None, f"Connection error: {str(e)}"
|
||||
|
||||
except ProcessError as e:
|
||||
return None, f"Process error (exit code {e.exit_code}): {str(e)}"
|
||||
|
||||
except ClaudeSDKError as e:
|
||||
return None, f"SDK error: {str(e)}"
|
||||
|
||||
except Exception as e:
|
||||
return None, f"Unexpected error: {str(e)}"
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# ADVANCED PATTERNS (The SDK Way)
|
||||
# ============================================================================
|
||||
|
||||
async def stream_with_progress(
|
||||
prompt: str,
|
||||
on_text: Optional[callable] = None,
|
||||
on_tool: Optional[callable] = None
|
||||
) -> ResultMessage:
|
||||
"""Stream query with progress callbacks.
|
||||
|
||||
The SDK way: Process messages as they arrive.
|
||||
|
||||
Args:
|
||||
prompt: What to ask Claude
|
||||
on_text: Callback for text blocks (optional)
|
||||
on_tool: Callback for tool use blocks (optional)
|
||||
|
||||
Returns:
|
||||
Final ResultMessage
|
||||
|
||||
Example:
|
||||
result = await stream_with_progress(
|
||||
"Analyze this codebase",
|
||||
on_text=lambda text: print(f"Claude: {text}"),
|
||||
on_tool=lambda tool: print(f"Using: {tool.name}")
|
||||
)
|
||||
print(f"Cost: ${result.total_cost_usd:.4f}")
|
||||
"""
|
||||
result = None
|
||||
|
||||
async for message in query(prompt=prompt):
|
||||
if isinstance(message, AssistantMessage):
|
||||
for block in message.content:
|
||||
if isinstance(block, TextBlock) and on_text:
|
||||
on_text(block.text)
|
||||
elif isinstance(block, ToolUseBlock) and on_tool:
|
||||
on_tool(block)
|
||||
|
||||
elif isinstance(message, ResultMessage):
|
||||
result = message
|
||||
|
||||
return result
|
||||
|
||||
|
||||
async def query_with_timeout(prompt: str, timeout_seconds: float = 30) -> Optional[str]:
|
||||
"""Query with timeout protection.
|
||||
|
||||
The SDK way: Use asyncio for timeout control.
|
||||
|
||||
Args:
|
||||
prompt: What to ask Claude
|
||||
timeout_seconds: Maximum time to wait
|
||||
|
||||
Returns:
|
||||
Response text or None if timeout
|
||||
|
||||
Example:
|
||||
response = await query_with_timeout("Complex analysis", timeout_seconds=60)
|
||||
if response is None:
|
||||
print("Query timed out")
|
||||
"""
|
||||
import asyncio
|
||||
|
||||
try:
|
||||
# Create the query task
|
||||
async def _query():
|
||||
return await simple_query(prompt)
|
||||
|
||||
# Run with timeout
|
||||
response = await asyncio.wait_for(_query(), timeout=timeout_seconds)
|
||||
return response
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
logger.warning(f"Query timed out after {timeout_seconds} seconds")
|
||||
return None
|
||||
|
||||
470
skills/adw-bootstrap/reference/enhanced/adws/adw_sdk_prompt.py
Executable file
470
skills/adw-bootstrap/reference/enhanced/adws/adw_sdk_prompt.py
Executable file
@@ -0,0 +1,470 @@
|
||||
#!/usr/bin/env -S uv run --script
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# dependencies = [
|
||||
# "pydantic",
|
||||
# "python-dotenv",
|
||||
# "click",
|
||||
# "rich",
|
||||
# "claude-code-sdk",
|
||||
# "anyio",
|
||||
# ]
|
||||
# ///
|
||||
"""
|
||||
Run Claude Code prompts using the official Python SDK.
|
||||
|
||||
This ADW demonstrates using the Claude Code Python SDK for both one-shot
|
||||
and interactive sessions. The SDK provides better type safety, error handling,
|
||||
and a more Pythonic interface compared to subprocess-based implementations.
|
||||
|
||||
Usage:
|
||||
# One-shot query (default)
|
||||
./adws/adw_sdk_prompt.py "Hello Claude Code"
|
||||
|
||||
# Interactive session
|
||||
./adws/adw_sdk_prompt.py --interactive
|
||||
|
||||
# Resume a previous session
|
||||
./adws/adw_sdk_prompt.py --interactive --session-id abc123
|
||||
|
||||
# With specific model
|
||||
./adws/adw_sdk_prompt.py "Create a FastAPI app" --model opus
|
||||
|
||||
# From different directory
|
||||
./adws/adw_sdk_prompt.py "List files here" --working-dir /path/to/project
|
||||
|
||||
Examples:
|
||||
# Simple query
|
||||
./adws/adw_sdk_prompt.py "Explain async/await in Python"
|
||||
|
||||
# Interactive debugging session
|
||||
./adws/adw_sdk_prompt.py --interactive --context "Debugging a memory leak"
|
||||
|
||||
# Resume session with context
|
||||
./adws/adw_sdk_prompt.py --interactive --session-id abc123 --context "Continue debugging"
|
||||
|
||||
# Query with tools
|
||||
./adws/adw_sdk_prompt.py "Create a Python web server" --tools Read,Write,Bash
|
||||
|
||||
Key Features:
|
||||
- Uses official Claude Code Python SDK
|
||||
- Supports both one-shot and interactive modes
|
||||
- Better error handling with typed exceptions
|
||||
- Native async/await support
|
||||
- Clean message type handling
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
from typing import Optional, List
|
||||
import click
|
||||
from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
from rich.table import Table
|
||||
from rich.live import Live
|
||||
from rich.spinner import Spinner
|
||||
from rich.text import Text
|
||||
from rich.prompt import Prompt
|
||||
|
||||
# Add the adw_modules directory to the path
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "adw_modules"))
|
||||
|
||||
# Import SDK functions from our clean module
|
||||
from agent_sdk import (
|
||||
simple_query,
|
||||
query_with_tools,
|
||||
collect_query_response,
|
||||
create_session,
|
||||
safe_query,
|
||||
stream_with_progress,
|
||||
extract_text,
|
||||
extract_tool_uses,
|
||||
)
|
||||
|
||||
# Import SDK types
|
||||
from claude_code_sdk import (
|
||||
ClaudeCodeOptions,
|
||||
AssistantMessage,
|
||||
ResultMessage,
|
||||
TextBlock,
|
||||
ToolUseBlock,
|
||||
)
|
||||
|
||||
|
||||
def generate_short_id() -> str:
|
||||
"""Generate a short ID for tracking."""
|
||||
import uuid
|
||||
|
||||
return str(uuid.uuid4())[:8]
|
||||
|
||||
|
||||
async def run_one_shot_query(
|
||||
prompt: str,
|
||||
model: str,
|
||||
working_dir: str,
|
||||
allowed_tools: Optional[List[str]] = None,
|
||||
session_id: Optional[str] = None,
|
||||
) -> None:
|
||||
"""Run a one-shot query using the SDK."""
|
||||
console = Console()
|
||||
adw_id = generate_short_id()
|
||||
|
||||
# Display execution info
|
||||
info_table = Table(show_header=False, box=None, padding=(0, 1))
|
||||
info_table.add_column(style="bold cyan")
|
||||
info_table.add_column()
|
||||
|
||||
info_table.add_row("ADW ID", adw_id)
|
||||
info_table.add_row("Mode", "One-shot Query")
|
||||
info_table.add_row("Prompt", prompt)
|
||||
info_table.add_row("Model", model)
|
||||
info_table.add_row("Working Dir", working_dir)
|
||||
if allowed_tools:
|
||||
info_table.add_row("Tools", ", ".join(allowed_tools))
|
||||
if session_id:
|
||||
info_table.add_row("Session ID", session_id)
|
||||
info_table.add_row("[bold green]SDK[/bold green]", "Claude Code Python SDK")
|
||||
|
||||
console.print(
|
||||
Panel(
|
||||
info_table,
|
||||
title="[bold blue]🚀 SDK Query Execution[/bold blue]",
|
||||
border_style="blue",
|
||||
)
|
||||
)
|
||||
console.print()
|
||||
|
||||
try:
|
||||
# Execute query based on whether tools are needed
|
||||
with console.status("[bold yellow]Executing via SDK...[/bold yellow]"):
|
||||
if allowed_tools:
|
||||
# Query with tools
|
||||
options = ClaudeCodeOptions(
|
||||
model=model,
|
||||
allowed_tools=allowed_tools,
|
||||
cwd=working_dir,
|
||||
permission_mode="bypassPermissions",
|
||||
)
|
||||
if session_id:
|
||||
options.resume = session_id
|
||||
messages, result = await collect_query_response(prompt, options=options)
|
||||
|
||||
# Extract response text
|
||||
response_text = ""
|
||||
tool_uses = []
|
||||
|
||||
for msg in messages:
|
||||
if isinstance(msg, AssistantMessage):
|
||||
text = extract_text(msg)
|
||||
if text:
|
||||
response_text += text + "\n"
|
||||
for tool in extract_tool_uses(msg):
|
||||
tool_uses.append(f"{tool.name} ({tool.id[:8]}...)")
|
||||
|
||||
success = result and not result.is_error if result else False
|
||||
|
||||
else:
|
||||
# Simple query
|
||||
response_text, error = await safe_query(prompt)
|
||||
success = error is None
|
||||
tool_uses = []
|
||||
|
||||
if error:
|
||||
response_text = error
|
||||
|
||||
# Display result
|
||||
if success:
|
||||
console.print(
|
||||
Panel(
|
||||
response_text.strip(),
|
||||
title="[bold green]✅ SDK Success[/bold green]",
|
||||
border_style="green",
|
||||
padding=(1, 2),
|
||||
)
|
||||
)
|
||||
|
||||
if tool_uses:
|
||||
console.print(
|
||||
f"\n[bold cyan]Tools used:[/bold cyan] {', '.join(tool_uses)}"
|
||||
)
|
||||
else:
|
||||
console.print(
|
||||
Panel(
|
||||
response_text,
|
||||
title="[bold red]❌ SDK Error[/bold red]",
|
||||
border_style="red",
|
||||
padding=(1, 2),
|
||||
)
|
||||
)
|
||||
|
||||
# Show cost and session info if available
|
||||
if "result" in locals() and result:
|
||||
if result.total_cost_usd:
|
||||
console.print(
|
||||
f"\n[bold cyan]Cost:[/bold cyan] ${result.total_cost_usd:.4f}"
|
||||
)
|
||||
if hasattr(result, 'session_id') and result.session_id:
|
||||
console.print(
|
||||
f"[bold cyan]Session ID:[/bold cyan] {result.session_id}"
|
||||
)
|
||||
console.print(
|
||||
f"[dim]Resume with: --session-id {result.session_id}[/dim]"
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
console.print(
|
||||
Panel(
|
||||
f"[bold red]{str(e)}[/bold red]",
|
||||
title="[bold red]❌ Unexpected Error[/bold red]",
|
||||
border_style="red",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
async def run_interactive_session(
|
||||
model: str,
|
||||
working_dir: str,
|
||||
context: Optional[str] = None,
|
||||
session_id: Optional[str] = None,
|
||||
) -> None:
|
||||
"""Run an interactive session using the SDK."""
|
||||
console = Console()
|
||||
adw_id = generate_short_id()
|
||||
|
||||
# Display session info
|
||||
info_table = Table(show_header=False, box=None, padding=(0, 1))
|
||||
info_table.add_column(style="bold cyan")
|
||||
info_table.add_column()
|
||||
|
||||
info_table.add_row("ADW ID", adw_id)
|
||||
info_table.add_row("Mode", "Interactive Session")
|
||||
info_table.add_row("Model", model)
|
||||
info_table.add_row("Working Dir", working_dir)
|
||||
if context:
|
||||
info_table.add_row("Context", context)
|
||||
if session_id:
|
||||
info_table.add_row("Session ID", session_id)
|
||||
info_table.add_row("[bold green]SDK[/bold green]", "Claude Code Python SDK")
|
||||
|
||||
console.print(
|
||||
Panel(
|
||||
info_table,
|
||||
title="[bold blue]💬 SDK Interactive Session[/bold blue]",
|
||||
border_style="blue",
|
||||
)
|
||||
)
|
||||
console.print()
|
||||
|
||||
# Instructions
|
||||
console.print("[bold yellow]Interactive Mode[/bold yellow]")
|
||||
console.print("Commands: 'exit' or 'quit' to end session")
|
||||
console.print("Just type your questions or requests\n")
|
||||
|
||||
# Start session
|
||||
options = ClaudeCodeOptions(
|
||||
model=model,
|
||||
cwd=working_dir,
|
||||
permission_mode="bypassPermissions",
|
||||
)
|
||||
if session_id:
|
||||
options.resume = session_id
|
||||
|
||||
from claude_code_sdk import ClaudeSDKClient
|
||||
client = ClaudeSDKClient(options=options)
|
||||
await client.connect()
|
||||
|
||||
# Track session ID from results throughout the session
|
||||
session_id_from_result = None
|
||||
|
||||
try:
|
||||
# Send initial context if provided
|
||||
if context:
|
||||
console.print(f"[dim]Setting context: {context}[/dim]\n")
|
||||
await client.query(f"Context: {context}")
|
||||
|
||||
# Consume the context response
|
||||
async for msg in client.receive_response():
|
||||
if isinstance(msg, AssistantMessage):
|
||||
text = extract_text(msg)
|
||||
if text:
|
||||
console.print(f"[dim]Claude: {text}[/dim]\n")
|
||||
|
||||
# Interactive loop
|
||||
while True:
|
||||
# Get user input
|
||||
try:
|
||||
user_input = Prompt.ask("[bold cyan]You[/bold cyan]")
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
console.print("\n[yellow]Session interrupted[/yellow]")
|
||||
break
|
||||
|
||||
if user_input.lower() in ["exit", "quit"]:
|
||||
break
|
||||
|
||||
# Send to Claude
|
||||
await client.query(user_input)
|
||||
|
||||
# Show response with progress
|
||||
console.print()
|
||||
response_parts = []
|
||||
tool_uses = []
|
||||
cost = None
|
||||
session_id_from_result = None
|
||||
|
||||
with Live(
|
||||
Spinner("dots", text="Thinking..."),
|
||||
console=console,
|
||||
refresh_per_second=4,
|
||||
):
|
||||
async for msg in client.receive_response():
|
||||
if isinstance(msg, AssistantMessage):
|
||||
text = extract_text(msg)
|
||||
if text:
|
||||
response_parts.append(text)
|
||||
|
||||
for tool in extract_tool_uses(msg):
|
||||
tool_uses.append(f"{tool.name}")
|
||||
|
||||
elif isinstance(msg, ResultMessage):
|
||||
if msg.total_cost_usd:
|
||||
cost = msg.total_cost_usd
|
||||
if hasattr(msg, 'session_id') and msg.session_id:
|
||||
session_id_from_result = msg.session_id
|
||||
|
||||
# Display response
|
||||
if response_parts:
|
||||
console.print("[bold green]Claude:[/bold green]")
|
||||
for part in response_parts:
|
||||
console.print(part)
|
||||
|
||||
if tool_uses:
|
||||
console.print(f"\n[dim]Tools used: {', '.join(tool_uses)}[/dim]")
|
||||
|
||||
if cost:
|
||||
console.print(f"[dim]Cost: ${cost:.4f}[/dim]")
|
||||
|
||||
if session_id_from_result:
|
||||
console.print(f"[dim]Session ID: {session_id_from_result}[/dim]")
|
||||
|
||||
console.print()
|
||||
|
||||
finally:
|
||||
await client.disconnect()
|
||||
|
||||
console.print("\n[bold green]Session ended[/bold green]")
|
||||
console.print(f"[dim]ADW ID: {adw_id}[/dim]")
|
||||
if 'session_id_from_result' in locals() and session_id_from_result:
|
||||
console.print(f"[bold cyan]Session ID:[/bold cyan] {session_id_from_result}")
|
||||
console.print(f"[dim]Resume with: ./adws/adw_sdk_prompt.py --interactive --session-id {session_id_from_result}[/dim]")
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.argument("prompt", required=False)
|
||||
@click.option(
|
||||
"--interactive",
|
||||
"-i",
|
||||
is_flag=True,
|
||||
help="Start an interactive session instead of one-shot query",
|
||||
)
|
||||
@click.option(
|
||||
"--model",
|
||||
type=click.Choice(["sonnet", "opus", "haiku"]),
|
||||
default="sonnet",
|
||||
help="Claude model to use (sonnet=balanced, opus=max intelligence, haiku=fast & economical)",
|
||||
)
|
||||
@click.option(
|
||||
"--working-dir",
|
||||
type=click.Path(exists=True, file_okay=False, dir_okay=True, resolve_path=True),
|
||||
help="Working directory (default: current directory)",
|
||||
)
|
||||
@click.option(
|
||||
"--tools",
|
||||
help="Comma-separated list of allowed tools (e.g., Read,Write,Bash)",
|
||||
)
|
||||
@click.option(
|
||||
"--context",
|
||||
help="Context for interactive session (e.g., 'Debugging a memory leak')",
|
||||
)
|
||||
@click.option(
|
||||
"--session-id",
|
||||
help="Resume a previous session by its ID",
|
||||
)
|
||||
def main(
|
||||
prompt: Optional[str],
|
||||
interactive: bool,
|
||||
model: str,
|
||||
working_dir: Optional[str],
|
||||
tools: Optional[str],
|
||||
context: Optional[str],
|
||||
session_id: Optional[str],
|
||||
):
|
||||
"""Run Claude Code prompts using the Python SDK.
|
||||
|
||||
Examples:
|
||||
# One-shot query
|
||||
adw_sdk_prompt.py "What is 2 + 2?"
|
||||
|
||||
# Interactive session
|
||||
adw_sdk_prompt.py --interactive
|
||||
|
||||
# Resume session
|
||||
adw_sdk_prompt.py --interactive --session-id abc123
|
||||
|
||||
# Query with tools
|
||||
adw_sdk_prompt.py "Create hello.py" --tools Write,Read
|
||||
"""
|
||||
if not working_dir:
|
||||
working_dir = os.getcwd()
|
||||
|
||||
# Convert model names
|
||||
model_map = {
|
||||
"sonnet": "claude-sonnet-4-5-20250929",
|
||||
"opus": "claude-opus-4-20250514",
|
||||
"haiku": "claude-haiku-4-5-20251001"
|
||||
}
|
||||
full_model = model_map.get(model, model)
|
||||
|
||||
# Parse tools if provided
|
||||
allowed_tools = None
|
||||
if tools:
|
||||
allowed_tools = [t.strip() for t in tools.split(",")]
|
||||
|
||||
# Run appropriate mode
|
||||
if interactive:
|
||||
if prompt:
|
||||
console = Console()
|
||||
console.print(
|
||||
"[yellow]Warning: Prompt ignored in interactive mode[/yellow]\n"
|
||||
)
|
||||
|
||||
asyncio.run(
|
||||
run_interactive_session(
|
||||
model=full_model,
|
||||
working_dir=working_dir,
|
||||
context=context,
|
||||
session_id=session_id,
|
||||
)
|
||||
)
|
||||
else:
|
||||
if not prompt:
|
||||
console = Console()
|
||||
console.print("[red]Error: Prompt required for one-shot mode[/red]")
|
||||
console.print("Use --interactive for interactive session")
|
||||
sys.exit(1)
|
||||
|
||||
asyncio.run(
|
||||
run_one_shot_query(
|
||||
prompt=prompt,
|
||||
model=full_model,
|
||||
working_dir=working_dir,
|
||||
allowed_tools=allowed_tools,
|
||||
session_id=session_id,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
247
skills/adw-bootstrap/reference/enhanced/adws/adw_slash_command.py
Executable file
247
skills/adw-bootstrap/reference/enhanced/adws/adw_slash_command.py
Executable file
@@ -0,0 +1,247 @@
|
||||
#!/usr/bin/env -S uv run --script
|
||||
# /// script
|
||||
# requires-python = ">=3.10"
|
||||
# dependencies = [
|
||||
# "pydantic",
|
||||
# "python-dotenv",
|
||||
# "click",
|
||||
# "rich",
|
||||
# ]
|
||||
# ///
|
||||
"""
|
||||
Run Claude Code slash commands from the command line.
|
||||
|
||||
Usage:
|
||||
# Method 1: Direct execution (requires uv)
|
||||
./adws/adw_slash_command.py /chore "Update documentation"
|
||||
|
||||
# Method 2: Using uv run
|
||||
uv run adws/adw_slash_command.py /implement specs/<name-of-spec>.md
|
||||
|
||||
uv run adws/adw_slash_command.py /start
|
||||
|
||||
|
||||
Examples:
|
||||
# Run a slash command
|
||||
./adws/adw_slash_command.py /chore "Add logging to agent.py"
|
||||
|
||||
# Run with specific model
|
||||
./adws/adw_slash_command.py /implement plan.md --model opus
|
||||
|
||||
# Run from a different working directory
|
||||
./adws/adw_slash_command.py /test --working-dir /path/to/project
|
||||
|
||||
# Use custom agent name
|
||||
./adws/adw_slash_command.py /review --agent-name reviewer
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
from pathlib import Path
|
||||
import click
|
||||
from rich.console import Console
|
||||
from rich.panel import Panel
|
||||
from rich.table import Table
|
||||
|
||||
# Add the adw_modules directory to the path so we can import agent
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "adw_modules"))
|
||||
|
||||
from agent import (
|
||||
AgentTemplateRequest,
|
||||
AgentPromptResponse,
|
||||
execute_template,
|
||||
generate_short_id,
|
||||
)
|
||||
|
||||
# Output file name constants
|
||||
OUTPUT_JSONL = "cc_raw_output.jsonl"
|
||||
OUTPUT_JSON = "cc_raw_output.json"
|
||||
FINAL_OBJECT_JSON = "cc_final_object.json"
|
||||
SUMMARY_JSON = "custom_summary_output.json"
|
||||
|
||||
|
||||
@click.command()
|
||||
@click.argument("slash_command", required=True)
|
||||
@click.argument("args", nargs=-1) # Accept multiple optional arguments
|
||||
@click.option(
|
||||
"--model",
|
||||
type=click.Choice(["sonnet", "opus", "haiku"]),
|
||||
default="sonnet",
|
||||
help="Claude model to use (sonnet=balanced, opus=max intelligence, haiku=fast & economical)",
|
||||
)
|
||||
@click.option(
|
||||
"--working-dir",
|
||||
type=click.Path(exists=True, file_okay=False, dir_okay=True, resolve_path=True),
|
||||
help="Working directory for command execution (default: current directory)",
|
||||
)
|
||||
@click.option(
|
||||
"--agent-name",
|
||||
default="executor",
|
||||
help="Agent name for tracking (default: executor)",
|
||||
)
|
||||
def main(
|
||||
slash_command: str,
|
||||
args: tuple,
|
||||
model: str,
|
||||
working_dir: str,
|
||||
agent_name: str,
|
||||
):
|
||||
"""Run Claude Code slash commands from the command line."""
|
||||
console = Console()
|
||||
|
||||
# Generate a unique ID for this execution
|
||||
adw_id = generate_short_id()
|
||||
|
||||
# Use current directory if no working directory specified
|
||||
if not working_dir:
|
||||
working_dir = os.getcwd()
|
||||
|
||||
# Create the template request
|
||||
request = AgentTemplateRequest(
|
||||
agent_name=agent_name,
|
||||
slash_command=slash_command,
|
||||
args=list(args), # Convert tuple to list
|
||||
adw_id=adw_id,
|
||||
model=model,
|
||||
working_dir=working_dir,
|
||||
)
|
||||
|
||||
# Create execution info table
|
||||
info_table = Table(show_header=False, box=None, padding=(0, 1))
|
||||
info_table.add_column(style="bold cyan")
|
||||
info_table.add_column()
|
||||
|
||||
info_table.add_row("ADW ID", adw_id)
|
||||
info_table.add_row("ADW Name", "adw_slash_command")
|
||||
info_table.add_row("Command", slash_command)
|
||||
info_table.add_row("Args", " ".join(args) if args else "(none)")
|
||||
info_table.add_row("Model", model)
|
||||
info_table.add_row("Working Dir", working_dir)
|
||||
|
||||
console.print(
|
||||
Panel(
|
||||
info_table,
|
||||
title="[bold blue]🚀 Inputs[/bold blue]",
|
||||
border_style="blue",
|
||||
)
|
||||
)
|
||||
console.print()
|
||||
|
||||
try:
|
||||
# Execute the slash command
|
||||
with console.status("[bold yellow]Executing command...[/bold yellow]"):
|
||||
response = execute_template(request)
|
||||
|
||||
# Display the result
|
||||
if response.success:
|
||||
# Success panel
|
||||
result_panel = Panel(
|
||||
response.output,
|
||||
title="[bold green]✅ Success[/bold green]",
|
||||
border_style="green",
|
||||
padding=(1, 2),
|
||||
)
|
||||
console.print(result_panel)
|
||||
|
||||
if response.session_id:
|
||||
console.print(
|
||||
f"\n[bold cyan]Session ID:[/bold cyan] {response.session_id}"
|
||||
)
|
||||
else:
|
||||
# Error panel
|
||||
error_panel = Panel(
|
||||
response.output,
|
||||
title="[bold red]❌ Failed[/bold red]",
|
||||
border_style="red",
|
||||
padding=(1, 2),
|
||||
)
|
||||
console.print(error_panel)
|
||||
|
||||
if response.retry_code != "none":
|
||||
console.print(
|
||||
f"\n[bold yellow]Retry code:[/bold yellow] {response.retry_code}"
|
||||
)
|
||||
|
||||
# Show output file info
|
||||
console.print()
|
||||
|
||||
# Output files are in agents/<adw_id>/<agent_name>/
|
||||
output_dir = f"./agents/{adw_id}/{agent_name}"
|
||||
|
||||
# Create the simple JSON summary file
|
||||
simple_json_output = f"{output_dir}/{SUMMARY_JSON}"
|
||||
|
||||
# Determine the template file path
|
||||
command_name = slash_command.lstrip("/") # Remove leading slash
|
||||
path_to_slash_command_prompt = f".claude/commands/{command_name}.md"
|
||||
|
||||
with open(simple_json_output, "w") as f:
|
||||
json.dump(
|
||||
{
|
||||
"adw_id": adw_id,
|
||||
"slash_command": slash_command,
|
||||
"args": list(args),
|
||||
"path_to_slash_command_prompt": path_to_slash_command_prompt,
|
||||
"model": model,
|
||||
"working_dir": working_dir,
|
||||
"success": response.success,
|
||||
"session_id": response.session_id,
|
||||
"retry_code": response.retry_code,
|
||||
"output": response.output,
|
||||
},
|
||||
f,
|
||||
indent=2,
|
||||
)
|
||||
|
||||
# Files saved panel
|
||||
files_table = Table(show_header=True, box=None)
|
||||
files_table.add_column("File Type", style="bold cyan")
|
||||
files_table.add_column("Path", style="dim")
|
||||
files_table.add_column("Description", style="italic")
|
||||
|
||||
files_table.add_row(
|
||||
"JSONL Stream",
|
||||
f"{output_dir}/{OUTPUT_JSONL}",
|
||||
"Raw streaming output from Claude Code",
|
||||
)
|
||||
files_table.add_row(
|
||||
"JSON Array",
|
||||
f"{output_dir}/{OUTPUT_JSON}",
|
||||
"All messages as a JSON array",
|
||||
)
|
||||
files_table.add_row(
|
||||
"Final Object",
|
||||
f"{output_dir}/{FINAL_OBJECT_JSON}",
|
||||
"Last message entry (final result)",
|
||||
)
|
||||
files_table.add_row(
|
||||
"Summary",
|
||||
simple_json_output,
|
||||
"High-level execution summary with metadata",
|
||||
)
|
||||
|
||||
console.print(
|
||||
Panel(
|
||||
files_table,
|
||||
title="[bold blue]📄 Output Files[/bold blue]",
|
||||
border_style="blue",
|
||||
)
|
||||
)
|
||||
|
||||
# Exit with appropriate code
|
||||
sys.exit(0 if response.success else 1)
|
||||
|
||||
except Exception as e:
|
||||
console.print(
|
||||
Panel(
|
||||
f"[bold red]{str(e)}[/bold red]",
|
||||
title="[bold red]❌ Unexpected Error[/bold red]",
|
||||
border_style="red",
|
||||
)
|
||||
)
|
||||
sys.exit(2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
109
skills/adw-bootstrap/reference/enhanced/commands/feature.md
Normal file
109
skills/adw-bootstrap/reference/enhanced/commands/feature.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# Feature Planning
|
||||
|
||||
Create a plan to implement the feature using the specified markdown `Plan Format`. Research the codebase and create a thorough plan.
|
||||
|
||||
## Variables
|
||||
adw_id: $1
|
||||
prompt: $2
|
||||
|
||||
## Instructions
|
||||
|
||||
- If the adw_id or prompt is not provided, stop and ask the user to provide them.
|
||||
- Create a plan to implement the feature described in the `prompt`
|
||||
- The plan should be comprehensive, well-designed, and follow existing patterns
|
||||
- Create the plan in the `specs/` directory with filename: `feature-{adw_id}-{descriptive-name}.md`
|
||||
- Replace `{descriptive-name}` with a short, descriptive name based on the feature (e.g., "add-agent-logging", "implement-retry-logic", "create-workflow-api")
|
||||
- Research the codebase starting with `README.md`
|
||||
- Replace every <placeholder> in the `Plan Format` with the requested value
|
||||
- Use your reasoning model: THINK HARD about the feature requirements, design, and implementation approach
|
||||
- Follow existing patterns and conventions in the codebase
|
||||
- Design for extensibility and maintainability
|
||||
|
||||
## Codebase Structure
|
||||
|
||||
- `README.md` - Project overview and instructions (start here)
|
||||
- `adws/` - AI Developer Workflow scripts and modules
|
||||
- `apps/` - Application layer you'll be working in
|
||||
- `.claude/commands/` - Claude command templates
|
||||
- `specs/` - Specification and plan documents
|
||||
|
||||
## Plan Format
|
||||
|
||||
```md
|
||||
# Feature: <feature name>
|
||||
|
||||
## Metadata
|
||||
adw_id: `{adw_id}`
|
||||
prompt: `{prompt}`
|
||||
|
||||
## Feature Description
|
||||
<describe the feature in detail, including its purpose and value to users>
|
||||
|
||||
## User Story
|
||||
As a <type of user>
|
||||
I want to <action/goal>
|
||||
So that <benefit/value>
|
||||
|
||||
## Problem Statement
|
||||
<clearly define the specific problem or opportunity this feature addresses>
|
||||
|
||||
## Solution Statement
|
||||
<describe the proposed solution approach and how it solves the problem>
|
||||
|
||||
## Relevant Files
|
||||
Use these files to implement the feature:
|
||||
|
||||
<list files relevant to the feature with bullet points explaining why. Include new files to be created under an h3 'New Files' section if needed>
|
||||
|
||||
## Implementation Plan
|
||||
### Phase 1: Foundation
|
||||
<describe the foundational work needed before implementing the main feature>
|
||||
|
||||
### Phase 2: Core Implementation
|
||||
<describe the main implementation work for the feature>
|
||||
|
||||
### Phase 3: Integration
|
||||
<describe how the feature will integrate with existing functionality>
|
||||
|
||||
## Step by Step Tasks
|
||||
IMPORTANT: Execute every step in order, top to bottom.
|
||||
|
||||
<list step by step tasks as h3 headers with bullet points. Start with foundational changes then move to specific changes. Include creating tests throughout the implementation process>
|
||||
|
||||
### 1. <First Task Name>
|
||||
- <specific action>
|
||||
- <specific action>
|
||||
|
||||
### 2. <Second Task Name>
|
||||
- <specific action>
|
||||
- <specific action>
|
||||
|
||||
<continue with additional tasks as needed>
|
||||
|
||||
## Testing Strategy
|
||||
### Unit Tests
|
||||
<describe unit tests needed for the feature>
|
||||
|
||||
### Edge Cases
|
||||
<list edge cases that need to be tested>
|
||||
|
||||
## Acceptance Criteria
|
||||
<list specific, measurable criteria that must be met for the feature to be considered complete>
|
||||
|
||||
## Validation Commands
|
||||
Execute these commands to validate the feature is complete:
|
||||
|
||||
<list specific commands to validate the work. Be precise about what to run>
|
||||
- Example: `uv run python -m py_compile apps/*.py` - Test to ensure the code compiles
|
||||
|
||||
|
||||
## Notes
|
||||
<optional additional context, future considerations, or dependencies. If new libraries are needed, specify using `uv add`>
|
||||
```
|
||||
|
||||
## Feature
|
||||
Use the feature description from the `prompt` variable.
|
||||
|
||||
## Report
|
||||
|
||||
Return the path to the plan file created.
|
||||
12
skills/adw-bootstrap/reference/enhanced/commands/prime.md
Normal file
12
skills/adw-bootstrap/reference/enhanced/commands/prime.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Prime
|
||||
Execute the `Run`, `Read` and `Report` sections to understand the codebase then summarize your understanding.
|
||||
|
||||
## Run
|
||||
git ls-files
|
||||
|
||||
## Read
|
||||
README.md
|
||||
adws/README.md
|
||||
|
||||
## Report
|
||||
Summarize your understanding of the codebase.
|
||||
@@ -0,0 +1,9 @@
|
||||
# Start your applications
|
||||
|
||||
## Instructions
|
||||
|
||||
- Run the commands in the `Run` section below top to bottom.
|
||||
|
||||
## Run
|
||||
uv run apps/main.py
|
||||
bun run apps/main.ts
|
||||
Reference in New Issue
Block a user