1use anyhow::Result;
2use colored::*;
3use console::style;
4use inquire::{Confirm, Select};
5use std::path::PathBuf;
6
7use crate::git::bulk_clone::{BulkCloneCommand, BulkCloneOptions};
8use crate::git::provider::github_cli::GitHubCliProvider;
9use crate::git::{GitConfig, Repository};
10use crate::workspace::install::RepositoryInstaller;
11use crate::workspace::manager::WorkspaceManager;
12
13pub struct CloneCommand;
14
15impl CloneCommand {
16 pub async fn execute(
17 url: String,
18 path: Option<PathBuf>,
19 open: bool,
20 install: bool,
21 workspace_manager: &mut WorkspaceManager,
22 git_config: &GitConfig,
23 ) -> Result<PathBuf> {
24 let workspace_root = workspace_manager.config().workspace.root.clone();
26
27 let installer = RepositoryInstaller::new(workspace_root, git_config.clone());
29
30 let installed = installer
32 .install_from_url_with_options(&url, path, open, install)
33 .await?;
34
35 workspace_manager
37 .add_repository(installed.repository.clone())
38 .await?;
39
40 if !installed.post_install_actions.is_empty() {
42 installer
43 .execute_post_install_actions(&installed.post_install_actions, &installed.path)
44 .await?;
45 }
46
47 println!(
48 "\n{} Repository successfully added to workspace!",
49 "đ".green()
50 );
51
52 println!("Path: {}", installed.path.display().to_string().cyan());
53
54 Ok(installed.path)
55 }
56
57 pub async fn execute_interactive(
59 url: String,
60 path: Option<PathBuf>,
61 workspace_manager: &mut WorkspaceManager,
62 git_config: &GitConfig,
63 ) -> Result<PathBuf> {
64 let cloned_path = Self::execute(
66 url.clone(),
67 path,
68 false,
69 false,
70 workspace_manager,
71 git_config,
72 )
73 .await?;
74
75 let repo_name = cloned_path
77 .file_name()
78 .and_then(|n| n.to_str())
79 .ok_or_else(|| anyhow::anyhow!("Could not determine repository name"))?;
80
81 Self::interactive_post_clone_workflow(repo_name, workspace_manager).await?;
83
84 Ok(cloned_path)
85 }
86
87 pub async fn clone_from_search_result(
88 repo: Repository,
89 workspace_manager: &mut WorkspaceManager,
90 git_config: &GitConfig,
91 ) -> Result<()> {
92 println!(
93 "\n{} Selected: {}",
94 "â
".green(),
95 repo.full_name.cyan().bold()
96 );
97
98 let _cloned_path =
100 Self::execute(repo.url, None, false, false, workspace_manager, git_config).await?;
101
102 Self::interactive_post_clone_workflow(&repo.name, workspace_manager).await?;
104
105 Ok(())
106 }
107
108 pub async fn interactive_post_clone_workflow(
110 repo_name: &str,
111 workspace_manager: &mut WorkspaceManager,
112 ) -> Result<()> {
113 println!("\n{} Repository cloned successfully!", style("đ").green());
114
115 let configure_apps = Confirm::new(&format!(
117 "Would you like to configure apps for '{}'?",
118 style(repo_name).cyan().bold()
119 ))
120 .with_default(true)
121 .with_help_message(
122 "Configure which applications can open this repository (VS Code, Warp, etc.)",
123 )
124 .prompt()?;
125
126 if configure_apps {
127 Self::configure_repository_apps(repo_name, workspace_manager).await?;
128 }
129
130 let open_now = Confirm::new(&format!(
132 "Would you like to open '{}' now?",
133 style(repo_name).cyan().bold()
134 ))
135 .with_default(true)
136 .with_help_message("Open the repository with your configured app")
137 .prompt()?;
138
139 if open_now {
140 Self::open_repository_interactive(repo_name, workspace_manager).await?;
141 }
142
143 Ok(())
144 }
145
146 async fn configure_repository_apps(
148 repo_name: &str,
149 workspace_manager: &mut WorkspaceManager,
150 ) -> Result<()> {
151 let available_apps = [
153 ("vscode", "Visual Studio Code - Code editor"),
154 ("warp", "Warp - Modern terminal"),
155 ("iterm2", "iTerm2 - Terminal emulator"),
156 ];
157
158 println!(
159 "\n{} Select an application to configure for this repository:",
160 style("đą").green()
161 );
162
163 let app_choices: Vec<String> = available_apps
164 .iter()
165 .map(|(name, desc)| format!("{name} - {desc}"))
166 .collect();
167
168 let selected_display = Select::new("Choose an application:", app_choices)
169 .with_help_message("Select an application to configure for this repository")
170 .prompt()?;
171
172 let app_name = selected_display
174 .split(" - ")
175 .next()
176 .unwrap_or(&selected_display);
177
178 workspace_manager
180 .configure_app_for_repo(repo_name, app_name, "default")
181 .await?;
182
183 println!(
184 "{} Configured {} for {}",
185 style("â
").green(),
186 style(app_name).blue(),
187 style(repo_name).cyan()
188 );
189
190 Ok(())
191 }
192
193 async fn open_repository_interactive(
195 repo_name: &str,
196 workspace_manager: &mut WorkspaceManager,
197 ) -> Result<()> {
198 if let Some(repo_info) = workspace_manager.get_repository(repo_name) {
200 if repo_info.apps.is_empty() {
201 println!(
202 "{} No apps configured for this repository",
203 style("â ī¸").yellow()
204 );
205 println!(" Configure apps first using the configuration workflow");
206 return Ok(());
207 }
208
209 let app_to_use = if repo_info.apps.len() == 1 {
211 repo_info.apps.keys().next().unwrap().clone()
212 } else {
213 let app_choices: Vec<String> = repo_info.apps.keys().cloned().collect();
215 Select::new("Choose an app to open with:", app_choices)
216 .with_help_message("Select which application to use")
217 .prompt()?
218 };
219
220 workspace_manager
222 .open_repo_with_app(repo_name, &app_to_use)
223 .await?;
224
225 println!(
226 "{} Opened {} with {}",
227 style("đ").green(),
228 style(repo_name).cyan().bold(),
229 style(&app_to_use).blue()
230 );
231 } else {
232 println!(
233 "{} Repository '{}' not found in workspace",
234 style("â").red(),
235 repo_name
236 );
237 }
238
239 Ok(())
240 }
241}
242
243pub struct EnhancedCloneCommand;
245
246impl EnhancedCloneCommand {
247 pub async fn execute_with_detection(
249 url_or_target: String,
250 app: Option<String>,
251 no_configure: bool,
252 no_open: bool,
253 workspace_manager: &mut WorkspaceManager,
254 git_config: &GitConfig,
255 ) -> Result<()> {
256 let contains_slash = url_or_target.contains('/');
257 let is_url = url_or_target.starts_with("http") || url_or_target.starts_with("git@");
258
259 match (contains_slash, is_url) {
261 (true, _) | (false, true) => {
263 Self::single_repository_workflow(
264 url_or_target,
265 app,
266 no_configure,
267 no_open,
268 workspace_manager,
269 git_config,
270 )
271 .await
272 }
273
274 (false, false) => {
276 Self::detect_and_route(
277 url_or_target,
278 app,
279 no_configure,
280 no_open,
281 workspace_manager,
282 git_config,
283 )
284 .await
285 }
286 }
287 }
288
289 async fn detect_and_route(
291 target: String,
292 app: Option<String>,
293 no_configure: bool,
294 no_open: bool,
295 workspace_manager: &mut WorkspaceManager,
296 git_config: &GitConfig,
297 ) -> Result<()> {
298 println!("đ Analyzing '{}'...", style(&target).cyan());
299
300 let github_cli = match GitHubCliProvider::new() {
302 Ok(cli) => cli,
303 Err(_) => {
304 println!(
305 "{} GitHub CLI not available, searching repositories...",
306 style("â ī¸").yellow()
307 );
308 return Self::fallback_to_search(target, workspace_manager, git_config).await;
309 }
310 };
311
312 match github_cli.user_or_org_exists(&target).await {
314 Ok(true) => {
315 match github_cli.count_repositories(&target).await {
317 Ok(0) => {
318 println!(
319 "{} '{}' has no public repositories.",
320 style("âšī¸").blue(),
321 style(&target).cyan()
322 );
323 Self::fallback_to_search(target, workspace_manager, git_config).await
324 }
325 Ok(count) => {
326 Self::interactive_clone_selection(
327 target,
328 count,
329 app,
330 no_configure,
331 no_open,
332 workspace_manager,
333 git_config,
334 )
335 .await
336 }
337 Err(_) => {
338 println!(
339 "{} Failed to count repositories for '{}', searching instead...",
340 style("â ī¸").yellow(),
341 style(&target).cyan()
342 );
343 Self::fallback_to_search(target, workspace_manager, git_config).await
344 }
345 }
346 }
347 Ok(false) => {
348 println!(
349 "đ '{}' not found as a GitHub user or organization.",
350 &target
351 );
352 println!("đ Searching repositories for '{}'...", &target);
353 Self::fallback_to_search(target, workspace_manager, git_config).await
354 }
355 Err(_) => {
356 println!(
357 "{} Failed to check GitHub, searching repositories instead...",
358 style("â ī¸").yellow()
359 );
360 Self::fallback_to_search(target, workspace_manager, git_config).await
361 }
362 }
363 }
364
365 async fn interactive_clone_selection(
367 target: String,
368 repo_count: usize,
369 _app: Option<String>,
370 _no_configure: bool,
371 _no_open: bool,
372 workspace_manager: &mut WorkspaceManager,
373 git_config: &GitConfig,
374 ) -> Result<()> {
375 println!(
376 "â
Found GitHub target '{}' with {} repositories",
377 style(&target).cyan().bold(),
378 style(repo_count).green().bold()
379 );
380
381 let options = vec![
382 format!("Clone all {} repositories", repo_count),
383 "Search for specific repository".to_string(),
384 "Cancel".to_string(),
385 ];
386
387 let selection = Select::new("What would you like to do?", options)
388 .with_help_message("Choose how to proceed with this GitHub target")
389 .prompt()?;
390
391 match selection.as_str() {
392 s if s.starts_with("Clone all") => {
393 Self::bulk_clone_workflow(target, workspace_manager, git_config).await
394 }
395 "Search for specific repository" => {
396 Self::fallback_to_search(target, workspace_manager, git_config).await
397 }
398 _ => {
399 println!("{} Operation cancelled", style("âšī¸").blue());
400 Ok(())
401 }
402 }
403 }
404
405 async fn bulk_clone_workflow(
407 target: String,
408 workspace_manager: &mut WorkspaceManager,
409 git_config: &GitConfig,
410 ) -> Result<()> {
411 let options = BulkCloneOptions {
412 exclude_patterns: Vec::new(),
413 include_patterns: Vec::new(),
414 skip_existing: true,
415 custom_path: None,
416 force: false, };
418
419 match BulkCloneCommand::execute(target, options, workspace_manager, git_config).await {
420 Ok(result) => {
421 println!(
422 "{} Bulk clone completed: {} successful, {} failed",
423 style("â
").green().bold(),
424 result.total_cloned,
425 result.failed.len()
426 );
427 Ok(())
428 }
429 Err(e) => {
430 println!("{} Bulk clone failed: {}", style("â").red(), e);
431 Err(e)
432 }
433 }
434 }
435
436 async fn fallback_to_search(
438 target: String,
439 workspace_manager: &mut WorkspaceManager,
440 git_config: &GitConfig,
441 ) -> Result<()> {
442 use crate::git::SearchCommand;
443
444 SearchCommand::execute_with_query(&target, workspace_manager, git_config).await
446 }
447
448 async fn single_repository_workflow(
450 url: String,
451 app: Option<String>,
452 no_configure: bool,
453 no_open: bool,
454 workspace_manager: &mut WorkspaceManager,
455 git_config: &GitConfig,
456 ) -> Result<()> {
457 use crate::ui::workflows::{execute_workflow, CloneWorkflow};
458
459 if !no_configure || !no_open {
461 let workflow = Box::new(CloneWorkflow {
462 url: url.clone(),
463 app: app.clone(),
464 });
465
466 execute_workflow(workflow, workspace_manager).await?;
467 } else {
468 let _cloned_path =
470 CloneCommand::execute(url, None, false, false, workspace_manager, git_config)
471 .await?;
472 }
473
474 Ok(())
475 }
476}