aboutsummaryrefslogtreecommitdiffstats
path: root/crates/atuin-ai/src
diff options
context:
space:
mode:
authorMichelle Tilley <michelle@michelletilley.net>2026-03-02 09:12:20 -0800
committerGitHub <noreply@github.com>2026-03-02 18:12:20 +0100
commit4c9180c2755b6457113e8d6a7566c32cf1ad547a (patch)
tree8136d818898232d811dbc452bb52a16c38b8f8e3 /crates/atuin-ai/src
parentfix: regen cargo dist (diff)
downloadatuin-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.rs72
-rw-r--r--crates/atuin-ai/src/commands/init.rs14
-rw-r--r--crates/atuin-ai/src/commands/inline.rs32
-rw-r--r--crates/atuin-ai/src/lib.rs2
-rw-r--r--crates/atuin-ai/src/main.rs7
-rw-r--r--crates/atuin-ai/src/tui/terminal.rs2
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