1mod cli;
18mod commands;
19pub mod hook;
20mod output;
21
22pub use cli::{
23 BrowseArgs, CapsuleCloseArgs, CapsuleCommand, CapsuleOpenArgs, Cli, Command, CommonOpts,
24 DemoArgs, ExportArgs, ForgetArgs, ImportArgs, InitArgs, ListArgs, LogArgs, PinArgs, SearchArgs,
25 ServeArgs, SpoolCommand, SpoolStatusArgs, StatusArgs, UnpinArgs,
26};
27
28use std::path::PathBuf;
29
30use kindling_service::KindlingService;
31
32#[derive(Debug, thiserror::Error)]
35pub enum CliError {
36 #[error(transparent)]
37 Service(#[from] kindling_service::ServiceError),
38
39 #[error(transparent)]
40 Store(#[from] kindling_store::StoreError),
41
42 #[error(transparent)]
43 Client(#[from] kindling_client::ClientError),
44
45 #[error(transparent)]
46 Spool(#[from] kindling_client::SpoolError),
47
48 #[error(transparent)]
49 Server(#[from] kindling_server::ServerError),
50
51 #[error(transparent)]
52 Io(#[from] std::io::Error),
53
54 #[error(transparent)]
55 Json(#[from] serde_json::Error),
56
57 #[error("sqlite error: {0}")]
60 Sqlite(#[from] rusqlite::Error),
61
62 #[error("{0}")]
65 Invalid(String),
66}
67
68pub type CliResult = Result<(), CliError>;
70
71pub fn resolve_db_path(explicit: Option<&str>) -> Result<PathBuf, CliError> {
84 if let Some(path) = explicit {
85 return Ok(PathBuf::from(path));
86 }
87 let cwd = std::env::current_dir()?;
88 let project_root = cwd.to_string_lossy();
89 kindling_store::resolve_db_path(&project_root).ok_or_else(|| {
90 CliError::Invalid(
91 "could not resolve a database path: no --db, no KINDLING_DB_PATH, \
92 and no home directory (HOME/USERPROFILE) to derive a per-project path"
93 .to_string(),
94 )
95 })
96}
97
98pub fn open_service(explicit_db: Option<&str>) -> Result<(KindlingService, PathBuf), CliError> {
101 let path = resolve_db_path(explicit_db)?;
102 if let Some(parent) = path.parent() {
103 if !parent.as_os_str().is_empty() {
104 std::fs::create_dir_all(parent)?;
105 }
106 }
107 let service = KindlingService::open(&path)?;
108 Ok((service, path))
109}
110
111pub fn cli_main() -> i32 {
115 let cli = <Cli as clap::Parser>::parse();
116 run(cli)
117}
118
119pub fn run(cli: Cli) -> i32 {
125 let as_json = json_flag(&cli.command);
126 match dispatch(cli) {
127 Ok(()) => 0,
128 Err(err) => {
129 output::print_error(&err.to_string(), as_json);
130 1
131 }
132 }
133}
134
135fn json_flag(command: &Command) -> bool {
138 match command {
139 Command::Init(a) => a.json,
140 Command::Log(a) => a.common.json,
141 Command::Capsule(CapsuleCommand::Open(a)) => a.common.json,
142 Command::Capsule(CapsuleCommand::Close(a)) => a.common.json,
143 Command::Status(a) => a.common.json,
144 Command::Search(a) => a.common.json,
145 Command::List(a) => a.common.json,
146 Command::Pin(a) => a.common.json,
147 Command::Unpin(a) => a.common.json,
148 Command::Forget(a) => a.common.json,
149 Command::Export(a) => a.common.json,
150 Command::Import(a) => a.common.json,
151 Command::Serve(_) => false,
152 Command::Demo(a) => a.common.json,
153 Command::Browse(a) => a.common.json,
154 Command::Spool(SpoolCommand::Status(a)) => a.json,
155 }
156}
157
158fn dispatch(cli: Cli) -> CliResult {
159 let via_daemon = cli.via_daemon;
160 match cli.command {
161 Command::Init(args) => commands::init::run(args),
162 Command::Log(args) => commands::log::run(args, via_daemon),
163 Command::Capsule(CapsuleCommand::Open(args)) => {
164 commands::capsule::run_open(args, via_daemon)
165 }
166 Command::Capsule(CapsuleCommand::Close(args)) => {
167 commands::capsule::run_close(args, via_daemon)
168 }
169 Command::Status(args) => commands::status::run(args),
170 Command::Search(args) => commands::search::run(args, via_daemon),
171 Command::List(args) => commands::list::run(args),
172 Command::Pin(args) => commands::pin::run_pin(args, via_daemon),
173 Command::Unpin(args) => commands::pin::run_unpin(args, via_daemon),
174 Command::Forget(args) => commands::forget::run(args, via_daemon),
175 Command::Export(args) => commands::export::run_export(args, via_daemon),
176 Command::Import(args) => commands::export::run_import(args, via_daemon),
177 Command::Serve(args) => commands::serve::run(args),
178 Command::Demo(args) => commands::demo::run(args),
179 Command::Browse(args) => commands::browse::run(args),
180 Command::Spool(SpoolCommand::Status(args)) => commands::spool::run_status(args),
181 }
182}
183
184pub(crate) fn build_client() -> Result<kindling_client::Client, CliError> {
191 Ok(kindling_client::Client::new()?)
192}
193
194pub(crate) fn runtime() -> std::io::Result<tokio::runtime::Runtime> {
196 tokio::runtime::Builder::new_current_thread()
197 .enable_all()
198 .build()
199}