ralph_workflow/cli/
init.rs

1//! Configuration initialization handlers.
2//!
3//! This module handles the `--init`, `--init-global`, legacy `--init-legacy` flags,
4//! and `--init-prompt` flag for creating default agent configuration files
5//! and PROMPT.md from templates.
6
7use crate::agents::{AgentsConfigFile, ConfigInitResult};
8use crate::config::{unified_config_path, UnifiedConfig, UnifiedConfigInitResult};
9use crate::logger::Colors;
10use crate::templates::{get_template, list_templates};
11use std::fs;
12use std::path::Path;
13
14/// Handle the `--init-global` flag.
15///
16/// Creates a unified config file at `~/.config/ralph-workflow.toml` if it doesn't exist.
17/// This is the recommended way to configure Ralph globally.
18///
19/// # Arguments
20///
21/// * `colors` - Terminal color configuration for output
22///
23/// # Returns
24///
25/// Returns `Ok(true)` if the flag was handled (program should exit after),
26/// or an error if config creation failed.
27pub fn handle_init_global(colors: Colors) -> anyhow::Result<bool> {
28    let global_path = unified_config_path()
29        .ok_or_else(|| anyhow::anyhow!("Cannot determine config directory (no home directory)"))?;
30
31    match UnifiedConfig::ensure_config_exists() {
32        Ok(UnifiedConfigInitResult::Created) => {
33            println!(
34                "{}Created unified config: {}{}{}\n",
35                colors.green(),
36                colors.bold(),
37                global_path.display(),
38                colors.reset()
39            );
40            println!("This is the primary configuration file for Ralph.");
41            println!();
42            println!("Features:");
43            println!("  - General settings (verbosity, iterations, etc.)");
44            println!("  - CCS aliases for Claude Code Switch integration");
45            println!("  - Custom agent definitions");
46            println!("  - Agent chain configuration with fallbacks");
47            println!();
48            println!("Environment variables (RALPH_*) override these settings.");
49            Ok(true)
50        }
51        Ok(UnifiedConfigInitResult::AlreadyExists) => {
52            println!(
53                "{}Unified config already exists:{} {}",
54                colors.yellow(),
55                colors.reset(),
56                global_path.display()
57            );
58            println!("Edit the file to customize, or delete it to regenerate from defaults.");
59            Ok(true)
60        }
61        Err(e) => Err(anyhow::anyhow!(
62            "Failed to create config file {}: {}",
63            global_path.display(),
64            e
65        )),
66    }
67}
68
69/// Handle the legacy `--init-legacy` flag.
70///
71/// Creates a local agents.toml file at the specified path if it doesn't exist.
72pub fn handle_init_legacy(colors: Colors, agents_config_path: &Path) -> anyhow::Result<bool> {
73    match AgentsConfigFile::ensure_config_exists(agents_config_path) {
74        Ok(ConfigInitResult::Created) => {
75            println!(
76                "{}Created {}{}{}\n",
77                colors.green(),
78                colors.bold(),
79                agents_config_path.display(),
80                colors.reset()
81            );
82            println!("Edit the file to customize agent configurations, then run ralph again.");
83            println!("Or run ralph now to use the default settings.");
84            Ok(true)
85        }
86        Ok(ConfigInitResult::AlreadyExists) => {
87            println!(
88                "{}Config file already exists:{} {}",
89                colors.yellow(),
90                colors.reset(),
91                agents_config_path.display()
92            );
93            println!("Edit the file to customize, or delete it to regenerate from defaults.");
94            Ok(true)
95        }
96        Err(e) => Err(anyhow::anyhow!(
97            "Failed to create config file {}: {}",
98            agents_config_path.display(),
99            e
100        )),
101    }
102}
103
104// NOTE: legacy per-repo agents.toml creation is handled by `--init-legacy` only.
105
106/// Handle the `--init-prompt` flag.
107///
108/// Creates a PROMPT.md file from the specified template.
109///
110/// # Arguments
111///
112/// * `template_name` - The name of the template to use
113/// * `colors` - Terminal color configuration for output
114///
115/// # Returns
116///
117/// Returns `Ok(true)` if the flag was handled (program should exit after),
118/// or an error if template creation failed.
119pub fn handle_init_prompt(template_name: &str, colors: Colors) -> anyhow::Result<bool> {
120    let prompt_path = Path::new("PROMPT.md");
121
122    // Check if PROMPT.md already exists
123    if prompt_path.exists() {
124        println!(
125            "{}PROMPT.md already exists:{} {}",
126            colors.yellow(),
127            colors.reset(),
128            prompt_path.display()
129        );
130        println!("Delete or backup the existing file to create a new one from a template.");
131        return Ok(true);
132    }
133
134    // Validate the template exists
135    let Some(template) = get_template(template_name) else {
136        println!(
137            "{}Unknown template: '{}'{}",
138            colors.red(),
139            template_name,
140            colors.reset()
141        );
142        println!();
143        println!("Available templates:");
144        for (name, description) in list_templates() {
145            println!(
146                "  {}{}{}  {}",
147                colors.cyan(),
148                name,
149                colors.reset(),
150                description
151            );
152        }
153        println!();
154        println!("Usage: ralph --init-prompt <template>");
155        println!("       ralph --list-templates");
156        return Ok(true);
157    };
158
159    // Write the template content to PROMPT.md
160    let content = template.content();
161    fs::write(prompt_path, content)?;
162
163    println!(
164        "{}Created PROMPT.md from template: {}{}{}",
165        colors.green(),
166        colors.bold(),
167        template_name,
168        colors.reset()
169    );
170    println!();
171    println!(
172        "Template: {}{}{}  {}",
173        colors.cyan(),
174        template.name(),
175        colors.reset(),
176        template.description()
177    );
178    println!();
179    println!("Next steps:");
180    println!("  1. Edit PROMPT.md with your task details");
181    println!("  2. Run: ralph \"your commit message\"");
182    println!();
183    println!("Tip: Use --list-templates to see all available templates.");
184
185    Ok(true)
186}
187
188/// Handle the `--list-templates` flag.
189///
190/// Lists all available PROMPT.md templates with descriptions.
191///
192/// # Arguments
193///
194/// * `colors` - Terminal color configuration for output
195///
196/// # Returns
197///
198/// Returns `true` if the flag was handled (program should exit after).
199pub fn handle_list_templates(colors: Colors) -> bool {
200    println!("Available PROMPT.md templates:");
201    println!();
202
203    let templates = list_templates();
204    let _max_name_len = templates
205        .iter()
206        .map(|(name, _)| name.len())
207        .max()
208        .unwrap_or(0);
209
210    for (name, description) in templates {
211        println!(
212            "  {}{}{}  {}{}{}",
213            colors.cyan(),
214            name,
215            colors.reset(),
216            colors.dim(),
217            description,
218            colors.reset()
219        );
220    }
221
222    println!();
223    println!("Usage: ralph --init-prompt <template>");
224    println!();
225    println!("Example:");
226    println!("  ralph --init-prompt feature-spec   # Create comprehensive spec template");
227    println!("  ralph --init-prompt bug-fix        # Create bug fix template");
228    println!("  ralph --init-prompt quick          # Create quick change template");
229
230    true
231}