Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions crates/atuin-client/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@ enter_accept = true
## Alternatively, set env NO_MOTION=true
# prefers_reduced_motion = false

[integrations]
## Automatically configure Claude Code Bash hooks in ~/.claude/settings.json
## when Atuin runs in daemon startup and direct history-end paths.
# claude = true

[stats]
## Set commands where we should consider the subcommand for statistics. Eg, kubectl get vs just kubectl
# common_subcommands = [
Expand Down
16 changes: 16 additions & 0 deletions crates/atuin-client/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,12 @@ pub struct Tmux {
pub height: String,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Integrations {
/// Automatically configure Claude Code hooks in ~/.claude/settings.json.
pub claude: bool,
}

#[derive(Default, Clone, Debug, Deserialize, Serialize)]
pub struct Ai {
/// The address of the Atuin AI endpoint. Used for AI features like command generation.
Expand Down Expand Up @@ -548,6 +554,12 @@ impl Default for Tmux {
}
}

impl Default for Integrations {
fn default() -> Self {
Self { claude: true }
}
}

// The preview height strategy also takes max_preview_height into account.
#[derive(Clone, Debug, Deserialize, Copy, PartialEq, Eq, ValueEnum, Serialize)]
pub enum PreviewStrategy {
Expand Down Expand Up @@ -830,6 +842,9 @@ pub struct Settings {
#[serde(default)]
pub daemon: Daemon,

#[serde(default)]
pub integrations: Integrations,

#[serde(default)]
pub search: Search,

Expand Down Expand Up @@ -1094,6 +1109,7 @@ impl Settings {
.set_default("smart_sort", false)?
.set_default("command_chaining", false)?
.set_default("store_failed", true)?
.set_default("integrations.claude", true)?
.set_default("daemon.sync_frequency", 300)?
.set_default("daemon.enabled", false)?
.set_default("daemon.autostart", false)?
Expand Down
31 changes: 20 additions & 11 deletions crates/atuin-common/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,6 @@ impl<T: AsRef<str>> Escapable for T {}
#[allow(unsafe_code)]
#[cfg(test)]
mod tests {
use pretty_assertions::assert_ne;

use super::*;

use std::collections::HashSet;
Expand Down Expand Up @@ -284,14 +282,25 @@ mod tests {

#[test]
fn dumb_random_test() {
// Obviously not a test of randomness, but make sure we haven't made some
// catastrophic error

assert_ne!(crypto_random_string::<1>(), crypto_random_string::<1>());
assert_ne!(crypto_random_string::<2>(), crypto_random_string::<2>());
assert_ne!(crypto_random_string::<4>(), crypto_random_string::<4>());
assert_ne!(crypto_random_string::<8>(), crypto_random_string::<8>());
assert_ne!(crypto_random_string::<16>(), crypto_random_string::<16>());
assert_ne!(crypto_random_string::<32>(), crypto_random_string::<32>());
fn assert_valid_random_string<const N: usize>() {
let encoded = crypto_random_string::<N>();
assert!(
encoded
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || byte == b'-' || byte == b'_')
);

let decoded = base64::Engine::decode(&BASE64_URL_SAFE_NO_PAD, encoded.as_bytes())
.expect("random string should be valid base64url");
assert_eq!(decoded.len(), N);
}

// Validate formatting and reversibility without relying on probabilistic uniqueness.
assert_valid_random_string::<1>();
assert_valid_random_string::<2>();
assert_valid_random_string::<4>();
assert_valid_random_string::<8>();
assert_valid_random_string::<16>();
assert_valid_random_string::<32>();
}
}
4 changes: 4 additions & 0 deletions crates/atuin/src/command/client/daemon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ use eyre::{Result, WrapErr, bail, eyre};
use fs4::fs_std::FileExt;
use tokio::time::sleep;

use super::history::maybe_ensure_claude_hook;

#[derive(clap::Args, Debug)]
pub struct Cmd {
/// Internal flag for daemonization
Expand Down Expand Up @@ -551,6 +553,8 @@ async fn run(settings: Settings, store: SqliteStore, history_db: Sqlite) -> Resu
let pidfile_path = PathBuf::from(&settings.daemon.pidfile_path);
let _pidfile_guard = PidfileGuard::acquire(&pidfile_path)?;

maybe_ensure_claude_hook(&settings);

listen(settings, store, history_db).await?;

Ok(())
Expand Down
Loading