diff --git a/.github/workflows/ci-xai-proxy.yml b/.github/workflows/ci-auth-proxy.yml similarity index 77% rename from .github/workflows/ci-xai-proxy.yml rename to .github/workflows/ci-auth-proxy.yml index 45ac2ed7..740b647c 100644 --- a/.github/workflows/ci-xai-proxy.yml +++ b/.github/workflows/ci-auth-proxy.yml @@ -1,13 +1,13 @@ -name: CI (xai-proxy) +name: CI (openab-auth-proxy) on: pull_request: paths: - - "xai-proxy/**" + - "openab-auth-proxy/**" push: branches: [main] paths: - - "xai-proxy/**" + - "openab-auth-proxy/**" env: CARGO_TERM_COLOR: always @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-latest defaults: run: - working-directory: xai-proxy + working-directory: openab-auth-proxy steps: - uses: actions/checkout@v6 @@ -27,7 +27,7 @@ jobs: - uses: Swatinem/rust-cache@v2 with: - workspaces: xai-proxy + workspaces: openab-auth-proxy - name: cargo check run: cargo check diff --git a/docs/refarch/sidecar-proxy.md b/docs/refarch/sidecar-proxy.md new file mode 100644 index 00000000..ca85e277 --- /dev/null +++ b/docs/refarch/sidecar-proxy.md @@ -0,0 +1,84 @@ +# Reference Architecture: OAuth Sidecar Proxy + +> **Note:** For xAI/Grok models, OpenCode ≥1.15.0 supports native xAI OAuth. +> The sidecar proxy is no longer required for OpenCode deployments. +> See [docs/xai-proxy.md](../xai-proxy.md) for the recommended approach. + +This document describes the **sidecar proxy pattern** implemented by +`openab-auth-proxy` — a generic OAuth proxy that injects Bearer tokens into +upstream API requests. + +## When to use this pattern + +- Agents **without** built-in OAuth (Hermes, custom agents) +- Centralizing token management across multiple containers in a pod +- Proxying to any OAuth-protected API (not just xAI) + +## Architecture + +``` +┌─ Kubernetes Pod ──────────────────────────────────────────────┐ +│ │ +│ agent container (any OpenAI-compatible client) │ +│ │ POST /v1/chat/completions │ +│ │ (no auth header needed) │ +│ ▼ │ +│ openab-auth-proxy :9090 │ +│ • Reads OAuth token from disk │ +│ • Injects Authorization: Bearer header │ +│ • Auto-refreshes 120s before expiry │ +│ │ │ +│ Token: ~/.openab-auth-proxy//tokens.json │ +└───────────────┼───────────────────────────────────────────────┘ + ▼ + upstream API (configured via TOML or xAI default) +``` + +## Configuration + +Without a config file, `openab-auth-proxy` defaults to xAI/SuperGrok. + +For other providers, create `auth-proxy.toml`: + +```toml +[provider] +name = "my-provider" +discovery_url = "https://auth.example.com/.well-known/openid-configuration" +client_id = "my-client-id" +scopes = "openid offline_access api:access" +upstream_base_url = "https://api.example.com" +redirect_port = 8080 +``` + +## Helm deployment (xAI example) + +```bash +# 1. Login locally +openab-auth-proxy login-device + +# 2. Create K8s secret +kubectl create secret generic auth-proxy-tokens \ + --from-file=tokens.json=$HOME/.openab-auth-proxy/xai/tokens.json + +# 3. Deploy with sidecar +helm install openab openab/openab \ + --set agents.mybot.command=opencode \ + --set-json 'agents.mybot.args=["acp"]' \ + --set agents.mybot.image=ghcr.io/openabdev/openab-opencode \ + --set-json 'agents.mybot.extraContainers=[{"name":"auth-proxy","image":"ghcr.io/openabdev/openab-auth-proxy:latest","args":["serve","--bind","0.0.0.0"],"ports":[{"containerPort":9090}],"volumeMounts":[{"name":"data","mountPath":"/home/agent"}]}]' \ + --set-json 'agents.mybot.extraInitContainers=[{"name":"copy-tokens","image":"busybox","command":["sh","-c","mkdir -p /dest/.openab-auth-proxy/xai && cp /src/tokens.json /dest/.openab-auth-proxy/xai/tokens.json"],"volumeMounts":[{"name":"tokens-src","mountPath":"/src","readOnly":true},{"name":"data","mountPath":"/dest"}]}]' \ + --set-json 'agents.mybot.extraVolumes=[{"name":"tokens-src","secret":{"secretName":"auth-proxy-tokens"}}]' +``` + +## Environment variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `AUTH_PROXY_TOKEN_PATH` | `~/.openab-auth-proxy//tokens.json` | Token file location | +| `XAI_PROXY_TOKEN_PATH` | (legacy alias) | Backward-compatible | +| `RUST_LOG` | `openab_auth_proxy=info` | Log verbosity | + +## See also + +- [openab-auth-proxy source](../../openab-auth-proxy/) — Rust implementation +- [docs/xai-proxy.md](../xai-proxy.md) — xAI-specific quick-start diff --git a/docs/xai-proxy.md b/docs/xai-proxy.md index 93bbbd44..3a2302c5 100644 --- a/docs/xai-proxy.md +++ b/docs/xai-proxy.md @@ -1,140 +1,43 @@ -# xAI Proxy (SuperGrok Sidecar) +# xAI / SuperGrok Integration -xai-proxy is a lightweight Rust sidecar that lets any OpenAI-compatible agent use your **SuperGrok subscription** instead of per-token API credits. It authenticates via OAuth and proxies requests to `api.x.ai/v1`. +## Recommended: Native xAI OAuth (OpenCode ≥1.15.0) -## Architecture +OpenCode now has **built-in xAI OAuth support** — no sidecar proxy needed. -``` -┌─ Kubernetes Pod ──────────────────────────────────────────────┐ -│ │ -│ openab → opencode acp │ -│ │ POST /v1/chat/completions │ -│ ▼ │ -│ xai-proxy :9090 │ -│ • Injects OAuth Bearer token │ -│ • Auto-refreshes 120s before expiry │ -│ │ │ -│ PVC: /home/agent/.openab/xai-proxy/tokens.json │ -└───────────────┼───────────────────────────────────────────────┘ - ▼ - https://api.x.ai/v1 (SuperGrok) -``` +1. Run `/connect` inside OpenCode → select **xAI Grok OAuth (Headless / Remote / VPS)** +2. Approve the device-code on any browser +3. Select your model with `/models` (e.g. `grok-4.3`) -## Prerequisites +OpenCode handles token storage and auto-refresh internally. -- Active SuperGrok subscription (any tier) -- A machine with browser access (or SSH tunnel) for initial login +--- -## Helm Install +## Alternative: openab-auth-proxy sidecar -```bash -# 1. Login locally to get tokens -xai-proxy login-device - -# 2. Create K8s secret from token file -kubectl create secret generic xai-proxy-tokens \ - --from-file=tokens.json=$HOME/.xai-proxy/tokens.json - -# 3. Deploy with opencode + xai-proxy sidecar -helm install openab openab/openab \ - --set agents.kiro.enabled=false \ - --set agents.mybot.discord.botToken="$DISCORD_BOT_TOKEN" \ - --set agents.mybot.discord.allowAllChannels=true \ - --set agents.mybot.command=opencode \ - --set-json 'agents.mybot.args=["acp"]' \ - --set agents.mybot.image=ghcr.io/openabdev/openab-opencode \ - --set-json 'agents.mybot.extraVolumes=[{"name":"xai-tokens-src","secret":{"secretName":"xai-proxy-tokens"}},{"name":"opencode-config","configMap":{"name":"opencode-xai-config"}},{"name":"opencode-auth","configMap":{"name":"opencode-xai-auth"}}]' \ - --set-json 'agents.mybot.extraVolumeMounts=[{"name":"opencode-config","mountPath":"/home/agent/opencode.json","subPath":"opencode.json"},{"name":"opencode-config","mountPath":"/home/agent/.config/opencode/opencode.json","subPath":"opencode.json"},{"name":"opencode-auth","mountPath":"/home/agent/.local/share/opencode/auth.json","subPath":"auth.json"}]' \ - --set-json 'agents.mybot.extraInitContainers=[{"name":"copy-tokens","image":"busybox","command":["sh","-c","if [ ! -f /dest/.openab/xai-proxy/tokens.json ]; then mkdir -p /dest/.openab/xai-proxy && cp /src/tokens.json /dest/.openab/xai-proxy/tokens.json; fi"],"volumeMounts":[{"name":"xai-tokens-src","mountPath":"/src","readOnly":true},{"name":"data","mountPath":"/dest"}]}]' \ - --set-json 'agents.mybot.extraContainers=[{"name":"xai-proxy","image":"xai-proxy:latest","args":["serve","--bind","0.0.0.0"],"env":[{"name":"XAI_PROXY_TOKEN_PATH","value":"/home/agent/.openab/xai-proxy/tokens.json"}],"ports":[{"containerPort":9090}],"volumeMounts":[{"name":"data","mountPath":"/home/agent"}]}]' -``` - -## OpenCode Configuration - -Create a ConfigMap for the opencode provider config: +For agents **without** native xAI OAuth (Hermes, custom agents), use +`openab-auth-proxy` — a generic OAuth sidecar that defaults to xAI. ```bash -kubectl create configmap opencode-xai-config --from-file=opencode.json=- <<'EOF' -{ - "$schema": "https://opencode.ai/config.json", - "model": "xai/grok-4.3", - "provider": { - "xai": { - "npm": "@ai-sdk/openai-compatible", - "name": "xAI (SuperGrok)", - "options": { - "baseURL": "http://localhost:9090/v1", - "apiKey": "dummy" - }, - "models": { - "grok-4.3": { "name": "Grok 4.3" } - } - } - } -} -EOF +# Login (one-time) +openab-auth-proxy login-device -kubectl create configmap opencode-xai-auth --from-file=auth.json=- <<'EOF' -{ "xai": "dummy" } -EOF -``` +# Start proxy +openab-auth-proxy serve --port 9090 -## Authentication - -### Device-code flow (recommended for headless) - -```bash -xai-proxy login-device -``` - -Prints a URL and code. Open the URL in any browser, enter the code, and authorize. - -### Browser OAuth (local machine) - -```bash -xai-proxy login -``` - -Opens your browser to `auth.x.ai`. Sign in and authorize. Callback is received on `127.0.0.1:56121`. - -### Token refresh - -xai-proxy auto-refreshes the OAuth token 120 seconds before expiry. The refreshed token is written back to the token file (persisted on PVC across pod restarts). - -## Environment Variables - -| Variable | Default | Description | -|----------|---------|-------------| -| `XAI_PROXY_TOKEN_PATH` | `~/.xai-proxy/tokens.json` | Custom token file path | -| `RUST_LOG` | `xai_proxy=info` | Log level | - -## Token Persistence - -The init container seeds the token from the K8s secret on first boot only. After that, xai-proxy reads and writes the token directly on the PVC. This means: - -- Token refreshes survive pod restarts -- The K8s secret is only needed for initial bootstrap -- To force a token reset, delete `/home/agent/.openab/xai-proxy/tokens.json` from the PVC - -## Standalone Usage (no K8s) - -```bash -# Build -cargo build --release - -# Login -./target/release/xai-proxy login-device - -# Serve -./target/release/xai-proxy serve --port 9090 - -# Use with any OpenAI-compatible client +# Point any OpenAI-compatible client at the proxy export OPENAI_BASE_URL=http://127.0.0.1:9090/v1 export OPENAI_API_KEY=dummy -opencode ``` -## Limitations +See [docs/refarch/sidecar-proxy.md](refarch/sidecar-proxy.md) for the full +architecture, Helm deployment, and custom provider configuration. + +## Comparison -- **codex-acp** and **claude-agent-acp** require their own proprietary auth and won't use `OPENAI_BASE_URL` — use opencode or hermes-acp instead -- Browser OAuth (`xai-proxy login`) requires Cloudflare to not block your IP — use `login-device` if blocked +| | Native OAuth | openab-auth-proxy sidecar | +|---|---|---| +| **Requires** | OpenCode ≥1.15.0 | Any OpenAI-compatible agent | +| **Extra container** | No | Yes | +| **Token management** | Built into OpenCode | Proxy handles refresh | +| **Multi-agent sharing** | Each agent needs own auth | Single proxy serves all | +| **Custom providers** | xAI only | Any OIDC provider via TOML config | diff --git a/xai-proxy/.gitignore b/openab-auth-proxy/.gitignore similarity index 100% rename from xai-proxy/.gitignore rename to openab-auth-proxy/.gitignore diff --git a/xai-proxy/Cargo.toml b/openab-auth-proxy/Cargo.toml similarity index 83% rename from xai-proxy/Cargo.toml rename to openab-auth-proxy/Cargo.toml index a392d436..637349df 100644 --- a/xai-proxy/Cargo.toml +++ b/openab-auth-proxy/Cargo.toml @@ -1,8 +1,8 @@ [package] -name = "xai-proxy" -version = "0.1.0" +name = "openab-auth-proxy" +version = "0.2.0" edition = "2021" -description = "Lightweight xAI OAuth proxy sidecar — PKCE login via accounts.x.ai, forwards OpenAI-compatible requests to api.x.ai/v1" +description = "Generic OAuth proxy sidecar — authenticates via OIDC/PKCE and forwards requests to any upstream API with Bearer token injection" [dependencies] tokio = { version = "1.44", features = ["full"] } @@ -15,6 +15,7 @@ http-body-util = "0.1" reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false } serde = { version = "1", features = ["derive"] } serde_json = "1" +toml = "0.8" clap = { version = "4", features = ["derive"] } sha2 = "0.10" base64 = "0.22" diff --git a/xai-proxy/Dockerfile b/openab-auth-proxy/Dockerfile similarity index 71% rename from xai-proxy/Dockerfile rename to openab-auth-proxy/Dockerfile index 21dc548c..ee4c8bae 100644 --- a/xai-proxy/Dockerfile +++ b/openab-auth-proxy/Dockerfile @@ -6,6 +6,6 @@ RUN cargo build --release FROM debian:bookworm-slim RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/* -COPY --from=builder /app/target/release/xai-proxy /usr/local/bin/ -ENTRYPOINT ["xai-proxy"] +COPY --from=builder /app/target/release/openab-auth-proxy /usr/local/bin/ +ENTRYPOINT ["openab-auth-proxy"] CMD ["serve", "--bind", "0.0.0.0"] diff --git a/openab-auth-proxy/README.md b/openab-auth-proxy/README.md new file mode 100644 index 00000000..d07ec94b --- /dev/null +++ b/openab-auth-proxy/README.md @@ -0,0 +1,81 @@ +# openab-auth-proxy + +Generic OAuth proxy sidecar for LLM APIs. Authenticates via OIDC (PKCE or device-code flow) and injects Bearer tokens into proxied requests. + +Ships with a built-in **xAI/SuperGrok** preset. Configure any OAuth-protected API via a TOML config file. + +## Quick start (xAI default) + +```bash +# Login (device-code for headless, or browser PKCE) +openab-auth-proxy login-device +openab-auth-proxy login + +# Start proxy +openab-auth-proxy serve --port 9090 + +# Use with any OpenAI-compatible client +export OPENAI_BASE_URL=http://127.0.0.1:9090/v1 +export OPENAI_API_KEY=dummy +opencode +``` + +## Custom provider + +Create `auth-proxy.toml`: + +```toml +[provider] +name = "my-provider" +discovery_url = "https://auth.example.com/.well-known/openid-configuration" +client_id = "my-client-id" +scopes = "openid offline_access api:access" +upstream_base_url = "https://api.example.com" +redirect_port = 8080 +``` + +```bash +openab-auth-proxy -c auth-proxy.toml login-device +openab-auth-proxy -c auth-proxy.toml serve +``` + +## Architecture + +``` +┌─ Pod / Host ──────────────────────────────────────────────────┐ +│ │ +│ agent (any OpenAI-compatible client) │ +│ │ POST /v1/chat/completions │ +│ ▼ │ +│ openab-auth-proxy :9090 │ +│ • Reads OAuth token from disk │ +│ • Injects Authorization: Bearer header │ +│ • Auto-refreshes 120s before expiry │ +│ │ │ +│ Token: ~/.openab-auth-proxy//tokens.json │ +└───────────────┼───────────────────────────────────────────────┘ + ▼ + upstream API (configured via TOML) +``` + +## Environment variables + +| Variable | Default | Description | +|----------|---------|-------------| +| `AUTH_PROXY_TOKEN_PATH` | `~/.openab-auth-proxy//tokens.json` | Custom token file path | +| `XAI_PROXY_TOKEN_PATH` | (legacy) | Backward-compatible alias | +| `RUST_LOG` | `openab_auth_proxy=info` | Log level | + +## Docker + +```bash +docker build -t openab-auth-proxy . +docker run --rm -v ~/.openab-auth-proxy:/root/.openab-auth-proxy openab-auth-proxy serve --bind 0.0.0.0 +``` + +## Presets + +| Provider | Config needed? | Notes | +|----------|---------------|-------| +| xAI SuperGrok | No (built-in default) | Uses Grok CLI public OAuth client | +| Custom | Yes (`auth-proxy.toml`) | Any OIDC provider with device-code or PKCE | diff --git a/xai-proxy/src/main.rs b/openab-auth-proxy/src/main.rs similarity index 59% rename from xai-proxy/src/main.rs rename to openab-auth-proxy/src/main.rs index c39649f7..256f56c6 100644 --- a/xai-proxy/src/main.rs +++ b/openab-auth-proxy/src/main.rs @@ -19,36 +19,104 @@ use tokio::{ }; use tracing::{error, info}; -// === Constants (borrowed from Hermes) === - -const XAI_OAUTH_DISCOVERY_URL: &str = "https://auth.x.ai/.well-known/openid-configuration"; -const XAI_OAUTH_CLIENT_ID: &str = "b1a00492-073a-47ea-816f-4c329264a828"; -const XAI_OAUTH_SCOPE: &str = "openid profile email offline_access grok-cli:access api:access"; -const XAI_OAUTH_REDIRECT_PORT: u16 = 56121; -const XAI_API_BASE: &str = "https://api.x.ai"; const REFRESH_SKEW_SECONDS: u64 = 120; +// === Provider Config === + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct ProviderConfig { + name: String, + discovery_url: String, + client_id: String, + scopes: String, + upstream_base_url: String, + #[serde(default = "default_redirect_port")] + redirect_port: u16, + #[serde(default)] + device_authorization_endpoint: Option, +} + +fn default_redirect_port() -> u16 { + 56121 +} + +impl ProviderConfig { + fn xai() -> Self { + Self { + name: "xAI".to_string(), + discovery_url: "https://auth.x.ai/.well-known/openid-configuration".to_string(), + client_id: "b1a00492-073a-47ea-816f-4c329264a828".to_string(), + scopes: "openid profile email offline_access grok-cli:access api:access".to_string(), + upstream_base_url: "https://api.x.ai".to_string(), + redirect_port: 56121, + device_authorization_endpoint: None, + } + } + + fn upstream_host(&self) -> &str { + self.upstream_base_url + .strip_prefix("https://") + .or_else(|| self.upstream_base_url.strip_prefix("http://")) + .unwrap_or(&self.upstream_base_url) + .split('/') + .next() + .unwrap_or("localhost") + } +} + +#[derive(Debug, Deserialize)] +struct ConfigFile { + provider: ProviderConfig, +} + +fn load_config(path: Option<&PathBuf>) -> Result { + if let Some(p) = path { + let content = std::fs::read_to_string(p) + .with_context(|| format!("Cannot read config file: {}", p.display()))?; + let cfg: ConfigFile = toml::from_str(&content)?; + return Ok(cfg.provider); + } + // Check default locations + let candidates = [ + PathBuf::from("auth-proxy.toml"), + dirs::config_dir() + .unwrap_or_default() + .join("openab-auth-proxy/config.toml"), + ]; + for c in &candidates { + if c.exists() { + let content = std::fs::read_to_string(c)?; + let cfg: ConfigFile = toml::from_str(&content)?; + return Ok(cfg.provider); + } + } + // Default to xAI + Ok(ProviderConfig::xai()) +} + // === CLI === #[derive(Parser)] -#[command(name = "xai-proxy", about = "xAI OAuth proxy sidecar")] +#[command(name = "openab-auth-proxy", about = "Generic OAuth proxy sidecar for LLM APIs")] struct Cli { + /// Path to config file (default: auth-proxy.toml or xAI preset) + #[arg(short, long, global = true)] + config: Option, + #[command(subcommand)] command: Commands, } #[derive(Subcommand)] enum Commands { - /// Authenticate with xAI via browser OAuth (PKCE) + /// Authenticate via browser OAuth (PKCE) Login, - /// Authenticate with xAI via device-code flow (headless/K8s/ECS) + /// Authenticate via device-code flow (headless/K8s/ECS) LoginDevice, /// Start the proxy server Serve { - /// Listen port #[arg(short, long, default_value = "9090")] port: u16, - /// Listen address #[arg(long, default_value = "127.0.0.1")] bind: String, }, @@ -61,12 +129,20 @@ struct TokenStore { access_token: String, refresh_token: String, #[serde(default)] - expires_at: u64, // unix timestamp + expires_at: u64, #[serde(default)] token_endpoint: String, } -fn token_path() -> PathBuf { +fn token_path(provider: &ProviderConfig) -> PathBuf { + if let Ok(p) = std::env::var("AUTH_PROXY_TOKEN_PATH") { + let path = PathBuf::from(p); + if let Some(dir) = path.parent() { + std::fs::create_dir_all(dir).ok(); + } + return path; + } + // Legacy env var for backward compat if let Ok(p) = std::env::var("XAI_PROXY_TOKEN_PATH") { let path = PathBuf::from(p); if let Some(dir) = path.parent() { @@ -76,23 +152,23 @@ fn token_path() -> PathBuf { } let dir = dirs::home_dir() .unwrap_or_else(|| PathBuf::from(".")) - .join(".xai-proxy"); + .join(".openab-auth-proxy") + .join(provider.name.to_lowercase().replace(' ', "-")); std::fs::create_dir_all(&dir).ok(); dir.join("tokens.json") } -fn load_tokens() -> Result { - let path = token_path(); +fn load_tokens(provider: &ProviderConfig) -> Result { + let path = token_path(provider); let data = std::fs::read_to_string(&path) - .with_context(|| format!("No token file at {}. Run `xai-proxy login` first.", path.display()))?; + .with_context(|| format!("No token file at {}. Run `openab-auth-proxy login` first.", path.display()))?; serde_json::from_str(&data).context("Invalid token file") } -fn save_tokens(store: &TokenStore) -> Result<()> { - let path = token_path(); +fn save_tokens(provider: &ProviderConfig, store: &TokenStore) -> Result<()> { + let path = token_path(provider); let data = serde_json::to_string_pretty(store)?; std::fs::write(&path, data)?; - // chmod 600 #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; @@ -111,10 +187,10 @@ struct OidcDiscovery { device_authorization_endpoint: String, } -async fn discover_endpoints() -> Result { +async fn discover_endpoints(provider: &ProviderConfig) -> Result { let client = reqwest::Client::new(); let resp = client - .get(XAI_OAUTH_DISCOVERY_URL) + .get(&provider.discovery_url) .send() .await? .error_for_status()?; @@ -134,101 +210,84 @@ fn pkce_challenge(verifier: &str) -> String { URL_SAFE_NO_PAD.encode(digest) } -// === OAuth Login === +// === OAuth Login (browser PKCE) === -async fn do_login() -> Result<()> { - info!("Starting xAI OAuth PKCE login..."); - let discovery = discover_endpoints().await?; +async fn do_login(provider: &ProviderConfig) -> Result<()> { + info!("Starting {} OAuth PKCE login...", provider.name); + let discovery = discover_endpoints(provider).await?; let code_verifier = pkce_verifier(); let code_challenge = pkce_challenge(&code_verifier); let state = uuid::Uuid::new_v4().to_string(); let nonce = uuid::Uuid::new_v4().to_string(); - - let redirect_uri = format!("http://127.0.0.1:{}/callback", XAI_OAUTH_REDIRECT_PORT); + let redirect_uri = format!("http://127.0.0.1:{}/callback", provider.redirect_port); let authorize_url = format!( - "{}?response_type=code&client_id={}&redirect_uri={}&scope={}&code_challenge={}&code_challenge_method=S256&state={}&nonce={}&plan=generic&referrer=xai-proxy", + "{}?response_type=code&client_id={}&redirect_uri={}&scope={}&code_challenge={}&code_challenge_method=S256&state={}&nonce={}", discovery.authorization_endpoint, - urlencoding::encode(XAI_OAUTH_CLIENT_ID), + urlencoding::encode(&provider.client_id), urlencoding::encode(&redirect_uri), - urlencoding::encode(XAI_OAUTH_SCOPE), + urlencoding::encode(&provider.scopes), urlencoding::encode(&code_challenge), urlencoding::encode(&state), urlencoding::encode(&nonce), ); - // Start local callback server - let listener = TcpListener::bind(format!("127.0.0.1:{}", XAI_OAUTH_REDIRECT_PORT)) + let listener = TcpListener::bind(format!("127.0.0.1:{}", provider.redirect_port)) .await - .context("Failed to bind callback port 56121")?; + .with_context(|| format!("Failed to bind callback port {}", provider.redirect_port))?; - println!("\nOpen this URL to authorize:\n"); - println!(" {}\n", authorize_url); - - // Try to open browser + println!("\nOpen this URL to authorize:\n\n {}\n", authorize_url); if open::that(&authorize_url).is_ok() { println!("Browser opened. Waiting for callback..."); } else { println!("Could not open browser. Please open the URL above manually."); } - // Wait for callback let (mut stream, _) = listener.accept().await?; let mut reader = BufReader::new(&mut stream); let mut request_line = String::new(); reader.read_line(&mut request_line).await?; - // Parse GET /callback?code=...&state=... HTTP/1.1 let path = request_line .split_whitespace() .nth(1) .ok_or_else(|| anyhow!("Invalid HTTP request"))?; - let url = url::Url::parse(&format!("http://localhost{}", path))?; let params: std::collections::HashMap<_, _> = url.query_pairs().collect(); - // Drain remaining headers loop { let mut line = String::new(); reader.read_line(&mut line).await?; - if line.trim().is_empty() { - break; - } + if line.trim().is_empty() { break; } } - // Send response - let body = "

