use clap::{Parser, Subcommand};
use std::path::PathBuf;
#[derive(Parser, Clone)]
#[command(author, version, about, long_about = None)]
#[command(arg_required_else_help = true, disable_help_subcommand = true)]
pub struct Cli {
pub name: Option<String>,
#[arg(short, long, value_name = "FILE")]
pub config: Option<PathBuf>,
#[arg(short, long, action = clap::ArgAction::Count)]
pub debug: u8,
#[arg(long = "no-color", help = "Disable colored output")]
pub no_color: bool,
#[arg(
long = "generate-config",
help = "bkmr --generate-config > ~/.config/bkmr/config.toml"
)]
pub generate_config: bool,
#[command(subcommand)]
pub command: Option<Commands>,
}
#[derive(Subcommand, Clone)]
pub enum Commands {
Search {
fts_query: Option<String>,
#[arg(
short = 'e',
long = "exact",
help = "match exact, comma separated list"
)]
tags_exact: Option<String>,
#[arg(long = "exact-prefix", help = "tags to prefix the exact option")]
tags_exact_prefix: Option<String>,
#[arg(short = 't', long = "tags", help = "match all, comma separated list")]
tags_all: Option<String>,
#[arg(long = "tags-prefix", help = "tags to prefix the tags option")]
tags_all_prefix: Option<String>,
#[arg(
short = 'T',
long = "Tags",
help = "not match all, comma separated list"
)]
tags_all_not: Option<String>,
#[arg(long = "Tags-prefix", help = "tags to prefix the Tags option")]
tags_all_not_prefix: Option<String>,
#[arg(short = 'n', long = "ntags", help = "match any, comma separated list")]
tags_any: Option<String>,
#[arg(long = "ntags-prefix", help = "tags to prefix the ntags option")]
tags_any_prefix: Option<String>,
#[arg(
short = 'N',
long = "Ntags",
help = "not match any, comma separated list"
)]
tags_any_not: Option<String>,
#[arg(long = "Ntags-prefix", help = "tags to prefix the Ntags option")]
tags_any_not_prefix: Option<String>,
#[arg(short = 'o', long = "descending", help = "sort descending (implies --sort modified if no --sort given)")]
order_desc: bool,
#[arg(short = 'O', long = "ascending", help = "sort ascending (implies --sort modified if no --sort given)")]
order_asc: bool,
#[arg(long = "sort", help = "sort field: id, title, modified (default: id). Without -o/-O, id/title default ascending, modified defaults descending")]
sort_field: Option<String>,
#[arg(long = "np", help = "no prompt")]
non_interactive: bool,
#[arg(
long = "fzf",
help = "use fuzzy finder: [CTRL-O: copy to clipboard (shell scripts: copy 'bkmr open --no-edit <id> --' command), CTRL-E: edit, CTRL-D: delete, CTRL-A: clone, CTRL-P: show details, ENTER: open]"
)]
is_fuzzy: bool,
#[arg(
long = "fzf-style",
help = "fuzzy finder style: classic or enhanced",
default_value = "classic"
)]
fzf_style: Option<String>,
#[arg(long = "json", help = "non-interactive mode, output as json")]
is_json: bool,
#[arg(short = 'l', long = "limit", help = "limit number of results")]
limit: Option<i32>,
#[arg(
long = "interpolate",
help = "process template interpolation in search results display (not needed for FZF mode or bookmark actions - they interpolate automatically)"
)]
interpolate: bool,
#[arg(
long = "shell-stubs",
help = "output shell function stubs for shell script bookmarks (automatically filters for _shell_ type)"
)]
shell_stubs: bool,
#[arg(
long = "stdout",
help = "output selected bookmark content to stdout instead of executing (for shell wrapper integration)"
)]
stdout: bool,
#[arg(long = "embeddable", help = "filter to show only embeddable bookmarks")]
embeddable: bool,
},
#[command(name = "hsearch")]
HSearch {
query: String,
#[arg(short = 't', long = "tags", help = "all-of tag filter (comma-separated)")]
tags_all: Option<String>,
#[arg(short = 'T', long = "Tags", help = "exclude-all tag filter")]
tags_all_not: Option<String>,
#[arg(short = 'n', long = "ntags", help = "any-of tag filter")]
tags_any: Option<String>,
#[arg(short = 'N', long = "Ntags", help = "exclude-any tag filter")]
tags_any_not: Option<String>,
#[arg(short = 'e', long = "exact", help = "exact tag match")]
tags_exact: Option<String>,
#[arg(long = "mode", default_value = "hybrid", help = "search mode: hybrid or exact")]
mode: String,
#[arg(short = 'l', long = "limit", help = "limit number of results")]
limit: Option<i32>,
#[arg(long = "json", help = "output as JSON (includes rrf_score)")]
is_json: bool,
#[arg(long = "fzf", help = "use fzf for interactive selection")]
is_fuzzy: bool,
#[arg(long = "stdout", help = "output to stdout for piping")]
stdout: bool,
#[arg(long = "np", help = "no prompt")]
non_interactive: bool,
},
SemSearch {
query: String,
#[arg(short = 'l', long = "limit", help = "limit number of results")]
limit: Option<i32>,
#[arg(long = "np", help = "no prompt")]
non_interactive: bool,
},
Open {
ids: String,
#[arg(long = "no-edit", help = "skip interactive editing for shell scripts")]
no_edit: bool,
#[arg(
long = "file",
help = "treat ids parameter as file path for direct viewing"
)]
file: bool,
#[arg(
last = true,
help = "Arguments to pass to shell scripts (use -- to separate: bkmr open ID -- arg1 arg2)"
)]
script_args: Vec<String>,
#[arg(
long = "stdout",
help = "output bookmark content to stdout instead of executing"
)]
stdout: bool,
},
Add {
url: Option<String>,
tags: Option<String>,
#[arg(long = "title", help = "title")]
title: Option<String>,
#[arg(short = 'd', long = "description", help = "title")]
desc: Option<String>,
#[arg(long = "no-web", help = "do not fetch URL data")]
no_web: bool,
#[arg(short = 'e', long = "edit", help = "edit the bookmark while adding")]
edit: bool,
#[arg(
short = 't',
long = "type",
help = "bookmark type (uri, snip, text, shell, md, env)",
default_value = "uri"
)]
bookmark_type: String,
#[arg(short = 'c', long = "clone", help = "clone an existing bookmark by ID")]
clone_id: Option<i32>,
#[arg(long = "stdin", help = "read content from stdin into url field")]
stdin: bool,
#[arg(
long = "open-with",
help = "custom command to open this bookmark (replaces default open behavior)"
)]
open_with: Option<String>,
},
Delete {
ids: String,
},
Update {
ids: String,
#[arg(short = 't', long = "tags", help = "add tags to taglist")]
tags: Option<String>,
#[arg(short = 'n', long = "ntags", help = "remove tags from taglist")]
tags_not: Option<String>,
#[arg(short = 'f', long = "force", help = "overwrite taglist with tags")]
force: bool,
#[arg(
long = "open-with",
help = "set custom command to open this bookmark (use empty string to clear)"
)]
open_with: Option<String>,
},
Edit {
ids: String,
#[arg(
long = "force-db",
help = "force edit database content instead of source file for file-imported bookmarks"
)]
force_db: bool,
},
Show {
ids: String,
#[arg(long = "json", help = "output as json")]
is_json: bool,
},
Surprise {
#[arg(short = 'n', help = "number of URLs to open", default_value_t = 1)]
n: i32,
},
Tags {
tag: Option<String>,
},
CreateDb {
#[arg(help = "Path where the database will be created (default: ~/.config/bkmr/bkmr.db)")]
path: Option<String>,
#[arg(long, help = "Pre-fill the database with demo entries")]
pre_fill: bool,
},
SetEmbeddable {
id: i32,
#[arg(long = "enable", help = "Enable embedding for this bookmark")]
enable: bool,
#[arg(long = "disable", help = "Disable embedding for this bookmark")]
disable: bool,
},
Backfill {
#[arg(short = 'd', long = "dry-run", help = "only show what would be done")]
dry_run: bool,
#[arg(
short = 'f',
long = "force",
help = "force recompute of all embeddings (except _imported_)"
)]
force: bool,
},
LoadJson {
#[arg(help = "Path to JSON file with an array of bookmark objects")]
path: String,
#[arg(short = 'd', long = "dry-run", help = "only show what would be done")]
dry_run: bool,
},
LoadTexts {
#[arg(short = 'd', long = "dry-run", help = "only show what would be done")]
dry_run: bool,
#[arg(
short = 'f',
long = "force",
help = "force update embeddings even if content has not changed"
)]
force: bool,
#[arg(help = "Path to NDJSON file with text documents (one JSON object per line)")]
path: String,
},
ImportFiles {
#[arg(help = "Directories or files to import")]
paths: Vec<String>,
#[arg(
short = 'u',
long = "update",
help = "Update existing bookmarks when content differs"
)]
update: bool,
#[arg(
long = "delete-missing",
help = "Delete bookmarks whose source files no longer exist"
)]
delete_missing: bool,
#[arg(
short = 'd',
long = "dry-run",
help = "Show what would be done without making changes"
)]
dry_run: bool,
#[arg(
short = 'v',
long = "verbose",
help = "Show detailed information about skipped files and validation issues"
)]
verbose: bool,
#[arg(
long = "base-path",
help = "Base path variable name from config (e.g., SCRIPTS_HOME). Paths must be relative to the base path location."
)]
base_path: Option<String>,
},
Info {
#[arg(short = 's', long = "schema", help = "Show database schema")]
show_schema: bool,
},
Completion {
shell: String,
},
Lsp {
#[arg(long, help = "Disable bkmr template interpolation")]
no_interpolation: bool,
},
#[command(hide = true)]
Xxx {
ids: String,
#[arg(short = 't', long = "tags", help = "add tags to taglist")]
tags: Option<String>,
},
}