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