Skip to content

Comments

feat: Generate commands or ask questions with atuin ai#3199

Open
BinaryMuse wants to merge 77 commits intomainfrom
mkt/atuin-ai-2-the-return-of-the-ai
Open

feat: Generate commands or ask questions with atuin ai#3199
BinaryMuse wants to merge 77 commits intomainfrom
mkt/atuin-ai-2-the-return-of-the-ai

Conversation

@BinaryMuse
Copy link
Member

@BinaryMuse BinaryMuse commented Feb 19, 2026

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.

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; 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┘

BinaryMuse and others added 30 commits February 13, 2026 19:29
- Add TerminalGuard with panic hook and Drop-based cleanup
- Implement install_panic_hook() for terminal restoration after panic
- Add non-TTY detection for early error handling
- Use Viewport::Inline(16) matching existing inline.rs behavior
- Capture cursor position before raw mode for accurate anchor
- Add AppEvent enum with Key, Tick, Resize, and Stream placeholders
- Implement EventLoop with biased tokio::select! for priority handling
- Add SIGINT/Ctrl+C handler for graceful shutdown
- Implement rapid keypress batching
- Use 150ms tick interval for spinner animation
- Filter keyboard events to Press only for cross-platform safety
- Add futures dependency for StreamExt trait
- Add tui module to main.rs
- Import and call install_panic_hook() in inline.rs run()
- Add event-stream feature to crossterm for async EventStream
- Verify existing inline functionality still works
- Build and help command tests pass
- BlockState enum with Building/Active/Static variants
- State transition methods (finish_building, make_static, tick)
- BlockKind enum for Input/Command/Spinner/Text
- Block struct combining kind, state, and content
- AppMode enum for Input/Generating/Review/Error states
- ExitAction enum for Execute/Insert/Cancel
- App struct managing blocks, mode, input, and exit state
- State transition methods (start_generating, generation_complete, cancel_generation, generation_error)
- Cancel during generation removes partial block and reverts previous to Active
- Tick method for advancing spinner animations
- App.handle_key() processes keyboard events per mode
- Input mode: Esc exits, Enter submits or cancels if empty, chars build input
- Generating mode: Esc cancels, other keys discarded
- Review mode: Esc cancels, Enter runs, Tab inserts, 'e' edits
- Error mode: Esc cancels, Enter/r retries
- Ctrl combinations pass through
- EventLoop.poll_and_apply() integrates events with App state
- Replace InlineUi::new() with TerminalGuard::new()
- Replace blocking event::poll() loop with async EventLoop
- Wire EventLoop.run().await to drive TUI state
- Remove old InlineUi struct and Screen enum
- Integrate App state management with event handling
- Remove unused wait_for_retry_or_cancel function
- Apply clippy suggestions to collapse nested if statements
- Verify all state transitions work correctly
- Pass cargo clippy and cargo build with no warnings
- Add render.rs with RenderContext struct and render_blocks() function
- Implement block-specific renderers for Input, Command, Spinner, Text
- Add theme integration using Style::from_crossterm()
- Implement cursor positioning with word wrapping support
- Add horizontal separators between blocks
- Use dynamic layout calculations for proper sizing
- Add ThemeManager import and load theme from settings
- Replace render_app() with render_blocks() call
- Remove old rendering functions (render_app, format_prompt, wrapped_line_count, prompt_cursor_position)
- Remove local SPINNER_FRAMES constant (now in render.rs)
- Remove unused ratatui imports
- Theme passed through RenderContext to render module
Phase 3 has most functionality already implemented from earlier work.
This plan fills remaining gaps:
- Task 1: Error block rendering with "!" symbol and Alert theme color
- Task 2: Exit mode configuration (keep_output parameter)

1 plan, 2 tasks, single wave.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add BlockKind::Error variant to block types
- Add Block::new_error() constructor with Static state
- Add render_error_block() with "!" symbol styled as AlertError
- Modify generation_error() to push error block to blocks vec
- Fix clippy warning in inline.rs (unnecessary_unwrap)
- Add keep_output parameter to TerminalGuard::new()
- Store keep_output as field in TerminalGuard struct
- Modify Drop impl to conditionally clear based on keep_output
- Add --keep CLI flag to Inline command
- Pass keep_output through run() and run_inline_tui()
- Default to false (erase behavior) for backward compatibility
Plan 01: SSE streaming infrastructure
- Add reqwest-eventsource and pulldown-cmark dependencies
- Add BlockState::Streaming and AppMode::Streaming variants
- Implement refine stream with event loop polling

