mod mcp;
mod server;
use rustmem::{config, graph, memory, store};
use clap::{Parser, Subcommand};
use tracing::info;
#[derive(Parser)]
#[command(name = "rustmem", about = "Long-term memory for AI agents", version)]
struct Cli {
#[arg(short, long, global = true)]
config: Option<String>,
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand)]
enum Command {
Server,
Add {
#[arg(short, long)]
user: String,
text: String,
},
Search {
#[arg(short, long)]
user: String,
query: String,
#[arg(short, long, default_value_t = 10)]
limit: usize,
},
List {
#[arg(short, long)]
user: String,
},
Graph {
#[arg(short, long)]
user: String,
},
Mcp,
Reset {
#[arg(short, long)]
user: String,
},
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let cli = Cli::parse();
let is_mcp = matches!(cli.command, Command::Mcp);
if is_mcp {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::from_default_env()
.add_directive("rustmem=info".parse()?),
)
.with_writer(std::io::stderr)
.with_ansi(false)
.init();
} else {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::from_default_env()
.add_directive("rustmem=info".parse()?),
)
.init();
}
let cfg = config::AppConfig::load(cli.config.as_deref())?;
match cli.command {
Command::Mcp => {
let mem = memory::MemoryManager::new(&cfg).await?;
mcp::run(mem).await
}
Command::Server => {
info!("Starting rustmem server on {}", cfg.server.listen_addr());
let mem = memory::MemoryManager::new(&cfg).await?;
server::run(cfg, mem).await
}
Command::Add { user, text } => {
let mem = memory::MemoryManager::new(&cfg).await?;
let results = mem.add(&user, &text).await?;
for r in &results {
println!("[{}] {} → {}", r.event, r.id, r.text);
}
if results.is_empty() {
println!("No facts extracted.");
}
Ok(())
}
Command::Search { user, query, limit } => {
let mem = memory::MemoryManager::new(&cfg).await?;
let results = mem.search(&user, &query, limit).await?;
if results.is_empty() {
println!("No memories found.");
}
for r in &results {
println!("[{:.3}] {} — {}", r.score, r.id, r.text);
}
Ok(())
}
Command::List { user } => {
let mem = memory::MemoryManager::new(&cfg).await?;
let records = mem.get_all(&user).await?;
if records.is_empty() {
println!("No memories for user '{user}'.");
}
for r in &records {
println!("{} | {} | {}", r.id, r.updated_at, r.text);
}
Ok(())
}
Command::Graph { user } => {
let mem = memory::MemoryManager::new(&cfg).await?;
let relations = mem.get_graph(&user).await?;
if relations.is_empty() {
println!("No graph relations for user '{user}'.");
}
for r in &relations {
println!(
"{} --[{}]--> {} (mentions: {})",
r.source, r.relation, r.destination, r.mentions
);
}
Ok(())
}
Command::Reset { user } => {
let mem = memory::MemoryManager::new(&cfg).await?;
let count = mem.reset(&user).await?;
println!("Deleted {count} memories for user '{user}'.");
Ok(())
}
}
}