aboutsummaryrefslogtreecommitdiffstats
path: root/crates/atuin-client/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/atuin-client/src')
-rw-r--r--crates/atuin-client/src/database.rs39
-rw-r--r--crates/atuin-client/src/history.rs18
-rw-r--r--crates/atuin-client/src/settings.rs14
3 files changed, 71 insertions, 0 deletions
diff --git a/crates/atuin-client/src/database.rs b/crates/atuin-client/src/database.rs
index 7c63368d..75ef51c3 100644
--- a/crates/atuin-client/src/database.rs
+++ b/crates/atuin-client/src/database.rs
@@ -5,6 +5,7 @@ use std::{
time::Duration,
};
+use crate::history::{AUTHOR_FILTER_ALL_AGENT, AUTHOR_FILTER_ALL_USER, KNOWN_AGENTS};
use async_trait::async_trait;
use atuin_common::utils;
use fs_err as fs;
@@ -53,6 +54,8 @@ pub struct OptFilters {
pub offset: Option<i64>,
pub reverse: bool,
pub include_duplicates: bool,
+ /// Author filter. Supports special values `$all-user` and `$all-agent`.
+ pub authors: Vec<String>,
}
pub async fn current_context() -> eyre::Result<Context> {
@@ -85,6 +88,38 @@ impl Context {
}
}
+/// Each entry is OR'd: `$all-user` → NOT IN agents, `$all-agent` → IN agents, literal → exact match.
+fn apply_author_filter(sql: &mut SqlBuilder, authors: &[String]) {
+ let mut conditions: Vec<String> = Vec::new();
+ let agent_list: String = KNOWN_AGENTS.iter().map(quote).join(", ");
+ let author_expr = "CASE \
+ WHEN author IS NULL OR trim(author) = '' THEN \
+ CASE \
+ WHEN instr(hostname, ':') > 0 THEN substr(hostname, instr(hostname, ':') + 1) \
+ ELSE hostname \
+ END \
+ ELSE author \
+ END";
+
+ for author in authors {
+ match author.as_str() {
+ AUTHOR_FILTER_ALL_USER => {
+ conditions.push(format!("{author_expr} NOT IN ({agent_list})"));
+ }
+ AUTHOR_FILTER_ALL_AGENT => {
+ conditions.push(format!("{author_expr} IN ({agent_list})"));
+ }
+ literal => {
+ conditions.push(format!("{author_expr} = {}", quote(literal)));
+ }
+ }
+ }
+
+ if !conditions.is_empty() {
+ sql.and_where(format!("({})", conditions.join(" OR ")));
+ }
+}
+
fn get_session_start_time(session_id: &str) -> Option<i64> {
if let Ok(uuid) = Uuid::parse_str(session_id)
&& let Some(timestamp) = uuid.get_timestamp()
@@ -595,6 +630,10 @@ impl Database for Sqlite {
.map(|after| sql.and_where_gt("timestamp", quote(after.unix_timestamp_nanos() as i64)))
});
+ if !filter_options.authors.is_empty() {
+ apply_author_filter(&mut sql, &filter_options.authors);
+ }
+
sql.and_where_is_null("deleted_at");
let query = sql.sql().expect("bug in search query. please report");
diff --git a/crates/atuin-client/src/history.rs b/crates/atuin-client/src/history.rs
index a5adc233..996208d9 100644
--- a/crates/atuin-client/src/history.rs
+++ b/crates/atuin-client/src/history.rs
@@ -18,6 +18,24 @@ use time::OffsetDateTime;
mod builder;
pub mod store;
+/// Known AI agent author values. Used to expand `$all-agent` and `$all-user` filters.
+pub const KNOWN_AGENTS: &[&str] = &["claude-code", "codex", "copilot"];
+pub const AUTHOR_FILTER_ALL_USER: &str = "$all-user";
+pub const AUTHOR_FILTER_ALL_AGENT: &str = "$all-agent";
+
+pub fn is_known_agent(author: &str) -> bool {
+ KNOWN_AGENTS.contains(&author)
+}
+
+pub fn author_matches_filters(author: &str, filters: &[String]) -> bool {
+ filters.is_empty()
+ || filters.iter().any(|filter| match filter.as_str() {
+ AUTHOR_FILTER_ALL_USER => !is_known_agent(author),
+ AUTHOR_FILTER_ALL_AGENT => is_known_agent(author),
+ literal => author == literal,
+ })
+}
+
pub(crate) const HISTORY_VERSION_V0: &str = "v0";
pub(crate) const HISTORY_VERSION_V1: &str = "v1";
const HISTORY_RECORD_VERSION_V0: u16 = 0;
diff --git a/crates/atuin-client/src/settings.rs b/crates/atuin-client/src/settings.rs
index 25c3bd65..5944de59 100644
--- a/crates/atuin-client/src/settings.rs
+++ b/crates/atuin-client/src/settings.rs
@@ -565,6 +565,13 @@ pub struct Search {
/// The overall frecency score multiplier for the search index (default: 1.0).
/// Applied after combining recency and frequency scores.
pub frecency_score_multiplier: f64,
+
+ /// Filter history by author. Special values:
+ /// - `$all-user`: any author that is NOT a known AI agent (default)
+ /// - `$all-agent`: any known AI agent author
+ /// - literal strings like "ellie", "claude-code"
+ #[serde(default = "Search::default_authors")]
+ pub authors: Vec<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
@@ -844,10 +851,17 @@ impl Default for Search {
recency_score_multiplier: 1.0,
frequency_score_multiplier: 1.0,
frecency_score_multiplier: 1.0,
+ authors: Self::default_authors(),
}
}
}
+impl Search {
+ fn default_authors() -> Vec<String> {
+ vec![crate::history::AUTHOR_FILTER_ALL_USER.to_string()]
+ }
+}
+
impl Default for Tmux {
fn default() -> Self {
Self {