aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-06-10 22:26:10 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-06-10 22:26:10 +0200
commit989f01ad230423c5a5105d6c9ff8580020e902ed (patch)
treeb750d06a9a39dcc76a1da7898b0748b0dad500cf
parentchore: Remove some unused rust code (diff)
downloadatuin-989f01ad230423c5a5105d6c9ff8580020e902ed.zip
chore: Remove more useless code
-rw-r--r--crates/atuin-client/Cargo.toml3
-rw-r--r--crates/atuin-client/src/auth.rs269
-rw-r--r--crates/atuin-client/src/hub.rs304
-rw-r--r--crates/atuin-client/src/lib.rs2
-rw-r--r--crates/atuin-client/src/logout.rs1
-rw-r--r--crates/atuin-client/src/meta.rs21
-rw-r--r--crates/atuin-client/src/settings.rs213
-rw-r--r--crates/atuin-client/src/settings/dotfiles.rs7
-rw-r--r--crates/atuin-client/src/settings/kv.rs17
-rw-r--r--crates/atuin-client/src/settings/scripts.rs17
-rw-r--r--crates/atuin/src/command/client.rs12
-rw-r--r--crates/atuin/src/command/client/account.rs7
-rw-r--r--crates/atuin/src/command/client/account/delete.rs1
-rw-r--r--crates/atuin/src/command/client/account/link.rs41
-rw-r--r--crates/atuin/src/command/client/account/login.rs100
-rw-r--r--crates/atuin/src/command/client/account/register.rs153
-rw-r--r--crates/atuin/src/command/client/doctor.rs15
-rw-r--r--crates/atuin/src/command/client/init.rs36
-rw-r--r--crates/atuin/src/command/client/init/bash.rs7
-rw-r--r--crates/atuin/src/command/client/init/fish.rs11
-rw-r--r--crates/atuin/src/command/client/init/powershell.rs6
-rw-r--r--crates/atuin/src/command/client/init/xonsh.rs7
-rw-r--r--crates/atuin/src/command/client/init/zsh.rs11
-rw-r--r--crates/atuin/src/command/client/kv.rs138
-rw-r--r--crates/atuin/src/command/client/search/interactive.rs34
-rw-r--r--crates/atuin/src/command/client/sync.rs2
-rw-r--r--crates/atuin/src/sync.rs6
27 files changed, 59 insertions, 1382 deletions
diff --git a/crates/atuin-client/Cargo.toml b/crates/atuin-client/Cargo.toml
index 1b007f2e..b5ac49a4 100644
--- a/crates/atuin-client/Cargo.toml
+++ b/crates/atuin-client/Cargo.toml
@@ -13,9 +13,8 @@ repository = { workspace = true }
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[features]
-default = ["sync", "hub", "daemon"]
+default = ["sync", "daemon"]
sync = ["urlencoding", "reqwest", "sha2", "hex"]
-hub = ["reqwest"]
daemon = []
check-update = []
diff --git a/crates/atuin-client/src/auth.rs b/crates/atuin-client/src/auth.rs
index 8ea4b8ab..1031c11f 100644
--- a/crates/atuin-client/src/auth.rs
+++ b/crates/atuin-client/src/auth.rs
@@ -1,13 +1,9 @@
use async_trait::async_trait;
use eyre::{Context, Result, bail};
-use reqwest::{StatusCode, Url, header::USER_AGENT};
-use serde::Deserialize;
+use reqwest::{Url, header::USER_AGENT};
use atuin_common::{
- api::{
- ATUIN_CARGO_VERSION, ATUIN_HEADER_VERSION, ChangePasswordRequest, LoginRequest,
- LoginResponse, RegisterResponse,
- },
+ api::{ATUIN_CARGO_VERSION, ATUIN_HEADER_VERSION, ChangePasswordRequest, LoginRequest},
tls::ensure_crypto_provider,
};
@@ -74,20 +70,12 @@ pub trait AuthClient: Send + Sync {
/// Resolve the appropriate [`AuthClient`] for the current settings.
pub async fn auth_client(settings: &Settings) -> Box<dyn AuthClient> {
- if settings.is_hub_sync() {
- let endpoint = settings.active_hub_endpoint().unwrap_or_default();
- Box::new(HubAuthClient::new(
- endpoint.as_ref(),
- settings.hub_session_token().await.ok(),
- )) as Box<dyn AuthClient>
- } else {
- Box::new(LegacyAuthClient::new(
- &settings.sync_address,
- settings.session_token().await.ok(),
- settings.network_connect_timeout,
- settings.network_timeout,
- )) as Box<dyn AuthClient>
- }
+ Box::new(LegacyAuthClient::new(
+ &settings.sync_address,
+ settings.session_token().await.ok(),
+ settings.network_connect_timeout,
+ settings.network_timeout,
+ )) as Box<dyn AuthClient>
}
// ---------------------------------------------------------------------------
@@ -221,247 +209,6 @@ impl AuthClient for LegacyAuthClient {
}
// ---------------------------------------------------------------------------
-// Hub backend — talks to the Hub v0 API endpoints
-// ---------------------------------------------------------------------------
-
-pub struct HubAuthClient {
- address: String,
- hub_token: Option<String>,
-}
-
-impl HubAuthClient {
- pub fn new(address: &str, hub_token: Option<String>) -> Self {
- Self {
- address: address.trim_end_matches('/').to_string(),
- hub_token,
- }
- }
-}
-
-/// Hub v0 error/status response — includes an optional `code` field for
-/// machine-readable status like `"2fa_required"`.
-#[derive(Debug, Deserialize)]
-struct HubErrorResponse {
- reason: String,
- code: Option<String>,
-}
-
-#[async_trait]
-impl AuthClient for HubAuthClient {
- async fn login(
- &self,
- username: &str,
- password: &str,
- totp_code: Option<&str>,
- ) -> Result<AuthResponse> {
- ensure_crypto_provider();
- let url = make_url(&self.address, "/api/v0/login")?;
- let client = reqwest::Client::new();
-
- let mut body = serde_json::json!({
- "username": username,
- "password": password,
- });
- if let Some(code) = totp_code {
- body["totp_code"] = serde_json::Value::String(code.to_string());
- }
-
- let resp = client
- .post(&url)
- .header(USER_AGENT, APP_USER_AGENT)
- .header(ATUIN_HEADER_VERSION, ATUIN_CARGO_VERSION)
- .json(&body)
- .send()
- .await
- .context("failed to connect to Atuin Hub")?;
-
- let status = resp.status();
-
- if status.is_success() {
- let login: LoginResponse = resp.json().await?;
- return Ok(AuthResponse::Success {
- session: login.session,
- auth_type: login.auth,
- });
- }
-
- if status == StatusCode::FORBIDDEN
- && let Ok(err) = resp.json::<HubErrorResponse>().await
- {
- if err.code.as_deref() == Some("2fa_required") {
- return Ok(AuthResponse::TwoFactorRequired);
- }
- bail!("{}", err.reason);
- }
-
- if status == StatusCode::UNAUTHORIZED {
- bail!("invalid credentials");
- }
-
- bail!("Hub login failed with status {status}");
- }
-
- async fn register(&self, username: &str, email: &str, password: &str) -> Result<AuthResponse> {
- ensure_crypto_provider();
- let url = make_url(&self.address, "/api/v0/register")?;
- let client = reqwest::Client::new();
-
- let resp = client
- .post(&url)
- .header(USER_AGENT, APP_USER_AGENT)
- .header(ATUIN_HEADER_VERSION, ATUIN_CARGO_VERSION)
- .json(&serde_json::json!({
- "email": email,
- "username": username,
- "password": password,
- }))
- .send()
- .await
- .context("failed to connect to Atuin Hub")?;
-
- let status = resp.status();
-
- if status.is_success() {
- let reg: RegisterResponse = resp.json().await?;
- return Ok(AuthResponse::Success {
- session: reg.session,
- auth_type: reg.auth,
- });
- }
-
- if let Ok(err) = resp.json::<HubErrorResponse>().await {
- bail!("{}", err.reason);
- }
-
- bail!("Hub registration failed with status {status}");
- }
-
- async fn change_password(
- &self,
- current_password: &str,
- new_password: &str,
- totp_code: Option<&str>,
- ) -> Result<MutateResponse> {
- let hub_token = self.hub_token.as_deref().ok_or_else(|| {
- eyre::eyre!(
- "Not logged in to Atuin Hub. \
- Please run 'atuin login' to authenticate."
- )
- })?;
-
- if !hub_token.starts_with("atapi_") {
- bail!(
- "Your Hub session token is invalid. \
- Please run 'atuin login' to re-authenticate with Atuin Hub."
- );
- }
-
- ensure_crypto_provider();
- let url = make_url(&self.address, "/api/v0/account/password")?;
- let client = reqwest::Client::new();
-
- let mut body = serde_json::json!({
- "current_password": current_password,
- "new_password": new_password,
- });
- if let Some(code) = totp_code {
- body["totp_code"] = serde_json::Value::String(code.to_string());
- }
-
- let resp = client
- .patch(&url)
- .header(USER_AGENT, APP_USER_AGENT)
- .header(ATUIN_HEADER_VERSION, ATUIN_CARGO_VERSION)
- .bearer_auth(hub_token)
- .json(&body)
- .send()
- .await
- .context("failed to connect to Atuin Hub")?;
-
- let status = resp.status();
-
- if status.is_success() {
- return Ok(MutateResponse::Success);
- }
-
- if let Ok(err) = resp.json::<HubErrorResponse>().await {
- match err.code.as_deref() {
- Some("2fa_required") => return Ok(MutateResponse::TwoFactorRequired),
- Some("invalid_2fa_code") => bail!("invalid two-factor code"),
- _ => bail!("{}", err.reason),
- }
- }
-
- match status {
- StatusCode::UNAUTHORIZED => bail!("current password is incorrect"),
- StatusCode::FORBIDDEN => bail!("invalid login details"),
- _ => bail!("Hub password change failed with status {status}"),
- }
- }
-
- async fn delete_account(
- &self,
- password: &str,
- totp_code: Option<&str>,
- ) -> Result<MutateResponse> {
- let hub_token = self.hub_token.as_deref().ok_or_else(|| {
- eyre::eyre!(
- "Not logged in to Atuin Hub. \
- Please run 'atuin login' to authenticate."
- )
- })?;
-
- if !hub_token.starts_with("atapi_") {
- bail!(
- "Your Hub session token is invalid. \
- Please run 'atuin login' to re-authenticate with Atuin Hub."
- );
- }
-
- ensure_crypto_provider();
- let url = make_url(&self.address, "/api/v0/account")?;
- let client = reqwest::Client::new();
-
- let mut body = serde_json::json!({
- "password": password,
- });
- if let Some(code) = totp_code {
- body["totp_code"] = serde_json::Value::String(code.to_string());
- }
-
- let resp = client
- .delete(&url)
- .header(USER_AGENT, APP_USER_AGENT)
- .header(ATUIN_HEADER_VERSION, ATUIN_CARGO_VERSION)
- .bearer_auth(hub_token)
- .json(&body)
- .send()
- .await
- .context("failed to connect to Atuin Hub")?;
-
- let status = resp.status();
-
- if status.is_success() {
- return Ok(MutateResponse::Success);
- }
-
- if let Ok(err) = resp.json::<HubErrorResponse>().await {
- match err.code.as_deref() {
- Some("2fa_required") => return Ok(MutateResponse::TwoFactorRequired),
- Some("invalid_2fa_code") => bail!("invalid two-factor code"),
- _ => bail!("{}", err.reason),
- }
- }
-
- match status {
- StatusCode::UNAUTHORIZED => bail!("password is incorrect"),
- StatusCode::FORBIDDEN => bail!("invalid login details"),
- _ => bail!("Hub account deletion failed with status {status}"),
- }
- }
-}
-
-// ---------------------------------------------------------------------------
// Shared helpers
// ---------------------------------------------------------------------------
diff --git a/crates/atuin-client/src/hub.rs b/crates/atuin-client/src/hub.rs
deleted file mode 100644
index 2e40aad4..00000000
--- a/crates/atuin-client/src/hub.rs
+++ /dev/null
@@ -1,304 +0,0 @@
-//! Hub authentication support for Atuin
-//!
-//! This module provides programmatic access to the Atuin Hub authentication flow.
-//! It can be used by other crates (like atuin-ai) to authenticate with the Hub
-//! and obtain session tokens.
-//!
-//! Hub authentication is separate from sync authentication - users can have both
-//! a sync session (for history sync) and a hub session (for Hub-specific features
-//! like AI).
-
-use std::time::Duration;
-
-use eyre::{Context, Result, bail};
-use reqwest::{StatusCode, Url, header::USER_AGENT};
-
-use atuin_common::{
- api::{
- ATUIN_CARGO_VERSION, ATUIN_HEADER_VERSION, CliCodeResponse, CliVerifyResponse,
- ErrorResponse,
- },
- tls::ensure_crypto_provider,
-};
-
-use crate::settings::Settings;
-
-static APP_USER_AGENT: &str = concat!("atuin/", env!("CARGO_PKG_VERSION"));
-
-/// The result of starting a hub authentication flow
-#[derive(Debug, Clone)]
-pub struct HubAuthSession {
- /// The code to be verified
- pub code: String,
- /// The URL the user should visit to authenticate
- pub auth_url: String,
- /// The hub address being used
- pub hub_address: String,
-}
-
-/// The result of polling for hub auth completion
-#[derive(Debug, Clone)]
-pub enum HubAuthStatus {
- /// Still waiting for user authorization
- Pending,
- /// Authorization complete, contains the session token
- Complete(String),
- /// Authorization failed with an error
- Failed(String),
-}
-
-/// Default poll interval for checking auth status
-pub const DEFAULT_POLL_INTERVAL: Duration = Duration::from_secs(2);
-
-/// Default timeout for the entire auth flow
-pub const DEFAULT_AUTH_TIMEOUT: Duration = Duration::from_secs(600);
-
-impl HubAuthSession {
- /// Start a new hub authentication session
- ///
- /// Returns a session containing the code and auth URL that the user should visit.
- pub async fn start(hub_address: &str) -> Result<Self> {
- debug!("Starting Hub authentication process...");
-
- let hub_address = hub_address.trim_end_matches('/');
- let code_response = request_code(hub_address)
- .await
- .context("Failed to request authentication code from Hub")?;
-
- debug!("Received code from Hub");
-
- let code = code_response.code;
- let auth_url = format!("{}/auth/cli?code={}", hub_address, code);
-
- Ok(Self {
- code,
- auth_url,
- hub_address: hub_address.to_string(),
- })
- }
-
- /// Poll for the authentication status
- ///
- /// Returns the current status of the authentication flow.
- pub async fn poll(&self) -> Result<HubAuthStatus> {
- match verify_code(&self.hub_address, &self.code).await {
- Ok(response) => {
- if let Some(token) = response.token {
- debug!("Authentication complete, received token");
- Ok(HubAuthStatus::Complete(token))
- } else if let Some(error) = response.error {
- error!("Authentication failed: {}", error);
- Ok(HubAuthStatus::Failed(error))
- } else {
- Ok(HubAuthStatus::Pending)
- }
- }
- Err(e) => {
- // Transient errors shouldn't fail the whole flow
- log::debug!("Verification poll failed: {}", e);
- Ok(HubAuthStatus::Pending)
- }
- }
- }
-
- /// Poll until completion or timeout
- ///
- /// This is a convenience method that polls repeatedly until the auth completes
- /// or times out.
- pub async fn wait_for_completion(
- &self,
- timeout: Duration,
- poll_interval: Duration,
- ) -> Result<String> {
- let start = std::time::Instant::now();
-
- debug!("Polling for Hub authentication completion...");
-
- loop {
- if start.elapsed() > timeout {
- warn!("Authentication loop exited due to timeout");
- bail!("Authentication timed out. Please try again.");
- }
-
- match self.poll().await? {
- HubAuthStatus::Complete(token) => return Ok(token),
- HubAuthStatus::Failed(error) => bail!("Authentication failed: {}", error),
- HubAuthStatus::Pending => {
- tokio::time::sleep(poll_interval).await;
- }
- }
- }
- }
-}
-
-/// Save a hub session token
-///
-/// This saves the token to the meta store so it can be used for subsequent Hub API calls.
-/// Note: This is separate from the sync session token.
-pub async fn save_session(token: &str) -> Result<()> {
- Settings::meta_store()
- .await?
- .save_hub_session(token)
- .await
- .context("Failed to save hub session")
-}
-
-/// Delete the hub session token (logout from Hub)
-pub async fn delete_session() -> Result<()> {
- Settings::meta_store()
- .await?
- .delete_hub_session()
- .await
- .context("Failed to delete hub session")
-}
-
-/// Check if the user is logged in with Hub authentication
-///
-/// Returns true if the user has a valid Hub session token.
-/// This is independent of whether they have a sync session.
-pub async fn is_logged_in() -> Result<bool> {
- Settings::meta_store().await?.hub_logged_in().await
-}
-
-/// Get the hub session token if available
-///
-/// Returns the Hub session token if the user is logged in with Hub auth,
-/// or None if not logged in.
-pub async fn get_session_token() -> Result<Option<String>> {
- Settings::meta_store().await?.hub_session_token().await
-}
-
-/// Link an existing CLI sync account to the current Hub user.
-///
-/// This associates the CLI's sync records with the Hub account, enabling
-/// unified authentication. After linking:
-/// - The Hub token can be used for sync operations
-/// - Records are migrated to be accessible via Hub auth
-///
-/// Requires:
-/// - A valid Hub session (user must be logged in to Hub)
-/// - A valid CLI session token to link
-///
-/// Returns Ok(()) on success, or an error if:
-/// - Not logged in to Hub
-/// - CLI token is invalid
-/// - CLI account is already linked to a different Hub account
-pub async fn link_account(hub_address: &str, cli_token: &str) -> Result<()> {
- let hub_token = get_session_token()
- .await?
- .ok_or_else(|| eyre::eyre!("Not logged in to Hub - cannot link account"))?;
-
- let url = make_url(hub_address, "/api/v0/account/link")?;
-
- debug!("Linking CLI account to Hub at {}", hub_address);
-
- ensure_crypto_provider();
- let client = reqwest::Client::new();
-
- let resp = client
- .post(&url)
- .header(USER_AGENT, APP_USER_AGENT)
- .header(ATUIN_HEADER_VERSION, ATUIN_CARGO_VERSION)
- .bearer_auth(&hub_token)
- .json(&serde_json::json!({ "token": cli_token }))
- .send()
- .await?;
-
- let status = resp.status();
-
- if status == StatusCode::CONFLICT {
- // 409 means CLI account is already linked to a (possibly different) Hub account
- debug!("CLI account already linked to a Hub account");
- return Ok(());
- }
-
- handle_resp_error(resp).await?;
-
- info!("Successfully linked CLI account to Hub");
- Ok(())
-}
-
-// --- Internal HTTP functions ---
-
-fn make_url(address: &str, path: &str) -> Result<String> {
- let address = if address.ends_with('/') {
- address.to_string()
- } else {
- format!("{address}/")
- };
-
- let path = path.strip_prefix('/').unwrap_or(path);
-
- let url = Url::parse(&address)
- .context("failed to parse hub address")?
- .join(path)
- .context("failed to join hub URL path")?;
-
- Ok(url.to_string())
-}
-
-async fn handle_resp_error(resp: reqwest::Response) -> Result<reqwest::Response> {
- let status = resp.status();
-
- if status == StatusCode::SERVICE_UNAVAILABLE {
- error!("Service unavailable: check https://status.atuin.sh");
- bail!("Service unavailable: check https://status.atuin.sh");
- }
-
- if status == StatusCode::TOO_MANY_REQUESTS {
- error!("Rate limited; please wait before trying again");
- bail!("Rate limited; please wait before trying again");
- }
-
- if !status.is_success() {
- if let Ok(error) = resp.json::<ErrorResponse>().await {
- error!("Hub error: {} - {}", status, error.reason);
- bail!("Hub error: {} - {}", status, error.reason);
- }
- error!("Hub request failed with status: {}", status);
- bail!("Hub request failed with status: {}", status);
- }
-
- Ok(resp)
-}
-
-/// Request a CLI auth code from the Atuin Hub
-async fn request_code(address: &str) -> Result<CliCodeResponse> {
- ensure_crypto_provider();
- let url = make_url(address, "/auth/cli/code")?;
- let client = reqwest::Client::new();
-
- debug!("Requesting code from Hub at {url}");
-
- let resp = client
- .post(&url)
- .header(USER_AGENT, APP_USER_AGENT)
- .header(ATUIN_HEADER_VERSION, ATUIN_CARGO_VERSION)
- .send()
- .await?;
- let resp = handle_resp_error(resp).await?;
-
- let code_response = resp.json::<CliCodeResponse>().await?;
- Ok(code_response)
-}
-
-/// Poll to verify the CLI auth code and get the session token
-async fn verify_code(address: &str, code: &str) -> Result<CliVerifyResponse> {
- ensure_crypto_provider();
- let base = make_url(address, "/auth/cli/verify")?;
- let url = format!("{base}?code={code}");
- let client = reqwest::Client::new();
-
- debug!("Verifying code with Hub at {base}?code=******");
-
- let resp = client
- .post(&url)
- .header(USER_AGENT, APP_USER_AGENT)
- .header(ATUIN_HEADER_VERSION, ATUIN_CARGO_VERSION)
- .send()
- .await?;
- let resp = handle_resp_error(resp).await?;
-
- let verify_response = resp.json::<CliVerifyResponse>().await?;
- Ok(verify_response)
-}
diff --git a/crates/atuin-client/src/lib.rs b/crates/atuin-client/src/lib.rs
index 7938176a..cd7785e1 100644
--- a/crates/atuin-client/src/lib.rs
+++ b/crates/atuin-client/src/lib.rs
@@ -7,8 +7,6 @@ extern crate log;
pub mod api_client;
#[cfg(feature = "sync")]
pub mod auth;
-#[cfg(feature = "hub")]
-pub mod hub;
#[cfg(feature = "sync")]
pub mod login;
#[cfg(feature = "sync")]
diff --git a/crates/atuin-client/src/logout.rs b/crates/atuin-client/src/logout.rs
index 80f0ad73..f720b302 100644
--- a/crates/atuin-client/src/logout.rs
+++ b/crates/atuin-client/src/logout.rs
@@ -7,7 +7,6 @@ pub async fn logout() -> Result<()> {
if meta.logged_in().await? {
meta.delete_session().await?;
- meta.delete_hub_session().await?;
println!("You have logged out!");
} else {
println!("You are not logged in");
diff --git a/crates/atuin-client/src/meta.rs b/crates/atuin-client/src/meta.rs
index eb6dd8cf..870f36d0 100644
--- a/crates/atuin-client/src/meta.rs
+++ b/crates/atuin-client/src/meta.rs
@@ -21,7 +21,6 @@ const KEY_LAST_SYNC: &str = "last_sync_time";
const KEY_LAST_VERSION_CHECK: &str = "last_version_check_time";
const KEY_LATEST_VERSION: &str = "latest_version";
const KEY_SESSION: &str = "session";
-const KEY_HUB_SESSION: &str = "hub_session";
const KEY_FILES_MIGRATED: &str = "files_migrated";
pub struct MetaStore {
@@ -187,25 +186,7 @@ impl MetaStore {
}
pub async fn logged_in(&self) -> Result<bool> {
- Ok(self.session_token().await?.is_some() || self.hub_session_token().await?.is_some())
- }
-
- // Hub session methods (separate from sync session, used for Hub-specific features like AI)
-
- pub async fn hub_session_token(&self) -> Result<Option<String>> {
- self.get(KEY_HUB_SESSION).await
- }
-
- pub async fn save_hub_session(&self, token: &str) -> Result<()> {
- self.set(KEY_HUB_SESSION, token).await
- }
-
- pub async fn delete_hub_session(&self) -> Result<()> {
- self.delete(KEY_HUB_SESSION).await
- }
-
- pub async fn hub_logged_in(&self) -> Result<bool> {
- Ok(self.hub_session_token().await?.is_some())
+ Ok(self.session_token().await?.is_some())
}
// File migration: on first open, migrate old plain-text files into the database.
diff --git a/crates/atuin-client/src/settings.rs b/crates/atuin-client/src/settings.rs
index 1be6f363..ce84c053 100644
--- a/crates/atuin-client/src/settings.rs
+++ b/crates/atuin-client/src/settings.rs
@@ -11,7 +11,6 @@ use eyre::{Context, Error, Result, bail, eyre};
use fs_err::{File, create_dir_all};
use humantime::parse_duration;
use regex::RegexSet;
-use semver::Version;
use serde::{Deserialize, Serialize};
use serde_with::DeserializeFromStr;
use time::{OffsetDateTime, UtcOffset, format_description::FormatItem, macros::format_description};
@@ -23,32 +22,12 @@ static DATA_DIR: OnceLock<PathBuf> = OnceLock::new();
static META_CONFIG: OnceLock<(String, f64)> = OnceLock::new();
static META_STORE: OnceCell<crate::meta::MetaStore> = OnceCell::const_new();
-mod dotfiles;
-mod kv;
pub(crate) mod meta;
-mod scripts;
pub mod watcher;
-pub struct HubEndpoint(String);
-
/// Default sync address for Atuin's hosted service
pub const DEFAULT_SYNC_ADDRESS: &str = "https://api.atuin.sh";
-/// Default Hub web/API endpoint for Atuin's hosted service
-pub const DEFAULT_HUB_ENDPOINT: &str = "https://hub.atuin.sh";
-
-impl Default for HubEndpoint {
- fn default() -> Self {
- HubEndpoint(DEFAULT_HUB_ENDPOINT.to_string())
- }
-}
-
-impl AsRef<str> for HubEndpoint {
- fn as_ref(&self) -> &str {
- &self.0
- }
-}
-
#[derive(Clone, Debug, Deserialize, Copy, ValueEnum, PartialEq, Serialize)]
pub enum SearchMode {
#[serde(rename = "prefix")]
@@ -374,13 +353,9 @@ pub struct Sync {
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Serialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum SyncProtocol {
- /// Use Hub authentication (Bearer token from Hub OAuth flow)
- Hub,
/// Use legacy CLI authentication (Token from CLI register/login)
- Legacy,
- /// Infer from sync_address (default behavior)
#[default]
- Auto,
+ Legacy,
}
/// Resolved authentication state for sync operations.
@@ -394,14 +369,7 @@ pub enum SyncAuth {
/// Self-hosted Rust server. Uses `Authorization: Token <session>` and
/// legacy endpoints.
Legacy { token: String },
- /// Hub with a valid Hub API token (`atapi_*`). Uses
- /// `Authorization: Bearer <token>` and v0 endpoints.
- Hub { token: String },
- /// Targeting Hub but only has a CLI session token. Uses
- /// `Authorization: Token <session>` against compat/record endpoints.
- /// Sync, password change, and account deletion still work, but the user
- /// should be nudged to run `atuin login` for full Hub auth.
- HubViaCli { token: String },
+
/// Not authenticated at all. Contains an actionable user-facing message.
NotLoggedIn { reason: String },
}
@@ -415,8 +383,6 @@ impl SyncAuth {
use crate::api_client::AuthToken;
match self {
SyncAuth::Legacy { token } => Ok(AuthToken::Token(token)),
- SyncAuth::Hub { token } => Ok(AuthToken::Bearer(token)),
- SyncAuth::HubViaCli { token } => Ok(AuthToken::Token(token)),
SyncAuth::NotLoggedIn { reason } => Err(eyre!(reason)),
}
}
@@ -1082,9 +1048,6 @@ pub struct Settings {
/// The sync address for atuin.
pub sync_address: String,
- /// Sync protocol for authentication. When set to "auto" (default), the protocol
- /// is inferred from sync_address. Set to "hub" to force Hub auth with a custom
- /// sync_address (useful for local development).
#[serde(default)]
pub sync_protocol: SyncProtocol,
@@ -1152,9 +1115,6 @@ pub struct Settings {
pub preview: Preview,
#[serde(default)]
- pub dotfiles: dotfiles::Settings,
-
- #[serde(default)]
pub daemon: Daemon,
#[serde(default)]
@@ -1167,12 +1127,6 @@ pub struct Settings {
pub ui: Ui,
#[serde(default)]
- pub scripts: scripts::Settings,
-
- #[serde(default)]
- pub kv: kv::Settings,
-
- #[serde(default)]
pub tmux: Tmux,
#[serde(default)]
@@ -1180,9 +1134,6 @@ pub struct Settings {
#[serde(default)]
pub meta: meta::Settings,
-
- #[serde(default)]
- pub ai: Ai,
}
impl Settings {
@@ -1266,56 +1217,6 @@ impl Settings {
}
}
- pub async fn hub_session_token(&self) -> Result<String> {
- match Self::meta_store().await?.hub_session_token().await? {
- Some(token) => Ok(token),
- None => Err(eyre!("Tried to load hub session; not logged in")),
- }
- }
-
- /// Normalize a URL for comparison by trimming trailing slashes
- fn normalize_url(url: &str) -> &str {
- url.trim_end_matches('/')
- }
-
- /// Check if a URL matches one of Atuin's official hosted addresses
- fn is_official_address(url: &str) -> bool {
- let normalized = Self::normalize_url(url);
- normalized == Self::normalize_url(DEFAULT_SYNC_ADDRESS)
- || normalized == Self::normalize_url(DEFAULT_HUB_ENDPOINT)
- }
-
- /// Returns whether this configuration uses Hub-style sync.
- ///
- /// Hub sync uses Bearer token authentication and is the default for
- /// Atuin's hosted service. This returns true when:
- /// - `sync_protocol` is explicitly set to `Hub`, OR
- /// - `sync_protocol` is `Auto` and `sync_address` is an official Atuin address
- pub fn is_hub_sync(&self) -> bool {
- match self.sync_protocol {
- SyncProtocol::Hub => true,
- SyncProtocol::Legacy => false,
- SyncProtocol::Auto => Self::is_official_address(&self.sync_address),
- }
- }
-
- /// Returns the base URL for the Hub endpoint.
- ///
- /// For Atuin's official hosted service, this always returns `https://hub.atuin.sh`
- /// regardless of whether `sync_address` is `api.atuin.sh` or `hub.atuin.sh`.
- /// For self-hosted instances, returns the configured `sync_address`.
- pub fn active_hub_endpoint(&self) -> Option<HubEndpoint> {
- if self.is_hub_sync() {
- if Self::is_official_address(&self.sync_address) {
- Some(HubEndpoint::default())
- } else {
- Some(HubEndpoint(self.sync_address.clone()))
- }
- } else {
- None
- }
- }
-
/// Examines the configured sync target and available tokens to determine
/// the correct auth strategy. Also performs cleanup of mis-stored tokens
/// (e.g. a CLI token incorrectly saved in the Hub session slot).
@@ -1330,45 +1231,12 @@ impl Settings {
}
};
- if !self.is_hub_sync() {
- // Self-hosted / legacy server
- return match meta.session_token().await {
- Ok(Some(token)) => SyncAuth::Legacy { token },
- _ => SyncAuth::NotLoggedIn {
- reason: "Not logged in. Run 'atuin login' to authenticate \
- with your sync server."
- .into(),
- },
- };
- }
-
- // Targeting Hub — check for a valid Hub API token first
- if let Ok(Some(hub_token)) = meta.hub_session_token().await {
- if hub_token.starts_with("atapi_") {
- return SyncAuth::Hub { token: hub_token };
- }
-
- // A non-atapi_ token in the hub_session slot is a mis-stored CLI
- // token (from the migration-fallback bug). Move it to the CLI
- // session slot if that slot is empty, then clear hub_session
- // only if the move succeeded.
- if let Ok(None) = meta.session_token().await {
- if meta.save_session(&hub_token).await.is_ok() {
- let _ = meta.delete_hub_session().await;
- }
- } else {
- // CLI slot already has a token; just clear the bad hub_session
- let _ = meta.delete_hub_session().await;
- }
- // Fall through to check CLI token below
- }
-
- // No valid Hub token — check for a CLI session token
+ // Self-hosted / legacy server
match meta.session_token().await {
- Ok(Some(token)) => SyncAuth::HubViaCli { token },
+ Ok(Some(token)) => SyncAuth::Legacy { token },
_ => SyncAuth::NotLoggedIn {
- reason: "Not logged in. Run 'atuin login' or 'atuin register' \
- to authenticate."
+ reason: "Not logged in. Run 'atuin login' to authenticate \
+ with your sync server."
.into(),
},
}
@@ -1384,70 +1252,6 @@ impl Settings {
self.resolve_sync_auth().await.into_auth_token()
}
- #[cfg(feature = "check-update")]
- async fn needs_update_check(&self) -> Result<bool> {
- let last_check = Settings::last_version_check().await?;
- let diff = OffsetDateTime::now_utc() - last_check;
-
- // Check a max of once per hour
- Ok(diff.whole_hours() >= 1)
- }
-
- #[cfg(feature = "check-update")]
- async fn latest_version(&self) -> Result<Version> {
- // Default to the current version, and if that doesn't parse, a version so high it's unlikely to ever
- // suggest upgrading.
- let current =
- Version::parse(env!("CARGO_PKG_VERSION")).unwrap_or(Version::new(100000, 0, 0));
-
- if !self.needs_update_check().await? {
- let meta = Self::meta_store().await?;
- let version = match meta.latest_version().await? {
- Some(v) => Version::parse(&v).unwrap_or(current),
- None => current,
- };
-
- return Ok(version);
- }
-
- #[cfg(feature = "sync")]
- let latest = crate::api_client::latest_version().await.unwrap_or(current);
-
- #[cfg(not(feature = "sync"))]
- let latest = current;
-
- let meta = Self::meta_store().await?;
- Settings::save_version_check_time().await?;
- meta.save_latest_version(&latest.to_string()).await?;
-
- Ok(latest)
- }
-
- // Return Some(latest version) if an update is needed. Otherwise, none.
- #[cfg(feature = "check-update")]
- pub async fn needs_update(&self) -> Option<Version> {
- if !self.update_check {
- return None;
- }
-
- let current =
- Version::parse(env!("CARGO_PKG_VERSION")).unwrap_or(Version::new(100000, 0, 0));
-
- let latest = self.latest_version().await;
-
- if latest.is_err() {
- return None;
- }
-
- let latest = latest.unwrap();
-
- if latest > current {
- return Some(latest);
- }
-
- None
- }
-
pub fn default_filter_mode(&self, git_root: bool) -> FilterMode {
self.filter_mode
.filter(|x| self.search.filters.contains(x))
@@ -1465,11 +1269,6 @@ impl Settings {
.unwrap_or(FilterMode::Global)
}
- #[cfg(not(feature = "check-update"))]
- pub async fn needs_update(&self) -> Option<Version> {
- None
- }
-
pub fn builder() -> Result<ConfigBuilder<DefaultState>> {
Self::builder_with_data_dir(&atuin_common::utils::data_dir())
}
diff --git a/crates/atuin-client/src/settings/dotfiles.rs b/crates/atuin-client/src/settings/dotfiles.rs
deleted file mode 100644
index bbaf914f..00000000
--- a/crates/atuin-client/src/settings/dotfiles.rs
+++ /dev/null
@@ -1,7 +0,0 @@
-use serde::{Deserialize, Serialize};
-
-#[derive(Debug, Serialize, Deserialize, Clone, Default)]
-pub struct Settings {
- #[serde(alias = "enable")]
- pub enabled: bool,
-}
diff --git a/crates/atuin-client/src/settings/kv.rs b/crates/atuin-client/src/settings/kv.rs
deleted file mode 100644
index afc24a35..00000000
--- a/crates/atuin-client/src/settings/kv.rs
+++ /dev/null
@@ -1,17 +0,0 @@
-use serde::{Deserialize, Serialize};
-
-#[derive(Debug, Serialize, Deserialize, Clone)]
-pub struct Settings {
- pub db_path: String,
-}
-
-impl Default for Settings {
- fn default() -> Self {
- let dir = atuin_common::utils::data_dir();
- let path = dir.join("kv.db");
-
- Self {
- db_path: path.to_string_lossy().to_string(),
- }
- }
-}
diff --git a/crates/atuin-client/src/settings/scripts.rs b/crates/atuin-client/src/settings/scripts.rs
deleted file mode 100644
index a1a5ed6a..00000000
--- a/crates/atuin-client/src/settings/scripts.rs
+++ /dev/null
@@ -1,17 +0,0 @@
-use serde::{Deserialize, Serialize};
-
-#[derive(Debug, Serialize, Deserialize, Clone)]
-pub struct Settings {
- pub db_path: String,
-}
-
-impl Default for Settings {
- fn default() -> Self {
- let dir = atuin_common::utils::data_dir();
- let path = dir.join("scripts.db");
-
- Self {
- db_path: path.to_string_lossy().to_string(),
- }
- }
-}
diff --git a/crates/atuin/src/command/client.rs b/crates/atuin/src/command/client.rs
index 3b0ef8a9..a1ebff29 100644
--- a/crates/atuin/src/command/client.rs
+++ b/crates/atuin/src/command/client.rs
@@ -56,7 +56,6 @@ mod history;
mod import;
mod info;
mod init;
-mod kv;
mod search;
mod setup;
mod stats;
@@ -92,10 +91,6 @@ pub enum Cmd {
#[cfg(feature = "sync")]
Account(account::Cmd),
- /// Get or set small key-value pairs
- #[command(subcommand)]
- Kv(kv::Cmd),
-
/// Manage the atuin data store
#[command(subcommand)]
Store(store::Cmd),
@@ -314,7 +309,10 @@ impl Cmd {
// runs
match self {
Self::History(history) => return history.run(&settings).await,
- Self::Init(init) => return init.run(&settings).await,
+ Self::Init(init) => {
+ init.run(&settings);
+ return Ok(());
+ }
Self::Doctor => return doctor::run(&settings).await,
Self::Config(config) => return config.run(&settings).await,
_ => {}
@@ -341,8 +339,6 @@ impl Cmd {
#[cfg(feature = "sync")]
Self::Account(account) => account.run(settings, sqlite_store).await,
- Self::Kv(kv) => kv.run(&settings, &sqlite_store).await,
-
Self::Store(store) => store.run(&settings, &db, sqlite_store).await,
Self::Info => {
diff --git a/crates/atuin/src/command/client/account.rs b/crates/atuin/src/command/client/account.rs
index a1be65a5..fc1c9343 100644
--- a/crates/atuin/src/command/client/account.rs
+++ b/crates/atuin/src/command/client/account.rs
@@ -6,7 +6,6 @@ use atuin_client::settings::Settings;
pub mod change_password;
pub mod delete;
-pub mod link;
pub mod login;
pub mod logout;
pub mod register;
@@ -33,20 +32,16 @@ pub enum Commands {
/// Change your password
ChangePassword(change_password::Cmd),
-
- /// Link your CLI sync account to your Hub account
- Link,
}
impl Cmd {
pub async fn run(self, settings: Settings, store: SqliteStore) -> Result<()> {
match self.command {
Commands::Login(l) => l.run(&settings, &store).await,
- Commands::Register(r) => r.run(&settings, &store).await,
+ Commands::Register(r) => r.run(&settings).await,
Commands::Logout => logout::run().await,
Commands::Delete(d) => d.run(&settings).await,
Commands::ChangePassword(c) => c.run(&settings).await,
- Commands::Link => link::run(&settings).await,
}
}
}
diff --git a/crates/atuin/src/command/client/account/delete.rs b/crates/atuin/src/command/client/account/delete.rs
index 7f8dc682..a5e7f0dd 100644
--- a/crates/atuin/src/command/client/account/delete.rs
+++ b/crates/atuin/src/command/client/account/delete.rs
@@ -49,7 +49,6 @@ impl Cmd {
// Clean up sessions from meta store
let meta = Settings::meta_store().await?;
meta.delete_session().await?;
- meta.delete_hub_session().await?;
println!("Your account is deleted");
diff --git a/crates/atuin/src/command/client/account/link.rs b/crates/atuin/src/command/client/account/link.rs
deleted file mode 100644
index 69c4eebe..00000000
--- a/crates/atuin/src/command/client/account/link.rs
+++ /dev/null
@@ -1,41 +0,0 @@
-use eyre::{Result, bail};
-
-use atuin_client::settings::Settings;
-
-pub async fn run(settings: &Settings) -> Result<()> {
- let meta = Settings::meta_store().await?;
-
- let cli_token = meta.session_token().await?;
- let hub_token = meta.hub_session_token().await?;
-
- let Some(cli_token) = cli_token else {
- bail!("No CLI session found. Please log in first with 'atuin login'.");
- };
-
- let hub_address = settings.active_hub_endpoint().unwrap_or_default();
-
- if hub_token.is_some() {
- println!("Found both Hub and CLI sessions. Linking accounts...");
- } else {
- println!("Found CLI session but no Hub session. Logging in to Hub first...");
-
- let session = atuin_client::hub::HubAuthSession::start(hub_address.as_ref()).await?;
- println!("Open this URL to authenticate with Atuin Hub:");
- println!("{}", session.auth_url);
-
- let token = session
- .wait_for_completion(
- atuin_client::hub::DEFAULT_AUTH_TIMEOUT,
- atuin_client::hub::DEFAULT_POLL_INTERVAL,
- )
- .await?;
-
- atuin_client::hub::save_session(&token).await?;
- println!("Hub authentication complete.");
- }
-
- atuin_client::hub::link_account(hub_address.as_ref(), &cli_token).await?;
- println!("Successfully linked CLI account to Hub.");
-
- Ok(())
-}
diff --git a/crates/atuin/src/command/client/account/login.rs b/crates/atuin/src/command/client/account/login.rs
index 072f0815..13918638 100644
--- a/crates/atuin/src/command/client/account/login.rs
+++ b/crates/atuin/src/command/client/account/login.rs
@@ -43,95 +43,19 @@ fn get_input() -> Result<String> {
impl Cmd {
pub async fn run(&self, settings: &Settings, store: &SqliteStore) -> Result<()> {
match settings.resolve_sync_auth().await {
- SyncAuth::Hub { .. } => {
- println!("You are authenticated with Atuin Hub.");
- println!("Run 'atuin logout' to log out.");
- return Ok(());
- }
SyncAuth::Legacy { .. } => {
println!("You are logged in to your sync server.");
println!("Run 'atuin logout' to log out.");
return Ok(());
}
- SyncAuth::HubViaCli { .. } => {
- println!(
- "You have a legacy sync session. \
- Continuing login to upgrade to full Hub authentication."
- );
- }
SyncAuth::NotLoggedIn { .. } => {}
}
- if settings.is_hub_sync() {
- self.run_hub_login(settings, store).await?;
- } else {
- self.run_legacy_login(settings, store).await?;
- }
+ self.run_legacy_login(settings, store).await?;
verify_key_against_remote(settings).await
}
- /// Hub login: use the browser flow unless the username was provided for headless use.
- async fn run_hub_login(&self, settings: &Settings, store: &SqliteStore) -> Result<()> {
- let endpoint = settings.active_hub_endpoint().unwrap_or_default();
-
- if let Some(username) = &self.username {
- // Headless login via v0 API (for CI / scripting).
- let client = auth::auth_client(settings).await;
-
- self.prompt_and_store_key(settings, store).await?;
-
- let password = self.password.clone().unwrap_or_else(read_user_password);
- let mut totp_code = self.totp_code.clone();
-
- let (session, auth_type) = loop {
- let response = client
- .login(username, &password, totp_code.as_deref())
- .await?;
-
- match response {
- AuthResponse::Success { session, auth_type } => break (session, auth_type),
- AuthResponse::TwoFactorRequired => {
- totp_code = Some(or_user_input(None, "two-factor code"));
- }
- }
- };
-
- let meta = Settings::meta_store().await?;
- let is_hub_token = auth_type.as_deref() == Some("hub") || session.starts_with("atapi_");
-
- if is_hub_token {
- meta.save_hub_session(&session).await?;
- } else {
- meta.save_session(&session).await?;
- println!("\nNote: Your account has not been fully migrated to Atuin Hub.");
- println!(
- "Sync will continue to work, but you can visit hub.atuin.sh \
- to create an account and link it to your existing CLI account."
- );
- }
- } else {
- // Interactive login via browser OAuth flow.
- if self.from_registration {
- load_key(settings)?;
- } else {
- self.prompt_and_store_key(settings, store).await?;
- }
-
- self.ensure_hub_session(settings, endpoint.as_ref()).await?;
- }
-
- // Silently attempt to link CLI account to Hub if one exists
- if let Ok(cli_token) = settings.session_token().await
- && let Err(e) = atuin_client::hub::link_account(endpoint.as_ref(), &cli_token).await
- {
- tracing::debug!("Could not link CLI account to Hub: {}", e);
- }
-
- println!("Successfully authenticated.");
- Ok(())
- }
-
/// Legacy login: always prompt for username/password interactively
/// (or accept them via flags).
async fn run_legacy_login(&self, settings: &Settings, store: &SqliteStore) -> Result<()> {
@@ -157,27 +81,6 @@ impl Cmd {
Ok(())
}
- async fn ensure_hub_session(&self, _settings: &Settings, hub_address: &str) -> Result<()> {
- tracing::info!("Authenticating with Atuin Hub...");
-
- let session = atuin_client::hub::HubAuthSession::start(hub_address).await?;
- println!("Open this URL to continue authenticating with Atuin Hub:");
- println!("{}", session.auth_url);
-
- let token = session
- .wait_for_completion(
- atuin_client::hub::DEFAULT_AUTH_TIMEOUT,
- atuin_client::hub::DEFAULT_POLL_INTERVAL,
- )
- .await?;
-
- tracing::info!("Authentication complete, saving session token");
-
- atuin_client::hub::save_session(&token).await?;
-
- Ok(())
- }
-
async fn prompt_and_store_key(&self, settings: &Settings, store: &SqliteStore) -> Result<()> {
let key_path = settings.key_path.as_str();
let key_path = PathBuf::from(key_path);
@@ -293,7 +196,6 @@ async fn verify_key_against_remote(settings: &Settings) -> Result<()> {
// half-authenticated state with a key that can't read the data.
if let Ok(meta) = Settings::meta_store().await {
let _ = meta.delete_session().await;
- let _ = meta.delete_hub_session().await;
}
crate::print_error::print_error(
"Wrong encryption key",
diff --git a/crates/atuin/src/command/client/account/register.rs b/crates/atuin/src/command/client/account/register.rs
index f01427c0..bd836e7b 100644
--- a/crates/atuin/src/command/client/account/register.rs
+++ b/crates/atuin/src/command/client/account/register.rs
@@ -2,11 +2,7 @@ use clap::Parser;
use eyre::{Result, bail};
use super::login::or_user_input;
-use atuin_client::{
- auth::{self, AuthResponse},
- record::sqlite_store::SqliteStore,
- settings::{Settings, SyncAuth},
-};
+use atuin_client::settings::{Settings, SyncAuth};
#[derive(Parser, Debug)]
pub struct Cmd {
@@ -21,139 +17,50 @@ pub struct Cmd {
}
impl Cmd {
- #[allow(clippy::too_many_lines)]
- pub async fn run(&self, settings: &Settings, store: &SqliteStore) -> Result<()> {
+ pub async fn run(&self, settings: &Settings) -> Result<()> {
match settings.resolve_sync_auth().await {
- SyncAuth::Hub { .. } => {
- println!("You are already authenticated with Atuin Hub.");
- println!("Run 'atuin logout' to log out.");
- return Ok(());
- }
SyncAuth::Legacy { .. } => {
println!("You are already logged in.");
println!("Run 'atuin logout' to log out.");
return Ok(());
}
- SyncAuth::HubViaCli { .. } => {
- println!(
- "You already have a sync session. \
- Run 'atuin login' to upgrade to full Hub authentication."
- );
- println!("Run 'atuin logout' first if you want to register a new account.");
- return Ok(());
- }
+
SyncAuth::NotLoggedIn { .. } => {}
}
- if settings.is_hub_sync() {
- let required_for_headless = 3;
- let provided = [
- self.username.is_some(),
- self.email.is_some(),
- self.password.is_some(),
- ]
- .iter()
- .filter(|&b| *b)
- .count();
- if provided < required_for_headless {
- println!(
- "Username, password, and email are all required for headless registration. Continuing with interactive registration.\n"
- );
- }
-
- if let (Some(username), Some(email), Some(password)) =
- (&self.username, &self.email, &self.password)
- {
- // Headless registration via v0 API (for CI / scripting).
- let client = auth::auth_client(settings).await;
-
- if password.is_empty() {
- bail!("please provide a password");
- }
+ // Legacy registration flow
+ println!("Registering for an Atuin Sync account");
- let response = client.register(username, email, password).await?;
+ let username = or_user_input(self.username.clone(), "username");
+ let email = or_user_input(self.email.clone(), "email");
+ let password = self
+ .password
+ .clone()
+ .unwrap_or_else(super::login::read_user_password);
- match response {
- AuthResponse::Success { session, auth_type } => {
- let meta = Settings::meta_store().await?;
- let is_hub_token =
- auth_type.as_deref() == Some("hub") || session.starts_with("atapi_");
-
- if is_hub_token {
- meta.save_hub_session(&session).await?;
- } else {
- meta.save_session(&session).await?;
- println!(
- "\nNote: Your account has not been fully migrated to Atuin Hub."
- );
- println!(
- "Sync will continue to work, but you can visit hub.atuin.sh \
- to create a new Hub account and link it to your existing CLI account."
- );
- }
- }
- AuthResponse::TwoFactorRequired => {
- bail!("unexpected two-factor requirement during registration");
- }
- }
-
- let _key = atuin_client::encryption::load_key(settings)?;
-
- println!(
- "Registration successful! Please make a note of your key (run 'atuin key') and keep it safe."
- );
- println!(
- "You will need it to log in on other devices, and we cannot help recover it if you lose it."
- );
- } else {
- // Interactive registration: delegate to the browser OAuth flow.
- // Registration on Hub happens on the website; the CLI just needs
- // to authenticate afterwards.
- super::login::Cmd {
- username: None,
- password: None,
- key: None,
- totp_code: None,
- from_registration: true,
- }
- .run(settings, store)
- .await?;
- }
- } else {
- // Legacy registration flow
- println!("Registering for an Atuin Sync account");
-
- let username = or_user_input(self.username.clone(), "username");
- let email = or_user_input(self.email.clone(), "email");
- let password = self
- .password
- .clone()
- .unwrap_or_else(super::login::read_user_password);
-
- if password.is_empty() {
- bail!("please provide a password");
- }
+ if password.is_empty() {
+ bail!("please provide a password");
+ }
- let session = atuin_client::api_client::register(
- settings.sync_address.as_str(),
- &username,
- &email,
- &password,
- )
- .await?;
+ let session = atuin_client::api_client::register(
+ settings.sync_address.as_str(),
+ &username,
+ &email,
+ &password,
+ )
+ .await?;
- let meta = Settings::meta_store().await?;
- meta.save_session(&session.session).await?;
+ let meta = Settings::meta_store().await?;
+ meta.save_session(&session.session).await?;
- let _key = atuin_client::encryption::load_key(settings)?;
+ let _key = atuin_client::encryption::load_key(settings)?;
- println!(
- "Registration successful! Please make a note of your key (run 'atuin key') and keep it safe."
- );
- println!(
- "You will need it to log in on other devices, and we cannot help recover it if you lose it."
- );
- }
+ println!(
+ "Registration successful! Please make a note of your key (run 'atuin key') and keep it safe."
+ );
+ println!(
+ "You will need it to log in on other devices, and we cannot help recover it if you lose it."
+ );
Ok(())
}
diff --git a/crates/atuin/src/command/client/doctor.rs b/crates/atuin/src/command/client/doctor.rs
index ce65f66a..f6470226 100644
--- a/crates/atuin/src/command/client/doctor.rs
+++ b/crates/atuin/src/command/client/doctor.rs
@@ -249,25 +249,12 @@ impl SyncInfo {
// resolve_sync_auth(), which has side effects (token migration cleanup)
// that a diagnostic command should not trigger.
let meta = Settings::meta_store().await.ok();
- let has_hub_token = match &meta {
- Some(m) => m
- .hub_session_token()
- .await
- .ok()
- .flatten()
- .is_some_and(|t| t.starts_with("atapi_")),
- None => false,
- };
let has_cli_token = match &meta {
Some(m) => m.session_token().await.ok().flatten().is_some(),
None => false,
};
- let auth_state = if has_hub_token {
- "Hub (authenticated)".into()
- } else if settings.is_hub_sync() && has_cli_token {
- "Hub (legacy token \u{2014} run 'atuin login' to upgrade)".into()
- } else if !settings.is_hub_sync() && has_cli_token {
+ let auth_state = if has_cli_token {
"Self-hosted (authenticated)".into()
} else {
"Not authenticated".into()
diff --git a/crates/atuin/src/command/client/init.rs b/crates/atuin/src/command/client/init.rs
index 98ef5c80..e0f284a7 100644
--- a/crates/atuin/src/command/client/init.rs
+++ b/crates/atuin/src/command/client/init.rs
@@ -1,6 +1,5 @@
use atuin_client::settings::{Settings, Tmux};
use clap::{Parser, ValueEnum};
-use eyre::Result;
mod bash;
mod fish;
@@ -116,44 +115,13 @@ $env.config = (
}
}
- async fn dotfiles_init(&self, settings: &Settings) -> Result<()> {
- match self.shell {
- Shell::Zsh => {
- zsh::init(self.disable_up_arrow, self.disable_ctrl_r, &settings.tmux).await?;
- }
- Shell::Bash => {
- bash::init(self.disable_up_arrow, self.disable_ctrl_r, &settings.tmux).await?;
- }
- Shell::Fish => {
- fish::init(self.disable_up_arrow, self.disable_ctrl_r, &settings.tmux).await?;
- }
- Shell::Nu => self.init_nu(&settings.tmux),
- Shell::Xonsh => {
- xonsh::init(self.disable_up_arrow, self.disable_ctrl_r, &settings.tmux).await?;
- }
- Shell::PowerShell => {
- powershell::init(self.disable_up_arrow, self.disable_ctrl_r, &settings.tmux)
- .await?;
- }
- }
-
- Ok(())
- }
-
- pub async fn run(self, settings: &Settings) -> Result<()> {
+ pub fn run(self, settings: &Settings) {
if !settings.paths_ok() {
eprintln!(
"Atuin settings paths are broken. Disabling atuin shell hooks. Run `atuin doctor` to diagnose."
);
- return Ok(());
- }
-
- if settings.dotfiles.enabled {
- self.dotfiles_init(settings).await?;
- } else {
- self.static_init(settings);
}
- Ok(())
+ self.static_init(settings);
}
}
diff --git a/crates/atuin/src/command/client/init/bash.rs b/crates/atuin/src/command/client/init/bash.rs
index 7fe57b33..2280dc3d 100644
--- a/crates/atuin/src/command/client/init/bash.rs
+++ b/crates/atuin/src/command/client/init/bash.rs
@@ -1,5 +1,4 @@
use atuin_client::settings::Tmux;
-use eyre::Result;
fn print_tmux_config(tmux: &Tmux) {
if tmux.enabled {
@@ -24,9 +23,3 @@ pub fn init_static(disable_up_arrow: bool, disable_ctrl_r: bool, tmux: &Tmux) {
println!("__atuin_bind_up_arrow={bind_up_arrow}");
println!("{base}");
}
-
-pub async fn init(disable_up_arrow: bool, disable_ctrl_r: bool, tmux: &Tmux) -> Result<()> {
- init_static(disable_up_arrow, disable_ctrl_r, tmux);
-
- Ok(())
-}
diff --git a/crates/atuin/src/command/client/init/fish.rs b/crates/atuin/src/command/client/init/fish.rs
index e477faed..07c6a5ba 100644
--- a/crates/atuin/src/command/client/init/fish.rs
+++ b/crates/atuin/src/command/client/init/fish.rs
@@ -1,5 +1,4 @@
use atuin_client::settings::Tmux;
-use eyre::Result;
fn print_tmux_config(tmux: &Tmux) {
if tmux.enabled {
@@ -85,13 +84,3 @@ pub fn init_static(disable_up_arrow: bool, disable_ctrl_r: bool, tmux: &Tmux) {
println!("end");
}
}
-
-pub async fn init(
- disable_up_arrow: bool,
- disable_ctrl_r: bool,
- tmux: &Tmux,
-) -> Result<()> {
- init_static(disable_up_arrow, disable_ctrl_r, tmux);
-
- Ok(())
-}
diff --git a/crates/atuin/src/command/client/init/powershell.rs b/crates/atuin/src/command/client/init/powershell.rs
index a36b8e67..f92f1cbe 100644
--- a/crates/atuin/src/command/client/init/powershell.rs
+++ b/crates/atuin/src/command/client/init/powershell.rs
@@ -18,12 +18,6 @@ pub fn init_static(disable_up_arrow: bool, disable_ctrl_r: bool, _tmux: &Tmux) {
);
}
-pub async fn init(disable_up_arrow: bool, disable_ctrl_r: bool, tmux: &Tmux) -> eyre::Result<()> {
- init_static(disable_up_arrow, disable_ctrl_r, tmux);
-
- Ok(())
-}
-
fn ps_bool(value: bool) -> &'static str {
if value { "$true" } else { "$false" }
}
diff --git a/crates/atuin/src/command/client/init/xonsh.rs b/crates/atuin/src/command/client/init/xonsh.rs
index f14da3d8..9fb5730d 100644
--- a/crates/atuin/src/command/client/init/xonsh.rs
+++ b/crates/atuin/src/command/client/init/xonsh.rs
@@ -1,5 +1,4 @@
use atuin_client::settings::Tmux;
-use eyre::Result;
pub fn init_static(disable_up_arrow: bool, disable_ctrl_r: bool, _tmux: &Tmux) {
let base = include_str!("../../../shell/atuin.xsh");
@@ -21,9 +20,3 @@ pub fn init_static(disable_up_arrow: bool, disable_ctrl_r: bool, _tmux: &Tmux) {
);
println!("{base}");
}
-
-pub async fn init(disable_up_arrow: bool, disable_ctrl_r: bool, tmux: &Tmux) -> Result<()> {
- init_static(disable_up_arrow, disable_ctrl_r, tmux);
-
- Ok(())
-}
diff --git a/crates/atuin/src/command/client/init/zsh.rs b/crates/atuin/src/command/client/init/zsh.rs
index 392e987c..3f325167 100644
--- a/crates/atuin/src/command/client/init/zsh.rs
+++ b/crates/atuin/src/command/client/init/zsh.rs
@@ -1,5 +1,4 @@
use atuin_client::settings::Tmux;
-use eyre::Result;
fn print_tmux_config(tmux: &Tmux) {
if tmux.enabled {
@@ -37,13 +36,3 @@ bindkey -M vicmd 'k' atuin-up-search-vicmd";
}
}
}
-
-pub async fn init(
- disable_up_arrow: bool,
- disable_ctrl_r: bool,
- tmux: &Tmux,
-) -> Result<()> {
- init_static(disable_up_arrow, disable_ctrl_r, tmux);
-
- Ok(())
-}
diff --git a/crates/atuin/src/command/client/kv.rs b/crates/atuin/src/command/client/kv.rs
deleted file mode 100644
index 88e3edeb..00000000
--- a/crates/atuin/src/command/client/kv.rs
+++ /dev/null
@@ -1,138 +0,0 @@
-use std::io::{self, IsTerminal, Read};
-
-use clap::Subcommand;
-use eyre::{Context, Result, eyre};
-
-use atuin_client::{encryption, record::sqlite_store::SqliteStore, settings::Settings};
-use atuin_kv::store::KvStore;
-
-#[derive(Subcommand, Debug)]
-#[command(infer_subcommands = true)]
-pub enum Cmd {
- /// Set a key-value pair
- Set {
- /// Key to set
- #[arg(long, short)]
- key: String,
-
- /// Value to store (reads from stdin if not provided)
- value: Option<String>,
-
- /// Namespace for the key-value pair
- #[arg(long, short, default_value = "default")]
- namespace: String,
- },
-
- /// Delete one or more key-value pairs
- #[command(alias = "rm")]
- Delete {
- /// Keys to delete
- #[arg(required = true)]
- keys: Vec<String>,
-
- /// Namespace for the key-value pair
- #[arg(long, short, default_value = "default")]
- namespace: String,
- },
-
- /// Retrieve a saved value
- Get {
- /// Key to retrieve
- key: String,
-
- /// Namespace for the key-value pair
- #[arg(long, short, default_value = "default")]
- namespace: String,
- },
-
- /// List all keys in a namespace, or in all namespaces
- #[command(alias = "ls")]
- List {
- /// Namespace to list keys from
- #[arg(long, short, default_value = "default")]
- namespace: String,
-
- /// List all keys in all namespaces
- #[arg(long, short, alias = "all")]
- all_namespaces: bool,
- },
-
- /// Rebuild the KV store
- Rebuild,
-}
-
-impl Cmd {
- pub async fn run(&self, settings: &Settings, store: &SqliteStore) -> Result<()> {
- let encryption_key: [u8; 32] = encryption::load_key(settings)
- .context("could not load encryption key")?
- .into();
-
- let host_id = Settings::host_id().await?;
-
- let kv_db = atuin_kv::database::Database::new(settings.kv.db_path.clone(), 1.0).await?;
- let kv_store = KvStore::new(store.clone(), kv_db, host_id, encryption_key);
-
- match self {
- Self::Set {
- key,
- value,
- namespace,
- } => {
- if namespace.is_empty() {
- return Err(eyre!("namespace cannot be empty"));
- }
-
- let value = if let Some(v) = value {
- v.clone()
- } else if !io::stdin().is_terminal() {
- let mut buf = String::new();
- io::stdin()
- .read_to_string(&mut buf)
- .context("failed to read value from stdin")?;
- buf
- } else {
- return Err(eyre!(
- "no value provided. Pass as an argument or pipe via stdin"
- ));
- };
-
- kv_store.set(namespace, key, &value).await
- }
-
- Self::Delete { keys, namespace } => kv_store.delete(namespace, keys).await,
-
- Self::Get { key, namespace } => {
- let kv = kv_store.get(namespace, key).await?;
-
- if let Some(val) = kv {
- println!("{val}");
- }
-
- Ok(())
- }
-
- Self::List {
- namespace,
- all_namespaces,
- } => {
- let entries = if *all_namespaces {
- kv_store.list(None).await?
- } else {
- kv_store.list(Some(namespace)).await?
- };
-
- for entry in entries {
- if *all_namespaces {
- println!("{}.{}", entry.namespace, entry.key);
- } else {
- println!("{}", entry.key);
- }
- }
-
- Ok(())
- }
-
- Self::Rebuild {} => kv_store.build().await,
- }
- }
-}
diff --git a/crates/atuin/src/command/client/search/interactive.rs b/crates/atuin/src/command/client/search/interactive.rs
index 28b29824..8efe1f42 100644
--- a/crates/atuin/src/command/client/search/interactive.rs
+++ b/crates/atuin/src/command/client/search/interactive.rs
@@ -8,8 +8,6 @@ use std::io::Read as _;
use atuin_common::{shell::Shell, utils::Escapable as _};
use eyre::Result;
-use futures_util::FutureExt;
-use semver::Version;
use time::OffsetDateTime;
use unicode_width::{UnicodeWidthChar, UnicodeWidthStr};
@@ -117,7 +115,6 @@ pub fn to_compactness(f: &Frame, settings: &Settings) -> Compactness {
#[allow(clippy::struct_excessive_bools)]
pub struct State {
history_count: i64,
- update_needed: Option<Version>,
results_state: ListState,
switched_search_mode: bool,
search_mode: SearchMode,
@@ -931,7 +928,7 @@ impl State {
)
.split(header_chunk);
- let title = self.build_title(theme);
+ let title = Self::build_title(theme);
f.render_widget(title, header_chunks[0]);
let help = self.build_help(settings, theme);
@@ -1086,14 +1083,8 @@ impl State {
));
}
- fn build_title(&self, theme: &Theme) -> Paragraph<'_> {
- let title = if self.update_needed.is_some() {
- let error_style: Style = Style::from_crossterm(theme.get_error());
- Paragraph::new(Text::from(Span::styled(
- format!("Atuin v{VERSION} - UPDATE"),
- error_style.add_modifier(Modifier::BOLD),
- )))
- } else {
+ fn build_title(theme: &Theme) -> Paragraph<'_> {
+ let title = {
let style: Style = Style::from_crossterm(theme.as_style(Meaning::Base));
Paragraph::new(Text::from(Span::styled(
format!("Atuin v{VERSION}"),
@@ -1746,10 +1737,6 @@ pub async fn history(
// Put the cursor at the end of the query by default
input.end();
- let settings2 = settings.clone();
- let update_needed = tokio::spawn(async move { settings2.needs_update().await }).fuse();
- tokio::pin!(update_needed);
-
let initial_context = current_context().await?;
let history_count = db.history_count(false).await?;
@@ -1767,7 +1754,6 @@ pub async fn history(
let mut app = State {
history_count,
results_state: ListState::default(),
- update_needed: None,
switched_search_mode: false,
search_mode,
tab_index: 0,
@@ -1915,11 +1901,6 @@ pub async fn history(
}
}
}
- update_needed = &mut update_needed => {
- // Don't fail interactive search if update check fails
- // The update check is a nice-to-have feature, not critical
- app.update_needed = update_needed.ok().flatten();
- }
}
if initial_input != app.search.input.as_str()
@@ -2244,7 +2225,6 @@ mod tests {
let settings = Settings::utc();
let mut state = State {
history_count: 0,
- update_needed: None,
results_state: ListState::default(),
switched_search_mode: false,
search_mode: SearchMode::Fuzzy,
@@ -2299,7 +2279,6 @@ mod tests {
let mut state = State {
history_count: 1,
- update_needed: None,
results_state: ListState::default(),
switched_search_mode: false,
search_mode: SearchMode::Fuzzy,
@@ -2418,7 +2397,6 @@ mod tests {
let mut state = State {
history_count: 100,
- update_needed: None,
results_state: ListState::default(),
switched_search_mode: false,
search_mode: SearchMode::Fuzzy,
@@ -2477,7 +2455,6 @@ mod tests {
let mut state = State {
history_count: 100,
- update_needed: None,
results_state: ListState::default(),
switched_search_mode: false,
search_mode: SearchMode::Fuzzy,
@@ -2532,7 +2509,6 @@ mod tests {
let mut state = State {
history_count: 100,
- update_needed: None,
results_state: ListState::default(),
switched_search_mode: false,
search_mode: SearchMode::Fuzzy,
@@ -2583,7 +2559,6 @@ mod tests {
let mut state = State {
history_count: 100,
- update_needed: None,
results_state: ListState::default(),
switched_search_mode: false,
search_mode: SearchMode::Fuzzy,
@@ -2643,7 +2618,6 @@ mod tests {
let mut state = State {
history_count: 100,
- update_needed: None,
results_state: ListState::default(),
switched_search_mode: false,
search_mode: SearchMode::Fuzzy,
@@ -2704,7 +2678,6 @@ mod tests {
let settings = Settings::utc();
let mut state = State {
history_count: results_len as i64,
- update_needed: None,
results_state: ListState::default(),
switched_search_mode: false,
search_mode: SearchMode::Fuzzy,
@@ -3083,7 +3056,6 @@ mod tests {
let mut state = State {
history_count: 100,
- update_needed: None,
results_state: ListState::default(),
switched_search_mode: false,
search_mode: SearchMode::Fuzzy,
diff --git a/crates/atuin/src/command/client/sync.rs b/crates/atuin/src/command/client/sync.rs
index 15123a7f..12f0cacd 100644
--- a/crates/atuin/src/command/client/sync.rs
+++ b/crates/atuin/src/command/client/sync.rs
@@ -54,7 +54,7 @@ impl Cmd {
Self::Sync { force } => run(&settings, force, db, store).await,
Self::Login(l) => l.run(&settings, &store).await,
Self::Logout => account::logout::run().await,
- Self::Register(r) => r.run(&settings, &store).await,
+ Self::Register(r) => r.run(&settings).await,
Self::Status => status::run(&settings, db).await,
Self::Key { base64 } => {
use atuin_client::encryption::{encode_key, load_key};
diff --git a/crates/atuin/src/sync.rs b/crates/atuin/src/sync.rs
index 14982300..02e4db69 100644
--- a/crates/atuin/src/sync.rs
+++ b/crates/atuin/src/sync.rs
@@ -5,7 +5,6 @@ use atuin_client::{
settings::Settings,
};
use atuin_common::record::RecordId;
-use atuin_kv::store::KvStore;
// This is the only crate that ties together all other crates.
// Therefore, it's the only crate where functions tying together all stores can live
@@ -27,14 +26,9 @@ pub async fn build(
let downloaded = downloaded.unwrap_or(&[]);
- let kv_db = atuin_kv::database::Database::new(settings.kv.db_path.clone(), 1.0).await?;
-
let history_store = HistoryStore::new(store.clone(), host_id, encryption_key);
- let kv_store = KvStore::new(store.clone(), kv_db, host_id, encryption_key);
history_store.incremental_build(db, downloaded).await?;
- kv_store.build().await?;
-
Ok(())
}