diff --git a/README.md b/README.md index aa927ea6..c8892890 100644 --- a/README.md +++ b/README.md @@ -360,7 +360,7 @@ Local version and pagination-key conversion helpers: Auth resolution order for commands is: 1. Explicit `--profile` -2. `--api-key` or `BRAINTRUST_API_KEY` (unless `--prefer-profile` is set) +2. `BRAINTRUST_API_KEY` (unless `--prefer-profile` is set) 3. `BRAINTRUST_PROFILE` 4. Compatible profile for the selected app URL and organization 5. Single-profile auto-select (if only one compatible profile exists) diff --git a/scripts/eval-runner.py b/scripts/eval-runner.py index ab2a785f..561067a1 100755 --- a/scripts/eval-runner.py +++ b/scripts/eval-runner.py @@ -1419,7 +1419,6 @@ def build_parser() -> argparse.ArgumentParser: parser = argparse.ArgumentParser(description="Run evals and emit SSE events for bt.") parser.add_argument("files", nargs="*", help="Eval files or directories to run.") parser.add_argument("--local", action="store_true", help="Do not send logs to Braintrust.") - parser.add_argument("--api-key", help="Specify a braintrust API key.") parser.add_argument("--org-name", help="Organization name.") parser.add_argument("--app-url", help="Braintrust app URL.") return parser @@ -1442,7 +1441,7 @@ def main(argv: list[str] | None = None) -> int: try: try: if not local: - login(api_key=args.api_key, org_name=args.org_name, app_url=args.app_url) + login(org_name=args.org_name, app_url=args.app_url) success = asyncio.run(run_once(files, local, sse, config)) except Exception as exc: stack = traceback.format_exc() diff --git a/src/args.rs b/src/args.rs index 313341bf..a98e1b2d 100644 --- a/src/args.rs +++ b/src/args.rs @@ -46,14 +46,18 @@ pub struct LoginBaseArgs { #[arg(skip = false)] pub profile_explicit: bool, - /// Override stored API key (or via BRAINTRUST_API_KEY) - #[arg(long, env = "BRAINTRUST_API_KEY", global = true, hide = true)] + #[arg( + long = "braintrust-internal-api-key-environment-binding-do-not-use", + env = "BRAINTRUST_API_KEY", + global = true, + hide = true + )] pub api_key: Option, #[arg(skip)] pub api_key_source: Option, - /// Prefer profile credentials even if BRAINTRUST_API_KEY/--api-key is set. + /// Prefer profile credentials even if BRAINTRUST_API_KEY is set. #[arg(long, global = true)] pub prefer_profile: bool, diff --git a/src/auth.rs b/src/auth.rs index 3bf08fad..2ebf98b4 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -459,9 +459,7 @@ pub async fn fast_login(base: &BaseArgs) -> Result { maybe_warn_api_key_override(base); let auth = resolve_auth(base).await?; let api_key = auth.api_key.clone().ok_or_else(|| { - anyhow::anyhow!( - "no login credentials found; set BRAINTRUST_API_KEY, pass --api-key, or run `bt login`" - ) + anyhow::anyhow!("no login credentials found; set BRAINTRUST_API_KEY or run `bt login`") })?; let org_name = auth.org_name.clone().unwrap_or_default(); let api_url = auth @@ -494,9 +492,7 @@ pub async fn login(base: &BaseArgs) -> Result { maybe_warn_api_key_override(base); let auth = resolve_auth(base).await?; let api_key = auth.api_key.clone().ok_or_else(|| { - anyhow::anyhow!( - "no login credentials found; set BRAINTRUST_API_KEY, pass --api-key, or run `bt login`" - ) + anyhow::anyhow!("no login credentials found; set BRAINTRUST_API_KEY or run `bt login`") })?; let mut builder = BraintrustClient::builder() @@ -778,7 +774,7 @@ fn maybe_warn_api_key_override(base: &BaseArgs) { if let Some(profile_name) = ignored_profile { eprintln!( - "Info: using --api-key/BRAINTRUST_API_KEY credentials; selected profile '{profile_name}' is ignored for this command. Use --prefer-profile or unset BRAINTRUST_API_KEY to use a profile with OAuth login.", + "Info: using BRAINTRUST_API_KEY credentials; selected profile '{profile_name}' is ignored for this command. Use --prefer-profile or unset BRAINTRUST_API_KEY to use a profile with OAuth login.", ); } } @@ -792,12 +788,7 @@ fn has_explicit_profile_selection(base: &BaseArgs) -> bool { } fn resolve_api_key_override(base: &BaseArgs) -> Option { - if (base.prefer_profile || has_explicit_profile_selection(base)) - && !matches!( - base.api_key_source, - Some(crate::args::ArgValueSource::CommandLine) - ) - { + if base.prefer_profile || has_explicit_profile_selection(base) { return None; } let value = base.api_key.as_deref()?.trim(); @@ -2864,7 +2855,7 @@ fn build_oauth_client( fn prompt_api_key() -> Result { let term = ui::prompt_term() - .ok_or_else(|| anyhow::anyhow!("--api-key is required in non-interactive mode"))?; + .ok_or_else(|| anyhow::anyhow!("BRAINTRUST_API_KEY is required in non-interactive mode"))?; let api_key = Password::new() .with_prompt("Braintrust API key") .allow_empty_password(false) @@ -2882,7 +2873,7 @@ fn prompt_api_key() -> Result { fn linux_secret_tool_exec_error(err: std::io::Error) -> anyhow::Error { if err.kind() == std::io::ErrorKind::NotFound { anyhow::anyhow!( - "`secret-tool` is not installed. Install `libsecret-tools` (Debian/Ubuntu) or your distro's equivalent package, or use BRAINTRUST_API_KEY/--api-key for non-persistent auth." + "`secret-tool` is not installed. Install `libsecret-tools` (Debian/Ubuntu) or your distro's equivalent package, or use BRAINTRUST_API_KEY for non-persistent auth." ) } else { anyhow::anyhow!("failed to execute Linux keychain utility `secret-tool`: {err}") @@ -2899,7 +2890,7 @@ fn linux_secret_service_unavailable(stderr: &str) -> bool { #[cfg(target_os = "linux")] fn linux_secret_service_error() -> anyhow::Error { anyhow::anyhow!( - "no Secret Service provider is running. Start a Secret Service daemon (for example gnome-keyring or keepassxc with Secret Service enabled), or use BRAINTRUST_API_KEY/--api-key for non-persistent auth." + "no Secret Service provider is running. Start a Secret Service daemon (for example gnome-keyring or keepassxc with Secret Service enabled), or use BRAINTRUST_API_KEY for non-persistent auth." ) } @@ -4012,40 +4003,6 @@ mod tests { assert_eq!(resolved.org_name.as_deref(), Some("Example Org")); } - #[test] - fn resolve_auth_prefers_cli_api_key_even_with_prefer_profile() { - let mut base = make_base(); - base.api_key = Some("explicit-key".to_string()); - base.api_key_source = Some(crate::args::ArgValueSource::CommandLine); - base.prefer_profile = true; - base.profile = Some("work".to_string()); - - let mut store = AuthStore::default(); - store.profiles.insert( - "work".to_string(), - AuthProfile { - auth_kind: AuthKind::ApiKey, - oauth_api_url: Some("https://api.example.com".to_string()), - app_url: None, - org_name: Some("Example Org".to_string()), - org_bound: Some(true), - oauth_client_id: None, - oauth_access_expires_at: None, - ..Default::default() - }, - ); - - let resolved = resolve_auth_from_store_with_secret_lookup( - &base, - &store, - |_| Ok(Some("profile-key".to_string())), - &None, - ) - .expect("resolve"); - assert_eq!(resolved.api_key.as_deref(), Some("explicit-key")); - assert_eq!(resolved.org_name, None); - } - #[test] fn resolve_auth_explicit_profile_ignores_env_api_key_override() { let mut base = make_base(); diff --git a/src/eval.rs b/src/eval.rs index 9f4dfc93..62d922be 100644 --- a/src/eval.rs +++ b/src/eval.rs @@ -3115,7 +3115,7 @@ impl EvalUi { self.record_deferred_error(message); } if show_hint { - let hint = "Hint: pass --api-key, set BRAINTRUST_API_KEY, run `bt login`/`bt login --oauth`, or use --no-send-logs for local evals."; + let hint = "Hint: set BRAINTRUST_API_KEY, run `bt login`/`bt login --oauth`, or use --no-send-logs for local evals."; if self.verbose { let _ = self.progress.println(hint.dark_grey().to_string()); } else { diff --git a/src/main.rs b/src/main.rs index 812f36d6..aa85c06c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -392,6 +392,11 @@ fn apply_base_arg_sources(matches: &ArgMatches, base: &mut LoginBaseArgs) { base.verbose_source = find_value_source(matches, "verbose").and_then(map_value_source); base.quiet_source = find_value_source(matches, "quiet").and_then(map_value_source); base.api_key_source = find_value_source(matches, "api_key").and_then(map_value_source); + + if !matches!(base.api_key_source, Some(ArgValueSource::EnvVariable)) { + base.api_key = None; + base.api_key_source = None; + } } fn apply_base_output_defaults(command: &mut Commands) { @@ -605,27 +610,6 @@ mod tests { } } - #[test] - fn apply_base_arg_sources_tracks_cli_api_key() { - let _guard = env_test_lock().lock().expect("env test lock"); - let previous_api_key = env::var_os("BRAINTRUST_API_KEY"); - env::remove_var("BRAINTRUST_API_KEY"); - - let matches = Cli::command() - .try_get_matches_from(["bt", "status", "--api-key", "secret"]) - .expect("matches"); - let mut cli = Cli::from_arg_matches(&matches).expect("cli"); - - apply_base_arg_sources(&matches, cli.command.base_mut()); - - restore_env_var("BRAINTRUST_API_KEY", previous_api_key); - - assert_eq!( - cli.command.base().api_key_source, - Some(ArgValueSource::CommandLine) - ); - } - #[test] fn apply_base_arg_sources_leaves_api_key_source_empty_when_unset() { let _guard = env_test_lock().lock().expect("env test lock"); diff --git a/src/setup/mod.rs b/src/setup/mod.rs index 4a9cf3a3..69af5f86 100644 --- a/src/setup/mod.rs +++ b/src/setup/mod.rs @@ -2200,12 +2200,7 @@ async fn build_setup_auth_context( } fn should_create_api_key_for_setup(is_oauth: bool, base: &BaseArgs, needs_api_key: bool) -> bool { - needs_api_key - && is_oauth - && !matches!( - base.api_key_source, - Some(ArgValueSource::CommandLine | ArgValueSource::EnvVariable) - ) + needs_api_key && is_oauth && !matches!(base.api_key_source, Some(ArgValueSource::EnvVariable)) } async fn maybe_create_api_key_for_oauth(base: &BaseArgs, client: &ApiClient) -> Result {