Plan 02: Markdown rendering and edit mode
- Add markdown_to_spans() for bold/italic/code formatting
- Implement edit mode transition and conversation history
- Wire refine stream trigger on edit submission

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- reqwest-eventsource 0.6.0 for SSE streaming
- pulldown-cmark 0.13.0 for markdown parsing
- async-stream 0.3 for stream creation
- Add BlockState::Streaming variant with is_streaming() method
- Add Block::new_streaming_text() constructor
- Add AppMode::Streaming variant
- Add streaming lifecycle methods to App:
  - start_streaming_response()
  - append_to_streaming_block()
  - finalize_streaming()
  - streaming_error()
- Add handle_streaming_key() for Esc cancellation
- Update render footer for Streaming mode
- Add RefineRequest and RefineMessage structs
- Add create_refine_stream() function using eventsource-stream
- Add refine_stream tracking variable in event loop
- Poll stream in Tick handler without blocking
- Handle stream chunks, errors, and completion
- Add stream cancellation on mode change
- Enable stream feature in workspace reqwest dependency
- Import pulldown_cmark for markdown parsing
- Add markdown_to_spans() to convert markdown to styled Lines
- Support **bold** (BOLD modifier), *italics* (UNDERLINED), `code` (Important style)
- Update render_text_block() to use markdown parsing
- Handle paragraphs, soft/hard breaks, nested formatting
- Add is_refine_mode flag to App struct
- Add start_edit_mode() method to transition to Input with refine flag set
- Add build_conversation_history() to create (role, content) pairs from blocks
- Update KeyCode::Char('e') handler to call start_edit_mode()
- Update inline.rs to trigger refine stream when is_refine_mode is true
- Update retry logic to skip refine mode (retry only works for initial generation)
- Remove #[allow(dead_code)] from refine stream functions (now in use)
Summary:
- 3 tasks completed (markdown parsing, edit mode, verification)
- 2 commits (Task 1: markdown_to_spans, Task 2: edit mode flow)
- 3 files modified (render.rs, app.rs, inline.rs)
- Duration: 3 minutes

State updates:
- Advanced to Phase 4 Plan 2 of 2 complete
- Updated progress to 75% (6 plans complete)
- Added decision D04-02-001 (Task 3 already complete)
- Removed blocker about edit mode implementation (now done)
- Updated session continuity
Plan 01: State and view model type definitions (state.rs, view_model.rs)
Plan 02: State transition methods and keyboard handling
Plan 03: Render integration and blocks module removal

3 plans in 3 sequential waves - foundation types, state management, integration.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Create AppState struct with flat fields (mode, messages, input, cursor_pos, error)
- Define AppMode enum (Input, Generating, Streaming, Review, Error)
- Add Message/MessageRole types for conversation history
- Implement character-based cursor handling with byte index conversion
- Add ExitAction enum for exit flow control
- Create Content enum with struct-like variants (Input, Command, Text, Error, Spinner)
- Define Block struct with content, separator_above, title fields
- Implement Blocks::from_state() as pure derivation function
- Derive complete view specification from AppState (messages, mode, error)
- Generate mode-dependent footer keybinds
- Add pub mod declarations for state and view_model
- Export AppState, Message, MessageRole from state
- Export Blocks and Content from view_model
- Maintain existing module exports for compatibility
- Generation lifecycle: start_generating, generation_complete, generation_error, cancel_generation
- Streaming lifecycle: start_streaming_response, append_streaming_text, finalize_streaming_with_command, streaming_error
- Edit mode and exit: start_edit_mode, exit, retry
- Utility: tick, current_command
- Conversation events tracked for refine API
- Messages mutated in place during streaming per user decision
- App now wraps AppState and delegates keyboard handling
- Keyboard events route to appropriate AppState methods based on mode
- Added delegation methods for backward compatibility with inline.rs
- Added blocks() method to convert messages to legacy blocks for render.rs
- Updated mod.rs exports to expose AppState types
- Fixed inline.rs and render.rs to use app.state fields
- All mutable state logic now centralized in AppState

