feat(cli): Add --color, --quiet, --no-X negations, completions, and help headings
Global flags: - --color (auto|always|never) for explicit color control - --quiet/-q to suppress non-essential output - Hidden Completions subcommand for bash/zsh/fish/powershell Flag negation (--no-X) with overrides_with for: has-due, asc, open (issues/mrs), force/full (ingest/sync), check (stats), explain (search), retry-failed (embed). Enables scripted flag composition where later flags override earlier ones. Validation: - value_parser on search --mode, --type, --fts-mode for early rejection - Remove requires="check" from --repair (auto-enabled in handler) Polish: - help_heading groups (Filters, Sorting, Output, Actions) on issues, mrs, and search args for cleaner --help output - Hide Backup, Reset, and Completions from --help Co-Authored-By: Claude (us.anthropic.claude-opus-4-5-20251101-v1:0) <noreply@anthropic.com>
This commit is contained in:
148
src/cli/mod.rs
148
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<i64>,
|
||||
|
||||
/// 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<String>,
|
||||
|
||||
/// Filter by project path
|
||||
#[arg(short = 'p', long)]
|
||||
#[arg(short = 'p', long, help_heading = "Filters")]
|
||||
pub project: Option<String>,
|
||||
|
||||
/// Filter by author username
|
||||
#[arg(short = 'a', long)]
|
||||
#[arg(short = 'a', long, help_heading = "Filters")]
|
||||
pub author: Option<String>,
|
||||
|
||||
/// Filter by assignee username
|
||||
#[arg(short = 'A', long)]
|
||||
#[arg(short = 'A', long, help_heading = "Filters")]
|
||||
pub assignee: Option<String>,
|
||||
|
||||
/// Filter by label (repeatable, AND logic)
|
||||
#[arg(short = 'l', long)]
|
||||
#[arg(short = 'l', long, help_heading = "Filters")]
|
||||
pub label: Option<Vec<String>>,
|
||||
|
||||
/// Filter by milestone title
|
||||
#[arg(short = 'm', long)]
|
||||
#[arg(short = 'm', long, help_heading = "Filters")]
|
||||
pub milestone: Option<String>,
|
||||
|
||||
/// Filter by time (7d, 2w, 1m, or YYYY-MM-DD)
|
||||
#[arg(long)]
|
||||
#[arg(long, help_heading = "Filters")]
|
||||
pub since: Option<String>,
|
||||
|
||||
/// 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<String>,
|
||||
|
||||
/// 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<i64>,
|
||||
|
||||
/// 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<String>,
|
||||
|
||||
/// Filter by project path
|
||||
#[arg(short = 'p', long)]
|
||||
#[arg(short = 'p', long, help_heading = "Filters")]
|
||||
pub project: Option<String>,
|
||||
|
||||
/// Filter by author username
|
||||
#[arg(short = 'a', long)]
|
||||
#[arg(short = 'a', long, help_heading = "Filters")]
|
||||
pub author: Option<String>,
|
||||
|
||||
/// Filter by assignee username
|
||||
#[arg(short = 'A', long)]
|
||||
#[arg(short = 'A', long, help_heading = "Filters")]
|
||||
pub assignee: Option<String>,
|
||||
|
||||
/// Filter by reviewer username
|
||||
#[arg(short = 'r', long)]
|
||||
#[arg(short = 'r', long, help_heading = "Filters")]
|
||||
pub reviewer: Option<String>,
|
||||
|
||||
/// Filter by label (repeatable, AND logic)
|
||||
#[arg(short = 'l', long)]
|
||||
#[arg(short = 'l', long, help_heading = "Filters")]
|
||||
pub label: Option<Vec<String>>,
|
||||
|
||||
/// Filter by time (7d, 2w, 1m, or YYYY-MM-DD)
|
||||
#[arg(long)]
|
||||
#[arg(long, help_heading = "Filters")]
|
||||
pub since: Option<String>,
|
||||
|
||||
/// 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<String>,
|
||||
|
||||
/// Filter by source branch
|
||||
#[arg(long)]
|
||||
#[arg(long, help_heading = "Filters")]
|
||||
pub source: Option<String>,
|
||||
|
||||
/// 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<String>,
|
||||
|
||||
/// 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<String>,
|
||||
|
||||
/// Filter by author username
|
||||
#[arg(long)]
|
||||
#[arg(long, help_heading = "Filters")]
|
||||
pub author: Option<String>,
|
||||
|
||||
/// Filter by project path
|
||||
#[arg(short = 'p', long)]
|
||||
#[arg(short = 'p', long, help_heading = "Filters")]
|
||||
pub project: Option<String>,
|
||||
|
||||
/// Filter by label (repeatable, AND logic)
|
||||
#[arg(long, action = clap::ArgAction::Append)]
|
||||
#[arg(long, action = clap::ArgAction::Append, help_heading = "Filters")]
|
||||
pub label: Vec<String>,
|
||||
|
||||
/// Filter by file path (trailing / for prefix match)
|
||||
#[arg(long)]
|
||||
#[arg(long, help_heading = "Filters")]
|
||||
pub path: Option<String>,
|
||||
|
||||
/// Filter by created after (7d, 2w, or YYYY-MM-DD)
|
||||
#[arg(long)]
|
||||
#[arg(long, help_heading = "Filters")]
|
||||
pub after: Option<String>,
|
||||
|
||||
/// 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<String>,
|
||||
|
||||
/// 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 <ENTITY>`
|
||||
|
||||
Reference in New Issue
Block a user