An AI agent built to do Ralph loops - plan mode for planning and ralph mode for implementing.
1use rustagent::{config, logging, planning, ralph};
2
3use clap::{Parser, Subcommand};
4use std::path::PathBuf;
5
6#[derive(Parser)]
7#[command(name = "rustagent")]
8#[command(about = "A Rust-based AI agent for task execution", long_about = None)]
9struct Cli {
10 #[command(subcommand)]
11 command: Option<Commands>,
12}
13
14#[derive(Subcommand)]
15enum Commands {
16 /// Initialize a new agent specification
17 Init {
18 /// Directory to store the specification
19 #[arg(long)]
20 spec_dir: Option<String>,
21 },
22 /// Create an execution plan from a specification
23 Plan {
24 /// Directory containing the specification
25 #[arg(long)]
26 spec_dir: Option<String>,
27 },
28 /// Execute a plan
29 Run {
30 /// Path to the specification file
31 spec_file: String,
32 /// Maximum number of iterations
33 #[arg(long)]
34 max_iterations: Option<usize>,
35 },
36 /// Launch interactive TUI
37 Tui,
38}
39
40/// Find config file in standard locations
41fn find_config_path() -> anyhow::Result<PathBuf> {
42 // Try current directory
43 let local_config = PathBuf::from("rustagent.toml");
44 if local_config.exists() {
45 return Ok(local_config);
46 }
47
48 // Try XDG config directory
49 if let Some(config_dir) = dirs::config_dir() {
50 let xdg_config = config_dir.join("rustagent").join("config.toml");
51 if xdg_config.exists() {
52 return Ok(xdg_config);
53 }
54 }
55
56 // Try home directory
57 if let Some(home_dir) = dirs::home_dir() {
58 let home_config = home_dir.join(".rustagent").join("config.toml");
59 if home_config.exists() {
60 return Ok(home_config);
61 }
62 }
63
64 anyhow::bail!(
65 "Config file not found. Please create rustagent.toml in current directory or ~/.rustagent/config.toml"
66 )
67}
68
69#[tokio::main]
70async fn main() -> anyhow::Result<()> {
71 let _log_guard = logging::init_logging()?;
72
73 let cli = Cli::parse();
74
75 // Default to TUI if no command specified
76 let command = cli.command.unwrap_or(Commands::Tui);
77
78 match command {
79 Commands::Init { spec_dir } => {
80 let dir = spec_dir.clone().unwrap_or_else(|| "specs".to_string());
81
82 let spec_path = std::path::Path::new(&dir);
83 if !spec_path.exists() {
84 std::fs::create_dir_all(spec_path)?;
85 println!("Created spec directory: {}", dir);
86 } else {
87 println!("Spec directory already exists: {}", dir);
88 }
89
90 let config_path = std::path::Path::new("rustagent.toml");
91 if !config_path.exists() {
92 let template = include_str!("../rustagent.toml.example");
93 std::fs::write(config_path, template)?;
94 println!("Created config template: rustagent.toml");
95 println!("Please edit rustagent.toml to add your API keys.");
96 } else {
97 println!("Config file already exists: rustagent.toml");
98 }
99
100 println!("\nInitialization complete!");
101 println!("Next steps:");
102 println!(" 1. Edit rustagent.toml with your API keys");
103 println!(" 2. Run 'rustagent plan' to create a spec");
104 println!(" 3. Run 'rustagent run <spec-file>' to execute");
105 }
106 Commands::Plan { spec_dir } => {
107 // Load config from standard locations
108 let config_path = find_config_path()?;
109 let config = config::Config::load(&config_path)?;
110
111 // Use provided spec_dir or default from config
112 let dir = spec_dir
113 .clone()
114 .unwrap_or_else(|| config.rustagent.spec_dir.clone());
115
116 // Create and run planning agent
117 let mut agent = planning::PlanningAgent::new(config, dir)?;
118 agent.run().await?;
119 }
120 Commands::Run {
121 spec_file,
122 max_iterations,
123 } => {
124 // Load config from standard locations
125 let config_path = find_config_path()?;
126 let config = config::Config::load(&config_path)?;
127
128 // Create and run Ralph loop
129 let ralph = ralph::RalphLoop::new(config, spec_file.clone(), max_iterations)?;
130 ralph.run().await?;
131 }
132 Commands::Tui => {
133 let config_path = find_config_path()?;
134 let config = config::Config::load(&config_path)?;
135 let spec_dir = config.rustagent.spec_dir.clone();
136
137 use rustagent::tui::{self, agent_channel};
138
139 let mut terminal = tui::setup_terminal()?;
140 let (tx, mut rx) = agent_channel();
141 let mut app = tui::App::new(&spec_dir, tx, Some(config));
142
143 let result = tui::run(&mut terminal, &mut app, &mut rx).await;
144
145 tui::restore_terminal(&mut terminal)?;
146
147 result?;
148 }
149 }
150
151 Ok(())
152}