use crate::protocol::metadata::DevelopmentState;
mod colors {
pub const RESET: &str = "\x1b[0m";
pub const BOLD: &str = "\x1b[1m";
pub const DIM: &str = "\x1b[2m";
pub const CYAN: &str = "\x1b[36m";
pub const BLUE: &str = "\x1b[34m";
pub const GREEN: &str = "\x1b[32m";
pub const YELLOW: &str = "\x1b[33m";
pub const MAGENTA: &str = "\x1b[35m";
pub const RED: &str = "\x1b[31m";
pub const GREY: &str = "\x1b[90m";
pub const BRIGHT_CYAN: &str = "\x1b[96m";
pub const BRIGHT_GREEN: &str = "\x1b[92m";
pub const BRIGHT_YELLOW: &str = "\x1b[93m";
}
pub fn list_all_protocols() -> String {
let mut output = String::new();
output.push_str(&format!(
"\n{}{}NetGet - LLM-Controlled Network Protocols{}\n\n",
colors::BOLD,
colors::BRIGHT_CYAN,
colors::RESET
));
output.push_str(&format!(
"{}NetGet is an experimental network application where an LLM (via Ollama){}\n",
colors::DIM,
colors::RESET
));
output.push_str(&format!(
"{}controls network protocols and acts as a server for 50+ protocols.{}\n",
colors::DIM,
colors::RESET
));
output.push_str(&format!("{}All protocol logic is handled by the LLM - you describe behavior in natural language.{}\n\n",
colors::DIM, colors::RESET));
output.push_str(&format!("{}Key Features:{}\n", colors::BOLD, colors::RESET));
output.push_str(&format!(" {}•{} {}Scripting:{} LLM generates on-the-fly Python/JavaScript code to reduce LLM calls\n",
colors::GREEN, colors::RESET,
colors::BOLD, colors::RESET));
output.push_str(&format!(
" {}•{} {}Web Search:{} LLM can fetch protocol RFCs and documentation from the web\n",
colors::GREEN,
colors::RESET,
colors::BOLD,
colors::RESET
));
output.push_str(&format!(
" {}•{} {}File Reading:{} LLM can read local files (schemas, configs, prompts)\n",
colors::GREEN,
colors::RESET,
colors::BOLD,
colors::RESET
));
output.push_str(&format!(
" {}•{} {}Logging:{} Comprehensive logging system (TRACE/DEBUG/INFO/WARN/ERROR levels)\n",
colors::GREEN,
colors::RESET,
colors::BOLD,
colors::RESET
));
output.push_str(&format!(
" {}•{} {}Action-Based:{} Structured JSON responses for precise protocol control\n",
colors::GREEN,
colors::RESET,
colors::BOLD,
colors::RESET
));
output.push_str(&format!(" {}•{} {}Dynamic Reconfiguration:{} Change server behavior at runtime without restart\n\n",
colors::GREEN, colors::RESET,
colors::BOLD, colors::RESET));
output.push_str(&format!(
"{}Available Protocols:{}\n",
colors::BOLD,
colors::RESET
));
output.push_str(&format!(
"{}Use{} {}{}/docs <protocol>{} {}to see detailed information.{}\n\n",
colors::DIM,
colors::RESET,
colors::CYAN,
colors::BOLD,
colors::RESET,
colors::DIM,
colors::RESET
));
let registry = crate::protocol::server_registry::registry();
let all_protocols = registry.all_protocols();
let mut groups: std::collections::HashMap<&'static str, Vec<(String, DevelopmentState)>> =
std::collections::HashMap::new();
for (protocol_name, protocol) in &all_protocols {
let group = protocol.group_name();
let state = protocol.metadata().state;
groups
.entry(group)
.or_default()
.push((protocol_name.clone(), state));
}
let mut sorted_group_names: Vec<&'static str> = groups.keys().copied().collect();
sorted_group_names.sort();
for group_name in sorted_group_names {
if let Some(protocols) = groups.get(group_name) {
if protocols.is_empty() {
continue;
}
output.push_str(&format!(
"{}━━━ {} ━━━{}\n",
colors::BRIGHT_GREEN,
group_name,
colors::RESET
));
let mut sorted_protocols = protocols.clone();
sorted_protocols.sort_by(|a, b| a.0.cmp(&b.0));
let colored_protocols: Vec<String> = sorted_protocols
.iter()
.map(|(name, state)| {
let color = match state {
DevelopmentState::Stable => colors::GREEN,
DevelopmentState::Beta => colors::BLUE,
DevelopmentState::Experimental => colors::YELLOW,
DevelopmentState::Incomplete => colors::RED,
};
format!("{}{}{}", color, name, colors::RESET)
})
.collect();
let protocol_list =
colored_protocols.join(&format!("{}, {}", colors::RESET, colors::DIM));
output.push_str(&format!(" {}{}\n\n", colors::DIM, protocol_list));
}
}
output
}
pub fn show_protocol_docs(protocol_name: &str) -> Result<String, String> {
let registry = crate::protocol::server_registry::registry();
let parsed_protocol_name = registry.parse_from_str(protocol_name).ok_or_else(|| {
format!(
"{}Unknown protocol: {}{}. Use /docs to see all protocols.",
colors::RED,
protocol_name,
colors::RESET
)
})?;
let protocol = registry.get(&parsed_protocol_name).ok_or_else(|| {
format!(
"{}Protocol {} not found in registry{}",
colors::RED,
parsed_protocol_name,
colors::RESET
)
})?;
let metadata = protocol.metadata();
let stack_name = registry
.stack_name_by_protocol(&parsed_protocol_name)
.unwrap_or("UNKNOWN");
let mut output = String::new();
output.push_str(&format!(
"{}╭─────────────────────────────────────────╮{}\n",
colors::CYAN,
colors::RESET
));
output.push_str(&format!(
"{}│{} {}Protocol: {}{} {}│{}\n",
colors::CYAN,
colors::RESET,
colors::BOLD,
parsed_protocol_name,
colors::RESET,
colors::CYAN,
colors::RESET
));
output.push_str(&format!(
"{}╰─────────────────────────────────────────╯{}\n\n",
colors::CYAN,
colors::RESET
));
output.push_str(&format!(
"{}▸ Stack:{} {}{}{}\n",
colors::BRIGHT_CYAN,
colors::RESET,
colors::GREEN,
stack_name,
colors::RESET
));
let (status_color, status_symbol) = match metadata.state {
DevelopmentState::Stable => (colors::BRIGHT_GREEN, "✓"),
DevelopmentState::Beta => (colors::BRIGHT_YELLOW, "β"),
DevelopmentState::Experimental => (colors::YELLOW, "α"),
DevelopmentState::Incomplete => (colors::RED, "✗"),
};
output.push_str(&format!(
"{}▸ Status:{} {}{} {}{}\n",
colors::BRIGHT_CYAN,
colors::RESET,
status_color,
status_symbol,
metadata.state.as_str(),
colors::RESET
));
if !metadata.implementation.is_empty() {
output.push_str(&format!(
"{}▸ Implementation:{} {}{}{}\n",
colors::BRIGHT_CYAN,
colors::RESET,
colors::DIM,
metadata.implementation,
colors::RESET
));
}
if !metadata.llm_control.is_empty() {
output.push_str(&format!(
"{}▸ LLM Control:{} {}{}{}\n",
colors::BRIGHT_CYAN,
colors::RESET,
colors::DIM,
metadata.llm_control,
colors::RESET
));
}
if !metadata.e2e_testing.is_empty() {
output.push_str(&format!(
"{}▸ E2E Testing:{} {}{}{}\n",
colors::BRIGHT_CYAN,
colors::RESET,
colors::DIM,
metadata.e2e_testing,
colors::RESET
));
}
if let Some(notes) = metadata.notes {
output.push_str(&format!(
"{}▸ Notes:{} {}{}{}\n",
colors::BRIGHT_CYAN,
colors::RESET,
colors::YELLOW,
notes,
colors::RESET
));
}
use crate::protocol::metadata::PrivilegeSeverity;
let priv_color = match metadata.privilege_requirement.severity() {
PrivilegeSeverity::None => colors::GREEN,
PrivilegeSeverity::Elevated => colors::YELLOW,
PrivilegeSeverity::Root => colors::RED,
};
let priv_text = metadata.privilege_requirement.description();
output.push_str(&format!(
"{}▸ Privilege Required:{} {}{}{}\n",
colors::BRIGHT_CYAN,
colors::RESET,
priv_color,
priv_text,
colors::RESET
));
output.push_str(&format!(
"\n{}▸ Description:{}\n {}{}{}\n\n",
colors::BRIGHT_CYAN,
colors::RESET,
colors::DIM,
protocol.description(),
colors::RESET
));
if let Some(protocol) = registry.get(&parsed_protocol_name) {
let params = protocol.get_startup_parameters();
if !params.is_empty() {
output.push_str(&format!(
"\n{}━━━ Startup Parameters ━━━{}\n\n",
colors::BRIGHT_CYAN,
colors::RESET
));
output.push_str(&format!(
"{}These parameters can be provided when opening the server:{}\n\n",
colors::DIM,
colors::RESET
));
for param in params {
output.push_str(&format!(
"{}•{} {}{}{} ({}{}{}): {}\n",
colors::BLUE,
colors::RESET,
colors::BOLD,
param.name,
colors::RESET,
colors::YELLOW,
param.type_hint,
colors::RESET,
param.description
));
if param.required {
output.push_str(&format!(" {}[REQUIRED]{}\n", colors::RED, colors::RESET));
}
if let Ok(pretty_json) = serde_json::to_string_pretty(¶m.example) {
output.push_str(&format!(
" {}Example:{} {}{}{}\n",
colors::DIM,
colors::RESET,
colors::GREY,
pretty_json,
colors::RESET
));
}
}
output.push('\n');
}
let event_types = protocol.get_event_types();
if !event_types.is_empty() {
output.push_str(&format!(
"\n{}━━━ Event Types ━━━{}\n\n",
colors::BRIGHT_CYAN,
colors::RESET
));
output.push_str(&format!(
"{}This protocol can emit the following network events:{}\n\n",
colors::DIM,
colors::RESET
));
for event_type in event_types {
output.push_str(&format!(
"{}▸ Event: {}{}{}\n",
colors::MAGENTA,
colors::BOLD,
event_type.id,
colors::RESET
));
output.push_str(&format!(
" {}{}{}\n\n",
colors::GREY,
event_type.description,
colors::RESET
));
if !event_type.parameters.is_empty() {
output.push_str(&format!(" {}Event Data:{}\n", colors::CYAN, colors::RESET));
for param in &event_type.parameters {
output.push_str(&format!(
" {}•{} {}{}{} ({}{}{}): {}\n",
colors::BLUE,
colors::RESET,
colors::BOLD,
param.name,
colors::RESET,
colors::YELLOW,
param.type_hint,
colors::RESET,
param.description
));
}
output.push('\n');
}
if !event_type.actions.is_empty() {
output.push_str(&format!(
" {}Available Actions:{}\n",
colors::CYAN,
colors::RESET
));
for action in &event_type.actions {
output.push_str(&format!(
" {}•{} {}{}{}: {}\n",
colors::GREEN,
colors::RESET,
colors::BOLD,
action.name,
colors::RESET,
action.description
));
if !action.parameters.is_empty() {
output.push_str(&format!(
" {}Parameters:{}\n",
colors::DIM,
colors::RESET
));
for param in &action.parameters {
output.push_str(&format!(
" - {}{}{} ({}{}{}): {}\n",
colors::BOLD,
param.name,
colors::RESET,
colors::YELLOW,
param.type_hint,
colors::RESET,
param.description
));
}
}
if let Ok(pretty_json) = serde_json::to_string_pretty(&action.example) {
output.push_str(&format!(
" {}Example:{} {}{}{}\n",
colors::DIM,
colors::RESET,
colors::GREY,
pretty_json,
colors::RESET
));
}
}
output.push('\n');
} else {
output.push_str(&format!(
" {}No specific actions available for this event.{}\n\n",
colors::GREY,
colors::RESET
));
}
}
} else {
output.push_str(&format!(
"\n{}━━━ Event Types ━━━{}\n\n",
colors::BRIGHT_CYAN,
colors::RESET
));
output.push_str(&format!(
"{}This protocol hasn't documented its event types yet.{}\n\n",
colors::GREY,
colors::RESET
));
}
let async_actions = protocol.get_async_actions(&crate::state::app_state::AppState::new());
if !async_actions.is_empty() {
output.push_str(&format!(
"\n{}━━━ User-Triggered Actions ━━━{}\n\n",
colors::BRIGHT_CYAN,
colors::RESET
));
output.push_str(&format!("{}These actions can be triggered by user input (not tied to network events):{}\n\n",
colors::DIM, colors::RESET));
for action in async_actions {
output.push_str(&format!(
"{}•{} {}{}{}: {}\n",
colors::GREEN,
colors::RESET,
colors::BOLD,
action.name,
colors::RESET,
action.description
));
if !action.parameters.is_empty() {
output.push_str(&format!(" {}Parameters:{}\n", colors::CYAN, colors::RESET));
for param in &action.parameters {
output.push_str(&format!(
" - {}{}{} ({}{}{}): {}\n",
colors::BOLD,
param.name,
colors::RESET,
colors::YELLOW,
param.type_hint,
colors::RESET,
param.description
));
}
}
if let Ok(pretty_json) = serde_json::to_string_pretty(&action.example) {
output.push_str(&format!(
" {}Example:{} {}{}{}\n",
colors::DIM,
colors::RESET,
colors::GREY,
pretty_json,
colors::RESET
));
}
output.push('\n');
}
}
} else {
output.push_str("## Documentation\n\n");
output.push_str("Detailed documentation not available for this protocol.\n");
output.push_str("This protocol may not implement the Server trait yet.\n");
}
Ok(output)
}