126 lines
4.0 KiB
Rust
126 lines
4.0 KiB
Rust
//! Lattice Interactive CLI
|
|
|
|
mod commands;
|
|
mod node_commands;
|
|
mod store_commands;
|
|
|
|
use lattice_net::spawn_accept_loop;
|
|
use commands::CommandResult;
|
|
use lattice_core::{NodeBuilder, StoreHandle};
|
|
use rustyline::error::ReadlineError;
|
|
use rustyline::DefaultEditor;
|
|
use std::sync::Arc;
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
println!("Lattice CLI v{}", env!("CARGO_PKG_VERSION"));
|
|
println!("Type 'help' for commands, 'quit' to exit.\n");
|
|
|
|
let node = match NodeBuilder::new().build() {
|
|
Ok(n) => Arc::new(n),
|
|
Err(e) => {
|
|
eprintln!("Failed to initialize: {}", e);
|
|
return;
|
|
}
|
|
};
|
|
|
|
// Start Iroh endpoint using same Ed25519 identity
|
|
let endpoint = match lattice_net::LatticeEndpoint::new(node.secret_key_bytes()).await {
|
|
Ok(ep) => {
|
|
println!("Iroh: {} (listening)", ep.public_key().fmt_short());
|
|
Some(ep)
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Warning: Iroh failed to start: {}", e);
|
|
None
|
|
}
|
|
};
|
|
|
|
// Spawn accept loop for incoming connections
|
|
if let Some(ref ep) = endpoint {
|
|
spawn_accept_loop(node.clone(), ep.endpoint().clone());
|
|
}
|
|
|
|
let info = node.info();
|
|
println!("Node ID: {}", info.node_id);
|
|
println!("Data: {}", info.data_path);
|
|
|
|
if !info.stores.is_empty() {
|
|
println!("Stores: {}", info.stores.len());
|
|
}
|
|
|
|
let mut current_store: Option<StoreHandle> = match node.open_root_store().await {
|
|
Ok(Some(open_info)) => {
|
|
if open_info.entries_replayed > 0 {
|
|
println!("Root: {} (replayed {})", open_info.store_id, open_info.entries_replayed);
|
|
} else {
|
|
println!("Root: {}", open_info.store_id);
|
|
}
|
|
node.root_store().await.as_ref().cloned()
|
|
}
|
|
Ok(None) => {
|
|
println!("Status: Not initialized (use 'init')");
|
|
None
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Warning: {}", e);
|
|
None
|
|
}
|
|
};
|
|
println!();
|
|
|
|
let mut rl = DefaultEditor::new().expect("Failed to create editor");
|
|
let cmds = commands::commands();
|
|
|
|
loop {
|
|
let prompt = match ¤t_store {
|
|
Some(h) => format!("lattice:{}> ", &h.id().to_string()[..8]),
|
|
None => "lattice:no-store> ".to_string(),
|
|
};
|
|
|
|
match rl.readline(&prompt) {
|
|
Ok(line) => {
|
|
let line = line.trim();
|
|
if line.is_empty() { continue; }
|
|
let _ = rl.add_history_entry(line);
|
|
|
|
let args = match shlex::split(line) {
|
|
Some(a) => a,
|
|
None => {
|
|
println!("Error: mismatched quotes");
|
|
continue;
|
|
}
|
|
};
|
|
|
|
let cmd_name = args.first().map(|s| s.as_str()).unwrap_or("");
|
|
|
|
match cmds.iter().find(|c| c.name == cmd_name || (cmd_name == "exit" && c.name == "quit")) {
|
|
Some(cmd) => {
|
|
let cmd_args = &args[1..];
|
|
if cmd_args.len() < cmd.min_args || cmd_args.len() > cmd.max_args {
|
|
println!("Usage: {} {}", cmd.name, cmd.args);
|
|
} else {
|
|
match (cmd.handler)(&node, current_store.as_ref(), endpoint.as_ref(), cmd_args) {
|
|
CommandResult::Ok => {}
|
|
CommandResult::SwitchTo(h) => {
|
|
current_store = Some(h);
|
|
}
|
|
CommandResult::Quit => break,
|
|
}
|
|
}
|
|
}
|
|
None => println!("Unknown: '{}'. Type 'help'.", cmd_name),
|
|
}
|
|
}
|
|
Err(ReadlineError::Interrupted | ReadlineError::Eof) => {
|
|
println!("Goodbye!");
|
|
break;
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Error: {:?}", e);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|