@@ -316,6 +316,16 @@ type pluginCLIAvailableList struct {
316316 Available []pluginCLIAvailableEntry `json:"available"`
317317}
318318
319+ type pluginCLIMarketplaceEntry struct {
320+ Name string `json:"name"`
321+ Source string `json:"source,omitempty"`
322+ Repo string `json:"repo,omitempty"`
323+ URL string `json:"url,omitempty"`
324+ Path string `json:"path,omitempty"`
325+ Package string `json:"package,omitempty"`
326+ InstallLocation string `json:"installLocation,omitempty"`
327+ }
328+
319329func runPluginCLI (ctx context.Context , state * bootstrap.State , args []string , stdout io.Writer , stderr io.Writer ) int {
320330 _ = ctx
321331 if len (args ) == 0 {
@@ -325,6 +335,8 @@ func runPluginCLI(ctx context.Context, state *bootstrap.State, args []string, st
325335 switch strings .ToLower (strings .TrimSpace (args [0 ])) {
326336 case "list" , "ls" :
327337 return runPluginListCLI (state , args [1 :], stdout , stderr )
338+ case "marketplace" , "marketplaces" :
339+ return runPluginMarketplaceCLI (state , args [1 :], stdout , stderr )
328340 default :
329341 fmt .Fprintf (stderr , "ccgo plugin: unsupported subcommand %s\n " , args [0 ])
330342 return 2
@@ -393,6 +405,66 @@ func runPluginListCLI(state *bootstrap.State, args []string, stdout io.Writer, s
393405 return 0
394406}
395407
408+ func runPluginMarketplaceCLI (state * bootstrap.State , args []string , stdout io.Writer , stderr io.Writer ) int {
409+ if len (args ) == 0 {
410+ fmt .Fprintln (stderr , "ccgo plugin marketplace: missing subcommand" )
411+ return 2
412+ }
413+ switch strings .ToLower (strings .TrimSpace (args [0 ])) {
414+ case "list" , "ls" :
415+ return runPluginMarketplaceListCLI (state , args [1 :], stdout , stderr )
416+ default :
417+ fmt .Fprintf (stderr , "ccgo plugin marketplace: unsupported subcommand %s\n " , args [0 ])
418+ return 2
419+ }
420+ }
421+
422+ func runPluginMarketplaceListCLI (state * bootstrap.State , args []string , stdout io.Writer , stderr io.Writer ) int {
423+ flags := flag .NewFlagSet ("claude plugin marketplace list" , flag .ContinueOnError )
424+ flags .SetOutput (stderr )
425+ jsonOutput := flags .Bool ("json" , false , "output JSON" )
426+ if err := flags .Parse (args ); err != nil {
427+ if err == flag .ErrHelp {
428+ return 0
429+ }
430+ return 2
431+ }
432+ if flags .NArg () > 0 {
433+ fmt .Fprintf (stderr , "ccgo plugin marketplace list: unexpected argument %s\n " , flags .Arg (0 ))
434+ return 2
435+ }
436+ runner , err := state .ConversationRunner ()
437+ if err != nil {
438+ fmt .Fprintf (stderr , "ccgo plugin marketplace list: %v\n " , err )
439+ return 1
440+ }
441+ marketplaces := pluginCLIMarketplaceEntries (runnerMergedSettings (runner ))
442+ if * jsonOutput {
443+ encoder := json .NewEncoder (stdout )
444+ encoder .SetIndent ("" , " " )
445+ if err := encoder .Encode (marketplaces ); err != nil {
446+ fmt .Fprintf (stderr , "ccgo plugin marketplace list: %v\n " , err )
447+ return 1
448+ }
449+ return 0
450+ }
451+ if len (marketplaces ) == 0 {
452+ fmt .Fprintln (stdout , "No marketplaces configured" )
453+ return 0
454+ }
455+ fmt .Fprintln (stdout , "Configured marketplaces:" )
456+ for _ , marketplace := range marketplaces {
457+ fmt .Fprintf (stdout , "- %s\n " , marketplace .Name )
458+ if source := pluginCLIMarketplaceSourceText (marketplace ); source != "" {
459+ fmt .Fprintf (stdout , " Source: %s\n " , source )
460+ }
461+ if marketplace .InstallLocation != "" {
462+ fmt .Fprintf (stdout , " Install location: %s\n " , marketplace .InstallLocation )
463+ }
464+ }
465+ return 0
466+ }
467+
396468func pluginCLIInstalledEntries (plugins []pluginpkg.LoadedPlugin , settings contracts.Settings ) []pluginCLIListEntry {
397469 out := make ([]pluginCLIListEntry , 0 , len (plugins ))
398470 for _ , plugin := range plugins {
@@ -444,6 +516,81 @@ func pluginCLIAvailableEntries(marketplacePlugins []pluginpkg.LoadedPlugin, inst
444516 return out
445517}
446518
519+ func pluginCLIMarketplaceEntries (settings contracts.Settings ) []pluginCLIMarketplaceEntry {
520+ names := make ([]string , 0 , len (settings .ExtraKnownMarketplaces ))
521+ for name := range settings .ExtraKnownMarketplaces {
522+ if strings .TrimSpace (name ) != "" {
523+ names = append (names , strings .TrimSpace (name ))
524+ }
525+ }
526+ sort .Strings (names )
527+ out := make ([]pluginCLIMarketplaceEntry , 0 , len (names ))
528+ for _ , name := range names {
529+ rawEntry , _ := settings .ExtraKnownMarketplaces [name ].(map [string ]any )
530+ source := pluginCLIMarketplaceSource (rawEntry )
531+ sourceType := strings .TrimSpace (pluginCLIStringFromMap (source , "source" ))
532+ entry := pluginCLIMarketplaceEntry {
533+ Name : name ,
534+ Source : sourceType ,
535+ InstallLocation : strings .TrimSpace (pluginCLIStringFromMap (rawEntry , "installLocation" )),
536+ }
537+ switch sourceType {
538+ case "github" :
539+ entry .Repo = pluginCLIStringFromMap (source , "repo" )
540+ case "git" , "url" :
541+ entry .URL = pluginCLIStringFromMap (source , "url" )
542+ case "directory" , "file" :
543+ entry .Path = pluginCLIStringFromMap (source , "path" )
544+ case "npm" :
545+ entry .Package = pluginCLIStringFromMap (source , "package" )
546+ }
547+ out = append (out , entry )
548+ }
549+ return out
550+ }
551+
552+ func pluginCLIMarketplaceSource (rawEntry map [string ]any ) map [string ]any {
553+ if len (rawEntry ) == 0 {
554+ return nil
555+ }
556+ if source , ok := rawEntry ["source" ].(map [string ]any ); ok {
557+ return source
558+ }
559+ return rawEntry
560+ }
561+
562+ func pluginCLIMarketplaceSourceText (marketplace pluginCLIMarketplaceEntry ) string {
563+ switch marketplace .Source {
564+ case "github" :
565+ if marketplace .Repo != "" {
566+ return "GitHub (" + marketplace .Repo + ")"
567+ }
568+ case "git" :
569+ if marketplace .URL != "" {
570+ return "Git (" + marketplace .URL + ")"
571+ }
572+ case "url" :
573+ if marketplace .URL != "" {
574+ return "URL (" + marketplace .URL + ")"
575+ }
576+ case "directory" :
577+ if marketplace .Path != "" {
578+ return "Directory (" + marketplace .Path + ")"
579+ }
580+ case "file" :
581+ if marketplace .Path != "" {
582+ return "File (" + marketplace .Path + ")"
583+ }
584+ case "npm" :
585+ if marketplace .Package != "" {
586+ return "NPM (" + marketplace .Package + ")"
587+ }
588+ case "settings" :
589+ return "Settings"
590+ }
591+ return strings .TrimSpace (marketplace .Source )
592+ }
593+
447594func pluginCLIID (plugin pluginpkg.LoadedPlugin ) string {
448595 name := strings .TrimSpace (plugin .Name )
449596 if name == "" {
@@ -467,6 +614,14 @@ func pluginCLINameKey(name string) string {
467614 return strings .ToLower (strings .TrimSpace (name ))
468615}
469616
617+ func pluginCLIStringFromMap (values map [string ]any , key string ) string {
618+ if len (values ) == 0 {
619+ return ""
620+ }
621+ value , _ := values [key ].(string )
622+ return strings .TrimSpace (value )
623+ }
624+
470625func pluginCLIMCPServerNames (servers map [string ]contracts.MCPServer ) []string {
471626 names := make ([]string , 0 , len (servers ))
472627 for name := range servers {
0 commit comments