vtcode_core/cli/
args.rs

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