Skip to content

Commit ebaabcd

Browse files
zhujian0805claude
andcommitted
feat: restructure plugin command architecture
- Move fetch functionality from 'cam plugin fetch' to 'cam plugin marketplace add --save' - Remove deprecated fetch command from plugin commands - Add numbering to marketplace list output for better usability - Update integration tests to reflect command changes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent da9ed6f commit ebaabcd

File tree

6 files changed

+281
-149
lines changed

6 files changed

+281
-149
lines changed

code_assistant_manager/cli/plugin_commands.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,4 @@
4242
# Discovery commands
4343
plugin_app.command("browse")(plugin_discovery_commands.browse_marketplace)
4444
plugin_app.command("view")(plugin_discovery_commands.view_plugin)
45-
plugin_app.command("fetch")(plugin_discovery_commands.fetch_repo)
4645
plugin_app.command("status")(plugin_discovery_commands.plugin_status)

code_assistant_manager/cli/plugins/plugin_discovery_commands.py

Lines changed: 143 additions & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -463,175 +463,178 @@ def view_plugin(
463463
typer.echo()
464464

465465

466-
@plugin_app.command("fetch")
467-
def fetch_repo(
468-
url: Optional[str] = typer.Argument(
466+
@plugin_app.command("status")
467+
def plugin_status(
468+
app_type: Optional[str] = typer.Option(
469469
None,
470-
help="GitHub URL or owner/repo (e.g., https://github.com/owner/repo or owner/repo)",
471-
),
472-
save: bool = typer.Option(
473-
False,
474-
"--save",
475-
"-s",
476-
help="Save the fetched repo to user config (only used with URL argument)",
470+
"--app",
471+
"-a",
472+
help=f"App type ({', '.join(VALID_APP_TYPES)}). If not specified, shows status for all apps.",
477473
),
478474
):
479-
"""Fetch plugin repos from configured repositories or a specific GitHub URL.
475+
"""Show plugin system status for an app, or all apps if none specified."""
476+
from code_assistant_manager.cli.option_utils import resolve_single_app
480477

481-
Without URL: Re-fetches info from all configured plugin repositories.
482-
With URL: Analyzes a GitHub repository to determine if it's a single plugin
483-
or a marketplace with multiple plugins, then optionally saves it.
478+
# If no app specified, show status for all apps
479+
if app_type is None:
480+
typer.echo(f"\n{Colors.BOLD}Plugin System Status (All Apps):{Colors.RESET}\n")
484481

485-
Examples:
486-
cam plugin fetch # Fetch from all configured repos
487-
cam plugin fetch owner/repo --save # Fetch and save a new repo
488-
cam plugin fetch https://github.com/owner/repo --save
489-
"""
490-
manager = PluginManager()
482+
# Show common CAM configuration
483+
manager = PluginManager()
484+
all_repos = manager.get_all_repos()
485+
configured_marketplaces = {
486+
k: v for k, v in all_repos.items() if v.type == "marketplace"
487+
}
488+
configured_plugins = {k: v for k, v in all_repos.items() if v.type == "plugin"}
491489

492-
# If no URL provided, fetch from all configured repos
493-
if not url:
494490
typer.echo(
495-
f"{Colors.CYAN}Fetching plugin repos from all configured repositories...{Colors.RESET}"
491+
f"{Colors.CYAN}Configured Marketplaces (CAM):{Colors.RESET} {len(configured_marketplaces)}"
496492
)
493+
for name, repo in sorted(configured_marketplaces.items()):
494+
if repo.repo_owner and repo.repo_name:
495+
typer.echo(f" • {name} ({repo.repo_owner}/{repo.repo_name})")
496+
else:
497+
typer.echo(f" • {name}")
497498

498-
all_repos = manager.get_all_repos()
499-
if not all_repos:
499+
if configured_plugins:
500500
typer.echo(
501-
f"{Colors.YELLOW}No plugin repositories configured{Colors.RESET}"
501+
f"\n{Colors.CYAN}Configured Plugins (CAM):{Colors.RESET} {len(configured_plugins)}"
502502
)
503-
return
503+
for name, repo in sorted(configured_plugins.items()):
504+
if repo.repo_owner and repo.repo_name:
505+
typer.echo(f" • {name} ({repo.repo_owner}/{repo.repo_name})")
506+
else:
507+
typer.echo(f" • {name}")
504508

505-
success_count = 0
506-
for repo_name, repo in all_repos.items():
507-
if not repo.enabled:
508-
typer.echo(f" {Colors.YELLOW}{Colors.RESET} {repo_name} (disabled)")
509-
continue
509+
typer.echo(f"\n{Colors.CYAN}{'='*50}{Colors.RESET}\n")
510510

511-
if not repo.repo_owner or not repo.repo_name:
512-
typer.echo(
513-
f" {Colors.YELLOW}{Colors.RESET} {repo_name} (no GitHub info)"
514-
)
515-
continue
511+
for app in VALID_APP_TYPES:
512+
show_app_info(app, show_cam_config=False)
513+
if app != VALID_APP_TYPES[-1]: # Don't add separator after last app
514+
typer.echo(f"\n{Colors.CYAN}{'='*50}{Colors.RESET}\n")
515+
return
516516

517-
# Fetch repo info
518-
from code_assistant_manager.plugins import fetch_repo_info
517+
# Show info for specific app
518+
app = resolve_single_app(app_type, VALID_APP_TYPES, default="claude")
519+
show_app_info(app)
519520

520-
info = fetch_repo_info(
521-
repo.repo_owner, repo.repo_name, repo.repo_branch or "main"
522-
)
523521

524-
if info:
525-
plugin_info = (
526-
f"{info.plugin_count} plugins"
527-
if info.type == "marketplace"
528-
else "plugin"
529-
)
530-
typer.echo(
531-
f" {Colors.GREEN}{Colors.RESET} {repo_name} ({info.type}: {plugin_info})"
532-
)
533-
success_count += 1
534-
else:
535-
typer.echo(
536-
f" {Colors.RED}{Colors.RESET} {repo_name} (failed to fetch)"
537-
)
522+
def show_app_info(app: str, show_cam_config: bool = True):
523+
"""Show plugin system information for a specific app."""
524+
handler = get_handler(app)
525+
manager = PluginManager()
538526

539-
typer.echo(
540-
f"\n{Colors.GREEN}✓ Fetched {success_count}/{len(all_repos)} repositories{Colors.RESET}"
541-
)
542-
typer.echo(
543-
f"\n{Colors.CYAN}Run 'cam plugin repos' to see all configured repos{Colors.RESET}"
544-
)
545-
return
527+
typer.echo(f"\n{Colors.BOLD}{app.capitalize()} Plugin System:{Colors.RESET}\n")
546528

547-
typer.echo(f"{Colors.CYAN}Fetching repository info...{Colors.RESET}")
529+
# Show paths
530+
typer.echo(f"{Colors.CYAN}Configuration:{Colors.RESET}")
531+
typer.echo(f" Home: {handler.home_dir}")
532+
typer.echo(f" Plugins: {handler.user_plugins_dir}")
533+
typer.echo(f" Marketplaces: {handler.marketplaces_dir}")
534+
typer.echo(f" Settings: {handler.settings_file}")
548535

549-
# Parse and validate URL
550-
parsed = parse_github_url(url)
551-
if not parsed:
552-
typer.echo(f"{Colors.RED}✗ Invalid GitHub URL: {url}{Colors.RESET}")
553-
raise typer.Exit(1)
536+
# Check status
537+
typer.echo(f"\n{Colors.CYAN}Status:{Colors.RESET}")
554538

555-
owner, repo, branch = parsed
556-
typer.echo(f" Repository: {Colors.BOLD}{owner}/{repo}{Colors.RESET}")
539+
home_exists = handler.home_dir.exists()
540+
status = (
541+
f"{Colors.GREEN}{Colors.RESET}"
542+
if home_exists
543+
else f"{Colors.RED}{Colors.RESET}"
544+
)
545+
typer.echo(f" {status} Home directory exists")
557546

558-
# Fetch repo info
559-
info = fetch_repo_info_from_url(url)
560-
if not info:
561-
typer.echo(
562-
f"{Colors.RED}✗ Could not fetch repository info. "
563-
f"Make sure the repo has .claude-plugin/marketplace.json{Colors.RESET}"
564-
)
565-
raise typer.Exit(1)
547+
plugins_exists = handler.user_plugins_dir.exists()
548+
status = (
549+
f"{Colors.GREEN}{Colors.RESET}"
550+
if plugins_exists
551+
else f"{Colors.RED}{Colors.RESET}"
552+
)
553+
typer.echo(f" {status} Plugins directory exists")
566554

567-
# Display results
568-
typer.echo(f"\n{Colors.BOLD}Repository Information:{Colors.RESET}\n")
569-
typer.echo(f" {Colors.CYAN}Name:{Colors.RESET} {info.name}")
570-
typer.echo(f" {Colors.CYAN}Type:{Colors.RESET} {info.type}")
571-
typer.echo(f" {Colors.CYAN}Description:{Colors.RESET} {info.description or 'N/A'}")
572-
typer.echo(f" {Colors.CYAN}Branch:{Colors.RESET} {info.branch}")
555+
cli_path = handler.get_cli_path()
556+
status = (
557+
f"{Colors.GREEN}{Colors.RESET}" if cli_path else f"{Colors.RED}{Colors.RESET}"
558+
)
559+
typer.echo(f" {status} {app.capitalize()} CLI: {cli_path or 'Not found'}")
573560

574-
if info.version:
575-
typer.echo(f" {Colors.CYAN}Version:{Colors.RESET} {info.version}")
576-
577-
if info.type == "marketplace":
578-
typer.echo(f" {Colors.CYAN}Plugin Count:{Colors.RESET} {info.plugin_count}")
579-
if info.plugins and len(info.plugins) <= 10:
580-
typer.echo(f"\n {Colors.CYAN}Plugins:{Colors.RESET}")
581-
for p in info.plugins[:10]:
582-
typer.echo(f" • {p.get('name', 'unknown')}")
583-
elif info.plugins:
584-
typer.echo(f"\n {Colors.CYAN}Plugins:{Colors.RESET} (showing first 10)")
585-
for p in info.plugins[:10]:
586-
typer.echo(f" • {p.get('name', 'unknown')}")
587-
typer.echo(f" ... and {len(info.plugins) - 10} more")
588-
else:
589-
if info.plugin_path:
590-
typer.echo(f" {Colors.CYAN}Plugin Path:{Colors.RESET} {info.plugin_path}")
591-
592-
# Save if requested
593-
if save:
594-
# Check if already exists
595-
existing = manager.get_repo(info.name)
596-
if existing:
597-
typer.echo(
598-
f"\n{Colors.YELLOW}Repository '{info.name}' already exists in config.{Colors.RESET}"
599-
)
600-
if not typer.confirm("Overwrite?"):
601-
raise typer.Exit(0)
602-
603-
# Create PluginRepo and save
604-
from code_assistant_manager.plugins.models import PluginRepo
605-
606-
plugin_repo = PluginRepo(
607-
name=info.name,
608-
description=info.description,
609-
repo_owner=info.owner,
610-
repo_name=info.repo,
611-
repo_branch=info.branch,
612-
plugin_path=info.plugin_path,
613-
type=info.type,
614-
enabled=True,
615-
)
616-
manager.add_user_repo(plugin_repo)
561+
# Get configured repos from CAM (only show once for all apps)
562+
all_repos = manager.get_all_repos()
563+
configured_marketplaces = {
564+
k: v for k, v in all_repos.items() if v.type == "marketplace"
565+
}
566+
configured_plugins = {k: v for k, v in all_repos.items() if v.type == "plugin"}
567+
568+
# Show configured marketplaces (from CAM) - only when requested
569+
if show_cam_config and app == VALID_APP_TYPES[0]: # Only show global config once
617570
typer.echo(
618-
f"\n{Colors.GREEN}✓ Saved '{info.name}' to user config as {info.type}{Colors.RESET}"
571+
f"\n{Colors.CYAN}Configured Marketplaces (CAM):{Colors.RESET} {len(configured_marketplaces)}"
619572
)
620-
typer.echo(f" Config file: {manager.plugin_repos_file}")
573+
for name, repo in sorted(configured_marketplaces.items()):
574+
if repo.repo_owner and repo.repo_name:
575+
typer.echo(f" • {name} ({repo.repo_owner}/{repo.repo_name})")
576+
else:
577+
typer.echo(f" • {name}")
621578

622-
# Show next steps
623-
if info.type == "marketplace":
579+
# Show configured plugins (from CAM)
580+
if configured_plugins:
624581
typer.echo(
625-
f"\n{Colors.CYAN}Next:{Colors.RESET} cam plugin install {info.name}"
582+
f"\n{Colors.CYAN}Configured Plugins (CAM):{Colors.RESET} {len(configured_plugins)}"
626583
)
627-
else:
628-
typer.echo(
629-
f"\n{Colors.CYAN}Next:{Colors.RESET} cam plugin install {info.name}"
584+
for name, repo in sorted(configured_plugins.items()):
585+
if repo.repo_owner and repo.repo_name:
586+
typer.echo(f" • {name} ({repo.repo_owner}/{repo.repo_name})")
587+
else:
588+
typer.echo(f" • {name}")
589+
590+
# Show installed marketplaces (from app)
591+
installed_marketplaces = handler.get_known_marketplaces()
592+
typer.echo(
593+
f"\n{Colors.CYAN}Installed Marketplaces ({app.capitalize()}):{Colors.RESET} {len(installed_marketplaces)}"
594+
)
595+
for name, info in sorted(installed_marketplaces.items()):
596+
source = info.get("source", {})
597+
source_url = source.get("url", "")
598+
# Extract repo from URL like https://github.com/owner/repo.git
599+
if "github.com" in source_url:
600+
repo_part = source_url.replace("https://github.com/", "").replace(
601+
".git", ""
630602
)
631-
else:
632-
typer.echo(
633-
f"\n{Colors.CYAN}To save:{Colors.RESET} cam plugin fetch '{url}' --save"
634-
)
603+
typer.echo(f" • {name} ({repo_part})")
604+
else:
605+
typer.echo(f" • {name}")
606+
607+
# Show installed/enabled plugins with details
608+
enabled = handler.get_enabled_plugins()
609+
enabled_plugins = {k: v for k, v in enabled.items() if v}
610+
disabled_plugins = {k: v for k, v in enabled.items() if not v}
611+
612+
typer.echo(
613+
f"\n{Colors.CYAN}Installed Plugins ({app.capitalize()}):{Colors.RESET} {len(enabled_plugins)} enabled, {len(disabled_plugins)} disabled"
614+
)
615+
616+
if enabled_plugins:
617+
typer.echo(f"\n {Colors.GREEN}Enabled:{Colors.RESET}")
618+
for plugin_key in sorted(enabled_plugins.keys()):
619+
# Parse plugin key (format: plugin-name@marketplace or just plugin-name)
620+
if "@" in plugin_key:
621+
plugin_name, marketplace = plugin_key.split("@", 1)
622+
typer.echo(
623+
f" {Colors.GREEN}{Colors.RESET} {plugin_name} ({marketplace})"
624+
)
625+
else:
626+
typer.echo(f" {Colors.GREEN}{Colors.RESET} {plugin_key}")
627+
628+
if disabled_plugins:
629+
typer.echo(f"\n {Colors.RED}Disabled:{Colors.RESET}")
630+
for plugin_key in sorted(disabled_plugins.keys()):
631+
if "@" in plugin_key:
632+
plugin_name, marketplace = plugin_key.split("@", 1)
633+
typer.echo(
634+
f" {Colors.RED}{Colors.RESET} {plugin_name} ({marketplace})"
635+
)
636+
else:
637+
typer.echo(f" {Colors.RED}{Colors.RESET} {plugin_key}")
635638

636639
typer.echo()
637640

0 commit comments

Comments
 (0)