diff options
| author | Michelle Tilley <michelle@michelletilley.net> | 2026-03-11 08:50:17 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-03-11 08:50:17 -0700 |
| commit | c8fd7d3d8f2c93c24a4a5fc41361dfed8714e73a (patch) | |
| tree | e88cf8afa702be0925c6f606df2bb3c270679fce /crates/atuin-ai/src/commands/inline.rs | |
| parent | chore: update changelog (diff) | |
| download | atuin-c8fd7d3d8f2c93c24a4a5fc41361dfed8714e73a.zip | |
feat: Allow authenticating with Atuin Hub (#3237)
## Summary
This PR enables the Atuin CLI to authenticate with Atuin Hub, unifying
authentication across CLI sync and Hub features (AI, runbooks, etc.).
### Key Changes
- **Dual auth support**: New `AuthToken` enum supports both `Bearer`
(Hub) and `Token` (legacy CLI) authentication
- **Smart protocol selection**: New `sync_protocol` setting
(`auto`/`hub`/`legacy`) determines auth method. By default,
`api.atuin.sh` uses Hub auth; custom sync addresses use legacy auth
- **Hub login flow**: `atuin login` now initiates an OAuth-like flow for
Hub users—generates a code, user authorizes in browser, CLI polls for
completion
- **Account linking**: After Hub auth, silently attempts to link
existing CLI sync account to Hub account for seamless migration
- **Graceful fallback**: `sync_auth_token()` prefers Hub token when
available, falls back to CLI session token
### Auth Flow
1. User runs `atuin login` (with default sync address)
2. CLI requests auth code from Hub, displays URL
3. User opens URL, logs in/registers on Hub
4. Hub attaches API token to code
5. CLI polls, receives token, saves as hub session
6. If user had existing CLI sync account, it's automatically linked
### Backward Compatibility
- Existing self-hosted users: unaffected (legacy auth via `Token`
header)
- Existing `api.atuin.sh` users: continue working with CLI session until
they run `atuin login`
- New users: go through Hub flow automatically
## Test Plan
- [ ] New user registration via Hub flow
- [ ] Existing CLI user can still sync without changes
- [ ] `atuin login` links CLI account to Hub account
- [ ] Self-hosted users unaffected by changes
- [ ] AI commands work after Hub auth
---------
Co-authored-by: Ellie Huxtable <ellie@elliehuxtable.com>
Diffstat (limited to 'crates/atuin-ai/src/commands/inline.rs')
| -rw-r--r-- | crates/atuin-ai/src/commands/inline.rs | 35 |
1 files changed, 27 insertions, 8 deletions
diff --git a/crates/atuin-ai/src/commands/inline.rs b/crates/atuin-ai/src/commands/inline.rs index cd670bf8..df4a2d19 100644 --- a/crates/atuin-ai/src/commands/inline.rs +++ b/crates/atuin-ai/src/commands/inline.rs @@ -45,7 +45,12 @@ pub async fn run( let token = if let Some(token) = &api_token { token.to_string() } else { - ensure_hub_session(settings, endpoint).await? + // If no token is provided, assume we're using Hub as the endpoint if we're using Hub sync + if settings.is_hub_sync() { + ensure_hub_session(settings).await? + } else { + bail!("No API token provided in ai.api_token settings or command line argument.") + } }; let action = run_inline_tui( @@ -62,15 +67,16 @@ pub async fn run( Ok(()) } -async fn ensure_hub_session( - settings: &atuin_client::settings::Settings, - hub_address: &str, -) -> Result<String> { +async fn ensure_hub_session(settings: &atuin_client::settings::Settings) -> Result<String> { if let Some(token) = atuin_client::hub::get_session_token().await? { debug!("Found Hub session, using existing token"); return Ok(token); } + let hub_address = settings + .active_hub_endpoint() + .unwrap_or("https://hub.atuin.sh".to_string()); + info!("No Hub session found, prompting for authentication"); println!("Atuin AI requires authenticating with Atuin Hub."); @@ -83,9 +89,7 @@ async fn ensure_hub_session( debug!("Starting Atuin Hub authentication..."); println!("Authenticating with Atuin Hub..."); - let mut auth_settings = settings.clone(); - auth_settings.hub_address = hub_address.to_string(); - let session = atuin_client::hub::HubAuthSession::start(&auth_settings).await?; + let session = atuin_client::hub::HubAuthSession::start(&hub_address).await?; println!("Open this URL to continue:"); println!("{}", session.auth_url); @@ -99,6 +103,21 @@ async fn ensure_hub_session( info!("Authentication complete, saving session token"); atuin_client::hub::save_session(&token).await?; + + // Silently attempt to link CLI account to Hub if one exists + // This enables unified auth - users can use their Hub token for sync + if let Ok(meta) = atuin_client::settings::Settings::meta_store().await + && let Ok(Some(cli_token)) = meta.session_token().await + { + debug!("CLI session found, attempting to link accounts"); + if let Err(e) = atuin_client::hub::link_account(&hub_address, &cli_token).await { + // Don't fail AI flow if linking fails - it's not critical + debug!("Could not link CLI account to Hub: {}", e); + } else { + info!("Successfully linked CLI account to Hub"); + } + } + Ok(token) } |
