From 5034fb2c8e69f0254c4d554b48967be988d85078 Mon Sep 17 00:00:00 2001 From: Eddie Tejeda <669988+eddietejeda@users.noreply.github.com> Date: Fri, 24 Apr 2026 09:33:07 -0700 Subject: [PATCH] feat(auth): add login subcommand mirroring bare auth - AuthCommands::Login calls auth::login() like no subcommand - README, SKILL, and CLI error hints document auth login vs auth --- README.md | 6 ++++-- skills/hotdata/SKILL.md | 7 ++++--- src/api.rs | 6 +++--- src/command.rs | 3 +++ src/config.rs | 4 ++-- src/main.rs | 2 +- 6 files changed, 17 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index debebf0..fc7ae0e 100644 --- a/README.md +++ b/README.md @@ -33,9 +33,11 @@ cp target/release/hotdata /usr/local/bin/hotdata ## Connect -Run the following command to authenticate: +Run either of the following (they are equivalent): ```sh +hotdata auth login +# or hotdata auth ``` @@ -60,7 +62,7 @@ API key priority (lowest to highest): config file → `HOTDATA_API_KEY` env var | Command | Subcommands | Description | | :-- | :-- | :-- | -| `auth` | `status`, `logout` | Authenticate (run without subcommand to log in) | +| `auth` | `login`, `status`, `logout` | `login` or bare `auth` opens browser login; `status` / `logout` manage the saved profile | | `workspaces` | `list`, `set` | Manage workspaces | | `connections` | `list`, `create`, `refresh`, `new` | Manage connections | | `tables` | `list` | List tables and columns | diff --git a/skills/hotdata/SKILL.md b/skills/hotdata/SKILL.md index b1dd973..9e69645 100644 --- a/skills/hotdata/SKILL.md +++ b/skills/hotdata/SKILL.md @@ -16,10 +16,10 @@ Or if installed on PATH: `hotdata [args]` ## Authentication -Run `hotdata auth` to authenticate via browser login. Config is stored in `~/.hotdata/config.yml`. +Run **`hotdata auth login`** (or **`hotdata auth`** with no subcommand—same behavior) to authenticate via browser login. Config is stored in `~/.hotdata/config.yml`. API key resolution (lowest to highest priority): -1. Config file (saved by `hotdata auth`) +1. Config file (saved by `hotdata auth login` / `hotdata auth`) 2. `HOTDATA_API_KEY` environment variable (or `.env` file) 3. `--api-key ` flag (works on any command) @@ -325,7 +325,8 @@ hotdata jobs [--workspace-id ] [--output table|json|yaml] ### Auth ``` -hotdata auth # Browser-based login +hotdata auth login # Browser-based login (same as: hotdata auth) +hotdata auth # Browser-based login (same as: hotdata auth login) hotdata auth status # Check current auth status hotdata auth logout # Remove saved auth for the default profile ``` diff --git a/src/api.rs b/src/api.rs index 4a1a99f..b200ad4 100644 --- a/src/api.rs +++ b/src/api.rs @@ -27,7 +27,7 @@ impl ApiClient { let api_key = match &profile_config.api_key { Some(key) if key != "PLACEHOLDER" => key.clone(), _ => { - eprintln!("error: not authenticated. Run 'hotdata auth' to log in."); + eprintln!("error: not authenticated. Run 'hotdata auth login' (or 'hotdata auth') to log in."); std::process::exit(1); } }; @@ -294,7 +294,7 @@ fn format_fail_message( ) -> String { if status.is_client_error() { if let Some(auth::AuthStatus::Invalid(_)) = auth_status { - return "error: API key is invalid. Run 'hotdata auth' to re-authenticate.".to_string(); + return "error: API key is invalid. Run 'hotdata auth login' (or 'hotdata auth') to re-authenticate.".to_string(); } } util::api_error(body.to_string()) @@ -313,7 +313,7 @@ mod tests { Some(&AuthStatus::Invalid(401)), ); assert!(msg.contains("API key is invalid")); - assert!(msg.contains("hotdata auth")); + assert!(msg.contains("hotdata auth login") || msg.contains("hotdata auth")); } #[test] diff --git a/src/command.rs b/src/command.rs index 1cdd10f..2b2ab85 100644 --- a/src/command.rs +++ b/src/command.rs @@ -236,6 +236,9 @@ pub enum QueryCommands { #[derive(Subcommand)] pub enum AuthCommands { + /// Log in via browser (same as `hotdata auth` with no subcommand) + Login, + /// Remove authentication for a profile Logout, diff --git a/src/config.rs b/src/config.rs index 0bf73c1..d4355f9 100644 --- a/src/config.rs +++ b/src/config.rs @@ -266,7 +266,7 @@ pub fn resolve_workspace_id(provided: Option, profile_config: &ProfileCo .workspaces .first() .map(|w| w.public_id.clone()) - .ok_or_else(|| "no workspace-id provided and no default workspace found. Run 'hotdata auth' or specify --workspace-id.".to_string()) + .ok_or_else(|| "no workspace-id provided and no default workspace found. Run 'hotdata auth login' (or 'hotdata auth') or specify --workspace-id.".to_string()) } /// Global API key override set via --api-key flag. @@ -285,7 +285,7 @@ pub fn load(profile: &str) -> Result { fs::read_to_string(&config_file).map_err(|e| format!("error reading config file: {e}"))?; let config_file: ConfigFile = serde_yaml::from_str(&content).unwrap_or_else(|_| { eprintln!("{}", "error parsing config file.".red()); - eprintln!("Run 'hotdata auth' to generate a new config file."); + eprintln!("Run 'hotdata auth login' (or 'hotdata auth') to generate a new config file."); std::process::exit(1); }); config_file.profiles.get(profile).cloned().unwrap_or_default() diff --git a/src/main.rs b/src/main.rs index 813f759..9807a75 100644 --- a/src/main.rs +++ b/src/main.rs @@ -92,7 +92,7 @@ fn main() { } Some(cmd) => match cmd { Commands::Auth { command } => match command { - None => auth::login(), + None | Some(AuthCommands::Login) => auth::login(), Some(AuthCommands::Status) => auth::status("default"), Some(AuthCommands::Logout) => auth::logout("default"), },