1use anyhow::Result;
2use colored::Colorize;
3use dialoguer::{Input, Select};
4use std::fs;
5use std::path::PathBuf;
6
7use crate::commands::config as config_cmd;
8use crate::commands::helpers::is_interactive;
9use crate::config::{Config, LLMConfig};
10use crate::storage::Storage;
11
12fn configure_provider_and_model(tier: &str) -> Result<(String, String)> {
14 let providers = vec![
15 "Claude Code (recommended - no API key needed)",
16 "OpenAI Codex CLI (no API key needed)",
17 "xAI (Grok)",
18 "Anthropic (Claude API)",
19 "OpenAI (GPT API)",
20 "OpenRouter",
21 ];
22 let provider_selection = Select::new()
23 .with_prompt(&format!("Select {} LLM provider", tier))
24 .items(&providers)
25 .default(if tier == "fast" { 2 } else { 0 }) .interact()?;
27
28 let provider = match provider_selection {
29 0 => "claude-cli",
30 1 => "codex",
31 2 => "xai",
32 3 => "anthropic",
33 4 => "openai",
34 5 => "openrouter",
35 _ => "claude-cli",
36 };
37
38 let suggested = Config::suggested_models_for_provider(provider);
40 let mut model_options: Vec<String> = suggested.iter().map(|s| s.to_string()).collect();
41 model_options.push("Custom (enter model name)".to_string());
42
43 let default_model_index = if tier == "fast" && provider == "xai" {
44 suggested
45 .iter()
46 .position(|m| *m == "grok-code-fast-1")
47 .unwrap_or(0)
48 } else if tier == "smart" && provider == "claude-cli" {
49 suggested.iter().position(|m| *m == "opus").unwrap_or(0)
50 } else {
51 0
52 };
53
54 let model_selection = Select::new()
55 .with_prompt(&format!(
56 "Select {} model (or choose Custom to enter any model)",
57 tier
58 ))
59 .items(&model_options)
60 .default(default_model_index)
61 .interact()?;
62
63 let model = if model_selection == model_options.len() - 1 {
64 Input::<String>::new()
66 .with_prompt("Enter model name")
67 .interact_text()?
68 } else {
69 suggested[model_selection].to_string()
70 };
71
72 Ok((provider.to_string(), model))
73}
74
75pub fn run(project_root: Option<PathBuf>, provider_arg: Option<String>) -> Result<()> {
76 let storage = Storage::new(project_root);
77
78 if storage.is_initialized() {
79 println!("{}", "✓ SCUD is already initialized".green());
80 return Ok(());
81 }
82
83 println!("{}", "Initializing SCUD...".blue());
84 println!();
85
86 let (provider, model, smart_provider, smart_model, fast_provider, fast_model) = if let Some(
87 provider_name,
88 ) =
89 provider_arg
90 {
91 let provider = provider_name.to_lowercase();
93 if !matches!(
94 provider.as_str(),
95 "xai" | "anthropic" | "openai" | "openrouter" | "claude-cli" | "codex"
96 ) {
97 anyhow::bail!(
98 "Invalid provider: {}. Valid options: claude-cli, codex, xai, anthropic, openai, openrouter",
99 provider
100 );
101 }
102 let model = Config::default_model_for_provider(&provider).to_string();
103 let smart_provider = "claude-cli".to_string();
105 let smart_model = "opus".to_string();
106 let fast_provider = "xai".to_string();
107 let fast_model = "grok-code-fast-1".to_string();
108 (
109 provider,
110 model,
111 smart_provider,
112 smart_model,
113 fast_provider,
114 fast_model,
115 )
116 } else if is_interactive() {
117 println!(
118 "{}",
119 "SCUD supports separate models for different types of tasks:".blue()
120 );
121 println!(" • Fast models: Quick coding, generation tasks");
122 println!(" • Smart models: Complex reasoning, analysis, validation");
123 println!();
124
125 println!("{}", "=== FAST MODEL CONFIGURATION ===".yellow().bold());
127 let (fast_provider, fast_model) = configure_provider_and_model("fast")?;
128
129 println!();
131 println!("{}", "=== SMART MODEL CONFIGURATION ===".yellow().bold());
132 let (smart_provider, smart_model) = configure_provider_and_model("smart")?;
133
134 let provider = fast_provider.clone();
136 let model = fast_model.clone();
137
138 (
139 provider,
140 model,
141 smart_provider,
142 smart_model,
143 fast_provider,
144 fast_model,
145 )
146 } else {
147 let provider = "claude-cli";
149 let model = Config::default_model_for_provider(provider);
150 let smart_provider = "claude-cli".to_string();
152 let smart_model = "opus".to_string();
153 let fast_provider = "xai".to_string();
154 let fast_model = "grok-code-fast-1".to_string();
155 (
156 provider.to_string(),
157 model.to_string(),
158 smart_provider,
159 smart_model,
160 fast_provider,
161 fast_model,
162 )
163 };
164
165 let config = Config {
166 llm: LLMConfig {
167 provider,
168 model,
169 smart_provider,
170 smart_model,
171 fast_provider,
172 fast_model,
173 max_tokens: 16000,
174 },
175 };
176
177 storage.initialize_with_config(&config)?;
178
179 println!("\n{}", "SCUD initialized successfully!".green().bold());
180
181 println!("\n{}", "Installing SCUD agents and commands...".blue());
183 if let Err(e) = config_cmd::agents_add(Some(storage.project_root().to_path_buf()), None, true) {
184 println!("{}", format!(" Could not install agents: {}", e).yellow());
185 println!(" You can install them later with: scud config agents add --all");
186 }
187
188 if let Err(e) = update_claude_md(&storage) {
190 println!(
191 "{}",
192 format!(" Could not update CLAUDE.md: {}", e).yellow()
193 );
194 }
195
196 println!("\n{}", "Configuration:".blue());
197 println!(
198 " Default Provider: {} ({})",
199 config.llm.provider.yellow(),
200 config.llm.model.yellow()
201 );
202 println!(
203 " Fast Provider: {} ({})",
204 config.llm.fast_provider.yellow(),
205 config.llm.fast_model.yellow()
206 );
207 println!(
208 " Smart Provider: {} ({})",
209 config.llm.smart_provider.yellow(),
210 config.llm.smart_model.yellow()
211 );
212 if config.requires_api_key() {
213 println!("\n{}", "Environment variables required:".blue());
214 let mut env_vars = std::collections::HashSet::new();
215 env_vars.insert(config.api_key_env_var());
216 if config.llm.fast_provider != config.llm.provider {
217 env_vars.insert(Config::api_key_env_var_for_provider(
218 &config.llm.fast_provider,
219 ));
220 }
221 if config.llm.smart_provider != config.llm.provider
222 && config.llm.smart_provider != config.llm.fast_provider
223 {
224 env_vars.insert(Config::api_key_env_var_for_provider(
225 &config.llm.smart_provider,
226 ));
227 }
228 for env_var in env_vars {
229 if env_var != "NONE" {
230 println!(" export {}=your-api-key", env_var.yellow());
231 }
232 }
233 } else {
234 println!("\n{}", "No API keys required (using CLI tools)".green());
235 }
236 println!("\n{}", "Next steps:".blue());
237 println!(" 1. Set your API key environment variable");
238 println!(" 2. Run: scud tags");
239 println!(" 3. Create or import tasks, then use: /scud:next\n");
240
241 Ok(())
242}
243
244fn update_claude_md(storage: &Storage) -> Result<()> {
246 let claude_md_path = storage.project_root().join("CLAUDE.md");
247
248 let scud_section = r#"
249## SCUD Task Management
250
251This project uses SCUD for AI-driven task management.
252
253### Quick Start
254- `scud tags` - List available phases
255- `scud next` - Find next available task
256- `scud set-status <id> in-progress` - Claim a task
257- `scud view` - Open interactive task viewer
258
259### Slash Commands
260Use `/scud:` commands in Claude Code for task operations.
261"#;
262
263 let marker = "## SCUD Task Management";
264
265 if claude_md_path.exists() {
266 let content = fs::read_to_string(&claude_md_path)?;
267 if content.contains(marker) {
268 return Ok(()); }
270 let new_content = format!("{}\n{}", content.trim_end(), scud_section);
272 fs::write(&claude_md_path, new_content)?;
273 } else {
274 fs::write(&claude_md_path, scud_section.trim_start())?;
276 }
277
278 println!(" {} Updated CLAUDE.md with SCUD instructions", "✓".green());
279 Ok(())
280}