aboutsummaryrefslogtreecommitdiffstats
path: root/crates/daemon
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-07-09 22:00:59 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-07-09 22:00:59 +0200
commit620cf8fa33835c1ce1e56b1028ff6e2a6fbe0f39 (patch)
treee97e54ff112c8e3fa9f88335bfd1275420a4d69f /crates/daemon
parentchore: Separate daemon, client, server, and lib into crates (diff)
downloadatuin-620cf8fa33835c1ce1e56b1028ff6e2a6fbe0f39.zip
chore: Add more things
Diffstat (limited to 'crates/daemon')
-rw-r--r--crates/daemon/Cargo.toml1
-rw-r--r--crates/daemon/build.rs58
-rw-r--r--crates/daemon/src/client.rs42
-rw-r--r--crates/daemon/src/components/history.rs10
-rw-r--r--crates/daemon/src/components/mod.rs16
-rw-r--r--crates/daemon/src/components/search.rs12
-rw-r--r--crates/daemon/src/components/semantic.rs12
-rw-r--r--crates/daemon/src/components/sync.rs6
-rw-r--r--crates/daemon/src/control/mod.rs6
-rw-r--r--crates/daemon/src/daemon.rs50
-rw-r--r--crates/daemon/src/events.rs4
-rw-r--r--crates/daemon/src/generated.rs10
-rw-r--r--crates/daemon/src/lib.rs26
-rw-r--r--crates/daemon/src/search/mod.rs54
-rw-r--r--crates/daemon/src/server.rs4
15 files changed, 149 insertions, 162 deletions
diff --git a/crates/daemon/Cargo.toml b/crates/daemon/Cargo.toml
index 509595b0..6d5de681 100644
--- a/crates/daemon/Cargo.toml
+++ b/crates/daemon/Cargo.toml
@@ -12,6 +12,7 @@ homepage = { workspace = true }
repository = { workspace = true }
[dependencies]
+turtle-common = { workspace = true }
async-trait = "0.1.58"
atuin-nucleo-matcher = { workspace = true }
atuin-nucleo = { workspace = true }
diff --git a/crates/daemon/build.rs b/crates/daemon/build.rs
index ad4bc3c8..646b8588 100644
--- a/crates/daemon/build.rs
+++ b/crates/daemon/build.rs
@@ -1,48 +1,34 @@
-use std::process::Command;
use std::{env, fs, path::PathBuf};
use protox::Compiler;
use protox::prost::Message;
fn main() -> Result<(), std::io::Error> {
- {
- let output = Command::new("git").args(["rev-parse", "HEAD"]).output();
+ let proto_paths = [
+ "proto/history.proto",
+ "proto/search.proto",
+ "proto/control.proto",
+ "proto/semantic.proto",
+ ];
+ let proto_include_dirs = ["proto"];
- let sha = match output {
- Ok(sha) => String::from_utf8(sha.stdout).unwrap(),
- Err(_) => String::from("NO_GIT"),
- };
+ let file_descriptor_set = Compiler::new(proto_include_dirs)
+ .map_err(std::io::Error::other)?
+ .include_source_info(true)
+ .include_imports(true)
+ .open_files(proto_paths)
+ .map_err(std::io::Error::other)?
+ .file_descriptor_set();
- println!("cargo:rustc-env=GIT_HASH={sha}");
- }
+ let file_descriptor_path = PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR not set"))
+ .join("file_descriptor_set.bin");
+ fs::write(&file_descriptor_path, file_descriptor_set.encode_to_vec()).unwrap();
- {
- let proto_paths = [
- "proto/history.proto",
- "proto/search.proto",
- "proto/control.proto",
- "proto/semantic.proto",
- ];
- let proto_include_dirs = ["proto"];
-
- let file_descriptor_set = Compiler::new(proto_include_dirs)
- .map_err(std::io::Error::other)?
- .include_source_info(true)
- .include_imports(true)
- .open_files(proto_paths)
- .map_err(std::io::Error::other)?
- .file_descriptor_set();
-
- let file_descriptor_path = PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR not set"))
- .join("file_descriptor_set.bin");
- fs::write(&file_descriptor_path, file_descriptor_set.encode_to_vec()).unwrap();
-
- tonic_prost_build::configure()
- .build_server(true)
- .file_descriptor_set_path(&file_descriptor_path)
- .skip_protoc_run()
- .compile_protos(&proto_paths, &proto_include_dirs)?;
- }
+ tonic_prost_build::configure()
+ .build_server(true)
+ .file_descriptor_set_path(&file_descriptor_path)
+ .skip_protoc_run()
+ .compile_protos(&proto_paths, &proto_include_dirs)?;
Ok(())
}
diff --git a/crates/daemon/src/client.rs b/crates/daemon/src/client.rs
index 2ea7ffc5..5cccb5ff 100644
--- a/crates/daemon/src/client.rs
+++ b/crates/daemon/src/client.rs
@@ -9,7 +9,7 @@ use hyper_util::rt::TokioIo;
use tokio::net::UnixStream;
use tracing::{Level, instrument, span};
-use crate::atuin_daemon::generated;
+use crate::generated;
use crate::{
atuin_client::{
database::Context,
@@ -41,12 +41,12 @@ use crate::{
},
};
-pub(crate) struct HistoryClient {
+pub struct HistoryClient {
client: HistoryServiceClient<Channel>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub(crate) enum DaemonClientErrorKind {
+pub enum DaemonClientErrorKind {
Connect,
Unavailable,
Unimplemented,
@@ -54,7 +54,7 @@ pub(crate) enum DaemonClientErrorKind {
}
#[must_use]
-pub(crate) fn classify_error(error: &eyre::Report) -> DaemonClientErrorKind {
+pub fn classify_error(error: &eyre::Report) -> DaemonClientErrorKind {
for cause in error.chain() {
if cause.downcast_ref::<tonic::transport::Error>().is_some() {
return DaemonClientErrorKind::Connect;
@@ -75,7 +75,7 @@ pub(crate) fn classify_error(error: &eyre::Report) -> DaemonClientErrorKind {
// Wrap the grpc client
impl HistoryClient {
#[cfg(unix)]
- pub(crate) async fn new(path: String) -> Result<Self> {
+ pub async fn new(path: String) -> Result<Self> {
use eyre::Context;
let log_path = path.clone();
@@ -100,7 +100,7 @@ impl HistoryClient {
Ok(Self { client })
}
- pub(crate) async fn start_history(&mut self, h: History) -> Result<StartHistoryReply> {
+ pub async fn start_history(&mut self, h: History) -> Result<StartHistoryReply> {
let req = StartHistoryRequest {
command: h.command,
cwd: h.cwd,
@@ -114,7 +114,7 @@ impl HistoryClient {
Ok(self.client.start_history(req).await?.into_inner())
}
- pub(crate) async fn end_history(
+ pub async fn end_history(
&mut self,
id: String,
duration: u64,
@@ -125,11 +125,11 @@ impl HistoryClient {
Ok(self.client.end_history(req).await?.into_inner())
}
- pub(crate) async fn status(&mut self) -> Result<StatusReply> {
+ pub async fn status(&mut self) -> Result<StatusReply> {
Ok(self.client.status(StatusRequest {}).await?.into_inner())
}
- pub(crate) async fn tail_history(&mut self) -> Result<tonic::Streaming<TailHistoryReply>> {
+ pub async fn tail_history(&mut self) -> Result<tonic::Streaming<TailHistoryReply>> {
Ok(self
.client
.tail_history(TailHistoryRequest {})
@@ -137,19 +137,19 @@ impl HistoryClient {
.into_inner())
}
- pub(crate) async fn shutdown(&mut self) -> Result<bool> {
+ pub async fn shutdown(&mut self) -> Result<bool> {
let resp = self.client.shutdown(ShutdownRequest {}).await?.into_inner();
Ok(resp.accepted)
}
}
-pub(crate) struct SearchClient {
+pub struct SearchClient {
client: SearchServiceClient<Channel>,
}
impl SearchClient {
#[cfg(unix)]
- pub(crate) async fn new(path: String) -> Result<Self> {
+ pub async fn new(path: String) -> Result<Self> {
let log_path = path.clone();
let channel = Endpoint::try_from("http://atuin_local_daemon:0")?
.connect_with_connector(service_fn(move |_: Uri| {
@@ -173,7 +173,7 @@ impl SearchClient {
}
#[instrument(skip_all, level = Level::TRACE, name = "daemon_client_search", fields(query = %query, query_id = query_id))]
- pub(crate) async fn search(
+ pub async fn search(
&mut self,
query: String,
query_id: u64,
@@ -222,13 +222,13 @@ impl From<Context> for RpcSearchContext {
}
}
-pub(crate) struct SemanticClient {
+pub struct SemanticClient {
client: SemanticServiceClient<Channel>,
}
impl SemanticClient {
#[cfg(unix)]
- pub(crate) async fn new(path: String) -> Result<Self> {
+ pub async fn new(path: String) -> Result<Self> {
let log_path = path.clone();
let channel = Endpoint::try_from("http://atuin_local_daemon:0")?
.connect_with_connector(service_fn(move |_: Uri| {
@@ -252,11 +252,11 @@ impl SemanticClient {
}
#[cfg(unix)]
- pub(crate) async fn from_settings(settings: &Settings) -> Result<Self> {
+ pub async fn from_settings(settings: &Settings) -> Result<Self> {
Self::new(settings.daemon.socket_path.clone()).await
}
- pub(crate) async fn record_commands(
+ pub async fn record_commands(
&mut self,
captures: Vec<CommandCapture>,
) -> Result<RecordCommandsReply> {
@@ -272,14 +272,14 @@ impl SemanticClient {
/// Client for the Control gRPC service.
///
/// Used to inject events into a running daemon from external processes.
-pub(crate) struct ControlClient {
+pub struct ControlClient {
client: ControlServiceClient<Channel>,
}
impl ControlClient {
/// Connect to the daemon's control service.
#[cfg(unix)]
- pub(crate) async fn new(path: String) -> Result<Self> {
+ pub async fn new(path: String) -> Result<Self> {
let log_path = path.clone();
let channel = Endpoint::try_from("http://atuin_local_daemon:0")?
.connect_with_connector(service_fn(move |_: Uri| {
@@ -304,12 +304,12 @@ impl ControlClient {
/// Connect using settings.
#[cfg(unix)]
- pub(crate) async fn from_settings(settings: &Settings) -> Result<Self> {
+ pub async fn from_settings(settings: &Settings) -> Result<Self> {
Self::new(settings.daemon.socket_path.clone()).await
}
/// Send an event to the daemon.
- pub(crate) async fn send_event(&mut self, event: DaemonEvent) -> Result<()> {
+ pub async fn send_event(&mut self, event: DaemonEvent) -> Result<()> {
let proto_event = daemon_event_to_proto(event);
let request = SendEventRequest {
event: Some(proto_event),
diff --git a/crates/daemon/src/components/history.rs b/crates/daemon/src/components/history.rs
index b4f91b06..a75ff774 100644
--- a/crates/daemon/src/components/history.rs
+++ b/crates/daemon/src/components/history.rs
@@ -15,7 +15,7 @@ use tokio_stream::Stream;
use tonic::{Request, Response, Status};
use tracing::{Level, instrument};
-use crate::atuin_daemon::{
+use crate::{
daemon::{Component, DaemonHandle},
events::DaemonEvent,
generated::history::{
@@ -35,7 +35,7 @@ const DAEMON_PROTOCOL_VERSION: u32 = 1;
/// - Saves completed commands to the database and record store
/// - Emits history events for other components (e.g., search indexing)
/// - Provides the History gRPC service
-pub(crate) struct HistoryComponent {
+pub struct HistoryComponent {
inner: Arc<HistoryComponentInner>,
}
@@ -52,7 +52,7 @@ struct HistoryComponentInner {
impl HistoryComponent {
/// Create a new history component.
- pub(crate) fn new() -> Self {
+ pub fn new() -> Self {
Self {
inner: Arc::new(HistoryComponentInner {
running: DashMap::new(),
@@ -65,7 +65,7 @@ impl HistoryComponent {
/// Get the gRPC service for this component.
///
/// This returns a tonic service that can be added to a gRPC server.
- pub(crate) fn grpc_service(&self) -> HistoryServer<HistoryGrpcService> {
+ pub fn grpc_service(&self) -> HistoryServer<HistoryGrpcService> {
HistoryServer::new(HistoryGrpcService {
inner: self.inner.clone(),
})
@@ -111,7 +111,7 @@ impl Component for HistoryComponent {
/// The gRPC service implementation.
///
/// This is a thin wrapper that delegates to the component's shared state.
-pub(crate) struct HistoryGrpcService {
+pub struct HistoryGrpcService {
inner: Arc<HistoryComponentInner>,
}
diff --git a/crates/daemon/src/components/mod.rs b/crates/daemon/src/components/mod.rs
index 5a93fbc1..447e31df 100644
--- a/crates/daemon/src/components/mod.rs
+++ b/crates/daemon/src/components/mod.rs
@@ -14,12 +14,12 @@
//! - [`semantic::SemanticComponent`]: In-memory semantic command captures
//! - [`sync::SyncComponent`]: Cloud sync
-pub(crate) mod history;
-pub(crate) mod search;
-pub(crate) mod semantic;
-pub(crate) mod sync;
+pub mod history;
+pub mod search;
+pub mod semantic;
+pub mod sync;
-pub(crate) use history::HistoryComponent;
-pub(crate) use search::SearchComponent;
-pub(crate) use semantic::SemanticComponent;
-pub(crate) use sync::SyncComponent;
+pub use history::HistoryComponent;
+pub use search::SearchComponent;
+pub use semantic::SemanticComponent;
+pub use sync::SyncComponent;
diff --git a/crates/daemon/src/components/search.rs b/crates/daemon/src/components/search.rs
index bcd60cc4..91f2db17 100644
--- a/crates/daemon/src/components/search.rs
+++ b/crates/daemon/src/components/search.rs
@@ -12,7 +12,7 @@ use tonic::{Request, Response, Status, Streaming};
use tracing::{Level, debug, info, instrument, span, trace};
use uuid::Uuid;
-use crate::atuin_daemon::{
+use crate::{
daemon::{Component, DaemonHandle},
events::DaemonEvent,
generated::search::{
@@ -34,7 +34,7 @@ const FRECENCY_REFRESH_INTERVAL_SECS: u64 = 60;
/// - Loads history from the database on startup
/// - Updates the index when history events occur
/// - Provides the Search gRPC service
-pub(crate) struct SearchComponent {
+pub struct SearchComponent {
index: Arc<RwLock<SearchIndex>>,
handle: RwLock<Option<DaemonHandle>>,
loader_handle: Option<tokio::task::JoinHandle<()>>,
@@ -43,7 +43,7 @@ pub(crate) struct SearchComponent {
impl SearchComponent {
/// Create a new search component.
- pub(crate) fn new() -> Self {
+ pub fn new() -> Self {
Self {
index: Arc::new(RwLock::new(SearchIndex::new())),
handle: RwLock::new(None),
@@ -53,7 +53,7 @@ impl SearchComponent {
}
/// Get the gRPC service for this component.
- pub(crate) fn grpc_service(&self) -> SearchServer<SearchGrpcService> {
+ pub fn grpc_service(&self) -> SearchServer<SearchGrpcService> {
SearchServer::new(SearchGrpcService {
index: self.index.clone(),
})
@@ -276,7 +276,7 @@ impl Component for SearchComponent {
}
/// The gRPC service implementation.
-pub(crate) struct SearchGrpcService {
+pub struct SearchGrpcService {
index: Arc<RwLock<SearchIndex>>,
}
@@ -398,7 +398,7 @@ fn convert_filter_mode(
}
#[cfg(not(windows))]
-pub(crate) fn with_trailing_slash(s: &str) -> String {
+pub fn with_trailing_slash(s: &str) -> String {
if s.ends_with('/') {
s.to_string()
} else {
diff --git a/crates/daemon/src/components/semantic.rs b/crates/daemon/src/components/semantic.rs
index e1d376de..02f5c3d1 100644
--- a/crates/daemon/src/components/semantic.rs
+++ b/crates/daemon/src/components/semantic.rs
@@ -9,13 +9,13 @@ use std::fmt::{Display, Formatter};
use std::sync::Arc;
use crate::atuin_client::history::{History, HistoryId};
-use crate::atuin_daemon::generated::semantic;
+use crate::generated::semantic;
use eyre::Result;
use tokio::sync::Mutex;
use tonic::{Request, Response, Status, Streaming};
use tracing::{Level, instrument};
-use crate::atuin_daemon::{
+use crate::{
daemon::{Component, DaemonHandle},
events::DaemonEvent,
generated::semantic::{
@@ -30,7 +30,7 @@ const MAX_BYTES_PER_SESSION: usize = 32 * 1024 * 1024;
const MAX_PENDING_HISTORIES: usize = 128;
/// Stores completed command captures and associates them with history events.
-pub(crate) struct SemanticComponent {
+pub struct SemanticComponent {
inner: Arc<SemanticComponentInner>,
}
@@ -84,7 +84,7 @@ struct SemanticCommandRecord {
}
impl SemanticComponent {
- pub(crate) fn new() -> Self {
+ pub fn new() -> Self {
Self {
inner: Arc::new(SemanticComponentInner {
state: Mutex::new(SemanticState::default()),
@@ -92,7 +92,7 @@ impl SemanticComponent {
}
}
- pub(crate) fn grpc_service(&self) -> SemanticServer<SemanticGrpcService> {
+ pub fn grpc_service(&self) -> SemanticServer<SemanticGrpcService> {
SemanticServer::new(SemanticGrpcService {
inner: self.inner.clone(),
})
@@ -453,7 +453,7 @@ impl Display for SessionId {
}
}
-pub(crate) struct SemanticGrpcService {
+pub struct SemanticGrpcService {
inner: Arc<SemanticComponentInner>,
}
diff --git a/crates/daemon/src/components/sync.rs b/crates/daemon/src/components/sync.rs
index 20d49839..e898e8bd 100644
--- a/crates/daemon/src/components/sync.rs
+++ b/crates/daemon/src/components/sync.rs
@@ -11,7 +11,7 @@ use tokio::time::{self, MissedTickBehavior};
use crate::atuin_client::{history::store::HistoryStore, record::sync, settings::Settings};
-use crate::atuin_daemon::{
+use crate::{
daemon::{Component, DaemonHandle},
events::DaemonEvent,
};
@@ -41,14 +41,14 @@ enum SyncState {
/// - Implements exponential backoff on sync failures
/// - Responds to [`ForceSync`] events for immediate sync
/// - Emits SyncCompleted/SyncFailed events
-pub(crate) struct SyncComponent {
+pub struct SyncComponent {
task_handle: Option<tokio::task::JoinHandle<()>>,
command_tx: Option<mpsc::Sender<SyncCommand>>,
}
impl SyncComponent {
/// Create a new sync component.
- pub(crate) fn new() -> Self {
+ pub fn new() -> Self {
Self {
task_handle: None,
command_tx: None,
diff --git a/crates/daemon/src/control/mod.rs b/crates/daemon/src/control/mod.rs
index 79398d61..fcc2a0b8 100644
--- a/crates/daemon/src/control/mod.rs
+++ b/crates/daemon/src/control/mod.rs
@@ -23,18 +23,18 @@ use crate::{
///
/// This service is used by external processes to inject events into the daemon.
/// It's not a component - it's part of the daemon's core infrastructure.
-pub(crate) struct ControlService {
+pub struct ControlService {
handle: DaemonHandle,
}
impl ControlService {
/// Create a new control service with the given daemon handle.
- pub(crate) fn new(handle: DaemonHandle) -> Self {
+ pub fn new(handle: DaemonHandle) -> Self {
Self { handle }
}
/// Get a tonic server for this service.
- pub(crate) fn into_server(self) -> ControlServer<Self> {
+ pub fn into_server(self) -> ControlServer<Self> {
ControlServer::new(self)
}
}
diff --git a/crates/daemon/src/daemon.rs b/crates/daemon/src/daemon.rs
index 80aaeef8..8f0a5957 100644
--- a/crates/daemon/src/daemon.rs
+++ b/crates/daemon/src/daemon.rs
@@ -17,7 +17,7 @@ use crate::atuin_client::{
use eyre::{Context, Result};
use tokio::sync::{RwLock, broadcast};
-use crate::atuin_daemon::events::DaemonEvent;
+use crate::events::DaemonEvent;
// ============================================================================
// DaemonState
@@ -27,7 +27,7 @@ use crate::atuin_daemon::events::DaemonEvent;
///
/// This contains all the resources that components and services need access to.
/// The state is wrapped in an `Arc` and accessed via [`DaemonHandle`].
-pub(crate) struct DaemonState {
+pub struct DaemonState {
// Event bus
event_tx: broadcast::Sender<DaemonEvent>,
@@ -72,7 +72,7 @@ pub(crate) struct DaemonState {
/// let history = handle.history_db().load(id).await?;
/// ```
#[derive(Clone)]
-pub(crate) struct DaemonHandle {
+pub struct DaemonHandle {
state: Arc<DaemonState>,
}
@@ -83,7 +83,7 @@ impl DaemonHandle {
///
/// This is fire-and-forget - if no receivers are listening (which shouldn't
/// happen in normal operation), the event is dropped silently.
- pub(crate) fn emit(&self, event: DaemonEvent) {
+ pub fn emit(&self, event: DaemonEvent) {
if let Err(e) = self.state.event_tx.send(event) {
tracing::warn!("failed to emit event (no receivers?): {e}");
}
@@ -94,12 +94,12 @@ impl DaemonHandle {
/// Returns a receiver that will receive all events emitted after this call.
/// Useful for components that need to listen for events outside of the
/// normal `handle_event` callback flow.
- pub(crate) fn subscribe(&self) -> broadcast::Receiver<DaemonEvent> {
+ pub fn subscribe(&self) -> broadcast::Receiver<DaemonEvent> {
self.state.event_tx.subscribe()
}
/// Request graceful shutdown of the daemon.
- pub(crate) fn shutdown(&self) {
+ pub fn shutdown(&self) {
self.emit(DaemonEvent::ShutdownRequested);
}
@@ -109,7 +109,7 @@ impl DaemonHandle {
///
/// This acquires a read lock on the settings. For most use cases, clone
/// the settings if you need to hold onto them.
- pub(crate) async fn settings(&self) -> tokio::sync::RwLockReadGuard<'_, Settings> {
+ pub async fn settings(&self) -> tokio::sync::RwLockReadGuard<'_, Settings> {
self.state.settings.read().await
}
@@ -117,26 +117,26 @@ impl DaemonHandle {
///
/// Use this when settings have already been loaded (e.g., from a file watcher)
/// to avoid parsing the config file twice.
- pub(crate) async fn apply_settings(&self, settings: Settings) {
+ pub async fn apply_settings(&self, settings: Settings) {
*self.state.settings.write().await = settings;
self.emit(DaemonEvent::SettingsReloaded);
tracing::info!("settings applied");
}
/// Get the encryption key.
- pub(crate) fn encryption_key(&self) -> &[u8; 32] {
+ pub fn encryption_key(&self) -> &[u8; 32] {
&self.state.encryption_key
}
// ---- Database ----
/// Get a reference to the history database.
- pub(crate) fn history_db(&self) -> &HistoryDatabase {
+ pub fn history_db(&self) -> &HistoryDatabase {
&self.state.history_db
}
/// Get a reference to the record store.
- pub(crate) fn store(&self) -> &SqliteStore {
+ pub fn store(&self) -> &SqliteStore {
&self.state.store
}
}
@@ -171,7 +171,7 @@ impl std::fmt::Debug for DaemonHandle {
/// # Example
///
/// ```ignore
-/// pub(crate) struct MyComponent {
+/// pub struct MyComponent {
/// handle: Option<DaemonHandle>,
/// }
///
@@ -203,7 +203,7 @@ impl std::fmt::Debug for DaemonHandle {
/// }
/// ```
#[tonic::async_trait]
-pub(crate) trait Component: Send + Sync {
+pub trait Component: Send + Sync {
/// Human-readable name for logging and debugging.
fn name(&self) -> &'static str;
@@ -247,21 +247,21 @@ pub(crate) trait Component: Send + Sync {
///
/// Events emitted during handling are queued and processed in subsequent
/// iterations, ensuring the loop eventually drains.
-pub(crate) struct Daemon {
+pub struct Daemon {
components: Vec<Box<dyn Component>>,
handle: DaemonHandle,
}
impl Daemon {
/// Create a new daemon builder.
- pub(crate) fn builder(settings: Settings) -> DaemonBuilder {
+ pub fn builder(settings: Settings) -> DaemonBuilder {
DaemonBuilder::new(settings)
}
/// Get a clone of the daemon handle.
///
/// The handle can be used to emit events, access settings, etc.
- pub(crate) fn handle(&self) -> DaemonHandle {
+ pub fn handle(&self) -> DaemonHandle {
self.handle.clone()
}
@@ -269,7 +269,7 @@ impl Daemon {
///
/// This must be called before `run_event_loop()`. It initializes all
/// registered components with the daemon handle.
- pub(crate) async fn start_components(&mut self) -> Result<()> {
+ pub async fn start_components(&mut self) -> Result<()> {
for component in &mut self.components {
tracing::info!(component = component.name(), "starting component");
component
@@ -284,7 +284,7 @@ impl Daemon {
///
/// This processes events until a [`ShutdownRequested`] event is received.
/// Components must be started first via `start_components()`.
- pub(crate) async fn run_event_loop(&mut self) -> Result<()> {
+ pub async fn run_event_loop(&mut self) -> Result<()> {
let mut event_rx = self.handle.subscribe();
loop {
match event_rx.recv().await {
@@ -314,7 +314,7 @@ impl Daemon {
/// Stop all components.
///
/// This performs graceful shutdown of all components.
- pub(crate) async fn stop_components(&mut self) {
+ pub async fn stop_components(&mut self) {
for component in &mut self.components {
tracing::info!(component = component.name(), "stopping component");
if let Err(e) = component.stop().await {
@@ -361,7 +361,7 @@ impl Daemon {
///
/// daemon.run().await?;
/// ```
-pub(crate) struct DaemonBuilder {
+pub struct DaemonBuilder {
settings: Settings,
store: Option<SqliteStore>,
history_db: Option<HistoryDatabase>,
@@ -370,7 +370,7 @@ pub(crate) struct DaemonBuilder {
impl DaemonBuilder {
/// Create a new daemon builder with the given settings.
- pub(crate) fn new(settings: Settings) -> Self {
+ pub fn new(settings: Settings) -> Self {
Self {
settings,
store: None,
@@ -380,13 +380,13 @@ impl DaemonBuilder {
}
/// Set the record store.
- pub(crate) fn store(mut self, store: SqliteStore) -> Self {
+ pub fn store(mut self, store: SqliteStore) -> Self {
self.store = Some(store);
self
}
/// Set the history database.
- pub(crate) fn history_db(mut self, db: HistoryDatabase) -> Self {
+ pub fn history_db(mut self, db: HistoryDatabase) -> Self {
self.history_db = Some(db);
self
}
@@ -394,7 +394,7 @@ impl DaemonBuilder {
/// Register a component.
///
/// Components are started in registration order and stopped in reverse order.
- pub(crate) fn component(mut self, component: impl Component + 'static) -> Self {
+ pub fn component(mut self, component: impl Component + 'static) -> Self {
self.components.push(Box::new(component));
self
}
@@ -402,7 +402,7 @@ impl DaemonBuilder {
/// Build the daemon.
///
/// This loads the encryption key and creates the daemon state.
- pub(crate) fn build(self) -> Result<Daemon> {
+ pub fn build(self) -> Result<Daemon> {
let store = self.store.ok_or_else(|| eyre::eyre!("store is required"))?;
let history_db = self
.history_db
diff --git a/crates/daemon/src/events.rs b/crates/daemon/src/events.rs
index d379277d..32ed1ff1 100644
--- a/crates/daemon/src/events.rs
+++ b/crates/daemon/src/events.rs
@@ -8,14 +8,14 @@
//! Control gRPC service.
use crate::atuin_client::history::{History, HistoryId};
-use crate::atuin_common::record::RecordId;
+use turtle_common::record::RecordId;
/// Events that flow through the daemon's event bus.
///
/// Events are broadcast to all components. Each component decides which
/// events it cares about in its `handle_event` implementation.
#[derive(Debug, Clone)]
-pub(crate) enum DaemonEvent {
+pub enum DaemonEvent {
// ---- History lifecycle ----
/// A command has started running.
HistoryStarted(History),
diff --git a/crates/daemon/src/generated.rs b/crates/daemon/src/generated.rs
index a3ea4d9d..6620e94c 100644
--- a/crates/daemon/src/generated.rs
+++ b/crates/daemon/src/generated.rs
@@ -11,14 +11,14 @@
)]
/// Semantic command capture gRPC service types.
-pub(crate) mod semantic {
+pub mod semantic {
tonic::include_proto!("semantic");
}
/// Search module for the daemon gRPC search service.
///
/// This module provides fuzzy search over command history using Nucleo.
-pub(crate) mod search {
+pub mod search {
// Include the generated proto code
tonic::include_proto!("search");
}
@@ -26,7 +26,7 @@ pub(crate) mod search {
/// History module for the daemon gRPC history service.
///
/// This module contains the proto-generated types for the history gRPC service.
-pub(crate) mod history {
+pub mod history {
// Include the generated proto code
tonic::include_proto!("history");
}
@@ -35,10 +35,10 @@ pub(crate) mod history {
///
/// This module provides the gRPC service that allows external processes
/// (like CLI commands) to inject events into the daemon's event bus.
-pub(crate) mod control {
+pub mod control {
// Include the generated proto code
tonic::include_proto!("control");
// Re-export the service
- pub(crate) use crate::atuin_daemon::control::ControlService;
+ pub use crate::control::ControlService;
}
diff --git a/crates/daemon/src/lib.rs b/crates/daemon/src/lib.rs
index 5f0f489e..1abf0314 100644
--- a/crates/daemon/src/lib.rs
+++ b/crates/daemon/src/lib.rs
@@ -3,31 +3,31 @@ use crate::atuin_client::record::sqlite_store::SqliteStore;
use crate::atuin_client::settings::{Settings, watcher::global_settings_watcher};
use eyre::Result;
-pub(crate) mod client;
-pub(crate) mod components;
-pub(crate) mod control;
-pub(crate) mod daemon;
-pub(crate) mod events;
-pub(crate) mod search;
-pub(crate) mod server;
+pub mod client;
+pub mod components;
+pub mod control;
+pub mod daemon;
+pub mod events;
+pub mod search;
+pub mod server;
-pub(crate) mod generated;
+pub mod generated;
// Re-export core daemon types for convenience
-pub(crate) use daemon::Daemon;
-pub(crate) use events::DaemonEvent;
+pub use daemon::Daemon;
+pub use events::DaemonEvent;
// Re-export components
-pub(crate) use components::{HistoryComponent, SearchComponent, SemanticComponent, SyncComponent};
+pub use components::{HistoryComponent, SearchComponent, SemanticComponent, SyncComponent};
// Re-export client helpers
-pub(crate) use client::SemanticClient;
+pub use client::SemanticClient;
/// Boot the daemon using the new component-based architecture.
///
/// This creates a daemon with the standard components (history, search, sync),
/// starts the gRPC server with their services, and runs the event loop.
-pub(crate) async fn boot(
+pub async fn boot(
settings: Settings,
store: SqliteStore,
history_db: HistoryDatabase,
diff --git a/crates/daemon/src/search/mod.rs b/crates/daemon/src/search/mod.rs
index 02c79c9c..b4d03bcd 100644
--- a/crates/daemon/src/search/mod.rs
+++ b/crates/daemon/src/search/mod.rs
@@ -37,16 +37,16 @@ fn format_uuid_bytes(bytes: &[u8; 16]) -> String {
/// Pre-computed frecency data for O(1) lookup.
#[derive(Debug, Clone, Default)]
-pub(crate) struct FrecencyData {
+pub struct FrecencyData {
/// Total number of times this command was used.
- pub(crate) count: u32,
+ pub count: u32,
/// Most recent usage timestamp (unix seconds).
- pub(crate) last_used: i64,
+ pub last_used: i64,
}
impl FrecencyData {
/// Record a new usage of this command.
- pub(crate) fn record_use(&mut self, timestamp: i64) {
+ pub fn record_use(&mut self, timestamp: i64) {
self.count += 1;
if timestamp > self.last_used {
self.last_used = timestamp;
@@ -65,7 +65,7 @@ impl FrecencyData {
/// A multiplier of 0.0 disables that component, 1.0 is unchanged, 2.0 doubles weight.
/// Values like 0.5 reduce weight by half, 1.5 increases by 50%, etc.
#[instrument(level = Level::TRACE, name = "index_frecency_compute")]
- pub(crate) fn compute(&self, now: i64, recency_mul: f64, frequency_mul: f64) -> u32 {
+ pub fn compute(&self, now: i64, recency_mul: f64, frequency_mul: f64) -> u32 {
if self.count == 0 {
return 0;
}
@@ -100,13 +100,13 @@ impl FrecencyData {
}
/// Data for a unique command.
-pub(crate) struct CommandData {
+pub struct CommandData {
/// History ID of the most recent invocation (16-byte UUID).
most_recent_id: [u8; 16],
/// Timestamp of the most recent invocation.
most_recent_timestamp: i64,
/// Pre-computed global frecency.
- pub(crate) global_frecency: FrecencyData,
+ pub global_frecency: FrecencyData,
// Pre-computed indexes for O(1) filter lookups
// Using HashSet instead of DashSet since CommandData lives inside DashMap (already synchronized)
@@ -121,7 +121,7 @@ pub(crate) struct CommandData {
impl CommandData {
/// Create a new [`CommandData`] from a history entry.
/// Returns None if the history entry has invalid UUIDs.
- pub(crate) fn new(history: &History, interner: &ThreadedRodeo) -> Option<Self> {
+ pub fn new(history: &History, interner: &ThreadedRodeo) -> Option<Self> {
let history_id = parse_uuid_bytes(&history.id.0)?;
let session = parse_uuid_bytes(&history.session)?;
let timestamp = history.timestamp.unix_timestamp();
@@ -153,7 +153,7 @@ impl CommandData {
/// Add an invocation from a history entry.
/// Returns false if the history entry has invalid UUIDs.
- pub(crate) fn add_invocation(&mut self, history: &History, interner: &ThreadedRodeo) -> bool {
+ pub fn add_invocation(&mut self, history: &History, interner: &ThreadedRodeo) -> bool {
let Some(history_id) = parse_uuid_bytes(&history.id.0) else {
return false;
};
@@ -182,13 +182,13 @@ impl CommandData {
}
/// Get the most recent history ID for this command.
- pub(crate) fn most_recent_id(&self) -> String {
+ pub fn most_recent_id(&self) -> String {
format_uuid_bytes(&self.most_recent_id)
}
/// Check if any invocation matches a directory filter (exact match).
/// O(1) lookup using pre-computed index.
- pub(crate) fn has_invocation_in_dir(&self, dir: &str, interner: &ThreadedRodeo) -> bool {
+ pub fn has_invocation_in_dir(&self, dir: &str, interner: &ThreadedRodeo) -> bool {
interner
.get(dir)
.is_some_and(|spur| self.directories.contains(&spur))
@@ -196,7 +196,7 @@ impl CommandData {
/// Check if any invocation matches a directory prefix (workspace/git root).
/// O(n) where n = number of unique directories for this command.
- pub(crate) fn has_invocation_in_workspace(
+ pub fn has_invocation_in_workspace(
&self,
prefix: &str,
interner: &ThreadedRodeo,
@@ -208,7 +208,7 @@ impl CommandData {
/// Check if any invocation matches a hostname.
/// O(1) lookup using pre-computed index.
- pub(crate) fn has_invocation_on_host(&self, hostname: &str, interner: &ThreadedRodeo) -> bool {
+ pub fn has_invocation_on_host(&self, hostname: &str, interner: &ThreadedRodeo) -> bool {
interner
.get(hostname)
.is_some_and(|spur| self.hosts.contains(&spur))
@@ -216,14 +216,14 @@ impl CommandData {
/// Check if any invocation matches a session.
/// O(1) lookup using pre-computed index.
- pub(crate) fn has_invocation_in_session(&self, session: &str) -> bool {
+ pub fn has_invocation_in_session(&self, session: &str) -> bool {
parse_uuid_bytes(session).is_some_and(|bytes| self.sessions.contains(&bytes))
}
}
/// Filter mode for search queries.
#[derive(Debug, Clone, PartialEq, Eq)]
-pub(crate) enum IndexFilterMode {
+pub enum IndexFilterMode {
/// No filtering - search all commands.
Global,
/// Filter to commands run in a specific directory.
@@ -238,15 +238,15 @@ pub(crate) enum IndexFilterMode {
/// Context for search queries.
#[derive(Debug, Clone, Default)]
-pub(crate) struct QueryContext {
+pub struct QueryContext {
#[expect(dead_code)]
- pub(crate) cwd: Option<String>,
+ pub cwd: Option<String>,
#[expect(dead_code)]
- pub(crate) git_root: Option<String>,
+ pub git_root: Option<String>,
#[expect(dead_code)]
- pub(crate) hostname: Option<String>,
+ pub hostname: Option<String>,
#[expect(dead_code)]
- pub(crate) session_id: Option<String>,
+ pub session_id: Option<String>,
}
/// Shareable frecency map: command -> frecency score.
@@ -261,7 +261,7 @@ type FrecencyMap = Arc<HashMap<Arc<str>, u32>>;
/// Global frecency is precomputed by a background task and used for scoring.
/// If frecency data is not available, search still works but without frecency ranking;
/// although this should never happen due to precomputing the frecency map.
-pub(crate) struct SearchIndex {
+pub struct SearchIndex {
/// Map from command text to command data.
/// Using `DashMap` for concurrent read/write access, wrapped in Arc for sharing with scorer.
/// Keys are Arc<str> to enable zero-copy sharing with `frecency_map`.
@@ -278,7 +278,7 @@ pub(crate) struct SearchIndex {
impl SearchIndex {
/// Create a new empty search index.
- pub(crate) fn new() -> Self {
+ pub fn new() -> Self {
let nucleo_config = atuin_nucleo::Config::DEFAULT;
// Single column for command text
let nucleo = Nucleo::<String>::new(nucleo_config, Arc::new(|| {}), None, 1);
@@ -297,7 +297,7 @@ impl SearchIndex {
///
/// If the command already exists, updates its invocation data.
/// If it's a new command, adds it to both the map and Nucleo.
- pub(crate) fn add_history(&self, history: &History) {
+ pub fn add_history(&self, history: &History) {
let command = history.command.as_str();
// DashMap with Arc<str> keys can be looked up with &str via Borrow trait
@@ -320,14 +320,14 @@ impl SearchIndex {
}
/// Add multiple history entries to the index.
- pub(crate) fn add_histories(&self, histories: &[History]) {
+ pub fn add_histories(&self, histories: &[History]) {
for history in histories {
self.add_history(history);
}
}
/// Get the number of unique commands in the index.
- pub(crate) fn command_count(&self) -> usize {
+ pub fn command_count(&self) -> usize {
self.commands.len()
}
@@ -340,7 +340,7 @@ impl SearchIndex {
clippy::significant_drop_tightening,
reason = "The nucleo early drop is a false-positive"
)]
- pub(crate) async fn search(
+ pub async fn search(
&self,
query: &str,
filter_mode: IndexFilterMode,
@@ -403,7 +403,7 @@ impl SearchIndex {
/// - `frequency_score_multiplier`: Weight for frequency component
/// - `frecency_score_multiplier`: Overall multiplier for final score
#[instrument(skip_all, level = Level::DEBUG, name = "rebuild_frecency")]
- pub(crate) async fn rebuild_frecency(&self, search_settings: &Search) {
+ pub async fn rebuild_frecency(&self, search_settings: &Search) {
let now = OffsetDateTime::now_utc().unix_timestamp();
let mut frecency_map: HashMap<Arc<str>, u32> = HashMap::new();
diff --git a/crates/daemon/src/server.rs b/crates/daemon/src/server.rs
index 36954cca..335f8260 100644
--- a/crates/daemon/src/server.rs
+++ b/crates/daemon/src/server.rs
@@ -21,7 +21,7 @@ use crate::{
/// This starts the gRPC server in the background and returns immediately.
/// The server will shut down when a [`ShutdownRequested`] event is received.
#[cfg(unix)]
-pub(crate) fn run_grpc_server(
+pub fn run_grpc_server(
settings: &Settings,
history_service: HistoryServer<HistoryGrpcService>,
search_service: SearchServer<SearchGrpcService>,
@@ -82,7 +82,7 @@ pub(crate) fn run_grpc_server(
let mut rx = handle.subscribe();
loop {
- use crate::atuin_daemon::DaemonEvent;
+ use crate::DaemonEvent;
match rx.recv().await {
Err(_) | Ok(DaemonEvent::ShutdownRequested) => break,