Skip to content
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,13 @@ cp target/release/hotdata /usr/local/bin/hotdata

## Connect

Run either of the following (they are equivalent):
Run:

```sh
hotdata auth login
# or
hotdata auth
```

This launches a browser window where you can authorize the CLI to access your Hotdata account.
This launches a browser window where you can authorize the CLI to access your Hotdata account. (Bare `hotdata auth` prints the `auth` subcommand help.)

Alternatively, authenticate with an API key using the `--api-key` flag:

Expand All @@ -62,7 +60,7 @@ API key priority (lowest to highest): config file → `HOTDATA_API_KEY` env var

| Command | Subcommands | Description |
| :-- | :-- | :-- |
| `auth` | `login`, `status`, `logout` | `login` or bare `auth` opens browser login; `status` / `logout` manage the saved profile |
| `auth` | `login`, `status`, `logout` | `login` opens browser login; `status` / `logout` manage the saved profile |
| `workspaces` | `list`, `set` | Manage workspaces |
| `connections` | `list`, `create`, `refresh`, `new` | Manage connections |
| `databases` | `list`, `create`, `delete`, `tables` | Managed databases (create and load tables via parquet) |
Expand Down
7 changes: 3 additions & 4 deletions skills/hotdata/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ Install all skills with **`hotdata skills install`**. Load specialized skills on

## Authentication

