use std::path::PathBuf;
use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use kkernel::{pack_introspect, sync};
#[derive(Parser, Debug)]
#[command(
name = "kkernel",
version,
about = "khive kernel — admin/management Rust binary (ADR-076)"
)]
struct Args {
#[arg(long, env = "KHIVE_LOG", default_value = "warn", global = true)]
log: String,
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand, Debug)]
enum Command {
Sync(SyncArgs),
#[command(subcommand)]
Pack(PackCommand),
}
#[derive(Parser, Debug)]
struct SyncArgs {
#[arg(long, default_value = ".")]
repo: PathBuf,
#[arg(long)]
db: PathBuf,
#[arg(long, default_value = "local")]
namespace: String,
}
#[derive(Subcommand, Debug)]
enum PackCommand {
List {
#[arg(long)]
human: bool,
},
Handler {
name: String,
#[arg(long)]
human: bool,
},
}
#[tokio::main]
async fn main() -> Result<()> {
let args = Args::parse();
init_tracing(&args.log);
match args.command {
Command::Sync(s) => cmd_sync(s).await,
Command::Pack(p) => cmd_pack(p),
}
}
fn init_tracing(level: &str) {
tracing_subscriber::fmt()
.with_writer(std::io::stderr)
.with_env_filter(level)
.with_ansi(false)
.init();
}
async fn cmd_sync(args: SyncArgs) -> Result<()> {
let report = sync::run_sync(&args.repo, &args.db, &args.namespace)
.await
.with_context(|| {
format!(
"sync failed for repo={} db={}",
args.repo.display(),
args.db.display()
)
})?;
let json = serde_json::to_string(&report).expect("serialize SyncReport");
println!("{json}");
Ok(())
}
fn cmd_pack(cmd: PackCommand) -> Result<()> {
match cmd {
PackCommand::List { human } => {
let packs = pack_introspect::list_packs()?;
if human {
for p in &packs {
println!("# {} ({} verbs)", p.name, p.verbs.len());
if !p.requires.is_empty() {
println!(" requires: {}", p.requires.join(", "));
}
if !p.note_kinds.is_empty() {
println!(" note_kinds: {}", p.note_kinds.join(", "));
}
if !p.entity_kinds.is_empty() {
println!(" entity_kinds: {}", p.entity_kinds.join(", "));
}
for v in &p.verbs {
println!(" {:<20} {}", v.name, v.description);
}
println!();
}
} else {
let json = serde_json::to_string(&packs).expect("serialize PackInfo[]");
println!("{json}");
}
Ok(())
}
PackCommand::Handler { name, human } => {
let info = pack_introspect::pack_handler(&name)?;
let info = info.with_context(|| format!("pack {name:?} is not registered"))?;
if human {
println!("# {} ({} verbs)", info.name, info.verbs.len());
if !info.requires.is_empty() {
println!("requires: {}", info.requires.join(", "));
}
if !info.note_kinds.is_empty() {
println!("note_kinds: {}", info.note_kinds.join(", "));
}
if !info.entity_kinds.is_empty() {
println!("entity_kinds: {}", info.entity_kinds.join(", "));
}
for v in &info.verbs {
println!(" {:<20} {}", v.name, v.description);
}
} else {
let json = serde_json::to_string(&info).expect("serialize PackInfo");
println!("{json}");
}
Ok(())
}
}
}