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. 512MB); defaults to 1GB and is capped at 1GB.
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 mut capacity_bytes = args.size.unwrap_or_else(|| tier.capacity_bytes());
75    let max_capacity = memvid_core::Tier::Free.capacity_bytes();
76    if capacity_bytes > max_capacity {
77        eprintln!(
78            "⚠️  Requested capacity {} exceeds the current CLI limit ({}). Capping to {}.",
79            format_bytes(capacity_bytes),
80            format_bytes(max_capacity),
81            format_bytes(max_capacity)
82        );
83        eprintln!("   To go beyond 1GB, create a 1GB file and then apply a signed capacity ticket.");
84        capacity_bytes = max_capacity;
85    }
86    let lexical_enabled = !args.no_lex;
87    let vector_enabled = !args.no_vector;
88
89    let mut mem = Memvid::create(&args.file)?;
90    apply_capacity_override(&mut mem, capacity_bytes)?;
91    if lexical_enabled {
92        mem.enable_lex()?;
93    }
94
95    if vector_enabled {
96        mem.enable_vec()?;
97    }
98    mem.commit()?;
99
100    let stats = mem.stats()?;
101
102    // Format output with next steps
103    let filename = args.file.display();
104    println!("✓ Created memory at {}", filename);
105    println!(
106        "  Capacity: {} ({} bytes)",
107        format_bytes(stats.capacity_bytes),
108        stats.capacity_bytes
109    );
110    println!("  Size: {}", format_bytes(stats.size_bytes));
111    println!(
112        "  Indexes: {} | {}",
113        if lexical_enabled { "lexical" } else { "no-lex" },
114        if vector_enabled { "vector" } else { "no-vec" }
115    );
116    println!();
117    println!("Next steps:");
118    println!("  memvid put {} --input <file>     # Add content", filename);
119    println!("  memvid find {} --query <text>    # Search", filename);
120    println!("  memvid stats {}                  # View stats", filename);
121    println!();
122    println!("Documentation: https://docs.memvid.com/cli/tickets-and-capacity");
123    Ok(())
124}
125
126/// Handler for `memvid open`
127pub fn handle_open(_config: &CliConfig, args: OpenArgs) -> Result<()> {
128    let mem = open_read_only_mem(&args.file)?;
129    let stats = mem.stats()?;
130    if args.json {
131        println!("{}", serde_json::to_string_pretty(&stats)?);
132    } else {
133        println!("Memory: {}", args.file.display());
134        println!("Frames: {}", stats.frame_count);
135        println!("Size: {} bytes", stats.size_bytes);
136        println!("Tier: {:?}", stats.tier);
137        println!(
138            "Indices → lex: {}, vec: {}, time: {}",
139            yes_no(stats.has_lex_index),
140            yes_no(stats.has_vec_index),
141            yes_no(stats.has_time_index)
142        );
143        if let Some(seq) = stats.seq_no {
144            println!("Ticket sequence: {seq}");
145        }
146    }
147    Ok(())
148}
149
150// Helper functions
151
152pub fn apply_capacity_override(mem: &mut Memvid, capacity_bytes: u64) -> Result<()> {
153    let current = mem.current_ticket();
154    if current.capacity_bytes == capacity_bytes {
155        return Ok(());
156    }
157
158    let seq = current.seq_no.saturating_add(1).max(1);
159    let mut ticket = Ticket::new(current.issuer.clone(), seq).capacity_bytes(capacity_bytes);
160    if current.expires_in_secs != 0 {
161        ticket = ticket.expires_in_secs(current.expires_in_secs);
162    }
163    apply_ticket_with_warning(mem, ticket)?;
164    Ok(())
165}
166
167pub fn apply_ticket_with_warning(mem: &mut Memvid, ticket: Ticket) -> Result<()> {
168    let before = mem.stats()?.capacity_bytes;
169    mem.apply_ticket(ticket)?;
170    let after = mem.stats()?.capacity_bytes;
171    if after < before {
172        println!(
173            "Warning: capacity reduced from {} to {}",
174            format_bytes(before),
175            format_bytes(after)
176        );
177    }
178    Ok(())
179}