use clap::{Parser, Subcommand, ValueEnum};
use std::path::PathBuf;
#[derive(Parser)]
#[command(
name = "edisonprompt",
version = env!("CARGO_PKG_VERSION"),
author = "EdisonPrompt Contributors",
about = "Lightning-fast AI prompt management CLI",
long_about = "A professional CLI tool for managing AI prompts with template variables, full-text search, and local-first architecture."
)]
pub struct Cli {
#[command(subcommand)]
pub command: Commands,
#[arg(short, long, global = true)]
pub verbose: bool,
#[arg(short, long, global = true, value_name = "FILE")]
pub config: Option<PathBuf>,
#[arg(long, global = true)]
pub no_color: bool,
}
#[derive(Subcommand)]
pub enum Commands {
Add {
#[arg(value_parser = validate_prompt_name)]
name: String,
#[arg(short, long)]
interactive: bool,
#[arg(short, long, value_delimiter = ',')]
tags: Vec<String>,
#[arg(short, long)]
force: bool,
},
Get {
name: String,
#[arg(long = "var", value_parser = parse_variable)]
variables: Vec<(String, String)>,
#[arg(short, long)]
copy: bool,
#[arg(short, long)]
raw: bool,
},
List {
#[arg(short, long)]
tag: Option<String>,
#[arg(short, long, value_enum, default_value = "table")]
format: OutputFormat,
#[arg(short, long)]
limit: Option<usize>,
#[arg(short, long, value_enum, default_value = "name")]
sort: SortField,
#[arg(long)]
names_only: bool,
},
Search {
query: String,
#[arg(long)]
highlight: bool,
#[arg(short, long, default_value = "50")]
limit: usize,
#[arg(short, long, value_enum, default_value = "table")]
format: OutputFormat,
},
Edit {
name: String,
#[arg(short, long)]
yes: bool,
},
Delete {
name: String,
#[arg(short, long)]
yes: bool,
#[arg(short, long)]
force: bool,
},
Export {
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(short, long)]
tag: Option<String>,
#[arg(short, long)]
pretty: bool,
},
Import {
#[arg(short, long)]
input: Option<PathBuf>,
#[arg(short, long, value_enum, default_value = "skip")]
merge: MergeStrategy,
#[arg(long)]
dry_run: bool,
},
Completions {
#[arg(value_enum)]
shell: Shell,
},
}
#[derive(ValueEnum, Clone)]
pub enum OutputFormat {
Table,
Json,
Plain,
}
#[derive(ValueEnum, Clone)]
pub enum SortField {
Name,
Created,
Updated,
}
#[derive(ValueEnum, Clone)]
pub enum MergeStrategy {
Skip,
Overwrite,
Rename,
}
#[derive(ValueEnum, Clone)]
pub enum Shell {
Bash,
Zsh,
Fish,
PowerShell,
}
fn validate_prompt_name(name: &str) -> Result<String, String> {
if name.is_empty() {
return Err("Name cannot be empty".to_string());
}
if name.len() > 100 {
return Err("Name too long (max 100 characters)".to_string());
}
if !regex::Regex::new(r"^[a-zA-Z0-9_-]+$").unwrap().is_match(name) {
return Err("Name can only contain letters, numbers, hyphens, and underscores".to_string());
}
Ok(name.to_string())
}
fn parse_variable(s: &str) -> Result<(String, String), String> {
let parts: Vec<&str> = s.splitn(2, '=').collect();
if parts.len() != 2 {
return Err("Variable must be in key=value format".to_string());
}
Ok((parts[0].to_string(), parts[1].to_string()))
}