vtcode_core/cli/
args.rs

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