Add `uv auth` commands (`login`, `logout`, and `token`) (#15539)

Picks up the work from

- #14559
- https://github.com/astral-sh/uv/pull/14896

There are some high-level changes from those pull requests

1. We do not stash seen credentials in the keyring automatically
2. We use `auth login` and `auth logout` (for future consistency)
3. We add a `token` command for showing the credential that will be used

As well as many smaller changes to API, messaging, testing, etc.

---------

Co-authored-by: John Mumm <jtfmumm@gmail.com>
This commit is contained in:
Zanie Blue 2025-08-31 15:14:51 -05:00
parent f76e0fe5e6
commit 460ea6e9eb
27 changed files with 1694 additions and 20 deletions

View File

@ -2,3 +2,10 @@
# Mark tests that take longer than 10s as slow.
# Terminate after 120s as a stop-gap measure to terminate on deadlock.
slow-timeout = { period = "10s", terminate-after = 12 }
[test-groups]
serial = { max-threads = 1 }
[[profile.default.overrides]]
filter = 'test(native_keyring)'
test-group = 'serial'

3
Cargo.lock generated
View File

@ -5089,9 +5089,11 @@ dependencies = [
"serde",
"tempfile",
"test-log",
"thiserror 2.0.16",
"tokio",
"tracing",
"url",
"uv-keyring",
"uv-once-map",
"uv-redacted",
"uv-small-str",
@ -5407,6 +5409,7 @@ dependencies = [
"uv-pep440",
"uv-pep508",
"uv-platform-tags",
"uv-redacted",
"uv-static",
]

View File

@ -43,6 +43,7 @@ uv-git-types = { path = "crates/uv-git-types" }
uv-globfilter = { path = "crates/uv-globfilter" }
uv-install-wheel = { path = "crates/uv-install-wheel", default-features = false }
uv-installer = { path = "crates/uv-installer" }
uv-keyring = { path = "crates/uv-keyring" }
uv-logging = { path = "crates/uv-logging" }
uv-macros = { path = "crates/uv-macros" }
uv-metadata = { path = "crates/uv-metadata" }

View File

@ -10,6 +10,7 @@ doctest = false
workspace = true
[dependencies]
uv-keyring = { workspace = true, features = ["apple-native", "secret-service", "windows-native"] }
uv-once-map = { workspace = true }
uv-redacted = { workspace = true }
uv-small-str = { workspace = true }
@ -28,6 +29,7 @@ rust-netrc = { workspace = true }
rustc-hash = { workspace = true }
schemars = { workspace = true, optional = true }
serde = { workspace = true, features = ["derive"] }
thiserror = { workspace = true }
tokio = { workspace = true }
tracing = { workspace = true }
url = { workspace = true }

View File

@ -1,11 +1,14 @@
use std::{io::Write, process::Stdio};
use tokio::process::Command;
use tracing::{instrument, trace, warn};
use tracing::{debug, instrument, trace, warn};
use uv_redacted::DisplaySafeUrl;
use uv_warnings::warn_user_once;
use crate::credentials::Credentials;
/// Service name prefix for storing credentials in a keyring.
static UV_SERVICE_PREFIX: &str = "uv:";
/// A backend for retrieving credentials from a keyring.
///
/// See pip's implementation for reference
@ -15,15 +18,47 @@ pub struct KeyringProvider {
backend: KeyringProviderBackend,
}
#[derive(Debug)]
pub(crate) enum KeyringProviderBackend {
/// Use the `keyring` command to fetch credentials.
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error(transparent)]
Keyring(#[from] uv_keyring::Error),
#[error("The '{0}' keyring provider does not support storing credentials")]
StoreUnsupported(KeyringProviderBackend),
#[error("The '{0}' keyring provider does not support removing credentials")]
RemoveUnsupported(KeyringProviderBackend),
}
#[derive(Debug, Clone)]
pub enum KeyringProviderBackend {
/// Use a native system keyring integration for credentials.
Native,
/// Use the external `keyring` command for credentials.
Subprocess,
#[cfg(test)]
Dummy(Vec<(String, &'static str, &'static str)>),
}
impl std::fmt::Display for KeyringProviderBackend {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Native => write!(f, "native"),
Self::Subprocess => write!(f, "subprocess"),
#[cfg(test)]
Self::Dummy(_) => write!(f, "dummy"),
}
}
}
impl KeyringProvider {
/// Create a new [`KeyringProvider::Native`].
pub fn native() -> Self {
Self {
backend: KeyringProviderBackend::Native,
}
}
/// Create a new [`KeyringProvider::Subprocess`].
pub fn subprocess() -> Self {
Self {
@ -31,6 +66,84 @@ impl KeyringProvider {
}
}
/// Store credentials for the given [`DisplaySafeUrl`] to the keyring.
///
/// Only [`KeyringProviderBackend::Native`] is supported at this time.
#[instrument(skip_all, fields(url = % url.to_string(), username))]
pub async fn store(
&self,
url: &DisplaySafeUrl,
credentials: &Credentials,
) -> Result<bool, Error> {
let Some(username) = credentials.username() else {
trace!("Unable to store credentials in keyring for {url} due to missing username");
return Ok(false);
};
let Some(password) = credentials.password() else {
trace!("Unable to store credentials in keyring for {url} due to missing password");
return Ok(false);
};
match &self.backend {
KeyringProviderBackend::Native => {
self.store_native(url.as_str(), username, password).await?;
Ok(true)
}
KeyringProviderBackend::Subprocess => {
Err(Error::StoreUnsupported(self.backend.clone()))
}
#[cfg(test)]
KeyringProviderBackend::Dummy(_) => Err(Error::StoreUnsupported(self.backend.clone())),
}
}
/// Store credentials to the system keyring.
#[instrument(skip(self))]
async fn store_native(
&self,
service: &str,
username: &str,
password: &str,
) -> Result<(), Error> {
let prefixed_service = format!("{UV_SERVICE_PREFIX}{service}");
let entry = uv_keyring::Entry::new(&prefixed_service, username)?;
entry.set_password(password).await?;
Ok(())
}
/// Remove credentials for the given [`DisplaySafeUrl`] and username from the keyring.
///
/// Only [`KeyringProviderBackend::Native`] is supported at this time.
#[instrument(skip_all, fields(url = % url.to_string(), username))]
pub async fn remove(&self, url: &DisplaySafeUrl, username: &str) -> Result<(), Error> {
match &self.backend {
KeyringProviderBackend::Native => {
self.remove_native(url.as_str(), username).await?;
Ok(())
}
KeyringProviderBackend::Subprocess => {
Err(Error::RemoveUnsupported(self.backend.clone()))
}
#[cfg(test)]
KeyringProviderBackend::Dummy(_) => Err(Error::RemoveUnsupported(self.backend.clone())),
}
}
/// Remove credentials from the system keyring for the given `service_name`/`username`
/// pair.
#[instrument(skip(self))]
async fn remove_native(
&self,
service_name: &str,
username: &str,
) -> Result<(), uv_keyring::Error> {
let prefixed_service = format!("{UV_SERVICE_PREFIX}{service_name}");
let entry = uv_keyring::Entry::new(&prefixed_service, username)?;
entry.delete_credential().await?;
trace!("Removed credentials for {username}@{service_name} from system keyring");
Ok(())
}
/// Fetch credentials for the given [`Url`] from the keyring.
///
/// Returns [`None`] if no password was found for the username or if any errors
@ -55,6 +168,7 @@ impl KeyringProvider {
// <https://github.com/pypa/pip/blob/ae5fff36b0aad6e5e0037884927eaa29163c0611/src/pip/_internal/network/auth.py#L376C1-L379C14>
trace!("Checking keyring for URL {url}");
let mut credentials = match self.backend {
KeyringProviderBackend::Native => self.fetch_native(url.as_str(), username).await,
KeyringProviderBackend::Subprocess => {
self.fetch_subprocess(url.as_str(), username).await
}
@ -72,6 +186,7 @@ impl KeyringProvider {
};
trace!("Checking keyring for host {host}");
credentials = match self.backend {
KeyringProviderBackend::Native => self.fetch_native(&host, username).await,
KeyringProviderBackend::Subprocess => self.fetch_subprocess(&host, username).await,
#[cfg(test)]
KeyringProviderBackend::Dummy(ref store) => {
@ -175,6 +290,32 @@ impl KeyringProvider {
}
}
#[instrument(skip(self))]
async fn fetch_native(
&self,
service: &str,
username: Option<&str>,
) -> Option<(String, String)> {
let prefixed_service = format!("{UV_SERVICE_PREFIX}{service}");
let username = username?;
let Ok(entry) = uv_keyring::Entry::new(&prefixed_service, username) else {
return None;
};
match entry.get_password().await {
Ok(password) => return Some((username.to_string(), password)),
Err(uv_keyring::Error::NoEntry) => {
debug!("No entry found in system keyring for {service}");
}
Err(err) => {
warn_user_once!(
"Unable to fetch credentials for {service} from system keyring: {}",
err
);
}
}
None
}
#[cfg(test)]
fn fetch_dummy(
store: &Vec<(String, &'static str, &'static str)>,

View File

@ -375,6 +375,7 @@ impl AuthMiddleware {
.as_ref()
.is_ok_and(|response| response.error_for_status_ref().is_ok())
{
// TODO(zanieb): Consider also updating the system keyring after successful use
trace!("Updating cached credentials for {url} to {credentials:?}");
self.cache().insert(&url, credentials);
}

View File

@ -11,7 +11,7 @@ use clap::{Args, Parser, Subcommand};
use uv_cache::CacheArgs;
use uv_configuration::{
ExportFormat, IndexStrategy, KeyringProviderType, PackageNameSpecifier, ProjectBuildBackend,
TargetTriple, TrustedHost, TrustedPublishing, VersionControlSystem,
Service, TargetTriple, TrustedHost, TrustedPublishing, VersionControlSystem,
};
use uv_distribution_types::{
ConfigSettingEntry, ConfigSettingPackageEntry, Index, IndexUrl, Origin, PipExtraIndex,
@ -399,6 +399,13 @@ impl From<ColorChoice> for anstream::ColorChoice {
#[derive(Subcommand)]
#[allow(clippy::large_enum_variant)]
pub enum Commands {
/// Manage authentication.
#[command(
after_help = "Use `uv help auth` for more details.",
after_long_help = ""
)]
Auth(AuthNamespace),
/// Manage Python projects.
#[command(flatten)]
Project(Box<ProjectCommand>),
@ -4386,6 +4393,22 @@ pub struct FormatArgs {
pub extra_args: Vec<String>,
}
#[derive(Args)]
pub struct AuthNamespace {
#[command(subcommand)]
pub command: AuthCommand,
}
#[derive(Subcommand)]
pub enum AuthCommand {
/// Login to a service
Login(AuthLoginArgs),
/// Logout of a service
Logout(AuthLogoutArgs),
/// Show the authentication token for a service
Token(AuthTokenArgs),
}
#[derive(Args)]
pub struct ToolNamespace {
#[command(subcommand)]
@ -5501,6 +5524,76 @@ pub struct PythonPinArgs {
pub rm: bool,
}
#[derive(Args)]
pub struct AuthLogoutArgs {
/// The service to logout of.
pub service: Service,
/// The username to logout.
#[arg(long, short)]
pub username: Option<String>,
/// The keyring provider to use for storage of credentials.
///
/// Only `--keyring-provider native` is supported for `logout`, which uses the system keyring
/// via an integration built into uv.
#[arg(
long,
value_enum,
env = EnvVars::UV_KEYRING_PROVIDER,
)]
pub keyring_provider: Option<KeyringProviderType>,
}
#[derive(Args)]
pub struct AuthLoginArgs {
/// The service to login to.
pub service: Service,
/// The username to use for the service.
#[arg(long, short, conflicts_with = "token")]
pub username: Option<String>,
/// The password to use for the service.
#[arg(long, conflicts_with = "token")]
pub password: Option<String>,
/// The token to use for the service.
///
/// The username will be set to `__token__`.
#[arg(long, short, conflicts_with = "username", conflicts_with = "password")]
pub token: Option<String>,
/// The keyring provider to use for storage of credentials.
///
/// Only `--keyring-provider native` is supported for `login`, which uses the system keyring via
/// an integration built into uv.
#[arg(
long,
value_enum,
env = EnvVars::UV_KEYRING_PROVIDER,
)]
pub keyring_provider: Option<KeyringProviderType>,
}
#[derive(Args)]
pub struct AuthTokenArgs {
/// The service to lookup.
pub service: Service,
/// The username to lookup.
#[arg(long, short)]
pub username: Option<String>,
/// The keyring provider to use for reading credentials.
#[arg(
long,
value_enum,
env = EnvVars::UV_KEYRING_PROVIDER,
)]
pub keyring_provider: Option<KeyringProviderType>,
}
#[derive(Args)]
pub struct GenerateShellCompletionArgs {
/// The shell to generate the completion script for

View File

@ -28,8 +28,7 @@ use tracing::{debug, trace};
use url::ParseError;
use url::Url;
use uv_auth::Credentials;
use uv_auth::{AuthMiddleware, Indexes};
use uv_auth::{AuthMiddleware, Credentials, Indexes};
use uv_configuration::{KeyringProviderType, TrustedHost};
use uv_fs::Simplified;
use uv_pep508::MarkerEnvironment;

View File

@ -25,6 +25,7 @@ uv-normalize = { workspace = true }
uv-pep440 = { workspace = true }
uv-pep508 = { workspace = true, features = ["schemars"] }
uv-platform-tags = { workspace = true }
uv-redacted = { workspace = true }
uv-static = { workspace = true }
clap = { workspace = true, features = ["derive"], optional = true }
either = { workspace = true }

View File

@ -1,4 +1,6 @@
use std::str::FromStr;
use uv_auth::{self, KeyringProvider};
use uv_redacted::DisplaySafeUrl;
/// Keyring provider type to use for credential lookup.
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
@ -9,6 +11,8 @@ pub enum KeyringProviderType {
/// Do not use keyring for credential lookup.
#[default]
Disabled,
/// Use a native integration with the system keychain for credential lookup.
Native,
/// Use the `keyring` command for credential lookup.
Subprocess,
// /// Not yet implemented
@ -22,7 +26,60 @@ impl KeyringProviderType {
pub fn to_provider(&self) -> Option<KeyringProvider> {
match self {
Self::Disabled => None,
Self::Native => Some(KeyringProvider::native()),
Self::Subprocess => Some(KeyringProvider::subprocess()),
}
}
}
impl std::fmt::Display for KeyringProviderType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Disabled => write!(f, "disabled"),
Self::Native => write!(f, "native"),
Self::Subprocess => write!(f, "subprocess"),
}
}
}
/// A service URL that wraps [`DisplaySafeUrl`] for CLI usage.
///
/// This type provides automatic URL parsing and validation when used as a CLI argument,
/// eliminating the need for manual parsing in command functions.
#[derive(Debug, Clone)]
pub struct Service(DisplaySafeUrl);
impl Service {
/// Get the underlying [`DisplaySafeUrl`].
pub fn url(&self) -> &DisplaySafeUrl {
&self.0
}
/// Convert into the underlying [`DisplaySafeUrl`].
pub fn into_url(self) -> DisplaySafeUrl {
self.0
}
}
impl FromStr for Service {
type Err = url::ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
// First try parsing as-is
match DisplaySafeUrl::parse(s) {
Ok(url) => Ok(Self(url)),
Err(url::ParseError::RelativeUrlWithoutBase) => {
// If it's a relative URL, try prepending https://
let with_https = format!("https://{s}");
DisplaySafeUrl::parse(&with_https).map(Service)
}
Err(e) => Err(e),
}
}
}
impl std::fmt::Display for Service {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}

View File

@ -112,6 +112,19 @@ pub fn password(prompt: &str, term: &Term) -> std::io::Result<String> {
Ok(input)
}
/// Prompt the user for username in the given [`Term`].
pub fn username(prompt: &str, term: &Term) -> std::io::Result<String> {
term.write_str(prompt)?;
term.show_cursor()?;
term.flush()?;
let input = term.read_line()?;
term.clear_line()?;
Ok(input)
}
/// Prompt the user for input text in the given [`Term`].
///
/// This is a slimmed-down version of `dialoguer::Input`.

View File

@ -18,6 +18,7 @@ bitflags::bitflags! {
const EXTRA_BUILD_DEPENDENCIES = 1 << 6;
const DETECT_MODULE_CONFLICTS = 1 << 7;
const FORMAT = 1 << 8;
const NATIVE_KEYRING = 1 << 9;
}
}
@ -36,6 +37,7 @@ impl PreviewFeatures {
Self::EXTRA_BUILD_DEPENDENCIES => "extra-build-dependencies",
Self::DETECT_MODULE_CONFLICTS => "detect-module-conflicts",
Self::FORMAT => "format",
Self::NATIVE_KEYRING => "native-keyring",
_ => panic!("`flag_as_str` can only be used for exactly one feature flag"),
}
}
@ -82,6 +84,7 @@ impl FromStr for PreviewFeatures {
"extra-build-dependencies" => Self::EXTRA_BUILD_DEPENDENCIES,
"detect-module-conflicts" => Self::DETECT_MODULE_CONFLICTS,
"format" => Self::FORMAT,
"native-keyring" => Self::NATIVE_KEYRING,
_ => {
warn_user_once!("Unknown preview feature: `{part}`");
continue;

View File

@ -152,6 +152,7 @@ ignored = [
[features]
default = ["performance", "uv-distribution/static", "default-tests"]
keyring-tests = []
# Use better memory allocators, etc.
performance = ["performance-memory-allocator"]
performance-memory-allocator = ["dep:uv-performance-memory-allocator"]

View File

@ -0,0 +1,80 @@
use anyhow::{Result, bail};
use std::fmt::Write;
use console::Term;
use uv_auth::Credentials;
use uv_configuration::{KeyringProviderType, Service};
use crate::{commands::ExitStatus, printer::Printer};
/// Login to a service.
pub(crate) async fn login(
service: Service,
username: Option<String>,
password: Option<String>,
token: Option<String>,
keyring_provider: Option<KeyringProviderType>,
printer: Printer,
) -> Result<ExitStatus> {
let url = service.url();
let display_url = username
.as_ref()
.map(|username| format!("{username}@{url}"))
.unwrap_or_else(|| url.to_string());
let username = if let Some(username) = username {
username
} else if token.is_some() {
String::from("__token__")
} else {
let term = Term::stderr();
if term.is_term() {
let prompt = "username: ";
uv_console::username(prompt, &term)?
} else {
bail!("No username provided; did you mean to provide `--username` or `--token`?");
}
};
// Be helpful about incompatible `keyring-provider` settings
let Some(keyring_provider) = &keyring_provider else {
bail!(
"Logging in requires setting `keyring-provider = {}` for credentials to be retrieved in subsequent commands",
KeyringProviderType::Native
);
};
let provider = match keyring_provider {
KeyringProviderType::Native => keyring_provider.to_provider().unwrap(),
KeyringProviderType::Disabled | KeyringProviderType::Subprocess => {
bail!(
"Cannot login with `keyring-provider = {keyring_provider}`, use `keyring-provider = {}` instead",
KeyringProviderType::Native
);
}
};
// FIXME: It would be preferable to accept the value of --password or --token
// from stdin, perhaps checking here for `-` as an indicator to read stdin. We
// could then warn if the password is provided as a plaintext argument.
let password = if let Some(password) = password {
password
} else if let Some(token) = token {
token
} else {
let term = Term::stderr();
if term.is_term() {
let prompt = "password: ";
uv_console::password(prompt, &term)?
} else {
bail!("No password provided; did you mean to provide `--password` or `--token`?");
}
};
// TODO(zanieb): Add support for other authentication schemes here, e.g., `Credentials::Bearer`
let credentials = Credentials::basic(Some(username), Some(password));
provider.store(url, &credentials).await?;
writeln!(printer.stderr(), "Logged in to {display_url}")?;
Ok(ExitStatus::Success)
}

View File

@ -0,0 +1,48 @@
use anyhow::{Context, Result, bail};
use std::{borrow::Cow, fmt::Write};
use uv_configuration::{KeyringProviderType, Service};
use crate::{commands::ExitStatus, printer::Printer};
/// Logout from a service.
///
/// If no username is provided, defaults to `__token__`.
pub(crate) async fn logout(
service: Service,
username: Option<String>,
keyring_provider: Option<KeyringProviderType>,
printer: Printer,
) -> Result<ExitStatus> {
let url = service.url();
let display_url = username
.as_ref()
.map(|username| format!("{username}@{url}"))
.unwrap_or_else(|| url.to_string());
let username = username
.map(Cow::Owned)
.unwrap_or(Cow::Borrowed("__token__"));
// Unlike login, we'll default to the native provider if none is requested since it's the only
// valid option and it doesn't matter if the credentials are available in subsequent commands.
let keyring_provider = keyring_provider.unwrap_or(KeyringProviderType::Native);
// Be helpful about incompatible `keyring-provider` settings
let provider = match keyring_provider {
KeyringProviderType::Native => keyring_provider.to_provider().unwrap(),
KeyringProviderType::Disabled | KeyringProviderType::Subprocess => {
bail!(
"Cannot logout with `keyring-provider = {keyring_provider}`, use `keyring-provider = {}` instead",
KeyringProviderType::Native
);
}
};
provider
.remove(url, &username)
.await
.with_context(|| format!("Unable to remove credentials for {display_url}"))?;
writeln!(printer.stderr(), "Logged out of {display_url}")?;
Ok(ExitStatus::Success)
}

View File

@ -0,0 +1,3 @@
pub(crate) mod login;
pub(crate) mod logout;
pub(crate) mod token;

View File

@ -0,0 +1,48 @@
use std::fmt::Write;
use anyhow::{Context, Result, bail};
use uv_configuration::{KeyringProviderType, Service};
use crate::{Printer, commands::ExitStatus};
/// Show the token that will be used for a service.
pub(crate) async fn token(
service: Service,
username: Option<String>,
keyring_provider: Option<KeyringProviderType>,
printer: Printer,
) -> Result<ExitStatus> {
// Determine the keyring provider to use
let Some(keyring_provider) = &keyring_provider else {
bail!("Retrieving credentials requires setting a `keyring-provider`");
};
let Some(provider) = keyring_provider.to_provider() else {
bail!("Cannot retrieve credentials with `keyring-provider = {keyring_provider}`");
};
let url = service.url();
let display_url = username
.as_ref()
.map(|username| format!("{username}@{url}"))
.unwrap_or_else(|| url.to_string());
let credentials = provider
.fetch(url, Some(username.as_deref().unwrap_or("__token__")))
.await
.with_context(|| format!("Failed to fetch credentials for {display_url}"))?;
let Some(password) = credentials.password() else {
bail!(
"No {} found for {display_url}",
if username.is_some() {
"password"
} else {
"token"
}
);
};
writeln!(printer.stdout(), "{password}")?;
Ok(ExitStatus::Success)
}

View File

@ -9,6 +9,9 @@ use anyhow::Context;
use owo_colors::OwoColorize;
use tracing::debug;
pub(crate) use auth::login::login as auth_login;
pub(crate) use auth::logout::logout as auth_logout;
pub(crate) use auth::token::token as auth_token;
pub(crate) use build_frontend::build_frontend;
pub(crate) use cache_clean::cache_clean;
pub(crate) use cache_dir::cache_dir;
@ -65,6 +68,7 @@ pub(crate) use venv::venv;
use crate::printer::Printer;
mod auth;
pub(crate) mod build_backend;
mod build_frontend;
mod cache_clean;

View File

@ -24,9 +24,9 @@ use uv_cache_info::Timestamp;
#[cfg(feature = "self-update")]
use uv_cli::SelfUpdateArgs;
use uv_cli::{
BuildBackendCommand, CacheCommand, CacheNamespace, Cli, Commands, PipCommand, PipNamespace,
ProjectCommand, PythonCommand, PythonNamespace, SelfCommand, SelfNamespace, ToolCommand,
ToolNamespace, TopLevelArgs, compat::CompatArgs,
AuthCommand, AuthNamespace, BuildBackendCommand, CacheCommand, CacheNamespace, Cli, Commands,
PipCommand, PipNamespace, ProjectCommand, PythonCommand, PythonNamespace, SelfCommand,
SelfNamespace, ToolCommand, ToolNamespace, TopLevelArgs, compat::CompatArgs,
};
use uv_client::BaseClientBuilder;
use uv_configuration::min_stack_size;
@ -439,6 +439,41 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
.retries_from_env()?;
match *cli.command {
Commands::Auth(AuthNamespace {
command: AuthCommand::Login(args),
}) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::AuthLoginSettings::resolve(args, filesystem);
show_settings!(args);
commands::auth_login(
args.service,
args.username,
args.password,
args.token,
args.keyring_provider,
printer,
)
.await
}
Commands::Auth(AuthNamespace {
command: AuthCommand::Logout(args),
}) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::AuthLogoutSettings::resolve(args, filesystem);
show_settings!(args);
commands::auth_logout(args.service, args.username, args.keyring_provider, printer).await
}
Commands::Auth(AuthNamespace {
command: AuthCommand::Token(args),
}) => {
// Resolve the settings from the command-line arguments and workspace configuration.
let args = settings::AuthTokenSettings::resolve(args, filesystem);
show_settings!(args);
commands::auth_token(args.service, args.username, args.keyring_provider, printer).await
}
Commands::Help(args) => commands::help(
args.command.unwrap_or_default().as_slice(),
printer,

View File

@ -7,12 +7,13 @@ use std::str::FromStr;
use uv_cache::{CacheArgs, Refresh};
use uv_cli::comma::CommaSeparatedRequirements;
use uv_cli::{
AddArgs, ColorChoice, ExternalCommand, GlobalArgs, InitArgs, ListFormat, LockArgs, Maybe,
PipCheckArgs, PipCompileArgs, PipFreezeArgs, PipInstallArgs, PipListArgs, PipShowArgs,
PipSyncArgs, PipTreeArgs, PipUninstallArgs, PythonFindArgs, PythonInstallArgs, PythonListArgs,
PythonListFormat, PythonPinArgs, PythonUninstallArgs, PythonUpgradeArgs, RemoveArgs, RunArgs,
SyncArgs, SyncFormat, ToolDirArgs, ToolInstallArgs, ToolListArgs, ToolRunArgs,
ToolUninstallArgs, TreeArgs, VenvArgs, VersionArgs, VersionBump, VersionFormat,
AddArgs, AuthLoginArgs, AuthLogoutArgs, AuthTokenArgs, ColorChoice, ExternalCommand,
GlobalArgs, InitArgs, ListFormat, LockArgs, Maybe, PipCheckArgs, PipCompileArgs, PipFreezeArgs,
PipInstallArgs, PipListArgs, PipShowArgs, PipSyncArgs, PipTreeArgs, PipUninstallArgs,
PythonFindArgs, PythonInstallArgs, PythonListArgs, PythonListFormat, PythonPinArgs,
PythonUninstallArgs, PythonUpgradeArgs, RemoveArgs, RunArgs, SyncArgs, SyncFormat, ToolDirArgs,
ToolInstallArgs, ToolListArgs, ToolRunArgs, ToolUninstallArgs, TreeArgs, VenvArgs, VersionArgs,
VersionBump, VersionFormat,
};
use uv_cli::{
AuthorFrom, BuildArgs, ExportArgs, FormatArgs, PublishArgs, PythonDirArgs,
@ -24,7 +25,8 @@ use uv_configuration::{
BuildIsolation, BuildOptions, Concurrency, DependencyGroups, DryRun, EditableMode,
ExportFormat, ExtrasSpecification, HashCheckingMode, IndexStrategy, InstallOptions,
KeyringProviderType, NoBinary, NoBuild, ProjectBuildBackend, Reinstall, RequiredVersion,
SourceStrategy, TargetTriple, TrustedHost, TrustedPublishing, Upgrade, VersionControlSystem,
Service, SourceStrategy, TargetTriple, TrustedHost, TrustedPublishing, Upgrade,
VersionControlSystem,
};
use uv_distribution_types::{
ConfigSettings, DependencyMetadata, ExtraBuildVariables, Index, IndexLocations, IndexUrl,
@ -3483,6 +3485,101 @@ impl PublishSettings {
}
}
/// The resolved settings to use for an invocation of the `uv auth logout` CLI.
#[derive(Debug, Clone)]
pub(crate) struct AuthLogoutSettings {
pub(crate) service: Service,
pub(crate) username: Option<String>,
// Both CLI and configuration.
pub(crate) keyring_provider: Option<KeyringProviderType>,
}
impl AuthLogoutSettings {
/// Resolve the [`AuthLogoutSettings`] from the CLI and filesystem configuration.
pub(crate) fn resolve(args: AuthLogoutArgs, filesystem: Option<FilesystemOptions>) -> Self {
let Options { top_level, .. } = filesystem
.map(FilesystemOptions::into_options)
.unwrap_or_default();
let ResolverInstallerSchema {
keyring_provider, ..
} = top_level;
Self {
service: args.service,
username: args.username,
keyring_provider,
}
}
}
/// The resolved settings to use for an invocation of the `uv auth token` CLI.
#[derive(Debug, Clone)]
pub(crate) struct AuthTokenSettings {
pub(crate) service: Service,
pub(crate) username: Option<String>,
// Both CLI and configuration.
pub(crate) keyring_provider: Option<KeyringProviderType>,
}
impl AuthTokenSettings {
/// Resolve the [`AuthTokenSettings`] from the CLI and filesystem configuration.
pub(crate) fn resolve(args: AuthTokenArgs, filesystem: Option<FilesystemOptions>) -> Self {
let Options { top_level, .. } = filesystem
.map(FilesystemOptions::into_options)
.unwrap_or_default();
let ResolverInstallerSchema {
keyring_provider, ..
} = top_level;
let keyring_provider = args.keyring_provider.combine(keyring_provider);
Self {
service: args.service,
username: args.username,
keyring_provider,
}
}
}
/// The resolved settings to use for an invocation of the `uv auth set` CLI.
#[derive(Debug, Clone)]
pub(crate) struct AuthLoginSettings {
pub(crate) service: Service,
pub(crate) username: Option<String>,
pub(crate) password: Option<String>,
pub(crate) token: Option<String>,
// Both CLI and configuration.
pub(crate) keyring_provider: Option<KeyringProviderType>,
}
impl AuthLoginSettings {
/// Resolve the [`AuthLoginSettings`] from the CLI and filesystem configuration.
pub(crate) fn resolve(args: AuthLoginArgs, filesystem: Option<FilesystemOptions>) -> Self {
let Options { top_level, .. } = filesystem
.map(FilesystemOptions::into_options)
.unwrap_or_default();
let ResolverInstallerSchema {
keyring_provider, ..
} = top_level;
let keyring_provider = args.keyring_provider.combine(keyring_provider);
Self {
service: args.service,
username: args.username,
password: args.password,
token: args.token,
keyring_provider,
}
}
}
// Environment variables that are not exposed as CLI arguments.
mod env {
use uv_static::EnvVars;

733
crates/uv/tests/it/auth.rs Normal file
View File

@ -0,0 +1,733 @@
use anyhow::Result;
use assert_cmd::assert::OutputAssertExt;
use assert_fs::{fixture::PathChild, prelude::FileWriteStr};
use uv_static::EnvVars;
use crate::common::{TestContext, uv_snapshot, venv_bin_path};
#[test]
fn add_package_native_keyring() -> Result<()> {
let context = TestContext::new("3.12").with_real_home();
// Clear state before the test
context
.auth_logout()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple")
.arg("--username")
.arg("public")
.status()?;
// Configure `pyproject.toml` with native keyring provider.
let pyproject_toml = context.temp_dir.child("pyproject.toml");
pyproject_toml.write_str(indoc::indoc! { r#"
[project]
name = "foo"
version = "1.0.0"
requires-python = ">=3.11, <4"
dependencies = []
[tool.uv]
keyring-provider = "native"
"#
})?;
// Try to add a package without credentials.
uv_snapshot!(context.add().arg("anyio").arg("--default-index").arg("https://public@pypi-proxy.fly.dev/basic-auth/simple"), @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
Because anyio was not found in the package registry and your project depends on anyio, we can conclude that your project's requirements are unsatisfiable.
hint: An index URL (https://pypi-proxy.fly.dev/basic-auth/simple) could not be queried due to a lack of valid authentication credentials (401 Unauthorized).
help: If you want to add the package regardless of the failed resolution, provide the `--frozen` flag to skip locking and syncing.
"
);
// Login to the index
uv_snapshot!(context.auth_login()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple")
.arg("--username")
.arg("public")
.arg("--password")
.arg("heron"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Logged in to public@https://pypi-proxy.fly.dev/basic-auth/simple
"
);
// Try to add the original package without credentials again. This should use
// credentials storied in the system keyring.
uv_snapshot!(context.add().arg("anyio").arg("--default-index").arg("https://public@pypi-proxy.fly.dev/basic-auth/simple"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved 4 packages in [TIME]
Prepared 3 packages in [TIME]
Installed 3 packages in [TIME]
+ anyio==4.3.0
+ idna==3.6
+ sniffio==1.3.1
"
);
// Logout of the index
uv_snapshot!(context.auth_logout()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple")
.arg("--username")
.arg("public"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Logged out of public@https://pypi-proxy.fly.dev/basic-auth/simple
"
);
// Authentication should fail again
uv_snapshot!(context.add().arg("iniconfig").arg("--default-index").arg("https://public@pypi-proxy.fly.dev/basic-auth/simple"), @r"
success: false
exit_code: 1
----- stdout -----
----- stderr -----
× No solution found when resolving dependencies:
Because iniconfig was not found in the package registry and your project depends on iniconfig, we can conclude that your project's requirements are unsatisfiable.
hint: An index URL (https://pypi-proxy.fly.dev/basic-auth/simple) could not be queried due to a lack of valid authentication credentials (401 Unauthorized).
help: If you want to add the package regardless of the failed resolution, provide the `--frozen` flag to skip locking and syncing.
"
);
Ok(())
}
#[test]
fn token_native_keyring() -> Result<()> {
let context = TestContext::new_with_versions(&[]).with_real_home();
// Clear state before the test
context
.auth_logout()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple")
.arg("--username")
.arg("public")
.status()?;
// Without a service name
uv_snapshot!(context.auth_token(), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: the following required arguments were not provided:
<SERVICE>
Usage: uv auth token --cache-dir [CACHE_DIR] <SERVICE>
For more information, try '--help'.
");
// Without a keyring provider...
uv_snapshot!(context.auth_token()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Retrieving credentials requires setting a `keyring-provider`
");
// Without persisted credentials
uv_snapshot!(context.auth_token()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple")
.arg("--keyring-provider")
.arg("native"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Failed to fetch credentials for https://pypi-proxy.fly.dev/basic-auth/simple
");
// Without persisted credentials (with a username in the request)
uv_snapshot!(context.auth_token()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple")
.arg("--username")
.arg("public")
.arg("--keyring-provider")
.arg("native"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Failed to fetch credentials for public@https://pypi-proxy.fly.dev/basic-auth/simple
");
// Login to the index
uv_snapshot!(context.auth_login()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple")
.arg("--username")
.arg("public")
.arg("--password")
.arg("heron")
.arg("--keyring-provider")
.arg("native"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Logged in to public@https://pypi-proxy.fly.dev/basic-auth/simple
"
);
// Show the credentials
uv_snapshot!(context.auth_token()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple")
.arg("--username")
.arg("public")
.arg("--keyring-provider")
.arg("native"), @r"
success: true
exit_code: 0
----- stdout -----
heron
----- stderr -----
");
// Without the username
// TODO(zanieb): Add a hint here if we can?
uv_snapshot!(context.auth_token()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple")
.arg("--keyring-provider")
.arg("native"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Failed to fetch credentials for https://pypi-proxy.fly.dev/basic-auth/simple
");
// With a mismatched username
// TODO(zanieb): Add a hint here if we can?
uv_snapshot!(context.auth_token()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple")
.arg("--username")
.arg("private")
.arg("--keyring-provider")
.arg("native"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Failed to fetch credentials for private@https://pypi-proxy.fly.dev/basic-auth/simple
");
// Login to the index with a token
uv_snapshot!(context.auth_login()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple")
.arg("--token")
.arg("heron")
.arg("--keyring-provider")
.arg("native"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Logged in to https://pypi-proxy.fly.dev/basic-auth/simple
"
);
// Retrieve the token without a username
uv_snapshot!(context.auth_token()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple")
.arg("--keyring-provider")
.arg("native"), @r"
success: true
exit_code: 0
----- stdout -----
heron
----- stderr -----
");
Ok(())
}
#[test]
fn token_subprocess_keyring() {
let context = TestContext::new("3.12");
// Without a keyring on the PATH
uv_snapshot!(context.auth_token()
.arg("https://public@pypi-proxy.fly.dev/basic-auth/simple")
.arg("--keyring-provider")
.arg("subprocess"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Failed to fetch credentials for https://****@pypi-proxy.fly.dev/basic-auth/simple
"
);
// Install our keyring plugin
context
.pip_install()
.arg(
context
.workspace_root
.join("scripts")
.join("packages")
.join("keyring_test_plugin"),
)
.assert()
.success();
// Without credentials available
uv_snapshot!(context.auth_token()
.arg("https://public@pypi-proxy.fly.dev/basic-auth/simple")
.arg("--keyring-provider")
.arg("subprocess")
.env(EnvVars::PATH, venv_bin_path(&context.venv)), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
Keyring request for __token__@https://public@pypi-proxy.fly.dev/basic-auth/simple
Keyring request for __token__@pypi-proxy.fly.dev
error: Failed to fetch credentials for https://****@pypi-proxy.fly.dev/basic-auth/simple
"
);
// Without a username
// TODO(zanieb): Add a hint here if we can?
uv_snapshot!(context.auth_token()
.arg("https://public@pypi-proxy.fly.dev/basic-auth/simple")
.arg("--keyring-provider")
.arg("subprocess")
.env(EnvVars::KEYRING_TEST_CREDENTIALS, r#"{"pypi-proxy.fly.dev": {"public": "heron"}}"#)
.env(EnvVars::PATH, venv_bin_path(&context.venv)), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
Keyring request for __token__@https://public@pypi-proxy.fly.dev/basic-auth/simple
Keyring request for __token__@pypi-proxy.fly.dev
error: Failed to fetch credentials for https://****@pypi-proxy.fly.dev/basic-auth/simple
"
);
// With the correct username
uv_snapshot!(context.auth_token()
.arg("https://public@pypi-proxy.fly.dev/basic-auth/simple")
.arg("--keyring-provider")
.arg("subprocess")
.arg("--username")
.arg("public")
.env(EnvVars::KEYRING_TEST_CREDENTIALS, r#"{"pypi-proxy.fly.dev": {"public": "heron"}}"#)
.env(EnvVars::PATH, venv_bin_path(&context.venv)), @r"
success: true
exit_code: 0
----- stdout -----
heron
----- stderr -----
Keyring request for public@https://public@pypi-proxy.fly.dev/basic-auth/simple
Keyring request for public@pypi-proxy.fly.dev
"
);
}
#[test]
fn login_native_keyring() -> Result<()> {
let context = TestContext::new_with_versions(&[]).with_real_home();
// Clear state before the test
context
.auth_logout()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple")
.arg("--username")
.arg("public")
.status()?;
// Without a service name
uv_snapshot!(context.auth_login(), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: the following required arguments were not provided:
<SERVICE>
Usage: uv auth login --cache-dir [CACHE_DIR] <SERVICE>
For more information, try '--help'.
");
// Without a username (or token)
uv_snapshot!(context.auth_login()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple")
.arg("--keyring-provider")
.arg("native"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: No username provided; did you mean to provide `--username` or `--token`?
");
// Without a password
uv_snapshot!(context.auth_login()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple")
.arg("--username")
.arg("public")
.arg("--keyring-provider")
.arg("native"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: No password provided; did you mean to provide `--password` or `--token`?
");
// Without a keyring provider
uv_snapshot!(context.auth_login()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple")
.arg("--token")
.arg("foo"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Logging in requires setting `keyring-provider = native` for credentials to be retrieved in subsequent commands
");
// Successful
uv_snapshot!(context.auth_login()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple")
.arg("--username")
.arg("public")
.arg("--password")
.arg("heron")
.arg("--keyring-provider")
.arg("native"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Logged in to public@https://pypi-proxy.fly.dev/basic-auth/simple
"
);
Ok(())
}
#[test]
fn login_token_native_keyring() -> Result<()> {
let context = TestContext::new_with_versions(&[]).with_real_home();
// Clear state before the test
context
.auth_logout()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple")
.arg("--username")
.arg("__token__")
.status()?;
// Successful with token
uv_snapshot!(context.auth_login()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple")
.arg("--token")
.arg("test-token")
.arg("--keyring-provider")
.arg("native"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Logged in to https://pypi-proxy.fly.dev/basic-auth/simple
"
);
Ok(())
}
#[test]
fn logout_native_keyring() -> Result<()> {
let context = TestContext::new_with_versions(&[]).with_real_home();
// Clear state before the test
context
.auth_logout()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple")
.arg("--username")
.arg("public")
.status()?;
// Without a service name
uv_snapshot!(context.auth_logout(), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: the following required arguments were not provided:
<SERVICE>
Usage: uv auth logout --cache-dir [CACHE_DIR] <SERVICE>
For more information, try '--help'.
");
// Logout without a keyring provider
uv_snapshot!(context.auth_logout()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Logged out of https://pypi-proxy.fly.dev/basic-auth/simple
");
// Logout before logging in (without a username)
uv_snapshot!(context.auth_logout()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple")
.arg("--keyring-provider")
.arg("native"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Unable to remove credentials for https://pypi-proxy.fly.dev/basic-auth/simple
Caused by: No matching entry found in secure storage
");
// Logout before logging in (with a username)
uv_snapshot!(context.auth_logout()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple")
.arg("--username")
.arg("public")
.arg("--keyring-provider")
.arg("native"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Unable to remove credentials for public@https://pypi-proxy.fly.dev/basic-auth/simple
Caused by: No matching entry found in secure storage
");
// Login with a username
uv_snapshot!(context.auth_login()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple")
.arg("--username")
.arg("public")
.arg("--password")
.arg("heron")
.arg("--keyring-provider")
.arg("native"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Logged in to public@https://pypi-proxy.fly.dev/basic-auth/simple
"
);
// Logout without a username
// TODO(zanieb): Add a hint here if we can?
uv_snapshot!(context.auth_logout()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple")
.arg("--keyring-provider")
.arg("native"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: Unable to remove credentials for https://pypi-proxy.fly.dev/basic-auth/simple
Caused by: No matching entry found in secure storage
");
// Logout with a username
uv_snapshot!(context.auth_logout()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple")
.arg("--username")
.arg("public")
.arg("--keyring-provider")
.arg("native"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Logged out of public@https://pypi-proxy.fly.dev/basic-auth/simple
");
Ok(())
}
#[test]
fn logout_token_native_keyring() -> Result<()> {
let context = TestContext::new_with_versions(&[]).with_real_home();
// Clear state before the test
context
.auth_logout()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple")
.status()?;
// Login with a token
uv_snapshot!(context.auth_login()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple")
.arg("--token")
.arg("test-token")
.arg("--keyring-provider")
.arg("native"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Logged in to https://pypi-proxy.fly.dev/basic-auth/simple
"
);
// Logout without a username
uv_snapshot!(context.auth_logout()
.arg("https://pypi-proxy.fly.dev/basic-auth/simple")
.arg("--keyring-provider")
.arg("native"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Logged out of https://pypi-proxy.fly.dev/basic-auth/simple
");
Ok(())
}
#[test]
fn login_url_parsing() {
let context = TestContext::new_with_versions(&[]).with_real_home();
// A domain-only service name gets https:// prepended
uv_snapshot!(context.auth_login()
.arg("example.com")
.arg("--username")
.arg("test")
.arg("--password")
.arg("test")
.arg("--keyring-provider")
.arg("native"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Logged in to test@https://example.com/
");
// When including a protocol explicitly, it is retained
uv_snapshot!(context.auth_login()
.arg("http://example.com")
.arg("--username")
.arg("test")
.arg("--password")
.arg("test")
.arg("--keyring-provider")
.arg("native"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Logged in to test@http://example.com/
");
uv_snapshot!(context.auth_login()
.arg("https://example.com")
.arg("--username")
.arg("test")
.arg("--password")
.arg("test")
.arg("--keyring-provider")
.arg("native"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Logged in to test@https://example.com/
");
// A domain-only service with a path also gets https:// prepended
uv_snapshot!(context.auth_login()
.arg("example.com/simple")
.arg("--username")
.arg("test")
.arg("--password")
.arg("test")
.arg("--keyring-provider")
.arg("native"), @r"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Logged in to test@https://example.com/simple
");
// An invalid URL is rejected
uv_snapshot!(context.auth_login()
.arg("not a valid url")
.arg("--username")
.arg("test")
.arg("--password")
.arg("test")
.arg("--keyring-provider")
.arg("native"), @r"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
error: invalid value 'not a valid url' for '<SERVICE>': invalid international domain name
For more information, try '--help'.
");
}

View File

@ -1274,6 +1274,42 @@ impl TestContext {
command
}
/// Create a `uv auth login` command.
pub fn auth_login(&self) -> Command {
let mut command = Self::new_command();
command.arg("auth").arg("login");
self.add_shared_options(&mut command, false);
command
}
/// Create a `uv auth logout` command.
pub fn auth_logout(&self) -> Command {
let mut command = Self::new_command();
command.arg("auth").arg("logout");
self.add_shared_options(&mut command, false);
command
}
/// Create a `uv auth token` command.
pub fn auth_token(&self) -> Command {
let mut command = Self::new_command();
command.arg("auth").arg("token");
self.add_shared_options(&mut command, false);
command
}
/// Set `HOME` to the real home directory.
///
/// We need this for testing commands which use the macOS keychain.
#[must_use]
pub fn with_real_home(mut self) -> Self {
if let Some(home) = env::var_os(EnvVars::HOME) {
self.extra_env
.push((EnvVars::HOME.to_string().into(), home));
}
self
}
/// Run the given python code and check whether it succeeds.
pub fn assert_command(&self, command: &str) -> Assert {
self.python_command()

View File

@ -16,6 +16,7 @@ fn help() {
Usage: uv [OPTIONS] <COMMAND>
Commands:
auth Manage authentication
run Run a command or script
init Create a new project
add Add dependencies to the project
@ -97,6 +98,7 @@ fn help_flag() {
Usage: uv [OPTIONS] <COMMAND>
Commands:
auth Manage authentication
run Run a command or script
init Create a new project
add Add dependencies to the project
@ -176,6 +178,7 @@ fn help_short_flag() {
Usage: uv [OPTIONS] <COMMAND>
Commands:
auth Manage authentication
run Run a command or script
init Create a new project
add Add dependencies to the project
@ -874,6 +877,7 @@ fn help_unknown_subcommand() {
----- stderr -----
error: There is no command `foobar` for `uv`. Did you mean one of:
auth
run
init
add
@ -902,6 +906,7 @@ fn help_unknown_subcommand() {
----- stderr -----
error: There is no command `foo bar` for `uv`. Did you mean one of:
auth
run
init
add
@ -959,6 +964,7 @@ fn help_with_global_option() {
Usage: uv [OPTIONS] <COMMAND>
Commands:
auth Manage authentication
run Run a command or script
init Create a new project
add Add dependencies to the project
@ -1081,6 +1087,7 @@ fn help_with_no_pager() {
Usage: uv [OPTIONS] <COMMAND>
Commands:
auth Manage authentication
run Run a command or script
init Create a new project
add Add dependencies to the project

View File

@ -3,6 +3,9 @@
pub(crate) mod common;
#[cfg(feature = "keyring-tests")]
mod auth;
mod branching_urls;
#[cfg(all(feature = "python", feature = "pypi"))]

View File

@ -7684,7 +7684,7 @@ fn preview_features() {
show_settings: true,
preview: Preview {
flags: PreviewFeatures(
PYTHON_INSTALL_DEFAULT | PYTHON_UPGRADE | JSON_OUTPUT | PYLOCK | ADD_BOUNDS | PACKAGE_CONFLICTS | EXTRA_BUILD_DEPENDENCIES | DETECT_MODULE_CONFLICTS | FORMAT,
PYTHON_INSTALL_DEFAULT | PYTHON_UPGRADE | JSON_OUTPUT | PYLOCK | ADD_BOUNDS | PACKAGE_CONFLICTS | EXTRA_BUILD_DEPENDENCIES | DETECT_MODULE_CONFLICTS | FORMAT | NATIVE_KEYRING,
),
},
python_preference: Managed,
@ -7908,7 +7908,7 @@ fn preview_features() {
show_settings: true,
preview: Preview {
flags: PreviewFeatures(
PYTHON_INSTALL_DEFAULT | PYTHON_UPGRADE | JSON_OUTPUT | PYLOCK | ADD_BOUNDS | PACKAGE_CONFLICTS | EXTRA_BUILD_DEPENDENCIES | DETECT_MODULE_CONFLICTS | FORMAT,
PYTHON_INSTALL_DEFAULT | PYTHON_UPGRADE | JSON_OUTPUT | PYLOCK | ADD_BOUNDS | PACKAGE_CONFLICTS | EXTRA_BUILD_DEPENDENCIES | DETECT_MODULE_CONFLICTS | FORMAT | NATIVE_KEYRING,
),
},
python_preference: Managed,

View File

@ -12,7 +12,8 @@ uv [OPTIONS] <COMMAND>
<h3 class="cli-reference">Commands</h3>
<dl class="cli-reference"><dt><a href="#uv-run"><code>uv run</code></a></dt><dd><p>Run a command or script</p></dd>
<dl class="cli-reference"><dt><a href="#uv-auth"><code>uv auth</code></a></dt><dd><p>Manage authentication</p></dd>
<dt><a href="#uv-run"><code>uv run</code></a></dt><dd><p>Run a command or script</p></dd>
<dt><a href="#uv-init"><code>uv init</code></a></dt><dd><p>Create a new project</p></dd>
<dt><a href="#uv-add"><code>uv add</code></a></dt><dd><p>Add dependencies to the project</p></dd>
<dt><a href="#uv-remove"><code>uv remove</code></a></dt><dd><p>Remove dependencies from the project</p></dd>
@ -33,6 +34,238 @@ uv [OPTIONS] <COMMAND>
<dt><a href="#uv-help"><code>uv help</code></a></dt><dd><p>Display documentation for a command</p></dd>
</dl>
## uv auth
Manage authentication
<h3 class="cli-reference">Usage</h3>
```
uv auth [OPTIONS] <COMMAND>
```
<h3 class="cli-reference">Commands</h3>
<dl class="cli-reference"><dt><a href="#uv-auth-login"><code>uv auth login</code></a></dt><dd><p>Login to a service</p></dd>
<dt><a href="#uv-auth-logout"><code>uv auth logout</code></a></dt><dd><p>Logout of a service</p></dd>
<dt><a href="#uv-auth-token"><code>uv auth token</code></a></dt><dd><p>Show the authentication token for a service</p></dd>
</dl>
### uv auth login
Login to a service
<h3 class="cli-reference">Usage</h3>
```
uv auth login [OPTIONS] <SERVICE>
```
<h3 class="cli-reference">Arguments</h3>
<dl class="cli-reference"><dt id="uv-auth-login--service"><a href="#uv-auth-login--service"<code>SERVICE</code></a></dt><dd><p>The service to login to</p>
</dd></dl>
<h3 class="cli-reference">Options</h3>
<dl class="cli-reference"><dt id="uv-auth-login--allow-insecure-host"><a href="#uv-auth-login--allow-insecure-host"><code>--allow-insecure-host</code></a>, <code>--trusted-host</code> <i>allow-insecure-host</i></dt><dd><p>Allow insecure connections to a host.</p>
<p>Can be provided multiple times.</p>
<p>Expects to receive either a hostname (e.g., <code>localhost</code>), a host-port pair (e.g., <code>localhost:8080</code>), or a URL (e.g., <code>https://localhost</code>).</p>
<p>WARNING: Hosts included in this list will not be verified against the system's certificate store. Only use <code>--allow-insecure-host</code> in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.</p>
<p>May also be set with the <code>UV_INSECURE_HOST</code> environment variable.</p></dd><dt id="uv-auth-login--cache-dir"><a href="#uv-auth-login--cache-dir"><code>--cache-dir</code></a> <i>cache-dir</i></dt><dd><p>Path to the cache directory.</p>
<p>Defaults to <code>$XDG_CACHE_HOME/uv</code> or <code>$HOME/.cache/uv</code> on macOS and Linux, and <code>%LOCALAPPDATA%\uv\cache</code> on Windows.</p>
<p>To view the location of the cache directory, run <code>uv cache dir</code>.</p>
<p>May also be set with the <code>UV_CACHE_DIR</code> environment variable.</p></dd><dt id="uv-auth-login--color"><a href="#uv-auth-login--color"><code>--color</code></a> <i>color-choice</i></dt><dd><p>Control the use of color in output.</p>
<p>By default, uv will automatically detect support for colors when writing to a terminal.</p>
<p>Possible values:</p>
<ul>
<li><code>auto</code>: Enables colored output only when the output is going to a terminal or TTY with support</li>
<li><code>always</code>: Enables colored output regardless of the detected environment</li>
<li><code>never</code>: Disables colored output</li>
</ul></dd><dt id="uv-auth-login--config-file"><a href="#uv-auth-login--config-file"><code>--config-file</code></a> <i>config-file</i></dt><dd><p>The path to a <code>uv.toml</code> file to use for configuration.</p>
<p>While uv configuration can be included in a <code>pyproject.toml</code> file, it is not allowed in this context.</p>
<p>May also be set with the <code>UV_CONFIG_FILE</code> environment variable.</p></dd><dt id="uv-auth-login--directory"><a href="#uv-auth-login--directory"><code>--directory</code></a> <i>directory</i></dt><dd><p>Change to the given directory prior to running the command.</p>
<p>Relative paths are resolved with the given directory as the base.</p>
<p>See <code>--project</code> to only change the project root directory.</p>
</dd><dt id="uv-auth-login--help"><a href="#uv-auth-login--help"><code>--help</code></a>, <code>-h</code></dt><dd><p>Display the concise help for this command</p>
</dd><dt id="uv-auth-login--keyring-provider"><a href="#uv-auth-login--keyring-provider"><code>--keyring-provider</code></a> <i>keyring-provider</i></dt><dd><p>The keyring provider to use for storage of credentials.</p>
<p>Only <code>--keyring-provider native</code> is supported for <code>login</code>, which uses the system keyring via an integration built into uv.</p>
<p>May also be set with the <code>UV_KEYRING_PROVIDER</code> environment variable.</p><p>Possible values:</p>
<ul>
<li><code>disabled</code>: Do not use keyring for credential lookup</li>
<li><code>native</code>: Use a native integration with the system keychain for credential lookup</li>
<li><code>subprocess</code>: Use the <code>keyring</code> command for credential lookup</li>
</ul></dd><dt id="uv-auth-login--managed-python"><a href="#uv-auth-login--managed-python"><code>--managed-python</code></a></dt><dd><p>Require use of uv-managed Python versions.</p>
<p>By default, uv prefers using Python versions it manages. However, it will use system Python versions if a uv-managed Python is not installed. This option disables use of system Python versions.</p>
<p>May also be set with the <code>UV_MANAGED_PYTHON</code> environment variable.</p></dd><dt id="uv-auth-login--native-tls"><a href="#uv-auth-login--native-tls"><code>--native-tls</code></a></dt><dd><p>Whether to load TLS certificates from the platform's native certificate store.</p>
<p>By default, uv loads certificates from the bundled <code>webpki-roots</code> crate. The <code>webpki-roots</code> are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).</p>
<p>However, in some cases, you may want to use the platform's native certificate store, especially if you're relying on a corporate trust root (e.g., for a mandatory proxy) that's included in your system's certificate store.</p>
<p>May also be set with the <code>UV_NATIVE_TLS</code> environment variable.</p></dd><dt id="uv-auth-login--no-cache"><a href="#uv-auth-login--no-cache"><code>--no-cache</code></a>, <code>--no-cache-dir</code>, <code>-n</code></dt><dd><p>Avoid reading from or writing to the cache, instead using a temporary directory for the duration of the operation</p>
<p>May also be set with the <code>UV_NO_CACHE</code> environment variable.</p></dd><dt id="uv-auth-login--no-config"><a href="#uv-auth-login--no-config"><code>--no-config</code></a></dt><dd><p>Avoid discovering configuration files (<code>pyproject.toml</code>, <code>uv.toml</code>).</p>
<p>Normally, configuration files are discovered in the current directory, parent directories, or user configuration directories.</p>
<p>May also be set with the <code>UV_NO_CONFIG</code> environment variable.</p></dd><dt id="uv-auth-login--no-managed-python"><a href="#uv-auth-login--no-managed-python"><code>--no-managed-python</code></a></dt><dd><p>Disable use of uv-managed Python versions.</p>
<p>Instead, uv will search for a suitable Python version on the system.</p>
<p>May also be set with the <code>UV_NO_MANAGED_PYTHON</code> environment variable.</p></dd><dt id="uv-auth-login--no-progress"><a href="#uv-auth-login--no-progress"><code>--no-progress</code></a></dt><dd><p>Hide all progress outputs.</p>
<p>For example, spinners or progress bars.</p>
<p>May also be set with the <code>UV_NO_PROGRESS</code> environment variable.</p></dd><dt id="uv-auth-login--no-python-downloads"><a href="#uv-auth-login--no-python-downloads"><code>--no-python-downloads</code></a></dt><dd><p>Disable automatic downloads of Python.</p>
</dd><dt id="uv-auth-login--offline"><a href="#uv-auth-login--offline"><code>--offline</code></a></dt><dd><p>Disable network access.</p>
<p>When disabled, uv will only use locally cached data and locally available files.</p>
<p>May also be set with the <code>UV_OFFLINE</code> environment variable.</p></dd><dt id="uv-auth-login--password"><a href="#uv-auth-login--password"><code>--password</code></a> <i>password</i></dt><dd><p>The password to use for the service</p>
</dd><dt id="uv-auth-login--project"><a href="#uv-auth-login--project"><code>--project</code></a> <i>project</i></dt><dd><p>Run the command within the given project directory.</p>
<p>All <code>pyproject.toml</code>, <code>uv.toml</code>, and <code>.python-version</code> files will be discovered by walking up the directory tree from the project root, as will the project's virtual environment (<code>.venv</code>).</p>
<p>Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.</p>
<p>See <code>--directory</code> to change the working directory entirely.</p>
<p>This setting has no effect when used in the <code>uv pip</code> interface.</p>
<p>May also be set with the <code>UV_PROJECT</code> environment variable.</p></dd><dt id="uv-auth-login--quiet"><a href="#uv-auth-login--quiet"><code>--quiet</code></a>, <code>-q</code></dt><dd><p>Use quiet output.</p>
<p>Repeating this option, e.g., <code>-qq</code>, will enable a silent mode in which uv will write no output to stdout.</p>
</dd><dt id="uv-auth-login--token"><a href="#uv-auth-login--token"><code>--token</code></a>, <code>-t</code> <i>token</i></dt><dd><p>The token to use for the service.</p>
<p>The username will be set to <code>__token__</code>.</p>
</dd><dt id="uv-auth-login--username"><a href="#uv-auth-login--username"><code>--username</code></a>, <code>-u</code> <i>username</i></dt><dd><p>The username to use for the service</p>
</dd><dt id="uv-auth-login--verbose"><a href="#uv-auth-login--verbose"><code>--verbose</code></a>, <code>-v</code></dt><dd><p>Use verbose output.</p>
<p>You can configure fine-grained logging using the <code>RUST_LOG</code> environment variable. (<a href="https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives">https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives</a>)</p>
</dd></dl>
### uv auth logout
Logout of a service
<h3 class="cli-reference">Usage</h3>
```
uv auth logout [OPTIONS] <SERVICE>
```
<h3 class="cli-reference">Arguments</h3>
<dl class="cli-reference"><dt id="uv-auth-logout--service"><a href="#uv-auth-logout--service"<code>SERVICE</code></a></dt><dd><p>The service to logout of</p>
</dd></dl>
<h3 class="cli-reference">Options</h3>
<dl class="cli-reference"><dt id="uv-auth-logout--allow-insecure-host"><a href="#uv-auth-logout--allow-insecure-host"><code>--allow-insecure-host</code></a>, <code>--trusted-host</code> <i>allow-insecure-host</i></dt><dd><p>Allow insecure connections to a host.</p>
<p>Can be provided multiple times.</p>
<p>Expects to receive either a hostname (e.g., <code>localhost</code>), a host-port pair (e.g., <code>localhost:8080</code>), or a URL (e.g., <code>https://localhost</code>).</p>
<p>WARNING: Hosts included in this list will not be verified against the system's certificate store. Only use <code>--allow-insecure-host</code> in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.</p>
<p>May also be set with the <code>UV_INSECURE_HOST</code> environment variable.</p></dd><dt id="uv-auth-logout--cache-dir"><a href="#uv-auth-logout--cache-dir"><code>--cache-dir</code></a> <i>cache-dir</i></dt><dd><p>Path to the cache directory.</p>
<p>Defaults to <code>$XDG_CACHE_HOME/uv</code> or <code>$HOME/.cache/uv</code> on macOS and Linux, and <code>%LOCALAPPDATA%\uv\cache</code> on Windows.</p>
<p>To view the location of the cache directory, run <code>uv cache dir</code>.</p>
<p>May also be set with the <code>UV_CACHE_DIR</code> environment variable.</p></dd><dt id="uv-auth-logout--color"><a href="#uv-auth-logout--color"><code>--color</code></a> <i>color-choice</i></dt><dd><p>Control the use of color in output.</p>
<p>By default, uv will automatically detect support for colors when writing to a terminal.</p>
<p>Possible values:</p>
<ul>
<li><code>auto</code>: Enables colored output only when the output is going to a terminal or TTY with support</li>
<li><code>always</code>: Enables colored output regardless of the detected environment</li>
<li><code>never</code>: Disables colored output</li>
</ul></dd><dt id="uv-auth-logout--config-file"><a href="#uv-auth-logout--config-file"><code>--config-file</code></a> <i>config-file</i></dt><dd><p>The path to a <code>uv.toml</code> file to use for configuration.</p>
<p>While uv configuration can be included in a <code>pyproject.toml</code> file, it is not allowed in this context.</p>
<p>May also be set with the <code>UV_CONFIG_FILE</code> environment variable.</p></dd><dt id="uv-auth-logout--directory"><a href="#uv-auth-logout--directory"><code>--directory</code></a> <i>directory</i></dt><dd><p>Change to the given directory prior to running the command.</p>
<p>Relative paths are resolved with the given directory as the base.</p>
<p>See <code>--project</code> to only change the project root directory.</p>
</dd><dt id="uv-auth-logout--help"><a href="#uv-auth-logout--help"><code>--help</code></a>, <code>-h</code></dt><dd><p>Display the concise help for this command</p>
</dd><dt id="uv-auth-logout--keyring-provider"><a href="#uv-auth-logout--keyring-provider"><code>--keyring-provider</code></a> <i>keyring-provider</i></dt><dd><p>The keyring provider to use for storage of credentials.</p>
<p>Only <code>--keyring-provider native</code> is supported for <code>logout</code>, which uses the system keyring via an integration built into uv.</p>
<p>May also be set with the <code>UV_KEYRING_PROVIDER</code> environment variable.</p><p>Possible values:</p>
<ul>
<li><code>disabled</code>: Do not use keyring for credential lookup</li>
<li><code>native</code>: Use a native integration with the system keychain for credential lookup</li>
<li><code>subprocess</code>: Use the <code>keyring</code> command for credential lookup</li>
</ul></dd><dt id="uv-auth-logout--managed-python"><a href="#uv-auth-logout--managed-python"><code>--managed-python</code></a></dt><dd><p>Require use of uv-managed Python versions.</p>
<p>By default, uv prefers using Python versions it manages. However, it will use system Python versions if a uv-managed Python is not installed. This option disables use of system Python versions.</p>
<p>May also be set with the <code>UV_MANAGED_PYTHON</code> environment variable.</p></dd><dt id="uv-auth-logout--native-tls"><a href="#uv-auth-logout--native-tls"><code>--native-tls</code></a></dt><dd><p>Whether to load TLS certificates from the platform's native certificate store.</p>
<p>By default, uv loads certificates from the bundled <code>webpki-roots</code> crate. The <code>webpki-roots</code> are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).</p>
<p>However, in some cases, you may want to use the platform's native certificate store, especially if you're relying on a corporate trust root (e.g., for a mandatory proxy) that's included in your system's certificate store.</p>
<p>May also be set with the <code>UV_NATIVE_TLS</code> environment variable.</p></dd><dt id="uv-auth-logout--no-cache"><a href="#uv-auth-logout--no-cache"><code>--no-cache</code></a>, <code>--no-cache-dir</code>, <code>-n</code></dt><dd><p>Avoid reading from or writing to the cache, instead using a temporary directory for the duration of the operation</p>
<p>May also be set with the <code>UV_NO_CACHE</code> environment variable.</p></dd><dt id="uv-auth-logout--no-config"><a href="#uv-auth-logout--no-config"><code>--no-config</code></a></dt><dd><p>Avoid discovering configuration files (<code>pyproject.toml</code>, <code>uv.toml</code>).</p>
<p>Normally, configuration files are discovered in the current directory, parent directories, or user configuration directories.</p>
<p>May also be set with the <code>UV_NO_CONFIG</code> environment variable.</p></dd><dt id="uv-auth-logout--no-managed-python"><a href="#uv-auth-logout--no-managed-python"><code>--no-managed-python</code></a></dt><dd><p>Disable use of uv-managed Python versions.</p>
<p>Instead, uv will search for a suitable Python version on the system.</p>
<p>May also be set with the <code>UV_NO_MANAGED_PYTHON</code> environment variable.</p></dd><dt id="uv-auth-logout--no-progress"><a href="#uv-auth-logout--no-progress"><code>--no-progress</code></a></dt><dd><p>Hide all progress outputs.</p>
<p>For example, spinners or progress bars.</p>
<p>May also be set with the <code>UV_NO_PROGRESS</code> environment variable.</p></dd><dt id="uv-auth-logout--no-python-downloads"><a href="#uv-auth-logout--no-python-downloads"><code>--no-python-downloads</code></a></dt><dd><p>Disable automatic downloads of Python.</p>
</dd><dt id="uv-auth-logout--offline"><a href="#uv-auth-logout--offline"><code>--offline</code></a></dt><dd><p>Disable network access.</p>
<p>When disabled, uv will only use locally cached data and locally available files.</p>
<p>May also be set with the <code>UV_OFFLINE</code> environment variable.</p></dd><dt id="uv-auth-logout--project"><a href="#uv-auth-logout--project"><code>--project</code></a> <i>project</i></dt><dd><p>Run the command within the given project directory.</p>
<p>All <code>pyproject.toml</code>, <code>uv.toml</code>, and <code>.python-version</code> files will be discovered by walking up the directory tree from the project root, as will the project's virtual environment (<code>.venv</code>).</p>
<p>Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.</p>
<p>See <code>--directory</code> to change the working directory entirely.</p>
<p>This setting has no effect when used in the <code>uv pip</code> interface.</p>
<p>May also be set with the <code>UV_PROJECT</code> environment variable.</p></dd><dt id="uv-auth-logout--quiet"><a href="#uv-auth-logout--quiet"><code>--quiet</code></a>, <code>-q</code></dt><dd><p>Use quiet output.</p>
<p>Repeating this option, e.g., <code>-qq</code>, will enable a silent mode in which uv will write no output to stdout.</p>
</dd><dt id="uv-auth-logout--username"><a href="#uv-auth-logout--username"><code>--username</code></a>, <code>-u</code> <i>username</i></dt><dd><p>The username to logout</p>
</dd><dt id="uv-auth-logout--verbose"><a href="#uv-auth-logout--verbose"><code>--verbose</code></a>, <code>-v</code></dt><dd><p>Use verbose output.</p>
<p>You can configure fine-grained logging using the <code>RUST_LOG</code> environment variable. (<a href="https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives">https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives</a>)</p>
</dd></dl>
### uv auth token
Show the authentication token for a service
<h3 class="cli-reference">Usage</h3>
```
uv auth token [OPTIONS] <SERVICE>
```
<h3 class="cli-reference">Arguments</h3>
<dl class="cli-reference"><dt id="uv-auth-token--service"><a href="#uv-auth-token--service"<code>SERVICE</code></a></dt><dd><p>The service to lookup</p>
</dd></dl>
<h3 class="cli-reference">Options</h3>
<dl class="cli-reference"><dt id="uv-auth-token--allow-insecure-host"><a href="#uv-auth-token--allow-insecure-host"><code>--allow-insecure-host</code></a>, <code>--trusted-host</code> <i>allow-insecure-host</i></dt><dd><p>Allow insecure connections to a host.</p>
<p>Can be provided multiple times.</p>
<p>Expects to receive either a hostname (e.g., <code>localhost</code>), a host-port pair (e.g., <code>localhost:8080</code>), or a URL (e.g., <code>https://localhost</code>).</p>
<p>WARNING: Hosts included in this list will not be verified against the system's certificate store. Only use <code>--allow-insecure-host</code> in a secure network with verified sources, as it bypasses SSL verification and could expose you to MITM attacks.</p>
<p>May also be set with the <code>UV_INSECURE_HOST</code> environment variable.</p></dd><dt id="uv-auth-token--cache-dir"><a href="#uv-auth-token--cache-dir"><code>--cache-dir</code></a> <i>cache-dir</i></dt><dd><p>Path to the cache directory.</p>
<p>Defaults to <code>$XDG_CACHE_HOME/uv</code> or <code>$HOME/.cache/uv</code> on macOS and Linux, and <code>%LOCALAPPDATA%\uv\cache</code> on Windows.</p>
<p>To view the location of the cache directory, run <code>uv cache dir</code>.</p>
<p>May also be set with the <code>UV_CACHE_DIR</code> environment variable.</p></dd><dt id="uv-auth-token--color"><a href="#uv-auth-token--color"><code>--color</code></a> <i>color-choice</i></dt><dd><p>Control the use of color in output.</p>
<p>By default, uv will automatically detect support for colors when writing to a terminal.</p>
<p>Possible values:</p>
<ul>
<li><code>auto</code>: Enables colored output only when the output is going to a terminal or TTY with support</li>
<li><code>always</code>: Enables colored output regardless of the detected environment</li>
<li><code>never</code>: Disables colored output</li>
</ul></dd><dt id="uv-auth-token--config-file"><a href="#uv-auth-token--config-file"><code>--config-file</code></a> <i>config-file</i></dt><dd><p>The path to a <code>uv.toml</code> file to use for configuration.</p>
<p>While uv configuration can be included in a <code>pyproject.toml</code> file, it is not allowed in this context.</p>
<p>May also be set with the <code>UV_CONFIG_FILE</code> environment variable.</p></dd><dt id="uv-auth-token--directory"><a href="#uv-auth-token--directory"><code>--directory</code></a> <i>directory</i></dt><dd><p>Change to the given directory prior to running the command.</p>
<p>Relative paths are resolved with the given directory as the base.</p>
<p>See <code>--project</code> to only change the project root directory.</p>
</dd><dt id="uv-auth-token--help"><a href="#uv-auth-token--help"><code>--help</code></a>, <code>-h</code></dt><dd><p>Display the concise help for this command</p>
</dd><dt id="uv-auth-token--keyring-provider"><a href="#uv-auth-token--keyring-provider"><code>--keyring-provider</code></a> <i>keyring-provider</i></dt><dd><p>The keyring provider to use for reading credentials</p>
<p>May also be set with the <code>UV_KEYRING_PROVIDER</code> environment variable.</p><p>Possible values:</p>
<ul>
<li><code>disabled</code>: Do not use keyring for credential lookup</li>
<li><code>native</code>: Use a native integration with the system keychain for credential lookup</li>
<li><code>subprocess</code>: Use the <code>keyring</code> command for credential lookup</li>
</ul></dd><dt id="uv-auth-token--managed-python"><a href="#uv-auth-token--managed-python"><code>--managed-python</code></a></dt><dd><p>Require use of uv-managed Python versions.</p>
<p>By default, uv prefers using Python versions it manages. However, it will use system Python versions if a uv-managed Python is not installed. This option disables use of system Python versions.</p>
<p>May also be set with the <code>UV_MANAGED_PYTHON</code> environment variable.</p></dd><dt id="uv-auth-token--native-tls"><a href="#uv-auth-token--native-tls"><code>--native-tls</code></a></dt><dd><p>Whether to load TLS certificates from the platform's native certificate store.</p>
<p>By default, uv loads certificates from the bundled <code>webpki-roots</code> crate. The <code>webpki-roots</code> are a reliable set of trust roots from Mozilla, and including them in uv improves portability and performance (especially on macOS).</p>
<p>However, in some cases, you may want to use the platform's native certificate store, especially if you're relying on a corporate trust root (e.g., for a mandatory proxy) that's included in your system's certificate store.</p>
<p>May also be set with the <code>UV_NATIVE_TLS</code> environment variable.</p></dd><dt id="uv-auth-token--no-cache"><a href="#uv-auth-token--no-cache"><code>--no-cache</code></a>, <code>--no-cache-dir</code>, <code>-n</code></dt><dd><p>Avoid reading from or writing to the cache, instead using a temporary directory for the duration of the operation</p>
<p>May also be set with the <code>UV_NO_CACHE</code> environment variable.</p></dd><dt id="uv-auth-token--no-config"><a href="#uv-auth-token--no-config"><code>--no-config</code></a></dt><dd><p>Avoid discovering configuration files (<code>pyproject.toml</code>, <code>uv.toml</code>).</p>
<p>Normally, configuration files are discovered in the current directory, parent directories, or user configuration directories.</p>
<p>May also be set with the <code>UV_NO_CONFIG</code> environment variable.</p></dd><dt id="uv-auth-token--no-managed-python"><a href="#uv-auth-token--no-managed-python"><code>--no-managed-python</code></a></dt><dd><p>Disable use of uv-managed Python versions.</p>
<p>Instead, uv will search for a suitable Python version on the system.</p>
<p>May also be set with the <code>UV_NO_MANAGED_PYTHON</code> environment variable.</p></dd><dt id="uv-auth-token--no-progress"><a href="#uv-auth-token--no-progress"><code>--no-progress</code></a></dt><dd><p>Hide all progress outputs.</p>
<p>For example, spinners or progress bars.</p>
<p>May also be set with the <code>UV_NO_PROGRESS</code> environment variable.</p></dd><dt id="uv-auth-token--no-python-downloads"><a href="#uv-auth-token--no-python-downloads"><code>--no-python-downloads</code></a></dt><dd><p>Disable automatic downloads of Python.</p>
</dd><dt id="uv-auth-token--offline"><a href="#uv-auth-token--offline"><code>--offline</code></a></dt><dd><p>Disable network access.</p>
<p>When disabled, uv will only use locally cached data and locally available files.</p>
<p>May also be set with the <code>UV_OFFLINE</code> environment variable.</p></dd><dt id="uv-auth-token--project"><a href="#uv-auth-token--project"><code>--project</code></a> <i>project</i></dt><dd><p>Run the command within the given project directory.</p>
<p>All <code>pyproject.toml</code>, <code>uv.toml</code>, and <code>.python-version</code> files will be discovered by walking up the directory tree from the project root, as will the project's virtual environment (<code>.venv</code>).</p>
<p>Other command-line arguments (such as relative paths) will be resolved relative to the current working directory.</p>
<p>See <code>--directory</code> to change the working directory entirely.</p>
<p>This setting has no effect when used in the <code>uv pip</code> interface.</p>
<p>May also be set with the <code>UV_PROJECT</code> environment variable.</p></dd><dt id="uv-auth-token--quiet"><a href="#uv-auth-token--quiet"><code>--quiet</code></a>, <code>-q</code></dt><dd><p>Use quiet output.</p>
<p>Repeating this option, e.g., <code>-qq</code>, will enable a silent mode in which uv will write no output to stdout.</p>
</dd><dt id="uv-auth-token--username"><a href="#uv-auth-token--username"><code>--username</code></a>, <code>-u</code> <i>username</i></dt><dd><p>The username to lookup</p>
</dd><dt id="uv-auth-token--verbose"><a href="#uv-auth-token--verbose"><code>--verbose</code></a>, <code>-v</code></dt><dd><p>Use verbose output.</p>
<p>You can configure fine-grained logging using the <code>RUST_LOG</code> environment variable. (<a href="https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives">https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives</a>)</p>
</dd></dl>
## uv run
Run a command or script.
@ -149,6 +382,7 @@ uv run [OPTIONS] [COMMAND]
<p>May also be set with the <code>UV_KEYRING_PROVIDER</code> environment variable.</p><p>Possible values:</p>
<ul>
<li><code>disabled</code>: Do not use keyring for credential lookup</li>
<li><code>native</code>: Use a native integration with the system keychain for credential lookup</li>
<li><code>subprocess</code>: Use the <code>keyring</code> command for credential lookup</li>
</ul></dd><dt id="uv-run--link-mode"><a href="#uv-run--link-mode"><code>--link-mode</code></a> <i>link-mode</i></dt><dd><p>The method to use when installing packages from the global cache.</p>
<p>Defaults to <code>clone</code> (also known as Copy-on-Write) on macOS, and <code>hardlink</code> on Linux and Windows.</p>
@ -554,6 +788,7 @@ uv add [OPTIONS] <PACKAGES|--requirements <REQUIREMENTS>>
<p>May also be set with the <code>UV_KEYRING_PROVIDER</code> environment variable.</p><p>Possible values:</p>
<ul>
<li><code>disabled</code>: Do not use keyring for credential lookup</li>
<li><code>native</code>: Use a native integration with the system keychain for credential lookup</li>
<li><code>subprocess</code>: Use the <code>keyring</code> command for credential lookup</li>
</ul></dd><dt id="uv-add--link-mode"><a href="#uv-add--link-mode"><code>--link-mode</code></a> <i>link-mode</i></dt><dd><p>The method to use when installing packages from the global cache.</p>
<p>Defaults to <code>clone</code> (also known as Copy-on-Write) on macOS, and <code>hardlink</code> on Linux and Windows.</p>
@ -754,6 +989,7 @@ uv remove [OPTIONS] <PACKAGES>...
<p>May also be set with the <code>UV_KEYRING_PROVIDER</code> environment variable.</p><p>Possible values:</p>
<ul>
<li><code>disabled</code>: Do not use keyring for credential lookup</li>
<li><code>native</code>: Use a native integration with the system keychain for credential lookup</li>
<li><code>subprocess</code>: Use the <code>keyring</code> command for credential lookup</li>
</ul></dd><dt id="uv-remove--link-mode"><a href="#uv-remove--link-mode"><code>--link-mode</code></a> <i>link-mode</i></dt><dd><p>The method to use when installing packages from the global cache.</p>
<p>Defaults to <code>clone</code> (also known as Copy-on-Write) on macOS, and <code>hardlink</code> on Linux and Windows.</p>
@ -936,6 +1172,7 @@ uv version [OPTIONS] [VALUE]
<p>May also be set with the <code>UV_KEYRING_PROVIDER</code> environment variable.</p><p>Possible values:</p>
<ul>
<li><code>disabled</code>: Do not use keyring for credential lookup</li>
<li><code>native</code>: Use a native integration with the system keychain for credential lookup</li>
<li><code>subprocess</code>: Use the <code>keyring</code> command for credential lookup</li>
</ul></dd><dt id="uv-version--link-mode"><a href="#uv-version--link-mode"><code>--link-mode</code></a> <i>link-mode</i></dt><dd><p>The method to use when installing packages from the global cache.</p>
<p>Defaults to <code>clone</code> (also known as Copy-on-Write) on macOS, and <code>hardlink</code> on Linux and Windows.</p>
@ -1134,6 +1371,7 @@ uv sync [OPTIONS]
<p>May also be set with the <code>UV_KEYRING_PROVIDER</code> environment variable.</p><p>Possible values:</p>
<ul>
<li><code>disabled</code>: Do not use keyring for credential lookup</li>
<li><code>native</code>: Use a native integration with the system keychain for credential lookup</li>
<li><code>subprocess</code>: Use the <code>keyring</code> command for credential lookup</li>
</ul></dd><dt id="uv-sync--link-mode"><a href="#uv-sync--link-mode"><code>--link-mode</code></a> <i>link-mode</i></dt><dd><p>The method to use when installing packages from the global cache.</p>
<p>Defaults to <code>clone</code> (also known as Copy-on-Write) on macOS, and <code>hardlink</code> on Linux and Windows.</p>
@ -1379,6 +1617,7 @@ uv lock [OPTIONS]
<p>May also be set with the <code>UV_KEYRING_PROVIDER</code> environment variable.</p><p>Possible values:</p>
<ul>
<li><code>disabled</code>: Do not use keyring for credential lookup</li>
<li><code>native</code>: Use a native integration with the system keychain for credential lookup</li>
<li><code>subprocess</code>: Use the <code>keyring</code> command for credential lookup</li>
</ul></dd><dt id="uv-lock--link-mode"><a href="#uv-lock--link-mode"><code>--link-mode</code></a> <i>link-mode</i></dt><dd><p>The method to use when installing packages from the global cache.</p>
<p>This option is only used when building source distributions.</p>
@ -1558,6 +1797,7 @@ uv export [OPTIONS]
<p>May also be set with the <code>UV_KEYRING_PROVIDER</code> environment variable.</p><p>Possible values:</p>
<ul>
<li><code>disabled</code>: Do not use keyring for credential lookup</li>
<li><code>native</code>: Use a native integration with the system keychain for credential lookup</li>
<li><code>subprocess</code>: Use the <code>keyring</code> command for credential lookup</li>
</ul></dd><dt id="uv-export--link-mode"><a href="#uv-export--link-mode"><code>--link-mode</code></a> <i>link-mode</i></dt><dd><p>The method to use when installing packages from the global cache.</p>
<p>This option is only used when building source distributions.</p>
@ -1752,6 +1992,7 @@ uv tree [OPTIONS]
<p>May also be set with the <code>UV_KEYRING_PROVIDER</code> environment variable.</p><p>Possible values:</p>
<ul>
<li><code>disabled</code>: Do not use keyring for credential lookup</li>
<li><code>native</code>: Use a native integration with the system keychain for credential lookup</li>
<li><code>subprocess</code>: Use the <code>keyring</code> command for credential lookup</li>
</ul></dd><dt id="uv-tree--link-mode"><a href="#uv-tree--link-mode"><code>--link-mode</code></a> <i>link-mode</i></dt><dd><p>The method to use when installing packages from the global cache.</p>
<p>This option is only used when building source distributions.</p>
@ -2095,6 +2336,7 @@ uv tool run [OPTIONS] [COMMAND]
<p>May also be set with the <code>UV_KEYRING_PROVIDER</code> environment variable.</p><p>Possible values:</p>
<ul>
<li><code>disabled</code>: Do not use keyring for credential lookup</li>
<li><code>native</code>: Use a native integration with the system keychain for credential lookup</li>
<li><code>subprocess</code>: Use the <code>keyring</code> command for credential lookup</li>
</ul></dd><dt id="uv-tool-run--link-mode"><a href="#uv-tool-run--link-mode"><code>--link-mode</code></a> <i>link-mode</i></dt><dd><p>The method to use when installing packages from the global cache.</p>
<p>Defaults to <code>clone</code> (also known as Copy-on-Write) on macOS, and <code>hardlink</code> on Linux and Windows.</p>
@ -2316,6 +2558,7 @@ uv tool install [OPTIONS] <PACKAGE>
<p>May also be set with the <code>UV_KEYRING_PROVIDER</code> environment variable.</p><p>Possible values:</p>
<ul>
<li><code>disabled</code>: Do not use keyring for credential lookup</li>
<li><code>native</code>: Use a native integration with the system keychain for credential lookup</li>
<li><code>subprocess</code>: Use the <code>keyring</code> command for credential lookup</li>
</ul></dd><dt id="uv-tool-install--link-mode"><a href="#uv-tool-install--link-mode"><code>--link-mode</code></a> <i>link-mode</i></dt><dd><p>The method to use when installing packages from the global cache.</p>
<p>Defaults to <code>clone</code> (also known as Copy-on-Write) on macOS, and <code>hardlink</code> on Linux and Windows.</p>
@ -2529,6 +2772,7 @@ uv tool upgrade [OPTIONS] <NAME>...
<p>May also be set with the <code>UV_KEYRING_PROVIDER</code> environment variable.</p><p>Possible values:</p>
<ul>
<li><code>disabled</code>: Do not use keyring for credential lookup</li>
<li><code>native</code>: Use a native integration with the system keychain for credential lookup</li>
<li><code>subprocess</code>: Use the <code>keyring</code> command for credential lookup</li>
</ul></dd><dt id="uv-tool-upgrade--link-mode"><a href="#uv-tool-upgrade--link-mode"><code>--link-mode</code></a> <i>link-mode</i></dt><dd><p>The method to use when installing packages from the global cache.</p>
<p>Defaults to <code>clone</code> (also known as Copy-on-Write) on macOS, and <code>hardlink</code> on Linux and Windows.</p>
@ -3749,6 +3993,7 @@ uv pip compile [OPTIONS] <SRC_FILE|--group <GROUP>>
<p>May also be set with the <code>UV_KEYRING_PROVIDER</code> environment variable.</p><p>Possible values:</p>
<ul>
<li><code>disabled</code>: Do not use keyring for credential lookup</li>
<li><code>native</code>: Use a native integration with the system keychain for credential lookup</li>
<li><code>subprocess</code>: Use the <code>keyring</code> command for credential lookup</li>
</ul></dd><dt id="uv-pip-compile--link-mode"><a href="#uv-pip-compile--link-mode"><code>--link-mode</code></a> <i>link-mode</i></dt><dd><p>The method to use when installing packages from the global cache.</p>
<p>This option is only used when building source distributions.</p>
@ -4044,6 +4289,7 @@ uv pip sync [OPTIONS] <SRC_FILE>...
<p>May also be set with the <code>UV_KEYRING_PROVIDER</code> environment variable.</p><p>Possible values:</p>
<ul>
<li><code>disabled</code>: Do not use keyring for credential lookup</li>
<li><code>native</code>: Use a native integration with the system keychain for credential lookup</li>
<li><code>subprocess</code>: Use the <code>keyring</code> command for credential lookup</li>
</ul></dd><dt id="uv-pip-sync--link-mode"><a href="#uv-pip-sync--link-mode"><code>--link-mode</code></a> <i>link-mode</i></dt><dd><p>The method to use when installing packages from the global cache.</p>
<p>Defaults to <code>clone</code> (also known as Copy-on-Write) on macOS, and <code>hardlink</code> on Linux and Windows.</p>
@ -4317,6 +4563,7 @@ uv pip install [OPTIONS] <PACKAGE|--requirements <REQUIREMENTS>|--editable <EDIT
<p>May also be set with the <code>UV_KEYRING_PROVIDER</code> environment variable.</p><p>Possible values:</p>
<ul>
<li><code>disabled</code>: Do not use keyring for credential lookup</li>
<li><code>native</code>: Use a native integration with the system keychain for credential lookup</li>
<li><code>subprocess</code>: Use the <code>keyring</code> command for credential lookup</li>
</ul></dd><dt id="uv-pip-install--link-mode"><a href="#uv-pip-install--link-mode"><code>--link-mode</code></a> <i>link-mode</i></dt><dd><p>The method to use when installing packages from the global cache.</p>
<p>Defaults to <code>clone</code> (also known as Copy-on-Write) on macOS, and <code>hardlink</code> on Linux and Windows.</p>
@ -4563,6 +4810,7 @@ uv pip uninstall [OPTIONS] <PACKAGE|--requirements <REQUIREMENTS>>
<p>May also be set with the <code>UV_KEYRING_PROVIDER</code> environment variable.</p><p>Possible values:</p>
<ul>
<li><code>disabled</code>: Do not use keyring for credential lookup</li>
<li><code>native</code>: Use a native integration with the system keychain for credential lookup</li>
<li><code>subprocess</code>: Use the <code>keyring</code> command for credential lookup</li>
</ul></dd><dt id="uv-pip-uninstall--managed-python"><a href="#uv-pip-uninstall--managed-python"><code>--managed-python</code></a></dt><dd><p>Require use of uv-managed Python versions.</p>
<p>By default, uv prefers using Python versions it manages. However, it will use system Python versions if a uv-managed Python is not installed. This option disables use of system Python versions.</p>
@ -4741,6 +4989,7 @@ uv pip list [OPTIONS]
<p>May also be set with the <code>UV_KEYRING_PROVIDER</code> environment variable.</p><p>Possible values:</p>
<ul>
<li><code>disabled</code>: Do not use keyring for credential lookup</li>
<li><code>native</code>: Use a native integration with the system keychain for credential lookup</li>
<li><code>subprocess</code>: Use the <code>keyring</code> command for credential lookup</li>
</ul></dd><dt id="uv-pip-list--managed-python"><a href="#uv-pip-list--managed-python"><code>--managed-python</code></a></dt><dd><p>Require use of uv-managed Python versions.</p>
<p>By default, uv prefers using Python versions it manages. However, it will use system Python versions if a uv-managed Python is not installed. This option disables use of system Python versions.</p>
@ -4916,6 +5165,7 @@ uv pip tree [OPTIONS]
<p>May also be set with the <code>UV_KEYRING_PROVIDER</code> environment variable.</p><p>Possible values:</p>
<ul>
<li><code>disabled</code>: Do not use keyring for credential lookup</li>
<li><code>native</code>: Use a native integration with the system keychain for credential lookup</li>
<li><code>subprocess</code>: Use the <code>keyring</code> command for credential lookup</li>
</ul></dd><dt id="uv-pip-tree--managed-python"><a href="#uv-pip-tree--managed-python"><code>--managed-python</code></a></dt><dd><p>Require use of uv-managed Python versions.</p>
<p>By default, uv prefers using Python versions it manages. However, it will use system Python versions if a uv-managed Python is not installed. This option disables use of system Python versions.</p>
@ -5157,6 +5407,7 @@ uv venv [OPTIONS] [PATH]
<p>May also be set with the <code>UV_KEYRING_PROVIDER</code> environment variable.</p><p>Possible values:</p>
<ul>
<li><code>disabled</code>: Do not use keyring for credential lookup</li>
<li><code>native</code>: Use a native integration with the system keychain for credential lookup</li>
<li><code>subprocess</code>: Use the <code>keyring</code> command for credential lookup</li>
</ul></dd><dt id="uv-venv--link-mode"><a href="#uv-venv--link-mode"><code>--link-mode</code></a> <i>link-mode</i></dt><dd><p>The method to use when installing packages from the global cache.</p>
<p>This option is only used for installing seed packages.</p>
@ -5313,6 +5564,7 @@ uv build [OPTIONS] [SRC]
<p>May also be set with the <code>UV_KEYRING_PROVIDER</code> environment variable.</p><p>Possible values:</p>
<ul>
<li><code>disabled</code>: Do not use keyring for credential lookup</li>
<li><code>native</code>: Use a native integration with the system keychain for credential lookup</li>
<li><code>subprocess</code>: Use the <code>keyring</code> command for credential lookup</li>
</ul></dd><dt id="uv-build--link-mode"><a href="#uv-build--link-mode"><code>--link-mode</code></a> <i>link-mode</i></dt><dd><p>The method to use when installing packages from the global cache.</p>
<p>This option is only used when building source distributions.</p>
@ -5465,6 +5717,7 @@ uv publish --publish-url https://upload.pypi.org/legacy/ --check-url https://pyp
<p>May also be set with the <code>UV_KEYRING_PROVIDER</code> environment variable.</p><p>Possible values:</p>
<ul>
<li><code>disabled</code>: Do not use keyring for credential lookup</li>
<li><code>native</code>: Use a native integration with the system keychain for credential lookup</li>
<li><code>subprocess</code>: Use the <code>keyring</code> command for credential lookup</li>
</ul></dd><dt id="uv-publish--managed-python"><a href="#uv-publish--managed-python"><code>--managed-python</code></a></dt><dd><p>Require use of uv-managed Python versions.</p>
<p>By default, uv prefers using Python versions it manages. However, it will use system Python versions if a uv-managed Python is not installed. This option disables use of system Python versions.</p>

5
uv.schema.json generated
View File

@ -1144,6 +1144,11 @@
"type": "string",
"const": "disabled"
},
{
"description": "Use a native integration with the system keychain for credential lookup.",
"type": "string",
"const": "native"
},
{
"description": "Use the `keyring` command for credential lookup.",
"type": "string",