Skip to main content

vtcode_config/loader/
config.rs

1use anyhow::{Context, Result};
2use serde::{Deserialize, Serialize};
3use std::fs;
4use std::path::Path;
5
6use crate::acp::AgentClientProtocolConfig;
7use crate::agent_teams::AgentTeamsConfig;
8use crate::context::ContextFeaturesConfig;
9use crate::core::{
10    AgentConfig, AnthropicConfig, AuthConfig, AutomationConfig, CommandsConfig,
11    DotfileProtectionConfig, ModelConfig, PermissionsConfig, PromptCachingConfig, SandboxConfig,
12    SecurityConfig, SkillsConfig, ToolsConfig,
13};
14use crate::debug::DebugConfig;
15use crate::defaults::{self, ConfigDefaultsProvider};
16use crate::hooks::HooksConfig;
17use crate::mcp::McpClientConfig;
18use crate::optimization::OptimizationConfig;
19use crate::output_styles::OutputStyleConfig;
20use crate::root::{ChatConfig, PtyConfig, UiConfig};
21use crate::subagent::SubagentsConfig;
22use crate::telemetry::TelemetryConfig;
23use crate::timeouts::TimeoutsConfig;
24
25use crate::loader::syntax_highlighting::SyntaxHighlightingConfig;
26
27/// Provider-specific configuration
28#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
29#[derive(Debug, Clone, Deserialize, Serialize, Default)]
30pub struct ProviderConfig {
31    /// Anthropic provider configuration
32    #[serde(default)]
33    pub anthropic: AnthropicConfig,
34}
35
36/// Main configuration structure for VT Code
37#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
38#[derive(Debug, Clone, Deserialize, Serialize, Default)]
39pub struct VTCodeConfig {
40    /// Agent-wide settings
41    #[serde(default)]
42    pub agent: AgentConfig,
43
44    /// Authentication configuration for OAuth flows
45    #[serde(default)]
46    pub auth: AuthConfig,
47
48    /// Tool execution policies
49    #[serde(default)]
50    pub tools: ToolsConfig,
51
52    /// Unix command permissions
53    #[serde(default)]
54    pub commands: CommandsConfig,
55
56    /// Permission system settings (resolution, audit logging, caching)
57    #[serde(default)]
58    pub permissions: PermissionsConfig,
59
60    /// Security settings
61    #[serde(default)]
62    pub security: SecurityConfig,
63
64    /// Sandbox settings for command execution isolation
65    #[serde(default)]
66    pub sandbox: SandboxConfig,
67
68    /// UI settings
69    #[serde(default)]
70    pub ui: UiConfig,
71
72    /// Chat settings
73    #[serde(default)]
74    pub chat: ChatConfig,
75
76    /// PTY settings
77    #[serde(default)]
78    pub pty: PtyConfig,
79
80    /// Debug and tracing settings
81    #[serde(default)]
82    pub debug: DebugConfig,
83
84    /// Context features (e.g., Decision Ledger)
85    #[serde(default)]
86    pub context: ContextFeaturesConfig,
87
88    /// Telemetry configuration (logging, trajectory)
89    #[serde(default)]
90    pub telemetry: TelemetryConfig,
91
92    /// Performance optimization settings
93    #[serde(default)]
94    pub optimization: OptimizationConfig,
95
96    /// Syntax highlighting configuration
97    #[serde(default)]
98    pub syntax_highlighting: SyntaxHighlightingConfig,
99
100    /// Timeout ceilings and UI warning thresholds
101    #[serde(default)]
102    pub timeouts: TimeoutsConfig,
103
104    /// Automation configuration
105    #[serde(default)]
106    pub automation: AutomationConfig,
107
108    /// Prompt cache configuration (local + provider integration)
109    #[serde(default)]
110    pub prompt_cache: PromptCachingConfig,
111
112    /// Model Context Protocol configuration
113    #[serde(default)]
114    pub mcp: McpClientConfig,
115
116    /// Agent Client Protocol configuration
117    #[serde(default)]
118    pub acp: AgentClientProtocolConfig,
119
120    /// Lifecycle hooks configuration
121    #[serde(default)]
122    pub hooks: HooksConfig,
123
124    /// Model-specific behavior configuration
125    #[serde(default)]
126    pub model: ModelConfig,
127
128    /// Provider-specific configuration
129    #[serde(default)]
130    pub provider: ProviderConfig,
131
132    /// Skills system configuration (Agent Skills spec)
133    #[serde(default)]
134    pub skills: SkillsConfig,
135
136    /// Subagent system configuration
137    #[serde(default)]
138    pub subagents: SubagentsConfig,
139
140    /// Agent teams configuration (experimental)
141    #[serde(default)]
142    pub agent_teams: AgentTeamsConfig,
143
144    /// Output style configuration
145    #[serde(default)]
146    pub output_style: OutputStyleConfig,
147
148    /// Dotfile protection configuration
149    #[serde(default)]
150    pub dotfile_protection: DotfileProtectionConfig,
151}
152
153impl VTCodeConfig {
154    pub fn validate(&self) -> Result<()> {
155        self.syntax_highlighting
156            .validate()
157            .context("Invalid syntax_highlighting configuration")?;
158
159        self.context
160            .validate()
161            .context("Invalid context configuration")?;
162
163        self.hooks
164            .validate()
165            .context("Invalid hooks configuration")?;
166
167        self.timeouts
168            .validate()
169            .context("Invalid timeouts configuration")?;
170
171        self.prompt_cache
172            .validate()
173            .context("Invalid prompt_cache configuration")?;
174
175        self.ui
176            .keyboard_protocol
177            .validate()
178            .context("Invalid keyboard_protocol configuration")?;
179
180        Ok(())
181    }
182
183    #[cfg(feature = "bootstrap")]
184    /// Bootstrap project with config + gitignore
185    pub fn bootstrap_project<P: AsRef<Path>>(workspace: P, force: bool) -> Result<Vec<String>> {
186        Self::bootstrap_project_with_options(workspace, force, false)
187    }
188
189    #[cfg(feature = "bootstrap")]
190    /// Bootstrap project with config + gitignore, with option to create in home directory
191    pub fn bootstrap_project_with_options<P: AsRef<Path>>(
192        workspace: P,
193        force: bool,
194        use_home_dir: bool,
195    ) -> Result<Vec<String>> {
196        let workspace = workspace.as_ref().to_path_buf();
197        defaults::with_config_defaults(|provider| {
198            Self::bootstrap_project_with_provider(&workspace, force, use_home_dir, provider)
199        })
200    }
201
202    #[cfg(feature = "bootstrap")]
203    /// Bootstrap project files using the supplied [`ConfigDefaultsProvider`].
204    pub fn bootstrap_project_with_provider<P: AsRef<Path>>(
205        workspace: P,
206        force: bool,
207        use_home_dir: bool,
208        defaults_provider: &dyn ConfigDefaultsProvider,
209    ) -> Result<Vec<String>> {
210        let workspace = workspace.as_ref();
211        let config_file_name = defaults_provider.config_file_name().to_string();
212        let (config_path, gitignore_path) = crate::loader::bootstrap::determine_bootstrap_targets(
213            workspace,
214            use_home_dir,
215            &config_file_name,
216            defaults_provider,
217        )?;
218
219        crate::loader::bootstrap::ensure_parent_dir(&config_path)?;
220        crate::loader::bootstrap::ensure_parent_dir(&gitignore_path)?;
221
222        let mut created_files = Vec::new();
223
224        if !config_path.exists() || force {
225            let config_content = Self::default_vtcode_toml_template();
226
227            fs::write(&config_path, config_content).with_context(|| {
228                format!("Failed to write config file: {}", config_path.display())
229            })?;
230
231            if let Some(file_name) = config_path.file_name().and_then(|name| name.to_str()) {
232                created_files.push(file_name.to_string());
233            }
234        }
235
236        if !gitignore_path.exists() || force {
237            let gitignore_content = Self::default_vtcode_gitignore();
238            fs::write(&gitignore_path, gitignore_content).with_context(|| {
239                format!(
240                    "Failed to write gitignore file: {}",
241                    gitignore_path.display()
242                )
243            })?;
244
245            if let Some(file_name) = gitignore_path.file_name().and_then(|name| name.to_str()) {
246                created_files.push(file_name.to_string());
247            }
248        }
249
250        Ok(created_files)
251    }
252
253    #[cfg(feature = "bootstrap")]
254    /// Generate the default `vtcode.toml` template used by bootstrap helpers.
255    fn default_vtcode_toml_template() -> String {
256        r#"# VT Code Configuration File (Example)
257# Getting-started reference; see docs/config/CONFIGURATION_PRECEDENCE.md for override order.
258# Copy this file to vtcode.toml and customize as needed.
259
260# Core agent behavior; see docs/config/CONFIGURATION_PRECEDENCE.md.
261[agent]
262# Primary LLM provider to use (e.g., "openai", "gemini", "anthropic", "openrouter")
263provider = "anthropic"
264
265# Environment variable containing the API key for the provider
266api_key_env = "ANTHROPIC_API_KEY"
267
268# Default model to use when no specific model is specified
269default_model = "claude-sonnet-4-5"
270
271# Visual theme for the terminal interface
272theme = "ciapre-dark"
273
274# Enable TODO planning helper mode for structured task management
275todo_planning_mode = true
276
277# UI surface to use ("auto", "alternate", "inline")
278ui_surface = "auto"
279
280# Maximum number of conversation turns before rotating context (affects memory usage)
281# Lower values reduce memory footprint but may lose context; higher values preserve context but use more memory
282max_conversation_turns = 50
283
284# Reasoning effort level ("low", "medium", "high") - affects model usage and response speed
285reasoning_effort = "low"
286
287# Enable self-review loop to check and improve responses (increases API calls)
288enable_self_review = false
289
290# Maximum number of review passes when self-review is enabled
291max_review_passes = 1
292
293# Enable prompt refinement loop for improved prompt quality (increases processing time)
294refine_prompts_enabled = false
295
296# Maximum passes for prompt refinement when enabled
297refine_prompts_max_passes = 1
298
299# Optional alternate model for refinement (leave empty to use default)
300refine_prompts_model = ""
301
302# Maximum size of project documentation to include in context (in bytes)
303project_doc_max_bytes = 16384
304
305# Maximum size of instruction files to process (in bytes)
306instruction_max_bytes = 16384
307
308# List of additional instruction files to include in context
309instruction_files = []
310
311# Default editing mode on startup: "edit" or "plan"
312# "edit" - Full tool access for file modifications and command execution (default)
313# "plan" - Read-only mode that produces implementation plans without making changes
314# Toggle during session with Shift+Tab or /plan command
315default_editing_mode = "edit"
316
317# Onboarding configuration - Customize the startup experience
318[agent.onboarding]
319# Enable the onboarding welcome message on startup
320enabled = true
321
322# Custom introduction text shown on startup
323intro_text = "Let's get oriented. I preloaded workspace context so we can move fast."
324
325# Include project overview information in welcome
326include_project_overview = true
327
328# Include language summary information in welcome
329include_language_summary = false
330
331# Include key guideline highlights from AGENTS.md
332include_guideline_highlights = true
333
334# Include usage tips in the welcome message
335include_usage_tips_in_welcome = false
336
337# Include recommended actions in the welcome message
338include_recommended_actions_in_welcome = false
339
340# Maximum number of guideline highlights to show
341guideline_highlight_limit = 3
342
343# List of usage tips shown during onboarding
344usage_tips = [
345    "Describe your current coding goal or ask for a quick status overview.",
346    "Reference AGENTS.md guidelines when proposing changes.",
347    "Prefer asking for targeted file reads or diffs before editing.",
348]
349
350# List of recommended actions shown during onboarding
351recommended_actions = [
352    "Review the highlighted guidelines and share the task you want to tackle.",
353    "Ask for a workspace tour if you need more context.",
354]
355
356# Custom API keys for specific providers
357[agent.custom_api_keys]
358# Moonshot AI API key (for specific provider access)
359moonshot = "sk-sDj3JUXDbfARCYKNL4q7iGWRtWuhL1M4O6zzgtDpN3Yxt9EA"
360
361# Checkpointing configuration for session persistence
362[agent.checkpointing]
363# Enable automatic session checkpointing
364enabled = false
365
366# Maximum number of checkpoints to keep on disk
367max_snapshots = 50
368
369# Maximum age of checkpoints to keep (in days)
370max_age_days = 30
371
372# Subagent system (opt-in)
373[subagents]
374# Enable subagents (default: false)
375enabled = false
376
377# Maximum concurrent subagents
378# max_concurrent = 3
379
380# Default timeout for subagent execution (seconds)
381# default_timeout_seconds = 300
382
383# Default model for subagents (override per-agent model if set)
384# default_model = ""
385
386# Agent teams (experimental)
387[agent_teams]
388# Enable agent teams (default: false)
389enabled = false
390
391# Maximum number of teammates per team
392# max_teammates = 4
393
394# Default model for agent team subagents
395# default_model = ""
396
397# Teammate display mode (auto, tmux, in_process)
398# teammate_mode = "auto"
399
400# Optional storage directory override for team state
401# storage_dir = "~/.vtcode"
402
403# Tool security configuration
404[tools]
405# Default policy when no specific policy is defined ("allow", "prompt", "deny")
406# "allow" - Execute without confirmation
407# "prompt" - Ask for confirmation
408# "deny" - Block the tool
409default_policy = "prompt"
410
411# Maximum number of tool loops allowed per turn (prevents infinite loops)
412# Higher values allow more complex operations but risk performance issues
413# Recommended: 20 for most tasks, 50 for complex multi-step workflows
414max_tool_loops = 20
415
416# Maximum number of repeated identical tool calls (prevents stuck loops)
417max_repeated_tool_calls = 2
418
419# Maximum consecutive blocked tool calls before force-breaking the turn
420# Helps prevent high-CPU churn when calls are repeatedly denied/blocked
421max_consecutive_blocked_tool_calls_per_turn = 8
422
423# Maximum sequential spool-chunk reads per turn before nudging targeted extraction/summarization
424max_sequential_spool_chunk_reads = 6
425
426# Specific tool policies - Override default policy for individual tools
427[tools.policies]
428apply_patch = "prompt"            # Apply code patches (requires confirmation)
429close_pty_session = "allow"        # Close PTY sessions (no confirmation needed)
430create_pty_session = "allow"       # Create PTY sessions (no confirmation needed)
431edit_file = "allow"               # Edit files directly (no confirmation needed)
432grep_file = "allow"               # Sole content-search tool (ripgrep-backed)
433list_files = "allow"              # List directory contents (no confirmation needed)
434list_pty_sessions = "allow"       # List PTY sessions (no confirmation needed)
435read_file = "allow"               # Read files (no confirmation needed)
436read_pty_session = "allow"        # Read PTY session output (no no confirmation needed)
437resize_pty_session = "allow"      # Resize PTY sessions (no confirmation needed)
438run_pty_cmd = "prompt"            # Run commands in PTY (requires confirmation)
439exec_command = "prompt"           # Execute command in unified session (requires confirmation)
440write_stdin = "prompt"            # Write to stdin in unified session (requires confirmation)
441
442send_pty_input = "prompt"         # Send input to PTY (requires confirmation)
443write_file = "allow"              # Write files (no confirmation needed)
444
445# Command security - Define safe and dangerous command patterns
446[commands]
447# Commands that are always allowed without confirmation
448allow_list = [
449    "ls",           # List directory contents
450    "pwd",          # Print working directory
451    "git status",   # Show git status
452    "git diff",     # Show git differences
453    "cargo check",  # Check Rust code
454    "echo",         # Print text
455]
456
457# Commands that are never allowed
458deny_list = [
459    "rm -rf /",        # Delete root directory (dangerous)
460    "rm -rf ~",        # Delete home directory (dangerous)
461    "shutdown",        # Shut down system (dangerous)
462    "reboot",          # Reboot system (dangerous)
463    "sudo *",          # Any sudo command (dangerous)
464    ":(){ :|:& };:",   # Fork bomb (dangerous)
465]
466
467# Command patterns that are allowed (supports glob patterns)
468allow_glob = [
469    "git *",        # All git commands
470    "cargo *",      # All cargo commands
471    "python -m *",  # Python module commands
472]
473
474# Command patterns that are denied (supports glob patterns)
475deny_glob = [
476    "rm *",         # All rm commands
477    "sudo *",       # All sudo commands
478    "chmod *",      # All chmod commands
479    "chown *",      # All chown commands
480    "kubectl *",    # All kubectl commands (admin access)
481]
482
483# Regular expression patterns for allowed commands (if needed)
484allow_regex = []
485
486# Regular expression patterns for denied commands (if needed)
487deny_regex = []
488
489# Security configuration - Safety settings for automated operations
490[security]
491# Require human confirmation for potentially dangerous actions
492human_in_the_loop = true
493
494# Require explicit write tool usage for claims about file modifications
495require_write_tool_for_claims = true
496
497# Auto-apply patches without prompting (DANGEROUS - disable for safety)
498auto_apply_detected_patches = false
499
500# UI configuration - Terminal and display settings
501[ui]
502# Tool output display mode
503# "compact" - Concise tool output
504# "full" - Detailed tool output
505tool_output_mode = "compact"
506
507# Maximum number of lines to display in tool output (prevents transcript flooding)
508# Lines beyond this limit are truncated to a tail preview
509tool_output_max_lines = 600
510
511# Maximum bytes threshold for spooling tool output to disk
512# Output exceeding this size is written to .vtcode/tool-output/*.log
513tool_output_spool_bytes = 200000
514
515# Optional custom directory for spooled tool output logs
516# If not set, defaults to .vtcode/tool-output/
517# tool_output_spool_dir = "/path/to/custom/spool/dir"
518
519# Allow ANSI escape sequences in tool output (enables colors but may cause layout issues)
520allow_tool_ansi = false
521
522# Number of rows to allocate for inline UI viewport
523inline_viewport_rows = 16
524
525# Show timeline navigation panel
526show_timeline_pane = false
527
528# Status line configuration
529[ui.status_line]
530# Status line mode ("auto", "command", "hidden")
531mode = "auto"
532
533# How often to refresh status line (milliseconds)
534refresh_interval_ms = 2000
535
536# Timeout for command execution in status line (milliseconds)
537command_timeout_ms = 200
538
539# PTY (Pseudo Terminal) configuration - For interactive command execution
540[pty]
541# Enable PTY support for interactive commands
542enabled = true
543
544# Default number of terminal rows for PTY sessions
545default_rows = 24
546
547# Default number of terminal columns for PTY sessions
548default_cols = 80
549
550# Maximum number of concurrent PTY sessions
551max_sessions = 10
552
553# Command timeout in seconds (prevents hanging commands)
554command_timeout_seconds = 300
555
556# Number of recent lines to show in PTY output
557stdout_tail_lines = 20
558
559# Total lines to keep in PTY scrollback buffer
560scrollback_lines = 400
561
562# Context management configuration - Controls conversation memory
563[context]
564# Maximum number of tokens to keep in context (affects model cost and performance)
565# Higher values preserve more context but cost more and may hit token limits
566max_context_tokens = 90000
567
568# Percentage to trim context to when it gets too large
569trim_to_percent = 60
570
571# Number of recent conversation turns to always preserve
572preserve_recent_turns = 6
573
574# Decision ledger configuration - Track important decisions
575[context.ledger]
576# Enable decision tracking and persistence
577enabled = true
578
579# Maximum number of decisions to keep in ledger
580max_entries = 12
581
582# Include ledger summary in model prompts
583include_in_prompt = true
584
585# Preserve ledger during context compression
586preserve_in_compression = true
587
588# AI model routing - Intelligent model selection
589# Telemetry and analytics
590[telemetry]
591# Enable trajectory logging for usage analysis
592trajectory_enabled = true
593
594# Syntax highlighting configuration
595[syntax_highlighting]
596# Enable syntax highlighting for code in tool output
597enabled = true
598
599# Theme for syntax highlighting
600theme = "base16-ocean.dark"
601
602# Cache syntax highlighting themes for performance
603cache_themes = true
604
605# Maximum file size for syntax highlighting (in MB)
606max_file_size_mb = 10
607
608# Programming languages to enable syntax highlighting for
609enabled_languages = [
610    "rust",
611    "python",
612    "javascript",
613    "typescript",
614    "go",
615    "java",
616]
617
618# Timeout for syntax highlighting operations (milliseconds)
619highlight_timeout_ms = 1000
620
621# Automation features - Full-auto mode settings
622[automation.full_auto]
623# Enable full automation mode (DANGEROUS - requires careful oversight)
624enabled = false
625
626# Maximum number of turns before asking for human input
627max_turns = 30
628
629# Tools allowed in full automation mode
630allowed_tools = [
631    "write_file",
632    "read_file",
633    "list_files",
634    "grep_file",
635]
636
637# Require profile acknowledgment before using full auto
638require_profile_ack = true
639
640# Path to full auto profile configuration
641profile_path = "automation/full_auto_profile.toml"
642
643# Prompt caching - Cache model responses for efficiency
644[prompt_cache]
645# Enable prompt caching (reduces API calls for repeated prompts)
646enabled = false
647
648# Directory for cache storage
649cache_dir = "~/.vtcode/cache/prompts"
650
651# Maximum number of cache entries to keep
652max_entries = 1000
653
654# Maximum age of cache entries (in days)
655max_age_days = 30
656
657# Enable automatic cache cleanup
658enable_auto_cleanup = true
659
660# Minimum quality threshold to keep cache entries
661min_quality_threshold = 0.7
662
663# Prompt cache configuration for OpenAI
664    [prompt_cache.providers.openai]
665    enabled = true
666    min_prefix_tokens = 1024
667    idle_expiration_seconds = 3600
668    surface_metrics = true
669    # Routing key strategy for OpenAI prompt cache locality.
670    # "session" creates one stable key per VT Code conversation.
671    prompt_cache_key_mode = "session"
672    # Optional: server-side prompt cache retention for OpenAI Responses API
673    # Example: "24h" (leave commented out for default behavior)
674    # prompt_cache_retention = "24h"
675
676# Prompt cache configuration for Anthropic
677[prompt_cache.providers.anthropic]
678enabled = true
679default_ttl_seconds = 300
680extended_ttl_seconds = 3600
681max_breakpoints = 4
682cache_system_messages = true
683cache_user_messages = true
684
685# Prompt cache configuration for Gemini
686[prompt_cache.providers.gemini]
687enabled = true
688mode = "implicit"
689min_prefix_tokens = 1024
690explicit_ttl_seconds = 3600
691
692# Prompt cache configuration for OpenRouter
693[prompt_cache.providers.openrouter]
694enabled = true
695propagate_provider_capabilities = true
696report_savings = true
697
698# Prompt cache configuration for Moonshot
699[prompt_cache.providers.moonshot]
700enabled = true
701
702# Prompt cache configuration for xAI
703[prompt_cache.providers.xai]
704enabled = true
705
706# Prompt cache configuration for DeepSeek
707[prompt_cache.providers.deepseek]
708enabled = true
709surface_metrics = true
710
711# Prompt cache configuration for Z.AI
712[prompt_cache.providers.zai]
713enabled = false
714
715# Model Context Protocol (MCP) - Connect external tools and services
716[mcp]
717# Enable Model Context Protocol (may impact startup time if services unavailable)
718enabled = true
719max_concurrent_connections = 5
720request_timeout_seconds = 30
721retry_attempts = 3
722
723# MCP UI configuration
724[mcp.ui]
725mode = "compact"
726max_events = 50
727show_provider_names = true
728
729# MCP renderer profiles for different services
730[mcp.ui.renderers]
731sequential-thinking = "sequential-thinking"
732context7 = "context7"
733
734# MCP provider configuration - External services that connect via MCP
735[[mcp.providers]]
736name = "time"
737command = "uvx"
738args = ["mcp-server-time"]
739enabled = true
740max_concurrent_requests = 3
741[mcp.providers.env]
742
743# Agent Client Protocol (ACP) - IDE integration
744[acp]
745enabled = true
746
747[acp.zed]
748enabled = true
749transport = "stdio"
750# workspace_trust controls ACP trust mode: "tools_policy" (prompts) or "full_auto" (no prompts)
751workspace_trust = "full_auto"
752
753[acp.zed.tools]
754read_file = true
755list_files = true"#.to_string()
756    }
757
758    #[cfg(feature = "bootstrap")]
759    fn default_vtcode_gitignore() -> String {
760        r#"# Security-focused exclusions
761.env, .env.local, secrets/, .aws/, .ssh/
762
763# Development artifacts
764target/, build/, dist/, node_modules/, vendor/
765
766# Database files
767*.db, *.sqlite, *.sqlite3
768
769# Binary files
770*.exe, *.dll, *.so, *.dylib, *.bin
771
772# IDE files (comprehensive)
773.vscode/, .idea/, *.swp, *.swo
774"#
775        .to_string()
776    }
777
778    #[cfg(feature = "bootstrap")]
779    /// Create sample configuration file
780    pub fn create_sample_config<P: AsRef<Path>>(output: P) -> Result<()> {
781        let output = output.as_ref();
782        let config_content = Self::default_vtcode_toml_template();
783
784        fs::write(output, config_content)
785            .with_context(|| format!("Failed to write config file: {}", output.display()))?;
786
787        Ok(())
788    }
789}