memvid_cli/commands/
creation.rs

1//! Creation command handlers (create, open)
2
3use std::fs;
4use std::path::PathBuf;
5
6use anyhow::Result;
7use clap::{ArgAction, Args, ValueEnum};
8use memvid_core::{Memvid, Ticket};
9
10use crate::config::CliConfig;
11use crate::utils::{format_bytes, open_read_only_mem, parse_size, yes_no};
12
13/// Tier argument for CLI
14#[derive(Clone, Copy, Debug, ValueEnum)]
15pub enum TierArg {
16    Free,
17    Dev,
18    Enterprise,
19}
20
21impl From<TierArg> for memvid_core::Tier {
22    fn from(value: TierArg) -> Self {
23        match value {
24            TierArg::Free => memvid_core::Tier::Free,
25            TierArg::Dev => memvid_core::Tier::Dev,
26            TierArg::Enterprise => memvid_core::Tier::Enterprise,
27        }
28    }
29}
30
31/// Arguments for the `create` subcommand
32#[derive(Args)]
33pub struct CreateArgs {
34    /// Path to the memory file to create
35    #[arg(value_name = "FILE", value_parser = clap::value_parser!(PathBuf))]
36    pub file: PathBuf,
37    /// Tier to apply when creating the memory
38    #[arg(long, value_enum)]
39    pub tier: Option<TierArg>,
40    /// Set the maximum memory size (e.g. 6MB); defaults to the tier capacity (Free = 300MB)
41    #[arg(long = "size", alias = "capacity", value_name = "SIZE", value_parser = parse_size)]
42    pub size: Option<u64>,
43    /// Disable lexical index
44    #[arg(long = "no-lex", action = ArgAction::SetTrue)]
45    pub no_lex: bool,
46    /// Disable vector index
47    #[arg(long = "no-vector", aliases = ["no-vec"], action = ArgAction::SetTrue)]
48    pub no_vector: bool,
49}
50
51/// Arguments for the `open` subcommand
52#[derive(Args)]
53pub struct OpenArgs {
54    /// Path to the memory file to open
55    #[arg(value_name = "FILE", value_parser = clap::value_parser!(PathBuf))]
56    pub file: PathBuf,
57    /// Emit JSON instead of human-readable output
58    #[arg(long)]
59    pub json: bool,
60}
61
62/// Handler for `memvid create`
63pub fn handle_create(_config: &CliConfig, args: CreateArgs) -> Result<()> {
64    if let Some(parent) = args.file.parent() {
65        if !parent.exists() {
66            fs::create_dir_all(parent)?;
67        }
68    }
69
70    let tier = args
71        .tier
72        .map(memvid_core::Tier::from)
73        .unwrap_or(memvid_core::Tier::Free);
74    let capacity_bytes = args.size.unwrap_or_else(|| tier.capacity_bytes());
75    let lexical_enabled = !args.no_lex;
76    let vector_enabled = !args.no_vector;
77
78    let mut mem = Memvid::create(&args.file)?;
79    apply_capacity_override(&mut mem, capacity_bytes)?;
80    if lexical_enabled {
81        mem.enable_lex()?;
82    }
83
84    if vector_enabled {
85        mem.enable_vec()?;
86    }
87    mem.commit()?;
88
89    let stats = mem.stats()?;
90
91    // Format output with next steps
92    let filename = args.file.display();
93    println!("✓ Created memory at {}", filename);
94    println!(
95        "  Capacity: {} ({} bytes)",
96        format_bytes(stats.capacity_bytes),
97        stats.capacity_bytes
98    );
99    println!("  Size: {}", format_bytes(stats.size_bytes));
100    println!(
101        "  Indexes: {} | {}",
102        if lexical_enabled { "lexical" } else { "no-lex" },
103        if vector_enabled { "vector" } else { "no-vec" }
104    );
105    println!();
106    println!("Next steps:");
107    println!("  memvid put {} --input <file>     # Add content", filename);
108    println!("  memvid find {} --query <text>    # Search", filename);
109    println!("  memvid stats {}                  # View stats", filename);
110    println!();
111    println!("Documentation: https://memvid.com/docs");
112    Ok(())
113}
114
115/// Handler for `memvid open`
116pub fn handle_open(_config: &CliConfig, args: OpenArgs) -> Result<()> {
117    let mem = open_read_only_mem(&args.file)?;
118    let stats = mem.stats()?;
119    if args.json {
120        println!("{}", serde_json::to_string_pretty(&stats)?);
121    } else {
122        println!("Memory: {}", args.file.display());
123        println!("Frames: {}", stats.frame_count);
124        println!("Size: {} bytes", stats.size_bytes);
125        println!("Tier: {:?}", stats.tier);
126        println!(
127            "Indices → lex: {}, vec: {}, time: {}",
128            yes_no(stats.has_lex_index),
129            yes_no(stats.has_vec_index),
130            yes_no(stats.has_time_index)
131        );
132        if let Some(seq) = stats.seq_no {
133            println!("Ticket sequence: {seq}");
134        }
135    }
136    Ok(())
137}
138
139// Helper functions
140
141pub fn apply_capacity_override(mem: &mut Memvid, capacity_bytes: u64) -> Result<()> {
142    let current = mem.current_ticket();
143    if current.capacity_bytes == capacity_bytes {
144        return Ok(());
145    }
146
147    let seq = current.seq_no.saturating_add(1).max(1);
148    let mut ticket = Ticket::new(current.issuer.clone(), seq).capacity_bytes(capacity_bytes);
149    if current.expires_in_secs != 0 {
150        ticket = ticket.expires_in_secs(current.expires_in_secs);
151    }
152    apply_ticket_with_warning(mem, ticket)?;
153    Ok(())
154}
155
156pub fn apply_ticket_with_warning(mem: &mut Memvid, ticket: Ticket) -> Result<()> {
157    let before = mem.stats()?.capacity_bytes;
158    mem.apply_ticket(ticket)?;
159    let after = mem.stats()?.capacity_bytes;
160    if after < before {
161        println!(
162            "Warning: capacity reduced from {} to {}",
163            format_bytes(before),
164            format_bytes(after)
165        );
166    }
167    Ok(())
168}