Skip to content

Commit 046704a

Browse files
authored
Merge pull request #109 from openSVM/copilot/fix-108
Add markdown rendering for AI responses in CLI
2 parents 6f579d2 + 2592cb0 commit 046704a

File tree

5 files changed

+221
-22
lines changed

5 files changed

+221
-22
lines changed

Cargo.lock

Lines changed: 113 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "osvm"
3-
version = "0.7.0"
3+
version = "0.8.0"
44
edition = "2021"
55
license = "MIT"
66
description = "OpenSVM CLI tool for managing SVM nodes and deployments"
@@ -61,6 +61,8 @@ solana-sdk-ids = "3.0.0"
6161
solana-program = "3.0.0"
6262
solana-system-interface = "2.0.0"
6363
solana-loader-v3-interface = { version = "6.1.0", features = ["bincode"] }
64+
# Terminal markdown rendering
65+
termimad = "0.30"
6466

6567
[target.'cfg(unix)'.dependencies]
6668
libc = "0.2"

src/main.rs

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,18 @@
22
#![allow(unused)]
33

44
use {
5-
crate::config::Config, // Added
5+
crate::config::Config,
66
crate::utils::diagnostics::DiagnosticCoordinator,
77
crate::utils::{dashboard, ebpf_deploy, examples, nodes, ssh_deploy, svm_info},
8+
crate::utils::markdown_renderer::MarkdownRenderer,
89
clparse::parse_command_line,
910
solana_client::rpc_client::RpcClient,
10-
solana_sdk::{native_token::Sol, pubkey::Pubkey, signature::Signer}, // Modified (removed CommitmentConfig) - bad formatting
11-
std::{process::exit, str::FromStr}, // Modified
11+
solana_sdk::{
12+
native_token::Sol,
13+
pubkey::Pubkey,
14+
signature::Signer
15+
},
16+
std::{process::exit, str::FromStr},
1217
};
1318

1419
// Helper function to handle the type mismatch between clap v2 and v4
@@ -56,22 +61,30 @@ async fn handle_ai_query(
5661
sub_matches: &clap::ArgMatches,
5762
app_matches: &clap::ArgMatches,
5863
) -> Result<(), Box<dyn std::error::Error>> {
59-
// For external subcommands, clap provides the additional arguments differently
60-
// We need to collect them from the raw args since clap doesn't know about them
61-
let args: Vec<String> = std::env::args().collect();
62-
63-
// Find where our subcommand starts and collect everything after osvm
64-
let mut query_parts = Vec::new();
65-
let mut found_osvm = false;
66-
67-
for arg in args.iter().skip(1) {
68-
// Skip the binary name
69-
if !found_osvm {
70-
found_osvm = true;
71-
}
72-
// Skip global flags like --help, --version, etc.
73-
if !arg.starts_with('-') {
74-
query_parts.push(arg.clone());
64+
// For external subcommands, clap collects additional arguments in subcommand_value
65+
// This is the proper way to handle external subcommands with clap
66+
let mut query_parts = vec![sub_command.to_string()];
67+
68+
// Get additional arguments from clap's external subcommand handling
69+
if let Some(external_args) = sub_matches.get_many::<String>("") {
70+
query_parts.extend(external_args.cloned());
71+
}
72+
73+
// If clap doesn't provide args (fallback), parse from environment
74+
// This maintains compatibility while documenting the limitation
75+
if query_parts.len() == 1 {
76+
let args: Vec<String> = std::env::args().collect();
77+
78+
// Collect non-flag arguments starting from the subcommand
79+
let mut found_subcommand = false;
80+
for arg in args.iter().skip(1) {
81+
if found_subcommand {
82+
if !arg.starts_with('-') {
83+
query_parts.push(arg.clone());
84+
}
85+
} else if arg == sub_command {
86+
found_subcommand = true;
87+
}
7588
}
7689
}
7790

@@ -91,7 +104,9 @@ async fn handle_ai_query(
91104
if debug_mode {
92105
println!("🤖 AI Response:");
93106
}
94-
println!("{}", response);
107+
// Render the response as markdown for better formatting
108+
let renderer = MarkdownRenderer::new();
109+
renderer.render(&response);
95110
}
96111
Err(e) => {
97112
eprintln!("❌ AI query failed: {}", e);

src/utils/markdown_renderer.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
use termimad::{MadSkin};
2+
3+
/// A markdown renderer for CLI output
4+
#[derive(Debug, Clone)]
5+
pub struct MarkdownRenderer {
6+
skin: MadSkin,
7+
}
8+
9+
impl MarkdownRenderer {
10+
/// Create a new markdown renderer with default styling
11+
pub fn new() -> Self {
12+
let skin = MadSkin::default();
13+
Self { skin }
14+
}
15+
16+
/// Create a new markdown renderer with custom theme configuration
17+
pub fn with_theme() -> Self {
18+
// For now, use default theme since termimad color API is complex
19+
// TODO: Implement proper color configuration once termimad usage is clarified
20+
let skin = MadSkin::default();
21+
Self { skin }
22+
}
23+
24+
/// Render markdown text to the terminal
25+
/// Note: termimad's print_text never actually fails, so we return unit type
26+
pub fn render(&self, markdown: &str) {
27+
self.skin.print_text(markdown);
28+
}
29+
30+
/// Render markdown text and return as string (for testing)
31+
pub fn render_to_string(&self, markdown: &str) -> String {
32+
let fmt_text = self.skin.text(markdown, None);
33+
fmt_text.to_string()
34+
}
35+
}
36+
37+
impl Default for MarkdownRenderer {
38+
fn default() -> Self {
39+
Self::new()
40+
}
41+
}
42+
43+
#[cfg(test)]
44+
mod tests {
45+
use super::*;
46+
47+
#[test]
48+
fn test_markdown_renderer_creation() {
49+
let renderer = MarkdownRenderer::new();
50+
let result = renderer.render_to_string("# Test");
51+
assert!(result.contains("Test"));
52+
}
53+
54+
#[test]
55+
fn test_basic_markdown_rendering() {
56+
let renderer = MarkdownRenderer::new();
57+
let markdown = "# Header\nSome **bold** text\n```rust\nlet x = 5;\n```";
58+
let output = renderer.render_to_string(markdown);
59+
assert!(output.contains("Header"));
60+
}
61+
62+
#[test]
63+
fn test_themed_renderer() {
64+
let renderer = MarkdownRenderer::with_theme();
65+
let result = renderer.render_to_string("# Themed Test");
66+
assert!(result.contains("Themed Test"));
67+
}
68+
}

src/utils/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ pub mod git_manager;
5252
pub mod local_rpc;
5353
/// Mainnet RPC connectivity
5454
pub mod mainnet_rpc;
55+
/// Markdown rendering for terminal output
56+
pub mod markdown_renderer;
5557
/// Node management utilities for monitoring and controlling nodes
5658
pub mod nodes;
5759
/// Configurable AI prompt templates system

0 commit comments

Comments
 (0)