memvid_cli/commands/
plan.rs1use anyhow::Result;
6use clap::{Args, Subcommand};
7use serde_json::json;
8
9use crate::config::CliConfig;
10use crate::org_ticket_cache;
11use crate::utils::{format_bytes, FREE_TIER_MAX_FILE_SIZE};
12
13#[derive(Subcommand)]
15pub enum PlanCommand {
16 Show(PlanShowArgs),
18 Sync(PlanSyncArgs),
20 Clear(PlanClearArgs),
22}
23
24#[derive(Args)]
26pub struct PlanArgs {
27 #[command(subcommand)]
28 pub command: PlanCommand,
29}
30
31#[derive(Args)]
33pub struct PlanShowArgs {
34 #[arg(long)]
36 pub json: bool,
37}
38
39#[derive(Args)]
41pub struct PlanSyncArgs {
42 #[arg(long)]
44 pub json: bool,
45}
46
47#[derive(Args)]
49pub struct PlanClearArgs {
50 #[arg(long)]
52 pub json: bool,
53}
54
55pub fn handle_plan(config: &CliConfig, args: PlanArgs) -> Result<()> {
56 match args.command {
57 PlanCommand::Show(show) => handle_plan_show(config, show),
58 PlanCommand::Sync(sync) => handle_plan_sync(config, sync),
59 PlanCommand::Clear(clear) => handle_plan_clear(config, clear),
60 }
61}
62
63fn handle_plan_show(config: &CliConfig, args: PlanShowArgs) -> Result<()> {
64 if config.api_key.is_none() {
65 if args.json {
66 let output = json!({
67 "plan": "free",
68 "capacity_bytes": FREE_TIER_MAX_FILE_SIZE,
69 "capacity": format_bytes(FREE_TIER_MAX_FILE_SIZE),
70 "features": ["core", "temporal_track", "clip", "whisper", "temporal_enrich"],
71 "authenticated": false,
72 });
73 println!("{}", serde_json::to_string_pretty(&output)?);
74 } else {
75 println!("Plan: Free (not authenticated)");
76 println!("Capacity: {}", format_bytes(FREE_TIER_MAX_FILE_SIZE));
77 println!();
78 println!("To unlock more capacity, set your API key:");
79 println!(" export MEMVID_API_KEY=your_api_key");
80 println!();
81 println!("Get your API key at: https://memvid.com/dashboard/api-keys");
82 }
83 return Ok(());
84 }
85
86 match org_ticket_cache::get_or_refresh(config) {
88 Ok(cached) => {
89 if args.json {
90 let output = json!({
91 "plan": cached.plan_id,
92 "plan_name": cached.plan_name,
93 "capacity_bytes": cached.capacity_bytes(),
94 "capacity": format_bytes(cached.capacity_bytes()),
95 "features": cached.ticket.features,
96 "org_id": cached.org_id,
97 "org_name": cached.org_name,
98 "total_storage_bytes": cached.total_storage_bytes,
99 "total_storage": format_bytes(cached.total_storage_bytes),
100 "subscription_status": cached.subscription_status,
101 "expires_at": cached.ticket.expires_at,
102 "expires_in_secs": cached.ticket.expires_in_secs(),
103 "authenticated": true,
104 });
105 println!("{}", serde_json::to_string_pretty(&output)?);
106 } else {
107 println!("Plan: {}", cached.plan_name);
108 println!("Organisation: {}", cached.org_name);
109 println!("Subscription: {}", cached.subscription_status);
110 println!();
111 println!("Capacity: {}", format_bytes(cached.capacity_bytes()));
112 println!("Storage Used: {}", format_bytes(cached.total_storage_bytes));
113 println!();
114 println!("Features: {}", cached.ticket.features.join(", "));
115 println!();
116 let expires_in = cached.ticket.expires_in_secs();
117 if expires_in > 0 {
118 let hours = expires_in / 3600;
119 let mins = (expires_in % 3600) / 60;
120 println!("Ticket expires in: {}h {}m", hours, mins);
121 } else {
122 println!("Ticket expired (run `memvid plan sync` to refresh)");
123 }
124 }
125 }
126 Err(err) => {
127 if args.json {
128 let output = json!({
129 "error": err.to_string(),
130 "plan": "unknown",
131 "authenticated": true,
132 });
133 println!("{}", serde_json::to_string_pretty(&output)?);
134 } else {
135 println!("Failed to get plan information: {}", err);
136 println!();
137 println!("Try syncing your plan ticket:");
138 println!(" memvid plan sync");
139 }
140 }
141 }
142
143 Ok(())
144}
145
146fn handle_plan_sync(config: &CliConfig, args: PlanSyncArgs) -> Result<()> {
147 if config.api_key.is_none() {
148 anyhow::bail!(
149 "API key required. Set it with:\n export MEMVID_API_KEY=your_api_key\n\n\
150 Get your API key at: https://memvid.com/dashboard/api-keys"
151 );
152 }
153
154 let cached = org_ticket_cache::refresh(config)?;
155
156 if args.json {
157 let output = json!({
158 "success": true,
159 "plan": cached.plan_id,
160 "plan_name": cached.plan_name,
161 "capacity_bytes": cached.capacity_bytes(),
162 "capacity": format_bytes(cached.capacity_bytes()),
163 "features": cached.ticket.features,
164 "org_id": cached.org_id,
165 "org_name": cached.org_name,
166 "subscription_status": cached.subscription_status,
167 "expires_at": cached.ticket.expires_at,
168 });
169 println!("{}", serde_json::to_string_pretty(&output)?);
170 } else {
171 println!("✓ Plan synced successfully");
172 println!();
173 println!("Plan: {}", cached.plan_name);
174 println!("Organisation: {}", cached.org_name);
175 println!("Capacity: {}", format_bytes(cached.capacity_bytes()));
176 println!("Subscription: {}", cached.subscription_status);
177 println!();
178 let expires_in = cached.ticket.expires_in_secs();
179 let hours = expires_in / 3600;
180 let mins = (expires_in % 3600) / 60;
181 println!("Ticket valid for: {}h {}m", hours, mins);
182 }
183
184 Ok(())
185}
186
187fn handle_plan_clear(config: &CliConfig, args: PlanClearArgs) -> Result<()> {
188 org_ticket_cache::clear(config)?;
189
190 if args.json {
191 let output = json!({
192 "success": true,
193 "message": "Plan ticket cache cleared",
194 });
195 println!("{}", serde_json::to_string_pretty(&output)?);
196 } else {
197 println!("✓ Plan ticket cache cleared");
198 }
199
200 Ok(())
201}