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
38 changes: 19 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,34 +312,34 @@ Local version and pagination-key conversion helpers:

## `bt auth`

- Authenticate interactively (prompts for auth method, profile name defaults to org name):
- Authenticate interactively (prompts for auth method and organization):
- `bt auth login`
- First prompt chooses: `OAuth (browser)` (default) or `API key`.
- If your API key can access multiple orgs, `bt` uses a searchable picker (alphabetized) and lets you choose a specific org or no default org (cross-org mode).
- After login, `bt` updates the active profile/org context immediately. If `--project` is set, it also switches that project; otherwise it clears any stale default project for the new login.
- OAuth can be saved for an org or in cross-org mode. API-key logins are saved per org; if a key can access multiple orgs, `bt` uses a searchable org picker.
- After login, `bt` updates the active org context immediately. If `--project` is set, it also switches that project; otherwise it clears any stale default project for the new login.
- `bt` confirms the resolved API URL before saving.
- Login with OAuth (browser-based, stores refresh token in secure credential store):
- `bt auth login --oauth --profile work`
- `bt auth login --oauth --org myorg`
- You can pass `--no-browser` to print the URL without auto-opening.
- On remote/SSH hosts, paste the final callback URL from your local browser if localhost callback cannot be delivered.
- List profiles:
- `bt auth profiles`
- Log out (remove a saved profile):
- `bt auth logout`
- List saved auth logins:
- `bt auth logins`
- Log out:
- `bt auth logout --org myorg`
- `bt auth logout --org myorg --api-key-hint sk-****abcde`
- `bt auth logout --force` (skip confirmation)
- Show current auth source/profile:
- `bt auth status`
- Show current auth context:
- `bt status`
- Force-refresh OAuth access token for debugging:
- `bt auth refresh --profile work`
- `bt auth refresh --org myorg`

Auth resolution order for commands is:

1. Explicit `--profile`
2. `--api-key` or `BRAINTRUST_API_KEY` (unless `--prefer-profile` is set)
3. `BRAINTRUST_PROFILE`
4. Org-based profile match (profile whose org matches `--org`/config org)
5. Single-profile auto-select (if only one profile exists)
6. Interactive profile picker (if multiple profiles exist and a TTY is available)
1. Explicit `--api-key sk-...`
2. `--prefer-api-key` / `BRAINTRUST_PREFER_API_KEY` (uses `BRAINTRUST_API_KEY` first, then a stored API key for the selected org, then falls back to OAuth)
3. Stored OAuth login for the selected org (or cross-org OAuth when selected)
4. Stored API key login for the selected org when no OAuth login is available
5. `BRAINTRUST_API_KEY`

On Linux, secure storage uses `secret-tool` (libsecret) with a running Secret Service daemon. On macOS, it uses the `security` keychain utility. If a secure store is unavailable, `bt` falls back to a plaintext secrets file with `0600` permissions.

Expand All @@ -357,8 +357,8 @@ Interactively switch org and project context:

Show current org and project context:

- `bt status` — display current org, project, and config source
- `bt status --verbose` — show detailed config resolution
- `bt status` — display current org, project, selected auth method, and config source
- `bt status --verbose` — show detailed config and auth resolution
- `bt status -j` — JSON output

## `bt setup` and `bt docs`
Expand Down
81 changes: 9 additions & 72 deletions src/args.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use std::ffi::OsString;
use std::path::{Path, PathBuf};

use clap::Args;
Expand Down Expand Up @@ -39,17 +38,13 @@ pub struct BaseArgs {
#[arg(long, env = "BRAINTRUST_NO_INPUT", global = true, value_parser = clap::builder::BoolishValueParser::new(), default_value_t = false)]
pub no_input: bool,

/// Use a saved login profile (or via BRAINTRUST_PROFILE)
#[arg(long, env = "BRAINTRUST_PROFILE", global = true)]
pub profile: Option<String>,

#[arg(skip = false)]
pub profile_explicit: bool,

/// Override active org (or via BRAINTRUST_ORG_NAME)
#[arg(short = 'o', long = "org", env = "BRAINTRUST_ORG_NAME", global = true)]
pub org_name: Option<String>,

#[arg(skip)]
pub org_name_source: Option<ArgValueSource>,

/// Override active project
#[arg(
short = 'p',
Expand All @@ -60,16 +55,19 @@ pub struct BaseArgs {
)]
pub project: Option<String>,

#[arg(skip)]
pub project_source: Option<ArgValueSource>,

/// Override stored API key (or via BRAINTRUST_API_KEY)
#[arg(long, env = "BRAINTRUST_API_KEY", global = true, hide = true)]
pub api_key: Option<String>,

#[arg(skip)]
pub api_key_source: Option<ArgValueSource>,

/// Prefer profile credentials even if BRAINTRUST_API_KEY/--api-key is set.
#[arg(long, global = true)]
pub prefer_profile: bool,
/// Prefer API key credentials for the selected org when available.
#[arg(long = "prefer-api-key", env = "BRAINTRUST_PREFER_API_KEY", global = true, value_parser = clap::builder::BoolishValueParser::new(), default_value_t = false)]
pub prefer_api_key: bool,

/// Override API URL (or via BRAINTRUST_API_URL)
#[arg(
Expand Down Expand Up @@ -126,64 +124,3 @@ impl BaseArgs {
self.verbose && self.verbose_source.is_some()
}
}

pub fn has_explicit_profile_arg(args: &[OsString]) -> bool {
let mut idx = 1usize;
while idx < args.len() {
let Some(arg) = args[idx].to_str() else {
idx += 1;
continue;
};

if arg == "--" {
break;
}

if arg == "--profile" || arg.starts_with("--profile=") {
return true;
}

idx += 1;
}

false
}

#[cfg(test)]
mod tests {
use super::has_explicit_profile_arg;
use std::ffi::OsString;

#[test]
fn has_explicit_profile_arg_detects_split_flag() {
let args = vec![
OsString::from("bt"),
OsString::from("status"),
OsString::from("--profile"),
OsString::from("work"),
];
assert!(has_explicit_profile_arg(&args));
}

#[test]
fn has_explicit_profile_arg_detects_equals_flag() {
let args = vec![
OsString::from("bt"),
OsString::from("status"),
OsString::from("--profile=work"),
];
assert!(has_explicit_profile_arg(&args));
}

#[test]
fn has_explicit_profile_arg_ignores_passthrough_args() {
let args = vec![
OsString::from("bt"),
OsString::from("eval"),
OsString::from("--"),
OsString::from("--profile"),
OsString::from("work"),
];
assert!(!has_explicit_profile_arg(&args));
}
}
Loading
Loading