mod cli;
mod commands;
pub mod hook;
mod output;
pub use cli::{
BrowseArgs, CapsuleCloseArgs, CapsuleCommand, CapsuleOpenArgs, Cli, Command, CommonOpts,
DemoArgs, ExportArgs, ForgetArgs, ImportArgs, InitArgs, ListArgs, LogArgs, PinArgs, SearchArgs,
ServeArgs, SpoolCommand, SpoolStatusArgs, StatusArgs, UnpinArgs,
};
use std::path::PathBuf;
use kindling_service::KindlingService;
#[derive(Debug, thiserror::Error)]
pub enum CliError {
#[error(transparent)]
Service(#[from] kindling_service::ServiceError),
#[error(transparent)]
Store(#[from] kindling_store::StoreError),
#[error(transparent)]
Client(#[from] kindling_client::ClientError),
#[error(transparent)]
Spool(#[from] kindling_client::SpoolError),
#[error(transparent)]
Server(#[from] kindling_server::ServerError),
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
Json(#[from] serde_json::Error),
#[error("sqlite error: {0}")]
Sqlite(#[from] rusqlite::Error),
#[error("{0}")]
Invalid(String),
}
pub type CliResult = Result<(), CliError>;
pub fn resolve_db_path(explicit: Option<&str>) -> Result<PathBuf, CliError> {
if let Some(path) = explicit {
return Ok(PathBuf::from(path));
}
let cwd = std::env::current_dir()?;
let project_root = cwd.to_string_lossy();
kindling_store::resolve_db_path(&project_root).ok_or_else(|| {
CliError::Invalid(
"could not resolve a database path: no --db, no KINDLING_DB_PATH, \
and no home directory (HOME/USERPROFILE) to derive a per-project path"
.to_string(),
)
})
}
pub fn open_service(explicit_db: Option<&str>) -> Result<(KindlingService, PathBuf), CliError> {
let path = resolve_db_path(explicit_db)?;
if let Some(parent) = path.parent() {
if !parent.as_os_str().is_empty() {
std::fs::create_dir_all(parent)?;
}
}
let service = KindlingService::open(&path)?;
Ok((service, path))
}
pub fn cli_main() -> i32 {
let cli = <Cli as clap::Parser>::parse();
run(cli)
}
pub fn run(cli: Cli) -> i32 {
let as_json = json_flag(&cli.command);
match dispatch(cli) {
Ok(()) => 0,
Err(err) => {
output::print_error(&err.to_string(), as_json);
1
}
}
}
fn json_flag(command: &Command) -> bool {
match command {
Command::Init(a) => a.json,
Command::Log(a) => a.common.json,
Command::Capsule(CapsuleCommand::Open(a)) => a.common.json,
Command::Capsule(CapsuleCommand::Close(a)) => a.common.json,
Command::Status(a) => a.common.json,
Command::Search(a) => a.common.json,
Command::List(a) => a.common.json,
Command::Pin(a) => a.common.json,
Command::Unpin(a) => a.common.json,
Command::Forget(a) => a.common.json,
Command::Export(a) => a.common.json,
Command::Import(a) => a.common.json,
Command::Serve(_) => false,
Command::Demo(a) => a.common.json,
Command::Browse(a) => a.common.json,
Command::Spool(SpoolCommand::Status(a)) => a.json,
}
}
fn dispatch(cli: Cli) -> CliResult {
let via_daemon = cli.via_daemon;
match cli.command {
Command::Init(args) => commands::init::run(args),
Command::Log(args) => commands::log::run(args, via_daemon),
Command::Capsule(CapsuleCommand::Open(args)) => {
commands::capsule::run_open(args, via_daemon)
}
Command::Capsule(CapsuleCommand::Close(args)) => {
commands::capsule::run_close(args, via_daemon)
}
Command::Status(args) => commands::status::run(args),
Command::Search(args) => commands::search::run(args, via_daemon),
Command::List(args) => commands::list::run(args),
Command::Pin(args) => commands::pin::run_pin(args, via_daemon),
Command::Unpin(args) => commands::pin::run_unpin(args, via_daemon),
Command::Forget(args) => commands::forget::run(args, via_daemon),
Command::Export(args) => commands::export::run_export(args, via_daemon),
Command::Import(args) => commands::export::run_import(args, via_daemon),
Command::Serve(args) => commands::serve::run(args),
Command::Demo(args) => commands::demo::run(args),
Command::Browse(args) => commands::browse::run(args),
Command::Spool(SpoolCommand::Status(args)) => commands::spool::run_status(args),
}
}
pub(crate) fn build_client() -> Result<kindling_client::Client, CliError> {
Ok(kindling_client::Client::new()?)
}
pub(crate) fn runtime() -> std::io::Result<tokio::runtime::Runtime> {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
}