Source code for pyralph.cli

#!/usr/bin/env python3
"""CLI module for Ralph.

This module contains the CLI entry point (main) and version functions that handle
command-line argument parsing and application startup.
"""
import sys
import argparse

from .logger import Logger
from .agents import list_agents


[docs] def get_version() -> str: """Get the version string from package metadata. Returns: Version string from package metadata, or 'unknown' if not found. """ try: from importlib.metadata import version return version("pyralph") except Exception: pass return "unknown"
[docs] def main() -> None: """Entry point for the ralph CLI.""" agent = list_agents()[0] parser = argparse.ArgumentParser(description="Ralph - Autonomous Software Development Agent", epilog="Examples: ralph | ralph architect | ralph -y execute | ralph -vvv --no-emoji execute", formatter_class=argparse.RawDescriptionHelpFormatter) parser.add_argument("phase", choices=["architect", "planner", "execute", "all"], default="all", nargs="?", help="Phase to run") parser.add_argument("--version", action="version", version=f"Ralph {get_version()}") parser.add_argument("--accept-all", "-y", action="store_true", help="Skip prompts") parser.add_argument("-v", "--verbose", action="count", default=0, help="Increase verbosity (-v, -vv, -vvv)") parser.add_argument("--quiet", "-q", action="store_true", help="Suppress non-essential output") parser.add_argument("--no-color", action="store_true", help="Disable colored output") parser.add_argument("--no-emoji", action="store_true", help="Replace emojis with text equivalents") parser.add_argument("--no-hooks", action="store_true", help="Disable hook execution") parser.add_argument("--hooks", nargs="+", metavar="NAME", help="Enable only specified hooks by name") parser.add_argument("--agent", choices=list_agents(), default=agent, help=f"Agent (default: {agent})") # Intent and input flags for non-interactive runs parser.add_argument("--intent", type=str, metavar="TEXT", help="Provide intent inline (what to build)") parser.add_argument("--intent-file", type=str, metavar="FILE", help="Load intent from a file") parser.add_argument("--enhance-intent", action="store_true", help="Process intent through enhancement agent before architect phase") parser.add_argument("--no-enhance-intent", action="store_true", help="Disable intent enhancement (overrides --enhance-all)") parser.add_argument("--enhance-intent-strict", action="store_true", help="Exit on enhancement failure instead of falling back to original intent") parser.add_argument("--prompt-file", type=str, metavar="FILE", help="Override prompt.md path for user context") # Architect control flags for context generation parser.add_argument("--tree-depth", type=int, default=2, metavar="N", help="File tree depth for architect (default: 2)") parser.add_argument("--tree-ignore", nargs="+", metavar="PATTERN", help="Patterns to ignore in file tree (default: node_modules, venv, .git, .ralph, __pycache__)") # Execution and verification flags for task control parser.add_argument("--test-cmd", type=str, metavar="CMD", help="Override test command for verification") parser.add_argument("--skip-verify", action="store_true", help="Skip verification step after task execution") parser.add_argument("--retries", type=int, metavar="N", help="Override max retries per task (default: 3)") parser.add_argument("--timeout", type=int, metavar="SECS", help="Override agent timeout in seconds (default: 600)") parser.add_argument("--only", nargs="+", metavar="TASK_ID", help="Execute only specified task IDs") parser.add_argument("--except", dest="except_tasks", nargs="+", metavar="TASK_ID", help="Skip specified task IDs") parser.add_argument("--resume", type=str, metavar="TASK_ID", help="Resume execution from a specific task ID") # Context control flags for file filtering parser.add_argument("--include", nargs="+", metavar="PATTERN", help="Include only files matching these glob patterns in context") parser.add_argument("--exclude", nargs="+", metavar="PATTERN", help="Exclude files matching these glob patterns from context") parser.add_argument("--context-limit", type=int, metavar="N", help="Limit maximum number of context files considered") # Model and prompting flags for LLM customization parser.add_argument("--model", type=str, metavar="MODEL", help="Model identifier for LLM requests (e.g., claude-3-opus)") parser.add_argument("--temperature", type=float, metavar="TEMP", help="Sampling temperature (0.0-1.0) for response generation") parser.add_argument("--max-tokens", type=int, metavar="N", help="Maximum number of tokens in the LLM response") parser.add_argument("--seed", type=int, metavar="N", help="Random seed for reproducible outputs") # I/O, logging and output flags parser.add_argument("--log-file", type=str, metavar="FILE", help="Redirect log output to specified file") parser.add_argument("--log-level", type=str, choices=["debug", "info", "warn", "error"], metavar="LEVEL", help="Set log level (debug, info, warn, error)") # Output format flags (mutually exclusive) output_format_group = parser.add_mutually_exclusive_group() output_format_group.add_argument("--json", dest="json_output", action="store_true", help="Output in JSON format") output_format_group.add_argument("--ndjson", dest="ndjson_output", action="store_true", help="Output in newline-delimited JSON format") # PRD output flags parser.add_argument("--print-prd", action="store_true", help="Print PRD contents and exit without executing") parser.add_argument("--prd-out", type=str, metavar="FILE", help="Export PRD to specified file") # Archive control flag parser.add_argument("--no-archive", action="store_false", dest="archive_enabled", default=True, help="Skip PRD archival after execution") # Headless operation flags for CI/CD pipelines parser.add_argument("--non-interactive", action="store_true", help="Disable all interactive prompts (fails if input required)") parser.add_argument("--ci", action="store_true", help="CI mode: enables --non-interactive --no-color --no-emoji --json") parser.add_argument("--status-check", action="store_true", help="Check PRD status and exit with code (0=complete, 1=incomplete, 2=no PRD)") # Extensibility and hook flags for custom commands and validators parser.add_argument("--pre", nargs="+", metavar="CMD", help="Shell command(s) to run before each phase (aborts on failure)") parser.add_argument("--post", nargs="+", metavar="CMD", help="Shell command(s) to run after each phase (receives RALPH_PHASE, RALPH_SUCCESS env vars)") parser.add_argument("--plugin", nargs="+", metavar="PATH", help="Load plugin(s) from Python file or directory path") # Safety and privacy flags for protecting sensitive data parser.add_argument("--redact", nargs="+", metavar="PATTERN", help="Regex patterns to redact from logs (e.g., API keys, passwords)") parser.add_argument("--redact-file", type=str, metavar="FILE", help="Load redaction patterns from file (one pattern per line)") parser.add_argument("--no-log-prompts", action="store_true", help="Do not log prompts to log file (protects sensitive input)") parser.add_argument("--no-log-responses", action="store_true", help="Do not log responses to log file (protects sensitive output)") # PRD and story control flags for validation and annotation parser.add_argument("--schema", type=str, metavar="FILE", help="Validate generated PRD against a JSON schema file") parser.add_argument("--min-criteria", type=int, metavar="N", help="Require at least N acceptance criteria per user story") parser.add_argument("--label", nargs="+", metavar="KEY=VAL", help="Add custom labels to PRD (format: key=value or just key)") parser.add_argument("--revise-prd", action="store_true", help="Pass PRD through revision agent for quality improvements before planner phase") parser.add_argument("--no-revise-prd", action="store_true", help="Disable PRD revision (overrides --enhance-all)") # Enhancement combination flag parser.add_argument("--enhance-all", action="store_true", help="Enable all enhancement features (--enhance-intent, --revise-prd). Individual --no-* flags can override specific features.") args = parser.parse_args() # Handle --ci flag: apply CI defaults before other options # --ci implies: --non-interactive --no-color --no-emoji --json ci_mode = args.ci non_interactive = args.non_interactive or ci_mode # Configure logger settings Logger.set_verbosity(args.verbose) Logger.set_quiet(args.quiet) Logger.set_no_emoji(args.no_emoji or ci_mode) Logger.set_non_interactive(non_interactive) # Handle color: --no-color disables, --ci disables (default: colors enabled) if args.no_color or ci_mode: Logger.set_no_color(True) # Configure I/O and output format settings if args.log_file: Logger.set_log_file(args.log_file) if args.log_level: Logger.set_log_level(args.log_level) # --ci enables JSON output unless --ndjson is explicitly specified # NDJSON takes precedence over CI's default JSON output if args.ndjson_output: Logger.set_ndjson_output(True) elif args.json_output or ci_mode: Logger.set_json_output(True) # Configure safety and privacy flags if args.redact: Logger.set_redact_patterns(args.redact) if args.redact_file: Logger.add_redact_patterns_from_file(args.redact_file) if args.no_log_prompts: Logger.set_no_log_prompts(True) if args.no_log_responses: Logger.set_no_log_responses(True) # Determine hook configuration enable_hooks = not args.no_hooks enabled_hook_names = args.hooks if args.hooks else None # Validate mutually exclusive intent options if args.intent and args.intent_file: Logger.error("Cannot use both --intent and --intent-file together.") sys.exit(1) # Handle --enhance-all flag: apply enhancement defaults with explicit overrides # --enhance-all enables: --enhance-intent, --revise-prd # Individual --no-* flags can override specific features enhance_all = args.enhance_all # Calculate effective enhancement flag values # Explicit positive flags or --enhance-all enable the feature # Explicit negative flags disable the feature (override --enhance-all) enhance_intent = args.enhance_intent or (enhance_all and not args.no_enhance_intent) revise_prd = args.revise_prd or (enhance_all and not args.no_revise_prd) # Log which enhancement features are actually enabled when --enhance-all is used if enhance_all: enabled_features = [] disabled_features = [] if enhance_intent: enabled_features.append("intent enhancement") else: disabled_features.append("intent enhancement") if revise_prd: enabled_features.append("PRD revision") else: disabled_features.append("PRD revision") if enabled_features: Logger.info(f"Enhancement features enabled: {', '.join(enabled_features)}") if disabled_features: Logger.info(f"Enhancement features disabled by explicit flags: {', '.join(disabled_features)}") # Late import to allow tests to patch ralph.RalphOrchestrator import pyralph pyralph.RalphOrchestrator( agent_name=args.agent, enable_hooks=enable_hooks, enabled_hook_names=enabled_hook_names, intent=args.intent, intent_file=args.intent_file, prompt_file=args.prompt_file, enhance_intent=enhance_intent, enhance_intent_strict=args.enhance_intent_strict, tree_depth=args.tree_depth, tree_ignore=args.tree_ignore, test_cmd=args.test_cmd, skip_verify=args.skip_verify, retries=args.retries, timeout=args.timeout, only=args.only, except_tasks=args.except_tasks, resume=args.resume, include=args.include, exclude=args.exclude, context_limit=args.context_limit, model=args.model, temperature=args.temperature, max_tokens=args.max_tokens, seed=args.seed, log_file=args.log_file, log_level=args.log_level, json_output=args.json_output, ndjson_output=args.ndjson_output, print_prd=args.print_prd, prd_out=args.prd_out, archive=args.archive_enabled, non_interactive=non_interactive, ci=ci_mode, status_check=args.status_check, pre=args.pre, post=args.post, plugin=args.plugin, schema=args.schema, min_criteria=args.min_criteria, label=args.label, revise_prd=revise_prd ).start(phase=args.phase, accept_all=args.accept_all)