@@ -4715,6 +4715,213 @@ def config_init(
47154715 console .print (f"[green]✓ Created { scope } config: { config_path } [/green]" )
47164716
47174717
4718+ # =============================================================================
4719+ # Hub Commands (Community Skill Registry)
4720+ # =============================================================================
4721+
4722+ hub_app = typer .Typer (
4723+ help = "SkillForge Hub - community skill registry" ,
4724+ no_args_is_help = True ,
4725+ )
4726+ app .add_typer (hub_app , name = "hub" )
4727+
4728+
4729+ @hub_app .command ("search" )
4730+ def hub_search (
4731+ query : str = typer .Argument (..., help = "Search query" ),
4732+ ) -> None :
4733+ """Search for skills in the SkillForge Hub.
4734+
4735+ Searches skill names, descriptions, and tags in the community registry.
4736+
4737+ Example:
4738+
4739+ \b
4740+ skillforge hub search debug
4741+ skillforge hub search "code review"
4742+ """
4743+ from skillforge .hub import search_skills
4744+
4745+ console .print (f"[dim]Searching for '{ query } '...[/dim]" )
4746+
4747+ try :
4748+ results = search_skills (query )
4749+
4750+ if not results :
4751+ console .print ()
4752+ console .print (f"[yellow]No skills found matching '{ query } '[/yellow]" )
4753+ console .print ("[dim]Try a different search term or run 'skillforge hub list' to see all skills[/dim]" )
4754+ return
4755+
4756+ console .print ()
4757+ table = Table (title = f"Search Results ({ len (results )} )" , show_header = True , header_style = "bold" )
4758+ table .add_column ("Skill" , style = "cyan" )
4759+ table .add_column ("Description" )
4760+ table .add_column ("Author" , style = "dim" )
4761+ table .add_column ("Version" , style = "dim" )
4762+
4763+ for skill in results :
4764+ desc = skill .description [:50 ] + "..." if len (skill .description ) > 50 else skill .description
4765+ table .add_row (skill .name , desc , skill .author , skill .version )
4766+
4767+ console .print (table )
4768+ console .print ()
4769+ console .print ("[dim]Install with: skillforge hub install <skill-name>[/dim]" )
4770+
4771+ except ConnectionError as e :
4772+ console .print (f"[red]Error:[/red] { e } " )
4773+ raise typer .Exit (code = 1 )
4774+
4775+
4776+ @hub_app .command ("list" )
4777+ def hub_list () -> None :
4778+ """List all skills in the SkillForge Hub.
4779+
4780+ Example:
4781+
4782+ \b
4783+ skillforge hub list
4784+ """
4785+ from skillforge .hub import list_skills
4786+
4787+ console .print ("[dim]Fetching skills from hub...[/dim]" )
4788+
4789+ try :
4790+ skills = list_skills ()
4791+
4792+ if not skills :
4793+ console .print ()
4794+ console .print ("[yellow]No skills found in the hub[/yellow]" )
4795+ return
4796+
4797+ console .print ()
4798+ table = Table (title = f"SkillForge Hub ({ len (skills )} skills)" , show_header = True , header_style = "bold" )
4799+ table .add_column ("Skill" , style = "cyan" )
4800+ table .add_column ("Description" )
4801+ table .add_column ("Author" , style = "dim" )
4802+ table .add_column ("Tags" , style = "dim" )
4803+
4804+ for skill in skills :
4805+ desc = skill .description [:45 ] + "..." if len (skill .description ) > 45 else skill .description
4806+ tags = ", " .join (skill .tags [:3 ])
4807+ if len (skill .tags ) > 3 :
4808+ tags += "..."
4809+ table .add_row (skill .name , desc , skill .author , tags )
4810+
4811+ console .print (table )
4812+ console .print ()
4813+ console .print ("[dim]Install with: skillforge hub install <skill-name>[/dim]" )
4814+
4815+ except ConnectionError as e :
4816+ console .print (f"[red]Error:[/red] { e } " )
4817+ raise typer .Exit (code = 1 )
4818+
4819+
4820+ @hub_app .command ("info" )
4821+ def hub_info (
4822+ name : str = typer .Argument (..., help = "Skill name" ),
4823+ ) -> None :
4824+ """Show details about a skill in the hub.
4825+
4826+ Example:
4827+
4828+ \b
4829+ skillforge hub info code-reviewer
4830+ """
4831+ from skillforge .hub import get_skill
4832+
4833+ try :
4834+ skill = get_skill (name )
4835+
4836+ if not skill :
4837+ console .print (f"[red]Skill not found:[/red] { name } " )
4838+ console .print ("[dim]Run 'skillforge hub search' to find skills[/dim]" )
4839+ raise typer .Exit (code = 1 )
4840+
4841+ console .print ()
4842+ console .print (f"[bold cyan]{ skill .name } [/bold cyan] v{ skill .version } " )
4843+ console .print ()
4844+ console .print (f"[bold]Description:[/bold] { skill .description } " )
4845+ console .print (f"[bold]Author:[/bold] { skill .author } " )
4846+ console .print (f"[bold]Tags:[/bold] { ', ' .join (skill .tags )} " )
4847+ console .print ()
4848+ console .print ("[dim]Install with:[/dim]" )
4849+ console .print (f" skillforge hub install { skill .name } " )
4850+ console .print (f" skillforge hub install { skill .name } --project [dim]# install in current project[/dim]" )
4851+
4852+ except ConnectionError as e :
4853+ console .print (f"[red]Error:[/red] { e } " )
4854+ raise typer .Exit (code = 1 )
4855+
4856+
4857+ @hub_app .command ("install" )
4858+ def hub_install (
4859+ name : str = typer .Argument (..., help = "Skill name to install" ),
4860+ project : bool = typer .Option (
4861+ False ,
4862+ "--project" ,
4863+ "-p" ,
4864+ help = "Install to project's .claude/skills/ instead of user's ~/.claude/skills/" ,
4865+ ),
4866+ output : Optional [Path ] = typer .Option (
4867+ None ,
4868+ "--output" ,
4869+ "-o" ,
4870+ help = "Custom output directory" ,
4871+ ),
4872+ ) -> None :
4873+ """Install a skill from the SkillForge Hub.
4874+
4875+ By default, installs to ~/.claude/skills/ (user scope).
4876+ Use --project to install to ./.claude/skills/ (project scope).
4877+
4878+ Example:
4879+
4880+ \b
4881+ skillforge hub install code-reviewer
4882+ skillforge hub install debugger --project
4883+ skillforge hub install sql-helper -o ./my-skills
4884+ """
4885+ from skillforge .hub import install_skill , get_skill
4886+
4887+ # First check if skill exists
4888+ skill = get_skill (name )
4889+ if not skill :
4890+ console .print (f"[red]Skill not found:[/red] { name } " )
4891+ console .print ("[dim]Run 'skillforge hub search' to find skills[/dim]" )
4892+ raise typer .Exit (code = 1 )
4893+
4894+ console .print (f"[dim]Installing { skill .name } v{ skill .version } ...[/dim]" )
4895+
4896+ try :
4897+ skill_dir = install_skill (name , output_dir = output , project = project )
4898+
4899+ console .print ()
4900+ console .print (f"[green]✓ Installed:[/green] { skill .name } " )
4901+ console .print (f" Location: { skill_dir } " )
4902+ console .print ()
4903+
4904+ # Show files installed
4905+ console .print ("[bold]Files:[/bold]" )
4906+ for item in sorted (skill_dir .rglob ("*" )):
4907+ if item .is_file ():
4908+ rel_path = item .relative_to (skill_dir )
4909+ console .print (f" { rel_path } " )
4910+
4911+ console .print ()
4912+ if not project and not output :
4913+ console .print ("[dim]Skill is now available globally in Claude Code[/dim]" )
4914+ elif project :
4915+ console .print ("[dim]Skill is available in this project's Claude Code sessions[/dim]" )
4916+
4917+ except ConnectionError as e :
4918+ console .print (f"[red]Error:[/red] Failed to download skill: { e } " )
4919+ raise typer .Exit (code = 1 )
4920+ except ValueError as e :
4921+ console .print (f"[red]Error:[/red] { e } " )
4922+ raise typer .Exit (code = 1 )
4923+
4924+
47184925# =============================================================================
47194926# Info Command
47204927# =============================================================================
0 commit comments