use std::path::PathBuf;
use clap::{Parser, Subcommand, ValueEnum};
use plugmem_host::MaintenanceMode;
#[derive(Parser)]
#[command(
name = "plugmem-cli",
version,
about = "Temporal memory for LLM agents — remember, recall, revise, forget over one file.",
long_about = LONG_ABOUT,
after_help = AFTER_HELP,
disable_help_subcommand = true,
)]
pub(crate) struct Cli {
#[arg(long, global = true, value_name = "PATH|NAME")]
pub(crate) db: Option<String>,
#[arg(long, global = true, value_name = "DIR")]
pub(crate) workspace: Option<PathBuf>,
#[arg(long, global = true, value_name = "PATH")]
pub(crate) config: Option<PathBuf>,
#[arg(long, global = true)]
pub(crate) json: bool,
#[command(subcommand)]
pub(crate) command: Command,
}
const LONG_ABOUT: &str = "\
A local memory an agent talks to in four verbs — remember / recall / revise / forget — \
plus link / unlink / show / stats / maintain / checkpoint / export / import, integrity: verify / \
scrub / recover, and an interactive `repl` (one open handle, host speed). Recall fuses lexical \
(BM25), vector, entity-graph and temporal evidence into one \
ranked, token-budgeted block. One database is a single snapshot file plus a journal; point \
--db at it (default: the platform data path, or $PLUGMEM_DB). Human output by default, --json for \
tooling. Exit code: 0 ok, 1 not found / database locked, 2 usage / runtime error / \
corruption.";
const AFTER_HELP: &str = "\
FOR AI AGENTS: you'll get markedly better results with the matching `plugmem` skill loaded \
— it carries the workflow, the remember/recall loop and the examples this binary expects. \
Check that its version matches `plugmem-cli --version`; the skill is attached to every \
release at https://github.com/m62624/plugmem/releases";
#[derive(Subcommand)]
pub(crate) enum Command {
Help {
#[command(subcommand)]
topic: HelpTopic,
},
Remember {
text: String,
#[arg(long)]
entity: Option<String>,
#[arg(long = "tag", value_name = "TAG")]
tags: Vec<String>,
#[arg(long = "link", value_name = "REL:ENTITY")]
links: Vec<String>,
#[arg(long = "meta", value_name = "KEY=VALUE")]
meta: Vec<String>,
#[arg(long = "valid-from", value_name = "TS")]
valid_from: Option<u64>,
},
Recall {
query: Option<String>,
#[arg(long = "tag", value_name = "TAG")]
tags: Vec<String>,
#[arg(long = "entity", value_name = "E")]
entities: Vec<String>,
#[arg(long = "as-of", value_name = "TS")]
as_of: Option<u64>,
#[arg(long, num_args = 2, value_names = ["FROM", "TO"])]
range: Option<Vec<u64>>,
#[arg(short = 'k', long, default_value_t = 0)]
k: usize,
#[arg(long)]
closed: bool,
},
Revise {
id: u32,
text: String,
#[arg(long)]
entity: Option<String>,
#[arg(long = "tag", value_name = "TAG")]
tags: Vec<String>,
#[arg(long = "link", value_name = "REL:ENTITY")]
links: Vec<String>,
#[arg(long = "meta", value_name = "KEY=VALUE")]
meta: Vec<String>,
#[arg(long = "valid-from", value_name = "TS")]
valid_from: Option<u64>,
},
Forget {
id: u32,
},
Link {
src: String,
rel: String,
dst: String,
},
Unlink {
src: String,
rel: String,
dst: String,
},
Show {
id: u32,
},
Stats,
Maintain {
#[arg(long, value_enum, default_value_t = MaintainMode::Auto)]
mode: MaintainMode,
},
Checkpoint,
Verify,
Scrub,
Recover {
dst: PathBuf,
},
Export,
Import {
file: PathBuf,
#[arg(long, value_name = "N")]
batch: Option<usize>,
},
Repl {
#[arg(long)]
read_only: bool,
},
Workspace {
#[command(subcommand)]
command: WorkspaceCommand,
},
}
#[derive(Subcommand)]
pub(crate) enum WorkspaceCommand {
List,
Find {
query: String,
#[arg(long, value_name = "N")]
k: Option<usize>,
},
Describe {
name: String,
text: String,
#[arg(long = "tag", value_name = "TAG")]
tags: Vec<String>,
#[arg(long)]
owner: Option<String>,
},
Archive {
name: String,
},
Reindex,
Verify,
Use {
name: String,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, ValueEnum)]
pub(crate) enum MaintainMode {
Auto,
Compact,
ReindexText,
OptimizeVectors,
Full,
}
impl From<MaintainMode> for MaintenanceMode {
fn from(mode: MaintainMode) -> Self {
match mode {
MaintainMode::Auto => Self::Auto,
MaintainMode::Compact => Self::Compact,
MaintainMode::ReindexText => Self::ReindexText,
MaintainMode::OptimizeVectors => Self::OptimizeVectors,
MaintainMode::Full => Self::Full,
}
}
}
#[derive(Subcommand)]
pub(crate) enum HelpTopic {
Settings,
}
#[cfg(test)]
mod tests {
use super::*;
use clap::Parser;
#[test]
fn settings_help_is_an_explicit_topic() {
let cli = Cli::try_parse_from(["plugmem-cli", "help", "settings"]).unwrap();
assert!(matches!(
cli.command,
Command::Help {
topic: HelpTopic::Settings
}
));
}
}