diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..877cbef --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,25 @@ +{ + "permissions": { + "allow": [ + "Bash(doppler run:*)", + "WebSearch", + "Bash(cargo clean:*)", + "Bash(cargo tree:*)", + "WebFetch(domain:docs.rs)", + "WebFetch(domain:seanmonstar.com)", + "WebFetch(domain:github.com)", + "WebFetch(domain:crates.io)", + "WebFetch(domain:raw.githubusercontent.com)", + "Bash(dir:*)", + "Bash(cargo check:*)", + "Bash(git add:*)", + "Bash(git commit -m \"$\\(cat <<''EOF''\nfix: use ephemeral port for dev server to avoid Hyper-V conflicts\n\nPort 7777 falls within Windows'' excluded port range \\(7747-7846\\) reserved\nby Hyper-V/WSL2/Docker, causing \"os error 10013\" on many Windows machines.\nNow lets the OS pick an available port automatically.\n\nCo-Authored-By: Claude Opus 4.5 \nEOF\n\\)\")", + "Bash(grep:*)", + "Bash(agent-browser *)" + ] + }, + "enabledPlugins": { + "rust-skills@rust-skills": true, + "github@claude-plugins-official": true + } +} diff --git a/.claude/settings.local.json b/.claude/settings.local.json index fe80515..60c9a02 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -1,19 +1,16 @@ { "permissions": { "allow": [ - "Bash(doppler run:*)", - "WebSearch", - "Bash(cargo clean:*)", - "Bash(cargo tree:*)", - "WebFetch(domain:docs.rs)", - "WebFetch(domain:seanmonstar.com)", - "WebFetch(domain:github.com)", - "WebFetch(domain:crates.io)", - "WebFetch(domain:raw.githubusercontent.com)", - "Bash(dir:*)", - "Bash(cargo check:*)", - "Bash(git add:*)", - "Bash(git commit -m \"$\\(cat <<''EOF''\nfix: use ephemeral port for dev server to avoid Hyper-V conflicts\n\nPort 7777 falls within Windows'' excluded port range \\(7747-7846\\) reserved\nby Hyper-V/WSL2/Docker, causing \"os error 10013\" on many Windows machines.\nNow lets the OS pick an available port automatically.\n\nCo-Authored-By: Claude Opus 4.5 \nEOF\n\\)\")" + "Bash(cargo build:*)", + "Bash(./target/debug/wvdsh auth:*)", + "Bash(WVDSH_TOKEN=test-token-12345678 ./target/debug/wvdsh auth status:*)", + "WebFetch(domain:docs.github.com)", + "Bash(gh ruleset list:*)", + "Bash(gh ruleset check:*)", + "Bash(gh api:*)", + "Bash(while read id)", + "Bash(do gh api -X DELETE \"repos/wvdsh/docs/rulesets/$id\")", + "Bash(done)" ] } } diff --git a/Cargo.lock b/Cargo.lock index 0614fac..12c739f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3121,7 +3121,7 @@ checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] name = "wvdsh" -version = "0.1.46" +version = "0.1.47" dependencies = [ "anyhow", "axoupdater", diff --git a/Cargo.toml b/Cargo.toml index ea86738..6e70a22 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wvdsh" -version = "0.1.46" +version = "0.1.47" edition = "2021" authors = ["Wavedash Team"] description = "Cross-platform CLI tool for uploading game projects to wavedash.com" diff --git a/src/auth.rs b/src/auth.rs index daa5732..68b6e81 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -10,6 +10,17 @@ struct Credentials { api_key: String, } +pub enum AuthSource { + Environment, + File, + None, +} + +pub struct AuthInfo { + pub source: AuthSource, + pub api_key: Option, +} + pub struct AuthManager; impl AuthManager { @@ -46,13 +57,42 @@ impl AuthManager { Ok(()) } - pub fn get_api_key(&self) -> Option { + fn read_file_credentials(&self) -> Option { let path = config::credentials_path().ok()?; let json = fs::read_to_string(&path).ok()?; let credentials: Credentials = serde_json::from_str(&json).ok()?; Some(credentials.api_key) } + pub fn get_auth_info(&self) -> AuthInfo { + // Check environment first + if let Ok(api_key) = std::env::var("WVDSH_TOKEN") { + if !api_key.is_empty() { + return AuthInfo { + source: AuthSource::Environment, + api_key: Some(api_key), + }; + } + } + + // Check file + if let Some(api_key) = self.read_file_credentials() { + return AuthInfo { + source: AuthSource::File, + api_key: Some(api_key), + }; + } + + AuthInfo { + source: AuthSource::None, + api_key: None, + } + } + + pub fn get_api_key(&self) -> Option { + self.get_auth_info().api_key + } + pub fn clear_credentials(&self) -> Result<()> { let path = config::credentials_path()?; if path.exists() { @@ -60,10 +100,6 @@ impl AuthManager { } Ok(()) } - - pub fn is_authenticated(&self) -> bool { - self.get_api_key().is_some() - } } fn find_available_port() -> Result { diff --git a/src/config.rs b/src/config.rs index 3644424..d4ed20e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,17 +1,13 @@ use anyhow::Result; -use directories::ProjectDirs; +use directories::BaseDirs; use serde::Deserialize; use std::path::PathBuf; -// Standard project directories configuration -pub const PROJECT_QUALIFIER: &str = "gg"; -pub const PROJECT_ORGANIZATION: &str = "wavedash"; -pub const PROJECT_APPLICATION: &str = "cli"; - -/// Get the project directories for this application -pub fn project_dirs() -> Result { - ProjectDirs::from(PROJECT_QUALIFIER, PROJECT_ORGANIZATION, PROJECT_APPLICATION) - .ok_or_else(|| anyhow::anyhow!("Could not determine project directories")) +/// Get the wvdsh config directory (~/.wvdsh) +pub fn wvdsh_dir() -> Result { + let base_dirs = BaseDirs::new() + .ok_or_else(|| anyhow::anyhow!("Could not determine home directory"))?; + Ok(base_dirs.home_dir().join(".wvdsh")) } #[derive(Deserialize)] @@ -52,8 +48,7 @@ pub fn get(key: &str) -> Result { /// Get the path to the credentials file pub fn credentials_path() -> Result { - let dirs = project_dirs()?; - Ok(dirs.config_dir().join("credentials.json")) + Ok(wvdsh_dir()?.join("credentials.json")) } /// Create an HTTP client configured with Cloudflare Access headers if needed diff --git a/src/dev/cert.rs b/src/dev/cert.rs index 6dbc1c3..bd68d21 100644 --- a/src/dev/cert.rs +++ b/src/dev/cert.rs @@ -16,8 +16,7 @@ const DEV_CERT_COMMON_NAME: &str = "wvdsh dev server"; const LINUX_CERT_INSTALL_PATH: &str = "/usr/local/share/ca-certificates/wvdsh-dev.crt"; pub async fn load_or_create_certificates() -> Result<(RustlsConfig, PathBuf, PathBuf)> { - let project_dirs = config::project_dirs()?; - let cert_dir = project_dirs.config_dir().join(DEV_CERT_SUBDIR); + let cert_dir = config::wvdsh_dir()?.join(DEV_CERT_SUBDIR); fs::create_dir_all(&cert_dir)?; let cert_path = cert_dir.join(DEV_CERT_NAME); diff --git a/src/main.rs b/src/main.rs index 068bbbc..b36cfb2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,12 +6,20 @@ mod file_staging; mod updater; use anyhow::Result; -use auth::{login_with_browser, AuthManager}; +use auth::{login_with_browser, AuthManager, AuthSource}; use builds::handle_build_push; use clap::{Parser, Subcommand}; use dev::handle_dev; use std::path::PathBuf; +fn mask_token(token: &str) -> String { + if token.len() > 10 { + format!("{}...{}", &token[..6], &token[token.len() - 3..]) + } else { + "***".to_string() + } +} + #[derive(Parser)] #[command(name = "wvdsh")] #[command(about = "Cross-platform CLI tool for uploading game projects to wavedash.com")] @@ -111,18 +119,23 @@ async fn main() -> Result<()> { println!("✓ Successfully logged out"); } AuthCommands::Status => { - if auth_manager.is_authenticated() { - println!("✓ Authenticated"); - if let Some(api_key) = auth_manager.get_api_key() { - let preview = if api_key.len() > 10 { - format!("{}...{}", &api_key[..6], &api_key[api_key.len() - 3..]) - } else { - "***".to_string() - }; - println!("API Key: {}", preview); + let auth_info = auth_manager.get_auth_info(); + match auth_info.source { + AuthSource::Environment => { + println!("✓ Authenticated (via WVDSH_TOKEN environment variable)"); + if let Some(api_key) = auth_info.api_key { + println!("Token: {}", mask_token(&api_key)); + } + } + AuthSource::File => { + println!("✓ Authenticated (via stored credentials)"); + if let Some(api_key) = auth_info.api_key { + println!("API Key: {}", mask_token(&api_key)); + } + } + AuthSource::None => { + println!("Not authenticated. Run 'wvdsh auth login' or set WVDSH_TOKEN environment variable."); } - } else { - println!("Not authenticated. Run 'wvdsh auth login' to get started."); } } } diff --git a/src/updater.rs b/src/updater.rs index 6f6bc8d..3a482cd 100644 --- a/src/updater.rs +++ b/src/updater.rs @@ -19,9 +19,8 @@ struct UpdateCache { impl UpdateCache { fn cache_path() -> Result { - let project_dirs = config::project_dirs()?; - let cache_dir = project_dirs.config_dir(); - fs::create_dir_all(cache_dir)?; + let cache_dir = config::wvdsh_dir()?; + fs::create_dir_all(&cache_dir)?; Ok(cache_dir.join("update-cache.json")) }