aboutsummaryrefslogtreecommitdiffstats
path: root/crates
diff options
context:
space:
mode:
authorEllie Huxtable <ellie@atuin.sh>2026-03-31 04:18:29 +0100
committerGitHub <noreply@github.com>2026-03-31 04:18:29 +0100
commit97b5a6c3247299e6863b17bf0b6e125692d33766 (patch)
tree4f452be07c75202c6b0b81cb981a083895dc5c01 /crates
parentfix(ui): make preview line breaking algorithm aware of CJK double-width chara... (diff)
downloadatuin-97b5a6c3247299e6863b17bf0b6e125692d33766.zip
feat: opt-in to sharing last command with ai (#3367)
This enables it to perform more effectively and give better suggestions. Same as send_cwd, disabled by default, opt in. ## Checks - [ ] I am happy for maintainers to push small adjustments to this PR, to speed up the review cycle - [ ] I have checked that there are no existing pull requests for the same thing
Diffstat (limited to 'crates')
-rw-r--r--crates/atuin-ai/src/commands/inline.rs37
-rw-r--r--crates/atuin-client/src/settings.rs18
2 files changed, 50 insertions, 5 deletions
diff --git a/crates/atuin-ai/src/commands/inline.rs b/crates/atuin-ai/src/commands/inline.rs
index c16e3dac..aeb414fb 100644
--- a/crates/atuin-ai/src/commands/inline.rs
+++ b/crates/atuin-ai/src/commands/inline.rs
@@ -1,9 +1,11 @@
+use std::path::PathBuf;
use std::sync::mpsc;
use crate::commands::detect_shell;
use crate::tui::events::AiTuiEvent;
use crate::tui::state::{AppState, ExitAction};
use crate::tui::view::ai_view;
+use atuin_client::database::{Database, Sqlite};
use atuin_client::distro::detect_linux_distribution;
use atuin_common::tls::ensure_crypto_provider;
use eventsource_stream::Eventsource;
@@ -138,6 +140,7 @@ fn create_chat_stream(
session_id: Option<String>,
messages: Vec<serde_json::Value>,
send_cwd: bool,
+ last_command: Option<String>,
) -> std::pin::Pin<Box<dyn futures::Stream<Item = Result<ChatStreamEvent>> + Send>> {
Box::pin(async_stream::stream! {
ensure_crypto_provider();
@@ -160,6 +163,7 @@ fn create_chat_stream(
"pwd": if send_cwd { std::env::current_dir()
.ok()
.map(|path| path.to_string_lossy().into_owned()) } else { None },
+ "last_command": last_command,
});
if os == "linux" {
@@ -294,8 +298,16 @@ async fn run_chat_stream(
session_id: Option<String>,
messages: Vec<serde_json::Value>,
send_cwd: bool,
+ last_command: Option<String>,
) {
- let stream = create_chat_stream(endpoint, token, session_id, messages, send_cwd);
+ let stream = create_chat_stream(
+ endpoint,
+ token,
+ session_id,
+ messages,
+ send_cwd,
+ last_command,
+ );
futures::pin_mut!(stream);
while let Some(event) = stream.next().await {
@@ -388,7 +400,22 @@ async fn run_inline_tui(
.extra_newlines_at_exit(1)
.build()?;
- let send_cwd = settings.ai.send_cwd;
+ // Support both legacy [ai] send_cwd and new [ai.opening] send_cwd
+ let send_cwd =
+ settings.ai.opening.send_cwd.unwrap_or(false) || settings.ai.send_cwd.unwrap_or(false);
+
+ let last_command = if settings.ai.opening.send_last_command.unwrap_or(false) {
+ let db_path = PathBuf::from(settings.db_path.as_str());
+ match Sqlite::new(db_path, settings.local_timeout).await {
+ Ok(db) => db.last().await.ok().flatten().map(|h| h.command),
+ Err(e) => {
+ debug!("Failed to open history database for read_history: {e}");
+ None
+ }
+ }
+ } else {
+ None
+ };
// Event loop: receives AiTuiEvent from components, mutates state via Handle.
let h = handle.clone();
@@ -433,6 +460,7 @@ async fn run_inline_tui(
let ep = ep.clone();
let tk = tk.clone();
let h2 = h.clone();
+ let lc = last_command.clone();
h.update(move |state| {
state.start_generating(input);
state.start_streaming();
@@ -440,7 +468,7 @@ async fn run_inline_tui(
let messages = state.events_to_messages();
let sid = state.session_id.clone();
let task = tokio::spawn(async move {
- run_chat_stream(h2, ep, tk, sid, messages, send_cwd).await;
+ run_chat_stream(h2, ep, tk, sid, messages, send_cwd, lc).await;
});
state.stream_abort = Some(task.abort_handle());
});
@@ -502,13 +530,14 @@ async fn run_inline_tui(
let ep = ep.clone();
let tk = tk.clone();
let h2 = h.clone();
+ let lc = last_command.clone();
h.update(move |state| {
state.retry();
state.start_streaming();
let messages = state.events_to_messages();
let sid = state.session_id.clone();
let task = tokio::spawn(async move {
- run_chat_stream(h2, ep, tk, sid, messages, send_cwd).await;
+ run_chat_stream(h2, ep, tk, sid, messages, send_cwd, lc).await;
});
state.stream_abort = Some(task.abort_handle());
});
diff --git a/crates/atuin-client/src/settings.rs b/crates/atuin-client/src/settings.rs
index 5b18d9ea..b3359d19 100644
--- a/crates/atuin-client/src/settings.rs
+++ b/crates/atuin-client/src/settings.rs
@@ -664,8 +664,22 @@ pub struct Ai {
/// Only necessary for custom AI endpoints.
pub api_token: Option<String>,
+ /// Deprecated: use opening.send_cwd instead. Kept for backwards compatibility.
+ #[serde(default)]
+ pub send_cwd: Option<bool>,
+
+ /// Configuration for what context is sent in the opening AI request.
+ #[serde(default)]
+ pub opening: AiOpening,
+}
+
+#[derive(Default, Clone, Debug, Deserialize, Serialize)]
+pub struct AiOpening {
/// Whether or not to send the current working directory to the AI endpoint.
- pub send_cwd: bool,
+ pub send_cwd: Option<bool>,
+
+ /// Whether or not to send the last command as context in the opening AI request.
+ pub send_last_command: Option<bool>,
}
impl Default for Preview {
@@ -1524,6 +1538,8 @@ impl Settings {
.set_default("search.frecency_score_multiplier", 1.0)?
.set_default("meta.db_path", meta_path.to_str())?
.set_default("ai.send_cwd", false)?
+ .set_default("ai.opening.send_cwd", false)?
+ .set_default("ai.opening.send_last_command", false)?
.set_default(
"search.filters",
vec![