Deviation (Rule 3 - Blocking): Added compatibility layer to App for render.rs and inline.rs to maintain compilation during transition
- Replace legacy block-based rendering with pure view derivation
- render() calls Blocks::from_state() each frame
- Renderer only reads Content variants from view model
- Cursor position calculated from view model's cursor_pos field
- Keep render_blocks() as legacy wrapper for compatibility
- Import render() function from render module
- Update render call to pass &app.state instead of &app
- Replace all app.method() calls with app.state.method()
- Direct state access via app.state for all state transitions
- Remove dependency on legacy app delegation methods
- Delete crates/atuin-ai/src/tui/blocks/ directory
- Remove blocks module from tui/mod.rs
- Clean up exports to only include new architecture types
- Remove legacy delegation methods from app.rs
- Remove render_blocks() compatibility wrapper
- Fix event.rs to use app.state.tick() directly
- No BlockKind, BlockState, or LegacyBlock types remain
BinaryMuse and others added 12 commits February 18, 2026 15:42
- Add test-renders.json with 23 UI state test cases covering:
  - Input states (empty, typing, follow-up)
  - Streaming states (spinner, text, tool calls)
  - Review states (commands, warnings, errors)
  - Edge cases (long text, multi-turn, null commands)
- Add render-tests.sh script to run all tests through debug-render

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add debug state logging to the inline command for debugging UI issues:
- New --debug-state <FILE> flag to log state changes as JSONL
- Logs include: events, mode, input, cursor_pos, streaming_text, etc.
- Each entry labeled with event type (key, text_chunk, tool_call, etc.)
- Useful for capturing real UI states to create accurate test cases

Usage: atuin-ai inline --debug-state /tmp/state.jsonl

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Interactive script to replay state snapshots from --debug-state output:
- ./replay-states.sh <file.jsonl>     # Interactive replay
- ./replay-states.sh <file.jsonl> 15  # Show specific entry

Interactive mode supports: next, prev, jump to entry, show raw JSON

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
LLM responses often start with "\n\n" which caused extra blank lines
at the top of text blocks. Now trim_start() when finalizing streaming
text to events.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Dynamic viewport: starts at 15 lines, grows +5 when needed
- Event ordering: flush streaming text before adding tool calls
- Hide conversation-only suggest_command tool calls (null command)
- Trim leading whitespace from LLM responses during streaming
- Debug logger captures actual content height for accurate replay
- Extract calculate_needed_height() for viewport sizing

Note: cursor positioning for wrapped input is WIP, will be replaced
with tui-textarea integration.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Replace manual input/cursor handling with tui-textarea-2 library which
properly handles cursor positioning, word wrapping, and text editing.

- Switch to tui-textarea-2 (compatible with ratatui 0.30)
- Replace input/cursor_pos fields with TextArea widget in AppState
- Delegate key handling to TextArea (handles cursor movement, editing)
- Render TextArea directly for active inputs (cursor via styled spans)
- Enable word wrapping (WrapMode::Word) for long input text
- Disable cursor line underline for cleaner appearance
- Add REVERSED/UNDERLINED modifier support to debug render output

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 19, 2026

Greptile Summary

Introduces atuin-ai, a TUI for LLM-powered command generation and Q&A. Fully opt-in feature accessible via ? on empty prompt after running eval "$(atuin ai init)".

Key additions:

  • SSE-based streaming chat with conversational follow-ups
  • Dangerous command detection with double-enter confirmation
  • Shell integrations for bash, zsh, fish with proper command execution vs. insertion
  • Pure functional rendering with dynamic viewport sizing
  • Comprehensive state logging for debugging

Important Files Changed

Filename Overview
crates/atuin-ai/src/tui/state.rs Domain state types for TUI application with conversation event tracking and generation lifecycle management
crates/atuin-ai/src/commands/inline.rs Main TUI runner with SSE streaming, authentication, and shell result emission
crates/atuin-ai/src/commands/init.rs Shell integration generators for zsh, bash, and fish with ? keybindings
crates/atuin-ai/src/tui/app.rs Keyboard event handler with dangerous command confirmation flow
crates/atuin-ai/src/tui/render.rs Pure rendering logic with dynamic height calculation and markdown support

Last reviewed commit: fff2360

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

25 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

@BinaryMuse
Copy link
Member Author

@greptileai

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

25 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

@BinaryMuse
Copy link
Member Author

@greptileai

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

32 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

@BinaryMuse BinaryMuse requested a review from ellie February 20, 2026 22:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant