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