#![recursion_limit = "256"] use clap::Parser; use dialoguer::{Confirm, Input}; use serde::Serialize; use strsim::jaro_winkler; use tracing_subscriber::Layer; use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::util::SubscriberInitExt; use lore::Config; use lore::cli::autocorrect::{self, CorrectionResult}; use lore::cli::commands::{ IngestDisplay, InitInputs, InitOptions, InitResult, ListFilters, MrListFilters, NoteListFilters, RefreshOptions, RefreshResult, SearchCliFilters, SyncOptions, TimelineParams, delete_orphan_projects, open_issue_in_browser, open_mr_in_browser, parse_trace_path, print_count, print_count_json, print_cron_install, print_cron_install_json, print_cron_status, print_cron_status_json, print_cron_uninstall, print_cron_uninstall_json, print_doctor_results, print_drift_human, print_drift_json, print_dry_run_preview, print_dry_run_preview_json, print_embed, print_embed_json, print_event_count, print_event_count_json, print_file_history, print_file_history_json, print_generate_docs, print_generate_docs_json, print_ingest_summary, print_ingest_summary_json, print_list_issues, print_list_issues_json, print_list_mrs, print_list_mrs_json, print_list_notes, print_list_notes_json, print_related_human, print_related_json, print_search_results, print_search_results_json, print_show_issue, print_show_issue_json, print_show_mr, print_show_mr_json, print_stats, print_stats_json, print_sync, print_sync_json, print_sync_status, print_sync_status_json, print_timeline, print_timeline_json_with_meta, print_trace, print_trace_json, print_who_human, print_who_json, query_notes, run_auth_test, run_count, run_count_events, run_cron_install, run_cron_status, run_cron_uninstall, run_doctor, run_drift, run_embed, run_file_history, run_generate_docs, run_ingest, run_ingest_dry_run, run_init, run_init_refresh, run_list_issues, run_list_mrs, run_me, run_related, run_search, run_show_issue, run_show_mr, run_stats, run_sync, run_sync_status, run_timeline, run_token_set, run_token_show, run_who, }; use lore::cli::render::{ColorMode, GlyphMode, Icons, LoreRenderer, Theme}; use lore::cli::robot::{RobotMeta, strip_schemas}; use lore::cli::{ Cli, Commands, CountArgs, CronAction, CronArgs, EmbedArgs, FileHistoryArgs, GenerateDocsArgs, IngestArgs, IssuesArgs, MeArgs, MrsArgs, NotesArgs, SearchArgs, StatsArgs, SyncArgs, TimelineArgs, TokenAction, TokenArgs, TraceArgs, WhoArgs, }; use lore::core::db::{ LATEST_SCHEMA_VERSION, create_connection, get_schema_version, run_migrations, }; use lore::core::error::{LoreError, RobotErrorOutput}; use lore::core::logging; use lore::core::metrics::MetricsLayer; use lore::core::path_resolver::{build_path_query, normalize_repo_path}; use lore::core::paths::{get_config_path, get_db_path, get_log_dir}; use lore::core::project::resolve_project; use lore::core::shutdown::{ShutdownSignal, install_ctrl_c_handler}; use lore::core::trace::run_trace; use lore::ingestion::storage::queue::release_all_locked_jobs; use lore::ingestion::storage::sync_run::SyncRunRecorder; fn main() { let rt = match asupersync::runtime::RuntimeBuilder::new().build() { Ok(rt) => rt, Err(e) => { eprintln!("Failed to build async runtime: {e}"); std::process::exit(1); } }; let rt_handle = rt.handle(); rt.block_on(async { #[cfg(unix)] unsafe { libc::signal(libc::SIGPIPE, libc::SIG_DFL); } // Phase 1: Early robot mode detection for structured clap errors let robot_mode_early = Cli::detect_robot_mode_from_env(); // Phase 1.5: Pre-clap arg correction for agent typo tolerance let raw_args: Vec = std::env::args().collect(); let correction_result = autocorrect::correct_args(raw_args, robot_mode_early); // Emit correction warnings to stderr (before clap parsing, so they appear // even if clap still fails on something else) if !correction_result.corrections.is_empty() { emit_correction_warnings(&correction_result, robot_mode_early); } let cli = match Cli::try_parse_from(&correction_result.args) { Ok(cli) => cli, Err(e) => { handle_clap_error(e, robot_mode_early, &correction_result); } }; let robot_mode = cli.is_robot_mode(); let logging_config = lore::Config::load(cli.config.as_deref()) .map(|c| c.logging) .unwrap_or_default(); let log_dir = get_log_dir(logging_config.log_dir.as_deref()); if logging_config.file_logging && logging_config.retention_days > 0 { logging::cleanup_old_logs(&log_dir, logging_config.retention_days); } let stderr_filter = logging::build_stderr_filter(cli.verbose, cli.quiet); let metrics_layer = MetricsLayer::new(); let registry = tracing_subscriber::registry(); let _file_guard: Option; if cli.log_format == "json" { let stderr_layer = tracing_subscriber::fmt::layer() .json() .with_writer(lore::cli::progress::SuspendingWriter) .with_filter(stderr_filter); if logging_config.file_logging { let file_filter = logging::build_file_filter(); std::fs::create_dir_all(&log_dir).ok(); let file_appender = tracing_appender::rolling::daily(&log_dir, "lore"); let (non_blocking, guard) = tracing_appender::non_blocking(file_appender); _file_guard = Some(guard); let file_layer = tracing_subscriber::fmt::layer() .json() .with_writer(non_blocking) .with_filter(file_filter); registry .with(stderr_layer) .with(file_layer) .with(metrics_layer.clone()) .init(); } else { _file_guard = None; registry .with(stderr_layer) .with(metrics_layer.clone()) .init(); } } else { let stderr_layer = tracing_subscriber::fmt::layer() .event_format(logging::CompactHumanFormat) .with_writer(lore::cli::progress::SuspendingWriter) .with_filter(stderr_filter); if logging_config.file_logging { let file_filter = logging::build_file_filter(); std::fs::create_dir_all(&log_dir).ok(); let file_appender = tracing_appender::rolling::daily(&log_dir, "lore"); let (non_blocking, guard) = tracing_appender::non_blocking(file_appender); _file_guard = Some(guard); let file_layer = tracing_subscriber::fmt::layer() .json() .with_writer(non_blocking) .with_filter(file_filter); registry .with(stderr_layer) .with(file_layer) .with(metrics_layer.clone()) .init(); } else { _file_guard = None; registry .with(stderr_layer) .with(metrics_layer.clone()) .init(); } } // Icon mode is independent of color flags; robot mode still defaults to ASCII. let glyphs = GlyphMode::detect(cli.icons.as_deref(), robot_mode); if std::env::var("NO_COLOR").is_ok_and(|v| !v.is_empty()) { LoreRenderer::init(ColorMode::Never, glyphs); console::set_colors_enabled(false); } else { match cli.color.as_str() { "never" => { LoreRenderer::init(ColorMode::Never, glyphs); console::set_colors_enabled(false); } "always" => { LoreRenderer::init(ColorMode::Always, glyphs); console::set_colors_enabled(true); } "auto" => { LoreRenderer::init(ColorMode::Auto, glyphs); } other => { LoreRenderer::init(ColorMode::Auto, glyphs); eprintln!("Warning: unknown color mode '{}', using auto", other); } } } let quiet = cli.quiet; let result = match cli.command { // Phase 2: Handle no-args case - in robot mode, output robot-docs; otherwise show help None => { if robot_mode { handle_robot_docs(robot_mode, false) } else { use clap::CommandFactory; let mut cmd = Cli::command(); cmd.print_help().ok(); println!(); Ok(()) } } Some(Commands::Issues(args)) => handle_issues(cli.config.as_deref(), args, robot_mode), Some(Commands::Mrs(args)) => handle_mrs(cli.config.as_deref(), args, robot_mode), Some(Commands::Notes(args)) => handle_notes(cli.config.as_deref(), args, robot_mode), Some(Commands::Search(args)) => { handle_search(cli.config.as_deref(), args, robot_mode).await } Some(Commands::Timeline(args)) => { handle_timeline(cli.config.as_deref(), args, robot_mode).await } Some(Commands::Who(args)) => handle_who(cli.config.as_deref(), args, robot_mode), Some(Commands::Me(args)) => handle_me(cli.config.as_deref(), args, robot_mode), Some(Commands::FileHistory(args)) => { handle_file_history(cli.config.as_deref(), args, robot_mode) } Some(Commands::Trace(args)) => handle_trace(cli.config.as_deref(), args, robot_mode), Some(Commands::Cron(args)) => handle_cron(cli.config.as_deref(), args, robot_mode), Some(Commands::Token(args)) => handle_token(cli.config.as_deref(), args, robot_mode).await, Some(Commands::Drift { entity_type, iid, threshold, project, }) => { handle_drift( cli.config.as_deref(), &entity_type, iid, threshold, project.as_deref(), robot_mode, ) .await } Some(Commands::Related { query_or_type, iid, limit, project, }) => { handle_related( cli.config.as_deref(), &query_or_type, iid, limit, project.as_deref(), robot_mode, ) .await } Some(Commands::Stats(args)) => handle_stats(cli.config.as_deref(), args, robot_mode).await, Some(Commands::Embed(args)) => handle_embed(cli.config.as_deref(), args, robot_mode, &rt_handle).await, Some(Commands::Sync(args)) => { handle_sync_cmd(cli.config.as_deref(), args, robot_mode, &metrics_layer, &rt_handle).await } Some(Commands::Ingest(args)) => { handle_ingest( cli.config.as_deref(), args, robot_mode, quiet, &metrics_layer, &rt_handle, ) .await } Some(Commands::Count(args)) => handle_count(cli.config.as_deref(), args, robot_mode).await, Some(Commands::Status) => handle_sync_status_cmd(cli.config.as_deref(), robot_mode).await, Some(Commands::Auth) => handle_auth_test(cli.config.as_deref(), robot_mode).await, Some(Commands::Doctor) => handle_doctor(cli.config.as_deref(), robot_mode).await, Some(Commands::Version) => handle_version(robot_mode), Some(Commands::Completions { shell }) => handle_completions(&shell), Some(Commands::Init { refresh, force, non_interactive, gitlab_url, token_env_var, projects, default_project, }) => { handle_init( cli.config.as_deref(), refresh, force, non_interactive, robot_mode, gitlab_url, token_env_var, projects, default_project, ) .await } Some(Commands::GenerateDocs(args)) => { handle_generate_docs(cli.config.as_deref(), args, robot_mode).await } Some(Commands::Backup) => handle_backup(robot_mode), Some(Commands::Reset { yes: _ }) => handle_reset(robot_mode), Some(Commands::Migrate) => handle_migrate(cli.config.as_deref(), robot_mode).await, Some(Commands::Health) => handle_health(cli.config.as_deref(), robot_mode).await, Some(Commands::RobotDocs { brief }) => handle_robot_docs(robot_mode, brief), Some(Commands::List { entity, limit, project, state, author, assignee, label, milestone, since, due_before, has_due_date, sort, order, open, draft, no_draft, reviewer, target_branch, source_branch, }) => { if robot_mode { eprintln!( r#"{{"warning":{{"type":"DEPRECATED","message":"'lore list' is deprecated, use 'lore issues' or 'lore mrs'","successor":"issues / mrs"}}}}"# ); } else { eprintln!( "{}", Theme::warning().render( "warning: 'lore list' is deprecated, use 'lore issues' or 'lore mrs'" ) ); } handle_list_compat( cli.config.as_deref(), &entity, limit, project.as_deref(), state.as_deref(), author.as_deref(), assignee.as_deref(), label.as_deref(), milestone.as_deref(), since.as_deref(), due_before.as_deref(), has_due_date, &sort, &order, open, robot_mode, draft, no_draft, reviewer.as_deref(), target_branch.as_deref(), source_branch.as_deref(), ) .await } Some(Commands::Show { entity, iid, project, }) => { if robot_mode { eprintln!( r#"{{"warning":{{"type":"DEPRECATED","message":"'lore show' is deprecated, use 'lore {entity}s {iid}'","successor":"{entity}s"}}}}"# ); } else { eprintln!( "{}", Theme::warning().render(&format!( "warning: 'lore show' is deprecated, use 'lore {}s {}'", entity, iid )) ); } handle_show_compat( cli.config.as_deref(), &entity, iid, project.as_deref(), robot_mode, ) .await } Some(Commands::AuthTest) => { if robot_mode { eprintln!( r#"{{"warning":{{"type":"DEPRECATED","message":"'lore auth-test' is deprecated, use 'lore auth'","successor":"auth"}}}}"# ); } else { eprintln!( "{}", Theme::warning() .render("warning: 'lore auth-test' is deprecated, use 'lore auth'") ); } handle_auth_test(cli.config.as_deref(), robot_mode).await } Some(Commands::SyncStatus) => { if robot_mode { eprintln!( r#"{{"warning":{{"type":"DEPRECATED","message":"'lore sync-status' is deprecated, use 'lore status'","successor":"status"}}}}"# ); } else { eprintln!( "{}", Theme::warning() .render("warning: 'lore sync-status' is deprecated, use 'lore status'") ); } handle_sync_status_cmd(cli.config.as_deref(), robot_mode).await } }; if let Err(e) = result { handle_error(e, robot_mode); } }); // rt.block_on } include!("app/dispatch.rs");