mod bench;
mod cell;
mod config;
mod docker;
mod embed;
mod health;
mod output;
mod paths;
use anyhow::Result;
use clap::{Parser, Subcommand};
use std::env;
#[derive(Parser)]
#[command(
name = "knishio",
about = "KnishIO Validator Orchestration CLI",
version,
propagate_version = true
)]
struct Cli {
#[arg(long, global = true, default_value = "https://localhost:8080")]
url: String,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Start {
#[arg(long)]
build: bool,
#[arg(short, long)]
detach: bool,
},
Stop,
Destroy {
#[arg(long)]
volumes: bool,
},
Rebuild,
Logs {
#[arg(short, long)]
follow: bool,
#[arg(long)]
tail: Option<usize>,
},
Status,
Cell {
#[command(subcommand)]
command: CellCommands,
},
Bench {
#[command(subcommand)]
command: BenchCommands,
},
Embed {
#[command(subcommand)]
command: EmbedCommands,
},
Health,
Ready,
Full,
Db,
}
#[derive(Subcommand)]
enum CellCommands {
Create {
slug: String,
#[arg(long)]
name: Option<String>,
#[arg(long, default_value = "active")]
status: String,
},
List,
Activate {
slug: String,
},
Pause {
slug: String,
},
Archive {
slug: String,
},
}
#[derive(Subcommand)]
enum BenchCommands {
Run {
#[arg(long, default_value_t = 50)]
identities: usize,
#[arg(long, default_value = "meta", value_delimiter = ',')]
types: Vec<String>,
#[arg(long, default_value_t = 100)]
metas_per_identity: usize,
#[arg(long, default_value_t = 10)]
transfers_per_identity: usize,
#[arg(long, default_value_t = 5)]
rules_per_identity: usize,
#[arg(long, default_value_t = 5)]
burns_per_identity: usize,
#[arg(long, default_value_t = 1_000_000.0)]
token_amount: f64,
#[arg(long, default_value = "https://localhost:8080")]
endpoint: String,
#[arg(long, default_value_t = 5)]
concurrency: usize,
#[arg(long)]
cell_slug: Option<String>,
#[arg(long)]
keep: bool,
},
Generate {
#[arg(long, default_value_t = 50)]
identities: usize,
#[arg(long, default_value = "meta", value_delimiter = ',')]
types: Vec<String>,
#[arg(long, default_value_t = 100)]
metas_per_identity: usize,
#[arg(long, default_value_t = 10)]
transfers_per_identity: usize,
#[arg(long, default_value_t = 5)]
rules_per_identity: usize,
#[arg(long, default_value_t = 5)]
burns_per_identity: usize,
#[arg(long, default_value_t = 1_000_000.0)]
token_amount: f64,
#[arg(short, long)]
output: String,
},
Execute {
plan: String,
#[arg(long, default_value = "https://localhost:8080")]
endpoint: String,
#[arg(long, default_value_t = 5)]
concurrency: usize,
#[arg(long)]
cell_slug: Option<String>,
#[arg(long)]
keep: bool,
},
Clean {
#[arg(long, conflicts_with = "all")]
cell_slug: Option<String>,
#[arg(long, conflicts_with = "cell_slug")]
all: bool,
},
}
#[derive(Subcommand)]
enum EmbedCommands {
Status,
Reset {
#[arg(long, conflicts_with = "all")]
model: Option<String>,
#[arg(long, conflicts_with = "model")]
all: bool,
#[arg(long, short = 'y')]
yes: bool,
},
Search {
query: String,
#[arg(long, default_value_t = 10)]
limit: i32,
#[arg(long, default_value_t = 0.7)]
threshold: f64,
#[arg(long)]
meta_type: Option<String>,
},
Ask {
question: String,
#[arg(long, default_value_t = 20)]
max_results: i32,
#[arg(long, default_value_t = 0.5)]
threshold: f64,
#[arg(long)]
meta_type: Option<String>,
},
}
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
let cwd = env::current_dir()?;
let cfg = config::Config::load(&cwd).with_url_override(&cli.url);
match cli.command {
Commands::Start { build, detach } => {
let compose = require_compose(&cwd, &cfg)?;
docker::start(&compose, build, detach).await?;
}
Commands::Stop => {
let compose = require_compose(&cwd, &cfg)?;
docker::stop(&compose).await?;
}
Commands::Destroy { volumes } => {
let compose = require_compose(&cwd, &cfg)?;
docker::destroy(&compose, volumes).await?;
}
Commands::Rebuild => {
let compose = require_compose(&cwd, &cfg)?;
docker::rebuild(&compose).await?;
}
Commands::Logs { follow, tail } => {
let compose = require_compose(&cwd, &cfg)?;
docker::logs(&compose, follow, tail).await?;
}
Commands::Status => {
let compose = require_compose(&cwd, &cfg)?;
docker::status(&compose).await?;
}
Commands::Cell { command } => match command {
CellCommands::Create { slug, name, status } => {
cell::create(&cfg, &slug, name.as_deref(), &status).await?;
}
CellCommands::List => {
cell::list(&cfg).await?;
}
CellCommands::Activate { slug } => {
cell::set_status(&cfg, &slug, "active").await?;
}
CellCommands::Pause { slug } => {
cell::set_status(&cfg, &slug, "paused").await?;
}
CellCommands::Archive { slug } => {
cell::set_status(&cfg, &slug, "archived").await?;
}
},
Commands::Bench { command } => match command {
BenchCommands::Run {
identities,
types,
metas_per_identity,
transfers_per_identity,
rules_per_identity,
burns_per_identity,
token_amount,
endpoint,
concurrency,
cell_slug,
keep,
} => {
let gen_args = bench::generate::GenerateArgs {
identities,
types,
metas_per_identity,
transfers_per_identity,
rules_per_identity,
burns_per_identity,
token_amount,
output: String::new(), };
let exec_args = bench::execute::ExecuteArgs {
plan: String::new(), endpoint: Some(endpoint),
endpoints: None,
strategy: bench::execute::Strategy::RoundRobin,
concurrency,
cell_slug,
csv: None,
plot: None,
insecure_tls: cfg.validator.insecure_tls,
};
bench::run(gen_args, exec_args, &cfg, keep).await?;
}
BenchCommands::Generate {
identities,
types,
metas_per_identity,
transfers_per_identity,
rules_per_identity,
burns_per_identity,
token_amount,
output: output_path,
} => {
let gen_args = bench::generate::GenerateArgs {
identities,
types,
metas_per_identity,
transfers_per_identity,
rules_per_identity,
burns_per_identity,
token_amount,
output: output_path,
};
bench::generate(gen_args)?;
output::success("Plan generation complete");
}
BenchCommands::Execute {
plan,
endpoint,
concurrency,
cell_slug,
keep,
} => {
let exec_args = bench::execute::ExecuteArgs {
plan,
endpoint: Some(endpoint),
endpoints: None,
strategy: bench::execute::Strategy::RoundRobin,
concurrency,
cell_slug,
csv: None,
plot: None,
insecure_tls: cfg.validator.insecure_tls,
};
bench::execute(exec_args, &cfg, keep).await?;
}
BenchCommands::Clean { cell_slug, all } => {
bench::clean(&cfg, cell_slug.as_deref(), all).await?;
}
},
Commands::Embed { command } => match command {
EmbedCommands::Status => {
embed::status(&cfg).await?;
}
EmbedCommands::Reset { model, all, yes } => {
embed::reset(&cfg, model.as_deref(), all, yes).await?;
}
EmbedCommands::Search {
query,
limit,
threshold,
meta_type,
} => {
embed::search(&cfg, &query, limit, threshold, meta_type.as_deref()).await?;
}
EmbedCommands::Ask {
question,
max_results,
threshold,
meta_type,
} => {
embed::ask(&cfg, &question, max_results, threshold, meta_type.as_deref()).await?;
}
},
Commands::Health => {
health::healthz(&cfg.validator.url, cfg.validator.insecure_tls).await?;
}
Commands::Ready => {
health::readyz(&cfg.validator.url, false, cfg.validator.insecure_tls).await?;
}
Commands::Full => {
health::readyz(&cfg.validator.url, true, cfg.validator.insecure_tls).await?;
}
Commands::Db => {
health::db_check(&cfg.validator.url, cfg.validator.insecure_tls).await?;
}
}
Ok(())
}
fn require_compose(cwd: &std::path::Path, cfg: &config::Config) -> Result<std::path::PathBuf> {
paths::find_compose_file(cwd, &cfg.docker.compose_file).ok_or_else(|| {
anyhow::anyhow!(
"Could not find {}.\n\
Run this command from inside the KnishIO project tree.",
cfg.docker.compose_file
)
})
}