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