use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use owo_colors::OwoColorize;
use std::path::PathBuf;
mod commands;
mod config;
mod cursor;
#[derive(Parser)]
#[command(name = "cursor-helper")]
#[command(about = "CLI helper for Cursor IDE operations", long_about = None)]
#[command(version)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Rename {
old_path: String,
new_path: String,
#[arg(short = 'n', long)]
dry_run: bool,
#[arg(short, long)]
copy: bool,
#[arg(long)]
force_index: bool,
},
List {
#[arg(long)]
with_id: bool,
#[arg(long, short, default_value = "modified")]
sort: String,
#[arg(long, short)]
reverse: bool,
#[arg(long, short)]
filter: Option<String>,
#[arg(short = 'n', long)]
limit: Option<usize>,
},
Stats {
project_path: Option<String>,
},
ExportChat {
project_path: Option<String>,
#[arg(long, conflicts_with = "project_path")]
workspace_id: Option<String>,
#[arg(long, short, default_value = "md")]
format: String,
#[arg(long, short)]
output: Option<String>,
#[arg(long)]
with_thinking: bool,
#[arg(long)]
with_tools: bool,
#[arg(long)]
with_stats: bool,
#[arg(short, long)]
verbose: bool,
#[arg(long)]
include_archived: bool,
#[arg(long)]
split: bool,
#[arg(long)]
exclude_blank: bool,
},
Clean {
#[arg(short = 'n', long)]
dry_run: bool,
#[arg(short, long)]
yes: bool,
},
Backup {
project_path: String,
backup_file: String,
},
Restore {
backup_file: String,
new_path: String,
},
Clone {
old_path: String,
new_path: String,
#[arg(short = 'n', long)]
dry_run: bool,
},
}
fn main() -> Result<()> {
let cli = Cli::parse();
match cli.command {
Commands::Rename {
old_path,
new_path,
dry_run,
copy,
force_index,
} => {
if dry_run {
println!("{}", "(DRY-RUN MODE - no changes will be made)".blue());
}
commands::rename::execute(&old_path, &new_path, dry_run, copy, force_index)?;
}
Commands::List {
with_id,
sort,
reverse,
filter,
limit,
} => {
let options = commands::list::ListOptions {
with_id,
sort,
reverse,
filter,
limit,
};
let output = commands::list::execute(options)?;
println!("{}", output);
}
Commands::Stats { project_path } => {
let project_path = project_path.map(PathBuf::from);
let stats = commands::stats::stats(project_path)?;
println!("{}", commands::stats::format_stats(&stats));
}
Commands::ExportChat {
project_path,
workspace_id,
format,
output,
with_thinking,
with_tools,
with_stats,
verbose,
include_archived,
split,
exclude_blank,
} => {
let format = commands::export_chat::ExportFormat::from_str(&format)
.context("Invalid format. Use 'md' or 'json'")?;
let options = commands::export_chat::ExportOptions {
with_thinking: with_thinking || verbose,
with_tools: with_tools || verbose,
with_stats: with_stats || verbose,
include_archived,
exclude_blank,
};
match (project_path, workspace_id) {
(Some(path), None) => {
commands::export_chat::execute(
&path,
format,
output.as_deref(),
&options,
split,
)?;
}
(None, Some(id)) => {
commands::export_chat::execute_by_id(
&id,
format,
output.as_deref(),
&options,
split,
)?;
}
(None, None) => {
anyhow::bail!("Either project_path or --workspace-id must be provided");
}
(Some(_), Some(_)) => {
unreachable!()
}
}
}
Commands::Clean { dry_run, yes } => {
if dry_run {
println!("{}", "(DRY-RUN MODE - no changes will be made)".blue());
}
commands::clean::execute(dry_run, yes)?;
}
Commands::Backup {
project_path,
backup_file,
} => {
commands::backup::execute(&project_path, &backup_file)?;
}
Commands::Restore {
backup_file,
new_path,
} => {
commands::restore::execute(&backup_file, &new_path)?;
}
Commands::Clone {
old_path,
new_path,
dry_run,
} => {
if dry_run {
println!("{}", "(DRY-RUN MODE - no changes will be made)".blue());
}
commands::clone::execute(&old_path, &new_path, dry_run)?;
}
}
Ok(())
}