diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 3f19e24..c6c2ce0 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -11,13 +11,17 @@ use std::io::IsTerminal; #[command(version, about, long_about = None)] pub struct Cli { /// Path to config file - #[arg(short, long, global = true)] + #[arg(short = 'c', long, global = true)] pub config: Option, /// Machine-readable JSON output (auto-enabled when piped) #[arg(long, global = true, env = "LORE_ROBOT")] pub robot: bool, + /// JSON output (global shorthand) + #[arg(short = 'J', long = "json", global = true)] + pub json: bool, + #[command(subcommand)] pub command: Commands, } @@ -25,17 +29,41 @@ pub struct Cli { impl Cli { /// Check if robot mode is active (explicit flag, env var, or non-TTY stdout) pub fn is_robot_mode(&self) -> bool { - self.robot || !std::io::stdout().is_terminal() + self.robot || self.json || !std::io::stdout().is_terminal() } } #[derive(Subcommand)] #[allow(clippy::large_enum_variant)] pub enum Commands { + /// List or show issues + Issues(IssuesArgs), + + /// List or show merge requests + Mrs(MrsArgs), + + /// Ingest data from GitLab + Ingest(IngestArgs), + + /// Count entities in local database + Count(CountArgs), + + /// Show sync state + Status, + + /// Verify GitLab authentication + Auth, + + /// Check environment health + Doctor, + + /// Show version information + Version, + /// Initialize configuration and database Init { /// Skip overwrite confirmation - #[arg(long)] + #[arg(short = 'f', long)] force: bool, /// Fail if prompts would be shown @@ -43,149 +71,67 @@ pub enum Commands { non_interactive: bool, }, - /// Verify GitLab authentication - AuthTest, - - /// Check environment health - Doctor { - /// Output as JSON - #[arg(long)] - json: bool, - }, - - /// Show version information - Version, - /// Create timestamped database backup Backup, /// Delete database and reset all state Reset { /// Skip confirmation prompt - #[arg(long)] - confirm: bool, + #[arg(short = 'y', long)] + yes: bool, }, /// Run pending database migrations Migrate, - /// Show sync state - SyncStatus, - - /// Ingest data from GitLab - Ingest { - /// Resource type to ingest - #[arg(long, value_parser = ["issues", "mrs"])] - r#type: String, - - /// Filter to single project - #[arg(long)] - project: Option, - - /// Override stale sync lock - #[arg(long)] - force: bool, - - /// Full re-sync: reset cursors and fetch all data from scratch - #[arg(long)] - full: bool, - }, - - /// List issues or MRs from local database + // --- Hidden backward-compat aliases --- + /// List issues or MRs (deprecated: use 'lore issues' or 'lore mrs') + #[command(hide = true)] List { /// Entity type to list #[arg(value_parser = ["issues", "mrs"])] entity: String, - /// Maximum results #[arg(long, default_value = "50")] limit: usize, - - /// Filter by project path #[arg(long)] project: Option, - - /// Filter by state (opened|closed|all for issues; opened|merged|closed|locked|all for MRs) #[arg(long)] state: Option, - - /// Filter by author username #[arg(long)] author: Option, - - /// Filter by assignee username #[arg(long)] assignee: Option, - - /// Filter by label (repeatable, AND logic) #[arg(long)] label: Option>, - - /// Filter by milestone title (issues only) #[arg(long)] milestone: Option, - - /// Filter by time (7d, 2w, 1m, or YYYY-MM-DD) #[arg(long)] since: Option, - - /// Filter by due date (before this date, YYYY-MM-DD) (issues only) #[arg(long)] due_before: Option, - - /// Show only issues with a due date (issues only) #[arg(long)] has_due_date: bool, - - /// Sort field #[arg(long, value_parser = ["updated", "created", "iid"], default_value = "updated")] sort: String, - - /// Sort order #[arg(long, value_parser = ["desc", "asc"], default_value = "desc")] order: String, - - /// Open first matching item in browser #[arg(long)] open: bool, - - /// Output as JSON - #[arg(long)] - json: bool, - - /// Show only draft MRs (MRs only) #[arg(long, conflicts_with = "no_draft")] draft: bool, - - /// Exclude draft MRs (MRs only) #[arg(long, conflicts_with = "draft")] no_draft: bool, - - /// Filter by reviewer username (MRs only) #[arg(long)] reviewer: Option, - - /// Filter by target branch (MRs only) #[arg(long)] target_branch: Option, - - /// Filter by source branch (MRs only) #[arg(long)] source_branch: Option, }, - /// Count entities in local database - Count { - /// Entity type to count - #[arg(value_parser = ["issues", "mrs", "discussions", "notes"])] - entity: String, - - /// Filter by noteable type (for discussions/notes) - #[arg(long, value_parser = ["issue", "mr"])] - r#type: Option, - }, - - /// Show detailed entity information + /// Show detailed entity information (deprecated: use 'lore issues ' or 'lore mrs ') + #[command(hide = true)] Show { /// Entity type to show #[arg(value_parser = ["issue", "mr"])] @@ -194,12 +140,173 @@ pub enum Commands { /// Entity IID iid: i64, - /// Filter by project path (required if iid is ambiguous) #[arg(long)] project: Option, - - /// Output as JSON - #[arg(long)] - json: bool, }, + + /// Verify GitLab authentication (deprecated: use 'lore auth') + #[command(hide = true, name = "auth-test")] + AuthTest, + + /// Show sync state (deprecated: use 'lore status') + #[command(hide = true, name = "sync-status")] + SyncStatus, +} + +/// Arguments for `lore issues [IID]` +#[derive(Parser)] +pub struct IssuesArgs { + /// Issue IID (omit to list, provide to show details) + pub iid: Option, + + /// Maximum results + #[arg(short = 'n', long = "limit", default_value = "50")] + pub limit: usize, + + /// Filter by state (opened, closed, all) + #[arg(short = 's', long)] + pub state: Option, + + /// Filter by project path + #[arg(short = 'p', long)] + pub project: Option, + + /// Filter by author username + #[arg(short = 'a', long)] + pub author: Option, + + /// Filter by assignee username + #[arg(short = 'A', long)] + pub assignee: Option, + + /// Filter by label (repeatable, AND logic) + #[arg(short = 'l', long)] + pub label: Option>, + + /// Filter by milestone title + #[arg(short = 'm', long)] + pub milestone: Option, + + /// Filter by time (7d, 2w, 1m, or YYYY-MM-DD) + #[arg(long)] + pub since: Option, + + /// Filter by due date (before this date, YYYY-MM-DD) + #[arg(long = "due-before")] + pub due_before: Option, + + /// Show only issues with a due date + #[arg(long = "has-due")] + pub has_due: bool, + + /// Sort field (updated, created, iid) + #[arg(long, value_parser = ["updated", "created", "iid"], default_value = "updated")] + pub sort: String, + + /// Sort ascending (default: descending) + #[arg(long)] + pub asc: bool, + + /// Open first matching item in browser + #[arg(short = 'o', long)] + pub open: bool, +} + +/// Arguments for `lore mrs [IID]` +#[derive(Parser)] +pub struct MrsArgs { + /// MR IID (omit to list, provide to show details) + pub iid: Option, + + /// Maximum results + #[arg(short = 'n', long = "limit", default_value = "50")] + pub limit: usize, + + /// Filter by state (opened, merged, closed, locked, all) + #[arg(short = 's', long)] + pub state: Option, + + /// Filter by project path + #[arg(short = 'p', long)] + pub project: Option, + + /// Filter by author username + #[arg(short = 'a', long)] + pub author: Option, + + /// Filter by assignee username + #[arg(short = 'A', long)] + pub assignee: Option, + + /// Filter by reviewer username + #[arg(short = 'r', long)] + pub reviewer: Option, + + /// Filter by label (repeatable, AND logic) + #[arg(short = 'l', long)] + pub label: Option>, + + /// Filter by time (7d, 2w, 1m, or YYYY-MM-DD) + #[arg(long)] + pub since: Option, + + /// Show only draft MRs + #[arg(short = 'd', long, conflicts_with = "no_draft")] + pub draft: bool, + + /// Exclude draft MRs + #[arg(short = 'D', long = "no-draft", conflicts_with = "draft")] + pub no_draft: bool, + + /// Filter by target branch + #[arg(long)] + pub target: Option, + + /// Filter by source branch + #[arg(long)] + pub source: Option, + + /// Sort field (updated, created, iid) + #[arg(long, value_parser = ["updated", "created", "iid"], default_value = "updated")] + pub sort: String, + + /// Sort ascending (default: descending) + #[arg(long)] + pub asc: bool, + + /// Open first matching item in browser + #[arg(short = 'o', long)] + pub open: bool, +} + +/// Arguments for `lore ingest [ENTITY]` +#[derive(Parser)] +pub struct IngestArgs { + /// Entity to ingest (issues, mrs). Omit to ingest everything. + #[arg(value_parser = ["issues", "mrs"])] + pub entity: Option, + + /// Filter to single project + #[arg(short = 'p', long)] + pub project: Option, + + /// Override stale sync lock + #[arg(short = 'f', long)] + pub force: bool, + + /// Full re-sync: reset cursors and fetch all data from scratch + #[arg(long)] + pub full: bool, +} + +/// Arguments for `lore count ` +#[derive(Parser)] +pub struct CountArgs { + /// Entity type to count (issues, mrs, discussions, notes) + #[arg(value_parser = ["issues", "mrs", "discussions", "notes"])] + pub entity: String, + + /// Parent type filter: issue or mr (for discussions/notes) + #[arg(short = 'f', long = "for", value_parser = ["issue", "mr"])] + pub for_entity: Option, } diff --git a/src/main.rs b/src/main.rs index 600ffa8..dbee8df 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,7 +18,7 @@ use lore::cli::commands::{ run_doctor, run_ingest, run_init, run_list_issues, run_list_mrs, run_show_issue, run_show_mr, run_sync_status, }; -use lore::cli::{Cli, Commands}; +use lore::cli::{Cli, Commands, CountArgs, IngestArgs, IssuesArgs, MrsArgs}; use lore::core::db::{create_connection, get_schema_version, run_migrations}; use lore::core::error::{GiError, RobotErrorOutput}; use lore::core::paths::get_config_path; @@ -47,33 +47,25 @@ async fn main() { let robot_mode = cli.is_robot_mode(); let result = match cli.command { + Commands::Issues(args) => handle_issues(cli.config.as_deref(), args, robot_mode).await, + Commands::Mrs(args) => handle_mrs(cli.config.as_deref(), args, robot_mode).await, + Commands::Ingest(args) => handle_ingest(cli.config.as_deref(), args, robot_mode).await, + Commands::Count(args) => { + handle_count(cli.config.as_deref(), args, robot_mode).await + } + Commands::Status => handle_sync_status_cmd(cli.config.as_deref(), robot_mode).await, + Commands::Auth => handle_auth_test(cli.config.as_deref(), robot_mode).await, + Commands::Doctor => handle_doctor(cli.config.as_deref(), robot_mode).await, + Commands::Version => handle_version(robot_mode), Commands::Init { force, non_interactive, } => handle_init(cli.config.as_deref(), force, non_interactive, robot_mode).await, - Commands::AuthTest => handle_auth_test(cli.config.as_deref(), robot_mode).await, - Commands::Doctor { json } => handle_doctor(cli.config.as_deref(), json || robot_mode).await, - Commands::Version => handle_version(robot_mode), Commands::Backup => handle_backup(robot_mode), - Commands::Reset { confirm: _ } => handle_reset(robot_mode), + Commands::Reset { yes: _ } => handle_reset(robot_mode), Commands::Migrate => handle_migrate(cli.config.as_deref(), robot_mode).await, - Commands::SyncStatus => handle_sync_status(cli.config.as_deref(), robot_mode).await, - Commands::Ingest { - r#type, - project, - force, - full, - } => { - handle_ingest( - cli.config.as_deref(), - &r#type, - project.as_deref(), - force, - full, - robot_mode, - ) - .await - } + + // --- Backward-compat: deprecated aliases --- Commands::List { entity, limit, @@ -89,14 +81,17 @@ async fn main() { sort, order, open, - json, draft, no_draft, reviewer, target_branch, source_branch, } => { - handle_list( + eprintln!( + "{}", + style("warning: 'lore list' is deprecated, use 'lore issues' or 'lore mrs'").yellow() + ); + handle_list_compat( cli.config.as_deref(), &entity, limit, @@ -112,7 +107,7 @@ async fn main() { &sort, &order, open, - json || robot_mode, + robot_mode, draft, no_draft, reviewer.as_deref(), @@ -121,24 +116,42 @@ async fn main() { ) .await } - Commands::Count { entity, r#type } => { - handle_count(cli.config.as_deref(), &entity, r#type.as_deref(), robot_mode).await - } Commands::Show { entity, iid, project, - json, } => { - handle_show( + eprintln!( + "{}", + style(format!( + "warning: 'lore show' is deprecated, use 'lore {}s {}'", + entity, iid + )) + .yellow() + ); + handle_show_compat( cli.config.as_deref(), &entity, iid, project.as_deref(), - json || robot_mode, + robot_mode, ) .await } + Commands::AuthTest => { + eprintln!( + "{}", + style("warning: 'lore auth-test' is deprecated, use 'lore auth'").yellow() + ); + handle_auth_test(cli.config.as_deref(), robot_mode).await + } + Commands::SyncStatus => { + eprintln!( + "{}", + style("warning: 'lore sync-status' is deprecated, use 'lore status'").yellow() + ); + handle_sync_status_cmd(cli.config.as_deref(), robot_mode).await + } }; if let Err(e) = result { @@ -207,6 +220,259 @@ fn handle_error(e: Box, robot_mode: bool) -> ! { std::process::exit(1); } +// ============================================================================ +// Primary command handlers +// ============================================================================ + +async fn handle_issues( + config_override: Option<&str>, + args: IssuesArgs, + robot_mode: bool, +) -> Result<(), Box> { + let config = Config::load(config_override)?; + let order = if args.asc { "asc" } else { "desc" }; + + if let Some(iid) = args.iid { + // Show mode + let result = run_show_issue(&config, iid, args.project.as_deref())?; + if robot_mode { + print_show_issue_json(&result); + } else { + print_show_issue(&result); + } + } else { + // List mode + let filters = ListFilters { + limit: args.limit, + project: args.project.as_deref(), + state: args.state.as_deref(), + author: args.author.as_deref(), + assignee: args.assignee.as_deref(), + labels: args.label.as_deref(), + milestone: args.milestone.as_deref(), + since: args.since.as_deref(), + due_before: args.due_before.as_deref(), + has_due_date: args.has_due, + sort: &args.sort, + order, + }; + + let result = run_list_issues(&config, filters)?; + + if args.open { + open_issue_in_browser(&result); + } else if robot_mode { + print_list_issues_json(&result); + } else { + print_list_issues(&result); + } + } + + Ok(()) +} + +async fn handle_mrs( + config_override: Option<&str>, + args: MrsArgs, + robot_mode: bool, +) -> Result<(), Box> { + let config = Config::load(config_override)?; + let order = if args.asc { "asc" } else { "desc" }; + + if let Some(iid) = args.iid { + // Show mode + let result = run_show_mr(&config, iid, args.project.as_deref())?; + if robot_mode { + print_show_mr_json(&result); + } else { + print_show_mr(&result); + } + } else { + // List mode + let filters = MrListFilters { + limit: args.limit, + project: args.project.as_deref(), + state: args.state.as_deref(), + author: args.author.as_deref(), + assignee: args.assignee.as_deref(), + reviewer: args.reviewer.as_deref(), + labels: args.label.as_deref(), + since: args.since.as_deref(), + draft: args.draft, + no_draft: args.no_draft, + target_branch: args.target.as_deref(), + source_branch: args.source.as_deref(), + sort: &args.sort, + order, + }; + + let result = run_list_mrs(&config, filters)?; + + if args.open { + open_mr_in_browser(&result); + } else if robot_mode { + print_list_mrs_json(&result); + } else { + print_list_mrs(&result); + } + } + + Ok(()) +} + +async fn handle_ingest( + config_override: Option<&str>, + args: IngestArgs, + robot_mode: bool, +) -> Result<(), Box> { + let config = Config::load(config_override)?; + + match args.entity.as_deref() { + Some(resource_type) => { + // Single entity ingest + let result = run_ingest( + &config, + resource_type, + args.project.as_deref(), + args.force, + args.full, + robot_mode, + ) + .await?; + + if robot_mode { + print_ingest_summary_json(&result); + } else { + print_ingest_summary(&result); + } + } + None => { + // Ingest everything: issues then MRs + if !robot_mode { + println!( + "{}", + style("Ingesting all content (issues + merge requests)...").blue() + ); + println!(); + } + + let issues_result = run_ingest( + &config, + "issues", + args.project.as_deref(), + args.force, + args.full, + robot_mode, + ) + .await?; + + let mrs_result = run_ingest( + &config, + "mrs", + args.project.as_deref(), + args.force, + args.full, + robot_mode, + ) + .await?; + + if robot_mode { + print_combined_ingest_json(&issues_result, &mrs_result); + } else { + print_ingest_summary(&issues_result); + print_ingest_summary(&mrs_result); + } + } + } + + Ok(()) +} + +/// JSON output for combined ingest (issues + mrs). +#[derive(Serialize)] +struct CombinedIngestOutput { + ok: bool, + data: CombinedIngestData, +} + +#[derive(Serialize)] +struct CombinedIngestData { + resource_type: String, + issues: CombinedIngestEntityStats, + merge_requests: CombinedIngestEntityStats, +} + +#[derive(Serialize)] +struct CombinedIngestEntityStats { + projects_synced: usize, + fetched: usize, + upserted: usize, + labels_created: usize, + discussions_fetched: usize, + notes_upserted: usize, +} + +fn print_combined_ingest_json( + issues: &lore::cli::commands::ingest::IngestResult, + mrs: &lore::cli::commands::ingest::IngestResult, +) { + let output = CombinedIngestOutput { + ok: true, + data: CombinedIngestData { + resource_type: "all".to_string(), + issues: CombinedIngestEntityStats { + projects_synced: issues.projects_synced, + fetched: issues.issues_fetched, + upserted: issues.issues_upserted, + labels_created: issues.labels_created, + discussions_fetched: issues.discussions_fetched, + notes_upserted: issues.notes_upserted, + }, + merge_requests: CombinedIngestEntityStats { + projects_synced: mrs.projects_synced, + fetched: mrs.mrs_fetched, + upserted: mrs.mrs_upserted, + labels_created: mrs.labels_created, + discussions_fetched: mrs.discussions_fetched, + notes_upserted: mrs.notes_upserted, + }, + }, + }; + + println!("{}", serde_json::to_string(&output).unwrap()); +} + +async fn handle_count( + config_override: Option<&str>, + args: CountArgs, + robot_mode: bool, +) -> Result<(), Box> { + let config = Config::load(config_override)?; + + let result = run_count(&config, &args.entity, args.for_entity.as_deref())?; + if robot_mode { + print_count_json(&result); + } else { + print_count(&result); + } + Ok(()) +} + +async fn handle_sync_status_cmd( + config_override: Option<&str>, + robot_mode: bool, +) -> Result<(), Box> { + let config = Config::load(config_override)?; + + let result = run_sync_status(&config)?; + if robot_mode { + print_sync_status_json(&result); + } else { + print_sync_status(&result); + } + Ok(()) +} + async fn handle_init( config_override: Option<&str>, force: bool, @@ -389,11 +655,11 @@ async fn handle_auth_test( async fn handle_doctor( config_override: Option<&str>, - json: bool, + robot_mode: bool, ) -> Result<(), Box> { let result = run_doctor(config_override).await; - if json { + if robot_mode { println!("{}", serde_json::to_string_pretty(&result)?); } else { print_doctor_results(&result); @@ -406,191 +672,6 @@ async fn handle_doctor( Ok(()) } -async fn handle_ingest( - config_override: Option<&str>, - resource_type: &str, - project_filter: Option<&str>, - force: bool, - full: bool, - robot_mode: bool, -) -> Result<(), Box> { - let config = Config::load(config_override)?; - - match run_ingest(&config, resource_type, project_filter, force, full, robot_mode).await { - Ok(result) => { - if robot_mode { - print_ingest_summary_json(&result); - } else { - print_ingest_summary(&result); - } - Ok(()) - } - Err(e) => { - eprintln!("{}", style(format!("Error: {e}")).red()); - std::process::exit(1); - } - } -} - -#[allow(clippy::too_many_arguments)] -async fn handle_list( - config_override: Option<&str>, - entity: &str, - limit: usize, - project_filter: Option<&str>, - state_filter: Option<&str>, - author_filter: Option<&str>, - assignee_filter: Option<&str>, - label_filter: Option<&[String]>, - milestone_filter: Option<&str>, - since_filter: Option<&str>, - due_before_filter: Option<&str>, - has_due_date: bool, - sort: &str, - order: &str, - open_browser: bool, - json_output: bool, - draft: bool, - no_draft: bool, - reviewer_filter: Option<&str>, - target_branch_filter: Option<&str>, - source_branch_filter: Option<&str>, -) -> Result<(), Box> { - let config = Config::load(config_override)?; - - match entity { - "issues" => { - let filters = ListFilters { - limit, - project: project_filter, - state: state_filter, - author: author_filter, - assignee: assignee_filter, - labels: label_filter, - milestone: milestone_filter, - since: since_filter, - due_before: due_before_filter, - has_due_date, - sort, - order, - }; - - let result = run_list_issues(&config, filters)?; - - if open_browser { - open_issue_in_browser(&result); - } else if json_output { - print_list_issues_json(&result); - } else { - print_list_issues(&result); - } - - Ok(()) - } - "mrs" => { - let filters = MrListFilters { - limit, - project: project_filter, - state: state_filter, - author: author_filter, - assignee: assignee_filter, - reviewer: reviewer_filter, - labels: label_filter, - since: since_filter, - draft, - no_draft, - target_branch: target_branch_filter, - source_branch: source_branch_filter, - sort, - order, - }; - - let result = run_list_mrs(&config, filters)?; - - if open_browser { - open_mr_in_browser(&result); - } else if json_output { - print_list_mrs_json(&result); - } else { - print_list_mrs(&result); - } - - Ok(()) - } - _ => { - eprintln!("{}", style(format!("Unknown entity: {entity}")).red()); - std::process::exit(1); - } - } -} - -async fn handle_count( - config_override: Option<&str>, - entity: &str, - type_filter: Option<&str>, - robot_mode: bool, -) -> Result<(), Box> { - let config = Config::load(config_override)?; - - let result = run_count(&config, entity, type_filter)?; - if robot_mode { - print_count_json(&result); - } else { - print_count(&result); - } - Ok(()) -} - -async fn handle_sync_status( - config_override: Option<&str>, - robot_mode: bool, -) -> Result<(), Box> { - let config = Config::load(config_override)?; - - let result = run_sync_status(&config)?; - if robot_mode { - print_sync_status_json(&result); - } else { - print_sync_status(&result); - } - Ok(()) -} - -async fn handle_show( - config_override: Option<&str>, - entity: &str, - iid: i64, - project_filter: Option<&str>, - json: bool, -) -> Result<(), Box> { - let config = Config::load(config_override)?; - - match entity { - "issue" => { - let result = run_show_issue(&config, iid, project_filter)?; - if json { - print_show_issue_json(&result); - } else { - print_show_issue(&result); - } - Ok(()) - } - "mr" => { - let result = run_show_mr(&config, iid, project_filter)?; - if json { - print_show_mr_json(&result); - } else { - print_show_mr(&result); - } - Ok(()) - } - _ => { - eprintln!("{}", style(format!("Unknown entity: {entity}")).red()); - std::process::exit(1); - } - } -} - /// JSON output for version command. #[derive(Serialize)] struct VersionOutput { @@ -758,3 +839,134 @@ async fn handle_migrate( Ok(()) } + +// ============================================================================ +// Backward-compat handlers (deprecated, delegate to new handlers) +// ============================================================================ + +#[allow(clippy::too_many_arguments)] +async fn handle_list_compat( + config_override: Option<&str>, + entity: &str, + limit: usize, + project_filter: Option<&str>, + state_filter: Option<&str>, + author_filter: Option<&str>, + assignee_filter: Option<&str>, + label_filter: Option<&[String]>, + milestone_filter: Option<&str>, + since_filter: Option<&str>, + due_before_filter: Option<&str>, + has_due_date: bool, + sort: &str, + order: &str, + open_browser: bool, + json_output: bool, + draft: bool, + no_draft: bool, + reviewer_filter: Option<&str>, + target_branch_filter: Option<&str>, + source_branch_filter: Option<&str>, +) -> Result<(), Box> { + let config = Config::load(config_override)?; + + match entity { + "issues" => { + let filters = ListFilters { + limit, + project: project_filter, + state: state_filter, + author: author_filter, + assignee: assignee_filter, + labels: label_filter, + milestone: milestone_filter, + since: since_filter, + due_before: due_before_filter, + has_due_date, + sort, + order, + }; + + let result = run_list_issues(&config, filters)?; + + if open_browser { + open_issue_in_browser(&result); + } else if json_output { + print_list_issues_json(&result); + } else { + print_list_issues(&result); + } + + Ok(()) + } + "mrs" => { + let filters = MrListFilters { + limit, + project: project_filter, + state: state_filter, + author: author_filter, + assignee: assignee_filter, + reviewer: reviewer_filter, + labels: label_filter, + since: since_filter, + draft, + no_draft, + target_branch: target_branch_filter, + source_branch: source_branch_filter, + sort, + order, + }; + + let result = run_list_mrs(&config, filters)?; + + if open_browser { + open_mr_in_browser(&result); + } else if json_output { + print_list_mrs_json(&result); + } else { + print_list_mrs(&result); + } + + Ok(()) + } + _ => { + eprintln!("{}", style(format!("Unknown entity: {entity}")).red()); + std::process::exit(1); + } + } +} + +async fn handle_show_compat( + config_override: Option<&str>, + entity: &str, + iid: i64, + project_filter: Option<&str>, + json: bool, +) -> Result<(), Box> { + let config = Config::load(config_override)?; + + match entity { + "issue" => { + let result = run_show_issue(&config, iid, project_filter)?; + if json { + print_show_issue_json(&result); + } else { + print_show_issue(&result); + } + Ok(()) + } + "mr" => { + let result = run_show_mr(&config, iid, project_filter)?; + if json { + print_show_mr_json(&result); + } else { + print_show_mr(&result); + } + Ok(()) + } + _ => { + eprintln!("{}", style(format!("Unknown entity: {entity}")).red()); + std::process::exit(1); + } + } +}