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}