Run **`hotdata auth login`** (or **`hotdata auth`** with no subcommand—same behavior) to authenticate via browser login. Config is stored in `~/.hotdata/config.yml`.
Run **`hotdata auth login`** 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 login` / `hotdata auth`)
1. Config file (saved by `hotdata auth login`)
2. `HOTDATA_API_KEY` environment variable (or `.env` file)
3. `--api-key <key>` flag (works on any command)

Expand Down Expand Up @@ -336,8 +336,7 @@ A newer release can be incompatible with the API, so in an **interactive termina

### Auth
```
hotdata auth login # Browser-based login (same as: hotdata auth)
hotdata auth # Browser-based login (same as: hotdata auth login)
hotdata auth login # Browser-based login
hotdata auth status # Check current auth status
hotdata auth logout # Remove saved auth for the default profile
```
Expand Down
2 changes: 1 addition & 1 deletion skills/hotdata/references/WORKFLOWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ End-to-end checklists. Use the linked sections for command detail and guardrails

**Skill:** **`hotdata`** (optional **`hotdata-analytics`** for first queries)

1. [ ] `hotdata auth login` (or `hotdata auth`)
1. [ ] `hotdata auth login`
2. [ ] `hotdata workspaces list` → `hotdata workspaces set` if not on the right workspace
3. [ ] `hotdata connections list` — note connection ids and names
4. [ ] (Optional) `hotdata connections create …` — see **`hotdata`** skill **Create a Connection**
Expand Down
2 changes: 1 addition & 1 deletion src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ pub enum QueryCommands {

#[derive(Subcommand)]
pub enum AuthCommands {
/// Log in via browser (same as `hotdata auth` with no subcommand)
/// Log in via browser
Login,

/// Create a new account via browser (defaults to GitHub OAuth)
Expand Down
4 changes: 2 additions & 2 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ pub fn resolve_workspace_id(
.workspaces
.first()
.map(|w| w.public_id.clone())
.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())
.ok_or_else(|| "no workspace-id provided and no default workspace found. Run 'hotdata auth login' or specify --workspace-id.".to_string())
}

/// Global API key override set via --api-key flag.
Expand All @@ -284,7 +284,7 @@ pub fn load(profile: &str) -> Result<ProfileConfig, String> {
let config_file: ConfigFile = serde_yaml::from_str(&content).unwrap_or_else(|_| {
eprintln!("{}", "error parsing config file.".red());
eprintln!(
"Run 'hotdata auth login' (or 'hotdata auth') to generate a new config file."
"Run 'hotdata auth login' to generate a new config file."
);
std::process::exit(1);
});
Expand Down
10 changes: 5 additions & 5 deletions src/jwt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
//! | Access token valid for > 30 s | return it directly |
//! | Access expiring or expired, refresh token valid | call `/o/token/` with `grant_type=refresh_token` |
//! | Refresh token dead, `api_key` present | re-mint via `grant_type=api_token` |
//! | Refresh token dead, no `api_key` | return an error — user must `hotdata auth` again |
//! | Refresh token dead, no `api_key` | return an error — user must `hotdata auth login` again |
//!
//! The raw `hd_...` API token (flow 3 in the design doc) is *never*
//! persisted to the session file — it stays in the main config or the
Expand Down Expand Up @@ -302,7 +302,7 @@ pub fn ensure_access_token(
// 0) An explicit identity override (`--api-key`, `HOTDATA_API_KEY`,
// or `.env`) is asserting a specific identity for *this invocation*.
// The on-disk session may belong to a completely different user
// from a prior `hotdata auth` and must not be reused. Mint fresh
// from a prior `hotdata auth login` and must not be reused. Mint fresh
// and deliberately skip persisting so we don't clobber the
// interactive session. Surface the real mint error here too — if
// the override key is bad, "HTTP 401" is more useful than the
Expand Down Expand Up @@ -358,7 +358,7 @@ pub fn ensure_access_token(
// API token rejected (revoked, expired, or invalid).
// Fall through to the re-auth hint — hide the raw HTTP
// error from the user; the api.rs caller appends a
// `hotdata auth` hint.
// `hotdata auth login` hint.
}
}
}
Expand Down Expand Up @@ -435,7 +435,7 @@ impl hotdata::auth::BearerTokenProvider for CliTokenProvider {
resolved.map_err(|body| {
// Surface as a 401 so `Configuration::resolve_bearer_token` logs the
// cause and the request proceeds to a 401 the wrapper shapes into
// the "run hotdata auth" hint (the same end-state as the old
// the "run hotdata auth login" hint (the same end-state as the old
// ApiClient refresher returning None).
hotdata::auth::TokenExchangeError::Status { status: 401, body }
})
Expand Down Expand Up @@ -1235,7 +1235,7 @@ mod tests {
let (_tmp, _guard) = with_temp_config_dir();
// No session, no api_key fallback -> ensure_access_token errors, the
// provider maps it to a 401 so the request proceeds to the wrapper's
// "run hotdata auth" hint.
// "run hotdata auth login" hint.
let profile = mock_profile("http://127.0.0.1:1");
let provider = session_provider(&profile, None);
match bearer(&provider).unwrap_err() {
Expand Down
15 changes: 13 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,9 @@ fn main() {
// never blocked (see `update::should_check`).
let gate_update = !matches!(
&cli.command,
None | Some(Commands::Upgrade) | Some(Commands::Completions { .. })
None | Some(Commands::Upgrade)
| Some(Commands::Completions { .. })
| Some(Commands::Auth { command: None })
);
if gate_update {
update::enforce_latest_or_exit();
Expand All @@ -191,10 +193,19 @@ fn main() {
}
Some(cmd) => match cmd {
Commands::Auth { command } => match command {
None | Some(AuthCommands::Login) => auth::login(),
Some(AuthCommands::Login) => auth::login(),
Some(AuthCommands::Register { email }) => auth::register(email),
Some(AuthCommands::Status) => auth::status("default"),
Some(AuthCommands::Logout) => auth::logout("default"),
None => {
use clap::CommandFactory;
let mut cmd = Cli::command();
cmd.build();
cmd.find_subcommand_mut("auth")
.unwrap()
.print_help()
.unwrap();
}
Comment on lines +200 to +208

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This behavior change leaves several user-facing hints that still tell users bare hotdata auth will log them in — they're now incorrect and should be updated in this PR:

  • src/sdk.rs:460-463 (blocking): "Run {} to log in, or pass --api-key." with "hotdata auth". This is the clearest break — it only references bare hotdata auth, which now just prints help. A user hitting an expired session who follows this hint will not be logged in. It should point at hotdata auth login.
  • src/sdk.rs:818: "... Run 'hotdata auth login' (or 'hotdata auth') to re-authenticate." — the (or 'hotdata auth') parenthetical is now wrong.
  • src/config.rs:267: "... Run 'hotdata auth login' (or 'hotdata auth') or specify --workspace-id." — same stale parenthetical.
  • src/config.rs:287: "Run 'hotdata auth login' (or 'hotdata auth') to generate a new config file." — same.
  • src/jwt.rs:15 (doc comment): "user must hotdata auth again" — minor, but worth updating for accuracy.

The PR's goal is consistency, so leaving these pointing at the removed shortcut is contradictory.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking (re-raised from cycle 1, still unaddressed): This PR changes bare hotdata auth to print help instead of logging in, but the user-facing hints that still tell users hotdata auth will log them in were not updated. They are now incorrect:

  • src/sdk.rs:460-463 (most severe): "Run {} to log in, or pass --api-key." with "hotdata auth".cyan(). This references only bare hotdata auth, which now just prints help — a user hitting an expired session who follows this hint will not be logged in. Should point at hotdata auth login.
  • src/sdk.rs:818: "... Run 'hotdata auth login' (or 'hotdata auth') to re-authenticate." — the (or 'hotdata auth') parenthetical is now wrong.
  • src/config.rs:288: "... Run 'hotdata auth login' (or 'hotdata auth') or specify --workspace-id." — same stale parenthetical.
  • src/config.rs:308: "Run 'hotdata auth login' (or 'hotdata auth') to generate a new config file." — same.
  • src/jwt.rs:15 (doc comment): "user must hotdata auth again" — minor, but worth updating for accuracy.

The PR's stated goal is consistency; leaving these pointing at the removed shortcut is contradictory. The sdk.rs:460-463 case is a real correctness break (the hint sends users down a dead end), so this remains blocking.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking (re-raised from cycles 1 & 2, still unaddressed): This PR changes bare hotdata auth to print help instead of logging in, but the user-facing hints that still tell users hotdata auth will log them in were not updated. None of src/sdk.rs, src/config.rs, or src/jwt.rs were touched in this PR, so they remain stale and now incorrect:

  • src/sdk.rs:460-463 (real correctness break): "Run {} to log in, or pass --api-key." with "hotdata auth".cyan(). This references only bare hotdata auth, which now just prints help — a user hitting an expired session who follows this hint will not be logged in. It must point at hotdata auth login.
  • src/sdk.rs:818: "... Run 'hotdata auth login' (or 'hotdata auth') to re-authenticate." — the (or 'hotdata auth') parenthetical is now wrong.
  • src/config.rs:267: "... Run 'hotdata auth login' (or 'hotdata auth') or specify --workspace-id." — same stale parenthetical.
  • src/config.rs:287: "Run 'hotdata auth login' (or 'hotdata auth') to generate a new config file." — same.
  • src/jwt.rs:15 (doc comment): "user must hotdata auth again" — minor, but worth updating for accuracy.

The PR's stated goal is consistency; leaving these pointing at the removed shortcut is contradictory. The sdk.rs:460-463 case sends users down a dead end, so this remains blocking.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking (re-raised from cycles 1, 2 & 3 — still unaddressed): This PR changes bare hotdata auth to print help instead of logging in, but the user-facing hints that still tell users hotdata auth will log them in were not updated. sdk.rs, config.rs, and jwt.rs are untouched in this PR, so they remain stale and are now incorrect:

  • src/sdk.rs:485-486 (real correctness break): "Run {} to log in, or pass --api-key." with "hotdata auth".cyan(). This references only bare hotdata auth, which now just prints help — a user hitting an expired session who follows this hint will not be logged in. It must point at hotdata auth login.
  • src/sdk.rs:843: "... Run 'hotdata auth login' (or 'hotdata auth') to re-authenticate." — the (or 'hotdata auth') parenthetical is now wrong.
  • src/config.rs:267: "... Run 'hotdata auth login' (or 'hotdata auth') or specify --workspace-id." — same stale parenthetical.
  • src/config.rs:287: "Run 'hotdata auth login' (or 'hotdata auth') to generate a new config file." — same.
  • src/jwt.rs:15 (doc comment): "user must hotdata auth again" — minor, but worth updating for accuracy.

The PR's stated goal is consistency; leaving these pointing at the removed shortcut is contradictory. The sdk.rs:485-486 case sends users down a dead end, so this remains blocking.

},
Commands::Query {
sql,
Expand Down
10 changes: 5 additions & 5 deletions src/sdk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ impl ApiError {
/// `ApiClient::fail_response`'s formatting.
///
/// On a 4xx, re-probe the auth status so a masked 404/403 is upgraded into
/// the "run hotdata auth" hint; otherwise surface the server body. Split out
/// the "run hotdata auth login" hint; otherwise surface the server body. Split out
/// from [`exit`](Self::exit) so callers that want to append their own hint
/// after the error (e.g. the query cross-source hint) can print, add the
/// hint, then exit.
Expand Down Expand Up @@ -483,7 +483,7 @@ impl Api {
eprintln!("{}", format!("error: {e}").red());
eprintln!(
"Run {} to log in, or pass --api-key.",
"hotdata auth".cyan()
"hotdata auth login".cyan()
);
std::process::exit(1);
}
Expand Down Expand Up @@ -840,7 +840,7 @@ pub fn format_fail_message(
if status.is_client_error()
&& let Some(auth::AuthStatus::Invalid(_)) = auth_status
{
return "error: API key is invalid. Run 'hotdata auth login' (or 'hotdata auth') to re-authenticate.".to_string();
return "error: API key is invalid. Run 'hotdata auth login' to re-authenticate.".to_string();
}
// A 403 ACCESS_DENIED is the allow-list guard rejecting an operation the
// credential can't perform — typically a database API token (which is
Expand Down Expand Up @@ -886,7 +886,7 @@ mod tests {
Some(&AuthStatus::Invalid(401)),
);
assert!(msg.contains("API key is invalid"));
assert!(msg.contains("hotdata auth login") || msg.contains("hotdata auth"));
assert!(msg.contains("hotdata auth login"));
}

#[test]
Expand Down Expand Up @@ -962,7 +962,7 @@ mod tests {
msg.to_lowercase().contains("database api token"),
"got: {msg}"
);
assert!(msg.contains("hotdata auth"), "got: {msg}");
assert!(msg.contains("hotdata auth login"), "got: {msg}");
}

#[test]
Expand Down
Loading