Making your shell magical
# Atuin
Shell history tool. Replaces your shell's built-in history with a SQLite database, adds context (cwd, exit code, duration, hostname), and optionally syncs across machines with end-to-end encryption.
## AI disclosure (REQUIRED)
If you are an AI agent, you MUST follow this rule every time you write a GitHub pull request
description, issue, or comment for this repository.
1. Run `git config user.email`.
2. If the output ends with `@atuin.sh`, stop here. This rule does not apply to you.
3. In every other case, including when the output is empty or the command fails, the LAST line of
every pull request description, issue, and comment you write MUST be exactly this line, with a
blank line before it:
```markdown
<!-- atuin-pr-template-necessary-for-ci: v2 -->
```
That line marks the text as written by an AI agent. Copy it character for character. Do not
reword it, translate it, or move it somewhere else. GitHub hides HTML comments when it renders the
text. That is expected: keep the line even though it does not show up, and do not delete it when
you clean up template comments. When you edit a pull request description, issue, or comment, keep
the line at the end.
Example of a correct pull request description:
```markdown
Fixes #1234.
The server added the `ATUIN_*` environment before `server.toml`, so the file always won.
<!-- atuin-pr-template-necessary-for-ci: v2 -->
```
## Workspace crates
```
atuin CLI binary + TUI (clap, ratatui, crossterm)
atuin-client Client library: local DB, encryption, sync, settings, client-facing domain types
atuin-common Low-level cross-crate utilities and API models (not a home for client-facing domain types)
atuin-daemon Background gRPC daemon (tonic) for shell hooks
atuin-dotfiles Legacy read-only alias/var listing via record store
atuin-history Sorting algorithms, stats
atuin-kv Key-value store (synced)
atuin-scripts Script management (minijinja)
atuin-server HTTP sync server (axum) - lib + standalone binary
atuin-server-database Database trait for server
atuin-server-postgres Postgres implementation (sqlx)
atuin-server-sqlite SQLite implementation (sqlx)
```
## Two sync protocols
- **V1 (legacy)**: Syncs history entries directly. Removed.
- **V2 (current)**: Record store abstraction. All data types (history, KV, aliases, vars, scripts) share the same sync infrastructure using tagged records. Envelope-encrypted with PASETO V4 and per-record CEKs.
## Encryption
- **V1**: XSalsa20Poly1305 (secretbox). Key at `~/.atuin/data/key`.
- **V2**: PASETO V4 Local (XChaCha20-Poly1305 + Blake2b). Envelope encryption: each record gets a random CEK wrapped with the master key. Record metadata (id, idx, version, tag, host) is authenticated as implicit assertions.
## Databases
- **Client**: SQLite everywhere. Separate DBs for history, record store, KV, scripts. All use sqlx + WAL mode.
- **Server**: Postgres (primary) or SQLite. Auto-detected from URI prefix.
- Migrations live alongside each crate. Never modify existing migrations, only add new ones.
- Exception: the AI session sidecar's migrations (`atuin-client/src/ai_session/migrations`) were squashed into one `0001_init.sql` before release, and the sidecar file was renamed (`ai_harness_sessions.db` โ `ai_session_sidecar.db`) so no old sidecar meets the new history. It is now `threads.db`; the daemon renames `ai_session_sidecar.db` on start. Don't add back compatibility with the old migrations; from here on, add new ones as usual.
## Hot paths
`history start`, `history end`, and `init` skip database initialization for latency. Don't add DB calls to these without good reason.
## Conventions
- Crate placement: anything client-facing -- domain types users' code touches and the stores over
them -- lives in `atuin-client`. `atuin-common` is for low-level, cross-crate utilities only; do
not put client-facing domain types there.
- Rust 2024 edition, toolchain 1.98.0.
- Errors: `eyre::Result` in binaries, `thiserror` for typed errors in libraries.
- Derive boilerplate: `derive_more` (workspace dep) for `Display`, `From`, `Into`, `AsRef`, `Deref`, `Debug` on newtypes and simple enums. Prefer `derive_more` over manual `impl` when the formatting/conversion is a straight delegation. Use `thiserror` (not `derive_more`) for error types. Use `#[as_ref(str)]` on string newtypes for `AsRef<str>`.
- Async: tokio. Client uses `current_thread`; server uses `multi_thread`.
- `#![deny(unsafe_code)]` on client/common, `#![forbid(unsafe_code)]` on server.
- Clippy: `pedantic` + `nursery` on main crate. CI enforces `-D warnings`, on both the default targets and `--tests`.
- Rustdoc: CI runs `cargo doc --document-private-items --no-deps --workspace` with `RUSTDOCFLAGS=-D warnings`. Broken intra-doc links fail the build.
- Format: `cargo +nightly fmt`. `.rustfmt.toml` uses nightly-only options, so formatting requires the nightly toolchain even though the project builds on stable 1.97.0.
- IDs: UUIDv7 (time-ordered), newtype wrappers (`HistoryId`, `RecordId`, `HostId`).
- Paths: when the original owned `PathBuf` isn't needed afterwards, use
`atuin_common::path::PathBufExt::joined` (import `PathBufExt as _`) instead of `.join()`. This
always applies to the second and later joins of a chain (`a.join(x).joined(y)`) and to owned
temporaries (`home_dir().joined(x)`). Also prefer it over `let mut p = ..; p.push(..);` when
that's simpler. Clippy can't flag this, so watch for it.
- Serialization: MessagePack for encrypted payloads, JSON for API, TOML for config.
- Storage traits: `Database` (client), `Store` (record store), `Database` (server) -- all `async_trait`.
- History builders: `HistoryImported`, `HistoryCaptured`, `HistoryFromDb` with compile-time field validation.
- Feature flags: `client`, `sync`, `daemon`, `clipboard`, `check-update`.
## Testing
- Unit tests inline with `#[cfg(test)]`. Use `rstest` for every test โ `#[rstest]`, never a
bare `#[test]` (async: `#[rstest]` + `#[tokio::test]`); migrate plain `#[test]`s in files you touch.
- Lean on `#[fixture]`s for shared setup and compose them; when a test needs teardown, return an
RAII guard from the fixture (e.g. a temp dir removed on `Drop`) rather than cleaning up by hand.
- Parametrize with `#[case(...)]` (input/expected tables) and `#[values(...)]` (cross-products of
independent parameters) instead of near-duplicate tests.
- Reach for `proptest` when a property holds across many inputs โ round-trips (encode/decode, serde,
parse/display), invariants, idempotence; keep targeted `#[case]`s for known edge cases and regressions.
- Server integration tests in `crates/atuin-server/tests/` need Postgres (`ATUIN_DB_URI` env var).
- E2e tests use temporary homes and private daemon sockets. Add shell setups in
`crates/atuin/tests/shells/*.toml`. See `crates/atuin/tests/README.md` for dependencies
and how to run them.
- Use `rstest` for tests, especially when they can be made simpler using `case`s and `fixture`s.
- Use `":memory:"` SQLite for unit tests needing a database.
- Runner: `cargo nextest`.
## Build and check
```sh
cargo build
cargo test
cargo clippy -- -D warnings
cargo clippy --tests -- -D warnings
cargo +nightly fmt --check
RUSTDOCFLAGS="-D warnings" cargo doc --document-private-items --no-deps --workspace
```