mielin_cli/commands/
debug.rs

1//! Debugging command handlers
2
3use crate::output::{render_output, MultiFormatDisplay, OutputFormat};
4use crate::types::OperationResult;
5use anyhow::Result;
6use clap::Subcommand;
7use comfy_table::{presets::UTF8_FULL, Cell, Color, ContentArrangement, Table};
8use serde::Serialize;
9
10#[derive(Subcommand)]
11pub enum DebugCommands {
12    /// Attach debugger to an agent
13    #[command(alias = "connect")]
14    Attach {
15        /// Agent ID to attach to
16        agent_id: String,
17        /// Debugger port
18        #[arg(short, long, default_value = "9229")]
19        port: u16,
20    },
21    /// Trace agent execution
22    #[command(aliases = ["track", "follow"])]
23    Trace {
24        /// Agent ID to trace
25        agent_id: String,
26        /// Duration in seconds
27        #[arg(short, long, default_value = "10")]
28        duration: u64,
29        /// Output file for trace data
30        #[arg(short = 'f', long = "file")]
31        output_file: Option<String>,
32    },
33    /// Dump agent state
34    #[command(aliases = ["snapshot", "export"])]
35    Dump {
36        /// Agent ID to dump
37        agent_id: String,
38        /// Include memory snapshot
39        #[arg(short, long)]
40        memory: bool,
41        /// Dump format (json, binary, text)
42        #[arg(long, default_value = "json")]
43        dump_format: String,
44    },
45    /// Show agent profiling data
46    #[command(aliases = ["prof", "perf"])]
47    Profile {
48        /// Agent ID to profile
49        agent_id: String,
50        /// Show CPU profile
51        #[arg(long)]
52        cpu: bool,
53        /// Show memory profile
54        #[arg(long)]
55        mem: bool,
56    },
57}
58
59#[derive(Debug, Serialize)]
60struct DebugSession {
61    agent_id: String,
62    session_id: String,
63    debugger_url: String,
64    status: String,
65}
66
67impl MultiFormatDisplay for DebugSession {
68    fn to_table(&self) -> Table {
69        let mut table = Table::new();
70        table
71            .load_preset(UTF8_FULL)
72            .set_content_arrangement(ContentArrangement::Dynamic);
73
74        table.add_row(vec![
75            Cell::new("Agent ID").fg(Color::Cyan),
76            Cell::new(&self.agent_id),
77        ]);
78        table.add_row(vec![
79            Cell::new("Session ID").fg(Color::Cyan),
80            Cell::new(&self.session_id),
81        ]);
82        table.add_row(vec![
83            Cell::new("Debugger URL").fg(Color::Cyan),
84            Cell::new(&self.debugger_url),
85        ]);
86        table.add_row(vec![
87            Cell::new("Status").fg(Color::Cyan),
88            Cell::new(&self.status).fg(Color::Green),
89        ]);
90
91        table
92    }
93
94    fn to_quiet(&self) -> String {
95        self.debugger_url.clone()
96    }
97}
98
99#[derive(Debug, Serialize)]
100struct TraceResult {
101    agent_id: String,
102    duration_secs: u64,
103    events_captured: u64,
104    output_file: String,
105    file_size_kb: u64,
106}
107
108impl MultiFormatDisplay for TraceResult {
109    fn to_table(&self) -> Table {
110        let mut table = Table::new();
111        table
112            .load_preset(UTF8_FULL)
113            .set_content_arrangement(ContentArrangement::Dynamic);
114
115        table.add_row(vec![
116            Cell::new("Agent ID").fg(Color::Cyan),
117            Cell::new(&self.agent_id),
118        ]);
119        table.add_row(vec![
120            Cell::new("Duration").fg(Color::Cyan),
121            Cell::new(format!("{}s", self.duration_secs)),
122        ]);
123        table.add_row(vec![
124            Cell::new("Events Captured").fg(Color::Cyan),
125            Cell::new(self.events_captured.to_string()),
126        ]);
127        table.add_row(vec![
128            Cell::new("Output File").fg(Color::Cyan),
129            Cell::new(&self.output_file),
130        ]);
131        table.add_row(vec![
132            Cell::new("File Size").fg(Color::Cyan),
133            Cell::new(format!("{} KB", self.file_size_kb)),
134        ]);
135
136        table
137    }
138
139    fn to_quiet(&self) -> String {
140        self.output_file.clone()
141    }
142}
143
144#[derive(Debug, Serialize)]
145struct AgentDump {
146    agent_id: String,
147    timestamp: String,
148    state: String,
149    memory_mb: f64,
150    stack_depth: usize,
151    local_variables: usize,
152}
153
154impl MultiFormatDisplay for AgentDump {
155    fn to_table(&self) -> Table {
156        let mut table = Table::new();
157        table
158            .load_preset(UTF8_FULL)
159            .set_content_arrangement(ContentArrangement::Dynamic);
160
161        table.add_row(vec![
162            Cell::new("Agent ID").fg(Color::Cyan),
163            Cell::new(&self.agent_id),
164        ]);
165        table.add_row(vec![
166            Cell::new("Timestamp").fg(Color::Cyan),
167            Cell::new(&self.timestamp),
168        ]);
169        table.add_row(vec![
170            Cell::new("State").fg(Color::Cyan),
171            Cell::new(&self.state).fg(Color::Green),
172        ]);
173        table.add_row(vec![
174            Cell::new("Memory").fg(Color::Cyan),
175            Cell::new(format!("{:.2} MB", self.memory_mb)),
176        ]);
177        table.add_row(vec![
178            Cell::new("Stack Depth").fg(Color::Cyan),
179            Cell::new(self.stack_depth.to_string()),
180        ]);
181        table.add_row(vec![
182            Cell::new("Local Variables").fg(Color::Cyan),
183            Cell::new(self.local_variables.to_string()),
184        ]);
185
186        table
187    }
188}
189
190pub async fn handle_debug_command(action: DebugCommands, format: OutputFormat) -> Result<()> {
191    match action {
192        DebugCommands::Attach { agent_id, port } => {
193            let session = mock_debug_session(&agent_id, port);
194            println!("{}", render_output(&session, format)?);
195        }
196        DebugCommands::Trace {
197            agent_id,
198            duration,
199            output_file,
200        } => {
201            let output_path = output_file.unwrap_or_else(|| {
202                format!("trace-{}-{}.json", agent_id, chrono::Utc::now().timestamp())
203            });
204
205            let trace = mock_trace_result(&agent_id, duration, &output_path);
206            println!("{}", render_output(&trace, format)?);
207        }
208        DebugCommands::Dump {
209            agent_id,
210            memory,
211            dump_format,
212        } => {
213            let dump = mock_agent_dump(&agent_id);
214
215            if memory {
216                let result = OperationResult {
217                    success: true,
218                    message: format!(
219                        "Agent {} state dumped to agent-{}.{} (with memory snapshot)",
220                        agent_id, agent_id, dump_format
221                    ),
222                    id: Some(agent_id),
223                };
224                println!("{}", render_output(&result, format)?);
225            } else {
226                println!("{}", render_output(&dump, format)?);
227            }
228        }
229        DebugCommands::Profile { agent_id, cpu, mem } => {
230            let profile_types = match (cpu, mem) {
231                (true, true) => "CPU and memory".to_string(),
232                (true, false) => "CPU".to_string(),
233                (false, true) => "memory".to_string(),
234                (false, false) => "CPU and memory".to_string(), // default to both
235            };
236
237            let result = OperationResult {
238                success: true,
239                message: format!(
240                    "Profiling agent {} ({} profile available at /tmp/profile-{}.pb.gz)",
241                    agent_id, profile_types, agent_id
242                ),
243                id: Some(agent_id),
244            };
245            println!("{}", render_output(&result, format)?);
246        }
247    }
248    Ok(())
249}
250
251fn mock_debug_session(agent_id: &str, port: u16) -> DebugSession {
252    DebugSession {
253        agent_id: agent_id.to_string(),
254        session_id: format!("debug-session-{}", uuid::Uuid::new_v4()),
255        debugger_url: format!("ws://localhost:{}/debugger/{}", port, agent_id),
256        status: "Attached".to_string(),
257    }
258}
259
260fn mock_trace_result(agent_id: &str, duration: u64, output_file: &str) -> TraceResult {
261    TraceResult {
262        agent_id: agent_id.to_string(),
263        duration_secs: duration,
264        events_captured: duration * 1250, // Simulate ~1250 events/sec
265        output_file: output_file.to_string(),
266        file_size_kb: duration * 85, // Simulate ~85KB/sec
267    }
268}
269
270fn mock_agent_dump(agent_id: &str) -> AgentDump {
271    AgentDump {
272        agent_id: agent_id.to_string(),
273        timestamp: chrono::Utc::now().to_rfc3339(),
274        state: "Running".to_string(),
275        memory_mb: 12.5,
276        stack_depth: 8,
277        local_variables: 24,
278    }
279}