#![allow(clippy::print_stdout)]
use std::io::IsTerminal;
use clap::{CommandFactory, Parser, Subcommand};
mod commands;
mod paths;
#[derive(Parser, Debug)]
#[command(
name = "lethe",
version,
about = "Persistent memory store for LLM agents.",
long_about = "Persistent memory store for LLM agents — hybrid retrieval, RIF, optional enrichment.\n\
Run with no arguments to launch the interactive TUI."
)]
struct Cli {
#[arg(long, global = true)]
root: Option<String>,
#[command(subcommand)]
cmd: Option<Cmd>,
}
#[derive(Subcommand, Debug)]
enum Cmd {
Index {
dir: Option<String>,
#[arg(long)]
json_output: bool,
#[arg(long)]
no_register: bool,
},
Search {
query: String,
#[arg(long, default_value_t = 5)]
top_k: usize,
#[arg(long)]
json_output: bool,
#[arg(long)]
all: bool,
#[arg(long)]
projects: Option<String>,
},
Expand { chunk_id: String },
Status {
#[arg(long)]
json_output: bool,
},
Config {
#[arg(value_parser = ["get", "set"])]
action: String,
key: Option<String>,
value: Option<String>,
},
Reset {
#[arg(long)]
yes: bool,
},
Enrich {
dir: Option<String>,
#[arg(long, default_value = "claude-haiku-4-5")]
model: String,
#[arg(long, default_value_t = 5)]
concurrency: usize,
},
Projects {
#[command(subcommand)]
action: ProjectsCmd,
},
Migrate {
#[arg(long)]
all: bool,
#[arg(long)]
json_output: bool,
},
Tui,
}
#[derive(Subcommand, Debug)]
enum ProjectsCmd {
List {
#[arg(long)]
json_output: bool,
},
Add { path: Option<String> },
Remove { name: String },
Prune,
}
fn main() -> std::process::ExitCode {
if std::env::var_os("LETHE_LOG").is_some() {
let _ = tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_env("LETHE_LOG")
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
)
.with_writer(std::io::stderr)
.try_init();
}
let cli = Cli::parse();
let rc = match dispatch(cli) {
Ok(code) => code,
Err(e) => {
eprintln!("error: {e}");
1
}
};
std::process::ExitCode::from(u8::try_from(rc).unwrap_or(1))
}
fn dispatch(cli: Cli) -> anyhow::Result<i32> {
let root = cli.root.as_deref();
let cmd = match cli.cmd {
Some(c) => c,
None => {
if std::io::stdout().is_terminal() {
Cmd::Tui
} else {
Cli::command().print_help()?;
return Ok(2);
}
}
};
match cmd {
Cmd::Index {
dir,
json_output,
no_register,
} => commands::index::run(root, dir.as_deref(), json_output, no_register),
Cmd::Search {
query,
top_k,
json_output,
all,
projects,
} => {
if all || projects.is_some() {
commands::search::run_union(&query, top_k, json_output, projects.as_deref())
} else {
commands::search::run_local(root, &query, top_k, json_output)
}
}
Cmd::Expand { chunk_id } => commands::expand::run(root, &chunk_id),
Cmd::Status { json_output: _ } => commands::status::run(root),
Cmd::Config { action, key, value } => {
commands::config::run(root, &action, key.as_deref(), value.as_deref())
}
Cmd::Reset { yes } => commands::reset::run(root, yes),
Cmd::Enrich { .. } => {
eprintln!(
"lethe does not implement `enrich` in v1 — run the legacy Python `lethe enrich` instead."
);
Ok(2)
}
Cmd::Projects { action } => commands::projects::run(action),
Cmd::Migrate { all, json_output } => commands::migrate::run(root, all, json_output),
Cmd::Tui => commands::tui::run(),
}
}
pub(crate) use ProjectsCmd as ProjectsCmdExport;