scud/commands/
init.rs

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
12pub fn run(project_root: Option<PathBuf>, provider_arg: Option<String>) -> Result<()> {
13    let storage = Storage::new(project_root);
14
15    if storage.is_initialized() {
16        println!("{}", "✓ SCUD is already initialized".green());
17        return Ok(());
18    }
19
20    println!("{}", "Initializing SCUD...".blue());
21    println!();
22
23    let (provider, model) = if let Some(provider_name) = provider_arg {
24        // Non-interactive mode with command-line argument
25        let provider = provider_name.to_lowercase();
26        if !matches!(
27            provider.as_str(),
28            "xai" | "anthropic" | "openai" | "openrouter"
29        ) {
30            anyhow::bail!(
31                "Invalid provider: {}. Valid options: xai, anthropic, openai, openrouter",
32                provider
33            );
34        }
35        let model = Config::default_model_for_provider(&provider).to_string();
36        (provider, model)
37    } else if is_interactive() {
38        // Interactive mode - prompt for LLM provider
39        let providers = vec![
40            "xAI (Grok)",
41            "Anthropic (Claude)",
42            "OpenAI (GPT)",
43            "OpenRouter",
44        ];
45        let provider_selection = Select::new()
46            .with_prompt("Select your LLM provider")
47            .items(&providers)
48            .default(0)
49            .interact()?;
50
51        let provider = match provider_selection {
52            0 => "xai",
53            1 => "anthropic",
54            2 => "openai",
55            3 => "openrouter",
56            _ => "anthropic",
57        };
58
59        // Build model options: suggested models + "Custom" option
60        let suggested = Config::suggested_models_for_provider(provider);
61        let mut model_options: Vec<String> = suggested.iter().map(|s| s.to_string()).collect();
62        model_options.push("Custom (enter model name)".to_string());
63
64        let model_selection = Select::new()
65            .with_prompt("Select model (or choose Custom to enter any model)")
66            .items(&model_options)
67            .default(0)
68            .interact()?;
69
70        let model = if model_selection == model_options.len() - 1 {
71            // User selected "Custom"
72            Input::<String>::new()
73                .with_prompt("Enter model name")
74                .interact_text()?
75        } else {
76            suggested[model_selection].to_string()
77        };
78
79        (provider.to_string(), model)
80    } else {
81        // Non-interactive without provider arg: use default (anthropic)
82        let provider = "anthropic";
83        let model = Config::default_model_for_provider(provider);
84        (provider.to_string(), model.to_string())
85    };
86
87    let config = Config {
88        llm: LLMConfig {
89            provider,
90            model,
91            max_tokens: 4096,
92        },
93    };
94
95    storage.initialize_with_config(&config)?;
96
97    println!("\n{}", "SCUD initialized successfully!".green().bold());
98
99    // Auto-install all agents and commands
100    println!("\n{}", "Installing SCUD agents and commands...".blue());
101    if let Err(e) = config_cmd::agents_add(Some(storage.project_root().to_path_buf()), None, true) {
102        println!(
103            "{}",
104            format!("  Could not install agents: {}", e).yellow()
105        );
106        println!("  You can install them later with: scud config agents add --all");
107    }
108
109    // Update CLAUDE.md with SCUD instructions
110    if let Err(e) = update_claude_md(&storage) {
111        println!(
112            "{}",
113            format!("  Could not update CLAUDE.md: {}", e).yellow()
114        );
115    }
116
117    println!("\n{}", "Configuration:".blue());
118    println!("  Provider: {}", config.llm.provider.yellow());
119    println!("  Model: {}", config.llm.model.yellow());
120    println!("\n{}", "Environment variable required:".blue());
121    println!(
122        "  export {}=your-api-key",
123        config.api_key_env_var().yellow()
124    );
125    println!("\n{}", "Next steps:".blue());
126    println!("  1. Set your API key environment variable");
127    println!("  2. Run: scud tags");
128    println!("  3. Create or import tasks, then use: /scud:next\n");
129
130    Ok(())
131}
132
133/// Update CLAUDE.md with SCUD instructions
134fn update_claude_md(storage: &Storage) -> Result<()> {
135    let claude_md_path = storage.project_root().join("CLAUDE.md");
136
137    let scud_section = r#"
138## SCUD Task Management
139
140This project uses SCUD for AI-driven task management.
141
142### Quick Start
143- `scud tags` - List available phases
144- `scud next` - Find next available task
145- `scud set-status <id> in-progress` - Claim a task
146- `scud view` - Open interactive task viewer
147
148### Slash Commands
149Use `/scud:` commands in Claude Code for task operations.
150"#;
151
152    let marker = "## SCUD Task Management";
153
154    if claude_md_path.exists() {
155        let content = fs::read_to_string(&claude_md_path)?;
156        if content.contains(marker) {
157            return Ok(()); // Already has SCUD section
158        }
159        // Append to existing file
160        let new_content = format!("{}\n{}", content.trim_end(), scud_section);
161        fs::write(&claude_md_path, new_content)?;
162    } else {
163        // Create new file
164        fs::write(&claude_md_path, scud_section.trim_start())?;
165    }
166
167    println!("  {} Updated CLAUDE.md with SCUD instructions", "✓".green());
168    Ok(())
169}