use std::fs::{self}; use std::path::{Path, PathBuf}; use clap::Subcommand; use eyre::{Result, WrapErr}; use tracing_subscriber::filter::EnvFilter; use crate::atuin_client::settings::Settings; fn cleanup_old_logs(log_dir: &Path, prefix: &str, retention_days: u64) { let cutoff = std::time::SystemTime::now() - std::time::Duration::from_secs(retention_days * 24 * 60 * 60); let Ok(entries) = fs::read_dir(log_dir) else { return; }; for entry in entries.flatten() { let path = entry.path(); let Some(name) = path.file_name().and_then(|n| n.to_str()) else { continue; }; // Match files like "search.log.2024-02-23" or "daemon.log.2024-02-23" if !name.starts_with(prefix) || name == prefix { continue; } if let Ok(metadata) = entry.metadata() && let Ok(modified) = metadata.modified() && modified < cutoff { drop(fs::remove_file(&path)); } } } mod config; mod daemon; mod default_config; mod history; mod info; mod stats; // mod store; mod sync; mod wrapped; #[derive(Subcommand, Debug)] #[command(infer_subcommands = true)] pub(crate) enum Cmd { /// Manipulate shell history #[command(subcommand)] History(history::Cmd), /// Interact with the daemon #[command(subcommand)] Daemon(daemon::Cmd), #[command(subcommand)] /// Request a sync or view sync status Sync(sync::Cmd), // /// Manage the atuin data store // #[command(subcommand)] // Store(store::Cmd), /// Information about dotfiles locations and ENV vars #[command()] Info, /// Calculate statistics for your history Stats(stats::Cmd), #[command()] /// Display a recap of your last year's history Wrapped { year: Option }, /// Print the default atuin configuration (config.toml) #[command()] DefaultConfig, #[command(subcommand)] /// Manage your configuration Config(config::Cmd), } impl Cmd { pub(crate) fn run(self) -> Result<()> { let mut runtime = tokio::runtime::Builder::new_current_thread(); let runtime = runtime.enable_all().build().unwrap(); let res = { let settings = Settings::new().wrap_err("could not load client settings")?; runtime.block_on(self.run_inner(settings)) }; runtime.shutdown_timeout(std::time::Duration::from_millis(50)); res } async fn run_inner(self, settings: Settings) -> Result<()> { // ATUIN_LOG env var overrides config file level settings let env_log_set = std::env::var("ATUIN_LOG").is_ok(); // Base filter from env var (or empty if not set) let base_filter = EnvFilter::from_env("ATUIN_LOG").add_directive("sqlx_sqlite::regexp=off".parse()?); tracing::trace!(command = ?self, "client command"); // Skip initializing any databases for history // This is a pretty hot path, as it runs before and after every single command the user // runs match self { Self::History(history) => return history.run(&settings).await, Self::Config(config) => return config.run(&settings).await, _ => {} } match self { Self::Daemon(cmd) => cmd.run(&settings).await, Self::Sync(sync) => sync.run(settings).await, Self::Stats(stats) => stats.run(&settings).await, Self::Wrapped { year } => wrapped::run(year, &settings).await, // Self::Store(store) => store.run(&settings, &db, sqlite_store).await, Self::Info => info::run(&settings).await, Self::DefaultConfig => { default_config::run(); Ok(()) } Self::History(_) | Self::Config(_) => { unreachable!() } } } }