From 6ea760bb6b36da241961e8ecd60cb2c5e15c0a78 Mon Sep 17 00:00:00 2001 From: Michelle Tilley Date: Tue, 24 Feb 2026 11:48:20 -0800 Subject: feat: Generate commands or ask questions with `atuin ai` (#3199) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR refines the system created in #3178 to be suitable for a v1 release. --- ## Overview `atuin-ai` is a separate binary that allows for generating commands and asking questions from the command line. It is fully opt-in. ## Usage `atuin ai init` will output bindings for your shell. Currently, bash, zsh, and fish are supported. ```bash eval "$(atuin ai init)" ``` Once the hooks are installed, just press `?` on an empty prompt line to call up the TUI. `atuin ai` requires an account on [Atuin Hub](https://hub.atuin.sh/); you will be prompted to log in on first use. ## Features ### Command generation Prompt the LLM to create a command, and get one back, no fuss. Press `enter` to run, or `tab` to insert. ``` ┌Ask questions or generate a command:──────────────────────────┐ │ │ │ > Get a list of running docker containers │ │ │ ├──────────────────────────────────────────────────────────────┤ │ │ │ $ docker ps │ │ │ └────[Enter]: Run [Tab]: Insert [f]: Follow-up [Esc]: Cancel┘ ``` ### Follow-up You can follow-up with `f` to specify a refinement prompt to update the command that will be inserted. ``` ┌Ask questions or generate a command:──────────────────────────┐ │ │ │ > Get a list of running docker containers │ │ │ ├──────────────────────────────────────────────────────────────┤ │ │ │ $ docker ps │ │ │ ├──────────────────────────────────────────────────────────────┤ │ │ │ > Actually I want to get all docker containers │ │ │ ├──────────────────────────────────────────────────────────────┤ │ │ │ $ docker ps -a │ │ │ └────[Enter]: Run [Tab]: Insert [f]: Follow-up [Esc]: Cancel┘ ``` You can also follow-up with questions to get responses in natural language. ``` ┌Ask questions or generate a command:──────────────────────────┐ │ │ │ > Get a list of running docker containers │ │ │ ├──────────────────────────────────────────────────────────────┤ │ │ │ $ docker ps │ │ │ ├──────────────────────────────────────────────────────────────┤ │ │ │ > Actually I want to get all docker containers │ │ │ ├──────────────────────────────────────────────────────────────┤ │ │ │ $ docker ps -a │ │ │ ├──────────────────────────────────────────────────────────────┤ │ │ │ > What other useful flags to `docker ps` should I know? │ │ │ ├──────────────────────────────────────────────────────────────┤ │ │ │ Here are some handy `docker ps` flags: │ │ │ │ - `-q` — Only show container IDs (great for piping to │ │ other commands) │ │ - `-s` — Show container sizes │ │ - `-n 5` — Show the last 5 created containers │ │ - `-l` — Show only the latest created container │ │ - `--no-trunc` — Don't truncate output (shows full IDs and │ │ commands) │ │ - `-f` or `--filter` — Filter by condition, e.g.: │ │ - `-f status=exited` — only exited containers │ │ - `-f name=myapp` — filter by name │ │ - `-f ancestor=nginx` — filter by image │ │ - `--format` — Custom output using Go templates, e.g.: │ │ `--format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"` │ │ │ │ A common combo is `docker ps -aq` to get all container │ │ IDs, useful for bulk operations like `docker rm $(docker │ │ ps -aq)`. │ │ │ └────[Enter]: Run [Tab]: Insert [f]: Follow-up [Esc]: Cancel┘ ``` You can use `enter` or `tab` at any time to run or insert the last suggested command, even if it was suggested in a previous turn. ### Conversational and search usage If you prompt the LLM with a question that doesn't imply you want to generate a command, it can respond in natural language, and use web search if necessary to fetch the data it needs. ``` ┌Ask questions or generate a command:──────────────────────────┐ │ │ │ > What is the latest version of atuin? │ │ │ ├──────────────────────────────────────────────────────────────┤ │ │ │ ✓ Used 2 tools │ │ │ │ The latest version of Atuin is **v18.12.0**, available on │ │ the [GitHub releases │ │ page](https://github.com/atuinsh/atuin/releases). │ │ │ └─────────────────────────────────[f]: Follow-up [Esc]: Cancel┘ ``` ### Dangerous or low-confidence command detection The LLM scores its confidence in the command, as well as how dangerous the command is. This information is shown if a threshold is exceeded, and requires an extra confirmation step before running automatically with `enter`. The Atuin Hub server also monitors suggested commands for dangerous patterns the LLM didn't catch, and appends its own assessment at the end of the LLM's own assessment. ``` ┌Ask questions or generate a command:──────────────────────────┐ │ │ │ > Delete all files from $HOME │ │ │ ├──────────────────────────────────────────────────────────────┤ │ │ │ $ rm -rf $HOME/* │ │ │ │ ! ⚠️ This will PERMANENTLY delete ALL files and directories │ │ in your home directory, including documents, downloads, │ │ configurations, SSH keys, and everything else. This is │ │ irreversible and will likely break your system. Also note │ │ this won't delete hidden (dot) files — if you want those │ │ too, that's even more destructive.; [Server] Recursive │ │ delete of critical directory │ │ │ └────[Enter]: Run [Tab]: Insert [f]: Follow-up [Esc]: Cancel┘ ``` --------- Co-authored-by: Claude Opus 4.5 --- crates/atuin-ai/replay-states.sh | 101 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100755 crates/atuin-ai/replay-states.sh (limited to 'crates/atuin-ai/replay-states.sh') diff --git a/crates/atuin-ai/replay-states.sh b/crates/atuin-ai/replay-states.sh new file mode 100755 index 00000000..4f586709 --- /dev/null +++ b/crates/atuin-ai/replay-states.sh @@ -0,0 +1,101 @@ +#!/bin/bash +# Replay state snapshots from a debug state JSONL file +# Usage: ./replay-states.sh [entry-number] +# With no entry: renders all frames in sequence (press Enter to advance) +# With entry number: renders just that frame + +set -e +# cd "$(dirname "$0")" + +STATE_FILE="${1:-}" +ENTRY_FILTER="${2:-}" + +if [[ -z "$STATE_FILE" ]]; then + echo "Usage: $0 [entry-number]" + echo "" + echo "Examples:" + echo " $0 /tmp/state.jsonl # Interactive replay of all frames" + echo " $0 /tmp/state.jsonl 15 # Show just entry 15" + exit 1 +fi + +if [[ ! -f "$STATE_FILE" ]]; then + echo "Error: File not found: $STATE_FILE" + exit 1 +fi + +# Build once +cargo build -p atuin-ai --quiet + +# Count entries +TOTAL=$(wc -l < "$STATE_FILE" | tr -d ' ') + +if [[ -n "$ENTRY_FILTER" ]]; then + # Show single entry + LINE=$(sed -n "${ENTRY_FILTER}p" "$STATE_FILE") + if [[ -z "$LINE" ]]; then + echo "Error: Entry $ENTRY_FILTER not found (file has $TOTAL entries)" + exit 1 + fi + + ENTRY=$(echo "$LINE" | jq -r '.entry') + LABEL=$(echo "$LINE" | jq -r '.label') + STATE=$(echo "$LINE" | jq -c '.state') + + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "[$ENTRY/$TOTAL] $LABEL" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "$STATE" | cargo run -p atuin-ai --quiet -- debug-render -f plain +else + # Interactive replay + echo "Replaying $TOTAL frames from $STATE_FILE" + echo "Press Enter to advance, 'q' to quit, or number+Enter to jump" + echo "" + + CURRENT=1 + while [[ $CURRENT -le $TOTAL ]]; do + LINE=$(sed -n "${CURRENT}p" "$STATE_FILE") + ENTRY=$(echo "$LINE" | jq -r '.entry') + LABEL=$(echo "$LINE" | jq -r '.label') + STATE=$(echo "$LINE" | jq -c '.state') + + clear + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "[$CURRENT/$TOTAL] $LABEL" + echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" + echo "$STATE" | cargo run -p atuin-ai --quiet -- debug-render -f plain + echo "" + echo "[Enter: next] [p: prev] [number: jump] [s: show state JSON] [q: quit]" + + read -r INPUT + case "$INPUT" in + q|Q) + break + ;; + p|P) + if [[ $CURRENT -gt 1 ]]; then + CURRENT=$((CURRENT - 1)) + fi + ;; + s|S) + echo "" + echo "State JSON:" + echo "$STATE" | jq . + echo "" + echo "Press Enter to continue..." + read -r + ;; + ''|' ') + CURRENT=$((CURRENT + 1)) + ;; + *[0-9]*) + if [[ "$INPUT" =~ ^[0-9]+$ ]] && [[ "$INPUT" -ge 1 ]] && [[ "$INPUT" -le $TOTAL ]]; then + CURRENT=$INPUT + else + echo "Invalid entry number (1-$TOTAL)" + sleep 1 + fi + ;; + esac + done +fi -- cgit v1.3.1