vtcode_core/cli/
args.rs

1//! CLI argument parsing and configuration
2
3use crate::config::models::ModelId;
4use clap::{ColorChoice, Parser, Subcommand, ValueEnum, ValueHint};
5use colorchoice_clap::Color as ColorSelection;
6use std::path::PathBuf;
7
8/// Main CLI structure for vtcode with advanced features
9#[derive(Parser, Debug)]
10#[command(
11    name = "vtcode",
12    version,
13    about = "Advanced coding agent with Decision Ledger\n\nFeatures:\n• Single-agent architecture with Decision Ledger for reliable task execution\n• Tree-sitter powered code analysis (Rust, Python, JavaScript, TypeScript, Go, Java)\n• Multi-provider LLM support (Gemini, OpenAI, Anthropic, DeepSeek)\n• Real-time performance monitoring and benchmarking\n• Enhanced security with tool policies and sandboxing\n• Research-preview context management and conversation compression\n\nQuick Start:\n  export GEMINI_API_KEY=\"your_key\"\n  vtcode chat",
14    color = ColorChoice::Auto
15)]
16pub struct Cli {
17    /// Color output selection (auto, always, never)
18    #[command(flatten)]
19    pub color: ColorSelection,
20
21    /// Optional positional path to run vtcode against a different workspace
22    #[arg(
23        value_name = "WORKSPACE",
24        value_hint = ValueHint::DirPath,
25        global = true
26    )]
27    pub workspace_path: Option<PathBuf>,
28
29    /// LLM Model ID with latest model support
30    ///
31    /// Available providers & models:
32    ///   • gemini-2.5-flash-preview-05-20 - Latest fast Gemini model (default)
33    ///   • gemini-2.5-flash - Fast, cost-effective
34    ///   • gemini-2.5-pro - Latest, most capable
35    ///   • gpt-5 - OpenAI's latest
36    ///   • claude-sonnet-4-5 - Anthropic's latest
37    ///   • qwen/qwen3-4b-2507 - Qwen3 local model
38    ///   • deepseek-reasoner - DeepSeek reasoning model
39    ///   • x-ai/grok-code-fast-1 - OpenRouter Grok fast coding model
40    ///   • qwen/qwen3-coder - OpenRouter Qwen3 Coder optimized for IDE usage
41    ///   • grok-2-latest - xAI Grok flagship model
42    #[arg(long, global = true)]
43    pub model: Option<String>,
44
45    /// **LLM Provider** with expanded support
46    ///
47    /// Available providers:
48    ///   • gemini - Google Gemini (default)
49    ///   • openai - OpenAI GPT models
50    ///   • anthropic - Anthropic Claude models
51    ///   • deepseek - DeepSeek models
52    ///   • openrouter - OpenRouter marketplace models
53    ///   • xai - xAI Grok models
54    ///   • zai - Z.AI GLM models
55    ///
56    /// Example: --provider deepseek
57    #[arg(long, global = true)]
58    pub provider: Option<String>,
59
60    /// **API key environment variable**\n\n**Auto-detects based on provider:**\n• Gemini: `GEMINI_API_KEY`\n• OpenAI: `OPENAI_API_KEY`\n• Anthropic: `ANTHROPIC_API_KEY`\n• DeepSeek: `DEEPSEEK_API_KEY`\n• OpenRouter: `OPENROUTER_API_KEY`\n• xAI: `XAI_API_KEY`\n• Z.AI: `ZAI_API_KEY`\n\n**Override:** --api-key-env CUSTOM_KEY
61    #[arg(long, global = true, default_value = crate::config::constants::defaults::DEFAULT_API_KEY_ENV)]
62    pub api_key_env: String,
63
64    /// **Workspace root directory for file operations**
65    ///
66    /// Security: All file operations restricted to this path
67    /// Default: Current directory
68    #[arg(
69        long,
70        global = true,
71        alias = "workspace-dir",
72        value_name = "PATH",
73        value_hint = ValueHint::DirPath
74    )]
75    pub workspace: Option<PathBuf>,
76
77    /// **Enable tree-sitter code analysis**
78    ///
79    /// Features:
80    ///   • AST-based code parsing
81    ///   • Symbol extraction and navigation
82    ///   • Intelligent refactoring suggestions
83    ///   • Multi-language support (Rust, Python, JS, TS, Go, Java)
84    #[arg(long, global = true)]
85    pub enable_tree_sitter: bool,
86
87    /// **Enable performance monitoring**
88    ///
89    /// Tracks:
90    ///   • Token usage and API costs
91    ///   • Response times and latency
92    ///   • Tool execution metrics
93    ///   • Memory usage patterns
94    #[arg(long, global = true)]
95    pub performance_monitoring: bool,
96
97    /// **Enable research-preview features**
98    ///
99    /// Includes:
100    ///   • Advanced context compression
101    ///   • Conversation summarization
102    ///   • Enhanced error recovery
103    ///   • Decision transparency tracking
104    #[arg(long, global = true)]
105    pub research_preview: bool,
106
107    /// **Security level** for tool execution
108    ///
109    /// Options:
110    ///   • strict - Maximum security, prompt for all tools
111    ///   • moderate - Balance security and usability
112    ///   • permissive - Minimal restrictions (not recommended)
113    #[arg(long, global = true, default_value = "moderate")]
114    pub security_level: String,
115
116    /// **Show diffs for file changes in chat interface**
117    ///
118    /// Features:
119    ///   • Real-time diff rendering
120    ///   • Syntax highlighting
121    ///   • Line-by-line changes
122    ///   • Before/after comparison
123    #[arg(long, global = true)]
124    pub show_file_diffs: bool,
125
126    /// **Maximum concurrent async operations**
127    ///
128    /// Default: 5
129    /// Higher values: Better performance but more resource usage
130    #[arg(long, global = true, default_value_t = 5)]
131    pub max_concurrent_ops: usize,
132
133    /// **Maximum API requests per minute**
134    ///
135    /// Default: 30
136    /// Purpose: Prevents rate limiting
137    #[arg(long, global = true, default_value_t = 30)]
138    pub api_rate_limit: usize,
139
140    /// **Maximum tool calls per session**
141    ///
142    /// Default: 10
143    /// Purpose: Prevents runaway execution
144    #[arg(long, global = true, default_value_t = 10)]
145    pub max_tool_calls: usize,
146
147    /// **Enable debug output for troubleshooting**
148    ///
149    /// Shows:
150    ///   • Tool call details
151    ///   • API request/response
152    ///   • Internal agent state
153    ///   • Performance metrics
154    #[arg(long, global = true)]
155    pub debug: bool,
156
157    /// **Enable verbose logging**
158    ///
159    /// Includes:
160    ///   • Detailed operation logs
161    ///   • Context management info
162    ///   • Agent coordination details
163    #[arg(long, global = true)]
164    pub verbose: bool,
165
166    /// **Configuration file path**
167    ///
168    /// Supported formats: TOML
169    /// Default locations: ./vtcode.toml, ~/.vtcode/vtcode.toml
170    #[arg(long, global = true)]
171    pub config: Option<PathBuf>,
172
173    /// Log level (error, warn, info, debug, trace)
174    ///
175    /// Default: info
176    #[arg(long, global = true, default_value = "info")]
177    pub log_level: String,
178
179    /// Disable color output
180    ///
181    /// Useful for: Log files, CI/CD pipelines
182    #[arg(long, global = true)]
183    pub no_color: bool,
184
185    /// Select UI theme for ANSI styling (e.g., ciapre-dark, ciapre-blue)
186    #[arg(long, global = true, value_name = "THEME")]
187    pub theme: Option<String>,
188
189    /// **Skip safety confirmations**
190    ///
191    /// Warning: Reduces security, use with caution
192    #[arg(long, global = true)]
193    pub skip_confirmations: bool,
194
195    /// **Enable full-auto mode (no interaction) or run a headless task**
196    ///
197    /// Use without a value to launch the interactive UI in full-auto mode.
198    /// Provide a prompt value to execute a single autonomous task headlessly.
199    /// The alias `--auto` is provided for ergonomic scripting.
200    #[arg(
201        long = "full-auto",
202        visible_alias = "auto",
203        global = true,
204        value_name = "PROMPT",
205        num_args = 0..=1,
206        default_missing_value = "",
207        value_hint = ValueHint::Other
208    )]
209    pub full_auto: Option<String>,
210
211    #[command(subcommand)]
212    pub command: Option<Commands>,
213}
214
215/// Available commands with comprehensive features
216#[derive(Subcommand, Debug)]
217pub enum Commands {
218    /// Start Agent Client Protocol bridge for IDE integrations
219    #[command(name = "acp")]
220    AgentClientProtocol {
221        /// Client to connect over ACP
222        #[arg(value_enum, default_value_t = AgentClientProtocolTarget::Zed)]
223        target: AgentClientProtocolTarget,
224    },
225
226    /// **Interactive AI coding assistant** with advanced capabilities
227    ///
228    /// Features:
229    ///   • Real-time code generation and editing
230    ///   • Tree-sitter powered analysis
231    ///   • Research-preview context management
232    ///
233    /// Usage: vtcode chat
234    Chat,
235
236    /// **Single prompt mode** - prints model reply without tools
237    ///
238    /// Perfect for:
239    ///   • Quick questions
240    ///   • Code explanations
241    ///   • Simple queries
242    ///
243    /// Example: vtcode ask "Explain Rust ownership"
244    Ask { prompt: String },
245
246    /// **Verbose interactive chat** with enhanced transparency
247    ///
248    /// Shows:
249    ///   • Tool execution details
250    ///   • API request/response
251    ///   • Performance metrics
252    ///
253    /// Usage: vtcode chat-verbose
254    ChatVerbose,
255
256    /// **Analyze workspace** with tree-sitter integration
257    ///
258    /// Provides:
259    ///   • Project structure analysis
260    ///   • Language detection
261    ///   • Code complexity metrics
262    ///   • Dependency insights
263    ///   • Symbol extraction
264    ///
265    /// Usage: vtcode analyze
266    Analyze,
267
268    /// **Display performance metrics** and system status\n\n**Shows:**\n• Token usage and API costs\n• Response times and latency\n• Tool execution statistics\n• Memory usage patterns\n\n**Usage:** vtcode performance
269    Performance,
270
271    /// Pretty-print trajectory logs and show basic analytics
272    ///
273    /// Sources:
274    ///   • .vtcode/logs/trajectory.jsonl (default)
275    /// Options:
276    ///   • --file to specify an alternate path
277    ///   • --top to limit report rows (default: 10)
278    ///
279    /// Shows:
280    ///   • Class distribution with percentages
281    ///   • Model usage statistics
282    ///   • Tool success rates with status indicators
283    ///   • Time range of logged activity
284    #[command(name = "trajectory")]
285    Trajectory {
286        /// Optional path to trajectory JSONL file
287        #[arg(long)]
288        file: Option<std::path::PathBuf>,
289        /// Number of top entries to show for each section
290        #[arg(long, default_value_t = 10)]
291        top: usize,
292    },
293
294    /// **Benchmark against SWE-bench evaluation framework**
295    ///
296    /// Features:
297    ///   • Automated performance testing
298    ///   • Comparative analysis across models
299    ///   • Benchmark scoring and metrics
300    ///   • Optimization insights
301    ///
302    /// Usage: vtcode benchmark --task-file swe_task.json --output reports/result.json
303    Benchmark {
304        /// Path to a JSON benchmark specification. Falls back to STDIN when omitted.
305        #[arg(long, value_name = "PATH", value_hint = ValueHint::FilePath)]
306        task_file: Option<PathBuf>,
307        /// Inline JSON specification for quick experiments.
308        #[arg(long, value_name = "JSON")]
309        task: Option<String>,
310        /// Optional path to write the structured benchmark report.
311        #[arg(long, value_name = "PATH", value_hint = ValueHint::FilePath)]
312        output: Option<PathBuf>,
313        /// Limit the number of tasks executed from the specification.
314        #[arg(long, value_name = "COUNT")]
315        max_tasks: Option<usize>,
316    },
317
318    /// **Create complete Rust project with advanced features**
319    ///
320    /// Features:
321    ///   • Web frameworks (Axum, Rocket, Warp)
322    ///   • Database integration
323    ///   • Authentication systems
324    ///   • Testing setup
325    ///   • Tree-sitter integration
326    ///
327    /// Example: vtcode create-project myapp web,auth,db
328    CreateProject { name: String, features: Vec<String> },
329
330    /// **Compress conversation context** for long-running sessions
331    ///
332    /// Benefits:
333    ///   • Reduced token usage
334    ///   • Faster responses
335    ///   • Memory optimization
336    ///   • Context preservation
337    ///
338    /// Usage: vtcode compress-context
339    CompressContext,
340
341    /// **Revert agent to a previous snapshot
342    ///
343    /// Features:
344    ///   • Revert to any previous turn
345    ///   • Partial reverts (memory, context, full)
346    ///   • Safe rollback with validation
347    ///
348    /// Examples:
349    ///   vtcode revert --turn 5
350    ///   vtcode revert --turn 3 --partial memory
351    Revert {
352        /// Turn number to revert to
353        ///
354        /// Required: Yes
355        /// Example: 5
356        #[arg(short, long)]
357        turn: usize,
358
359        /// Scope of revert operation
360        ///
361        /// Options: memory, context, full
362        /// Default: full
363        /// Examples:
364        ///   --partial memory (revert conversation only)
365        ///   --partial context (revert decisions/errors only)
366        #[arg(short, long)]
367        partial: Option<String>,
368    },
369
370    /// **List all available snapshots**
371    ///
372    /// Shows:
373    ///   • Snapshot ID and turn number
374    ///   • Creation timestamp
375    ///   • Description
376    ///   • File size and compression status
377    ///
378    /// Usage: vtcode snapshots
379    Snapshots,
380
381    /// **Clean up old snapshots**
382    ///
383    /// Features:
384    ///   • Remove snapshots beyond limit
385    ///   • Configurable retention policy
386    ///   • Safe deletion with confirmation
387    ///
388    /// Examples:
389    ///   vtcode cleanup-snapshots
390    ///   vtcode cleanup-snapshots --max 20
391    #[command(name = "cleanup-snapshots")]
392    CleanupSnapshots {
393        /// Maximum number of snapshots to keep
394        ///
395        /// Default: 50
396        /// Example: --max 20
397        #[arg(short, long, default_value_t = 50)]
398        max: usize,
399    },
400
401    /// **Initialize project** with enhanced dot-folder structure
402    ///
403    /// Features:
404    ///   • Creates project directory structure
405    ///   • Sets up config, cache, embeddings directories
406    ///   • Creates .project metadata file
407    ///   • Tree-sitter parser setup
408    ///
409    /// Usage: vtcode init
410    Init,
411
412    /// **Initialize project with dot-folder structure** - sets up ~/.vtcode/projects/<project-name> structure
413    ///
414    /// Features:
415    ///   • Creates project directory structure in ~/.vtcode/projects/
416    ///   • Sets up config, cache, embeddings, and retrieval directories
417    ///   • Creates .project metadata file
418    ///   • Migrates existing config/cache files with user confirmation
419    ///
420    /// Examples:
421    ///   vtcode init-project
422    ///   vtcode init-project --name my-project
423    ///   vtcode init-project --force
424    #[command(name = "init-project")]
425    InitProject {
426        /// Project name - defaults to current directory name
427        #[arg(long)]
428        name: Option<String>,
429
430        /// Force initialization - overwrite existing project structure
431        #[arg(long)]
432        force: bool,
433
434        /// Migrate existing files - move existing config/cache files to new structure
435        #[arg(long)]
436        migrate: bool,
437    },
438
439    /// **Generate configuration file - creates a vtcode.toml configuration file
440    ///
441    /// Features:
442    ///   • Generate default configuration
443    ///   • Support for global (home directory) and local configuration
444    ///   • TOML format with comprehensive settings
445    ///   • Tree-sitter and performance monitoring settings
446    ///
447    /// Examples:
448    ///   vtcode config
449    ///   vtcode config --output ./custom-config.toml
450    ///   vtcode config --global
451    Config {
452        /// Output file path - where to save the configuration file
453        #[arg(long)]
454        output: Option<std::path::PathBuf>,
455
456        /// Create in user home directory - creates ~/.vtcode/vtcode.toml
457        #[arg(long)]
458        global: bool,
459    },
460
461    /// **Manage tool execution policies** - control which tools the agent can use
462    ///
463    /// Features:
464    ///   • Granular tool permissions
465    ///   • Security level presets
466    ///   • Audit logging
467    ///   • Safe tool execution
468    ///
469    /// Examples:
470    ///   vtcode tool-policy status
471    ///   vtcode tool-policy allow file-write
472    ///   vtcode tool-policy deny shell-exec
473    #[command(name = "tool-policy")]
474    ToolPolicy {
475        #[command(subcommand)]
476        command: crate::cli::tool_policy_commands::ToolPolicyCommands,
477    },
478
479    /// **Manage models and providers** - configure and switch between LLM providers\n\n**Features:**\n• Support for latest models (DeepSeek, etc.)\n• Provider configuration and testing\n• Model performance comparison\n• API key management\n\n**Examples:**\n  vtcode models list\n  vtcode models set-provider deepseek\n  vtcode models set-model deepseek-reasoner
480    Models {
481        #[command(subcommand)]
482        command: ModelCommands,
483    },
484
485    /// **Security and safety management**\n\n**Features:**\n• Security scanning and vulnerability detection\n• Audit logging and monitoring\n• Access control management\n• Privacy protection settings\n\n**Usage:** vtcode security
486    Security,
487
488    /// **Tree-sitter code analysis tools**\n\n**Features:**\n• AST-based code parsing\n• Symbol extraction and navigation\n• Code complexity analysis\n• Multi-language refactoring\n\n**Usage:** vtcode tree-sitter
489    #[command(name = "tree-sitter")]
490    TreeSitter,
491
492    /// **Generate or display man pages** for VTCode commands\n\n**Features:**\n• Generate Unix man pages for all commands\n• Display detailed command documentation\n• Save man pages to files\n• Comprehensive help for all VTCode features\n\n**Examples:**\n  vtcode man\n  vtcode man chat\n  vtcode man chat --output chat.1
493    Man {
494        /// **Command name** to generate man page for (optional)\n\n**Available commands:**\n• chat, ask, analyze, performance, benchmark\n• create-project, init, man\n\n**If not specified, shows main VTCode man page**
495        command: Option<String>,
496
497        /// **Output file path** to save man page\n\n**Format:** Standard Unix man page format (.1, .8, etc.)\n**Default:** Display to stdout
498        #[arg(short, long)]
499        output: Option<std::path::PathBuf>,
500    },
501}
502
503/// Supported Agent Client Protocol clients
504#[derive(Clone, Copy, Debug, ValueEnum)]
505pub enum AgentClientProtocolTarget {
506    /// Zed IDE integration
507    Zed,
508}
509
510/// Model management commands with concise, actionable help
511#[derive(Subcommand, Debug)]
512pub enum ModelCommands {
513    /// List all providers and models with status indicators
514    List,
515
516    /// Set default provider (gemini, openai, anthropic, deepseek)
517    #[command(name = "set-provider")]
518    SetProvider {
519        /// Provider name to set as default
520        provider: String,
521    },
522
523    /// Set default model (e.g., deepseek-reasoner, gpt-5, claude-sonnet-4-5)
524    #[command(name = "set-model")]
525    SetModel {
526        /// Model name to set as default
527        model: String,
528    },
529
530    /// Configure provider settings (API keys, base URLs, models)
531    Config {
532        /// Provider name to configure
533        provider: String,
534
535        /// API key for the provider
536        #[arg(long)]
537        api_key: Option<String>,
538
539        /// Base URL for local providers
540        #[arg(long)]
541        base_url: Option<String>,
542
543        /// Default model for this provider
544        #[arg(long)]
545        model: Option<String>,
546    },
547
548    /// Test provider connectivity and validate configuration
549    Test {
550        /// Provider name to test
551        provider: String,
552    },
553
554    /// Compare model performance across providers (coming soon)
555    Compare,
556
557    /// Show detailed model information and specifications
558    Info {
559        /// Model name to get information about
560        model: String,
561    },
562}
563
564/// Configuration file structure with latest features
565#[derive(Debug)]
566pub struct ConfigFile {
567    pub model: Option<String>,
568    pub provider: Option<String>,
569    pub api_key_env: Option<String>,
570    pub verbose: Option<bool>,
571    pub log_level: Option<String>,
572    pub workspace: Option<PathBuf>,
573    pub tools: Option<ToolConfig>,
574    pub context: Option<ContextConfig>,
575    pub logging: Option<LoggingConfig>,
576    pub tree_sitter: Option<TreeSitterConfig>,
577    pub performance: Option<PerformanceConfig>,
578    pub security: Option<SecurityConfig>,
579}
580
581/// Tool configuration from config file
582#[derive(Debug, serde::Deserialize)]
583pub struct ToolConfig {
584    pub enable_validation: Option<bool>,
585    pub max_execution_time_seconds: Option<u64>,
586    pub allow_file_creation: Option<bool>,
587    pub allow_file_deletion: Option<bool>,
588}
589
590/// Context management configuration
591#[derive(Debug, serde::Deserialize)]
592pub struct ContextConfig {
593    pub max_context_length: Option<usize>,
594    pub compression_threshold: Option<usize>,
595    pub summarization_interval: Option<usize>,
596}
597
598/// Logging configuration
599#[derive(Debug, serde::Deserialize)]
600pub struct LoggingConfig {
601    pub file_logging: Option<bool>,
602    pub log_directory: Option<String>,
603    pub max_log_files: Option<usize>,
604    pub max_log_size_mb: Option<usize>,
605}
606
607/// Tree-sitter configuration
608#[derive(Debug, serde::Deserialize)]
609pub struct TreeSitterConfig {
610    pub enabled: Option<bool>,
611    pub supported_languages: Option<Vec<String>>,
612    pub max_file_size_kb: Option<usize>,
613    pub enable_symbol_extraction: Option<bool>,
614    pub enable_complexity_analysis: Option<bool>,
615}
616
617/// Performance monitoring configuration
618#[derive(Debug, serde::Deserialize)]
619pub struct PerformanceConfig {
620    pub enabled: Option<bool>,
621    pub track_token_usage: Option<bool>,
622    pub track_api_costs: Option<bool>,
623    pub track_response_times: Option<bool>,
624    pub enable_benchmarking: Option<bool>,
625    pub metrics_retention_days: Option<usize>,
626}
627
628/// Security configuration
629#[derive(Debug, serde::Deserialize)]
630pub struct SecurityConfig {
631    pub level: Option<String>,
632    pub enable_audit_logging: Option<bool>,
633    pub enable_vulnerability_scanning: Option<bool>,
634    pub allow_external_urls: Option<bool>,
635    pub max_file_access_depth: Option<usize>,
636}
637
638impl Default for Cli {
639    fn default() -> Self {
640        Self {
641            color: ColorSelection {
642                color: ColorChoice::Auto,
643            },
644            workspace_path: None,
645            model: Some(ModelId::default().as_str().to_string()),
646            provider: Some("gemini".to_string()),
647            api_key_env: "GEMINI_API_KEY".to_string(),
648            workspace: None,
649            enable_tree_sitter: false,
650            performance_monitoring: false,
651            research_preview: false,
652            security_level: "moderate".to_string(),
653            show_file_diffs: false,
654            max_concurrent_ops: 5,
655            api_rate_limit: 30,
656            max_tool_calls: 10,
657            verbose: false,
658            config: None,
659            log_level: "info".to_string(),
660            no_color: false,
661            theme: None,
662            skip_confirmations: false,
663            full_auto: None,
664            debug: false,
665            command: Some(Commands::Chat),
666        }
667    }
668}
669
670impl Cli {
671    /// Get the model to use, with fallback to default
672    pub fn get_model(&self) -> String {
673        self.model
674            .clone()
675            .unwrap_or_else(|| ModelId::default().as_str().to_string())
676    }
677
678    /// Load configuration from a simple TOML-like file without external deps
679    ///
680    /// Supported keys (top-level): model, api_key_env, verbose, log_level, workspace
681    /// Example:
682    ///   model = "gemini-2.5-flash-preview-05-20"
683    ///   api_key_env = "GEMINI_API_KEY"
684    ///   verbose = true
685    ///   log_level = "info"
686    ///   workspace = "/path/to/workspace"
687    pub fn load_config(&self) -> Result<ConfigFile, Box<dyn std::error::Error>> {
688        use std::fs;
689        use std::path::Path;
690
691        // Resolve candidate path
692        let path = if let Some(p) = &self.config {
693            p.clone()
694        } else {
695            let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
696            let primary = cwd.join("vtcode.toml");
697            let secondary = cwd.join(".vtcode.toml");
698            if primary.exists() {
699                primary
700            } else if secondary.exists() {
701                secondary
702            } else {
703                // No config file; return empty config
704                return Ok(ConfigFile {
705                    model: None,
706                    provider: None,
707                    api_key_env: None,
708                    verbose: None,
709                    log_level: None,
710                    workspace: None,
711                    tools: None,
712                    context: None,
713                    logging: None,
714                    tree_sitter: None,
715                    performance: None,
716                    security: None,
717                });
718            }
719        };
720
721        let text = fs::read_to_string(&path)?;
722
723        // Very small parser: key = value, supports quoted strings, booleans, and plain paths
724        let mut cfg = ConfigFile {
725            model: None,
726            provider: None,
727            api_key_env: None,
728            verbose: None,
729            log_level: None,
730            workspace: None,
731            tools: None,
732            context: None,
733            logging: None,
734            tree_sitter: None,
735            performance: None,
736            security: None,
737        };
738
739        for raw_line in text.lines() {
740            let line = raw_line.trim();
741            if line.is_empty() || line.starts_with('#') || line.starts_with("//") {
742                continue;
743            }
744            // Strip inline comments after '#'
745            let line = match line.find('#') {
746                Some(idx) => &line[..idx],
747                None => line,
748            }
749            .trim();
750
751            // Expect key = value
752            let mut parts = line.splitn(2, '=');
753            let key = parts.next().map(|s| s.trim()).unwrap_or("");
754            let val = parts.next().map(|s| s.trim()).unwrap_or("");
755            if key.is_empty() || val.is_empty() {
756                continue;
757            }
758
759            // Remove surrounding quotes if present
760            let unquote = |s: &str| -> String {
761                let s = s.trim();
762                if (s.starts_with('"') && s.ends_with('"'))
763                    || (s.starts_with('\'') && s.ends_with('\''))
764                {
765                    s[1..s.len() - 1].to_string()
766                } else {
767                    s.to_string()
768                }
769            };
770
771            match key {
772                "model" => cfg.model = Some(unquote(val)),
773                "api_key_env" => cfg.api_key_env = Some(unquote(val)),
774                "verbose" => {
775                    let v = unquote(val).to_lowercase();
776                    cfg.verbose = Some(matches!(v.as_str(), "true" | "1" | "yes"));
777                }
778                "log_level" => cfg.log_level = Some(unquote(val)),
779                "workspace" => {
780                    let v = unquote(val);
781                    let p = if Path::new(&v).is_absolute() {
782                        PathBuf::from(v)
783                    } else {
784                        // Resolve relative to config file directory
785                        let base = path.parent().unwrap_or(Path::new("."));
786                        base.join(v)
787                    };
788                    cfg.workspace = Some(p);
789                }
790                _ => {
791                    // Ignore unknown keys in this minimal parser
792                }
793            }
794        }
795
796        Ok(cfg)
797    }
798
799    /// Get the effective workspace path
800    pub fn get_workspace(&self) -> std::path::PathBuf {
801        self.workspace
802            .clone()
803            .unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")))
804    }
805
806    /// Get the effective API key environment variable
807    ///
808    /// Automatically infers the API key environment variable based on the provider
809    /// when the current value matches the default or is not explicitly set.
810    pub fn get_api_key_env(&self) -> String {
811        // If api_key_env is the default or empty, infer from provider
812        if self.api_key_env == crate::config::constants::defaults::DEFAULT_API_KEY_ENV
813            || self.api_key_env.is_empty()
814        {
815            if let Some(provider) = &self.provider {
816                match provider.to_lowercase().as_str() {
817                    "openai" => "OPENAI_API_KEY".to_string(),
818                    "anthropic" => "ANTHROPIC_API_KEY".to_string(),
819                    "gemini" => "GEMINI_API_KEY".to_string(),
820                    "deepseek" => "DEEPSEEK_API_KEY".to_string(),
821                    "openrouter" => "OPENROUTER_API_KEY".to_string(),
822                    "xai" => "XAI_API_KEY".to_string(),
823                    _ => "GEMINI_API_KEY".to_string(),
824                }
825            } else {
826                "GEMINI_API_KEY".to_string()
827            }
828        } else {
829            self.api_key_env.clone()
830        }
831    }
832
833    /// Check if verbose mode is enabled
834    pub fn is_verbose(&self) -> bool {
835        self.verbose
836    }
837
838    /// Check if tree-sitter analysis is enabled
839    pub fn is_tree_sitter_enabled(&self) -> bool {
840        self.enable_tree_sitter
841    }
842
843    /// Check if performance monitoring is enabled
844    pub fn is_performance_monitoring_enabled(&self) -> bool {
845        self.performance_monitoring
846    }
847
848    /// Check if research-preview features are enabled
849    pub fn is_research_preview_enabled(&self) -> bool {
850        self.research_preview
851    }
852
853    /// Get the security level
854    pub fn get_security_level(&self) -> &str {
855        &self.security_level
856    }
857
858    /// Check if debug mode is enabled (includes verbose)
859    pub fn is_debug_mode(&self) -> bool {
860        self.debug || self.verbose
861    }
862}