Skip to main content

lc/cli/
search.rs

1//! Search functionality commands matching the documentation
2
3use crate::cli::{SearchCommands, SearchProviderCommands};
4use crate::search::{SearchConfig, SearchEngine, SearchProviderType};
5use anyhow::Result;
6use colored::*;
7
8/// Handle search-related commands
9pub async fn handle(command: SearchCommands) -> Result<()> {
10    match command {
11        SearchCommands::Provider { command } => handle_provider(command).await,
12        SearchCommands::Query {
13            provider,
14            query,
15            format,
16            count,
17        } => handle_query(provider, query, format, count).await,
18    }
19}
20
21async fn handle_provider(command: SearchProviderCommands) -> Result<()> {
22    let mut config = SearchConfig::load()?;
23
24    match command {
25        SearchProviderCommands::Add { name, url } => {
26            println!(
27                "{} Adding search provider '{}' with URL: {}",
28                "🔍".blue(),
29                name.bold(),
30                url.dimmed()
31            );
32
33            // Auto-detect provider type from URL
34            match SearchProviderType::detect_from_url(&url) {
35                Ok(provider_type) => {
36                    println!(
37                        "  {} Auto-detected provider type: {}",
38                        "✓".green(),
39                        format!("{:?}", provider_type).cyan()
40                    );
41                    
42                    config.add_provider(name.clone(), url, provider_type.clone())?;
43                    config.save()?;
44                    
45                    println!(
46                        "{} Search provider '{}' added successfully",
47                        "✓".green(),
48                        name.bold()
49                    );
50
51                    // Show provider-specific setup instructions
52                    show_provider_setup_help(&name, provider_type);
53                }
54                Err(e) => {
55                    eprintln!(
56                        "{} Failed to detect provider type from URL: {}",
57                        "✗".red(),
58                        e
59                    );
60                    eprintln!(
61                        "  {} Make sure the URL matches one of the supported patterns:",
62                        "ℹ".blue()
63                    );
64                    eprintln!("    • Brave: api.search.brave.com");
65                    eprintln!("    • Exa: api.exa.ai");
66                    eprintln!("    • Serper: google.serper.dev");
67                    eprintln!("    • SerpApi: serpapi.com");
68                    eprintln!("    • DuckDuckGo: api.duckduckgo.com");
69                    eprintln!("    • Jina: s.jina.ai");
70                    eprintln!("    • Tavily: api.tavily.com");
71                }
72            }
73        }
74        SearchProviderCommands::List => {
75            let providers = config.list_providers();
76            
77            if providers.is_empty() {
78                println!("{} No search providers configured", "📋".blue());
79                println!(
80                    "\n{}",
81                    "Add one with: lc search provider add <name> <url>".italic().dimmed()
82                );
83            } else {
84                println!("{} Configured search providers:", "📋".blue());
85                println!();
86                
87                for (name, provider) in providers {
88                    let is_default = config.get_default_provider() == Some(name);
89                    let default_marker = if is_default { " (default)" } else { "" };
90                    
91                    println!(
92                        "  {} {}{}",
93                        "•".cyan(),
94                        name.bold(),
95                        default_marker.green()
96                    );
97                    println!("    Type: {:?}", provider.provider_type);
98                    println!("    URL: {}", provider.url.dimmed());
99                    
100                    if !provider.headers.is_empty() {
101                        println!("    Headers:");
102                        for (key, _) in &provider.headers {
103                            println!("      - {}: ***", key.yellow());
104                        }
105                    } else {
106                        println!("    Headers: {}", "None configured".dimmed());
107                    }
108                    println!();
109                }
110            }
111        }
112        SearchProviderCommands::Delete { name } => {
113            println!(
114                "{} Removing search provider '{}'...",
115                "🗑".red(),
116                name.bold()
117            );
118            
119            config.delete_provider(&name)?;
120            config.save()?;
121            
122            println!(
123                "{} Search provider '{}' removed successfully",
124                "✓".green(),
125                name
126            );
127        }
128        SearchProviderCommands::Set {
129            provider,
130            header_name,
131            header_value,
132        } => {
133            println!(
134                "{} Setting header '{}' for provider '{}'",
135                "⚙".blue(),
136                header_name.yellow(),
137                provider.bold()
138            );
139            
140            config.set_header(&provider, header_name.clone(), header_value)?;
141            config.save()?;
142            
143            println!(
144                "{} Header '{}' set successfully for '{}'",
145                "✓".green(),
146                header_name.yellow(),
147                provider.bold()
148            );
149        }
150    }
151
152    Ok(())
153}
154
155async fn handle_query(provider: String, query: String, format: String, count: usize) -> Result<()> {
156    println!(
157        "{} Searching with '{}' for: {}",
158        "🔍".blue(),
159        provider.bold(),
160        query.cyan()
161    );
162    
163    let engine = SearchEngine::new()?;
164    let results = engine.search(&provider, &query, Some(count)).await?;
165    
166    match format.as_str() {
167        "json" => {
168            println!("{}", engine.format_results_json(&results)?);
169        }
170        "md" | "markdown" => {
171            println!("{}", engine.format_results_markdown(&results));
172        }
173        _ => {
174            eprintln!(
175                "{} Invalid format '{}'. Use 'json' or 'md'/'markdown'",
176                "✗".red(),
177                format
178            );
179        }
180    }
181    
182    Ok(())
183}
184
185fn show_provider_setup_help(name: &str, provider_type: SearchProviderType) {
186    println!();
187    println!("{} Next steps:", "ℹ".blue());
188    
189    match provider_type {
190        SearchProviderType::Brave => {
191            println!("  1. Get your API key from: https://brave.com/search/api/");
192            println!("  2. Set the API key:");
193            println!(
194                "     {}",
195                format!("lc search provider set {} X-Subscription-Token YOUR_API_KEY", name).bold()
196            );
197        }
198        SearchProviderType::Exa => {
199            println!("  1. Get your API key from: https://exa.ai/");
200            println!("  2. Set the API key:");
201            println!(
202                "     {}",
203                format!("lc search provider set {} x-api-key YOUR_API_KEY", name).bold()
204            );
205        }
206        SearchProviderType::Serper => {
207            println!("  1. Get your API key from: https://serper.dev/");
208            println!("  2. Set the API key:");
209            println!(
210                "     {}",
211                format!("lc search provider set {} X-API-KEY YOUR_API_KEY", name).bold()
212            );
213        }
214        SearchProviderType::SerpApi => {
215            println!("  1. Get your API key from: https://serpapi.com/");
216            println!("  2. Set the API key:");
217            println!(
218                "     {}",
219                format!("lc search provider set {} api_key YOUR_API_KEY", name).bold()
220            );
221        }
222        SearchProviderType::DuckDuckGo => {
223            println!("  {} No API key required! You can start searching immediately:", "✓".green());
224            println!(
225                "     {}",
226                format!("lc search query {} \"your query\"", name).bold()
227            );
228        }
229        SearchProviderType::Jina => {
230            println!("  1. Get your API key from: https://jina.ai/");
231            println!("  2. Set the API key:");
232            println!(
233                "     {}",
234                format!("lc search provider set {} Authorization \"Bearer YOUR_API_KEY\"", name).bold()
235            );
236            println!("  3. (Optional) Enable full content reading:");
237            println!(
238                "     {}",
239                format!("lc search provider set {} X-Engine direct", name).bold()
240            );
241            println!("  4. (Optional) Enable JSON response:");
242            println!(
243                "     {}",
244                format!("lc search provider set {} Accept application/json", name).bold()
245            );
246        }
247        SearchProviderType::Tavily => {
248            println!("  1. Get your API key from: https://tavily.com/");
249            println!("  2. Set the API key:");
250            println!(
251                "     {}",
252                format!("lc search provider set {} api-key YOUR_API_KEY", name).bold()
253            );
254        }
255    }
256    
257    println!();
258}