#![allow(dead_code)]
pub mod spec;
pub use spec::{Category, CommandSpec};
#[derive(Debug, Clone)]
pub enum RenderBlock {
Text(String),
Markdown(String),
Error(String),
}
#[derive(Debug, Clone)]
pub enum CommandResult {
Output(RenderBlock),
SwitchView(String),
Noop,
Quit,
Error(String),
}
#[derive(Debug, Default, Clone)]
pub struct CommandRegistry {
specs: Vec<CommandSpec>,
}
impl CommandRegistry {
pub fn new() -> Self {
CommandRegistry { specs: Vec::new() }
}
pub fn with_builtins() -> Self {
CommandRegistry {
specs: builtin_specs().to_vec(),
}
}
pub fn register(&mut self, spec: CommandSpec) {
self.specs.push(spec);
}
pub fn all(&self) -> &[CommandSpec] {
&self.specs
}
pub fn resolve(&self, token: &str) -> Option<&CommandSpec> {
self.specs.iter().find(|s| s.matches(token))
}
pub fn by_category(&self, cat: Category) -> Vec<&CommandSpec> {
self.specs.iter().filter(|s| s.category == cat).collect()
}
pub fn search(&self, query: &str) -> Vec<&CommandSpec> {
let q = query.trim().to_ascii_lowercase();
if q.is_empty() {
return self.specs.iter().collect();
}
self.specs
.iter()
.filter(|s| {
s.name.to_ascii_lowercase().contains(&q)
|| s.summary.to_ascii_lowercase().contains(&q)
|| s.aliases
.iter()
.any(|a| a.to_ascii_lowercase().contains(&q))
})
.collect()
}
}
pub fn builtin_specs() -> &'static [CommandSpec] {
use spec::ArgSpec as A;
static SPECS: &[CommandSpec] = &[
CommandSpec {
name: "broker",
aliases: &[],
args: &[
A::optional("host", "Bind host (default 0.0.0.0)"),
A::optional("port", "Bind port (default 9000)"),
],
summary: "Start the compute broker with a live transaction log.",
help_md: "Start the broker. Use `-t broker` for the interactive dashboard, \
or `-d broker` to run in the background (daemon).",
category: Category::Broker,
},
CommandSpec {
name: "up",
aliases: &[],
args: &[
A::optional("--workers N", "Number of workers to start (default 1)"),
A::optional("--port P", "Base worker port (default 3960)"),
A::optional("--broker-port P", "Broker port (default 9000)"),
],
summary: "Start a local cluster (broker + N workers).",
help_md: "Start a local cluster. Example: `zc up --workers 4` starts a \
broker plus four workers. Add `-d` to run in the background.",
category: Category::Cluster,
},
CommandSpec {
name: "mesh",
aliases: &[],
args: &[
A::optional("up|down|status", "Mesh action (default status)"),
A::optional("N", "Node count for `up` (default 4)"),
],
summary: "Launch/manage a local Docker compute-node mesh + broker.",
help_md: "Orchestrate a local mesh of zakuro compute nodes: `mesh up 4` \
launches 4 nodes on a shared network, starts a worker on each, and \
runs a broker that discovers them; `mesh down` tears it down; \
`mesh status` lists running nodes. Local mode — no auth.",
category: Category::Cluster,
},
CommandSpec {
name: "down",
aliases: &[],
args: &[
A::optional("--port BASE", "Base worker port to scan"),
A::optional("--broker-port P", "Broker port"),
],
summary: "Stop all workers + broker started by 'zc up'.",
help_md: "Stop the local cluster previously started with `zc up`.",
category: Category::Cluster,
},
CommandSpec {
name: "workers",
aliases: &[],
args: &[A::optional("zc://node", "Broker to query (default local)")],
summary: "List workers on a broker.",
help_md: "List workers on the local broker, or on a named broker: \
`zc workers zc://node-name`.",
category: Category::Monitoring,
},
CommandSpec {
name: "brokers",
aliases: &[],
args: &[],
summary: "List mesh brokers by key-derived id (IP-free).",
help_md: "List the brokers reachable from the local broker's mesh view, each \
shown as its key-derived `zc://node-<fp>` id with a reachability dot. \
Never prints an IP or peer address.",
category: Category::Monitoring,
},
CommandSpec {
name: "price",
aliases: &[],
args: &[A::optional("value", "New price (credits/hour) to set")],
summary: "Show or set this broker's advertised price (credits/hour).",
help_md: "Show the local broker's advertised price with `zc price`, or set it \
with `zc price <value>`. Price-setting is local-only (loopback), never \
settable by a remote peer.",
category: Category::Monitoring,
},
CommandSpec {
name: "bench",
aliases: &[],
args: &[
A::optional("-s strategy", "Routing strategy (e.g. best_latency)"),
A::optional("--compare", "Compare all routing strategies"),
A::optional("zc://node", "Broker to benchmark"),
],
summary: "Benchmark broker throughput and latency.",
help_md: "Benchmark the broker. `bench --compare` compares all routing \
strategies; `bench -c 50 -n 10000` sets concurrency and request count.",
category: Category::Benchmark,
},
CommandSpec {
name: "me",
aliases: &["whoami", "credits"],
args: &[],
summary: "Show authenticated user info and credit balance.",
help_md: "Show the authenticated user and credit balance. Requires \
`ZAKURO_API_KEY`.",
category: Category::Identity,
},
CommandSpec {
name: "discovery",
aliases: &["discover"],
args: &[A::optional("zc://node", "Broker to query (default local)")],
summary: "List the worker fleet discovered by the broker.",
help_md: "List the workers a broker has discovered, e.g. `/discovery` for the \
local broker or `/discovery zc://node`. Shows node, status, URI, CPU \
and memory for each registered worker.",
category: Category::Monitoring,
},
CommandSpec {
name: "attach",
aliases: &[],
args: &[A::required("zc://node", "Broker to attach to")],
summary: "Attach to a broker's live dashboard.",
help_md: "Attach to a running broker's interactive dashboard, e.g. \
`zc attach zc://node-name` or `zc attach zc://localhost`.",
category: Category::Monitoring,
},
CommandSpec {
name: "info",
aliases: &[],
args: &[],
summary: "Show system info, clusters, and network status.",
help_md: "Print local system information, detected clusters, and \
network/mesh status.",
category: Category::Diagnostics,
},
CommandSpec {
name: "update",
aliases: &[],
args: &[],
summary: "Update the command line.",
help_md: "Update `zc` to the latest released version.",
category: Category::Container,
},
CommandSpec {
name: "pull",
aliases: &[],
args: &[],
summary: "Pull updated images.",
help_md: "Pull the latest Zakuro container images.",
category: Category::Container,
},
CommandSpec {
name: "images",
aliases: &[],
args: &[],
summary: "List Zakuro images built on the machine.",
help_md: "List Zakuro container images present locally.",
category: Category::Container,
},
CommandSpec {
name: "ps",
aliases: &[],
args: &[],
summary: "List running Zakuro containers.",
help_md: "List currently running Zakuro containers.",
category: Category::Container,
},
CommandSpec {
name: "kill",
aliases: &[],
args: &[],
summary: "Remove running Zakuro containers.",
help_md: "Stop and remove running Zakuro containers.",
category: Category::Container,
},
CommandSpec {
name: "restart",
aliases: &[],
args: &[],
summary: "Restart containers with updated images.",
help_md: "Restart Zakuro containers using updated images.",
category: Category::Container,
},
CommandSpec {
name: "theme",
aliases: &[],
args: &[A::optional("name", "Theme name (dark, light)")],
summary: "Show or switch the interface theme.",
help_md: "Show the current theme, or switch it: `/theme light`. Custom \
themes load from `~/.config/zc/theme.toml`.",
category: Category::Ui,
},
CommandSpec {
name: "mode",
aliases: &[],
args: &[A::optional("local|p2p", "Network mode to switch to")],
summary: "Show or switch the broker network mode (local/p2p).",
help_md: "Show the current mode, or switch: `/mode p2p` joins the peer \
network (requires `ZAKURO_API_KEY`); `/mode local` runs against \
the local broker with no auth.",
category: Category::Ui,
},
CommandSpec {
name: "vpn",
aliases: &[],
args: &[A::optional(
"connect|disconnect|status",
"VPN action (default status)",
)],
summary: "Connect zc to the zakuro mesh VPN (p2p).",
help_md: "Join the zakuro WireGuard mesh: `zc vpn connect` fetches this \
node's profile (requires `ZAKURO_API_KEY`) and brings up the \
tunnel (native if root+WireGuard, else a Docker sidecar). \
`zc vpn status` shows the link + peers; `zc vpn disconnect` \
tears it down. Add `--native` or `--docker` to force a backend.",
category: Category::Cluster,
},
CommandSpec {
name: "shell",
aliases: &["repl"],
args: &[],
summary: "Launch the interactive terminal.",
help_md: "Launch the interactive `zc` terminal: a REPL with slash \
commands, a command palette, and the live dashboard as a view.",
category: Category::Ui,
},
CommandSpec {
name: "help",
aliases: &["-h", "--help"],
args: &[A::optional("command", "Show help for a specific command")],
summary: "Show help.",
help_md: "Show general help, or help for a specific command: `help bench`.",
category: Category::Meta,
},
CommandSpec {
name: "clear",
aliases: &[],
args: &[],
summary: "Clear the REPL scrollback.",
help_md: "Clear the interactive terminal's output area.",
category: Category::Meta,
},
CommandSpec {
name: "quit",
aliases: &["exit", "q"],
args: &[],
summary: "Exit the interactive terminal.",
help_md: "Exit the interactive `zc` terminal.",
category: Category::Meta,
},
];
SPECS
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builtins_are_registered() {
let reg = CommandRegistry::with_builtins();
assert!(!reg.all().is_empty());
assert!(reg.resolve("workers").is_some());
}
#[test]
fn aliases_resolve() {
let reg = CommandRegistry::with_builtins();
let me = reg.resolve("whoami").expect("alias resolves");
assert_eq!(me.name, "me");
let credits = reg.resolve("credits").expect("alias resolves");
assert_eq!(credits.name, "me");
}
#[test]
fn usage_formatting() {
let reg = CommandRegistry::with_builtins();
let attach = reg.resolve("attach").unwrap();
assert_eq!(attach.usage(), "attach <zc://node>");
}
#[test]
fn search_matches_summary_and_name() {
let reg = CommandRegistry::with_builtins();
assert!(reg.search("bench").iter().any(|s| s.name == "bench"));
assert!(reg.search("credit").iter().any(|s| s.name == "me"));
assert_eq!(reg.search("").len(), reg.all().len());
}
#[test]
fn categories_have_titles() {
for c in Category::order() {
assert!(!c.title().is_empty());
}
}
}