aboutsummaryrefslogtreecommitdiffstats
path: root/crates/turtle/src/command/client
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-06-12 01:54:21 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-06-12 01:54:21 +0200
commitbbdf38018b47328b5faa2cef635c37095045be72 (patch)
tree8983817d547551ae12508a8ae8731b622d990af4 /crates/turtle/src/command/client
parentfeat(server): Make user stuff stateless (diff)
downloadatuin-bbdf38018b47328b5faa2cef635c37095045be72.zip
feat(server): Really make users stateless (with tests)
This commit also remove another load of unneeded features.
Diffstat (limited to 'crates/turtle/src/command/client')
-rw-r--r--crates/turtle/src/command/client/daemon.rs6
-rw-r--r--crates/turtle/src/command/client/doctor.rs405
-rw-r--r--crates/turtle/src/command/client/history.rs24
-rw-r--r--crates/turtle/src/command/client/import.rs186
-rw-r--r--crates/turtle/src/command/client/info.rs9
-rw-r--r--crates/turtle/src/command/client/init.rs4
-rw-r--r--crates/turtle/src/command/client/search.rs7
-rw-r--r--crates/turtle/src/command/client/search/engines.rs11
-rw-r--r--crates/turtle/src/command/client/search/engines/daemon.rs11
-rw-r--r--crates/turtle/src/command/client/search/engines/db.rs11
-rw-r--r--crates/turtle/src/command/client/search/engines/skim.rs15
-rw-r--r--crates/turtle/src/command/client/search/interactive.rs11
-rw-r--r--crates/turtle/src/command/client/server.rs1
-rw-r--r--crates/turtle/src/command/client/stats.rs14
-rw-r--r--crates/turtle/src/command/client/store.rs6
-rw-r--r--crates/turtle/src/command/client/store/pull.rs9
-rw-r--r--crates/turtle/src/command/client/store/push.rs11
-rw-r--r--crates/turtle/src/command/client/store/rebuild.rs6
-rw-r--r--crates/turtle/src/command/client/store/rekey.rs12
-rw-r--r--crates/turtle/src/command/client/sync.rs40
-rw-r--r--crates/turtle/src/command/client/sync/status.rs34
-rw-r--r--crates/turtle/src/command/client/wrapped.rs5
22 files changed, 120 insertions, 718 deletions
diff --git a/crates/turtle/src/command/client/daemon.rs b/crates/turtle/src/command/client/daemon.rs
index 2fb090aa..cb5dd118 100644
--- a/crates/turtle/src/command/client/daemon.rs
+++ b/crates/turtle/src/command/client/daemon.rs
@@ -7,7 +7,7 @@ use std::process::{Command, Stdio};
use std::time::{Duration, Instant};
use crate::atuin_client::{
- database::Sqlite, history::History, record::sqlite_store::SqliteStore, settings::Settings,
+ database::ClientSqlite, history::History, record::sqlite_store::SqliteStore, settings::Settings,
};
use crate::atuin_daemon::DaemonEvent;
use crate::atuin_daemon::client::{
@@ -86,7 +86,7 @@ impl Cmd {
self,
settings: Settings,
store: SqliteStore,
- history_db: Sqlite,
+ history_db: ClientSqlite,
) -> Result<()> {
match self.subcmd {
None => {
@@ -634,7 +634,7 @@ pub(crate) fn daemonize_current_process() -> Result<()> {
async fn run(
settings: Settings,
store: SqliteStore,
- history_db: Sqlite,
+ history_db: ClientSqlite,
force: bool,
) -> Result<()> {
if force {
diff --git a/crates/turtle/src/command/client/doctor.rs b/crates/turtle/src/command/client/doctor.rs
deleted file mode 100644
index eec690a5..00000000
--- a/crates/turtle/src/command/client/doctor.rs
+++ /dev/null
@@ -1,405 +0,0 @@
-use std::process::Command;
-use std::{env, str::FromStr};
-
-use crate::atuin_client::database::Sqlite;
-use crate::atuin_client::settings::Settings;
-use crate::atuin_common::shell::{Shell, shell_name};
-use crate::atuin_common::utils;
-use colored::Colorize;
-use eyre::Result;
-use serde::Serialize;
-
-use sysinfo::{Disks, System, get_current_pid};
-
-#[derive(Debug, Serialize)]
-struct ShellInfo {
- pub(crate) name: String,
-
- // best-effort, not supported on all OSes
- pub(crate) default: String,
-
- // Detect some shell plugins that the user has installed.
- // I'm just going to start with preexec/blesh
- pub(crate) plugins: Vec<String>,
-
- // The preexec framework used in the current session, if Atuin is loaded.
- pub(crate) preexec: Option<String>,
-}
-
-impl ShellInfo {
- // HACK ALERT!
- // Many of the shell vars we need to detect are not exported :(
- // So, we're going to run a interactive session and directly check the
- // variable. There's a chance this won't work, so it should not be fatal.
- //
- // Every shell we support handles `shell -ic 'command'`
- fn shellvar_exists(shell: &str, var: &str) -> bool {
- let cmd = Command::new(shell)
- .args([
- "-ic",
- format!("[ -z ${var} ] || echo ATUIN_DOCTOR_ENV_FOUND").as_str(),
- ])
- .output()
- .map_or(String::new(), |v| {
- let out = v.stdout;
- String::from_utf8(out).unwrap_or_default()
- });
-
- cmd.contains("ATUIN_DOCTOR_ENV_FOUND")
- }
-
- fn detect_preexec_framework(shell: &str) -> Option<String> {
- if env::var("ATUIN_SESSION").ok().is_none() {
- None
- } else if shell.starts_with("bash") || shell == "sh" {
- env::var("ATUIN_PREEXEC_BACKEND")
- .ok()
- .filter(|value| !value.is_empty())
- .and_then(|atuin_preexec_backend| {
- atuin_preexec_backend.rfind(':').and_then(|pos_colon| {
- u32::from_str(&atuin_preexec_backend[..pos_colon])
- .ok()
- .is_some_and(|preexec_shlvl| {
- env::var("SHLVL")
- .ok()
- .and_then(|shlvl| u32::from_str(&shlvl).ok())
- .is_some_and(|shlvl| shlvl == preexec_shlvl)
- })
- .then(|| atuin_preexec_backend[pos_colon + 1..].to_string())
- })
- })
- } else {
- Some("built-in".to_string())
- }
- }
-
- fn validate_plugin_blesh(
- _shell: &str,
- shell_process: &sysinfo::Process,
- ble_session_id: &str,
- ) -> Option<String> {
- ble_session_id
- .split('/')
- .nth(1)
- .and_then(|field| u32::from_str(field).ok())
- .filter(|&blesh_pid| blesh_pid == shell_process.pid().as_u32())
- .map(|_| "blesh".to_string())
- }
-
- pub(crate) fn plugins(shell: &str, shell_process: &sysinfo::Process) -> Vec<String> {
- // consider a different detection approach if there are plugins
- // that don't set shell vars
-
- enum PluginShellType {
- Any,
- Bash,
-
- // Note: these are currently unused
- #[expect(dead_code)]
- Zsh,
- #[expect(dead_code)]
- Fish,
- #[expect(dead_code)]
- Nushell,
- #[expect(dead_code)]
- Xonsh,
- }
-
- enum PluginProbeType {
- EnvironmentVariable(&'static str),
- InteractiveShellVariable(&'static str),
- }
-
- type PluginValidator = fn(&str, &sysinfo::Process, &str) -> Option<String>;
-
- let plugin_list: [(
- &str,
- PluginShellType,
- PluginProbeType,
- Option<PluginValidator>,
- ); 3] = [
- (
- "atuin",
- PluginShellType::Any,
- PluginProbeType::EnvironmentVariable("ATUIN_SESSION"),
- None,
- ),
- (
- "blesh",
- PluginShellType::Bash,
- PluginProbeType::EnvironmentVariable("BLE_SESSION_ID"),
- Some(Self::validate_plugin_blesh),
- ),
- (
- "bash-preexec",
- PluginShellType::Bash,
- PluginProbeType::InteractiveShellVariable("bash_preexec_imported"),
- None,
- ),
- ];
-
- plugin_list
- .into_iter()
- .filter(|(_, shell_type, _, _)| match shell_type {
- PluginShellType::Any => true,
- PluginShellType::Bash => shell.starts_with("bash") || shell == "sh",
- PluginShellType::Zsh => shell.starts_with("zsh"),
- PluginShellType::Fish => shell.starts_with("fish"),
- PluginShellType::Nushell => shell.starts_with("nu"),
- PluginShellType::Xonsh => shell.starts_with("xonsh"),
- })
- .filter_map(|(plugin, _, probe_type, validator)| -> Option<String> {
- match probe_type {
- PluginProbeType::EnvironmentVariable(env) => {
- env::var(env).ok().filter(|value| !value.is_empty())
- }
- PluginProbeType::InteractiveShellVariable(shellvar) => {
- ShellInfo::shellvar_exists(shell, shellvar).then_some(String::default())
- }
- }
- .and_then(|value| {
- validator.map_or_else(
- || Some(plugin.to_string()),
- |validator| validator(shell, shell_process, &value),
- )
- })
- })
- .collect()
- }
-
- pub(crate) fn new() -> Self {
- // TODO: rework to use crate::atuin_common::Shell
-
- let sys = System::new_all();
-
- let process = sys
- .process(get_current_pid().expect("Failed to get current PID"))
- .expect("Process with current pid does not exist");
-
- let parent = sys
- .process(process.parent().expect("Atuin running with no parent!"))
- .expect("Process with parent pid does not exist");
-
- let name = shell_name(Some(parent));
-
- let plugins = ShellInfo::plugins(name.as_str(), parent);
-
- let default = Shell::default_shell().unwrap_or(Shell::Unknown).to_string();
-
- let preexec = Self::detect_preexec_framework(name.as_str());
-
- Self {
- name,
- default,
- plugins,
- preexec,
- }
- }
-}
-
-#[derive(Debug, Serialize)]
-struct DiskInfo {
- pub(crate) name: String,
- pub(crate) filesystem: String,
-}
-
-#[derive(Debug, Serialize)]
-struct SystemInfo {
- pub(crate) os: String,
-
- pub(crate) arch: String,
-
- pub(crate) version: String,
- pub(crate) disks: Vec<DiskInfo>,
-}
-
-impl SystemInfo {
- pub(crate) fn new() -> Self {
- let disks = Disks::new_with_refreshed_list();
- let disks = disks
- .list()
- .iter()
- .map(|d| DiskInfo {
- name: d.name().to_os_string().into_string().unwrap(),
- filesystem: d.file_system().to_os_string().into_string().unwrap(),
- })
- .collect();
-
- Self {
- os: System::name().unwrap_or_else(|| "unknown".to_string()),
- arch: System::cpu_arch().unwrap_or_else(|| "unknown".to_string()),
- version: System::os_version().unwrap_or_else(|| "unknown".to_string()),
- disks,
- }
- }
-}
-
-#[derive(Debug, Serialize)]
-struct SyncInfo {
- pub(crate) auth_state: String,
- pub(crate) auto_sync: bool,
-
- pub(crate) last_sync: String,
-}
-
-impl SyncInfo {
- pub(crate) async fn new(settings: &Settings) -> Result<Self> {
- let has_cli_token = settings.have_sync_key().await?;
-
- let auth_state = if has_cli_token {
- "Self-hosted (authenticated)".into()
- } else {
- "Not authenticated".into()
- };
-
- Ok(Self {
- auth_state,
- auto_sync: settings.auto_sync,
- last_sync: Settings::last_sync()
- .await
- .map_or_else(|_| "no last sync".to_string(), |v| v.to_string()),
- })
- }
-}
-
-#[derive(Debug)]
-struct SettingPaths {
- db: String,
- record_store: String,
- key: String,
-}
-
-impl SettingPaths {
- pub(crate) fn new(settings: &Settings) -> Self {
- Self {
- db: settings.db_path.clone(),
- record_store: settings.record_store_path.clone(),
- key: settings.key_path.clone(),
- }
- }
-
- pub(crate) fn verify(&self) {
- let paths = vec![
- ("ATUIN_DB_PATH", &self.db),
- ("ATUIN_RECORD_STORE", &self.record_store),
- ("ATUIN_KEY", &self.key),
- ];
-
- for (path_env_var, path) in paths {
- if utils::broken_symlink(path) {
- eprintln!(
- "{path} (${path_env_var}) is a broken symlink. This may cause issues with Atuin."
- );
- }
- }
- }
-}
-
-#[derive(Debug, Serialize)]
-struct AtuinInfo {
- pub(crate) version: String,
- pub(crate) commit: String,
-
- /// Whether the main Atuin sync server is in use
- /// I'm just calling it Atuin Cloud for lack of a better name atm
- pub(crate) sync: Option<SyncInfo>,
-
- pub(crate) sqlite_version: String,
-
- #[serde(skip)] // probably unnecessary to expose this
- pub(crate) setting_paths: SettingPaths,
-}
-
-impl AtuinInfo {
- pub(crate) async fn new(settings: &Settings) -> Result<Self> {
- let logged_in = settings.have_sync_key().await?;
-
- let sync = if logged_in {
- Some(SyncInfo::new(settings).await?)
- } else {
- None
- };
-
- let sqlite_version = match Sqlite::new("sqlite::memory:", 0.1).await {
- Ok(db) => db
- .sqlite_version()
- .await
- .unwrap_or_else(|_| "unknown".to_string()),
- Err(_) => "error".to_string(),
- };
-
- Ok(Self {
- version: crate::VERSION.to_string(),
- commit: crate::SHA.to_string(),
- sync,
- sqlite_version,
- setting_paths: SettingPaths::new(settings),
- })
- }
-}
-
-#[derive(Debug, Serialize)]
-struct DoctorDump {
- pub(crate) atuin: AtuinInfo,
- pub(crate) shell: ShellInfo,
- pub(crate) system: SystemInfo,
-}
-
-impl DoctorDump {
- pub(crate) async fn new(settings: &Settings) -> Result<Self> {
- Ok(Self {
- atuin: AtuinInfo::new(settings).await?,
- shell: ShellInfo::new(),
- system: SystemInfo::new(),
- })
- }
-}
-
-fn checks(info: &DoctorDump) {
- println!(); // spacing
- //
- let zfs_error = "[Filesystem] ZFS is known to have some issues with SQLite. Atuin uses SQLite heavily. If you are having poor performance, there are some workarounds here: https://github.com/atuinsh/atuin/issues/952".bold().red();
- let bash_plugin_error = "[Shell] If you are using Bash, Atuin requires that either bash-preexec or ble.sh (>= 0.4) be installed. An older ble.sh may not be detected. so ignore this if you have ble.sh >= 0.4 set up! Read more here: https://docs.atuin.sh/guide/installation/#bash".bold().red();
- let blesh_integration_error = "[Shell] Atuin and ble.sh seem to be loaded in the session, but the integration does not seem to be working. Please check the setup in .bashrc.".bold().red();
-
- // ZFS: https://github.com/atuinsh/atuin/issues/952
- if info.system.disks.iter().any(|d| d.filesystem == "zfs") {
- println!("{zfs_error}");
- }
-
- info.atuin.setting_paths.verify();
-
- // Shell
- if info.shell.name == "bash" {
- if !info
- .shell
- .plugins
- .iter()
- .any(|p| p == "blesh" || p == "bash-preexec")
- {
- println!("{bash_plugin_error}");
- }
-
- if info.shell.plugins.iter().any(|plugin| plugin == "atuin")
- && info.shell.plugins.iter().any(|plugin| plugin == "blesh")
- && info.shell.preexec.as_ref().is_some_and(|val| val == "none")
- {
- println!("{blesh_integration_error}");
- }
- }
-}
-
-pub(crate) async fn run(settings: &Settings) -> Result<()> {
- println!("{}", "Atuin Doctor".bold());
- println!("Checking for diagnostics");
- let dump = DoctorDump::new(settings).await?;
-
- checks(&dump);
-
- let dump = serde_json::to_string_pretty(&dump)?;
-
- println!("\nPlease include the output below with any bug reports or issues\n");
- println!("{dump}");
-
- Ok(())
-}
diff --git a/crates/turtle/src/command/client/history.rs b/crates/turtle/src/command/client/history.rs
index e533759b..693098c0 100644
--- a/crates/turtle/src/command/client/history.rs
+++ b/crates/turtle/src/command/client/history.rs
@@ -21,7 +21,7 @@ use serde::Serialize;
use crate::atuin_daemon::history::{HistoryEventKind, TailHistoryReply};
use crate::atuin_client::{
- database::{Database, Sqlite, current_context},
+ database::{ClientSqlite, current_context},
encryption,
history::{History, store::HistoryStore},
record::sqlite_store::SqliteStore,
@@ -411,7 +411,7 @@ fn normalize_command_for_storage<'a>(command: &'a str, settings: &Settings) -> &
}
async fn handle_start(
- db: &impl Database,
+ db: &ClientSqlite,
settings: &Settings,
command: &str,
author: Option<&str>,
@@ -484,7 +484,7 @@ async fn handle_daemon_start(
#[expect(unused_variables)]
async fn handle_end(
- db: &impl Database,
+ db: &ClientSqlite,
store: SqliteStore,
history_store: HistoryStore,
settings: &Settings,
@@ -527,7 +527,7 @@ async fn handle_end(
db.update(&h).await?;
history_store.push(h).await?;
- if settings.should_sync().await? {
+ if settings.sync.should_sync().await? {
let (_, downloaded) =
record::sync::sync(settings, &store, &history_store.encryption_key).await?;
Settings::save_sync_time().await?;
@@ -564,7 +564,7 @@ pub(super) async fn start_history_entry(
}
let db_path = PathBuf::from(settings.db_path.as_str());
- let db = Sqlite::new(db_path, settings.local_timeout).await?;
+ let db = ClientSqlite::new(db_path, settings.local_timeout).await?;
handle_start(&db, settings, command, author, intent).await
}
@@ -582,7 +582,7 @@ pub(super) async fn end_history_entry(
let db_path = PathBuf::from(settings.db_path.as_str());
let record_store_path = PathBuf::from(settings.record_store_path.as_str());
- let db = Sqlite::new(db_path, settings.local_timeout).await?;
+ let db = ClientSqlite::new(db_path, settings.local_timeout).await?;
let store = SqliteStore::new(record_store_path, settings.local_timeout).await?;
let encryption_key: [u8; 32] = encryption::load_key(settings)
@@ -922,7 +922,7 @@ impl Cmd {
#[expect(clippy::too_many_arguments)]
#[expect(clippy::fn_params_excessive_bools)]
async fn handle_list(
- db: &impl Database,
+ db: &ClientSqlite,
settings: &Settings,
context: crate::atuin_client::database::Context,
session: bool,
@@ -964,7 +964,7 @@ impl Cmd {
}
async fn handle_prune(
- db: &impl Database,
+ db: &ClientSqlite,
settings: &Settings,
store: SqliteStore,
context: crate::atuin_client::database::Context,
@@ -1017,7 +1017,7 @@ impl Cmd {
}
async fn handle_dedup(
- db: &impl Database,
+ db: &ClientSqlite,
settings: &Settings,
store: SqliteStore,
before: i64,
@@ -1119,7 +1119,7 @@ impl Cmd {
let db_path = PathBuf::from(settings.db_path.as_str());
let record_store_path = PathBuf::from(settings.record_store_path.as_str());
- let db = Sqlite::new(db_path, settings.local_timeout).await?;
+ let db = ClientSqlite::new(db_path, settings.local_timeout).await?;
let store = SqliteStore::new(record_store_path, settings.local_timeout).await?;
let encryption_key: [u8; 32] = encryption::load_key(settings)
@@ -1233,7 +1233,7 @@ mod tests {
#[tokio::test]
async fn handle_start_saves_trimmed_command() {
- let db = Sqlite::new("sqlite::memory:", 2.0).await.unwrap();
+ let db = ClientSqlite::new("sqlite::memory:", 2.0).await.unwrap();
let settings = Settings::utc();
handle_start(&db, &settings, "ls \t", None, None)
@@ -1251,7 +1251,7 @@ mod tests {
#[tokio::test]
async fn handle_start_can_keep_trailing_whitespace() {
- let db = Sqlite::new("sqlite::memory:", 2.0).await.unwrap();
+ let db = ClientSqlite::new("sqlite::memory:", 2.0).await.unwrap();
let settings = Settings {
strip_trailing_whitespace: false,
..Settings::utc()
diff --git a/crates/turtle/src/command/client/import.rs b/crates/turtle/src/command/client/import.rs
deleted file mode 100644
index 3ec524d2..00000000
--- a/crates/turtle/src/command/client/import.rs
+++ /dev/null
@@ -1,186 +0,0 @@
-use std::env;
-
-use async_trait::async_trait;
-use clap::Parser;
-use eyre::Result;
-use indicatif::ProgressBar;
-
-use crate::atuin_client::{
- database::Database,
- history::History,
- import::{
- Importer, Loader, bash::Bash, fish::Fish, nu::Nu, nu_histdb::NuHistDb,
- powershell::PowerShell, replxx::Replxx, resh::Resh, xonsh::Xonsh,
- xonsh_sqlite::XonshSqlite, zsh::Zsh, zsh_histdb::ZshHistDb,
- },
-};
-
-#[derive(Parser, Debug)]
-#[command(infer_subcommands = true)]
-pub(crate) enum Cmd {
- /// Import history for the current shell
- Auto,
-
- /// Import history from the zsh history file
- Zsh,
- /// Import history from the zsh history file
- ZshHistDb,
- /// Import history from the bash history file
- Bash,
- /// Import history from the replxx history file
- Replxx,
- /// Import history from the resh history file
- Resh,
- /// Import history from the fish history file
- Fish,
- /// Import history from the nu history file
- Nu,
- /// Import history from the nu history file
- NuHistDb,
- /// Import history from xonsh json files
- Xonsh,
- /// Import history from xonsh sqlite db
- XonshSqlite,
- /// Import history from the powershell history file
- Powershell,
-}
-
-const BATCH_SIZE: usize = 100;
-
-impl Cmd {
- #[expect(clippy::cognitive_complexity)]
- pub(crate) async fn run<DB: Database>(&self, db: &DB) -> Result<()> {
- println!(" Atuin ");
- println!("======================");
- println!(" \u{1f30d} ");
- println!(" \u{1f418}\u{1f418}\u{1f418}\u{1f418} ");
- println!(" \u{1f422} ");
- println!("======================");
- println!("Importing history...");
-
- match self {
- Self::Auto => {
- if cfg!(windows) {
- return if env::var("PSModulePath").is_ok() {
- println!("Detected PowerShell");
- import::<PowerShell, DB>(db).await
- } else {
- println!("Could not detect the current shell.");
- println!("Please run atuin import <SHELL>.");
- println!("To view a list of shells, run atuin import.");
- Ok(())
- };
- }
-
- // $XONSH_HISTORY_BACKEND isn't always set, but $XONSH_HISTORY_FILE is
- let xonsh_histfile =
- env::var("XONSH_HISTORY_FILE").unwrap_or_else(|_| String::new());
- let shell = env::var("SHELL").unwrap_or_else(|_| String::from("NO_SHELL"));
-
- if xonsh_histfile.to_lowercase().ends_with(".json") {
- println!("Detected Xonsh");
- import::<Xonsh, DB>(db).await
- } else if xonsh_histfile.to_lowercase().ends_with(".sqlite") {
- println!("Detected Xonsh (SQLite backend)");
- import::<XonshSqlite, DB>(db).await
- } else if shell.ends_with("/zsh") {
- if ZshHistDb::histpath().is_ok() {
- println!(
- "Detected Zsh-HistDb, using :{}",
- ZshHistDb::histpath().unwrap().to_str().unwrap()
- );
- import::<ZshHistDb, DB>(db).await
- } else {
- println!("Detected ZSH");
- import::<Zsh, DB>(db).await
- }
- } else if shell.ends_with("/fish") {
- println!("Detected Fish");
- import::<Fish, DB>(db).await
- } else if shell.ends_with("/bash") {
- println!("Detected Bash");
- import::<Bash, DB>(db).await
- } else if shell.ends_with("/nu") {
- if NuHistDb::histpath().is_ok() {
- println!(
- "Detected Nu-HistDb, using :{}",
- NuHistDb::histpath().unwrap().to_str().unwrap()
- );
- import::<NuHistDb, DB>(db).await
- } else {
- println!("Detected Nushell");
- import::<Nu, DB>(db).await
- }
- } else if shell.ends_with("/pwsh") {
- println!("Detected PowerShell");
- import::<PowerShell, DB>(db).await
- } else {
- println!("cannot import {shell} history");
- Ok(())
- }
- }
-
- Self::Zsh => import::<Zsh, DB>(db).await,
- Self::ZshHistDb => import::<ZshHistDb, DB>(db).await,
- Self::Bash => import::<Bash, DB>(db).await,
- Self::Replxx => import::<Replxx, DB>(db).await,
- Self::Resh => import::<Resh, DB>(db).await,
- Self::Fish => import::<Fish, DB>(db).await,
- Self::Nu => import::<Nu, DB>(db).await,
- Self::NuHistDb => import::<NuHistDb, DB>(db).await,
- Self::Xonsh => import::<Xonsh, DB>(db).await,
- Self::XonshSqlite => import::<XonshSqlite, DB>(db).await,
- Self::Powershell => import::<PowerShell, DB>(db).await,
- }
- }
-}
-
-pub(crate) struct HistoryImporter<'db, DB: Database> {
- pb: ProgressBar,
- buf: Vec<History>,
- db: &'db DB,
-}
-
-impl<'db, DB: Database> HistoryImporter<'db, DB> {
- fn new(db: &'db DB, len: usize) -> Self {
- Self {
- pb: ProgressBar::new(len as u64),
- buf: Vec::with_capacity(BATCH_SIZE),
- db,
- }
- }
-
- async fn flush(self) -> Result<()> {
- if !self.buf.is_empty() {
- self.db.save_bulk(&self.buf).await?;
- }
- self.pb.finish();
- Ok(())
- }
-}
-
-#[async_trait]
-impl<DB: Database> Loader for HistoryImporter<'_, DB> {
- async fn push(&mut self, hist: History) -> Result<()> {
- self.pb.inc(1);
- self.buf.push(hist);
- if self.buf.len() == self.buf.capacity() {
- self.db.save_bulk(&self.buf).await?;
- self.buf.clear();
- }
- Ok(())
- }
-}
-
-async fn import<I: Importer + Send, DB: Database>(db: &DB) -> Result<()> {
- println!("Importing history from {}", I::NAME);
-
- let mut importer = I::new().await?;
- let len = importer.entries().await.unwrap();
- let mut loader = HistoryImporter::new(db, len);
- importer.load(&mut loader).await?;
- loader.flush().await?;
-
- println!("Import complete!");
- Ok(())
-}
diff --git a/crates/turtle/src/command/client/info.rs b/crates/turtle/src/command/client/info.rs
index fc944987..49c92193 100644
--- a/crates/turtle/src/command/client/info.rs
+++ b/crates/turtle/src/command/client/info.rs
@@ -1,8 +1,9 @@
use crate::atuin_client::settings::Settings;
-
use crate::{SHA, VERSION};
-pub(crate) fn run(settings: &Settings) {
+use eyre::Result;
+
+pub(crate) fn run(settings: &Settings) -> Result<()> {
let config = crate::atuin_common::utils::config_dir();
let mut config_file = config.clone();
config_file.push("config.toml");
@@ -14,7 +15,7 @@ pub(crate) fn run(settings: &Settings) {
config_file.to_string_lossy(),
sever_config.to_string_lossy(),
settings.db_path,
- settings.key_path,
+ settings.sync.encryption_key()?,
settings.meta.db_path
);
@@ -28,4 +29,6 @@ pub(crate) fn run(settings: &Settings) {
let print_out = format!("{config_paths}\n\n{env_vars}\n\n{general_info}");
println!("{print_out}");
+
+ Ok(())
}
diff --git a/crates/turtle/src/command/client/init.rs b/crates/turtle/src/command/client/init.rs
index 0643cb73..0cdcd425 100644
--- a/crates/turtle/src/command/client/init.rs
+++ b/crates/turtle/src/command/client/init.rs
@@ -89,7 +89,7 @@ $env.config = (
}
}
- fn static_init(&self, settings: &Settings) {
+ fn static_init(&self) {
match self.shell {
Shell::Zsh => {
zsh::init_static(self.disable_up_arrow, self.disable_ctrl_r);
@@ -119,6 +119,6 @@ $env.config = (
);
}
- self.static_init(settings);
+ self.static_init();
}
}
diff --git a/crates/turtle/src/command/client/search.rs b/crates/turtle/src/command/client/search.rs
index 72112084..962e6b1e 100644
--- a/crates/turtle/src/command/client/search.rs
+++ b/crates/turtle/src/command/client/search.rs
@@ -1,12 +1,12 @@
use std::fs::File;
use std::io::{IsTerminal as _, Write, stderr, stdout};
+use crate::atuin_client::database::ClientSqlite;
use crate::atuin_common::utils::{self, Escapable as _};
use clap::Parser;
use eyre::Result;
use crate::atuin_client::{
- database::Database,
database::{OptFilters, current_context},
encryption,
history::{History, store::HistoryStore},
@@ -157,7 +157,7 @@ impl Cmd {
#[expect(clippy::too_many_lines)]
pub(crate) async fn run(
self,
- db: impl Database,
+ db: ClientSqlite,
settings: &mut Settings,
store: SqliteStore,
theme: &Theme,
@@ -253,7 +253,6 @@ impl Cmd {
offset: self.offset,
reverse: self.reverse,
include_duplicates: self.include_duplicates,
- authors: self.author.clone().unwrap_or_default(),
};
let mut entries =
@@ -310,7 +309,7 @@ async fn run_non_interactive(
settings: &Settings,
filter_options: OptFilters,
query: &[String],
- db: &impl Database,
+ db: &ClientSqlite,
) -> Result<Vec<History>> {
let dir = if filter_options.cwd.as_deref() == Some(".") {
Some(utils::get_current_dir())
diff --git a/crates/turtle/src/command/client/search/engines.rs b/crates/turtle/src/command/client/search/engines.rs
index d6335a38..a84c4798 100644
--- a/crates/turtle/src/command/client/search/engines.rs
+++ b/crates/turtle/src/command/client/search/engines.rs
@@ -1,9 +1,9 @@
-use async_trait::async_trait;
use crate::atuin_client::{
- database::{Context, Database, OptFilters},
- history::{AUTHOR_FILTER_ALL_USER, History, HistoryId},
+ database::{ClientSqlite, Context, OptFilters},
+ history::{History, HistoryId},
settings::{FilterMode, SearchMode, Settings},
};
+use async_trait::async_trait;
use eyre::Result;
use super::cursor::Cursor;
@@ -67,10 +67,10 @@ pub(crate) trait SearchEngine: Send + Sync + 'static {
async fn full_query(
&mut self,
state: &SearchState,
- db: &mut dyn Database,
+ db: &mut ClientSqlite,
) -> Result<Vec<History>>;
- async fn query(&mut self, state: &SearchState, db: &mut dyn Database) -> Result<Vec<History>> {
+ async fn query(&mut self, state: &SearchState, db: &mut ClientSqlite) -> Result<Vec<History>> {
if state.input.as_str().is_empty() {
Ok(db
.search(
@@ -80,7 +80,6 @@ pub(crate) trait SearchEngine: Send + Sync + 'static {
"",
OptFilters {
limit: Some(200),
- authors: vec![AUTHOR_FILTER_ALL_USER.to_string()],
..Default::default()
},
)
diff --git a/crates/turtle/src/command/client/search/engines/daemon.rs b/crates/turtle/src/command/client/search/engines/daemon.rs
index df5ab9f8..55b3c6f2 100644
--- a/crates/turtle/src/command/client/search/engines/daemon.rs
+++ b/crates/turtle/src/command/client/search/engines/daemon.rs
@@ -1,6 +1,6 @@
use crate::atuin_client::{
- database::{Database, OptFilters},
- history::{AUTHOR_FILTER_ALL_USER, History},
+ database::{ClientSqlite, OptFilters},
+ history::History,
settings::{SearchMode, Settings},
};
use crate::atuin_daemon::client::{DaemonClientErrorKind, SearchClient, classify_error};
@@ -75,7 +75,7 @@ impl Search {
async fn fallback_to_db_search(
&self,
state: &SearchState,
- db: &dyn Database,
+ db: &ClientSqlite,
) -> Result<Vec<History>> {
let results = db
.search(
@@ -85,7 +85,6 @@ impl Search {
state.input.as_str(),
OptFilters {
limit: Some(200),
- authors: vec![AUTHOR_FILTER_ALL_USER.to_string()],
..Default::default()
},
)
@@ -95,7 +94,7 @@ impl Search {
}
#[instrument(skip_all, level = Level::TRACE, name = "hydrate_from_db", fields(count = ids.len()))]
- async fn hydrate_from_db(&self, db: &dyn Database, ids: &[String]) -> Result<Vec<History>> {
+ async fn hydrate_from_db(&self, db: &ClientSqlite, ids: &[String]) -> Result<Vec<History>> {
let placeholders: Vec<String> = ids.iter().map(|id| format!("'{id}'")).collect();
let sql_query = format!(
"SELECT * FROM history WHERE id IN ({}) ORDER BY timestamp DESC",
@@ -111,7 +110,7 @@ impl SearchEngine for Search {
async fn full_query(
&mut self,
state: &SearchState,
- db: &mut dyn Database,
+ db: &mut ClientSqlite,
) -> Result<Vec<History>> {
let query = state.input.as_str().to_string();
diff --git a/crates/turtle/src/command/client/search/engines/db.rs b/crates/turtle/src/command/client/search/engines/db.rs
index 86917a02..e6657b17 100644
--- a/crates/turtle/src/command/client/search/engines/db.rs
+++ b/crates/turtle/src/command/client/search/engines/db.rs
@@ -1,12 +1,10 @@
use super::{SearchEngine, SearchState};
-use async_trait::async_trait;
use crate::atuin_client::{
- database::Database,
- database::OptFilters,
- database::{QueryToken, QueryTokenizer},
- history::{AUTHOR_FILTER_ALL_USER, History},
+ database::{ClientSqlite, OptFilters, QueryToken, QueryTokenizer},
+ history::History,
settings::SearchMode,
};
+use async_trait::async_trait;
use eyre::Result;
use norm::Metric;
use norm::fzf::{FzfParser, FzfV2};
@@ -21,7 +19,7 @@ impl SearchEngine for Search {
async fn full_query(
&mut self,
state: &SearchState,
- db: &mut dyn Database,
+ db: &mut ClientSqlite,
) -> Result<Vec<History>> {
let results = db
.search(
@@ -31,7 +29,6 @@ impl SearchEngine for Search {
state.input.as_str(),
OptFilters {
limit: Some(200),
- authors: vec![AUTHOR_FILTER_ALL_USER.to_string()],
..Default::default()
},
)
diff --git a/crates/turtle/src/command/client/search/engines/skim.rs b/crates/turtle/src/command/client/search/engines/skim.rs
index fe2bdea3..a6a77573 100644
--- a/crates/turtle/src/command/client/search/engines/skim.rs
+++ b/crates/turtle/src/command/client/search/engines/skim.rs
@@ -1,18 +1,13 @@
use std::path::Path;
+use crate::atuin_client::{database::ClientSqlite, history::History, settings::FilterMode};
use async_trait::async_trait;
-use crate::atuin_client::{
- database::Database,
- history::{History, is_known_agent},
- settings::FilterMode,
-};
use eyre::Result;
use fuzzy_matcher::{FuzzyMatcher, skim::SkimMatcherV2};
use itertools::Itertools;
use time::OffsetDateTime;
use tokio::task::yield_now;
use tracing::{Level, instrument, warn};
-use uuid;
use super::{SearchEngine, SearchState};
@@ -36,7 +31,7 @@ impl SearchEngine for Search {
async fn full_query(
&mut self,
state: &SearchState,
- db: &mut dyn Database,
+ db: &mut ClientSqlite,
) -> Result<Vec<History>> {
if self.all_history.is_empty() {
self.all_history = load_all_history(db).await;
@@ -56,7 +51,7 @@ impl SearchEngine for Search {
}
#[instrument(skip_all, level = Level::TRACE, name = "load_all_history")]
-async fn load_all_history(db: &dyn Database) -> Vec<(History, i32)> {
+async fn load_all_history(db: &ClientSqlite) -> Vec<(History, i32)> {
db.all_with_count().await.unwrap()
}
@@ -76,9 +71,7 @@ async fn fuzzy_search(
if i % 256 == 0 {
yield_now().await;
}
- if is_known_agent(&history.author) {
- continue;
- }
+
let context = &state.context;
let git_root = context
.git_root
diff --git a/crates/turtle/src/command/client/search/interactive.rs b/crates/turtle/src/command/client/search/interactive.rs
index 380fc33b..1d067e50 100644
--- a/crates/turtle/src/command/client/search/interactive.rs
+++ b/crates/turtle/src/command/client/search/interactive.rs
@@ -6,7 +6,10 @@ use std::{
#[cfg(unix)]
use std::io::Read as _;
-use crate::atuin_common::{shell::Shell, utils::Escapable as _};
+use crate::{
+ atuin_client::database::ClientSqlite,
+ atuin_common::{shell::Shell, utils::Escapable as _},
+};
use eyre::Result;
use time::OffsetDateTime;
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
@@ -17,7 +20,7 @@ use super::{
history_list::{HistoryList, ListState},
};
use crate::atuin_client::{
- database::{Context, Database, current_context},
+ database::{Context, current_context},
history::{History, HistoryId, HistoryStats, store::HistoryStore},
settings::{
CursorStyle, ExitMode, FilterMode, KeymapMode, PreviewStrategy, SearchMode, Settings,
@@ -149,7 +152,7 @@ struct StyleState {
impl State {
async fn query_results(
&mut self,
- db: &mut dyn Database,
+ db: &mut ClientSqlite,
smart_sort: bool,
) -> Result<Vec<History>> {
let results = self.engine.query(&self.search, db).await?;
@@ -1550,7 +1553,7 @@ fn compute_popup_placement(
pub(crate) async fn history(
query: &[String],
settings: &Settings,
- mut db: impl Database,
+ mut db: ClientSqlite,
history_store: &HistoryStore,
theme: &Theme,
) -> Result<String> {
diff --git a/crates/turtle/src/command/client/server.rs b/crates/turtle/src/command/client/server.rs
index def1dfb3..d821d6f8 100644
--- a/crates/turtle/src/command/client/server.rs
+++ b/crates/turtle/src/command/client/server.rs
@@ -24,7 +24,6 @@ pub(crate) enum Cmd {
}
impl Cmd {
- #[expect(clippy::too_many_lines)]
pub(crate) async fn run(self) -> Result<()> {
match self {
Cmd::Start { host, port } => {
diff --git a/crates/turtle/src/command/client/stats.rs b/crates/turtle/src/command/client/stats.rs
index 98401cd3..17432bb2 100644
--- a/crates/turtle/src/command/client/stats.rs
+++ b/crates/turtle/src/command/client/stats.rs
@@ -3,11 +3,8 @@ use eyre::Result;
use interim::parse_date_string;
use time::{Duration, OffsetDateTime, Time};
-use crate::atuin_client::{
- database::{Database, current_context},
- settings::Settings,
- theme::Theme,
-};
+use crate::atuin_client::database::ClientSqlite;
+use crate::atuin_client::{database::current_context, settings::Settings, theme::Theme};
use crate::atuin_history::stats::{compute, pretty_print};
@@ -39,7 +36,12 @@ pub(crate) struct Cmd {
}
impl Cmd {
- pub(crate) async fn run(&self, db: &impl Database, settings: &Settings, theme: &Theme) -> Result<()> {
+ pub(crate) async fn run(
+ &self,
+ db: &ClientSqlite,
+ settings: &Settings,
+ theme: &Theme,
+ ) -> Result<()> {
let context = current_context().await?;
let words = if self.period.is_empty() {
String::from("all")
diff --git a/crates/turtle/src/command/client/store.rs b/crates/turtle/src/command/client/store.rs
index 3e9355b5..347c4bee 100644
--- a/crates/turtle/src/command/client/store.rs
+++ b/crates/turtle/src/command/client/store.rs
@@ -2,9 +2,7 @@ use clap::Subcommand;
use eyre::Result;
use crate::atuin_client::{
- database::Database,
- record::{sqlite_store::SqliteStore, store::Store},
- settings::Settings,
+ database::ClientSqlite, record::{sqlite_store::SqliteStore, store::Store}, settings::Settings
};
use itertools::Itertools;
use time::{OffsetDateTime, UtcOffset};
@@ -51,7 +49,7 @@ impl Cmd {
pub(crate) async fn run(
&self,
settings: &Settings,
- database: &dyn Database,
+ database: &ClientSqlite,
store: SqliteStore,
) -> Result<()> {
match self {
diff --git a/crates/turtle/src/command/client/store/pull.rs b/crates/turtle/src/command/client/store/pull.rs
index 6b709a64..f2e628d6 100644
--- a/crates/turtle/src/command/client/store/pull.rs
+++ b/crates/turtle/src/command/client/store/pull.rs
@@ -2,12 +2,7 @@ use clap::Args;
use eyre::Result;
use crate::atuin_client::{
- database::Database,
- encryption::load_key,
- record::store::Store,
- record::sync::Operation,
- record::{sqlite_store::SqliteStore, sync},
- settings::Settings,
+ database::ClientSqlite, encryption::load_key, record::{sqlite_store::SqliteStore, store::Store, sync::{self, Operation}}, settings::Settings
};
#[derive(Args, Debug)]
@@ -32,7 +27,7 @@ impl Pull {
&self,
settings: &Settings,
store: SqliteStore,
- db: &dyn Database,
+ db: &ClientSqlite,
) -> Result<()> {
if self.force {
println!("Forcing local overwrite!");
diff --git a/crates/turtle/src/command/client/store/push.rs b/crates/turtle/src/command/client/store/push.rs
index 30177dbd..beec613c 100644
--- a/crates/turtle/src/command/client/store/push.rs
+++ b/crates/turtle/src/command/client/store/push.rs
@@ -1,6 +1,6 @@
use crate::atuin_common::record::HostId;
use clap::Args;
-use eyre::Result;
+use eyre::{OptionExt, Result};
use uuid::Uuid;
use crate::atuin_client::{
@@ -42,11 +42,12 @@ impl Push {
println!("Clearing remote store");
let client = Client::new(
- &settings.sync_address,
- settings.sync_auth().await?.into_auth_token()?,
+ &settings.sync.address,
settings.network_connect_timeout,
- settings.network_timeout * 10, // we may be deleting a lot of data... so up the
- // timeout
+ // we may be deleting a lot of data... so increase the
+ // timeout
+ settings.network_timeout * 10,
+ settings.sync.user_id()?.ok_or_eyre("no sync user-id")?,
)
.expect("failed to create client");
diff --git a/crates/turtle/src/command/client/store/rebuild.rs b/crates/turtle/src/command/client/store/rebuild.rs
index 0959b74e..bee1aa05 100644
--- a/crates/turtle/src/command/client/store/rebuild.rs
+++ b/crates/turtle/src/command/client/store/rebuild.rs
@@ -5,7 +5,7 @@ use eyre::{Result, bail};
use crate::command::client::daemon as daemon_cmd;
use crate::atuin_client::{
- database::Database, encryption, history::store::HistoryStore,
+ database::ClientSqlite, encryption, history::store::HistoryStore,
record::sqlite_store::SqliteStore, settings::Settings,
};
@@ -19,7 +19,7 @@ impl Rebuild {
&self,
settings: &Settings,
store: SqliteStore,
- database: &dyn Database,
+ database: &ClientSqlite,
) -> Result<()> {
// keep it as a string and not an enum atm
// would be super cool to build this dynamically in the future
@@ -41,7 +41,7 @@ impl Rebuild {
&self,
settings: &Settings,
store: SqliteStore,
- database: &dyn Database,
+ database: &ClientSqlite,
) -> Result<()> {
let encryption_key: [u8; 32] = encryption::load_key(settings)?.into();
diff --git a/crates/turtle/src/command/client/store/rekey.rs b/crates/turtle/src/command/client/store/rekey.rs
index 3472222f..b99fb16a 100644
--- a/crates/turtle/src/command/client/store/rekey.rs
+++ b/crates/turtle/src/command/client/store/rekey.rs
@@ -32,9 +32,15 @@ impl Rekey {
store.re_encrypt(&current_key, &new_key).await?;
- println!("Store rewritten. Saving new key");
- let mut file = File::create(settings.key_path.clone()).await?;
- file.write_all(key.as_bytes()).await?;
+ if let Some(key_path) = settings.sync.encryption_key_path.as_ref() {
+ println!("Store rewritten. Saving new key");
+ let mut file = File::create(key_path).await?;
+ file.write_all(key.as_bytes()).await?;
+ } else {
+ println!(
+ "No key-path (settings.sync.encryption_key_path) set in config, will not save new key."
+ );
+ }
Ok(())
}
diff --git a/crates/turtle/src/command/client/sync.rs b/crates/turtle/src/command/client/sync.rs
index 7adf90ed..84b74cc1 100644
--- a/crates/turtle/src/command/client/sync.rs
+++ b/crates/turtle/src/command/client/sync.rs
@@ -1,12 +1,12 @@
use clap::Subcommand;
use eyre::{Result, WrapErr};
+use serde_json::json;
-use crate::atuin_client::{
- database::Database,
- encryption,
- history::store::HistoryStore,
- record::{sqlite_store::SqliteStore, store::Store, sync},
- settings::Settings,
+use crate::{
+ atuin_client::{
+ database::ClientSqlite, encryption, history::store::HistoryStore, record::{sqlite_store::SqliteStore, store::Store, sync}, settings::Settings
+ },
+ atuin_common::utils,
};
mod status;
@@ -15,14 +15,14 @@ mod status;
#[command(infer_subcommands = true)]
pub(crate) enum Cmd {
/// Sync with the configured server
- Sync {
+ Perform {
/// Force re-download everything
#[arg(long, short)]
force: bool,
},
- /// Print the encryption key for transfer to another machine
- Key {},
+ /// Print (or generate) the encryption key and user id for transfer to another machine
+ KeyAndId {},
/// Display the sync status
Status,
@@ -32,18 +32,28 @@ impl Cmd {
pub(crate) async fn run(
self,
settings: Settings,
- db: &impl Database,
+ db: &ClientSqlite,
store: SqliteStore,
) -> Result<()> {
match self {
- Self::Sync { force } => run(&settings, force, db, store).await,
+ Self::Perform { force } => run(&settings, force, db, store).await,
Self::Status => status::run(&settings).await,
- Self::Key {} => {
+ Self::KeyAndId {} => {
use crate::atuin_client::encryption::{encode_key, load_key};
+
let key = load_key(&settings).wrap_err("could not load encryption key")?;
+ let user_id = settings
+ .sync
+ .user_id()
+ .wrap_err("Failed to load user-id")?
+ .unwrap_or_else(utils::uuid_v7);
+
+ let key = encode_key(&key).wrap_err("could not encode encryption key")?;
+
+ let json = serde_json::to_string_pretty(&json!({ "key": key, "user_id": user_id }))
+ .expect("Will always be formattable");
- let encode = encode_key(&key).wrap_err("could not encode encryption key")?;
- println!("{encode}");
+ println!("{json}");
Ok(())
}
@@ -54,7 +64,7 @@ impl Cmd {
async fn run(
settings: &Settings,
force: bool,
- db: &impl Database,
+ db: &ClientSqlite,
store: SqliteStore,
) -> Result<()> {
let encryption_key: [u8; 32] = encryption::load_key(settings)
diff --git a/crates/turtle/src/command/client/sync/status.rs b/crates/turtle/src/command/client/sync/status.rs
index 27b10dbd..e75171eb 100644
--- a/crates/turtle/src/command/client/sync/status.rs
+++ b/crates/turtle/src/command/client/sync/status.rs
@@ -1,36 +1,24 @@
-use crate::atuin_client::{api_client, settings::Settings};
+use crate::atuin_client::settings::Settings;
use crate::{SHA, VERSION};
use colored::Colorize;
use eyre::{Result, bail};
pub(crate) async fn run(settings: &Settings) -> Result<()> {
- if !settings.have_sync_key().await? {
- bail!("You are not logged in to a sync server - cannot show sync status");
- }
-
- let client = api_client::Client::new(
- &settings.sync_address,
- settings.sync_auth().await?.into_auth_token()?,
- settings.network_connect_timeout,
- settings.network_timeout,
- )?;
-
- let me = client.me().await?;
- let last_sync = Settings::last_sync().await?;
+ if let Some(me) = settings.sync.user_id()? {
+ let last_sync = Settings::last_sync().await?;
- println!("Atuin v{VERSION} - Build rev {SHA}\n");
+ println!("Atuin v{VERSION} - Build rev {SHA}\n");
- println!("{}", "[Local]".green());
-
- if settings.auto_sync {
- println!("Sync frequency: {}", settings.sync_frequency);
+ println!("{}", "[Local]".green());
+ println!("Sync frequency: {}", settings.sync.frequency);
println!("Last sync: {}", last_sync.to_offset(settings.timezone.0));
- }
+ println!("Auto sync: {}", settings.sync.auto);
- if settings.auto_sync {
println!("{}", "[Remote]".green());
- println!("Address: {}", settings.sync_address);
- println!("Username: {}", me.username);
+ println!("Address: {}", settings.sync.address);
+ println!("User id: {}", me);
+ } else {
+ bail!("You are not logged in to a sync server - cannot show sync status");
}
Ok(())
diff --git a/crates/turtle/src/command/client/wrapped.rs b/crates/turtle/src/command/client/wrapped.rs
index 5e41657e..d502d3ec 100644
--- a/crates/turtle/src/command/client/wrapped.rs
+++ b/crates/turtle/src/command/client/wrapped.rs
@@ -3,7 +3,8 @@ use eyre::Result;
use std::collections::{HashMap, HashSet};
use time::{Date, Duration, Month, OffsetDateTime, Time};
-use crate::atuin_client::{database::Database, settings::Settings, theme::Theme};
+use crate::atuin_client::database::ClientSqlite;
+use crate::atuin_client::{settings::Settings, theme::Theme};
use crate::atuin_history::stats::{Stats, compute};
@@ -268,7 +269,7 @@ fn print_fun_facts(wrapped_stats: &WrappedStats, stats: &Stats, year: i32) {
pub(crate) async fn run(
year: Option<i32>,
- db: &impl Database,
+ db: &ClientSqlite,
settings: &Settings,
theme: &Theme,
) -> Result<()> {