use clap::{CommandFactory, Parser, Subcommand};
use clap_complete::{generate, Shell};
use mielin_cli::commands::*;
use mielin_cli::output::{render_output, MultiFormatDisplay, OutputFormat};
use std::io;
#[derive(Parser)]
#[command(name = "mielinctl")]
#[command(about = "MielinOS Control Interface", long_about = None)]
#[command(version)]
struct Cli {
#[arg(short, long, value_enum, default_value = "table", global = true)]
output: OutputFormat,
#[arg(short, long, global = true)]
quiet: bool,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Node {
#[command(subcommand)]
action: node::NodeCommands,
},
Agent {
#[command(subcommand)]
action: agent::AgentCommands,
},
Mesh {
#[command(subcommand)]
action: mesh::MeshCommands,
},
Cluster {
#[command(subcommand)]
action: cluster::ClusterCommands,
},
Migrate {
#[command(subcommand)]
action: migrate::MigrateCommands,
},
Registry {
#[command(subcommand)]
action: registry::RegistryCommands,
},
Gossip {
#[command(subcommand)]
action: gossip::GossipCommands,
},
Wasm {
#[command(subcommand)]
action: wasm::WasmCommands,
},
Debug {
#[command(subcommand)]
action: debug::DebugCommands,
},
#[command(visible_aliases = &["log", "logs"])]
Audit(audit::AuditCommand),
#[command(visible_aliases = &["cfg"])]
Config(config::ConfigCommand),
#[command(visible_aliases = &["hist"])]
History(history::HistoryCommand),
#[command(visible_aliases = &["mon", "observe"])]
Monitor(monitor::MonitorCommand),
#[command(visible_aliases = &["plugins", "ext"])]
Plugin {
#[command(subcommand)]
action: plugin::PluginCommands,
},
#[command(visible_aliases = &["scripts"])]
Script {
#[command(subcommand)]
action: script::ScriptCommands,
},
#[command(visible_aliases = &["nodes"])]
Remote {
#[command(subcommand)]
action: remote::RemoteCommands,
},
Daemon {
#[arg(short, long, default_value = "0.0.0.0:8080")]
listen: String,
#[arg(short, long, default_value = "edge")]
role: String,
#[arg(short, long)]
bootstrap: Option<String>,
},
Completion {
#[arg(value_enum)]
shell: Shell,
},
Version,
#[command(visible_aliases = &["repl", "shell"])]
Interactive,
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt::init();
let cli = Cli::parse();
let format = if cli.quiet {
OutputFormat::Quiet
} else {
cli.output
};
let result = match cli.command {
Commands::Node { action } => handle_node_command(action, format).await,
Commands::Agent { action } => handle_agent_command(action, format).await,
Commands::Mesh { action } => handle_mesh_command(action, format).await,
Commands::Cluster { action } => handle_cluster_command(action, format).await,
Commands::Migrate { action } => handle_migrate_command(action, format).await,
Commands::Registry { action } => handle_registry_command(action, format).await,
Commands::Gossip { action } => handle_gossip_command(action, format).await,
Commands::Wasm { action } => handle_wasm_command(action, format).await,
Commands::Debug { action } => handle_debug_command(action, format).await,
Commands::Audit(cmd) => handle_audit_command(cmd, format).await,
Commands::Config(cmd) => handle_config_command(cmd, format).await,
Commands::History(cmd) => cmd.execute(format).await,
Commands::Monitor(cmd) => handle_monitor_command(cmd, format).await,
Commands::Plugin { action } => handle_plugin_command(action, format).await,
Commands::Script { action } => handle_script_command(action, format).await,
Commands::Remote { action } => handle_remote_command(action, format).await,
Commands::Daemon {
listen,
role,
bootstrap,
} => handle_daemon_command(listen, role, bootstrap, format).await,
Commands::Completion { shell } => {
handle_completion_command(shell);
Ok(())
}
Commands::Version => {
handle_version_command(format);
Ok(())
}
Commands::Interactive => handle_interactive_command().await,
};
if let Err(e) = result {
eprintln!("Error: {}", mielin_cli::format_error(&e));
std::process::exit(1);
}
Ok(())
}
fn handle_completion_command(shell: Shell) {
let mut cmd = Cli::command();
let name = cmd.get_name().to_string();
generate(shell, &mut cmd, name, &mut io::stdout());
}
async fn handle_interactive_command() -> anyhow::Result<()> {
let mut repl = mielin_cli::Repl::new()?;
repl.run().await
}
fn handle_version_command(format: OutputFormat) {
use comfy_table::{presets::UTF8_FULL, Cell, Color, ContentArrangement, Table};
use serde::Serialize;
#[derive(Debug, Serialize)]
struct VersionInfo {
name: String,
version: String,
git_commit: String,
build_date: String,
rust_version: String,
target: String,
}
impl MultiFormatDisplay for VersionInfo {
fn to_table(&self) -> Table {
let mut table = Table::new();
table
.load_preset(UTF8_FULL)
.set_content_arrangement(ContentArrangement::Dynamic);
table.add_row(vec![
Cell::new("Name").fg(Color::Cyan),
Cell::new(&self.name),
]);
table.add_row(vec![
Cell::new("Version").fg(Color::Cyan),
Cell::new(&self.version),
]);
table.add_row(vec![
Cell::new("Rust").fg(Color::Cyan),
Cell::new(&self.rust_version),
]);
table.add_row(vec![
Cell::new("Target").fg(Color::Cyan),
Cell::new(&self.target),
]);
table
}
fn to_quiet(&self) -> String {
self.version.clone()
}
}
let info = VersionInfo {
name: "MielinOS CLI".to_string(),
version: env!("CARGO_PKG_VERSION").to_string(),
git_commit: option_env!("GIT_COMMIT").unwrap_or("unknown").to_string(),
build_date: chrono::Utc::now().format("%Y-%m-%d").to_string(),
rust_version: env!("CARGO_PKG_RUST_VERSION")
.parse::<String>()
.unwrap_or_else(|_| "unknown".to_string()),
target: std::env::consts::ARCH.to_string(),
};
println!("{}", render_output(&info, format).unwrap_or_default());
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn verify_cli() {
use clap::CommandFactory;
Cli::command().debug_assert();
}
}