memvid_cli/commands/
creation.rs1use 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#[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#[derive(Args)]
33pub struct CreateArgs {
34 #[arg(value_name = "FILE", value_parser = clap::value_parser!(PathBuf))]
36 pub file: PathBuf,
37 #[arg(long, value_enum)]
39 pub tier: Option<TierArg>,
40 #[arg(long = "size", alias = "capacity", value_name = "SIZE", value_parser = parse_size)]
42 pub size: Option<u64>,
43 #[arg(long = "no-lex", action = ArgAction::SetTrue)]
45 pub no_lex: bool,
46 #[arg(long = "no-vector", aliases = ["no-vec"], action = ArgAction::SetTrue)]
48 pub no_vector: bool,
49}
50
51#[derive(Args)]
53pub struct OpenArgs {
54 #[arg(value_name = "FILE", value_parser = clap::value_parser!(PathBuf))]
56 pub file: PathBuf,
57 #[arg(long)]
59 pub json: bool,
60}
61
62pub 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 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
126pub 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
150pub 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}