@@ -34,10 +34,11 @@ const defaultHttpClient: HttpClient = {
3434} ;
3535
3636interface AddProfileArgs {
37- "api-base-url" ? : string ;
37+ "api-base-url" : string ;
3838 "openapi-spec" : string ;
3939 "api-basic-auth" ? : string ;
4040 "api-bearer-token" ? : string ;
41+ "auth-values" ? : string ;
4142 "include-endpoints" ? : string ;
4243 "exclude-endpoints" ? : string ;
4344 "command-prefix" ? : string ;
@@ -164,8 +165,9 @@ async function runApiCommand(
164165 throw new Error ( `Missing required options: ${ missingRequired . map ( ( n ) => `--${ n } ` ) . join ( ", " ) } ` ) ;
165166 }
166167
167- const url = buildRequestUrl ( profile , command , flags ) ;
168- const headers = buildHeaders ( profile , command , flags ) ;
168+ const securityArtifacts = buildSecurityArtifacts ( profile , spec , command ) ;
169+ const url = buildRequestUrl ( profile , command , flags , securityArtifacts . queryParts ) ;
170+ const headers = buildHeaders ( profile , command , flags , securityArtifacts ) ;
169171 const payload = buildRequestPayload ( command , flags ) ;
170172
171173 const method = command . method . toUpperCase ( ) ;
@@ -234,7 +236,12 @@ function parseArgs(args: string[]): { flags: Record<string, string>; positional:
234236 return { flags, positional } ;
235237}
236238
237- function buildRequestUrl ( profile : Profile , command : CliCommand , flags : Record < string , string > ) : string {
239+ function buildRequestUrl (
240+ profile : Profile ,
241+ command : CliCommand ,
242+ flags : Record < string , string > ,
243+ securityQueryParts : string [ ] = [ ]
244+ ) : string {
238245 let pathValue = command . path ;
239246
240247 command . options
@@ -260,14 +267,20 @@ function buildRequestUrl(profile: Profile, command: CliCommand, flags: Record<st
260267 }
261268 } ) ;
262269
263- if ( queryParts . length > 0 ) {
264- url += url . includes ( "?" ) ? `&${ queryParts . join ( "&" ) } ` : `?${ queryParts . join ( "&" ) } ` ;
270+ const allQueryParts = [ ...queryParts , ...securityQueryParts ] ;
271+ if ( allQueryParts . length > 0 ) {
272+ url += url . includes ( "?" ) ? `&${ allQueryParts . join ( "&" ) } ` : `?${ allQueryParts . join ( "&" ) } ` ;
265273 }
266274
267275 return url ;
268276}
269277
270- function buildHeaders ( profile : Profile , command : CliCommand , flags : Record < string , string > ) : Record < string , string > {
278+ function buildHeaders (
279+ profile : Profile ,
280+ command : CliCommand ,
281+ flags : Record < string , string > ,
282+ securityArtifacts ?: { headers : Record < string , string > ; cookies : string [ ] }
283+ ) : Record < string , string > {
271284 const headers : Record < string , string> = { } ;
272285
273286 if ( profile . customHeaders ) {
@@ -281,6 +294,10 @@ function buildHeaders(profile: Profile, command: CliCommand, flags: Record<strin
281294 headers . Authorization = `Bearer ${ profile . apiBearerToken } ` ;
282295 }
283296
297+ if ( securityArtifacts ?. headers ) {
298+ Object . assign ( headers , securityArtifacts . headers ) ;
299+ }
300+
284301 const cookiePairs : string [ ] = [ ] ;
285302 command . options
286303 . filter ( ( opt ) => opt . location === "header" || opt . location === "cookie" )
@@ -302,9 +319,176 @@ function buildHeaders(profile: Profile, command: CliCommand, flags: Record<strin
302319 headers . Cookie = cookiePairs . join ( "; " ) ;
303320 }
304321
322+ if ( securityArtifacts ?. cookies && securityArtifacts . cookies . length > 0 ) {
323+ headers . Cookie = headers . Cookie
324+ ? `${ headers . Cookie } ; ${ securityArtifacts . cookies . join ( "; " ) } `
325+ : securityArtifacts . cookies . join ( "; " ) ;
326+ }
327+
305328 return headers ;
306329}
307330
331+ interface SecuritySchemeLike {
332+ type ?: string ;
333+ scheme ?: string ;
334+ in ?: string ;
335+ name ?: string ;
336+ }
337+
338+ function buildSecurityArtifacts (
339+ profile : Profile ,
340+ spec : unknown ,
341+ command : CliCommand
342+ ) : {
343+ headers: Record < string , string > ;
344+ queryParts: string [ ] ;
345+ cookies: string [ ] ;
346+ } {
347+ const operation = getOperationFromSpec ( spec , command ) ;
348+ const securityRequirements = getSecurityRequirements ( spec , operation ) ;
349+ const securitySchemes = getSecuritySchemes ( spec ) ;
350+
351+ if ( securityRequirements . length === 0 ) {
352+ return { headers : { } , queryParts : [ ] , cookies : [ ] } ;
353+ }
354+
355+ for ( const requirement of securityRequirements ) {
356+ const headers : Record < string , string > = { } ;
357+ const queryParts : string [ ] = [ ] ;
358+ const cookies : string [ ] = [ ] ;
359+ let satisfied = true ;
360+
361+ for ( const schemeName of Object . keys ( requirement ) ) {
362+ const scheme = securitySchemes [ schemeName ] ;
363+ const artifacts = applySecurityScheme ( profile , schemeName , scheme ) ;
364+ if ( ! artifacts ) {
365+ satisfied = false ;
366+ break ;
367+ }
368+
369+ Object . assign ( headers , artifacts . headers ) ;
370+ queryParts . push ( ...artifacts . queryParts ) ;
371+ cookies . push ( ...artifacts . cookies ) ;
372+ }
373+
374+ if ( satisfied ) {
375+ return { headers , queryParts , cookies } ;
376+ }
377+ }
378+
379+ throw new Error ( "Missing credentials for the security requirements defined by this operation" ) ;
380+ }
381+
382+ function getOperationFromSpec ( spec : unknown , command : CliCommand ) : Record < string , unknown > | undefined {
383+ if ( ! spec || typeof spec !== "object" ) {
384+ return undefined ;
385+ }
386+
387+ const pathItem = ( spec as Record < string , unknown > ) . paths as Record < string , unknown > | undefined ;
388+ const operationGroup = pathItem ?. [ command . path ] as Record < string , unknown > | undefined ;
389+ return operationGroup ?. [ command . method ] as Record < string , unknown > | undefined ;
390+ }
391+
392+ function getSecurityRequirements ( spec : unknown , operation ?: Record < string , unknown > ) : Array < Record < string , string [ ] >> {
393+ if ( operation && Array . isArray ( operation . security ) ) {
394+ return operation . security as Array < Record < string , string [ ] >> ;
395+ }
396+
397+ if ( spec && typeof spec === "object" && Array . isArray ( ( spec as Record < string , unknown > ) . security ) ) {
398+ return ( spec as Record < string , unknown > ) . security as Array < Record < string , string [ ] >> ;
399+ }
400+
401+ return [ ] ;
402+ }
403+
404+ function getSecuritySchemes ( spec : unknown ) : Record < string , SecuritySchemeLike > {
405+ if ( ! spec || typeof spec !== "object" ) {
406+ return { } ;
407+ }
408+
409+ const record = spec as Record < string , unknown > ;
410+ const components = record . components as Record < string , unknown > | undefined ;
411+ const oasSchemes = components ?. securitySchemes as Record < string , SecuritySchemeLike > | undefined ;
412+ const swaggerSchemes = record . securityDefinitions as Record < string , SecuritySchemeLike > | undefined ;
413+
414+ return {
415+ ...( oasSchemes ?? { } ) ,
416+ ...( swaggerSchemes ?? { } ) ,
417+ } ;
418+ }
419+
420+ function applySecurityScheme (
421+ profile : Profile ,
422+ schemeName : string ,
423+ scheme ?: SecuritySchemeLike
424+ ) : {
425+ headers : Record < string , string > ;
426+ queryParts : string [ ] ;
427+ cookies : string [ ] ;
428+ } | null {
429+ if ( ! scheme ?. type ) {
430+ return null ;
431+ }
432+
433+ if ( scheme . type === "apiKey" ) {
434+ const value = profile . authValues [ schemeName ] ;
435+ if ( ! value || ! scheme . name || ! scheme . in ) {
436+ return null ;
437+ }
438+
439+ if ( scheme . in === "header" ) {
440+ return {
441+ headers : { [ scheme . name ] : value } ,
442+ queryParts : [ ] ,
443+ cookies : [ ] ,
444+ } ;
445+ }
446+
447+ if ( scheme . in === "query" ) {
448+ return {
449+ headers : { } ,
450+ queryParts : [ `${ encodeURIComponent ( scheme . name ) } =${ encodeURIComponent ( value ) } ` ] ,
451+ cookies : [ ] ,
452+ } ;
453+ }
454+
455+ if ( scheme . in === "cookie" ) {
456+ return {
457+ headers : { } ,
458+ queryParts : [ ] ,
459+ cookies : [ `${ encodeURIComponent ( scheme . name ) } =${ encodeURIComponent ( value ) } ` ] ,
460+ } ;
461+ }
462+
463+ return null ;
464+ }
465+
466+ if ( scheme . type === "http" ) {
467+ const normalized = ( scheme . scheme ?? "" ) . toLowerCase ( ) ;
468+ if ( normalized === "basic" && profile . apiBasicAuth ) {
469+ const encoded = Buffer . from ( profile . apiBasicAuth ) . toString ( "base64" ) ;
470+ return { headers : { Authorization : `Basic ${ encoded } ` } , queryParts : [ ] , cookies : [ ] } ;
471+ }
472+
473+ if ( normalized === "bearer" && profile . apiBearerToken ) {
474+ return { headers : { Authorization : `Bearer ${ profile . apiBearerToken } ` } , queryParts : [ ] , cookies : [ ] } ;
475+ }
476+
477+ return null ;
478+ }
479+
480+ if ( scheme . type === "basic" && profile . apiBasicAuth ) {
481+ const encoded = Buffer . from ( profile . apiBasicAuth ) . toString ( "base64" ) ;
482+ return { headers : { Authorization : `Basic ${ encoded } ` } , queryParts : [ ] , cookies : [ ] } ;
483+ }
484+
485+ if ( ( scheme . type === "oauth2" || scheme . type === "openIdConnect" ) && profile . apiBearerToken ) {
486+ return { headers : { Authorization : `Bearer ${ profile . apiBearerToken } ` } , queryParts : [ ] , cookies : [ ] } ;
487+ }
488+
489+ return null ;
490+ }
491+
308492function buildRequestPayload (
309493 command : CliCommand ,
310494 flags : Record < string , string >
@@ -507,6 +691,7 @@ export async function run(argv: string[], options?: RunOptions): Promise<void> {
507691 : [ ] ;
508692
509693 const customHeaders : Record < string , string> = { } ;
694+ const authValues : Record < string , string> = { } ;
510695 if ( args [ "custom-headers" ] ) {
511696 const raw = args [ "custom-headers" ] . trim ( ) ;
512697 if ( raw . startsWith ( "{" ) ) {
@@ -527,12 +712,24 @@ export async function run(argv: string[], options?: RunOptions): Promise<void> {
527712 } ) ;
528713 }
529714 }
715+ if ( args [ "auth - values "] ) {
716+ const raw = args [ "auth-values" ] . trim ( ) ;
717+ if ( ! raw . startsWith ( "{" ) ) {
718+ throw new Error ( "Invalid --auth-values JSON. Expected format: '{\"SchemeName\":\"value\"}'" ) ;
719+ }
720+ try {
721+ Object . assign ( authValues , JSON . parse ( raw ) ) ;
722+ } catch {
723+ throw new Error ( "Invalid --auth-values JSON. Expected format: '{\"SchemeName\":\"value\"}'" ) ;
724+ }
725+ }
530726
531727 const profile : Profile = {
532728 name : profileName ,
533- apiBaseUrl : args [ "api-base-url" ] ?? "" ,
729+ apiBaseUrl : args [ "api-base-url" ] ,
534730 apiBasicAuth : args [ "api-basic-auth" ] ?? "" ,
535731 apiBearerToken : args [ "api-bearer-token" ] ?? "" ,
732+ authValues ,
536733 openapiSpecSource : args [ "openapi-spec" ] ,
537734 openapiSpecCache : cachePath ,
538735 includeEndpoints ,
@@ -541,11 +738,7 @@ export async function run(argv: string[], options?: RunOptions): Promise<void> {
541738 customHeaders ,
542739 } ;
543740
544- const spec = await openapiLoader . loadSpec ( profile , { refresh : true } ) ;
545- profile . apiBaseUrl = profile . apiBaseUrl || deriveApiBaseUrlFromSpec ( spec ) ;
546- if ( ! profile . apiBaseUrl ) {
547- throw new Error ( "Unable to determine API base URL. Provide --api-base-url explicitly." ) ;
548- }
741+ await openapiLoader . loadSpec ( profile , { refresh : true } ) ;
549742 profileStore . saveProfile ( cwd , profile , { makeCurrent : true } ) ;
550743 } ;
551744
@@ -559,6 +752,11 @@ export async function run(argv: string[], options?: RunOptions): Promise<void> {
559752 . option ( "openapi-spec" , { type : "string" , demandOption : true } )
560753 . option ( "api-basic-auth" , { type : "string" , default : "" } )
561754 . option ( "api-bearer-token" , { type : "string" , default : "" } )
755+ . option ( "auth-values" , {
756+ type : "string" ,
757+ default : "" ,
758+ description : "Security scheme values as JSON, e.g. '{\"ApiKeyAuth\":\"secret\"}'" ,
759+ } )
562760 . option ( "include-endpoints" , { type : "string" , default : "" } )
563761 . option ( "exclude-endpoints" , { type : "string" , default : "" } )
564762 . option ( "command-prefix" , { type : "string" , default : "" , description : "Prefix for command names (e.g. api_ -> api_messages)" } )
0 commit comments