1use anyhow::{Context, Result, bail};
7use roff::{Roff, bold, italic, roman};
8use std::fs;
9use std::path::Path;
10
11pub struct ManPageGenerator;
13
14impl ManPageGenerator {
15 fn current_date() -> String {
17 use chrono::Utc;
18 Utc::now().format("%Y-%m-%d").to_string()
19 }
20
21 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", ¤t_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 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 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", ¤t_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 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", ¤t_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 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 ¤t_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 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 ¤t_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 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 ¤t_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 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 ¤t_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 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", ¤t_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 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", ¤t_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 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 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}