1use crate::protocol::metadata::DevelopmentState;
7
8mod colors {
10 pub const RESET: &str = "\x1b[0m";
11 pub const BOLD: &str = "\x1b[1m";
12 pub const DIM: &str = "\x1b[2m";
13
14 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 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
29pub fn list_all_protocols() -> String {
31 let mut output = String::new();
32
33 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 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 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 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 let registry = crate::protocol::server_registry::registry();
111 let all_protocols = registry.all_protocols();
112
113 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 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.push_str(&format!(
138 "{}━━━ {} ━━━{}\n",
139 colors::BRIGHT_GREEN,
140 group_name,
141 colors::RESET
142 ));
143
144 let mut sorted_protocols = protocols.clone();
146 sorted_protocols.sort_by(|a, b| a.0.cmp(&b.0));
147
148 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
171pub fn show_protocol_docs(protocol_name: &str) -> Result<String, String> {
173 let registry = crate::protocol::server_registry::registry();
174
175 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 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 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 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 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 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 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 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 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 if let Some(protocol) = registry.get(&parsed_protocol_name) {
330 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(¶m.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 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 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 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 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 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 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 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 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}