#![cfg_attr(check_cfg, allow(unexpected_cfgs))]
#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used))]
use std::process::ExitCode;
use anyhow::Result;
use clap::{ArgAction, Parser, Subcommand};
use memvid_cli::analytics::{force_flush_sync, init_analytics, track_command_with_tier};
use memvid_cli::commands::{
handle_api_fetch, handle_ask, handle_audit, handle_binding, handle_config, handle_correct,
handle_create, handle_debug_segment, handle_delete, handle_doctor, handle_enrich,
handle_export, handle_facts, handle_find, handle_follow, handle_memories, handle_models,
handle_nudge, handle_open, handle_plan, handle_process_queue, handle_put, handle_schema,
handle_sketch, handle_state, handle_stats, handle_status, handle_tables, handle_tickets,
handle_timeline, handle_update, handle_vec_search, handle_verify, handle_verify_single_file,
handle_version, handle_view, handle_who, ApiFetchArgs, AskArgs, AuditArgs, BindingArgs,
ConfigArgs, CorrectArgs, CreateArgs, DebugSegmentArgs, DeleteArgs, DoctorArgs, EnrichArgs,
ExportArgs, FactsArgs, FindArgs, FollowArgs, MemoriesArgs, ModelsArgs, NudgeArgs, OpenArgs,
PlanArgs, ProcessQueueArgs, PutArgs, SchemaArgs, SketchArgs, StateArgs, StatsArgs, StatusArgs,
TablesArgs, TicketsArgs, TimelineArgs, UpdateArgs, VecSearchArgs, VerifyArgs,
VerifySingleFileArgs, ViewArgs, WhoArgs,
};
#[cfg(feature = "encryption")]
use memvid_cli::commands::{handle_lock, handle_unlock, LockArgs, UnlockArgs};
#[cfg(feature = "parallel_segments")]
use memvid_cli::commands::{handle_put_many, PutManyArgs};
#[cfg(feature = "replay")]
use memvid_cli::commands::{handle_session, SessionArgs};
#[cfg(feature = "temporal_track")]
use memvid_cli::commands::{handle_when, WhenArgs};
use memvid_cli::config::{init_tracing, CliConfig, EmbeddingModelChoice};
use memvid_cli::org_ticket_cache;
#[cfg(feature = "parallel_segments")]
#[derive(Parser)]
#[command(name = "memvid", author, version, about = "Memvid single-file memory CLI", long_about = None)]
struct Cli {
#[arg(short, long, action = ArgAction::Count)]
verbose: u8,
#[arg(
short = 'm',
long,
global = true,
value_name = "MODEL",
alias = "model"
)]
embedding_model: Option<EmbeddingModelChoice>,
#[cfg(feature = "parallel_segments")]
#[arg(long, global = true, action = ArgAction::SetTrue, conflicts_with = "global_no_parallel_segments")]
parallel_segments: bool,
#[cfg(feature = "parallel_segments")]
#[arg(
long = "global-no-parallel-segments",
global = true,
action = ArgAction::SetTrue,
conflicts_with = "parallel_segments"
)]
global_no_parallel_segments: bool,
#[command(subcommand)]
command: Option<Commands>,
}
#[derive(Subcommand)]
enum Commands {
Create(CreateArgs),
Open(OpenArgs),
Put(PutArgs),
Correct(CorrectArgs),
#[cfg(feature = "parallel_segments")]
PutMany(PutManyArgs),
ApiFetch(ApiFetchArgs),
View(ViewArgs),
Update(UpdateArgs),
Delete(DeleteArgs),
Timeline(TimelineArgs),
Ask(AskArgs),
Audit(AuditArgs),
Find(FindArgs),
VecSearch(VecSearchArgs),
DebugSegment(DebugSegmentArgs),
#[cfg(feature = "temporal_track")]
When(WhenArgs),
Stats(StatsArgs),
Verify(VerifyArgs),
Doctor(DoctorArgs),
ProcessQueue(ProcessQueueArgs),
VerifySingleFile(VerifySingleFileArgs),
Tables(TablesArgs),
Tickets(TicketsArgs),
Plan(PlanArgs),
Binding(BindingArgs),
Config(ConfigArgs),
Status(StatusArgs),
Who(WhoArgs),
Nudge(NudgeArgs),
Enrich(EnrichArgs),
Memories(MemoriesArgs),
State(StateArgs),
Facts(FactsArgs),
Export(ExportArgs),
Schema(SchemaArgs),
Models(ModelsArgs),
Follow(FollowArgs),
Sketch(SketchArgs),
#[cfg(feature = "replay")]
Session(SessionArgs),
#[cfg(feature = "encryption")]
Lock(LockArgs),
#[cfg(feature = "encryption")]
Unlock(UnlockArgs),
Version,
}
fn main() -> ExitCode {
match try_main() {
Ok(()) => ExitCode::SUCCESS,
Err(err) => {
let (code, message) = memvid_cli::error::render_error(&err);
eprintln!("{message}");
ExitCode::from(code as u8)
}
}
}
fn try_main() -> Result<()> {
let cli = Cli::parse();
init_tracing(cli.verbose)?;
#[cfg(feature = "parallel_segments")]
apply_global_parallel_flags(&cli);
let mut config = CliConfig::load()?;
init_analytics();
if let Some(model) = cli.embedding_model {
config = config.with_embedding_model(model);
}
let result = if let Some(command) = cli.command {
dispatch(&config, command)
} else {
print_command_overview();
Ok(())
};
let _ = force_flush_sync();
result
}
fn print_command_overview() {
println!("Memvid CLI – common commands:");
println!(" create <file> Create an empty .mv2 memory");
println!(" put <file> Append documents or folders to a memory");
println!(" find <file> --query Run lexical + vector search");
println!(" view <file> --frame Inspect the contents/metadata of a frame");
println!(" ask <file> --question Ask retrieval-augmented questions");
println!(" timeline <file> Browse the chronological frame list");
println!(" stats <file> Show storage and index utilization");
println!(" doctor <file> Repair or vacuum a memory");
println!(" plan show Show your current plan and capacity");
println!(" plan sync Sync your plan ticket from the dashboard");
println!(" config set <key> <v> Set a configuration value (e.g., api_key)");
println!(" config list List all configuration values");
#[cfg(feature = "encryption")]
{
println!(" lock <file> Encrypt a .mv2 into a .mv2e capsule");
println!(" unlock <file> Decrypt a .mv2e capsule back to .mv2");
}
println!();
println!("Run `memvid <command> --help` for detailed flags.");
}
#[cfg(feature = "parallel_segments")]
fn apply_global_parallel_flags(cli: &Cli) {
if cli.parallel_segments {
unsafe {
std::env::set_var("MEMVID_PARALLEL_SEGMENTS", "1");
}
} else if cli.global_no_parallel_segments {
unsafe {
std::env::set_var("MEMVID_PARALLEL_SEGMENTS", "0");
}
}
}
fn dispatch(config: &CliConfig, command: Commands) -> Result<()> {
let (cmd_name, file_path, is_create, is_open) = get_command_info(&command);
let result = match command {
Commands::Create(args) => handle_create(config, args),
Commands::Open(args) => handle_open(config, args),
Commands::Put(args) => handle_put(config, args),
Commands::Correct(args) => handle_correct(config, args),
#[cfg(feature = "parallel_segments")]
Commands::PutMany(args) => handle_put_many(config, args),
Commands::ApiFetch(args) => handle_api_fetch(config, args),
Commands::View(args) => handle_view(args),
Commands::Update(args) => handle_update(config, args),
Commands::Delete(args) => handle_delete(config, args),
Commands::Timeline(args) => handle_timeline(config, args),
Commands::Ask(args) => handle_ask(config, args),
Commands::Audit(args) => handle_audit(config, args),
Commands::Find(args) => handle_find(config, args),
Commands::VecSearch(args) => handle_vec_search(config, args),
Commands::DebugSegment(args) => handle_debug_segment(args),
#[cfg(feature = "temporal_track")]
Commands::When(args) => handle_when(config, args),
Commands::Stats(args) => handle_stats(config, args),
Commands::Verify(args) => handle_verify(config, args),
Commands::Doctor(args) => handle_doctor(config, args),
Commands::ProcessQueue(args) => handle_process_queue(config, args),
Commands::VerifySingleFile(args) => handle_verify_single_file(config, args),
Commands::Tables(args) => handle_tables(config, args),
Commands::Tickets(cmd) => handle_tickets(config, cmd),
Commands::Plan(args) => handle_plan(config, args),
Commands::Binding(args) => handle_binding(args),
Commands::Config(args) => handle_config(args),
Commands::Status(args) => handle_status(args),
Commands::Who(args) => handle_who(args),
Commands::Nudge(args) => handle_nudge(args),
Commands::Enrich(args) => handle_enrich(config, args),
Commands::Memories(args) => handle_memories(config, args),
Commands::State(args) => handle_state(config, args),
Commands::Facts(args) => handle_facts(config, args),
Commands::Export(args) => handle_export(config, args),
Commands::Schema(args) => handle_schema(config, args),
Commands::Models(args) => handle_models(config, args),
Commands::Follow(args) => handle_follow(config, args),
Commands::Sketch(args) => handle_sketch(args),
#[cfg(feature = "replay")]
Commands::Session(args) => handle_session(args),
#[cfg(feature = "encryption")]
Commands::Lock(args) => handle_lock(args),
#[cfg(feature = "encryption")]
Commands::Unlock(args) => handle_unlock(args),
Commands::Version => handle_version(),
};
let user_tier = org_ticket_cache::get_optional(config)
.map(|t| t.plan_name)
.unwrap_or_else(|| "free".to_string());
track_command_with_tier(
file_path.as_deref(),
cmd_name,
result.is_ok(),
is_create && result.is_ok(),
is_open && result.is_ok(),
&user_tier,
);
result
}
fn path_to_string(path: &std::path::Path) -> String {
path.display().to_string()
}
fn get_command_info(command: &Commands) -> (&'static str, Option<String>, bool, bool) {
match command {
Commands::Create(args) => ("create", Some(path_to_string(&args.file)), true, false),
Commands::Open(args) => ("open", Some(path_to_string(&args.file)), false, true),
Commands::Put(args) => ("put", Some(path_to_string(&args.file)), false, false),
Commands::Correct(args) => ("correct", Some(path_to_string(&args.file)), false, false),
#[cfg(feature = "parallel_segments")]
Commands::PutMany(args) => ("put_many", Some(path_to_string(&args.file)), false, false),
Commands::ApiFetch(args) => ("api_fetch", Some(path_to_string(&args.file)), false, false),
Commands::View(args) => ("view", Some(path_to_string(&args.file)), false, true),
Commands::Update(args) => ("update", Some(path_to_string(&args.file)), false, false),
Commands::Delete(args) => ("delete", Some(path_to_string(&args.file)), false, false),
Commands::Timeline(args) => ("timeline", Some(path_to_string(&args.file)), false, true),
Commands::Ask(args) => ("ask", args.targets.first().cloned(), false, true),
Commands::Audit(args) => ("audit", Some(path_to_string(&args.file)), false, true),
Commands::Find(args) => ("find", Some(path_to_string(&args.file)), false, true),
Commands::VecSearch(args) => ("vec_search", Some(path_to_string(&args.file)), false, true),
Commands::DebugSegment(args) => (
"debug_segment",
Some(path_to_string(&args.file)),
false,
false,
),
#[cfg(feature = "temporal_track")]
Commands::When(args) => ("when", Some(path_to_string(&args.file)), false, true),
Commands::Stats(args) => ("stats", Some(path_to_string(&args.file)), false, true),
Commands::Verify(args) => ("verify", Some(path_to_string(&args.file)), false, true),
Commands::Doctor(args) => ("doctor", Some(path_to_string(&args.file)), false, false),
Commands::ProcessQueue(args) => (
"process_queue",
Some(path_to_string(&args.file)),
false,
false,
),
Commands::VerifySingleFile(args) => (
"verify_single_file",
Some(path_to_string(&args.file)),
false,
true,
),
Commands::Tables(_) => ("tables", None, false, false),
Commands::Tickets(_) => ("tickets", None, false, false),
Commands::Plan(_) => ("plan", None, false, false),
Commands::Binding(args) => ("binding", Some(path_to_string(&args.file)), false, true),
Commands::Config(_) => ("config", None, false, false),
Commands::Status(_) => ("status", None, false, false),
Commands::Who(args) => ("who", Some(path_to_string(&args.file)), false, true),
Commands::Nudge(args) => ("nudge", Some(path_to_string(&args.file)), false, false),
Commands::Enrich(args) => ("enrich", Some(path_to_string(&args.file)), false, false),
Commands::Memories(args) => ("memories", Some(path_to_string(&args.file)), false, true),
Commands::State(args) => ("state", Some(path_to_string(&args.file)), false, true),
Commands::Facts(args) => ("facts", Some(path_to_string(&args.file)), false, true),
Commands::Export(args) => ("export", Some(path_to_string(&args.file)), false, true),
Commands::Schema(_) => ("schema", None, false, false),
Commands::Models(_) => ("models", None, false, false),
Commands::Follow(_) => ("follow", None, false, false),
Commands::Sketch(_) => ("sketch", None, false, false),
#[cfg(feature = "replay")]
Commands::Session(_) => ("session", None, false, false),
#[cfg(feature = "encryption")]
Commands::Lock(args) => ("lock", Some(path_to_string(&args.file)), false, true),
#[cfg(feature = "encryption")]
Commands::Unlock(args) => ("unlock", Some(path_to_string(&args.file)), false, true),
Commands::Version => ("version", None, false, false),
}
}