xAI authorization received.

You can close this tab."; + let body = "

Authorization received.

You can close this tab."; let response = format!( "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: {}\r\nConnection: close\r\n\r\n{}", - body.len(), - body + body.len(), body ); stream.write_all(response.as_bytes()).await?; - // Validate state let received_state = params.get("state").ok_or_else(|| anyhow!("No state in callback"))?; if received_state.as_ref() != state { return Err(anyhow!("State mismatch — possible CSRF")); } - let code = params .get("code") .ok_or_else(|| anyhow!("No code in callback. Error: {:?}", params.get("error")))?; - // Exchange code for tokens info!("Exchanging authorization code for tokens..."); let client = reqwest::Client::new(); let resp = client .post(&discovery.token_endpoint) - .header("Content-Type", "application/x-www-form-urlencoded") .form(&[ ("grant_type", "authorization_code"), ("code", code.as_ref()), - ("redirect_uri", &redirect_uri), - ("client_id", XAI_OAUTH_CLIENT_ID), - ("code_verifier", &code_verifier), - ("code_challenge", &code_challenge), + ("redirect_uri", redirect_uri.as_str()), + ("client_id", provider.client_id.as_str()), + ("code_verifier", code_verifier.as_str()), + ("code_challenge", code_challenge.as_str()), ("code_challenge_method", "S256"), ]) .send() @@ -241,47 +300,45 @@ async fn do_login() -> Result<()> { } let token_resp: serde_json::Value = resp.json().await?; - let access_token = token_resp["access_token"] - .as_str() - .ok_or_else(|| anyhow!("No access_token in response"))?; - let refresh_token = token_resp["refresh_token"] - .as_str() - .ok_or_else(|| anyhow!("No refresh_token in response"))?; + let access_token = token_resp["access_token"].as_str().ok_or_else(|| anyhow!("No access_token"))?; + let refresh_token = token_resp["refresh_token"].as_str().ok_or_else(|| anyhow!("No refresh_token"))?; let expires_in = token_resp["expires_in"].as_u64().unwrap_or(3600); - let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); + let store = TokenStore { access_token: access_token.to_string(), refresh_token: refresh_token.to_string(), expires_at: now + expires_in, token_endpoint: discovery.token_endpoint, }; - save_tokens(&store)?; - - println!("\n✅ Login successful! Token saved to {:?}", token_path()); - println!(" Run `xai-proxy serve` to start the proxy."); + save_tokens(provider, &store)?; + println!("\n✅ Login successful! Token saved to {:?}", token_path(provider)); Ok(()) } -// === Device-Code Login (headless) === - -async fn do_login_device() -> Result<()> { - info!("Starting xAI device-code login..."); - let discovery = discover_endpoints().await?; - - let device_endpoint = if discovery.device_authorization_endpoint.is_empty() { - "https://auth.x.ai/oauth2/device/code".to_string() - } else { - discovery.device_authorization_endpoint - }; +// === Device-Code Login === + +async fn do_login_device(provider: &ProviderConfig) -> Result<()> { + info!("Starting {} device-code login...", provider.name); + let discovery = discover_endpoints(provider).await?; + + let device_endpoint = provider + .device_authorization_endpoint + .clone() + .unwrap_or_else(|| { + if discovery.device_authorization_endpoint.is_empty() { + // Fallback: derive from discovery URL + let base = provider.discovery_url.trim_end_matches("/.well-known/openid-configuration"); + format!("{}/oauth2/device/code", base) + } else { + discovery.device_authorization_endpoint.clone() + } + }); let client = reqwest::Client::new(); let resp = client .post(&device_endpoint) - .form(&[ - ("client_id", XAI_OAUTH_CLIENT_ID), - ("scope", XAI_OAUTH_SCOPE), - ]) + .form(&[("client_id", provider.client_id.as_str()), ("scope", provider.scopes.as_str())]) .send() .await?; @@ -291,16 +348,12 @@ async fn do_login_device() -> Result<()> { } let device_resp: serde_json::Value = resp.json().await?; - let device_code = device_resp["device_code"] - .as_str() - .ok_or_else(|| anyhow!("No device_code in response"))?; - let user_code = device_resp["user_code"] - .as_str() - .ok_or_else(|| anyhow!("No user_code in response"))?; + let device_code = device_resp["device_code"].as_str().ok_or_else(|| anyhow!("No device_code"))?; + let user_code = device_resp["user_code"].as_str().ok_or_else(|| anyhow!("No user_code"))?; let verification_uri = device_resp["verification_uri"] .as_str() .or_else(|| device_resp["verification_url"].as_str()) - .unwrap_or("https://auth.x.ai/oauth2/device"); + .unwrap_or("(see provider docs)"); let interval = device_resp["interval"].as_u64().unwrap_or(5); println!("\n Go to: {}", verification_uri); @@ -309,12 +362,11 @@ async fn do_login_device() -> Result<()> { loop { tokio::time::sleep(tokio::time::Duration::from_secs(interval)).await; - let resp = client .post(&discovery.token_endpoint) .form(&[ ("grant_type", "urn:ietf:params:oauth:grant-type:device_code"), - ("client_id", XAI_OAUTH_CLIENT_ID), + ("client_id", provider.client_id.as_str()), ("device_code", device_code), ]) .send() @@ -324,12 +376,8 @@ async fn do_login_device() -> Result<()> { let payload: serde_json::Value = resp.json().await?; if status.is_success() { - let access_token = payload["access_token"] - .as_str() - .ok_or_else(|| anyhow!("No access_token"))?; - let refresh_token = payload["refresh_token"] - .as_str() - .ok_or_else(|| anyhow!("No refresh_token"))?; + let access_token = payload["access_token"].as_str().ok_or_else(|| anyhow!("No access_token"))?; + let refresh_token = payload["refresh_token"].as_str().ok_or_else(|| anyhow!("No refresh_token"))?; let expires_in = payload["expires_in"].as_u64().unwrap_or(3600); let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); @@ -339,32 +387,30 @@ async fn do_login_device() -> Result<()> { expires_at: now + expires_in, token_endpoint: discovery.token_endpoint, }; - save_tokens(&store)?; - println!("\n✅ Login successful! Token saved to {:?}", token_path()); - println!(" Run `xai-proxy serve` to start the proxy."); + save_tokens(provider, &store)?; + println!("\n✅ Login successful! Token saved to {:?}", token_path(provider)); return Ok(()); } - let error = payload["error"].as_str().unwrap_or_default(); - match error { + match payload["error"].as_str().unwrap_or_default() { "authorization_pending" | "slow_down" => continue, "expired_token" => return Err(anyhow!("Device code expired. Try again.")), "access_denied" => return Err(anyhow!("Authorization denied by user.")), - _ => return Err(anyhow!("Device-code error: {} — {:?}", error, payload)), + e => return Err(anyhow!("Device-code error: {} — {:?}", e, payload)), } } } // === Token Refresh === -async fn refresh_token(store: &TokenStore) -> Result { +async fn refresh_token(provider: &ProviderConfig, store: &TokenStore) -> Result { let client = reqwest::Client::new(); let resp = client .post(&store.token_endpoint) .form(&[ ("grant_type", "refresh_token"), ("refresh_token", &store.refresh_token), - ("client_id", XAI_OAUTH_CLIENT_ID), + ("client_id", &provider.client_id), ]) .send() .await?; @@ -376,26 +422,23 @@ async fn refresh_token(store: &TokenStore) -> Result { } let token_resp: serde_json::Value = resp.json().await?; - let access_token = token_resp["access_token"] - .as_str() - .ok_or_else(|| anyhow!("No access_token in refresh response"))?; - let refresh_token = token_resp["refresh_token"] - .as_str() - .unwrap_or(&store.refresh_token); + let access_token = token_resp["access_token"].as_str().ok_or_else(|| anyhow!("No access_token"))?; + let new_refresh = token_resp["refresh_token"].as_str().unwrap_or(&store.refresh_token); let expires_in = token_resp["expires_in"].as_u64().unwrap_or(3600); - let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); + Ok(TokenStore { access_token: access_token.to_string(), - refresh_token: refresh_token.to_string(), + refresh_token: new_refresh.to_string(), expires_at: now + expires_in, token_endpoint: store.token_endpoint.clone(), }) } -// === Proxy State === +// === Proxy === struct ProxyState { + provider: ProviderConfig, tokens: RwLock, http_client: Client, Body>, } @@ -409,27 +452,20 @@ impl ProxyState { return Ok(tokens.access_token.clone()); } } - // Need refresh let mut tokens = self.tokens.write().await; - // Double-check after acquiring write lock let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); if tokens.expires_at > now + REFRESH_SKEW_SECONDS { return Ok(tokens.access_token.clone()); } - info!("Refreshing xAI OAuth token..."); - let new_tokens = refresh_token(&tokens).await?; - save_tokens(&new_tokens)?; + info!("Refreshing OAuth token..."); + let new_tokens = refresh_token(&self.provider, &tokens).await?; + save_tokens(&self.provider, &new_tokens)?; *tokens = new_tokens; Ok(tokens.access_token.clone()) } } -// === Proxy Handler === - -async fn proxy_handler( - State(state): State>, - mut req: Request, -) -> Response { +async fn proxy_handler(State(state): State>, mut req: Request) -> Response { let token = match state.get_valid_token().await { Ok(t) => t, Err(e) => { @@ -441,27 +477,19 @@ async fn proxy_handler( } }; - // Rewrite URI to api.x.ai - let path_and_query = req - .uri() - .path_and_query() - .map(|pq| pq.as_str()) - .unwrap_or("/"); - let target_uri = format!("{}{}", XAI_API_BASE, path_and_query); - + let path_and_query = req.uri().path_and_query().map(|pq| pq.as_str()).unwrap_or("/"); + let target_uri = format!("{}{}", state.provider.upstream_base_url, path_and_query); *req.uri_mut() = target_uri.parse().unwrap(); - // Inject auth header req.headers_mut().insert( hyper::header::AUTHORIZATION, format!("Bearer {}", token).parse().unwrap(), ); req.headers_mut().insert( hyper::header::HOST, - "api.x.ai".parse().unwrap(), + state.provider.upstream_host().parse().unwrap(), ); - // Forward match state.http_client.request(req).await { Ok(resp) => { let (parts, body) = resp.into_parts(); @@ -477,23 +505,19 @@ async fn proxy_handler( } } -// === Serve === - -async fn do_serve(bind: &str, port: u16) -> Result<()> { - let store = load_tokens()?; +async fn do_serve(provider: &ProviderConfig, bind: &str, port: u16) -> Result<()> { + let store = load_tokens(provider)?; - // Check if token needs immediate refresh let now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(); let store = if store.expires_at <= now + REFRESH_SKEW_SECONDS { info!("Token expired, refreshing..."); - let new_store = refresh_token(&store).await?; - save_tokens(&new_store)?; + let new_store = refresh_token(provider, &store).await?; + save_tokens(provider, &new_store)?; new_store } else { store }; - // Build HTTPS client for upstream let https = hyper_rustls::HttpsConnectorBuilder::new() .with_native_roots()? .https_or_http() @@ -503,6 +527,7 @@ async fn do_serve(bind: &str, port: u16) -> Result<()> { let http_client = Client::builder(TokioExecutor::new()).build(https); let state = Arc::new(ProxyState { + provider: provider.clone(), tokens: RwLock::new(store), http_client, }); @@ -514,9 +539,9 @@ async fn do_serve(bind: &str, port: u16) -> Result<()> { let addr: SocketAddr = format!("{}:{}", bind, port).parse()?; let listener = TcpListener::bind(addr).await?; - info!("xai-proxy listening on http://{}", addr); - println!("xai-proxy listening on http://{}", addr); - println!("Set your client's base URL to: http://{}/v1", addr); + info!("openab-auth-proxy ({}) listening on http://{}", provider.name, addr); + println!("openab-auth-proxy ({}) listening on http://{}", provider.name, addr); + println!("Upstream: {}", provider.upstream_base_url); axum::serve(listener, app).await?; Ok(()) @@ -533,14 +558,17 @@ async fn main() -> Result<()> { tracing_subscriber::fmt() .with_env_filter( tracing_subscriber::EnvFilter::try_from_default_env() - .unwrap_or_else(|_| "xai_proxy=info".into()), + .unwrap_or_else(|_| "openab_auth_proxy=info".into()), ) .init(); let cli = Cli::parse(); + let provider = load_config(cli.config.as_ref())?; + info!("Provider: {} (upstream: {})", provider.name, provider.upstream_base_url); + match cli.command { - Commands::Login => do_login().await, - Commands::LoginDevice => do_login_device().await, - Commands::Serve { port, bind } => do_serve(&bind, port).await, + Commands::Login => do_login(&provider).await, + Commands::LoginDevice => do_login_device(&provider).await, + Commands::Serve { port, bind } => do_serve(&provider, &bind, port).await, } } diff --git a/xai-proxy/Cargo.lock b/xai-proxy/Cargo.lock deleted file mode 100644 index 5fe2be21..00000000 --- a/xai-proxy/Cargo.lock +++ /dev/null @@ -1,2381 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[package]] -name = "anstream" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" - -[[package]] -name = "anstyle-parse" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys 0.61.2", -] - -[[package]] -name = "anyhow" -version = "1.0.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "aws-lc-rs" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" -dependencies = [ - "aws-lc-sys", - "zeroize", -] - -[[package]] -name = "aws-lc-sys" -version = "0.41.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" -dependencies = [ - "cc", - "cmake", - "dunce", - "fs_extra", -] - -[[package]] -name = "axum" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" -dependencies = [ - "axum-core", - "bytes", - "form_urlencoded", - "futures-util", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-util", - "itoa", - "matchit", - "memchr", - "mime", - "percent-encoding", - "pin-project-lite", - "serde_core", - "serde_json", - "serde_path_to_error", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tower", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "axum-core" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "http-body-util", - "mime", - "pin-project-lite", - "sync_wrapper", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "bitflags" -version = "2.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bumpalo" -version = "3.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" - -[[package]] -name = "bytes" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" - -[[package]] -name = "cc" -version = "1.2.62" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1dce859f0832a7d088c4f1119888ab94ef4b5d6795d1ce05afb7fe159d79f98" -dependencies = [ - "find-msvc-tools", - "jobserver", - "libc", - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "clap" -version = "4.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "clap_lex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" - -[[package]] -name = "cmake" -version = "0.1.58" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" -dependencies = [ - "cc", -] - -[[package]] -name = "colorchoice" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" - -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crypto-common" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "dirs" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.61.2", -] - -[[package]] -name = "displaydoc" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "find-msvc-tools" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "fs_extra" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" - -[[package]] -name = "futures-channel" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" -dependencies = [ - "futures-core", -] - -[[package]] -name = "futures-core" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" - -[[package]] -name = "futures-sink" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" - -[[package]] -name = "futures-task" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" - -[[package]] -name = "futures-util" -version = "0.3.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" -dependencies = [ - "futures-core", - "futures-task", - "pin-project-lite", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi 5.3.0", - "wasip2", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" -dependencies = [ - "cfg-if", - "libc", - "r-efi 6.0.0", - "wasip2", - "wasip3", -] - -[[package]] -name = "h2" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.15.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" -dependencies = [ - "foldhash", -] - -[[package]] -name = "hashbrown" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "http" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" -dependencies = [ - "bytes", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http", -] - -[[package]] -name = "http-body-util" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" -dependencies = [ - "bytes", - "futures-core", - "http", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "hyper" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" -dependencies = [ - "http", - "hyper", - "hyper-util", - "log", - "rustls", - "rustls-native-certs", - "tokio", - "tokio-rustls", - "tower-service", - "webpki-roots", -] - -[[package]] -name = "hyper-util" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" -dependencies = [ - "base64", - "bytes", - "futures-channel", - "futures-util", - "http", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", -] - -[[package]] -name = "icu_collections" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" -dependencies = [ - "displaydoc", - "potential_utf", - "utf8_iter", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" - -[[package]] -name = "icu_properties" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" - -[[package]] -name = "icu_provider" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "indexmap" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" -dependencies = [ - "equivalent", - "hashbrown 0.17.1", - "serde", - "serde_core", -] - -[[package]] -name = "ipnet" -version = "2.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" - -[[package]] -name = "is-docker" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" -dependencies = [ - "once_cell", -] - -[[package]] -name = "is-wsl" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" -dependencies = [ - "is-docker", - "once_cell", -] - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "jobserver" -version = "0.1.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" -dependencies = [ - "getrandom 0.3.4", - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67df7112613f8bfd9150013a0314e196f4800d3201ae742489d999db2f979f08" -dependencies = [ - "cfg-if", - "futures-util", - "once_cell", - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - -[[package]] -name = "libc" -version = "0.2.186" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" - -[[package]] -name = "libredox" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" -dependencies = [ - "libc", -] - -[[package]] -name = "litemap" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" - -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" - -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" - -[[package]] -name = "matchers" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" -dependencies = [ - "regex-automata", -] - -[[package]] -name = "matchit" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" - -[[package]] -name = "memchr" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "mio" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", -] - -[[package]] -name = "nu-ansi-term" -version = "0.50.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - -[[package]] -name = "open" -version = "5.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fbaa89d2ddc8473c78a3adf69eea8cffa28c483b8e02a971ef31527cd0fc92c" -dependencies = [ - "is-wsl", - "libc", - "pathdiff", -] - -[[package]] -name = "openssl-probe" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" - -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link", -] - -[[package]] -name = "pathdiff" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] -name = "potential_utf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" -dependencies = [ - "zerovec", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "prettyplease" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" -dependencies = [ - "proc-macro2", - "syn", -] - -[[package]] -name = "proc-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quinn" -version = "0.11.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" -dependencies = [ - "bytes", - "cfg_aliases", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "socket2", - "thiserror", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" -dependencies = [ - "bytes", - "getrandom 0.3.4", - "lru-slab", - "rand 0.9.4", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "thiserror", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.60.2", -] - -[[package]] -name = "quote" -version = "1.0.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - -[[package]] -name = "rand" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.5", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.5", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", -] - -[[package]] -name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags", -] - -[[package]] -name = "redox_users" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" -dependencies = [ - "getrandom 0.2.17", - "libredox", - "thiserror", -] - -[[package]] -name = "regex-automata" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" - -[[package]] -name = "reqwest" -version = "0.12.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" -dependencies = [ - "base64", - "bytes", - "futures-core", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "js-sys", - "log", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-pki-types", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "webpki-roots", -] - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.17", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustc-hash" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" - -[[package]] -name = "rustls" -version = "0.23.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" -dependencies = [ - "aws-lc-rs", - "log", - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-native-certs" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" -dependencies = [ - "openssl-probe", - "rustls-pki-types", - "schannel", - "security-framework", -] - -[[package]] -name = "rustls-pki-types" -version = "1.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" -dependencies = [ - "web-time", - "zeroize", -] - -[[package]] -name = "rustls-webpki" -version = "0.103.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" -dependencies = [ - "aws-lc-rs", - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - -[[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - -[[package]] -name = "schannel" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "security-framework" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "semver" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" - -[[package]] -name = "serde" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.228" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.149" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_path_to_error" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" -dependencies = [ - "itoa", - "serde", - "serde_core", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sharded-slab" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" -dependencies = [ - "errno", - "libc", -] - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "smallvec" -version = "1.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" - -[[package]] -name = "socket2" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "syn" -version = "2.0.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thiserror" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "thread_local" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "tinystr" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinyvec" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "tokio" -version = "1.52.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" -dependencies = [ - "bytes", - "libc", - "mio", - "parking_lot", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-macros" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tower" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-http" -version = "0.6.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68d6fdd9f81c2819c9a8b0e0cd91660e7746a8e6ea2ba7c6b2b057985f6bcb51" -dependencies = [ - "bitflags", - "bytes", - "futures-util", - "http", - "http-body", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", - "tracing", - "url", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "log", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex-automata", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "typenum" -version = "1.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" - -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "url" -version = "2.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "urlencoding" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "uuid" -version = "1.23.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" -dependencies = [ - "getrandom 0.4.2", - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "valuable" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasip2" -version = "1.0.3+wasi-0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" -dependencies = [ - "wit-bindgen 0.57.1", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen 0.51.0", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.121" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49ace1d07c165b0864824eee619580c4689389afa9dc9ed3a4c75040d82e6790" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.71" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96492d0d3ffba25305a7dc88720d250b1401d7edca02cc3bcd50633b424673b8" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.121" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e68e6f4afd367a562002c05637acb8578ff2dea1943df76afb9e83d177c8578" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.121" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95a9ec35c64b2a7cb35d3fead40c4238d0940c86d107136999567a4703259f2" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.121" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4e0100b01e9f0d03189a92b96772a1fb998639d981193d7dbab487302513441" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" -dependencies = [ - "bitflags", - "hashbrown 0.15.5", - "indexmap", - "semver", -] - -[[package]] -name = "web-sys" -version = "0.3.98" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b572dff8bcf38bad0fa19729c89bb5748b2b9b1d8be70cf90df697e3a8f32aa" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki-roots" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" -dependencies = [ - "windows-targets 0.53.5", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm 0.52.6", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.53.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" -dependencies = [ - "windows-link", - "windows_aarch64_gnullvm 0.53.1", - "windows_aarch64_msvc 0.53.1", - "windows_i686_gnu 0.53.1", - "windows_i686_gnullvm 0.53.1", - "windows_i686_msvc 0.53.1", - "windows_x86_64_gnu 0.53.1", - "windows_x86_64_gnullvm 0.53.1", - "windows_x86_64_msvc 0.53.1", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_i686_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" - -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" - -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap", - "prettyplease", - "syn", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] - -[[package]] -name = "writeable" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" - -[[package]] -name = "xai-proxy" -version = "0.1.0" -dependencies = [ - "anyhow", - "axum", - "base64", - "clap", - "dirs", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "open", - "rand 0.8.6", - "reqwest", - "rustls", - "serde", - "serde_json", - "sha2", - "tokio", - "tower", - "tower-http", - "tracing", - "tracing-subscriber", - "url", - "urlencoding", - "uuid", -] - -[[package]] -name = "yoke" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zerocopy" -version = "0.8.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zerofrom" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" - -[[package]] -name = "zerotrie" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "zmij" -version = "1.0.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/xai-proxy/README.md b/xai-proxy/README.md deleted file mode 100644 index ccd63868..00000000 --- a/xai-proxy/README.md +++ /dev/null @@ -1,143 +0,0 @@ -# xai-proxy - -Lightweight Rust sidecar that authenticates with xAI via OAuth PKCE (SuperGrok subscription) and proxies OpenAI-compatible requests to `api.x.ai/v1`. - -## Why - -Use your SuperGrok subscription quota (instead of API credits) with any OpenAI-compatible coding agent — OpenCode, Hermes, etc. - -## How it works - -``` -┌──────────────────────────────────────────────────────────────┐ -│ Pod / Host │ -│ │ -│ ┌────────────────────┐ POST /v1/chat/completions │ -│ │ coding agent │──────────────────────┐ │ -│ │ (OpenCode, etc.) │ ▼ │ -│ └────────────────────┘ ┌─────────────────────────────┐ │ -│ │ xai-proxy :9090 │ │ -│ │ │ │ -│ │ • Injects Bearer token │ │ -│ │ • Auto-refreshes < 120s │ │ -│ └──────────────┬──────────────┘ │ -└───────────────────────────────────────────┼──────────────────┘ - │ - ▼ - ┌─────────────────────────────┐ - │ https://api.x.ai/v1 │ - │ (SuperGrok subscription) │ - └─────────────────────────────┘ -``` - -## Build - -```bash -cargo build --release -``` - -## Docker - -```bash -docker build -t xai-proxy . -docker run --rm -v ~/.xai-proxy:/root/.xai-proxy xai-proxy serve --bind 0.0.0.0 -``` - -## Usage - -### 1. Login (one-time) - -```bash -# Browser OAuth (local machine) -./target/release/xai-proxy login - -# Device-code flow (headless / K8s / ECS) -./target/release/xai-proxy login-device -``` - -Token is saved to `~/.xai-proxy/tokens.json` (or custom path via `XAI_PROXY_TOKEN_PATH`). - -### 2. Start proxy - -```bash -./target/release/xai-proxy serve --port 9090 -``` - -### 3. Point your client - -```bash -export OPENAI_BASE_URL=http://127.0.0.1:9090/v1 -export OPENAI_API_KEY=dummy -``` - -## Environment Variables - -| Variable | Default | Description | -|----------|---------|-------------| -| `XAI_PROXY_TOKEN_PATH` | `~/.xai-proxy/tokens.json` | Custom token file location | -| `RUST_LOG` | `xai_proxy=info` | Log level | - -## Kubernetes Sidecar - -Deploy alongside openab as a sidecar container with PVC persistence: - -```yaml -extraInitContainers: - - name: copy-tokens - image: busybox - command: ["sh", "-c", "if [ ! -f /dest/.openab/xai-proxy/tokens.json ]; then mkdir -p /dest/.openab/xai-proxy && cp /src/tokens.json /dest/.openab/xai-proxy/tokens.json; fi"] - volumeMounts: - - name: xai-tokens-src - mountPath: /src - readOnly: true - - name: data - mountPath: /dest - -extraContainers: - - name: xai-proxy - image: xai-proxy:latest - args: ["serve", "--bind", "0.0.0.0"] - env: - - name: XAI_PROXY_TOKEN_PATH - value: /home/agent/.openab/xai-proxy/tokens.json - volumeMounts: - - name: data - mountPath: /home/agent - -extraVolumes: - - name: xai-tokens-src - secret: - secretName: xai-proxy-tokens -``` - -## OAuth details - -| Item | Value | -|------|-------| -| Auth server | `https://auth.x.ai` | -| Client ID | Grok CLI public client | -| Flow | OAuth 2.0 PKCE (loopback) or device-code | -| Scope | `openid profile email offline_access grok-cli:access api:access` | -| Token storage | `~/.xai-proxy/tokens.json` (chmod 600) | -| Auto-refresh | Yes, 120s before expiry | - -## Requirements - -- Active SuperGrok subscription (any tier) -- Rust 1.86+ (build) -- Browser or device-code access for initial login - -## Headless / SSH login - -```bash -# Option A: device-code (recommended) -xai-proxy login-device - -# Option B: SSH port-forward -ssh -N -L 56121:127.0.0.1:56121 user@remote-host -xai-proxy login # open the URL in your local browser -``` - -## License - -MIT