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}