1use crate::ui::UI;
4use std::collections::HashMap;
5use std::fs;
6use std::io::{self, Write};
7
8use anyhow::Result;
9
10pub async fn handle(
11 interactive: bool,
12 template: Option<String>,
13 tools: Option<String>,
14 force: bool,
15 dry_run: bool,
16 list_templates: bool,
17) -> Result<()> {
18 if list_templates {
19 return list_available_templates();
20 }
21
22 let config_path = std::env::current_dir()
23 .map_err(|e| anyhow::anyhow!("Failed to get current directory: {}", e))?
24 .join(".vx.toml");
25
26 if config_path.exists() && !force {
28 UI::warn("Configuration file .vx.toml already exists");
29 UI::info("Use --force to overwrite or edit the existing file");
30 return Ok(());
31 }
32
33 let config_content = if interactive {
34 generate_interactive_config().await?
35 } else if let Some(template_name) = template {
36 generate_template_config(&template_name)?
37 } else if let Some(tools_str) = tools {
38 generate_tools_config(&tools_str)?
39 } else {
40 generate_auto_detected_config().await?
41 };
42
43 if dry_run {
44 UI::info("Preview of .vx.toml configuration:");
45 println!();
46 println!("{}", config_content);
47 return Ok(());
48 }
49
50 fs::write(&config_path, config_content)
52 .map_err(|e| anyhow::anyhow!("Failed to write .vx.toml: {}", e))?;
53
54 UI::success("✅ Created .vx.toml configuration file");
55
56 println!();
58 println!("Next steps:");
59 println!(" 1. Review the configuration: cat .vx.toml");
60 println!(" 2. Install tools: vx sync");
61 println!(" 3. Start using tools: vx <tool> --version");
62 println!();
63 println!("Optional:");
64 println!(" - Add to version control: git add .vx.toml");
65 println!(" - Customize configuration: vx config edit --local");
66
67 Ok(())
68}
69
70fn list_available_templates() -> Result<()> {
71 UI::info("Available templates:");
72 println!();
73 println!(" node - Node.js project with npm");
74 println!(" python - Python project with uv");
75 println!(" rust - Rust project with cargo");
76 println!(" go - Go project");
77 println!(" fullstack - Full-stack project (Node.js + Python)");
78 println!(" minimal - Minimal configuration");
79 println!();
80 println!("Usage: vx init --template <template>");
81 Ok(())
82}
83
84async fn generate_interactive_config() -> Result<String> {
85 UI::header("🚀 VX Project Initialization");
86
87 print!("Project name (optional): ");
89 io::stdout().flush().unwrap();
90 let mut project_name = String::new();
91 io::stdin().read_line(&mut project_name).unwrap();
92 let project_name = project_name.trim();
93
94 print!("Description (optional): ");
96 io::stdout().flush().unwrap();
97 let mut description = String::new();
98 io::stdin().read_line(&mut description).unwrap();
99 let description = description.trim();
100
101 println!();
103 println!("Select tools to include:");
104 let available_tools = vec![
105 ("node", "18.17.0", "Node.js JavaScript runtime"),
106 ("npm", "latest", "Node.js package manager"),
107 ("python", "3.11", "Python interpreter"),
108 ("uv", "latest", "Fast Python package manager"),
109 ("go", "latest", "Go programming language"),
110 ("cargo", "latest", "Rust package manager"),
111 ];
112
113 let mut selected_tools = HashMap::new();
114 for (tool, default_version, desc) in &available_tools {
115 print!("Include {} ({})? (y/N): ", tool, desc);
116 io::stdout().flush().unwrap();
117 let mut input = String::new();
118 io::stdin().read_line(&mut input).unwrap();
119 if input.trim().to_lowercase().starts_with('y') {
120 selected_tools.insert(tool.to_string(), default_version.to_string());
121 }
122 }
123
124 if selected_tools.is_empty() {
125 selected_tools.insert("node".to_string(), "18.17.0".to_string());
126 UI::info("No tools selected, adding Node.js as default");
127 }
128
129 generate_config_content(project_name, description, &selected_tools, true)
130}
131
132fn generate_template_config(template_name: &str) -> Result<String> {
133 let tools = match template_name {
134 "node" => {
135 let mut tools = HashMap::new();
136 tools.insert("node".to_string(), "18.17.0".to_string());
137 tools.insert("npm".to_string(), "latest".to_string());
138 tools
139 }
140 "python" => {
141 let mut tools = HashMap::new();
142 tools.insert("python".to_string(), "3.11".to_string());
143 tools.insert("uv".to_string(), "latest".to_string());
144 tools
145 }
146 "rust" => {
147 let mut tools = HashMap::new();
148 tools.insert("cargo".to_string(), "latest".to_string());
149 tools
150 }
151 "go" => {
152 let mut tools = HashMap::new();
153 tools.insert("go".to_string(), "latest".to_string());
154 tools
155 }
156 "fullstack" => {
157 let mut tools = HashMap::new();
158 tools.insert("node".to_string(), "18.17.0".to_string());
159 tools.insert("python".to_string(), "3.11".to_string());
160 tools.insert("uv".to_string(), "latest".to_string());
161 tools
162 }
163 "minimal" => HashMap::new(),
164 _ => {
165 return Err(anyhow::anyhow!(
166 "Unknown template: {}. Use --list-templates to see available templates.",
167 template_name
168 ));
169 }
170 };
171
172 generate_config_content("", "", &tools, false)
173}
174
175fn generate_tools_config(tools_str: &str) -> Result<String> {
176 let mut tools = HashMap::new();
177
178 for tool_spec in tools_str.split(',') {
179 let tool_spec = tool_spec.trim();
180 if tool_spec.contains('@') {
181 let parts: Vec<&str> = tool_spec.split('@').collect();
182 if parts.len() == 2 {
183 tools.insert(parts[0].to_string(), parts[1].to_string());
184 }
185 } else {
186 tools.insert(tool_spec.to_string(), "latest".to_string());
187 }
188 }
189
190 generate_config_content("", "", &tools, false)
191}
192
193async fn generate_auto_detected_config() -> Result<String> {
194 let current_dir = std::env::current_dir()
195 .map_err(|e| anyhow::anyhow!("Failed to get current directory: {}", e))?;
196
197 let mut tools = HashMap::new();
198 let mut detected_types = Vec::new();
199
200 if current_dir.join("package.json").exists() {
202 tools.insert("node".to_string(), "18.17.0".to_string());
203 tools.insert("npm".to_string(), "latest".to_string());
204 detected_types.push("Node.js");
205 UI::info("🔍 Detected Node.js project (package.json found)");
206 }
207
208 if current_dir.join("pyproject.toml").exists() || current_dir.join("requirements.txt").exists()
210 {
211 tools.insert("python".to_string(), "3.11".to_string());
212 tools.insert("uv".to_string(), "latest".to_string());
213 detected_types.push("Python");
214 UI::info("🔍 Detected Python project");
215 }
216
217 if current_dir.join("go.mod").exists() {
219 tools.insert("go".to_string(), "latest".to_string());
220 detected_types.push("Go");
221 UI::info("🔍 Detected Go project (go.mod found)");
222 }
223
224 if current_dir.join("Cargo.toml").exists() {
226 tools.insert("cargo".to_string(), "latest".to_string());
227 detected_types.push("Rust");
228 UI::info("🔍 Detected Rust project (Cargo.toml found)");
229 }
230
231 if tools.is_empty() {
232 UI::info("No project type detected, creating minimal configuration");
233 tools.insert("node".to_string(), "18.17.0".to_string());
234 } else if detected_types.len() > 1 {
235 UI::info(&format!(
236 "🔍 Detected mixed project ({})",
237 detected_types.join(" + ")
238 ));
239 }
240
241 generate_config_content("", "", &tools, false)
242}
243
244fn generate_config_content(
245 project_name: &str,
246 description: &str,
247 tools: &HashMap<String, String>,
248 include_extras: bool,
249) -> Result<String> {
250 let mut content = String::new();
251
252 content.push_str("# VX Project Configuration\n");
254 content.push_str("# This file defines the tools and versions required for this project.\n");
255 content.push_str("# Run 'vx sync' to install all required tools.\n");
256
257 if !project_name.is_empty() {
258 content.push_str(&format!("# Project: {}\n", project_name));
259 }
260 if !description.is_empty() {
261 content.push_str(&format!("# Description: {}\n", description));
262 }
263
264 content.push('\n');
265
266 content.push_str("[tools]\n");
268 if tools.is_empty() {
269 content.push_str("# Add your tools here, for example:\n");
270 content.push_str("# node = \"18.17.0\"\n");
271 content.push_str("# python = \"3.11\"\n");
272 content.push_str("# uv = \"latest\"\n");
273 } else {
274 for (tool, version) in tools {
275 content.push_str(&format!("{} = \"{}\"\n", tool, version));
276 }
277 }
278
279 content.push('\n');
280
281 content.push_str("[settings]\n");
283 content.push_str("auto_install = true\n");
284 content.push_str("cache_duration = \"7d\"\n");
285
286 if include_extras {
287 content.push_str("parallel_install = true\n");
288 content.push('\n');
289
290 content.push_str("[scripts]\n");
292 content.push_str("# Add custom scripts here\n");
293 content.push_str("# dev = \"vx node server.js\"\n");
294 content.push_str("# test = \"vx uv run pytest\"\n");
295 content.push('\n');
296
297 content.push_str("[env]\n");
299 content.push_str("# Add environment variables here\n");
300 content.push_str("# NODE_ENV = \"development\"\n");
301 content.push_str("# DEBUG = \"true\"\n");
302 }
303
304 Ok(content)
305}