@@ -111,6 +111,16 @@ impl ApiClient {
111111 }
112112 }
113113
114+ /// Returns the API token, if set.
115+ pub fn api_token ( & self ) -> Option < & String > {
116+ self . api_token . as_ref ( )
117+ }
118+
119+ /// Returns the org slug, if set.
120+ pub fn org_slug ( & self ) -> Option < & String > {
121+ self . org_slug . as_ref ( )
122+ }
123+
114124 // ── Internal helpers ──────────────────────────────────────────────
115125
116126 /// Internal GET that deserialises JSON. Returns `Ok(None)` on 404.
@@ -397,6 +407,46 @@ impl ApiClient {
397407 } )
398408 }
399409
410+ /// Fetch organizations accessible to the current API token.
411+ pub async fn fetch_organizations (
412+ & self ,
413+ ) -> Result < Vec < crate :: api:: types:: OrganizationInfo > , ApiError > {
414+ let path = "/v0/organizations" ;
415+ match self
416+ . get_json :: < crate :: api:: types:: OrganizationsResponse > ( path)
417+ . await ?
418+ {
419+ Some ( resp) => Ok ( resp. organizations . into_values ( ) . collect ( ) ) ,
420+ None => Ok ( Vec :: new ( ) ) ,
421+ }
422+ }
423+
424+ /// Resolve the org slug from the API token by querying `/v0/organizations`.
425+ ///
426+ /// If there is exactly one org, returns its slug.
427+ /// If there are multiple, picks the first and prints a warning.
428+ /// If there are none, returns an error.
429+ pub async fn resolve_org_slug ( & self ) -> Result < String , ApiError > {
430+ let orgs = self . fetch_organizations ( ) . await ?;
431+ match orgs. len ( ) {
432+ 0 => Err ( ApiError :: Other (
433+ "No organizations found for this API token." . into ( ) ,
434+ ) ) ,
435+ 1 => Ok ( orgs. into_iter ( ) . next ( ) . unwrap ( ) . slug ) ,
436+ _ => {
437+ let slugs: Vec < _ > = orgs. iter ( ) . map ( |o| o. slug . as_str ( ) ) . collect ( ) ;
438+ let first = orgs[ 0 ] . slug . clone ( ) ;
439+ eprintln ! (
440+ "Multiple organizations found: {}. Using \" {}\" . \
441+ Pass --org to select a different one.",
442+ slugs. join( ", " ) ,
443+ first
444+ ) ;
445+ Ok ( first)
446+ }
447+ }
448+ }
449+
400450 /// Fetch a blob by its SHA-256 hash.
401451 ///
402452 /// Returns the raw binary content, or `Ok(None)` if not found.
@@ -490,6 +540,10 @@ impl ApiClient {
490540/// API proxy which provides free access to free-tier patches without
491541/// authentication.
492542///
543+ /// When `SOCKET_API_TOKEN` is set but no org slug is provided (neither via
544+ /// argument nor `SOCKET_ORG_SLUG` env var), the function will attempt to
545+ /// auto-resolve the org slug by querying `GET /v0/organizations`.
546+ ///
493547/// # Environment variables
494548///
495549/// | Variable | Purpose |
@@ -500,7 +554,7 @@ impl ApiClient {
500554/// | `SOCKET_ORG_SLUG` | Organization slug |
501555///
502556/// Returns `(client, use_public_proxy)`.
503- pub fn get_api_client_from_env ( org_slug : Option < & str > ) -> ( ApiClient , bool ) {
557+ pub async fn get_api_client_from_env ( org_slug : Option < & str > ) -> ( ApiClient , bool ) {
504558 let api_token = std:: env:: var ( "SOCKET_API_TOKEN" ) . ok ( ) ;
505559 let resolved_org_slug = org_slug
506560 . map ( String :: from)
@@ -524,11 +578,30 @@ pub fn get_api_client_from_env(org_slug: Option<&str>) -> (ApiClient, bool) {
524578 let api_url =
525579 std:: env:: var ( "SOCKET_API_URL" ) . unwrap_or_else ( |_| DEFAULT_SOCKET_API_URL . to_string ( ) ) ;
526580
581+ // Auto-resolve org slug if not provided
582+ let final_org_slug = if resolved_org_slug. is_some ( ) {
583+ resolved_org_slug
584+ } else {
585+ let temp_client = ApiClient :: new ( ApiClientOptions {
586+ api_url : api_url. clone ( ) ,
587+ api_token : api_token. clone ( ) ,
588+ use_public_proxy : false ,
589+ org_slug : None ,
590+ } ) ;
591+ match temp_client. resolve_org_slug ( ) . await {
592+ Ok ( slug) => Some ( slug) ,
593+ Err ( e) => {
594+ eprintln ! ( "Warning: Could not auto-detect organization: {e}" ) ;
595+ None
596+ }
597+ }
598+ } ;
599+
527600 let client = ApiClient :: new ( ApiClientOptions {
528601 api_url,
529602 api_token,
530603 use_public_proxy : false ,
531- org_slug : resolved_org_slug ,
604+ org_slug : final_org_slug ,
532605 } ) ;
533606 ( client, false )
534607}
@@ -714,11 +787,11 @@ mod tests {
714787 assert_eq ! ( info. title, "Test vulnerability" ) ;
715788 }
716789
717- #[ test]
718- fn test_get_api_client_from_env_no_token ( ) {
790+ #[ tokio :: test]
791+ async fn test_get_api_client_from_env_no_token ( ) {
719792 // Clear token to ensure public proxy mode
720793 std:: env:: remove_var ( "SOCKET_API_TOKEN" ) ;
721- let ( client, is_public) = get_api_client_from_env ( None ) ;
794+ let ( client, is_public) = get_api_client_from_env ( None ) . await ;
722795 assert ! ( is_public) ;
723796 assert ! ( client. use_public_proxy) ;
724797 }
0 commit comments