use super::Command;
use crate::error::{CliError, CliResult};
use crate::output::OutputStyle;
use std::io::{self, Write};
pub struct InitCommand {
pub project_path: Option<String>,
pub interactive: bool,
}
impl InitCommand {
pub fn new(project_path: Option<String>) -> Self {
Self {
project_path,
interactive: true,
}
}
pub fn with_interactive(mut self, interactive: bool) -> Self {
self.interactive = interactive;
self
}
fn prompt(&self, question: &str) -> CliResult<String> {
let style = OutputStyle::default();
print!("{}", style.prompt(question));
io::stdout().flush().map_err(CliError::Io)?;
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.map_err(CliError::Io)?;
Ok(input.trim().to_string())
}
#[allow(dead_code)]
fn prompt_yes_no(&self, question: &str) -> CliResult<bool> {
loop {
let response = self.prompt(&format!("{} (y/n): ", question))?;
match response.to_lowercase().as_str() {
"y" | "yes" => return Ok(true),
"n" | "no" => return Ok(false),
_ => println!("Please enter 'y' or 'n'"),
}
}
}
fn show_welcome(&self) {
let style = OutputStyle::default();
println!("{}", style.section("Welcome to RiceCoder"));
println!();
println!("This wizard will help you set up a new RiceCoder project.");
println!();
println!("{}",
style.list_item("Create project configuration")
);
println!("{}", style.list_item("Set up AI provider"));
println!("{}", style.list_item("Configure storage"));
println!();
}
fn show_getting_started(&self, project_path: &str) {
let style = OutputStyle::default();
println!();
println!("{}", style.section("Getting Started"));
println!();
println!("Your project is ready! Here are the next steps:");
println!();
println!("{}", style.numbered_item(1, "Navigate to your project"));
println!(" cd {}", project_path);
println!();
println!("{}", style.numbered_item(2, "Start the interactive chat"));
println!(" rice chat");
println!();
println!("{}", style.numbered_item(3, "Generate code from specifications"));
println!(" rice gen --spec my-spec.md");
println!();
println!("{}", style.section("Learn More"));
println!();
println!("{}", style.link("Documentation", "https://ricecoder.dev/docs"));
println!("{}", style.link("Examples", "https://ricecoder.dev/examples"));
println!("{}", style.link("Troubleshooting", "https://ricecoder.dev/docs/troubleshooting"));
println!();
}
fn run_wizard(&self, _path: &str) -> CliResult<(String, String, String)> {
self.show_welcome();
let project_name = self.prompt("Project name")?;
let project_name = if project_name.is_empty() {
"My Project".to_string()
} else {
project_name
};
let project_description = self.prompt("Project description (optional)")?;
println!();
println!("Available AI providers:");
println!(" 1. OpenAI (GPT-4, GPT-3.5)");
println!(" 2. Anthropic (Claude)");
println!(" 3. Local (Ollama)");
println!(" 4. Other");
println!();
let provider = loop {
let choice = self.prompt("Select provider (1-4)")?;
match choice.as_str() {
"1" => break "openai".to_string(),
"2" => break "anthropic".to_string(),
"3" => break "ollama".to_string(),
"4" => break "other".to_string(),
_ => println!("Please enter 1, 2, 3, or 4"),
}
};
Ok((project_name, project_description, provider))
}
}
impl Command for InitCommand {
fn execute(&self) -> CliResult<()> {
let path = self.project_path.as_deref().unwrap_or(".");
let style = OutputStyle::default();
let (project_name, project_description, provider) = if self.interactive {
self.run_wizard(path)?
} else {
("My Project".to_string(), String::new(), "openai".to_string())
};
std::fs::create_dir_all(format!("{}/.agent", path)).map_err(CliError::Io)?;
let config_content = format!(
r#"# RiceCoder Project Configuration
# This file configures ricecoder for your project
[project]
name = "{}"
description = "{}"
[providers]
default = "{}"
[storage]
mode = "merged"
# For more configuration options, see:
# https://ricecoder.dev/docs/configuration
"#,
project_name, project_description, provider
);
std::fs::write(format!("{}/.agent/ricecoder.toml", path), config_content)
.map_err(CliError::Io)?;
let example_spec = r#"# Example Specification
## Overview
This is an example specification for RiceCoder. You can use this as a template
for your own specifications.
## Requirements
### Requirement 1
**User Story:** As a user, I want to do something, so that I can achieve a goal.
#### Acceptance Criteria
1. WHEN I do something THEN the system SHALL do something else
2. WHEN I do another thing THEN the system SHALL respond appropriately
## Design
### Architecture
Describe your architecture here.
### Data Models
Describe your data models here.
## Tasks
- [ ] Task 1: Implement feature
- [ ] Task 2: Write tests
- [ ] Task 3: Document
For more information, see: https://ricecoder.dev/docs/specifications
"#;
std::fs::write(format!("{}/.agent/example-spec.md", path), example_spec)
.map_err(CliError::Io)?;
let readme = format!(
r#"# {}
{}
## Getting Started
1. Configure your AI provider in `.agent/ricecoder.toml`
2. Create a specification file (see `example-spec.md`)
3. Run `rice gen --spec your-spec.md` to generate code
## Documentation
- [RiceCoder Documentation](https://ricecoder.dev/docs)
- [Configuration Guide](https://ricecoder.dev/docs/configuration)
- [Specification Guide](https://ricecoder.dev/docs/specifications)
## Support
- [GitHub Issues](https://github.com/ricecoder/ricecoder/issues)
- [Discussions](https://github.com/ricecoder/ricecoder/discussions)
- [Troubleshooting](https://ricecoder.dev/docs/troubleshooting)
"#,
project_name, project_description
);
std::fs::write(format!("{}/README.md", path), readme)
.map_err(CliError::Io)?;
println!();
println!("{}", style.success(&format!("Initialized ricecoder project at {}", path)));
println!();
println!("Created files:");
println!("{}", style.list_item(".agent/ricecoder.toml - Project configuration"));
println!("{}", style.list_item(".agent/example-spec.md - Example specification"));
println!("{}", style.list_item("README.md - Project documentation"));
println!();
self.show_getting_started(path);
Ok(())
}
}