Skip to main content

netget/
docs.rs

1//! Protocol documentation generation
2//!
3//! Provides functionality to generate documentation for all protocols
4//! including their event types, actions, and parameters.
5
6use crate::protocol::metadata::DevelopmentState;
7
8/// ANSI color codes for terminal output
9mod colors {
10    pub const RESET: &str = "\x1b[0m";
11    pub const BOLD: &str = "\x1b[1m";
12    pub const DIM: &str = "\x1b[2m";
13
14    // Colors
15    pub const CYAN: &str = "\x1b[36m";
16    pub const BLUE: &str = "\x1b[34m";
17    pub const GREEN: &str = "\x1b[32m";
18    pub const YELLOW: &str = "\x1b[33m";
19    pub const MAGENTA: &str = "\x1b[35m";
20    pub const RED: &str = "\x1b[31m";
21    pub const GREY: &str = "\x1b[90m";
22
23    // Bright colors
24    pub const BRIGHT_CYAN: &str = "\x1b[96m";
25    pub const BRIGHT_GREEN: &str = "\x1b[92m";
26    pub const BRIGHT_YELLOW: &str = "\x1b[93m";
27}
28
29/// Generate a list of all available protocols with brief descriptions
30pub fn list_all_protocols() -> String {
31    let mut output = String::new();
32
33    // Title with colors
34    output.push_str(&format!(
35        "\n{}{}NetGet - LLM-Controlled Network Protocols{}\n\n",
36        colors::BOLD,
37        colors::BRIGHT_CYAN,
38        colors::RESET
39    ));
40
41    // NetGet description
42    output.push_str(&format!(
43        "{}NetGet is an experimental network application where an LLM (via Ollama){}\n",
44        colors::DIM,
45        colors::RESET
46    ));
47    output.push_str(&format!(
48        "{}controls network protocols and acts as a server for 50+ protocols.{}\n",
49        colors::DIM,
50        colors::RESET
51    ));
52    output.push_str(&format!("{}All protocol logic is handled by the LLM - you describe behavior in natural language.{}\n\n",
53        colors::DIM, colors::RESET));
54
55    // Key Features
56    output.push_str(&format!("{}Key Features:{}\n", colors::BOLD, colors::RESET));
57    output.push_str(&format!("  {}•{} {}Scripting:{} LLM generates on-the-fly Python/JavaScript code to reduce LLM calls\n",
58        colors::GREEN, colors::RESET,
59        colors::BOLD, colors::RESET));
60    output.push_str(&format!(
61        "  {}•{} {}Web Search:{} LLM can fetch protocol RFCs and documentation from the web\n",
62        colors::GREEN,
63        colors::RESET,
64        colors::BOLD,
65        colors::RESET
66    ));
67    output.push_str(&format!(
68        "  {}•{} {}File Reading:{} LLM can read local files (schemas, configs, prompts)\n",
69        colors::GREEN,
70        colors::RESET,
71        colors::BOLD,
72        colors::RESET
73    ));
74    output.push_str(&format!(
75        "  {}•{} {}Logging:{} Comprehensive logging system (TRACE/DEBUG/INFO/WARN/ERROR levels)\n",
76        colors::GREEN,
77        colors::RESET,
78        colors::BOLD,
79        colors::RESET
80    ));
81    output.push_str(&format!(
82        "  {}•{} {}Action-Based:{} Structured JSON responses for precise protocol control\n",
83        colors::GREEN,
84        colors::RESET,
85        colors::BOLD,
86        colors::RESET
87    ));
88    output.push_str(&format!("  {}•{} {}Dynamic Reconfiguration:{} Change server behavior at runtime without restart\n\n",
89        colors::GREEN, colors::RESET,
90        colors::BOLD, colors::RESET));
91
92    // Dynamically generate grouped protocol list
93    output.push_str(&format!(
94        "{}Available Protocols:{}\n",
95        colors::BOLD,
96        colors::RESET
97    ));
98    output.push_str(&format!(
99        "{}Use{} {}{}/docs <protocol>{} {}to see detailed information.{}\n\n",
100        colors::DIM,
101        colors::RESET,
102        colors::CYAN,
103        colors::BOLD,
104        colors::RESET,
105        colors::DIM,
106        colors::RESET
107    ));
108
109    // Get all protocols from registry and group them
110    let registry = crate::protocol::server_registry::registry();
111    let all_protocols = registry.all_protocols();
112
113    // Group protocols by their group_name with their state
114    let mut groups: std::collections::HashMap<&'static str, Vec<(String, DevelopmentState)>> =
115        std::collections::HashMap::new();
116
117    for (protocol_name, protocol) in &all_protocols {
118        let group = protocol.group_name();
119        let state = protocol.metadata().state;
120        groups
121            .entry(group)
122            .or_default()
123            .push((protocol_name.clone(), state));
124    }
125
126    // Sort groups alphabetically
127    let mut sorted_group_names: Vec<&'static str> = groups.keys().copied().collect();
128    sorted_group_names.sort();
129
130    for group_name in sorted_group_names {
131        if let Some(protocols) = groups.get(group_name) {
132            if protocols.is_empty() {
133                continue;
134            }
135
136            // Output group header
137            output.push_str(&format!(
138                "{}━━━ {} ━━━{}\n",
139                colors::BRIGHT_GREEN,
140                group_name,
141                colors::RESET
142            ));
143
144            // Sort protocols alphabetically within group
145            let mut sorted_protocols = protocols.clone();
146            sorted_protocols.sort_by(|a, b| a.0.cmp(&b.0));
147
148            // Output protocol names with color coding based on state
149            let colored_protocols: Vec<String> = sorted_protocols
150                .iter()
151                .map(|(name, state)| {
152                    let color = match state {
153                        DevelopmentState::Stable => colors::GREEN,
154                        DevelopmentState::Beta => colors::BLUE,
155                        DevelopmentState::Experimental => colors::YELLOW,
156                        DevelopmentState::Incomplete => colors::RED,
157                    };
158                    format!("{}{}{}", color, name, colors::RESET)
159                })
160                .collect();
161
162            let protocol_list =
163                colored_protocols.join(&format!("{}, {}", colors::RESET, colors::DIM));
164            output.push_str(&format!("  {}{}\n\n", colors::DIM, protocol_list));
165        }
166    }
167
168    output
169}
170
171/// Generate detailed documentation for a specific protocol
172pub fn show_protocol_docs(protocol_name: &str) -> Result<String, String> {
173    let registry = crate::protocol::server_registry::registry();
174
175    // Try to parse the protocol name using registry
176    let parsed_protocol_name = registry.parse_from_str(protocol_name).ok_or_else(|| {
177        format!(
178            "{}Unknown protocol: {}{}. Use /docs to see all protocols.",
179            colors::RED,
180            protocol_name,
181            colors::RESET
182        )
183    })?;
184
185    let protocol = registry.get(&parsed_protocol_name).ok_or_else(|| {
186        format!(
187            "{}Protocol {} not found in registry{}",
188            colors::RED,
189            parsed_protocol_name,
190            colors::RESET
191        )
192    })?;
193
194    let metadata = protocol.metadata();
195    let stack_name = registry
196        .stack_name_by_protocol(&parsed_protocol_name)
197        .unwrap_or("UNKNOWN");
198
199    let mut output = String::new();
200
201    // Title with box drawing characters
202    output.push_str(&format!(
203        "{}╭─────────────────────────────────────────╮{}\n",
204        colors::CYAN,
205        colors::RESET
206    ));
207    output.push_str(&format!(
208        "{}│{} {}Protocol: {}{} {}│{}\n",
209        colors::CYAN,
210        colors::RESET,
211        colors::BOLD,
212        parsed_protocol_name,
213        colors::RESET,
214        colors::CYAN,
215        colors::RESET
216    ));
217    output.push_str(&format!(
218        "{}╰─────────────────────────────────────────╯{}\n\n",
219        colors::CYAN,
220        colors::RESET
221    ));
222
223    // Stack name
224    output.push_str(&format!(
225        "{}▸ Stack:{} {}{}{}\n",
226        colors::BRIGHT_CYAN,
227        colors::RESET,
228        colors::GREEN,
229        stack_name,
230        colors::RESET
231    ));
232
233    // Status badge with color
234    let (status_color, status_symbol) = match metadata.state {
235        DevelopmentState::Stable => (colors::BRIGHT_GREEN, "✓"),
236        DevelopmentState::Beta => (colors::BRIGHT_YELLOW, "β"),
237        DevelopmentState::Experimental => (colors::YELLOW, "α"),
238        DevelopmentState::Incomplete => (colors::RED, "✗"),
239    };
240    output.push_str(&format!(
241        "{}▸ Status:{} {}{} {}{}\n",
242        colors::BRIGHT_CYAN,
243        colors::RESET,
244        status_color,
245        status_symbol,
246        metadata.state.as_str(),
247        colors::RESET
248    ));
249
250    // Show implementation details
251    if !metadata.implementation.is_empty() {
252        output.push_str(&format!(
253            "{}▸ Implementation:{} {}{}{}\n",
254            colors::BRIGHT_CYAN,
255            colors::RESET,
256            colors::DIM,
257            metadata.implementation,
258            colors::RESET
259        ));
260    }
261
262    // Show LLM control scope
263    if !metadata.llm_control.is_empty() {
264        output.push_str(&format!(
265            "{}▸ LLM Control:{} {}{}{}\n",
266            colors::BRIGHT_CYAN,
267            colors::RESET,
268            colors::DIM,
269            metadata.llm_control,
270            colors::RESET
271        ));
272    }
273
274    // Show E2E testing approach
275    if !metadata.e2e_testing.is_empty() {
276        output.push_str(&format!(
277            "{}▸ E2E Testing:{} {}{}{}\n",
278            colors::BRIGHT_CYAN,
279            colors::RESET,
280            colors::DIM,
281            metadata.e2e_testing,
282            colors::RESET
283        ));
284    }
285
286    // Show notes if present
287    if let Some(notes) = metadata.notes {
288        output.push_str(&format!(
289            "{}▸ Notes:{} {}{}{}\n",
290            colors::BRIGHT_CYAN,
291            colors::RESET,
292            colors::YELLOW,
293            notes,
294            colors::RESET
295        ));
296    }
297
298    // Show privilege requirement.
299    //
300    // Colour comes from `severity()` and text from `description()` rather than a
301    // match here: this duplicated both, so adding a requirement variant meant
302    // editing this renderer in lockstep or failing to compile.
303    use crate::protocol::metadata::PrivilegeSeverity;
304    let priv_color = match metadata.privilege_requirement.severity() {
305        PrivilegeSeverity::None => colors::GREEN,
306        PrivilegeSeverity::Elevated => colors::YELLOW,
307        PrivilegeSeverity::Root => colors::RED,
308    };
309    let priv_text = metadata.privilege_requirement.description();
310    output.push_str(&format!(
311        "{}▸ Privilege Required:{} {}{}{}\n",
312        colors::BRIGHT_CYAN,
313        colors::RESET,
314        priv_color,
315        priv_text,
316        colors::RESET
317    ));
318
319    output.push_str(&format!(
320        "\n{}▸ Description:{}\n  {}{}{}\n\n",
321        colors::BRIGHT_CYAN,
322        colors::RESET,
323        colors::DIM,
324        protocol.description(),
325        colors::RESET
326    ));
327
328    // Try to get protocol instance for detailed info
329    if let Some(protocol) = registry.get(&parsed_protocol_name) {
330        // Show startup parameters
331        let params = protocol.get_startup_parameters();
332        if !params.is_empty() {
333            output.push_str(&format!(
334                "\n{}━━━ Startup Parameters ━━━{}\n\n",
335                colors::BRIGHT_CYAN,
336                colors::RESET
337            ));
338            output.push_str(&format!(
339                "{}These parameters can be provided when opening the server:{}\n\n",
340                colors::DIM,
341                colors::RESET
342            ));
343            for param in params {
344                output.push_str(&format!(
345                    "{}•{} {}{}{} ({}{}{}): {}\n",
346                    colors::BLUE,
347                    colors::RESET,
348                    colors::BOLD,
349                    param.name,
350                    colors::RESET,
351                    colors::YELLOW,
352                    param.type_hint,
353                    colors::RESET,
354                    param.description
355                ));
356                if param.required {
357                    output.push_str(&format!("  {}[REQUIRED]{}\n", colors::RED, colors::RESET));
358                }
359                if let Ok(pretty_json) = serde_json::to_string_pretty(&param.example) {
360                    output.push_str(&format!(
361                        "  {}Example:{} {}{}{}\n",
362                        colors::DIM,
363                        colors::RESET,
364                        colors::GREY,
365                        pretty_json,
366                        colors::RESET
367                    ));
368                }
369            }
370            output.push('\n');
371        }
372
373        // Show event types
374        let event_types = protocol.get_event_types();
375        if !event_types.is_empty() {
376            output.push_str(&format!(
377                "\n{}━━━ Event Types ━━━{}\n\n",
378                colors::BRIGHT_CYAN,
379                colors::RESET
380            ));
381            output.push_str(&format!(
382                "{}This protocol can emit the following network events:{}\n\n",
383                colors::DIM,
384                colors::RESET
385            ));
386
387            for event_type in event_types {
388                output.push_str(&format!(
389                    "{}▸ Event: {}{}{}\n",
390                    colors::MAGENTA,
391                    colors::BOLD,
392                    event_type.id,
393                    colors::RESET
394                ));
395                output.push_str(&format!(
396                    "  {}{}{}\n\n",
397                    colors::GREY,
398                    event_type.description,
399                    colors::RESET
400                ));
401
402                // Show event parameters
403                if !event_type.parameters.is_empty() {
404                    output.push_str(&format!("  {}Event Data:{}\n", colors::CYAN, colors::RESET));
405                    for param in &event_type.parameters {
406                        output.push_str(&format!(
407                            "    {}•{} {}{}{} ({}{}{}): {}\n",
408                            colors::BLUE,
409                            colors::RESET,
410                            colors::BOLD,
411                            param.name,
412                            colors::RESET,
413                            colors::YELLOW,
414                            param.type_hint,
415                            colors::RESET,
416                            param.description
417                        ));
418                    }
419                    output.push('\n');
420                }
421
422                // Show available actions for this event
423                if !event_type.actions.is_empty() {
424                    output.push_str(&format!(
425                        "  {}Available Actions:{}\n",
426                        colors::CYAN,
427                        colors::RESET
428                    ));
429                    for action in &event_type.actions {
430                        output.push_str(&format!(
431                            "    {}•{} {}{}{}: {}\n",
432                            colors::GREEN,
433                            colors::RESET,
434                            colors::BOLD,
435                            action.name,
436                            colors::RESET,
437                            action.description
438                        ));
439
440                        // Show action parameters
441                        if !action.parameters.is_empty() {
442                            output.push_str(&format!(
443                                "      {}Parameters:{}\n",
444                                colors::DIM,
445                                colors::RESET
446                            ));
447                            for param in &action.parameters {
448                                output.push_str(&format!(
449                                    "        - {}{}{} ({}{}{}): {}\n",
450                                    colors::BOLD,
451                                    param.name,
452                                    colors::RESET,
453                                    colors::YELLOW,
454                                    param.type_hint,
455                                    colors::RESET,
456                                    param.description
457                                ));
458                            }
459                        }
460
461                        // Show example with pretty JSON
462                        if let Ok(pretty_json) = serde_json::to_string_pretty(&action.example) {
463                            output.push_str(&format!(
464                                "      {}Example:{} {}{}{}\n",
465                                colors::DIM,
466                                colors::RESET,
467                                colors::GREY,
468                                pretty_json,
469                                colors::RESET
470                            ));
471                        }
472                    }
473                    output.push('\n');
474                } else {
475                    output.push_str(&format!(
476                        "  {}No specific actions available for this event.{}\n\n",
477                        colors::GREY,
478                        colors::RESET
479                    ));
480                }
481            }
482        } else {
483            output.push_str(&format!(
484                "\n{}━━━ Event Types ━━━{}\n\n",
485                colors::BRIGHT_CYAN,
486                colors::RESET
487            ));
488            output.push_str(&format!(
489                "{}This protocol hasn't documented its event types yet.{}\n\n",
490                colors::GREY,
491                colors::RESET
492            ));
493        }
494
495        // Show async actions (user-triggered)
496        let async_actions = protocol.get_async_actions(&crate::state::app_state::AppState::new());
497        if !async_actions.is_empty() {
498            output.push_str(&format!(
499                "\n{}━━━ User-Triggered Actions ━━━{}\n\n",
500                colors::BRIGHT_CYAN,
501                colors::RESET
502            ));
503            output.push_str(&format!("{}These actions can be triggered by user input (not tied to network events):{}\n\n",
504                colors::DIM, colors::RESET));
505            for action in async_actions {
506                output.push_str(&format!(
507                    "{}•{} {}{}{}: {}\n",
508                    colors::GREEN,
509                    colors::RESET,
510                    colors::BOLD,
511                    action.name,
512                    colors::RESET,
513                    action.description
514                ));
515
516                // Show parameters
517                if !action.parameters.is_empty() {
518                    output.push_str(&format!("  {}Parameters:{}\n", colors::CYAN, colors::RESET));
519                    for param in &action.parameters {
520                        output.push_str(&format!(
521                            "    - {}{}{} ({}{}{}): {}\n",
522                            colors::BOLD,
523                            param.name,
524                            colors::RESET,
525                            colors::YELLOW,
526                            param.type_hint,
527                            colors::RESET,
528                            param.description
529                        ));
530                    }
531                }
532
533                // Show example with pretty JSON
534                if let Ok(pretty_json) = serde_json::to_string_pretty(&action.example) {
535                    output.push_str(&format!(
536                        "  {}Example:{} {}{}{}\n",
537                        colors::DIM,
538                        colors::RESET,
539                        colors::GREY,
540                        pretty_json,
541                        colors::RESET
542                    ));
543                }
544                output.push('\n');
545            }
546        }
547    } else {
548        output.push_str("## Documentation\n\n");
549        output.push_str("Detailed documentation not available for this protocol.\n");
550        output.push_str("This protocol may not implement the Server trait yet.\n");
551    }
552
553    Ok(output)
554}