use std::path::PathBuf;
use clap::{Args, Parser, Subcommand};
use crate::commands::{chats, customers, people, products, repos, search};
use crate::output::{exit, report, Ctx};
use crate::{data, init, validate};
#[derive(Parser, Debug)]
#[command(
name = "liber",
bin_name = "liber",
version,
about = "AI-agent-readable company directory CLI",
long_about = "liber — query a liber data directory (people / products / repos / customers / chats).\n\n\
Data dir resolution: --data-dir > $LIBER_DATA_DIR > ./.liber > cwd > ~/.liber.\n\
Output is human-friendly on a TTY, JSON otherwise (or with --json).\n\
Exit codes: 0=ok, 2=validation, 4=not-found, 5=conflict, 6=data."
)]
struct Cli {
#[arg(long, global = true)]
json: bool,
#[arg(long, global = true)]
quiet: bool,
#[arg(long, global = true)]
full: bool,
#[arg(long, global = true)]
no_interactive: bool,
#[arg(long, global = true, value_name = "DIR")]
data_dir: Option<PathBuf>,
#[command(subcommand)]
cmd: Cmd,
}
#[derive(Subcommand, Debug)]
enum Cmd {
Init(InitArgs),
Validate(ValidateArgs),
People(NounArgs<PeopleVerb>),
Products(NounArgs<ProductsVerb>),
Customers(NounArgs<CustomersVerb>),
Chats(NounArgs<ChatsVerb>),
Repos(NounArgs<ReposVerb>),
Search(SearchArgs),
}
#[derive(Args, Debug)]
struct NounArgs<V: Subcommand> {
#[command(subcommand)]
verb: V,
}
#[derive(Subcommand, Debug)]
enum PeopleVerb {
List {
#[arg(long)]
dept: Option<String>,
},
Get { name: String },
}
#[derive(Subcommand, Debug)]
enum ProductsVerb {
List,
Get { slug: String },
}
#[derive(Subcommand, Debug)]
enum CustomersVerb {
List,
Get { slug: String },
}
#[derive(Subcommand, Debug)]
enum ChatsVerb {
List,
Get { name: String },
}
#[derive(Subcommand, Debug)]
enum ReposVerb {
List {
#[arg(long)]
visibility: Option<String>,
},
Get { slug: String },
}
#[derive(Args, Debug)]
struct InitArgs {
slug: String,
path: Option<PathBuf>,
#[arg(long)]
force: bool,
}
#[derive(Args, Debug)]
struct ValidateArgs {}
#[derive(Args, Debug)]
struct SearchArgs {
query: String,
}
pub fn run() -> i32 {
let cli = match Cli::try_parse() {
Ok(c) => c,
Err(e) => {
let kind = e.kind();
e.print().ok();
return match kind {
clap::error::ErrorKind::DisplayHelp
| clap::error::ErrorKind::DisplayVersion => exit::OK,
_ => exit::VALIDATION,
};
}
};
let ctx = Ctx::new(cli.json, cli.quiet, cli.full, cli.no_interactive);
let result = dispatch(&cli, ctx);
match result {
Ok(()) => exit::OK,
Err(err) => {
report(&err);
err.code
}
}
}
fn dispatch(cli: &Cli, ctx: Ctx) -> Result<(), crate::output::CliError> {
match &cli.cmd {
Cmd::Init(a) => {
let path = a
.path
.clone()
.unwrap_or_else(|| PathBuf::from(format!("./{}", a.slug)));
init::run(ctx, &a.slug, &path, a.force)
}
Cmd::Validate(_) => {
let dir = data::resolve_data_dir(cli.data_dir.as_deref())?;
validate::run(&dir, ctx)
}
Cmd::People(n) => {
let dir = data::resolve_data_dir(cli.data_dir.as_deref())?;
match &n.verb {
PeopleVerb::List { dept } => people::list(&dir, ctx, dept.as_deref()),
PeopleVerb::Get { name } => people::get(&dir, ctx, name),
}
}
Cmd::Products(n) => {
let dir = data::resolve_data_dir(cli.data_dir.as_deref())?;
match &n.verb {
ProductsVerb::List => products::list(&dir, ctx),
ProductsVerb::Get { slug } => products::get(&dir, ctx, slug),
}
}
Cmd::Customers(n) => {
let dir = data::resolve_data_dir(cli.data_dir.as_deref())?;
match &n.verb {
CustomersVerb::List => customers::list(&dir, ctx),
CustomersVerb::Get { slug } => customers::get(&dir, ctx, slug),
}
}
Cmd::Chats(n) => {
let dir = data::resolve_data_dir(cli.data_dir.as_deref())?;
match &n.verb {
ChatsVerb::List => chats::list(&dir, ctx),
ChatsVerb::Get { name } => chats::get(&dir, ctx, name),
}
}
Cmd::Repos(n) => {
let dir = data::resolve_data_dir(cli.data_dir.as_deref())?;
match &n.verb {
ReposVerb::List { visibility } => {
repos::list(&dir, ctx, visibility.as_deref())
}
ReposVerb::Get { slug } => repos::get(&dir, ctx, slug),
}
}
Cmd::Search(a) => {
let dir = data::resolve_data_dir(cli.data_dir.as_deref())?;
search::run(&dir, ctx, &a.query)
}
}
}