manx_cli/
cli.rs

1use clap::{Parser, Subcommand};
2use std::path::PathBuf;
3
4#[derive(Parser)]
5#[command(
6    name = "manx",
7    about = "A blazing-fast CLI documentation finder",
8    long_about = r#"🚀 Intelligent documentation finder with native RAG and AI synthesis
9
10CORE COMMANDS:
11  snippet <lib> [query]          Search code snippets and examples (official + local docs)
12  search <query>                 Search official documentation across the web
13  doc <lib> [topic]              Browse comprehensive documentation  
14  get <id>                       Retrieve specific results by ID
15
16LOCAL RAG COMMANDS:
17  index <path>                   Index your documents for semantic search
18  sources list                   View indexed document sources
19  sources clear                  Clear all indexed documents
20
21EMBEDDING SYSTEM - Smart semantic search (works great out of the box):
22  embedding status               View current embedding configuration
23  embedding list                 Show installed models
24  embedding download <model>     Install neural models from HuggingFace
25  embedding test <query>         Test embedding quality
26
27DEFAULT MODE (No setup required):
28  ⚡ Hash-based embeddings       Built-in algorithm (0ms, offline, 0MB storage)
29  📚 Official documentation      Context7 API integration
30  🔍 Keyword matching           Excellent for exact phrases and terms
31
32ENHANCED MODE (Optional setup for better results):
33  🧠 Neural embeddings          Install: sentence-transformers/all-MiniLM-L6-v2
34  🎯 Semantic understanding     "database connection" = "data storage"
35  📊 Intent matching            Superior relevance ranking
36  🔄 Easy switching             manx config --embedding-provider onnx:model-name
37
38AI SYNTHESIS - Get comprehensive answers with citations (optional):
39  manx config --llm-api "sk-key"           Enable AI answer synthesis
40  manx snippet react hooks                 Search + AI explanation (if configured)
41
42LOCAL RAG - Search your own documents and code (optional):
43  manx index /path/to/docs                 Index your documentation
44  manx config --rag-enabled                Enable local document search
45  manx search "authentication" --rag       Search indexed documents only
46
47QUICK START:
48  manx snippet react "state management"    Works great with defaults
49  manx embedding download all-MiniLM-L6-v2 Optional: Better semantic search
50  manx config --llm-api "sk-openai-key"    Optional: AI synthesis
51
52Use 'manx <command> --help' for detailed options."#,
53    version = get_version_info(),
54    author,
55    arg_required_else_help = true
56)]
57pub struct Cli {
58    #[command(subcommand)]
59    pub command: Option<Commands>,
60
61    /// Show detailed debug information and API requests
62    #[arg(long, help_heading = "DEBUG OPTIONS")]
63    pub debug: bool,
64
65    /// Output JSON format (useful for scripts and automation)
66    #[arg(short = 'q', long, help_heading = "OUTPUT OPTIONS")]
67    pub quiet: bool,
68
69    /// Clear all cached documentation and start fresh
70    #[arg(long, help_heading = "CACHE OPTIONS")]
71    pub clear_cache: bool,
72
73    /// Enable automatic caching of all search results
74    #[arg(long, help_heading = "CACHE OPTIONS")]
75    pub auto_cache_on: bool,
76
77    /// Disable automatic caching (manual caching only)
78    #[arg(long, help_heading = "CACHE OPTIONS")]
79    pub auto_cache_off: bool,
80
81    /// Override API key for this session
82    #[arg(long, help_heading = "GLOBAL OPTIONS")]
83    pub api_key: Option<String>,
84
85    /// Override cache directory for this session
86    #[arg(long, help_heading = "GLOBAL OPTIONS")]
87    pub cache_dir: Option<PathBuf>,
88
89    /// Work offline using only cached results
90    #[arg(long, help_heading = "GLOBAL OPTIONS")]
91    pub offline: bool,
92}
93
94#[derive(Subcommand)]
95#[allow(clippy::large_enum_variant)]
96pub enum Commands {
97    /// 📚 Browse comprehensive documentation sections and guides
98    Doc {
99        /// Library name (examples: 'fastapi', 'react@18', 'django')
100        #[arg(value_name = "LIBRARY")]
101        library: String,
102        /// Topic to search for within documentation (optional - omit for general docs)
103        #[arg(value_name = "TOPIC", default_value = "")]
104        query: String,
105        /// Save documentation to file (auto-detects format)
106        #[arg(short = 'o', long, value_name = "FILE")]
107        output: Option<PathBuf>,
108        /// Limit number of sections shown (default: 10, use 0 for unlimited)
109        #[arg(short = 'l', long, value_name = "NUMBER")]
110        limit: Option<usize>,
111        /// Force retrieval-only mode (disable LLM synthesis even if API key configured)
112        #[arg(long)]
113        no_llm: bool,
114        /// Search locally indexed documents instead of Context7 API
115        #[arg(long)]
116        rag: bool,
117    },
118
119    /// 🔍 Search code snippets and examples with AI-powered understanding
120    ///
121    /// ENHANCED SEARCH:
122    ///   • Searches official docs (Context7) + your indexed documents (RAG)
123    ///   • Semantic understanding finds relevant content with different wording
124    ///   • Quote prioritization: "useEffect cleanup" gets 10x higher relevance
125    ///   • Optional AI synthesis provides comprehensive answers with citations
126    ///
127    /// SEMANTIC FEATURES:
128    ///   • "memory leaks" finds: "memory cleanup", "performance issues", "leak prevention"
129    ///   • "authentication" finds: "auth", "login", "security", "credentials"
130    ///   • Version-specific: react@18, django@4.2
131    ///
132    /// AI SYNTHESIS:
133    ///   • Configure: manx config --llm-api "sk-your-key"
134    ///   • Get answers: manx snippet "react hooks best practices"
135    ///   • Force retrieval: manx snippet react hooks --no-llm
136    ///
137    /// EXAMPLES:
138    ///   manx snippet react "useEffect cleanup"           # Semantic search with phrase priority
139    ///   manx snippet "database pooling" --llm-api        # Get AI answer with citations  
140    ///   manx snippet fastapi middleware --no-llm         # Raw results only
141    ///   manx snippet python "async functions" --rag      # Search your indexed code files
142    Snippet {
143        /// Library name (examples: 'fastapi', 'react@18', 'vue@3')
144        #[arg(value_name = "LIBRARY")]
145        library: String,
146        /// Search query for specific code snippets
147        #[arg(value_name = "QUERY")]
148        query: Option<String>,
149        /// Export results to file (format auto-detected by extension: .md, .json)
150        #[arg(short = 'o', long, value_name = "FILE")]
151        output: Option<PathBuf>,
152        /// Work offline using only cached results (no network requests)
153        #[arg(long)]
154        offline: bool,
155        /// Save specific search results by number (e.g., --save 1,3,7)
156        #[arg(long, value_name = "NUMBERS")]
157        save: Option<String>,
158        /// Save all search results to file
159        #[arg(long)]
160        save_all: bool,
161        /// Export in JSON format instead of Markdown (use with --save or --save-all)
162        #[arg(long)]
163        json: bool,
164        /// Limit number of results shown (default: 10, use 0 for unlimited)
165        #[arg(short = 'l', long, value_name = "NUMBER")]
166        limit: Option<usize>,
167        /// Force retrieval-only mode (disable LLM synthesis even if API key configured)
168        #[arg(long)]
169        no_llm: bool,
170        /// Search locally indexed documents instead of Context7 API (requires: manx config --rag-enabled)
171        #[arg(long)]
172        rag: bool,
173    },
174
175    /// 🔍 Search official documentation across the web
176    ///
177    /// INTELLIGENT WEB SEARCH:
178    ///   • Prioritizes official documentation sites (docs.python.org, reactjs.org, etc.)
179    ///   • Uses semantic embeddings for relevance matching  
180    ///   • Falls back to trusted community sources with clear notification
181    ///   • Optional LLM verification ensures result authenticity
182    ///
183    /// OFFICIAL-FIRST STRATEGY:
184    ///   • Always searches official sources first (10x relevance boost)
185    ///   • Expands to community sources only if insufficient official results
186    ///   • Transparent fallback notifications: "⚠️ Expanded to community sources"
187    ///
188    /// EXAMPLES:
189    ///   manx search "hydra configuration commands"      # Auto-detects LLM availability  
190    ///   manx search "react hooks best practices"        # Uses LLM if API key configured
191    ///   manx search "python async await" --no-llm       # Force embeddings-only mode
192    ///   manx search "authentication" --rag              # Search your indexed documents
193    Search {
194        /// Search query for official documentation
195        #[arg(value_name = "QUERY")]
196        query: String,
197        /// Disable LLM verification (use embeddings-only mode even if API key is configured)
198        #[arg(long)]
199        no_llm: bool,
200        /// Export results to file (format auto-detected by extension: .md, .json)
201        #[arg(short = 'o', long, value_name = "FILE")]
202        output: Option<PathBuf>,
203        /// Limit number of results shown (default: 8)
204        #[arg(short = 'l', long, value_name = "NUMBER")]
205        limit: Option<usize>,
206        /// Search locally indexed documents instead of web search (requires: manx config --rag-enabled)
207        #[arg(long)]
208        rag: bool,
209    },
210
211    /// 📥 Get specific item by ID (doc-3, section-5, etc.)
212    Get {
213        /// Item ID from previous search or doc command output
214        #[arg(value_name = "ITEM_ID")]
215        id: String,
216        /// Save retrieved item to file
217        #[arg(short = 'o', long, value_name = "FILE")]
218        output: Option<PathBuf>,
219    },
220
221    /// 🗂️ Manage local documentation cache
222    Cache {
223        #[command(subcommand)]
224        command: CacheCommands,
225    },
226
227    /// ⚙️ Configure Manx settings, API keys, and AI integration
228    Config {
229        /// Display current configuration settings
230        #[arg(long)]
231        show: bool,
232        /// Set Context7 API key (get one at context7.com)
233        #[arg(long, value_name = "KEY")]
234        api_key: Option<String>,
235        /// Set custom cache directory path
236        #[arg(long, value_name = "PATH")]
237        cache_dir: Option<PathBuf>,
238        /// Enable/disable automatic caching (values: on, off)
239        #[arg(long, value_name = "MODE")]
240        auto_cache: Option<String>,
241        /// Set cache expiration time in hours (default: 24)
242        #[arg(long, value_name = "HOURS")]
243        cache_ttl: Option<u64>,
244        /// Set maximum cache size in MB (default: 100)
245        #[arg(long, value_name = "SIZE")]
246        max_cache_size: Option<u64>,
247        /// Set OpenAI API key for GPT models
248        #[arg(long, value_name = "API_KEY")]
249        openai_api: Option<String>,
250        /// Set Anthropic API key for Claude models
251        #[arg(long, value_name = "API_KEY")]
252        anthropic_api: Option<String>,
253        /// Set Groq API key for fast inference
254        #[arg(long, value_name = "API_KEY")]
255        groq_api: Option<String>,
256        /// Set OpenRouter API key for multi-model access
257        #[arg(long, value_name = "API_KEY")]
258        openrouter_api: Option<String>,
259        /// Set HuggingFace API key for open-source models
260        #[arg(long, value_name = "API_KEY")]
261        huggingface_api: Option<String>,
262        /// Set custom endpoint URL for self-hosted models
263        #[arg(long, value_name = "URL")]
264        custom_endpoint: Option<String>,
265        /// Set preferred LLM provider (openai, anthropic, groq, openrouter, huggingface, custom, auto)
266        #[arg(long, value_name = "PROVIDER")]
267        llm_provider: Option<String>,
268        /// Set specific model name (overrides provider defaults)
269        #[arg(long, value_name = "MODEL")]
270        llm_model: Option<String>,
271        /// Legacy option - Set LLM API key (deprecated, use provider-specific options)
272        #[arg(long, value_name = "API_KEY")]
273        llm_api: Option<String>,
274        /// Enable/disable local RAG system (values: on, off)
275        #[arg(long, value_name = "MODE")]
276        rag: Option<String>,
277        /// Add custom official documentation domain (format: domain.com)
278        #[arg(long, value_name = "DOMAIN")]
279        add_official_domain: Option<String>,
280        /// Set embedding provider for RAG system (hash, onnx:model, ollama:model, openai:model, huggingface:model, custom:url)
281        #[arg(long, value_name = "PROVIDER")]
282        embedding_provider: Option<String>,
283        /// Set embedding API key for API-based providers
284        #[arg(long, value_name = "API_KEY")]
285        embedding_api_key: Option<String>,
286        /// Set embedding model path for local models
287        #[arg(long, value_name = "PATH")]
288        embedding_model_path: Option<PathBuf>,
289        /// Set embedding dimension (default: 384)
290        #[arg(long, value_name = "DIMENSION")]
291        embedding_dimension: Option<usize>,
292    },
293
294    /// 📁 Index local documents or web URLs for RAG search
295    ///
296    /// INDEXING SOURCES:
297    ///   • Local files: manx index ~/docs/api.md
298    ///   • Directories: manx index ~/documentation/  
299    ///   • Web URLs: manx index https://docs.rust-lang.org/book/ch01-01-installation.html
300    ///
301    /// SUPPORTED FORMATS:
302    ///   • Documents: .md, .txt, .docx, .pdf (with security validation)
303    ///   • Web content: HTML pages (auto text extraction)
304    ///
305    /// SECURITY FEATURES:
306    ///   • PDF processing disabled by default (configure to enable)  
307    ///   • URL validation (HTTP/HTTPS only)
308    ///   • Content sanitization and size limits
309    ///
310    /// EXAMPLES:
311    ///   manx index ~/my-docs/                              # Index directory
312    ///   manx index https://docs.python.org --crawl        # Deep crawl documentation site
313    ///   manx index https://fastapi.tiangolo.com --crawl --max-depth 2  # Limited depth crawl
314    ///   manx index api.pdf --alias "API Reference"        # Index with custom alias
315    Index {
316        /// Path to document/directory or URL to index
317        #[arg(value_name = "PATH_OR_URL")]
318        path: String,
319        /// Optional alias for the indexed source
320        #[arg(long, value_name = "ALIAS")]
321        id: Option<String>,
322        /// Enable deep crawling for URLs (follows links to discover more pages)
323        #[arg(long)]
324        crawl: bool,
325        /// Maximum crawl depth for deep crawling (default: 3)
326        #[arg(long, value_name = "DEPTH")]
327        max_depth: Option<u32>,
328        /// Maximum number of pages to crawl (default: no limit)
329        #[arg(long, value_name = "PAGES")]
330        max_pages: Option<u32>,
331    },
332
333    /// 📂 Manage indexed document sources
334    Sources {
335        #[command(subcommand)]
336        command: SourceCommands,
337    },
338
339    /// 🔗 Open a specific documentation section by ID
340    Open {
341        /// Section ID from previous doc command output
342        #[arg(value_name = "SECTION_ID")]
343        id: String,
344        /// Save opened section to file
345        #[arg(short = 'o', long, value_name = "FILE")]
346        output: Option<PathBuf>,
347    },
348
349    /// 🔄 Update Manx to the latest version from GitHub
350    Update {
351        /// Check for updates without installing
352        #[arg(long)]
353        check: bool,
354        /// Force update even if already on latest version
355        #[arg(long)]
356        force: bool,
357    },
358
359    /// 🧠 Manage embedding models and providers for semantic search
360    ///
361    /// EMBEDDING PROVIDERS:
362    ///   • hash: Hash-based embeddings (default, fast, lightweight)
363    ///   • onnx:model: Local ONNX models (requires download)
364    ///   • ollama:model: Ollama API (requires Ollama server)
365    ///   • openai:model: OpenAI embeddings API (requires API key)
366    ///   • huggingface:model: HuggingFace embeddings API (requires API key)
367    ///   • custom:url: Custom endpoint API
368    ///
369    /// EXAMPLES:
370    ///   manx embedding status                     # Show current provider and models
371    ///   manx embedding set hash                   # Use hash-based (default)
372    ///   manx embedding set onnx:all-MiniLM-L6-v2 # Use local ONNX model
373    ///   manx embedding set ollama:nomic-embed-text # Use Ollama model
374    ///   manx embedding download all-MiniLM-L6-v2  # Download ONNX model
375    ///   manx embedding test "sample query"        # Test current embedding setup
376    Embedding {
377        #[command(subcommand)]
378        command: EmbeddingCommands,
379    },
380}
381
382#[derive(Subcommand)]
383pub enum CacheCommands {
384    /// Remove all cached documentation and free up disk space
385    Clear,
386    /// Display cache size, file count, and storage statistics  
387    Stats,
388    /// Show all currently cached libraries and their sizes
389    List,
390}
391
392#[derive(Subcommand)]
393pub enum SourceCommands {
394    /// List all indexed document sources
395    List,
396    /// Add a document source to the index
397    Add {
398        /// Path to document or directory
399        path: PathBuf,
400        /// Optional alias for the source
401        #[arg(long)]
402        id: Option<String>,
403    },
404    /// Clear all indexed documents
405    Clear,
406}
407
408#[derive(Subcommand)]
409pub enum EmbeddingCommands {
410    /// Show current embedding provider status and configuration
411    Status,
412    /// Set embedding provider (hash, onnx:model, ollama:model, openai:model, huggingface:model, custom:url)
413    Set {
414        /// Provider specification
415        #[arg(value_name = "PROVIDER")]
416        provider: String,
417        /// API key for API-based providers
418        #[arg(long, value_name = "API_KEY")]
419        api_key: Option<String>,
420        /// Custom endpoint URL (for custom provider)
421        #[arg(long, value_name = "URL")]
422        endpoint: Option<String>,
423        /// Embedding dimension (default: 384)
424        #[arg(long, value_name = "DIMENSION")]
425        dimension: Option<usize>,
426    },
427    /// Download and install a local ONNX model
428    Download {
429        /// Model name to download (e.g., 'all-MiniLM-L6-v2')
430        #[arg(value_name = "MODEL_NAME")]
431        model: String,
432        /// Force redownload if model already exists
433        #[arg(long)]
434        force: bool,
435    },
436    /// List available models for download or installed models
437    List {
438        /// List available models for download instead of installed models
439        #[arg(long)]
440        available: bool,
441    },
442    /// Test current embedding setup with a sample query
443    Test {
444        /// Query text to test embeddings with
445        #[arg(value_name = "QUERY")]
446        query: String,
447        /// Show detailed embedding vector information
448        #[arg(long)]
449        verbose: bool,
450    },
451    /// Remove downloaded local models
452    Remove {
453        /// Model name to remove
454        #[arg(value_name = "MODEL_NAME")]
455        model: String,
456    },
457}
458
459impl Cli {
460    pub fn parse_args() -> Self {
461        Cli::parse()
462    }
463}
464
465fn get_version_info() -> &'static str {
466    concat!(
467        "\n",
468        "__| |__________________________________________________________________________| |__\n",
469        "__   __________________________________________________________________________   __\n",
470        "  | |                                                                          | |  \n",
471        "  | |       ███        ██████   ██████   █████████   ██████   █████ █████ █████| |  \n",
472        "  | |      ░░░██      ░░██████ ██████   ███░░░░░███ ░░██████ ░░███ ░░███ ░░███ | |  \n",
473        "  | | ██     ░░██      ░███░█████░███  ░███    ░███  ░███░███ ░███  ░░███ ███  | |  \n",
474        "  | |░░       ░░███    ░███░░███ ░███  ░███████████  ░███░░███░███   ░░█████   | |  \n",
475        "  | |          ██░     ░███ ░░░  ░███  ░███░░░░░███  ░███ ░░██████    ███░███  | |  \n",
476        "  | |         ██       ░███      ░███  ░███    ░███  ░███  ░░█████   ███ ░░███ | |  \n",
477        "  | | ██    ███        █████     █████ █████   █████ █████  ░░█████ █████ █████| |  \n",
478        "  | |░░    ░░░        ░░░░░     ░░░░░ ░░░░░   ░░░░░ ░░░░░    ░░░░░ ░░░░░ ░░░░░ | |  \n",
479        "__| |__________________________________________________________________________| |__\n",
480        "__   __________________________________________________________________________   __\n",
481        "  | |                                                                          | |  \n",
482        "\n",
483        "  v",
484        env!("CARGO_PKG_VERSION"),
485        " • blazing-fast docs finder\n"
486    )
487}