diff --git a/src/cli/mod.rs b/src/cli/mod.rs index c01a894..25f4190 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -22,6 +22,14 @@ pub struct Cli { #[arg(short = 'J', long = "json", global = true)] pub json: bool, + /// Color output: auto (default), always, or never + #[arg(long, global = true, value_parser = ["auto", "always", "never"], default_value = "auto")] + pub color: String, + + /// Suppress non-essential output + #[arg(short = 'q', long, global = true)] + pub quiet: bool, + #[command(subcommand)] pub command: Commands, } @@ -84,9 +92,11 @@ pub enum Commands { }, /// Create timestamped database backup + #[command(hide = true)] Backup, /// Delete database and reset all state + #[command(hide = true)] Reset { /// Skip confirmation prompt #[arg(short = 'y', long)] @@ -119,6 +129,14 @@ pub enum Commands { #[command(name = "robot-docs")] RobotDocs, + /// Generate shell completions + #[command(hide = true)] + Completions { + /// Shell to generate completions for + #[arg(value_parser = ["bash", "zsh", "fish", "powershell"])] + shell: String, + }, + // --- Hidden backward-compat aliases --- /// List issues or MRs (deprecated: use 'lore issues' or 'lore mrs') #[command(hide = true)] @@ -195,56 +213,65 @@ pub struct IssuesArgs { pub iid: Option, /// Maximum results - #[arg(short = 'n', long = "limit", default_value = "50")] + #[arg(short = 'n', long = "limit", default_value = "50", help_heading = "Output")] pub limit: usize, /// Filter by state (opened, closed, all) - #[arg(short = 's', long)] + #[arg(short = 's', long, help_heading = "Filters")] pub state: Option, /// Filter by project path - #[arg(short = 'p', long)] + #[arg(short = 'p', long, help_heading = "Filters")] pub project: Option, /// Filter by author username - #[arg(short = 'a', long)] + #[arg(short = 'a', long, help_heading = "Filters")] pub author: Option, /// Filter by assignee username - #[arg(short = 'A', long)] + #[arg(short = 'A', long, help_heading = "Filters")] pub assignee: Option, /// Filter by label (repeatable, AND logic) - #[arg(short = 'l', long)] + #[arg(short = 'l', long, help_heading = "Filters")] pub label: Option>, /// Filter by milestone title - #[arg(short = 'm', long)] + #[arg(short = 'm', long, help_heading = "Filters")] pub milestone: Option, /// Filter by time (7d, 2w, 1m, or YYYY-MM-DD) - #[arg(long)] + #[arg(long, help_heading = "Filters")] pub since: Option, /// Filter by due date (before this date, YYYY-MM-DD) - #[arg(long = "due-before")] + #[arg(long = "due-before", help_heading = "Filters")] pub due_before: Option, /// Show only issues with a due date - #[arg(long = "has-due")] + #[arg(long = "has-due", help_heading = "Filters", overrides_with = "no_has_due")] pub has_due: bool, + #[arg(long = "no-has-due", hide = true, overrides_with = "has_due")] + pub no_has_due: bool, + /// Sort field (updated, created, iid) - #[arg(long, value_parser = ["updated", "created", "iid"], default_value = "updated")] + #[arg(long, value_parser = ["updated", "created", "iid"], default_value = "updated", help_heading = "Sorting")] pub sort: String, /// Sort ascending (default: descending) - #[arg(long)] + #[arg(long, help_heading = "Sorting", overrides_with = "no_asc")] pub asc: bool, + #[arg(long = "no-asc", hide = true, overrides_with = "asc")] + pub no_asc: bool, + /// Open first matching item in browser - #[arg(short = 'o', long)] + #[arg(short = 'o', long, help_heading = "Actions", overrides_with = "no_open")] pub open: bool, + + #[arg(long = "no-open", hide = true, overrides_with = "open")] + pub no_open: bool, } /// Arguments for `lore mrs [IID]` @@ -254,64 +281,70 @@ pub struct MrsArgs { pub iid: Option, /// Maximum results - #[arg(short = 'n', long = "limit", default_value = "50")] + #[arg(short = 'n', long = "limit", default_value = "50", help_heading = "Output")] pub limit: usize, /// Filter by state (opened, merged, closed, locked, all) - #[arg(short = 's', long)] + #[arg(short = 's', long, help_heading = "Filters")] pub state: Option, /// Filter by project path - #[arg(short = 'p', long)] + #[arg(short = 'p', long, help_heading = "Filters")] pub project: Option, /// Filter by author username - #[arg(short = 'a', long)] + #[arg(short = 'a', long, help_heading = "Filters")] pub author: Option, /// Filter by assignee username - #[arg(short = 'A', long)] + #[arg(short = 'A', long, help_heading = "Filters")] pub assignee: Option, /// Filter by reviewer username - #[arg(short = 'r', long)] + #[arg(short = 'r', long, help_heading = "Filters")] pub reviewer: Option, /// Filter by label (repeatable, AND logic) - #[arg(short = 'l', long)] + #[arg(short = 'l', long, help_heading = "Filters")] pub label: Option>, /// Filter by time (7d, 2w, 1m, or YYYY-MM-DD) - #[arg(long)] + #[arg(long, help_heading = "Filters")] pub since: Option, /// Show only draft MRs - #[arg(short = 'd', long, conflicts_with = "no_draft")] + #[arg(short = 'd', long, conflicts_with = "no_draft", help_heading = "Filters")] pub draft: bool, /// Exclude draft MRs - #[arg(short = 'D', long = "no-draft", conflicts_with = "draft")] + #[arg(short = 'D', long = "no-draft", conflicts_with = "draft", help_heading = "Filters")] pub no_draft: bool, /// Filter by target branch - #[arg(long)] + #[arg(long, help_heading = "Filters")] pub target: Option, /// Filter by source branch - #[arg(long)] + #[arg(long, help_heading = "Filters")] pub source: Option, /// Sort field (updated, created, iid) - #[arg(long, value_parser = ["updated", "created", "iid"], default_value = "updated")] + #[arg(long, value_parser = ["updated", "created", "iid"], default_value = "updated", help_heading = "Sorting")] pub sort: String, /// Sort ascending (default: descending) - #[arg(long)] + #[arg(long, help_heading = "Sorting", overrides_with = "no_asc")] pub asc: bool, + #[arg(long = "no-asc", hide = true, overrides_with = "asc")] + pub no_asc: bool, + /// Open first matching item in browser - #[arg(short = 'o', long)] + #[arg(short = 'o', long, help_heading = "Actions", overrides_with = "no_open")] pub open: bool, + + #[arg(long = "no-open", hide = true, overrides_with = "open")] + pub no_open: bool, } /// Arguments for `lore ingest [ENTITY]` @@ -326,23 +359,32 @@ pub struct IngestArgs { pub project: Option, /// Override stale sync lock - #[arg(short = 'f', long)] + #[arg(short = 'f', long, overrides_with = "no_force")] pub force: bool, + #[arg(long = "no-force", hide = true, overrides_with = "force")] + pub no_force: bool, + /// Full re-sync: reset cursors and fetch all data from scratch - #[arg(long)] + #[arg(long, overrides_with = "no_full")] pub full: bool, + + #[arg(long = "no-full", hide = true, overrides_with = "full")] + pub no_full: bool, } /// Arguments for `lore stats` #[derive(Parser)] pub struct StatsArgs { /// Run integrity checks - #[arg(long)] + #[arg(long, overrides_with = "no_check")] pub check: bool, - /// Repair integrity issues (requires --check) - #[arg(long, requires = "check")] + #[arg(long = "no-check", hide = true, overrides_with = "check")] + pub no_check: bool, + + /// Repair integrity issues (auto-enables --check) + #[arg(long)] pub repair: bool, } @@ -353,47 +395,50 @@ pub struct SearchArgs { pub query: String, /// Search mode (lexical, hybrid, semantic) - #[arg(long, default_value = "hybrid")] + #[arg(long, default_value = "hybrid", value_parser = ["lexical", "hybrid", "semantic"], help_heading = "Output")] pub mode: String, /// Filter by source type (issue, mr, discussion) - #[arg(long = "type", value_name = "TYPE")] + #[arg(long = "type", value_name = "TYPE", value_parser = ["issue", "mr", "discussion"], help_heading = "Filters")] pub source_type: Option, /// Filter by author username - #[arg(long)] + #[arg(long, help_heading = "Filters")] pub author: Option, /// Filter by project path - #[arg(short = 'p', long)] + #[arg(short = 'p', long, help_heading = "Filters")] pub project: Option, /// Filter by label (repeatable, AND logic) - #[arg(long, action = clap::ArgAction::Append)] + #[arg(long, action = clap::ArgAction::Append, help_heading = "Filters")] pub label: Vec, /// Filter by file path (trailing / for prefix match) - #[arg(long)] + #[arg(long, help_heading = "Filters")] pub path: Option, /// Filter by created after (7d, 2w, or YYYY-MM-DD) - #[arg(long)] + #[arg(long, help_heading = "Filters")] pub after: Option, /// Filter by updated after (7d, 2w, or YYYY-MM-DD) - #[arg(long = "updated-after")] + #[arg(long = "updated-after", help_heading = "Filters")] pub updated_after: Option, /// Maximum results (default 20, max 100) - #[arg(short = 'n', long = "limit", default_value = "20")] + #[arg(short = 'n', long = "limit", default_value = "20", help_heading = "Output")] pub limit: usize, /// Show ranking explanation per result - #[arg(long)] + #[arg(long, help_heading = "Output", overrides_with = "no_explain")] pub explain: bool, + #[arg(long = "no-explain", hide = true, overrides_with = "explain")] + pub no_explain: bool, + /// FTS query mode: safe (default) or raw - #[arg(long = "fts-mode", default_value = "safe")] + #[arg(long = "fts-mode", default_value = "safe", value_parser = ["safe", "raw"], help_heading = "Output")] pub fts_mode: String, } @@ -413,13 +458,19 @@ pub struct GenerateDocsArgs { #[derive(Parser)] pub struct SyncArgs { /// Reset cursors, fetch everything - #[arg(long)] + #[arg(long, overrides_with = "no_full")] pub full: bool, + #[arg(long = "no-full", hide = true, overrides_with = "full")] + pub no_full: bool, + /// Override stale lock - #[arg(long)] + #[arg(long, overrides_with = "no_force")] pub force: bool, + #[arg(long = "no-force", hide = true, overrides_with = "force")] + pub no_force: bool, + /// Skip embedding step #[arg(long)] pub no_embed: bool, @@ -433,8 +484,11 @@ pub struct SyncArgs { #[derive(Parser)] pub struct EmbedArgs { /// Retry previously failed embeddings - #[arg(long)] + #[arg(long, overrides_with = "no_retry_failed")] pub retry_failed: bool, + + #[arg(long = "no-retry-failed", hide = true, overrides_with = "retry_failed")] + pub no_retry_failed: bool, } /// Arguments for `lore count `