vtcode_core/cli/
man_pages.rs

1//! Man page generation for VTCode CLI using roff-rs
2//!
3//! This module provides functionality to generate Unix man pages for VTCode
4//! commands and subcommands using the roff-rs library.
5
6use anyhow::{Context, Result, bail};
7use roff::{Roff, bold, italic, roman};
8use std::fs;
9use std::path::Path;
10
11/// Man page generator for VTCode CLI
12pub struct ManPageGenerator;
13
14impl ManPageGenerator {
15    /// Get current date in YYYY-MM-DD format
16    fn current_date() -> String {
17        use chrono::Utc;
18        Utc::now().format("%Y-%m-%d").to_string()
19    }
20
21    /// Generate man page for the main VTCode command
22    pub fn generate_main_man_page() -> Result<String> {
23        let current_date = Self::current_date();
24        let page = Roff::new()
25            .control("TH", ["VTCODE", "1", &current_date, "VTCode", "User Commands"])
26            .control("SH", ["NAME"])
27            .text([roman("vtcode - Advanced coding agent with Decision Ledger")])
28            .control("SH", ["SYNOPSIS"])
29            .text([
30                bold("vtcode"),
31                roman(" ["),
32                bold("OPTIONS"),
33                roman("] ["),
34                bold("COMMAND"),
35                roman("] ["),
36                bold("ARGS"),
37                roman("]"),
38            ])
39            .control("SH", ["DESCRIPTION"])
40            .text([
41                roman("VTCode is an advanced coding agent with single-agent architecture and Decision Ledger that provides"),
42                roman(" intelligent code generation, analysis, and modification capabilities. It supports"),
43                roman(" multiple LLM providers and includes tree-sitter powered code analysis for"),
44                roman(" Rust, Python, JavaScript, TypeScript, Go, and Java."),
45            ])
46            .control("SH", ["OPTIONS"])
47            .control("TP", [])
48            .text([bold("-m"), roman(", "), bold("--model"), roman(" "), italic("MODEL")])
49            .text([roman("Specify the LLM model to use (default: gemini-2.5-flash-preview-05-20)")])
50            .control("TP", [])
51            .text([bold("-p"), roman(", "), bold("--provider"), roman(" "), italic("PROVIDER")])
52            .text([roman("Specify the LLM provider (gemini, openai, anthropic, deepseek)")])
53            .control("TP", [])
54            .text([bold("--workspace"), roman(" "), italic("PATH")])
55            .text([roman("Set the workspace root directory for file operations")])
56            .control("TP", [])
57            .text([bold("--enable-tree-sitter")])
58            .text([roman("Enable tree-sitter code analysis")])
59            .control("TP", [])
60            .text([bold("--performance-monitoring")])
61            .text([roman("Enable performance monitoring and metrics")])
62            .control("TP", [])
63            .text([bold("--debug")])
64            .text([roman("Enable debug output")])
65            .control("TP", [])
66            .text([bold("--verbose")])
67            .text([roman("Enable verbose logging")])
68            .control("TP", [])
69            .text([bold("-h"), roman(", "), bold("--help")])
70            .text([roman("Display help information")])
71            .control("TP", [])
72            .text([bold("-V"), roman(", "), bold("--version")])
73            .text([roman("Display version information")])
74            .control("SH", ["COMMANDS"])
75            .control("TP", [])
76            .text([bold("chat")])
77            .text([roman("Start interactive AI coding assistant")])
78            .control("TP", [])
79            .text([bold("ask"), roman(" "), italic("PROMPT")])
80            .text([roman("Single prompt mode without tools")])
81            .control("TP", [])
82            .text([bold("analyze")])
83            .text([roman("Analyze workspace with tree-sitter integration")])
84            .control("TP", [])
85            .text([bold("performance")])
86            .text([roman("Display performance metrics and system status")])
87            .control("TP", [])
88            .text([bold("benchmark")])
89            .text([roman("Run SWE-bench evaluation framework")])
90            .control("TP", [])
91            .text([bold("create-project"), roman(" "), italic("NAME"), roman(" "), italic("FEATURES")])
92            .text([roman("Create complete Rust project with features")])
93            .control("TP", [])
94            .text([bold("init")])
95            .text([roman("Initialize project with enhanced structure")])
96            .control("TP", [])
97            .text([bold("man"), roman(" "), italic("COMMAND")])
98            .text([roman("Generate or display man pages for commands")])
99            .control("SH", ["EXAMPLES"])
100            .text([roman("Start interactive chat:")])
101            .text([bold("  vtcode chat")])
102            .text([roman("Ask a question:")])
103            .text([bold("  vtcode ask \"Explain Rust ownership\"")])
104            .text([roman("Create a web project:")])
105            .text([bold("  vtcode create-project myapp web,auth,db")])
106            .text([roman("Generate man page:")])
107            .text([bold("  vtcode man chat")])
108            .control("SH", ["ENVIRONMENT"])
109            .control("TP", [])
110            .text([bold("GEMINI_API_KEY")])
111            .text([roman("API key for Google Gemini (default provider)")])
112            .control("TP", [])
113            .text([bold("OPENAI_API_KEY")])
114            .text([roman("API key for OpenAI GPT models")])
115            .control("TP", [])
116            .text([bold("ANTHROPIC_API_KEY")])
117            .text([roman("API key for Anthropic Claude models")])
118            .control("TP", [])
119            .text([bold("DEEPSEEK_API_KEY")])
120            .text([roman("API key for DeepSeek models")])
121            .control("SH", ["FILES"])
122            .control("TP", [])
123            .text([bold("vtcode.toml")])
124            .text([roman("Configuration file (current directory or ~/.vtcode/)")])
125            .control("TP", [])
126            .text([bold(".vtcode/")])
127            .text([roman("Project cache and context directory")])
128            .control("SH", ["SEE ALSO"])
129            .text([roman("Full documentation: https://github.com/vinhnx/vtcode")])
130            .text([roman("Related commands: cargo(1), rustc(1), git(1)")])
131            .render();
132
133        Ok(page)
134    }
135
136    /// Generate man page for a specific command
137    pub fn generate_command_man_page(command: &str) -> Result<String> {
138        match command {
139            "chat" => Self::generate_chat_man_page(),
140            "ask" => Self::generate_ask_man_page(),
141            "analyze" => Self::generate_analyze_man_page(),
142            "performance" => Self::generate_performance_man_page(),
143            "benchmark" => Self::generate_benchmark_man_page(),
144            "create-project" => Self::generate_create_project_man_page(),
145            "init" => Self::generate_init_man_page(),
146            "man" => Self::generate_man_man_page(),
147            _ => bail!("Unknown command: {}", command),
148        }
149    }
150
151    /// Generate man page for the chat command
152    fn generate_chat_man_page() -> Result<String> {
153        let current_date = Self::current_date();
154        let page = Roff::new()
155            .control("TH", ["VTCODE-CHAT", "1", &current_date, "VTCode", "User Commands"])
156            .control("SH", ["NAME"])
157            .text([roman("vtcode-chat - Interactive AI coding assistant")])
158            .control("SH", ["SYNOPSIS"])
159            .text([
160                bold("vtcode"),
161                roman(" ["),
162                bold("OPTIONS"),
163                roman("] "),
164                bold("chat"),
165            ])
166            .control("SH", ["DESCRIPTION"])
167            .text([
168                roman("Start an interactive AI coding assistant session."),
169                roman(" The chat command provides intelligent code generation, analysis, and modification"),
170                roman(" with support for multiple LLM providers and tree-sitter powered code analysis."),
171            ])
172            .control("SH", ["OPTIONS"])
173            .text([roman("All global options are supported. See "), bold("vtcode(1)"), roman(" for details.")])
174            .control("SH", ["EXAMPLES"])
175            .text([roman("Start basic chat session:")])
176            .text([bold("  vtcode chat")])
177            .text([roman("Start with specific model:")])
178            .text([bold("  vtcode --model gemini-2.5-pro chat")])
179            .control("SH", ["SEE ALSO"])
180            .text([bold("vtcode(1)"), roman(", "), bold("vtcode-ask(1)"), roman(", "), bold("vtcode-analyze(1)")])
181            .render();
182
183        Ok(page)
184    }
185
186    /// Generate man page for the ask command
187    fn generate_ask_man_page() -> Result<String> {
188        let current_date = Self::current_date();
189        let page = Roff::new()
190            .control("TH", ["VTCODE-ASK", "1", &current_date, "VTCode", "User Commands"])
191            .control("SH", ["NAME"])
192            .text([roman("vtcode-ask - Single prompt mode without tools")])
193            .control("SH", ["SYNOPSIS"])
194            .text([
195                bold("vtcode"),
196                roman(" ["),
197                bold("OPTIONS"),
198                roman("] "),
199                bold("ask"),
200                roman(" "),
201                italic("PROMPT"),
202            ])
203            .control("SH", ["DESCRIPTION"])
204            .text([
205                roman("Execute a single prompt without tool usage. This is perfect for quick questions,"),
206                roman(" code explanations, and simple queries that don't require file operations or"),
207                roman(" complex tool interactions."),
208            ])
209            .control("SH", ["EXAMPLES"])
210            .text([roman("Ask about Rust ownership:")])
211            .text([bold("  vtcode ask \"Explain Rust ownership\"")])
212            .text([roman("Get code explanation:")])
213            .text([bold("  vtcode ask \"What does this regex do: \\w+@\\w+\\.\\w+\"")])
214            .control("SH", ["SEE ALSO"])
215            .text([bold("vtcode(1)"), roman(", "), bold("vtcode-chat(1)")])
216            .render();
217
218        Ok(page)
219    }
220
221    /// Generate man page for the analyze command
222    fn generate_analyze_man_page() -> Result<String> {
223        let current_date = Self::current_date();
224        let page = Roff::new()
225            .control(
226                "TH",
227                [
228                    "VTCODE-ANALYZE",
229                    "1",
230                    &current_date,
231                    "VTCode",
232                    "User Commands",
233                ],
234            )
235            .control("SH", ["NAME"])
236            .text([roman(
237                "vtcode-analyze - Analyze workspace with tree-sitter integration",
238            )])
239            .control("SH", ["SYNOPSIS"])
240            .text([
241                bold("vtcode"),
242                roman(" ["),
243                bold("OPTIONS"),
244                roman("] "),
245                bold("analyze"),
246            ])
247            .control("SH", ["DESCRIPTION"])
248            .text([
249                roman(
250                    "Analyze the current workspace using tree-sitter integration. Provides project",
251                ),
252                roman(
253                    " structure analysis, language detection, code complexity metrics, dependency",
254                ),
255                roman(" insights, and symbol extraction for supported languages."),
256            ])
257            .control("SH", ["SUPPORTED LANGUAGES"])
258            .text([roman(
259                "• Rust • Python • JavaScript • TypeScript • Go • Java",
260            )])
261            .control("SH", ["FEATURES"])
262            .control("TP", [])
263            .text([bold("Project Structure")])
264            .text([roman("Directory tree and file organization analysis")])
265            .control("TP", [])
266            .text([bold("Language Detection")])
267            .text([roman("Automatic detection of programming languages used")])
268            .control("TP", [])
269            .text([bold("Code Metrics")])
270            .text([roman("Complexity analysis and code quality metrics")])
271            .control("TP", [])
272            .text([bold("Symbol Extraction")])
273            .text([roman("Functions, classes, and other code symbols")])
274            .control("SH", ["EXAMPLES"])
275            .text([roman("Analyze current workspace:")])
276            .text([bold("  vtcode analyze")])
277            .control("SH", ["SEE ALSO"])
278            .text([bold("vtcode(1)"), roman(", "), bold("vtcode-chat(1)")])
279            .render();
280
281        Ok(page)
282    }
283
284    /// Generate man page for the performance command
285    fn generate_performance_man_page() -> Result<String> {
286        let current_date = Self::current_date();
287        let page = Roff::new()
288            .control(
289                "TH",
290                [
291                    "VTCODE-PERFORMANCE",
292                    "1",
293                    &current_date,
294                    "VTCode",
295                    "User Commands",
296                ],
297            )
298            .control("SH", ["NAME"])
299            .text([roman(
300                "vtcode-performance - Display performance metrics and system status",
301            )])
302            .control("SH", ["SYNOPSIS"])
303            .text([
304                bold("vtcode"),
305                roman(" ["),
306                bold("OPTIONS"),
307                roman("] "),
308                bold("performance"),
309            ])
310            .control("SH", ["DESCRIPTION"])
311            .text([
312                roman("Display comprehensive performance metrics and system status information."),
313                roman(" Shows token usage, API costs, response times, tool execution statistics,"),
314                roman(" memory usage patterns, and agent performance metrics."),
315            ])
316            .control("SH", ["METRICS DISPLAYED"])
317            .control("TP", [])
318            .text([bold("Token Usage")])
319            .text([roman("Input/output token counts and API costs")])
320            .control("TP", [])
321            .text([bold("Response Times")])
322            .text([roman("API response latency and processing times")])
323            .control("TP", [])
324            .text([bold("Tool Execution")])
325            .text([roman("Tool call statistics and execution times")])
326            .control("TP", [])
327            .text([bold("Memory Usage")])
328            .text([roman("Memory consumption patterns")])
329            .control("TP", [])
330            .text([bold("Agent Performance")])
331            .text([roman("Single-agent execution metrics")])
332            .control("SH", ["EXAMPLES"])
333            .text([roman("Show performance metrics:")])
334            .text([bold("  vtcode performance")])
335            .control("SH", ["SEE ALSO"])
336            .text([bold("vtcode(1)"), roman(", "), bold("vtcode-benchmark(1)")])
337            .render();
338
339        Ok(page)
340    }
341
342    /// Generate man page for the benchmark command
343    fn generate_benchmark_man_page() -> Result<String> {
344        let current_date = Self::current_date();
345        let page = Roff::new()
346            .control(
347                "TH",
348                [
349                    "VTCODE-BENCHMARK",
350                    "1",
351                    &current_date,
352                    "VTCode",
353                    "User Commands",
354                ],
355            )
356            .control("SH", ["NAME"])
357            .text([roman(
358                "vtcode-benchmark - Run SWE-bench evaluation framework",
359            )])
360            .control("SH", ["SYNOPSIS"])
361            .text([
362                bold("vtcode"),
363                roman(" ["),
364                bold("OPTIONS"),
365                roman("] "),
366                bold("benchmark"),
367            ])
368            .control("SH", ["DESCRIPTION"])
369            .text([
370                roman(
371                    "Run automated performance testing against the SWE-bench evaluation framework.",
372                ),
373                roman(" Provides comparative analysis across different models, benchmark scoring,"),
374                roman(" and optimization insights for coding tasks."),
375            ])
376            .control("SH", ["FEATURES"])
377            .control("TP", [])
378            .text([bold("Automated Testing")])
379            .text([roman("Run standardized coding tasks and challenges")])
380            .control("TP", [])
381            .text([bold("Comparative Analysis")])
382            .text([roman("Compare performance across different models")])
383            .control("TP", [])
384            .text([bold("Benchmark Scoring")])
385            .text([roman("Quantitative performance metrics and scores")])
386            .control("TP", [])
387            .text([bold("Optimization Insights")])
388            .text([roman("Recommendations for performance improvements")])
389            .control("SH", ["EXAMPLES"])
390            .text([roman("Run benchmark suite:")])
391            .text([bold("  vtcode benchmark")])
392            .control("SH", ["SEE ALSO"])
393            .text([
394                bold("vtcode(1)"),
395                roman(", "),
396                bold("vtcode-performance(1)"),
397            ])
398            .render();
399
400        Ok(page)
401    }
402
403    /// Generate man page for the create-project command
404    fn generate_create_project_man_page() -> Result<String> {
405        let current_date = Self::current_date();
406        let page = Roff::new()
407            .control(
408                "TH",
409                [
410                    "VTCODE-CREATE-PROJECT",
411                    "1",
412                    &current_date,
413                    "VTCode",
414                    "User Commands",
415                ],
416            )
417            .control("SH", ["NAME"])
418            .text([roman(
419                "vtcode-create-project - Create complete Rust project with features",
420            )])
421            .control("SH", ["SYNOPSIS"])
422            .text([
423                bold("vtcode"),
424                roman(" ["),
425                bold("OPTIONS"),
426                roman("] "),
427                bold("create-project"),
428                roman(" "),
429                italic("NAME"),
430                roman(" "),
431                italic("FEATURES"),
432            ])
433            .control("SH", ["DESCRIPTION"])
434            .text([
435                roman("Create a complete Rust project with advanced features and integrations."),
436                roman(" Supports web frameworks, database integration, authentication systems,"),
437                roman(" testing setup, and tree-sitter integration."),
438            ])
439            .control("SH", ["AVAILABLE FEATURES"])
440            .text([roman("• web - Web framework (Axum, Rocket, Warp)")])
441            .text([roman("• auth - Authentication system")])
442            .text([roman("• db - Database integration")])
443            .text([roman("• test - Testing setup")])
444            .text([roman("• tree-sitter - Code analysis integration")])
445            .control("SH", ["EXAMPLES"])
446            .text([roman("Create web app with auth and database:")])
447            .text([bold("  vtcode create-project myapp web,auth,db")])
448            .text([roman("Create basic project:")])
449            .text([bold("  vtcode create-project simple_app")])
450            .control("SH", ["SEE ALSO"])
451            .text([bold("vtcode(1)"), roman(", "), bold("vtcode-init(1)")])
452            .render();
453
454        Ok(page)
455    }
456
457    /// Generate man page for the init command
458    fn generate_init_man_page() -> Result<String> {
459        let current_date = Self::current_date();
460        let page = Roff::new()
461            .control(
462                "TH",
463                ["VTCODE-INIT", "1", &current_date, "VTCode", "User Commands"],
464            )
465            .control("SH", ["NAME"])
466            .text([roman(
467                "vtcode-init - Initialize project with enhanced structure",
468            )])
469            .control("SH", ["SYNOPSIS"])
470            .text([
471                bold("vtcode"),
472                roman(" ["),
473                bold("OPTIONS"),
474                roman("] "),
475                bold("init"),
476            ])
477            .control("SH", ["DESCRIPTION"])
478            .text([
479                roman("Initialize a project with enhanced dot-folder structure for VTCode."),
480                roman(" Creates project directory structure, config files, cache directories,"),
481                roman(" embeddings storage, and tree-sitter parser setup."),
482            ])
483            .control("SH", ["DIRECTORY STRUCTURE"])
484            .text([roman(
485                "• .vtcode/ - Main project cache and context directory",
486            )])
487            .text([roman("• .vtcode/config/ - Configuration files")])
488            .text([roman("• .vtcode/cache/ - File and analysis cache")])
489            .text([roman("• .vtcode/embeddings/ - Code embeddings storage")])
490            .text([roman("• .vtcode/parsers/ - Tree-sitter parsers")])
491            .text([roman("• .vtcode/context/ - Agent context stores")])
492            .control("SH", ["EXAMPLES"])
493            .text([roman("Initialize current directory:")])
494            .text([bold("  vtcode init")])
495            .control("SH", ["SEE ALSO"])
496            .text([
497                bold("vtcode(1)"),
498                roman(", "),
499                bold("vtcode-create-project(1)"),
500            ])
501            .render();
502
503        Ok(page)
504    }
505
506    /// Generate man page for the man command itself
507    fn generate_man_man_page() -> Result<String> {
508        let current_date = Self::current_date();
509        let page = Roff::new()
510            .control("TH", ["VTCODE-MAN", "1", &current_date, "VTCode", "User Commands"])
511            .control("SH", ["NAME"])
512            .text([roman("vtcode-man - Generate or display man pages for VTCode commands")])
513            .control("SH", ["SYNOPSIS"])
514            .text([
515                bold("vtcode"),
516                roman(" ["),
517                bold("OPTIONS"),
518                roman("] "),
519                bold("man"),
520                roman(" ["),
521                italic("COMMAND"),
522                roman("] ["),
523                bold("--output"),
524                roman(" "),
525                italic("FILE"),
526                roman("]"),
527            ])
528            .control("SH", ["DESCRIPTION"])
529            .text([
530                roman("Generate or display Unix man pages for VTCode commands. Man pages provide"),
531                roman(" detailed documentation for all VTCode functionality including usage examples,"),
532                roman(" option descriptions, and feature explanations."),
533            ])
534            .control("SH", ["OPTIONS"])
535            .control("TP", [])
536            .text([bold("--output"), roman(" "), italic("FILE")])
537            .text([roman("Write man page to specified file instead of displaying")])
538            .control("SH", ["AVAILABLE COMMANDS"])
539            .text([roman("• chat - Interactive AI coding assistant")])
540            .text([roman("• ask - Single prompt mode")])
541            .text([roman("• analyze - Workspace analysis")])
542            .text([roman("• performance - Performance metrics")])
543            .text([roman("• benchmark - SWE-bench evaluation")])
544            .text([roman("• create-project - Project creation")])
545            .text([roman("• init - Project initialization")])
546            .text([roman("• man - Man page generation (this command)")])
547            .control("SH", ["EXAMPLES"])
548            .text([roman("Display main VTCode man page:")])
549            .text([bold("  vtcode man")])
550            .text([roman("Display chat command man page:")])
551            .text([bold("  vtcode man chat")])
552            .text([roman("Save man page to file:")])
553            .text([bold("  vtcode man chat --output chat.1")])
554            .control("SH", ["SEE ALSO"])
555            .text([bold("vtcode(1)"), roman(", "), bold("man(1)")])
556            .render();
557
558        Ok(page)
559    }
560
561    /// Save man page to file
562    pub fn save_man_page(content: &str, filename: &Path) -> Result<()> {
563        fs::write(filename, content)
564            .with_context(|| format!("Failed to write man page to {}", filename.display()))?;
565        Ok(())
566    }
567
568    /// Get list of available commands for man page generation
569    pub fn available_commands() -> Vec<&'static str> {
570        vec![
571            "chat",
572            "ask",
573            "analyze",
574            "performance",
575            "benchmark",
576            "create-project",
577            "init",
578            "man",
579        ]
580    }
581}