Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions crates/atuin/src/command/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,7 @@ impl Cmd {
Self::History(history) => return history.run(&settings).await,
Self::Init(init) => return init.run(&settings).await,
Self::Doctor => return doctor::run(&settings).await,
Self::Setup => return setup::run(&settings).await,
_ => {}
}

Expand All @@ -338,7 +339,6 @@ impl Cmd {
let theme = theme_manager.load_theme(theme_name.as_str(), settings.theme.max_depth);

match self {
Self::Setup => setup::run(&settings).await,
Self::Import(import) => import.run(&db).await,
Self::Stats(stats) => stats.run(&db, &settings, theme).await,
Self::Search(search) => search.run(db, &mut settings, sqlite_store, theme).await,
Expand Down Expand Up @@ -372,7 +372,7 @@ impl Cmd {
#[cfg(feature = "daemon")]
Self::Daemon(cmd) => cmd.run(settings, sqlite_store, db).await,

Self::History(_) | Self::Init(_) | Self::Doctor => unreachable!(),
Self::History(_) | Self::Init(_) | Self::Doctor | Self::Setup => unreachable!(),

#[cfg(feature = "ai")]
Self::Ai(cli) => atuin_ai::commands::run(cli, &settings).await,
Expand Down
96 changes: 93 additions & 3 deletions crates/atuin/src/command/client/setup.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,80 @@
use atuin_client::settings::Settings;
use std::io::{self, Write};
use std::path::PathBuf;

use atuin_client::{
database::{Database, Sqlite},
settings::Settings,
};
use colored::Colorize;
use eyre::Result;
use std::io::{self, Write};
use toml_edit::{DocumentMut, value};

pub async fn run(_settings: &Settings) -> Result<()> {
use super::import;

pub async fn run(settings: &Settings) -> Result<()> {
// Step 1: Offer to import shell history if the user has very few commands.
// Sqlite::new handles creating the DB and running migrations if it doesn't exist yet.
let db_path = PathBuf::from(settings.db_path.as_str());
let db = Sqlite::new(db_path, settings.local_timeout).await?;
let history_count = db.history_count(false).await.unwrap_or(0);

if history_count < 10 {
let do_import = prompt(
"Import shell history",
"You have fewer than 10 commands in Atuin. Import your existing shell history?",
)?;

if do_import {
println!();
if let Err(e) = import::Cmd::Auto.run(&db).await {
println!(
"{warn} History import failed: {e}",
warn = "!".bold().bright_yellow()
);
println!(" You can retry later with 'atuin import auto'.");
}
println!();
}
}

// Step 2: Offer to sign up for the hub if not already logged in
let logged_in = atuin_client::hub::is_logged_in().await.unwrap_or(false);
if !logged_in {
println!();
println!(" {title}", title = "Atuin Hub".bold().bright_blue());
println!(" Sync your history across all your machines:");
println!(" - End-to-end encrypted — only you can read your data");
println!(" - Access your history from any device");
println!(" - Never lose your history, even if you wipe a machine");
println!();

let do_signup = prompt("Sign up for Atuin Hub", "Create a free sync account?")?;

if do_signup {
let hub_address = settings
.active_hub_endpoint()
.unwrap_or_else(|| Settings::DEFAULT_HUB_ENDPOINT.to_string());

match hub_signup(&hub_address).await {
Ok(()) => {
println!(
"{check} Successfully authenticated with Atuin Hub!",
check = "✓".bold().bright_green()
);
}
Err(e) => {
println!(
"{warn} Hub sign up did not complete: {e}",
warn = "!".bold().bright_yellow()
);
println!(" You can sign up later with 'atuin login'.");
}
}
println!();
}
}

// Step 3: Configure features (AI, Daemon)
let enable_ai = prompt(
"Atuin AI",
"This will enable command generation and other AI features via the question mark key",
Expand Down Expand Up @@ -56,6 +125,27 @@ pub async fn run(_settings: &Settings) -> Result<()> {
Ok(())
}

async fn hub_signup(hub_address: &str) -> Result<()> {
let session = atuin_client::hub::HubAuthSession::start(hub_address).await?;

println!();
println!(" Open this URL to sign up:");
println!(" {url}", url = session.auth_url.bold().underline());
println!();
println!(" Waiting for authentication...");

let token = session
.wait_for_completion(
atuin_client::hub::DEFAULT_AUTH_TIMEOUT,
atuin_client::hub::DEFAULT_POLL_INTERVAL,
)
.await?;

atuin_client::hub::save_session(&token).await?;

Ok(())
}

pub fn prompt(feature: &str, description: &str) -> Result<bool> {
println!(
"> Enable {feature}?",
Expand Down