diff options
| author | Michelle Tilley <michelle@michelletilley.net> | 2026-03-02 09:12:20 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-03-02 18:12:20 +0100 |
| commit | 4c9180c2755b6457113e8d6a7566c32cf1ad547a (patch) | |
| tree | 8136d818898232d811dbc452bb52a16c38b8f8e3 /crates/atuin-ai/src | |
| parent | fix: regen cargo dist (diff) | |
| download | atuin-4c9180c2755b6457113e8d6a7566c32cf1ad547a.zip | |
chore: Move atuin ai subcommand into core binary (#3212)
Diffstat (limited to 'crates/atuin-ai/src')
| -rw-r--r-- | crates/atuin-ai/src/commands.rs | 72 | ||||
| -rw-r--r-- | crates/atuin-ai/src/commands/init.rs | 14 | ||||
| -rw-r--r-- | crates/atuin-ai/src/commands/inline.rs | 32 | ||||
| -rw-r--r-- | crates/atuin-ai/src/lib.rs | 2 | ||||
| -rw-r--r-- | crates/atuin-ai/src/main.rs | 7 | ||||
| -rw-r--r-- | crates/atuin-ai/src/tui/terminal.rs | 2 |
6 files changed, 63 insertions, 66 deletions
diff --git a/crates/atuin-ai/src/commands.rs b/crates/atuin-ai/src/commands.rs index b35cec9e..d04875ea 100644 --- a/crates/atuin-ai/src/commands.rs +++ b/crates/atuin-ai/src/commands.rs @@ -4,7 +4,7 @@ use std::{ }; use atuin_common::shell::Shell; -use clap::{Parser, Subcommand}; +use clap::{Args, Subcommand}; use eyre::Result; use tracing_appender::rolling::{RollingFileAppender, Rotation}; use tracing_subscriber::{EnvFilter, Layer, fmt, layer::SubscriberExt, util::SubscriberInitExt}; @@ -14,27 +14,23 @@ pub mod debug_render; pub mod init; pub mod inline; -#[derive(Parser, Debug)] -#[command(author, version, about, long_about = None)] -struct Cli { +#[derive(Args, Debug)] +pub struct AiArgs { /// Enable verbose logging #[arg(short, long, global = true)] verbose: bool, - /// Custom API endpoint - #[arg(long, global = true, env = "ATUIN_AI_API_ENDPOINT")] + /// Custom API endpoint; defaults to reading from the `ai.endpoint` setting. + #[arg(long, global = true)] api_endpoint: Option<String>, - /// Custom API token - #[arg(long, global = true, env = "ATUIN_AI_API_TOKEN")] + /// Custom API token; defaults to reading from the `ai.api_token` setting. + #[arg(long, global = true)] api_token: Option<String>, - - #[command(subcommand)] - command: Commands, } #[derive(Subcommand, Debug)] -enum Commands { +pub enum Commands { /// Initialize shell integration Init { /// Shell to generate integration for; defaults to "auto" @@ -44,20 +40,23 @@ enum Commands { /// Inline completion mode with small TUI overlay Inline { + #[command(flatten)] + args: AiArgs, + /// Current command line to complete #[arg(value_name = "COMMAND")] command: Option<String>, - /// Start in natural language mode - #[arg(long)] - natural_language: bool, - /// Keep TUI output visible after exit (default: erase) #[arg(long)] keep: bool, + /// Use the hook mode + #[arg(long, hide = true)] + hook: bool, + /// Log state changes to file for debugging (dev tool) - #[arg(long, value_name = "FILE")] + #[arg(long, value_name = "FILE", hide = true)] debug_state: Option<String>, }, @@ -74,31 +73,32 @@ enum Commands { }, } -pub async fn run() -> eyre::Result<()> { - let cli = Cli::parse(); - - let settings = atuin_client::settings::Settings::new()?; - - if settings.logs.ai_enabled() { - init_logging(&settings, cli.verbose)?; - } - - match cli.command { +pub async fn run( + command: Commands, + settings: &atuin_client::settings::Settings, +) -> eyre::Result<()> { + match command { Commands::Init { shell } => init::run(shell).await, Commands::Inline { command, - natural_language, keep, debug_state, + hook, + args, + .. } => { + if settings.logs.ai_enabled() { + init_logging(settings, args.verbose)?; + } + inline::run( command, - natural_language, - cli.api_endpoint, - cli.api_token, + args.api_endpoint, + args.api_token, keep, debug_state, - &settings, + settings, + hook, ) .await } @@ -137,12 +137,10 @@ fn init_logging(settings: &atuin_client::settings::Settings, verbose: bool) -> R }; let log_dir = PathBuf::from(&settings.logs.dir); - fs::create_dir_all(&log_dir)?; - - let filename = settings.logs.ai.file.clone(); + let ai_log_filename = settings.logs.ai.file.clone(); // Clean up old log files - cleanup_old_logs(&log_dir, &filename, settings.logs.ai_retention()); + cleanup_old_logs(&log_dir, &ai_log_filename, settings.logs.ai_retention()); let console_layer = if verbose { Some( @@ -156,7 +154,7 @@ fn init_logging(settings: &atuin_client::settings::Settings, verbose: bool) -> R None }; - let file_appender = RollingFileAppender::new(Rotation::DAILY, &log_dir, &filename); + let file_appender = RollingFileAppender::new(Rotation::DAILY, &log_dir, &ai_log_filename); let base = tracing_subscriber::registry().with( fmt::layer() diff --git a/crates/atuin-ai/src/commands/init.rs b/crates/atuin-ai/src/commands/init.rs index 8174b583..caf4c8d9 100644 --- a/crates/atuin-ai/src/commands/init.rs +++ b/crates/atuin-ai/src/commands/init.rs @@ -38,7 +38,7 @@ _atuin_ai_question_mark() { if [[ -z "$BUFFER" || "$BUFFER" == "?" ]]; then BUFFER="" local output - output=$(atuin-ai inline --natural-language 3>&1 1>&2 2>&3) + output=$(atuin ai inline --hook 3>&1 1>&2 2>&3) # Clean up the inline viewport _atuin_ai_cleanup @@ -84,7 +84,7 @@ _atuin_ai_question_mark() { READLINE_POINT=0 local output - output=$(atuin-ai inline --natural-language 3>&1 1>&2 2>&3) + output=$(atuin ai inline --hook 3>&1 1>&2 2>&3) if [[ $output == __atuin_ai_cancel__ ]]; then # User cancelled, do nothing @@ -142,8 +142,8 @@ function _atuin_ai_question_mark if test -z "$buf" -o "$buf" = "?" commandline -r "" - # Run atuin-ai inline, swapping stdout and stderr - set -l output (atuin-ai inline --natural-language 3>&1 1>&2 2>&3 | string collect) + # Run atuin ai inline, swapping stdout and stderr + set -l output (atuin ai inline --hook 3>&1 1>&2 2>&3 | string collect) if test "$output" = "__atuin_ai_cancel__" # User cancelled, do nothing @@ -187,7 +187,7 @@ mod tests { let result = generate_zsh_integration(); assert!(result.contains("_atuin_ai_question_mark")); assert!(result.contains("bindkey")); - assert!(result.contains("atuin-ai inline")); + assert!(result.contains("atuin ai inline --hook")); assert!(result.contains("__atuin_ai_cancel__")); assert!(result.contains("__atuin_ai_execute__")); assert!(result.contains("__atuin_ai_insert__")); @@ -199,7 +199,7 @@ mod tests { assert!(result.contains("_atuin_ai_question_mark")); assert!(result.contains("bind")); assert!(result.contains("READLINE_LINE")); - assert!(result.contains("atuin-ai inline")); + assert!(result.contains("atuin ai inline --hook")); assert!(result.contains("__atuin_ai_cancel__")); assert!(result.contains("__atuin_ai_execute__")); assert!(result.contains("__atuin_ai_insert__")); @@ -211,7 +211,7 @@ mod tests { assert!(result.contains("_atuin_ai_question_mark")); assert!(result.contains("bind")); assert!(result.contains("commandline")); - assert!(result.contains("atuin-ai inline")); + assert!(result.contains("atuin ai inline --hook")); assert!(result.contains("__atuin_ai_cancel__")); assert!(result.contains("__atuin_ai_execute__")); assert!(result.contains("__atuin_ai_insert__")); diff --git a/crates/atuin-ai/src/commands/inline.rs b/crates/atuin-ai/src/commands/inline.rs index b49bfece..67241574 100644 --- a/crates/atuin-ai/src/commands/inline.rs +++ b/crates/atuin-ai/src/commands/inline.rs @@ -19,12 +19,12 @@ use tracing::{debug, error, info, trace}; pub async fn run( initial_command: Option<String>, - natural_language: bool, api_endpoint: Option<String>, api_token: Option<String>, keep_output: bool, debug_state_file: Option<String>, settings: &atuin_client::settings::Settings, + output_for_hook: bool, ) -> Result<()> { // Install panic hook once at entry point to ensure terminal restoration install_panic_hook(); @@ -36,11 +36,11 @@ pub async fn run( let endpoint = api_endpoint.as_deref().unwrap_or( settings .ai - .ai_endpoint + .endpoint .as_deref() .unwrap_or("https://hub.atuin.sh"), ); - let api_token = api_token.as_deref().or(settings.ai.ai_api_token.as_deref()); + let api_token = api_token.as_deref().or(settings.ai.api_token.as_deref()); let token = if let Some(token) = &api_token { token.to_string() @@ -51,17 +51,13 @@ pub async fn run( let action = run_inline_tui( endpoint.to_string(), token, - if natural_language { - None - } else { - initial_command - }, + initial_command, keep_output, debug_state_file, settings, ) .await?; - emit_shell_result(action.0, &action.1); + emit_shell_result(action.0, &action.1, output_for_hook); Ok(()) } @@ -619,11 +615,19 @@ impl Drop for RawModeGuard { } } -fn emit_shell_result(action: Action, command: &str) { - match action { - Action::Execute => eprintln!("__atuin_ai_execute__:{command}"), - Action::Insert => eprintln!("__atuin_ai_insert__:{command}"), - Action::Cancel => eprintln!("__atuin_ai_cancel__"), +fn emit_shell_result(action: Action, command: &str, output_for_hook: bool) { + if output_for_hook { + match action { + Action::Execute => eprintln!("__atuin_ai_execute__:{command}"), + Action::Insert => eprintln!("__atuin_ai_insert__:{command}"), + Action::Cancel => eprintln!("__atuin_ai_cancel__"), + } + } else { + match action { + Action::Execute => eprintln!("{command}"), + Action::Insert => eprintln!("{command}"), + Action::Cancel => eprintln!(), + } } } diff --git a/crates/atuin-ai/src/lib.rs b/crates/atuin-ai/src/lib.rs new file mode 100644 index 00000000..2d86271d --- /dev/null +++ b/crates/atuin-ai/src/lib.rs @@ -0,0 +1,2 @@ +pub mod commands; +pub mod tui; diff --git a/crates/atuin-ai/src/main.rs b/crates/atuin-ai/src/main.rs deleted file mode 100644 index fb1e517e..00000000 --- a/crates/atuin-ai/src/main.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod commands; -pub mod tui; - -#[tokio::main] -async fn main() -> eyre::Result<()> { - commands::run().await -} diff --git a/crates/atuin-ai/src/tui/terminal.rs b/crates/atuin-ai/src/tui/terminal.rs index 2e0bcbaa..75bfd6e6 100644 --- a/crates/atuin-ai/src/tui/terminal.rs +++ b/crates/atuin-ai/src/tui/terminal.rs @@ -54,7 +54,7 @@ const VIEWPORT_BOTTOM_MARGIN: u16 = 2; /// use atuin_ai::tui::{install_panic_hook, TerminalGuard}; /// /// install_panic_hook(); // Once at program start -/// let mut guard = TerminalGuard::new()?; +/// let mut guard = TerminalGuard::new(true)?; /// let terminal = guard.terminal(); /// // ... use terminal ... /// // Drop automatically cleans up |
