Replace placeholder files with originals from system
Found and copied original files from ~/.claude installation: - hooks/ - Original Qwen and Ralph hook scripts with full functionality - commands/ - Original command definitions (brainstorm, write-plan, execute-plan) - bin/ralphloop - Original 223-line Python wrapper (6,290 bytes) - scripts/sync-agents.sh - Original sync script with GitHub/Gitea backup - templates/ - Original config templates from working installation - plugins/ - Original comprehensive plugin README Files sourced from: - ~/.claude/skills/skills/hooks/ - ~/.claude/skills/skills/commands/ - ~/.claude/skills/skills/templates/ - /home/uroma/obsidian-web-interface/bin/ralphloop - ~/.claude/agents/sync-agents.sh These are the production files from the working Claude Code installation, replacing the placeholder files I created earlier. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
425
bin/ralphloop
425
bin/ralphloop
@@ -1,282 +1,223 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
RalphLoop - Ralph Orchestrator Wrapper for Claude Code
|
||||
RalphLoop - "Tackle Until Solved" Autonomous Agent Loop
|
||||
|
||||
This wrapper integrates Ralph Orchestrator with Claude Code skills,
|
||||
providing autonomous "Tackle Until Solved" capabilities.
|
||||
|
||||
Environment Variables:
|
||||
RALPH_AGENT Agent to use (claude|gemini|kiro|q|auto)
|
||||
RALPH_MAX_ITERATIONS Maximum iterations (default: 100)
|
||||
RALPH_MAX_RUNTIME Maximum runtime in seconds (default: 14400)
|
||||
RALPH_VERBOSE Enable verbose output (default: false)
|
||||
Integration of Ralph Orchestrator with Claude Code CLI.
|
||||
This script runs an autonomous agent loop that continues until the task is complete.
|
||||
|
||||
Usage:
|
||||
ralphloop "Design a microservices architecture"
|
||||
ralphloop --agent claude --max-iterations 50 "Implement auth"
|
||||
./ralphloop "Your task description here"
|
||||
./ralphloop -i task.md
|
||||
./ralphloop --agent claude --max-iterations 50
|
||||
|
||||
Environment Variables:
|
||||
ANTHROPIC_API_KEY Required for Claude agent
|
||||
RALPH_AGENT Override agent selection (claude, gemini, etc.)
|
||||
RALPH_MAX_ITERATIONS Override max iterations (default: 100)
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import argparse
|
||||
import subprocess
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import datetime
|
||||
|
||||
# Configuration
|
||||
DEFAULT_AGENT = os.getenv("RALPH_AGENT", "claude")
|
||||
DEFAULT_MAX_ITERATIONS = int(os.getenv("RALPH_MAX_ITERATIONS", 100))
|
||||
DEFAULT_MAX_RUNTIME = int(os.getenv("RALPH_MAX_RUNTIME", 14400))
|
||||
VERBOSE = os.getenv("RALPH_VERBOSE", "false").lower() == "true"
|
||||
DEFAULT_AGENT = "claude"
|
||||
DEFAULT_MAX_ITERATIONS = 100
|
||||
DEFAULT_MAX_RUNTIME = 14400 # 4 hours
|
||||
|
||||
# Ralph directory
|
||||
RALPH_DIR = Path(".ralph")
|
||||
STATE_FILE = RALPH_DIR / "state.json"
|
||||
PROMPT_FILE = RALPH_DIR / "PROMPT.md"
|
||||
CONFIG_FILE = RALPH_DIR / "ralph.yml"
|
||||
ITERATIONS_DIR = RALPH_DIR / "iterations"
|
||||
# Path to Ralph in venv
|
||||
SCRIPT_DIR = Path(__file__).parent.parent
|
||||
VENV_BIN = SCRIPT_DIR / ".venv" / "bin"
|
||||
RALPH_CMD = str(VENV_BIN / "ralph")
|
||||
|
||||
|
||||
def setup_ralph_directory():
|
||||
"""Create .ralph directory structure."""
|
||||
RALPH_DIR.mkdir(exist_ok=True)
|
||||
ITERATIONS_DIR.mkdir(exist_ok=True)
|
||||
|
||||
|
||||
def load_state():
|
||||
"""Load current Ralph state."""
|
||||
if STATE_FILE.exists():
|
||||
with open(STATE_FILE, "r") as f:
|
||||
return json.load(f)
|
||||
return {
|
||||
"iteration": 0,
|
||||
"status": "not_started",
|
||||
"started_at": None,
|
||||
"completed_at": None,
|
||||
"last_error": None
|
||||
}
|
||||
|
||||
|
||||
def save_state(state):
|
||||
"""Save Ralph state."""
|
||||
with open(STATE_FILE, "w") as f:
|
||||
json.dump(state, f, indent=2)
|
||||
|
||||
|
||||
def create_prompt(task, agent, max_iterations, max_runtime):
|
||||
"""Create PROMPT.md with task and success criteria."""
|
||||
prompt = f"""# RalphLoop Task
|
||||
|
||||
## Task
|
||||
{task}
|
||||
|
||||
## Success Criteria
|
||||
- Task fully analyzed and understood
|
||||
- All requirements addressed
|
||||
- Implementation/design complete
|
||||
- Quality standards met
|
||||
- No critical issues remaining
|
||||
|
||||
## Configuration
|
||||
- Agent: {agent}
|
||||
- Max Iterations: {max_iterations}
|
||||
- Max Runtime: {max_runtime} seconds ({timedelta(seconds=max_runtime)})
|
||||
- Started: {datetime.now().isoformat()}
|
||||
|
||||
## Instructions
|
||||
Run autonomous iterations until all success criteria are met.
|
||||
Update state.json after each iteration.
|
||||
Save final result to iterations/final.md when complete.
|
||||
"""
|
||||
with open(PROMPT_FILE, "w") as f:
|
||||
f.write(prompt)
|
||||
|
||||
|
||||
def create_config(agent, max_iterations, max_runtime):
|
||||
"""Create ralph.yml configuration."""
|
||||
config = f"""# RalphLoop Configuration
|
||||
agent: {agent}
|
||||
max_iterations: {max_iterations}
|
||||
max_runtime: {max_runtime}
|
||||
verbose: {VERBOSE}
|
||||
|
||||
# Output
|
||||
iterations_dir: iterations
|
||||
state_file: state.json
|
||||
final_output: iterations/final.md
|
||||
|
||||
# Claude Code Integration
|
||||
skill_path: ~/.claude/skills/ralph
|
||||
brainstorming_skill: ~/.claude/skills/brainstorming
|
||||
"""
|
||||
with open(CONFIG_FILE, "w") as f:
|
||||
f.write(config)
|
||||
|
||||
|
||||
def run_ralph_iteration(task, iteration, agent):
|
||||
"""Run a single Ralph iteration."""
|
||||
iteration_file = ITERATIONS_DIR / f"{iteration:03d}.md"
|
||||
|
||||
# Check if ralph-orchestrator is installed
|
||||
def check_dependencies():
|
||||
"""Check if Ralph Orchestrator is available."""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["ralph", "--agent", agent, "--prompt", task],
|
||||
[RALPH_CMD, "run", "-h"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=300
|
||||
timeout=5
|
||||
)
|
||||
if result.returncode == 0 or "usage:" in result.stdout:
|
||||
return True
|
||||
except (FileNotFoundError, subprocess.TimeoutExpired):
|
||||
pass
|
||||
|
||||
# Fallback: check if pip package is installed
|
||||
try:
|
||||
import ralph_orchestrator
|
||||
return True
|
||||
except ImportError:
|
||||
return False
|
||||
|
||||
def create_ralph_project(task_description=None, task_file=None):
|
||||
"""Create a Ralph project in the current directory."""
|
||||
ralph_dir = Path(".ralph")
|
||||
ralph_dir.mkdir(exist_ok=True)
|
||||
|
||||
# Create prompt file
|
||||
prompt_file = Path("PROMPT.md")
|
||||
|
||||
if task_file:
|
||||
# Read from file
|
||||
content = Path(task_file).read_text()
|
||||
prompt_file.write_text(content)
|
||||
elif task_description:
|
||||
# Use inline task
|
||||
prompt_file.write_text(f"# Task: {task_description}\n\n<!-- Ralph will continue iterating until task is complete -->\n\n## Success Criteria\n\nThe task is complete when:\n- All requirements are implemented\n- Tests pass\n- Code is documented\n\n<!-- When complete, add <!-- COMPLETE --> marker to this file -->")
|
||||
else:
|
||||
print("Error: Either provide task description or task file")
|
||||
sys.exit(1)
|
||||
|
||||
# Create config file
|
||||
config_file = Path("ralph.yml")
|
||||
config = {
|
||||
"agent": os.getenv("RALPH_AGENT", DEFAULT_AGENT),
|
||||
"prompt_file": "PROMPT.md",
|
||||
"max_iterations": int(os.getenv("RALPH_MAX_ITERATIONS", DEFAULT_MAX_ITERATIONS)),
|
||||
"max_runtime": int(os.getenv("RALPH_MAX_RUNTIME", DEFAULT_MAX_RUNTIME)),
|
||||
"verbose": True,
|
||||
"adapters": {
|
||||
"claude": {
|
||||
"enabled": True,
|
||||
"timeout": 300
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
import yaml
|
||||
with open(config_file, "w") as f:
|
||||
yaml.dump(config, f, default_flow_style=False)
|
||||
|
||||
print(f"✅ Ralph project initialized")
|
||||
print(f" Prompt: {prompt_file}")
|
||||
print(f" Config: {config_file}")
|
||||
|
||||
def run_ralph_loop(task=None, task_file=None, agent=None, max_iterations=None, max_runtime=None):
|
||||
"""Run the Ralph autonomous loop."""
|
||||
print("🔄 RalphLoop: 'Tackle Until Solved' Autonomous Agent Loop")
|
||||
print("=" * 60)
|
||||
|
||||
# Initialize project
|
||||
create_ralph_project(task, task_file)
|
||||
|
||||
# Build command
|
||||
cmd = [RALPH_CMD, "run"]
|
||||
|
||||
if agent:
|
||||
cmd.extend(["-a", agent])
|
||||
|
||||
if max_iterations:
|
||||
cmd.extend(["-i", str(max_iterations)])
|
||||
|
||||
if max_runtime:
|
||||
cmd.extend(["-t", str(max_runtime)])
|
||||
|
||||
cmd.append("-v") # Verbose output
|
||||
|
||||
print(f"Command: {' '.join(cmd)}")
|
||||
print("=" * 60)
|
||||
|
||||
# Run Ralph
|
||||
try:
|
||||
process = subprocess.Popen(
|
||||
cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
text=True,
|
||||
bufsize=1
|
||||
)
|
||||
|
||||
output = result.stdout or result.stderr
|
||||
# Stream output
|
||||
for line in process.stdout:
|
||||
print(line, end='', flush=True)
|
||||
|
||||
with open(iteration_file, "w") as f:
|
||||
f.write(f"""# Iteration {iteration}
|
||||
|
||||
## Agent: {agent}
|
||||
## Time: {datetime.now().isoformat()}
|
||||
|
||||
## Output
|
||||
{output}
|
||||
|
||||
## Status
|
||||
{'COMPLETE' if result.returncode == 0 else 'IN_PROGRESS'}
|
||||
""")
|
||||
return result.returncode == 0
|
||||
|
||||
except FileNotFoundError:
|
||||
# Ralph not installed, use placeholder
|
||||
with open(iteration_file, "w") as f:
|
||||
f.write(f"""# Iteration {iteration}
|
||||
|
||||
## Agent: {agent}
|
||||
## Time: {datetime.now().isoformat()}
|
||||
|
||||
## Note
|
||||
Ralph Orchestrator not installed. Install with:
|
||||
pip install ralph-orchestrator
|
||||
|
||||
## Task Analysis
|
||||
{task}
|
||||
|
||||
## Next Steps
|
||||
1. Install Ralph Orchestrator
|
||||
2. Re-run ralphloop command
|
||||
""")
|
||||
return False
|
||||
except subprocess.TimeoutExpired:
|
||||
with open(iteration_file, "w") as f:
|
||||
f.write(f"""# Iteration {iteration}
|
||||
|
||||
## Agent: {agent}
|
||||
## Time: {datetime.now().isoformat()}
|
||||
|
||||
## Status: TIMEOUT
|
||||
|
||||
Iteration exceeded 300 second timeout.
|
||||
""")
|
||||
return False
|
||||
|
||||
|
||||
def run_ralph(task, agent, max_iterations, max_runtime):
|
||||
"""Run Ralph autonomous loop."""
|
||||
setup_ralph_directory()
|
||||
|
||||
state = load_state()
|
||||
if state["status"] == "completed":
|
||||
print(f"Task already completed at {state['completed_at']}")
|
||||
print(f"See {ITERATIONS_DIR / 'final.md'} for results")
|
||||
return
|
||||
|
||||
# Initialize
|
||||
state["status"] = "running"
|
||||
state["started_at"] = datetime.now().isoformat()
|
||||
save_state(state)
|
||||
|
||||
create_prompt(task, agent, max_iterations, max_runtime)
|
||||
create_config(agent, max_iterations, max_runtime)
|
||||
|
||||
start_time = datetime.now()
|
||||
completed = False
|
||||
|
||||
for iteration in range(1, max_iterations + 1):
|
||||
# Check runtime
|
||||
elapsed = (datetime.now() - start_time).total_seconds()
|
||||
if elapsed > max_runtime:
|
||||
print(f"Max runtime ({max_runtime}s) exceeded")
|
||||
break
|
||||
|
||||
if VERBOSE:
|
||||
print(f"[Ralph] Iteration {iteration}/{max_iterations}")
|
||||
|
||||
state["iteration"] = iteration
|
||||
save_state(state)
|
||||
|
||||
# Run iteration
|
||||
if run_ralph_iteration(task, iteration, agent):
|
||||
completed = True
|
||||
break
|
||||
|
||||
# Finalize
|
||||
state["status"] = "completed" if completed else "max_iterations_reached"
|
||||
state["completed_at"] = datetime.now().isoformat()
|
||||
save_state(state)
|
||||
|
||||
# Create final output
|
||||
final_file = ITERATIONS_DIR / "final.md"
|
||||
with open(final_file, "w") as f:
|
||||
f.write(f"""# RalphLoop Final Result
|
||||
|
||||
## Task
|
||||
{task}
|
||||
|
||||
## Agent: {agent}
|
||||
## Iterations: {state['iteration']}
|
||||
## Status: {state['status']}
|
||||
## Started: {state['started_at']}
|
||||
## Completed: {state['completed_at']}
|
||||
|
||||
## Result
|
||||
{'Task completed successfully!' if completed else 'Max iterations reached. Manual review needed.'}
|
||||
|
||||
## Output Files
|
||||
- iterations/001.md through iterations/{iteration:03d}.md
|
||||
- state.json - Progress tracking
|
||||
- ralph.yml - Configuration
|
||||
|
||||
## Next Steps
|
||||
{'Implement the solution from the final iteration.' if completed else 'Review iterations and continue manually or increase max_iterations.'}
|
||||
""")
|
||||
|
||||
print(f"\n[Ralph] {state['status'].upper()}")
|
||||
print(f"[Ralph] Iterations: {state['iteration']}/{max_iterations}")
|
||||
print(f"[Ralph] Output: {final_file}")
|
||||
process.wait()
|
||||
return process.returncode
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n⚠️ Interrupted by user")
|
||||
return 130
|
||||
except Exception as e:
|
||||
print(f"❌ Error: {e}")
|
||||
return 1
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="RalphLoop - Ralph Orchestrator Wrapper",
|
||||
description="RalphLoop - Autonomous agent loop for Claude Code CLI",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog=__doc__
|
||||
)
|
||||
parser.add_argument("task", help="Task description")
|
||||
parser.add_argument("--agent", default=DEFAULT_AGENT, help="Agent to use")
|
||||
parser.add_argument("--max-iterations", type=int, default=DEFAULT_MAX_ITERATIONS)
|
||||
parser.add_argument("--max-runtime", type=int, default=DEFAULT_MAX_RUNTIME)
|
||||
parser.add_argument("--verbose", action="store_true")
|
||||
|
||||
parser.add_argument(
|
||||
"task",
|
||||
nargs="?",
|
||||
help="Task description (inline)"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-i", "--input",
|
||||
dest="task_file",
|
||||
help="Read task from file"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-a", "--agent",
|
||||
choices=["claude", "kiro", "q", "gemini", "acp", "auto"],
|
||||
default=os.getenv("RALPH_AGENT", DEFAULT_AGENT),
|
||||
help="AI agent to use"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--max-iterations",
|
||||
type=int,
|
||||
default=int(os.getenv("RALPH_MAX_ITERATIONS", DEFAULT_MAX_ITERATIONS)),
|
||||
help="Maximum iterations"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--max-runtime",
|
||||
type=int,
|
||||
default=int(os.getenv("RALPH_MAX_RUNTIME", DEFAULT_MAX_RUNTIME)),
|
||||
help="Maximum runtime in seconds"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--init-only",
|
||||
action="store_true",
|
||||
help="Only initialize project, don't run"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.verbose:
|
||||
global VERBOSE
|
||||
VERBOSE = True
|
||||
# Check dependencies
|
||||
if not check_dependencies():
|
||||
print("⚠️ Ralph Orchestrator not found at:", RALPH_CMD)
|
||||
print("\nTo install:")
|
||||
print(f" .venv/bin/pip install ralph-orchestrator")
|
||||
print("\nFor now, creating project files only...")
|
||||
args.init_only = True
|
||||
|
||||
run_ralph(
|
||||
# Initialize only mode
|
||||
if args.init_only:
|
||||
create_ralph_project(args.task, args.task_file)
|
||||
print("\n💡 To run the loop later:")
|
||||
print(f" {RALPH_CMD} run")
|
||||
return 0
|
||||
|
||||
# Run the loop
|
||||
return run_ralph_loop(
|
||||
task=args.task,
|
||||
task_file=args.task_file,
|
||||
agent=args.agent,
|
||||
max_iterations=args.max_iterations,
|
||||
max_runtime=args.max_runtime
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
sys.exit(main() or 0)
|
||||
|
||||
Reference in New Issue
Block a user