use std::path::PathBuf;
use clap::{Args, Parser, Subcommand};
const ROOT_AFTER_HELP: &str = "\
Selection rules:
Most note-targeting commands accept either --id or a title/search selector.
--id is exact and preferred for automation.
Output conventions:
Human-readable commands usually print tab-separated rows.
Commands with --json emit structured JSON for agent consumption.
Examples:
bear notes --limit 20 --json
bear open-note --title \"Scratch\"
bear add-text --title \"Scratch\" \"more text\"
";
#[derive(Parser, Debug)]
#[command(name = "bear")]
#[command(about = "CloudKit CLI for Bear notes on macOS", version, after_help = ROOT_AFTER_HELP)]
pub struct Cli {
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand, Debug)]
pub enum Commands {
Auth(AuthCommand),
OpenNote(OpenNoteCommand),
Tags,
OpenTag(OpenTagCommand),
Search(SearchCommand),
Notes(CloudNotesCommand),
Export(ExportCommand),
Duplicates(DuplicatesCommand),
Stats(StatsCommand),
Health(HealthCommand),
Untagged(FilterCommand),
Todo(FilterCommand),
Today(FilterCommand),
Locked(FilterCommand),
Create(CreateCommand),
AddText(AddTextCommand),
AddFile(AddFileCommand),
Trash(IdOrSearchCommand),
Archive(IdOrSearchCommand),
RenameTag(RenameTagCommand),
DeleteTag(DeleteTagCommand),
}
#[derive(Args, Debug)]
#[command(after_help = "Examples:\n bear auth\n bear auth --token '<CK_WEB_AUTH_TOKEN>'\n")]
pub struct AuthCommand {
#[arg(long, value_name = "CK_WEB_AUTH_TOKEN")]
pub token: Option<String>,
}
#[derive(Args, Debug)]
#[command(
after_help = "Exactly one of --id or --title should be provided.\nExamples:\n bear open-note --id NOTE_RECORD_NAME\n bear open-note --title 'Scratch'\n"
)]
pub struct OpenNoteCommand {
#[arg(long)]
pub id: Option<String>,
#[arg(long)]
pub title: Option<String>,
#[arg(long, default_value_t = false)]
pub exclude_trashed: bool,
}
#[derive(Args, Debug)]
#[command(after_help = "Examples:\n bear open-tag work\n bear open-tag work,project\n")]
pub struct OpenTagCommand {
pub name: String,
}
#[derive(Args, Debug)]
#[command(
after_help = "Examples:\n bear search rust\n bear search meeting --tag work --json\n bear search '' --since 2026-04-01 --before 2026-04-17\n"
)]
pub struct SearchCommand {
pub term: Option<String>,
#[arg(long, value_name = "TAG")]
pub tag: Option<String>,
#[arg(long, value_name = "DATE")]
pub since: Option<String>,
#[arg(long, value_name = "DATE")]
pub before: Option<String>,
#[arg(long, default_value_t = false)]
pub json: bool,
}
#[derive(Args, Debug)]
#[command(
after_help = "Default output is tab-separated: RECORD_NAME<TAB>TITLE\nExamples:\n bear notes\n bear notes --limit 50 --json\n bear notes --archived\n"
)]
pub struct CloudNotesCommand {
#[arg(long, value_name = "N")]
pub limit: Option<usize>,
#[arg(long, default_value_t = false)]
pub trashed: bool,
#[arg(long, default_value_t = false)]
pub archived: bool,
#[arg(long, default_value_t = false)]
pub json: bool,
}
#[derive(Args, Debug)]
#[command(after_help = "Use --json for structured duplicate groups.\n")]
pub struct DuplicatesCommand {
#[arg(long, default_value_t = false)]
pub json: bool,
}
#[derive(Args, Debug)]
#[command(
after_help = "Examples:\n bear export ./notes\n bear export ./notes --tag work --frontmatter --by-tag\n"
)]
pub struct ExportCommand {
pub output: PathBuf,
#[arg(long, value_name = "TAG")]
pub tag: Option<String>,
#[arg(long, default_value_t = false)]
pub frontmatter: bool,
#[arg(long = "by-tag", default_value_t = false)]
pub by_tag: bool,
}
#[derive(Args, Debug)]
#[command(after_help = "Use --json for structured metrics.\n")]
pub struct StatsCommand {
#[arg(long, default_value_t = false)]
pub json: bool,
}
#[derive(Args, Debug)]
#[command(after_help = "Use --json for structured issue lists.\n")]
pub struct HealthCommand {
#[arg(long, default_value_t = false)]
pub json: bool,
}
#[derive(Args, Debug)]
#[command(
after_help = "Examples:\n bear untagged\n bear todo meeting\n bear today standup\n bear locked finance\n"
)]
pub struct FilterCommand {
pub search: Option<String>,
}
#[derive(Args, Debug)]
#[command(
after_help = "Examples:\n bear create '# Scratch'\n printf '# Scratch\\n\\nBody' | bear create -t work -t inbox\n"
)]
pub struct CreateCommand {
pub text: Option<String>,
#[arg(long, short = 't', value_name = "TAG")]
pub tag: Vec<String>,
}
#[derive(Args, Debug)]
#[command(
after_help = "Exactly one of --id or --title should be provided.\nExamples:\n bear add-text --title 'Scratch' 'new line'\n bear add-text --id NOTE_RECORD_NAME --mode replace-all '# Rewritten'\n bear add-text --title 'Scratch' --header Tasks '- [ ] follow up'\n"
)]
pub struct AddTextCommand {
pub text: Option<String>,
#[arg(long)]
pub id: Option<String>,
#[arg(long)]
pub title: Option<String>,
#[arg(long, default_value = "append")]
pub mode: AddTextMode,
#[arg(long)]
pub header: Option<String>,
}
#[derive(Debug, Clone, clap::ValueEnum)]
pub enum AddTextMode {
Append,
Prepend,
ReplaceAll,
}
#[derive(Args, Debug)]
#[command(
after_help = "Exactly one of --id or --title should be provided.\nExamples:\n bear add-file ./report.pdf --title 'Scratch'\n bear add-file ./image.png --id NOTE_RECORD_NAME --mode prepend\n"
)]
pub struct AddFileCommand {
pub file: PathBuf,
#[arg(long)]
pub id: Option<String>,
#[arg(long)]
pub title: Option<String>,
#[arg(long)]
pub filename: Option<String>,
#[arg(long, default_value = "append")]
pub mode: AddFileMode,
}
#[derive(Debug, Clone, clap::ValueEnum)]
pub enum AddFileMode {
Append,
Prepend,
}
#[derive(Args, Debug)]
#[command(
after_help = "Use --id for exact targeting. Use --search to match by title, preferring the most recently modified note.\nExamples:\n bear trash --id NOTE_RECORD_NAME\n bear archive --search 'Scratch'\n"
)]
pub struct IdOrSearchCommand {
#[arg(long)]
pub id: Option<String>,
#[arg(long)]
pub search: Option<String>,
}
#[derive(Args, Debug)]
#[command(after_help = "Example:\n bear rename-tag inbox archive/inbox\n")]
pub struct RenameTagCommand {
pub name: String,
pub new_name: String,
}
#[derive(Args, Debug)]
#[command(after_help = "Example:\n bear delete-tag old-tag\n")]
pub struct DeleteTagCommand {
pub name: String,
}