@@ -616,6 +616,148 @@ def _print_repo(name: str, repo: PluginRepo, is_user: bool = False):
616616 typer .echo ()
617617
618618
619+ # ==================== Browse Marketplace Helper Functions ====================
620+
621+
622+ def _resolve_marketplace_repo (
623+ manager : PluginManager , handler : ClaudePluginHandler , marketplace : str
624+ ) -> tuple [Optional [str ], Optional [str ], str ]:
625+ """Resolve marketplace name to repo owner/name/branch.
626+
627+ Returns:
628+ Tuple of (repo_owner, repo_name, repo_branch) or (None, None, "main") if not found
629+ """
630+ repo = manager .get_repo (marketplace )
631+
632+ if repo and repo .repo_owner and repo .repo_name :
633+ return repo .repo_owner , repo .repo_name , repo .repo_branch
634+
635+ # Try Claude's known marketplaces as fallback
636+ return _resolve_from_known_marketplaces (handler , marketplace )
637+
638+
639+ def _resolve_from_known_marketplaces (
640+ handler : ClaudePluginHandler , marketplace : str
641+ ) -> tuple [Optional [str ], Optional [str ], str ]:
642+ """Fallback resolution from Claude's known_marketplaces.json."""
643+ import json
644+
645+ known_file = handler .known_marketplaces_file
646+ if not known_file .exists ():
647+ return None , None , "main"
648+
649+ try :
650+ with open (known_file , "r" ) as f :
651+ known = json .load (f )
652+
653+ if marketplace not in known :
654+ return None , None , "main"
655+
656+ source_url = known [marketplace ].get ("source" , {}).get ("url" , "" )
657+ if "github.com" not in source_url :
658+ return None , None , "main"
659+
660+ parsed = parse_github_url (source_url )
661+ if parsed :
662+ return parsed
663+ except Exception :
664+ pass
665+
666+ return None , None , "main"
667+
668+
669+ def _filter_plugins (
670+ plugins : list [dict ],
671+ query : Optional [str ] = None ,
672+ category : Optional [str ] = None ,
673+ ) -> list [dict ]:
674+ """Filter plugins by query string and/or category."""
675+ result = plugins
676+
677+ if query :
678+ query_lower = query .lower ()
679+ result = [
680+ p
681+ for p in result
682+ if query_lower in p .get ("name" , "" ).lower ()
683+ or query_lower in p .get ("description" , "" ).lower ()
684+ ]
685+
686+ if category :
687+ category_lower = category .lower ()
688+ result = [p for p in result if category_lower in p .get ("category" , "" ).lower ()]
689+
690+ return result
691+
692+
693+ def _display_marketplace_not_found (
694+ manager : PluginManager , handler : ClaudePluginHandler , marketplace : str
695+ ) -> None :
696+ """Display error message when marketplace is not found."""
697+ typer .echo (
698+ f"{ Colors .RED } ✗ Marketplace '{ marketplace } ' not found in config or Claude.{ Colors .RESET } "
699+ )
700+ typer .echo (f"\n { Colors .CYAN } Available repos:{ Colors .RESET } " )
701+ for name in manager .get_all_repos ():
702+ typer .echo (f" • { name } " )
703+ typer .echo (f"\n { Colors .CYAN } Installed marketplaces:{ Colors .RESET } " )
704+ for name in handler .get_known_marketplaces ():
705+ typer .echo (f" • { name } " )
706+
707+
708+ def _display_marketplace_header (
709+ info , query : Optional [str ], category : Optional [str ], total : int
710+ ) -> None :
711+ """Display marketplace info header."""
712+ typer .echo (
713+ f"\n { Colors .BOLD } { info .name } { Colors .RESET } - { info .description or 'No description' } "
714+ )
715+ if info .version :
716+ typer .echo (f"Version: { info .version } " )
717+ typer .echo (f"Total plugins: { info .plugin_count } " )
718+ if query or category :
719+ typer .echo (f"Matching: { total } " )
720+
721+
722+ def _display_plugin (plugin : dict ) -> None :
723+ """Display a single plugin entry."""
724+ name = plugin .get ("name" , "unknown" )
725+ version = plugin .get ("version" , "" )
726+ desc = plugin .get ("description" , "" )
727+ cat = plugin .get ("category" , "" )
728+
729+ version_str = f" v{ version } " if version else ""
730+ cat_str = f" [{ cat } ]" if cat else ""
731+
732+ typer .echo (
733+ f" { Colors .BOLD } { name } { Colors .RESET } { version_str } { Colors .CYAN } { cat_str } { Colors .RESET } "
734+ )
735+ if desc :
736+ if len (desc ) > 80 :
737+ desc = desc [:77 ] + "..."
738+ typer .echo (f" { desc } " )
739+
740+
741+ def _display_marketplace_footer (info , marketplace : str , total : int , limit : int ) -> None :
742+ """Display marketplace footer with categories and install hint."""
743+ if total > limit :
744+ typer .echo (f"\n ... and { total - limit } more" )
745+
746+ categories = {p .get ("category" ) for p in info .plugins if p .get ("category" )}
747+ if categories :
748+ typer .echo (
749+ f"\n { Colors .CYAN } Categories:{ Colors .RESET } { ', ' .join (sorted (categories ))} "
750+ )
751+
752+ typer .echo (
753+ f"\n { Colors .CYAN } Install with:{ Colors .RESET } cam plugin install <plugin-name>@{ marketplace } "
754+ )
755+ typer .echo ()
756+
757+
758+ # ==================== Browse Marketplace Command ====================
759+
760+
619761@plugin_app .command ("browse" )
620762def browse_marketplace (
621763 marketplace : str = typer .Argument (
@@ -646,135 +788,40 @@ def browse_marketplace(
646788 Fetches the marketplace manifest from GitHub and lists all available plugins.
647789 Use --query to search by name/description, --category to filter by category.
648790 """
791+ from code_assistant_manager .plugins .fetch import fetch_repo_info
792+
649793 manager = PluginManager ()
650794 handler = _get_handler ()
651795
652- # Get the marketplace repo config
653- repo = manager .get_repo (marketplace )
654-
655- # If not in our config, try to get URL from Claude's known_marketplaces.json
656- repo_owner = None
657- repo_name = None
658- repo_branch = "main"
659-
660- if repo and repo .repo_owner and repo .repo_name :
661- repo_owner = repo .repo_owner
662- repo_name = repo .repo_name
663- repo_branch = repo .repo_branch
664- else :
665- # Try to extract from Claude's known marketplaces
666- known_file = handler .known_marketplaces_file
667- if known_file .exists ():
668- import json
669-
670- try :
671- with open (known_file , "r" ) as f :
672- known = json .load (f )
673- if marketplace in known :
674- source_url = known [marketplace ].get ("source" , {}).get ("url" , "" )
675- # Parse URL like https://github.com/owner/repo.git
676- if "github.com" in source_url :
677- from code_assistant_manager .plugins .fetch import (
678- parse_github_url ,
679- )
680-
681- parsed = parse_github_url (source_url )
682- if parsed :
683- repo_owner , repo_name , repo_branch = parsed
684- except Exception :
685- pass
796+ # Resolve marketplace to repo info
797+ repo_owner , repo_name , repo_branch = _resolve_marketplace_repo (
798+ manager , handler , marketplace
799+ )
686800
687801 if not repo_owner or not repo_name :
688- typer .echo (
689- f"{ Colors .RED } ✗ Marketplace '{ marketplace } ' not found in config or Claude.{ Colors .RESET } "
690- )
691- typer .echo (f"\n { Colors .CYAN } Available repos:{ Colors .RESET } " )
692- for name in manager .get_all_repos ():
693- typer .echo (f" • { name } " )
694- typer .echo (f"\n { Colors .CYAN } Installed marketplaces:{ Colors .RESET } " )
695- for name in handler .get_known_marketplaces ():
696- typer .echo (f" • { name } " )
802+ _display_marketplace_not_found (manager , handler , marketplace )
697803 raise typer .Exit (1 )
698804
805+ # Fetch plugins
699806 typer .echo (f"{ Colors .CYAN } Fetching plugins from { marketplace } ...{ Colors .RESET } " )
700-
701- from code_assistant_manager .plugins .fetch import fetch_repo_info
702-
703807 info = fetch_repo_info (repo_owner , repo_name , repo_branch )
808+
704809 if not info or not info .plugins :
705810 typer .echo (f"{ Colors .RED } ✗ Could not fetch plugins from repo.{ Colors .RESET } " )
706811 raise typer .Exit (1 )
707812
708- # Filter plugins
709- plugins = info .plugins
710- if query :
711- query_lower = query .lower ()
712- plugins = [
713- p
714- for p in plugins
715- if query_lower in p .get ("name" , "" ).lower ()
716- or query_lower in p .get ("description" , "" ).lower ()
717- ]
718-
719- if category :
720- category_lower = category .lower ()
721- plugins = [
722- p for p in plugins if category_lower in p .get ("category" , "" ).lower ()
723- ]
724-
725- # Display results
813+ # Filter and display
814+ plugins = _filter_plugins (info .plugins , query , category )
726815 total = len (plugins )
727816 plugins = plugins [:limit ]
728817
729- typer .echo (
730- f"\n { Colors .BOLD } { info .name } { Colors .RESET } - { info .description or 'No description' } "
731- )
732- if info .version :
733- typer .echo (f"Version: { info .version } " )
734- typer .echo (f"Total plugins: { info .plugin_count } " )
735-
736- if query or category :
737- typer .echo (f"Matching: { total } " )
738-
818+ _display_marketplace_header (info , query , category , total )
739819 typer .echo (f"\n { Colors .BOLD } Plugins:{ Colors .RESET } \n " )
740820
741- # Get unique categories for reference
742- categories = set ()
743- for p in info .plugins :
744- if p .get ("category" ):
745- categories .add (p ["category" ])
746-
747- for p in plugins :
748- name = p .get ("name" , "unknown" )
749- version = p .get ("version" , "" )
750- desc = p .get ("description" , "" )
751- cat = p .get ("category" , "" )
821+ for plugin in plugins :
822+ _display_plugin (plugin )
752823
753- version_str = f" v{ version } " if version else ""
754- cat_str = f" [{ cat } ]" if cat else ""
755-
756- typer .echo (
757- f" { Colors .BOLD } { name } { Colors .RESET } { version_str } { Colors .CYAN } { cat_str } { Colors .RESET } "
758- )
759- if desc :
760- # Truncate long descriptions
761- if len (desc ) > 80 :
762- desc = desc [:77 ] + "..."
763- typer .echo (f" { desc } " )
764-
765- if total > limit :
766- typer .echo (f"\n ... and { total - limit } more" )
767-
768- # Show categories if available
769- if categories :
770- typer .echo (
771- f"\n { Colors .CYAN } Categories:{ Colors .RESET } { ', ' .join (sorted (categories ))} "
772- )
773-
774- typer .echo (
775- f"\n { Colors .CYAN } Install with:{ Colors .RESET } cam plugin install <plugin-name>@{ marketplace } "
776- )
777- typer .echo ()
824+ _display_marketplace_footer (info , marketplace , total , limit )
778825
779826
780827@plugin_app .command ("fetch" )
0 commit comments