Skip to content

Commit 47c36c1

Browse files
James Zhuclaude
andcommitted
feat: deprecate 'cam plugin browse' command, enhance 'list' command
- Add deprecation warning to 'cam plugin browse' command - Enhance 'cam plugin list' command with browse functionality: - Add marketplace argument for browsing specific marketplaces - Add --query, --category, and --limit options for filtering - Show both installed and available plugins by default - Update help text and maintain backward compatibility 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 8e98f2c commit 47c36c1

2 files changed

Lines changed: 202 additions & 14 deletions

File tree

code_assistant_manager/cli/plugins/plugin_discovery_commands.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,9 @@ def browse_marketplace(
191191
help=f"App type ({', '.join(VALID_APP_TYPES)})",
192192
),
193193
):
194-
"""Browse plugins in configured marketplaces or a specific one.
194+
"""[DEPRECATED] Browse plugins in configured marketplaces or a specific one.
195+
196+
⚠️ This command is deprecated. Use 'cam plugin list' instead.
195197
196198
Without a marketplace name: Shows all plugins from all marketplaces and standalone plugins.
197199
With a marketplace name: Fetches the marketplace manifest from GitHub and lists available plugins.
@@ -200,6 +202,11 @@ def browse_marketplace(
200202
from code_assistant_manager.cli.option_utils import resolve_single_app
201203
from code_assistant_manager.plugins.fetch import fetch_repo_info
202204

205+
# Show deprecation warning
206+
typer.echo(f"{Colors.YELLOW}⚠️ Warning: 'cam plugin browse' is deprecated.{Colors.RESET}")
207+
typer.echo(f"{Colors.CYAN} Use 'cam plugin list' instead to view installed and available plugins.{Colors.RESET}")
208+
typer.echo()
209+
203210
app = resolve_single_app(app_type, VALID_APP_TYPES, default="claude")
204211
manager = PluginManager()
205212
handler = get_handler(app)

code_assistant_manager/cli/plugins/plugin_management_commands.py

Lines changed: 194 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,33 +26,111 @@
2626

2727
@plugin_app.command("list")
2828
def list_plugins(
29+
marketplace: Optional[str] = typer.Argument(
30+
None,
31+
help="Marketplace name to browse (from 'cam plugin repos'). If not specified, shows all plugins from all marketplaces.",
32+
),
2933
show_all: bool = typer.Option(
3034
False,
3135
"--all",
32-
help="Show all plugins from marketplaces (not just enabled)",
36+
help="Show all plugins from marketplaces (not just enabled). Deprecated: use without marketplace argument instead.",
3337
),
3438
app_type: Optional[str] = typer.Option(
3539
None,
3640
"--app",
3741
"-a",
3842
help=f"App type to show plugins for ({', '.join(VALID_APP_TYPES)}). Shows all apps if not specified.",
3943
),
44+
query: Optional[str] = typer.Option(
45+
None,
46+
"--query",
47+
"-q",
48+
help="Filter plugins by name or description",
49+
),
50+
category: Optional[str] = typer.Option(
51+
None,
52+
"--category",
53+
"-c",
54+
help="Filter plugins by category",
55+
),
56+
limit: int = typer.Option(
57+
50,
58+
"--limit",
59+
"-n",
60+
help="Maximum number of plugins to show",
61+
),
4062
):
41-
"""List installed/enabled plugins."""
63+
"""List installed and available plugins from configured marketplaces.
64+
65+
Without arguments: Shows installed plugins for all apps plus available plugins from all marketplaces.
66+
With marketplace name: Shows plugins from the specified marketplace.
67+
Use --query to search by name/description, --category to filter by category.
68+
"""
4269
from code_assistant_manager.plugins import VALID_APP_TYPES, get_handler
70+
from code_assistant_manager.plugins.fetch import fetch_repo_info
71+
from code_assistant_manager.cli.plugins.plugin_discovery_commands import (
72+
_filter_plugins,
73+
_display_plugin,
74+
_display_marketplace_header,
75+
_display_marketplace_footer,
76+
_resolve_marketplace_repo,
77+
_display_marketplace_not_found,
78+
)
79+
80+
# Handle marketplace-specific browsing (replaces browse functionality)
81+
if marketplace:
82+
from code_assistant_manager.cli.option_utils import resolve_single_app
83+
84+
app = resolve_single_app(app_type or "claude", VALID_APP_TYPES, default="claude")
85+
manager = PluginManager()
86+
handler = get_handler(app)
87+
88+
# Resolve marketplace to repo info
89+
repo_owner, repo_name, repo_branch = _resolve_marketplace_repo(
90+
manager, handler, marketplace
91+
)
4392

93+
if not repo_owner or not repo_name:
94+
_display_marketplace_not_found(manager, handler, marketplace)
95+
raise typer.Exit(1)
96+
97+
# Fetch plugins
98+
typer.echo(f"{Colors.CYAN}Fetching plugins from {marketplace}...{Colors.RESET}")
99+
info = fetch_repo_info(repo_owner, repo_name, repo_branch)
100+
101+
if not info or not info.plugins:
102+
typer.echo(f"{Colors.RED}✗ Could not fetch plugins from repo.{Colors.RESET}")
103+
raise typer.Exit(1)
104+
105+
# Filter and display
106+
plugins = _filter_plugins(info.plugins, query, category)
107+
total = len(plugins)
108+
plugins = plugins[:limit]
109+
110+
_display_marketplace_header(info, query, category, total)
111+
typer.echo(f"\n{Colors.BOLD}Plugins:{Colors.RESET}\n")
112+
113+
for plugin in plugins:
114+
_display_plugin(plugin)
115+
116+
_display_marketplace_footer(info, marketplace, total, limit)
117+
return
118+
119+
# Show both installed and available plugins (default behavior)
44120
if app_type:
45-
# Show plugins for specific app (original behavior)
121+
# Show plugins for specific app
46122
if app_type not in VALID_APP_TYPES:
47123
typer.echo(
48124
f"{Colors.RED}✗ Invalid app type: {app_type}. Valid: {', '.join(VALID_APP_TYPES)}{Colors.RESET}"
49125
)
50126
raise typer.Exit(1)
51127

52128
handler = get_handler(app_type)
53-
_show_app_plugins(app_type, handler, show_all)
129+
_show_app_plugins(app_type, handler, True, query, category, limit) # Always show available now
54130
else:
55-
# Show plugins for all apps
131+
# Show plugins for all apps plus available plugins
132+
manager = PluginManager()
133+
56134
typer.echo(f"{Colors.BOLD}Plugin Status Across All Apps:{Colors.RESET}\n")
57135

58136
apps_with_plugins = []
@@ -61,22 +139,18 @@ def list_plugins(
61139
enabled_plugins = handler.get_enabled_plugins()
62140
if enabled_plugins:
63141
apps_with_plugins.append(current_app)
64-
_show_app_plugins(current_app, handler, show_all, show_header=False)
142+
_show_app_plugins(current_app, handler, True, query, category, limit, show_header=False)
65143
typer.echo() # Add spacing between apps
66144

67145
if not apps_with_plugins:
68146
typer.echo(f"{Colors.YELLOW}No plugins installed in any app.{Colors.RESET}")
69147
typer.echo(f"Use 'cam plugin install <plugin>' to install one.")
70148

71-
# Show available built-in repos
72-
if BUILTIN_PLUGIN_REPOS:
73-
typer.echo(f"\n{Colors.CYAN}Available built-in plugins:{Colors.RESET}")
74-
for name, repo in BUILTIN_PLUGIN_REPOS.items():
75-
typer.echo(f" • {name}: {repo.description or 'No description'}")
76-
typer.echo(f"\nInstall with: cam plugin install <name>")
149+
# Show available plugins from all marketplaces
150+
_show_available_plugins(manager, query, category, limit)
77151

78152

79-
def _show_app_plugins(app_name: str, handler, show_all: bool, show_header: bool = True):
153+
def _show_app_plugins(app_name: str, handler, show_all: bool, query: Optional[str] = None, category: Optional[str] = None, limit: int = 50, show_header: bool = True):
80154
"""Show plugins for a specific app."""
81155
# Get enabled plugins from settings
82156
enabled_plugins = handler.get_enabled_plugins()
@@ -116,6 +190,16 @@ def _show_app_plugins(app_name: str, handler, show_all: bool, show_header: bool
116190
# Scan plugins from marketplaces
117191
plugins = handler.scan_marketplace_plugins()
118192
if plugins:
193+
# Apply filtering if specified
194+
if query or category:
195+
plugins = [p for p in plugins if
196+
(not query or query.lower() in p.name.lower() or
197+
(p.description and query.lower() in p.description.lower())) and
198+
(not category or category.lower() in (p.category or "").lower())]
199+
200+
# Apply limit
201+
plugins = plugins[:limit]
202+
119203
typer.echo(
120204
f"{Colors.BOLD}Available Plugins from Marketplaces ({app_name}):{Colors.RESET}\n"
121205
)
@@ -136,6 +220,103 @@ def _show_app_plugins(app_name: str, handler, show_all: bool, show_header: bool
136220
typer.echo()
137221

138222

223+
def _show_available_plugins(manager: PluginManager, query: Optional[str] = None, category: Optional[str] = None, limit: int = 50):
224+
"""Show available plugins from all configured marketplaces."""
225+
from code_assistant_manager.plugins.fetch import fetch_repo_info
226+
from code_assistant_manager.cli.plugins.plugin_discovery_commands import _filter_plugins, _display_plugin
227+
228+
all_repos = manager.get_all_repos()
229+
if not all_repos:
230+
return
231+
232+
typer.echo(f"{Colors.BOLD}Available Plugins from All Marketplaces:{Colors.RESET}")
233+
234+
all_plugins = []
235+
repo_sources = {} # Track which repo each plugin comes from
236+
237+
for repo_name, repo in all_repos.items():
238+
if not repo.repo_owner or not repo.repo_name:
239+
continue
240+
241+
# Fetch repo info
242+
info = fetch_repo_info(
243+
repo.repo_owner, repo.repo_name, repo.repo_branch or "main"
244+
)
245+
if not info:
246+
continue
247+
248+
if info.type == "marketplace":
249+
# Add plugins from marketplace with their source
250+
for plugin in info.plugins:
251+
plugin["marketplace"] = repo_name
252+
repo_sources[f"{plugin.get('name', '')}@{repo_name}"] = repo_name
253+
all_plugins.extend(info.plugins)
254+
else:
255+
# Single plugin repository
256+
plugin_name = info.name
257+
all_plugins.append(
258+
{
259+
"name": plugin_name,
260+
"version": info.version or "",
261+
"description": info.description or "",
262+
"category": "",
263+
"marketplace": repo_name,
264+
}
265+
)
266+
repo_sources[f"{plugin_name}@{repo_name}"] = repo_name
267+
268+
if not all_plugins:
269+
typer.echo(f" {Colors.YELLOW}No plugins found in configured repositories{Colors.RESET}")
270+
return
271+
272+
# Filter and display
273+
plugins = _filter_plugins(all_plugins, query, category)
274+
total = len(plugins)
275+
plugins = plugins[:limit]
276+
277+
if query or category:
278+
typer.echo(f" Showing {len(plugins)} of {total} matching plugins\n")
279+
else:
280+
typer.echo(f" Showing {len(plugins)} plugins\n")
281+
282+
# Organize plugins by marketplace
283+
plugins_by_marketplace = {}
284+
for plugin in plugins:
285+
marketplace_name = plugin.get("marketplace", "Unknown")
286+
if marketplace_name not in plugins_by_marketplace:
287+
plugins_by_marketplace[marketplace_name] = []
288+
plugins_by_marketplace[marketplace_name].append(plugin)
289+
290+
# Display plugins organized by marketplace
291+
displayed_count = 0
292+
for marketplace_name in sorted(plugins_by_marketplace.keys()):
293+
marketplace_plugins = plugins_by_marketplace[marketplace_name]
294+
typer.echo(f"{Colors.BOLD}{marketplace_name}:{Colors.RESET}")
295+
296+
for plugin in marketplace_plugins:
297+
if displayed_count >= limit:
298+
break
299+
_display_plugin(plugin)
300+
displayed_count += 1
301+
302+
if displayed_count >= limit:
303+
break
304+
305+
if total > limit:
306+
typer.echo(f"\n ... and {total - limit} more plugins")
307+
308+
categories = {p.get("category") for p in all_plugins if p.get("category")}
309+
if categories:
310+
typer.echo(
311+
f"\n{Colors.CYAN}Categories:{Colors.RESET} {', '.join(sorted(categories))}"
312+
)
313+
314+
typer.echo(
315+
f"\n{Colors.CYAN}Install with:{Colors.RESET} cam plugin install <marketplace>:<plugin-name>"
316+
)
317+
typer.echo()
318+
319+
139320
@plugin_app.command("repos")
140321
def list_repos():
141322
"""List available plugin repositories and marketplaces (built-in + user)."""

0 commit comments

Comments
 (0)