Skip to content

Commit da9ed6f

Browse files
zhujian0805claude
andcommitted
feat: enhance plugin command structure to match CodeBuddy/Claude design
- Add marketplace subcommands (add, list, remove, update) matching CodeBuddy/Claude CLI structure - Update marketplace commands to work across all apps by default (--app flag for specific app) - Enhance plugin list to show which app each plugin is installed for - Fix repository type classification (compounding-engineering and superpowers-dev are marketplaces, not plugins) - Improve marketplace list to show all CAM configured marketplaces with descriptions and sources 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent bd72bd5 commit da9ed6f

File tree

4 files changed

+285
-41
lines changed

4 files changed

+285
-41
lines changed

code_assistant_manager/cli/plugin_commands.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@
2222
no_args_is_help=True,
2323
)
2424

25-
# Register commands directly with the main app
25+
# Register sub-apps as subcommands
26+
plugin_app.add_typer(plugin_marketplace_commands.marketplace_app, name="marketplace")
27+
28+
# Register individual commands
2629
# Management commands
2730
plugin_app.command("list")(plugin_management_commands.list_plugins)
2831
plugin_app.command("repos")(plugin_management_commands.list_repos)
@@ -41,6 +44,3 @@
4144
plugin_app.command("view")(plugin_discovery_commands.view_plugin)
4245
plugin_app.command("fetch")(plugin_discovery_commands.fetch_repo)
4346
plugin_app.command("status")(plugin_discovery_commands.plugin_status)
44-
45-
# Marketplace commands
46-
plugin_app.command("marketplace")(plugin_marketplace_commands.marketplace_update)

code_assistant_manager/cli/plugins/plugin_management_commands.py

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,39 +31,69 @@ def list_plugins(
3131
"--all",
3232
help="Show all plugins from marketplaces (not just enabled)",
3333
),
34-
app_type: str = typer.Option(
35-
"claude",
34+
app_type: Optional[str] = typer.Option(
35+
None,
3636
"--app",
3737
"-a",
38-
help=f"App type ({', '.join(VALID_APP_TYPES)})",
38+
help=f"App type to show plugins for ({', '.join(VALID_APP_TYPES)}). Shows all apps if not specified.",
3939
),
4040
):
4141
"""List installed/enabled plugins."""
42-
from code_assistant_manager.cli.option_utils import resolve_single_app
43-
from code_assistant_manager.plugins import get_handler
42+
from code_assistant_manager.plugins import VALID_APP_TYPES, get_handler
4443

45-
app = resolve_single_app(app_type, VALID_APP_TYPES, default="claude")
46-
handler = get_handler(app)
44+
if app_type:
45+
# Show plugins for specific app (original behavior)
46+
if app_type not in VALID_APP_TYPES:
47+
typer.echo(
48+
f"{Colors.RED}✗ Invalid app type: {app_type}. Valid: {', '.join(VALID_APP_TYPES)}{Colors.RESET}"
49+
)
50+
raise typer.Exit(1)
4751

52+
handler = get_handler(app_type)
53+
_show_app_plugins(app_type, handler, show_all)
54+
else:
55+
# Show plugins for all apps
56+
typer.echo(f"{Colors.BOLD}Plugin Status Across All Apps:{Colors.RESET}\n")
57+
58+
apps_with_plugins = []
59+
for current_app in VALID_APP_TYPES:
60+
handler = get_handler(current_app)
61+
enabled_plugins = handler.get_enabled_plugins()
62+
if enabled_plugins:
63+
apps_with_plugins.append(current_app)
64+
_show_app_plugins(current_app, handler, show_all, show_header=False)
65+
typer.echo() # Add spacing between apps
66+
67+
if not apps_with_plugins:
68+
typer.echo(f"{Colors.YELLOW}No plugins installed in any app.{Colors.RESET}")
69+
typer.echo(f"Use 'cam plugin install <plugin>' to install one.")
70+
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>")
77+
78+
79+
def _show_app_plugins(app_name: str, handler, show_all: bool, show_header: bool = True):
80+
"""Show plugins for a specific app."""
4881
# Get enabled plugins from settings
4982
enabled_plugins = handler.get_enabled_plugins()
5083

84+
if show_header:
85+
typer.echo(f"{Colors.BOLD}{app_name.capitalize()} Plugins:{Colors.RESET}\n")
86+
5187
if not enabled_plugins and not show_all:
5288
typer.echo(
53-
f"{Colors.YELLOW}No plugins installed. "
54-
f"Use 'cam plugin install <plugin>' to install one.{Colors.RESET}"
89+
f"{Colors.YELLOW}No plugins installed for {app_name}. "
90+
f"Use 'cam plugin install <plugin> --app {app_name}' to install one.{Colors.RESET}"
5591
)
56-
57-
# Show available built-in repos
58-
if BUILTIN_PLUGIN_REPOS:
59-
typer.echo(f"\n{Colors.CYAN}Available built-in plugins:{Colors.RESET}")
60-
for name, repo in BUILTIN_PLUGIN_REPOS.items():
61-
typer.echo(f" • {name}: {repo.description or 'No description'}")
62-
typer.echo(f"\nInstall with: cam plugin install <name>")
6392
return
6493

6594
if enabled_plugins:
66-
typer.echo(f"\n{Colors.BOLD}Enabled Plugins:{Colors.RESET}\n")
95+
if not show_header:
96+
typer.echo(f"{Colors.BOLD}{app_name.capitalize()}:{Colors.RESET}")
6797
for plugin_key, enabled in sorted(enabled_plugins.items()):
6898
# Extract plugin name from key (format: owner/repo:name or name@marketplace)
6999
if ":" in plugin_key:
@@ -87,7 +117,7 @@ def list_plugins(
87117
plugins = handler.scan_marketplace_plugins()
88118
if plugins:
89119
typer.echo(
90-
f"\n{Colors.BOLD}Available Plugins from Marketplaces:{Colors.RESET}\n"
120+
f"{Colors.BOLD}Available Plugins from Marketplaces ({app_name}):{Colors.RESET}\n"
91121
)
92122
for plugin in sorted(plugins, key=lambda p: p.name):
93123
if plugin.installed:

code_assistant_manager/cli/plugins/plugin_marketplace_commands.py

Lines changed: 231 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@
2929
)
3030

3131

32-
@marketplace_app.command("update")
33-
def marketplace_update(
34-
name: Optional[str] = typer.Argument(
35-
None,
36-
help="Marketplace name to update (updates all if not specified)",
32+
@marketplace_app.command("add")
33+
def marketplace_add(
34+
source: str = typer.Argument(
35+
...,
36+
help="Marketplace source (URL, path, or GitHub repo)",
3737
),
3838
app_type: str = typer.Option(
3939
"claude",
@@ -42,27 +42,133 @@ def marketplace_update(
4242
help=f"App type ({', '.join(VALID_APP_TYPES)})",
4343
),
4444
):
45-
"""Update installed marketplace(s) from their source.
45+
"""Add a marketplace from a URL, path, or GitHub repo."""
46+
from code_assistant_manager.cli.option_utils import resolve_single_app
4647

47-
This command updates installed marketplaces by pulling the latest changes
48-
from their source repositories. If no name is specified, all marketplaces
49-
are updated.
48+
app = resolve_single_app(app_type, VALID_APP_TYPES, default="claude")
49+
handler = get_handler(app)
5050

51-
Examples:
52-
cam plugin marketplace update # Update all marketplaces
53-
cam plugin marketplace update my-marketplace # Update specific marketplace
51+
typer.echo(f"{Colors.CYAN}Adding marketplace: {source}...{Colors.RESET}")
52+
success, msg = handler.marketplace_add(source)
53+
54+
if success:
55+
typer.echo(f"{Colors.GREEN}{msg}{Colors.RESET}")
56+
else:
57+
typer.echo(f"{Colors.RED}{msg}{Colors.RESET}")
58+
raise typer.Exit(1)
59+
60+
61+
@marketplace_app.command("list")
62+
def marketplace_list(
63+
app_type: str = typer.Option(
64+
"claude",
65+
"--app",
66+
"-a",
67+
help=f"App type ({', '.join(VALID_APP_TYPES)})",
68+
),
69+
show_installed: bool = typer.Option(
70+
False,
71+
"--installed",
72+
help="Show only marketplaces installed in the app (not configured in CAM)",
73+
),
74+
):
75+
"""List all configured marketplaces.
76+
77+
By default, shows marketplaces configured in CAM. Use --installed to show
78+
marketplaces actually installed in the target app.
5479
"""
5580
from code_assistant_manager.cli.option_utils import resolve_single_app
81+
from code_assistant_manager.plugins import PluginManager
5682

5783
app = resolve_single_app(app_type, VALID_APP_TYPES, default="claude")
58-
handler = get_handler(app)
5984

60-
if name:
61-
typer.echo(f"{Colors.CYAN}Updating marketplace: {name}...{Colors.RESET}")
85+
if show_installed:
86+
# Show marketplaces installed in the app
87+
handler = get_handler(app)
88+
success, output = handler.marketplace_list()
89+
if success:
90+
typer.echo(output)
91+
else:
92+
typer.echo(f"{Colors.RED}{output}{Colors.RESET}")
93+
raise typer.Exit(1)
6294
else:
63-
typer.echo(f"{Colors.CYAN}Updating all marketplaces...{Colors.RESET}")
95+
# Show CAM configured marketplaces
96+
manager = PluginManager()
97+
all_repos = manager.get_all_repos()
98+
configured_marketplaces = {
99+
k: v for k, v in all_repos.items() if v.type == "marketplace"
100+
}
101+
102+
if not configured_marketplaces:
103+
typer.echo(
104+
f"{Colors.YELLOW}No marketplaces configured in CAM.{Colors.RESET}"
105+
)
106+
typer.echo(
107+
f"Use 'cam plugin add-repo --type marketplace <owner>/<repo>' to add one."
108+
)
109+
return
110+
111+
typer.echo("Configured marketplaces:")
112+
typer.echo()
113+
114+
for name, repo in sorted(configured_marketplaces.items()):
115+
# Show status indicator
116+
status = (
117+
f"{Colors.GREEN}{Colors.RESET}"
118+
if repo.enabled
119+
else f"{Colors.RED}{Colors.RESET}"
120+
)
121+
typer.echo(f" {status} {name}")
64122

65-
success, msg = handler.marketplace_update(name)
123+
# Show aliases if any
124+
if hasattr(repo, "aliases") and repo.aliases:
125+
aliases_str = ", ".join(repo.aliases)
126+
typer.echo(f" Aliases: {aliases_str}")
127+
128+
# Show description
129+
if repo.description:
130+
typer.echo(f" {repo.description}")
131+
132+
# Show source
133+
if repo.repo_owner and repo.repo_name:
134+
source_url = (
135+
f"https://github.com/{repo.repo_owner}/{repo.repo_name}.git"
136+
)
137+
typer.echo(f" Source: Git ({source_url})")
138+
else:
139+
typer.echo(
140+
f" Source: {Colors.YELLOW}No GitHub source configured{Colors.RESET}"
141+
)
142+
143+
typer.echo()
144+
145+
146+
@marketplace_app.command("remove")
147+
@marketplace_app.command("rm", hidden=True)
148+
def marketplace_remove(
149+
name: str = typer.Argument(
150+
...,
151+
help="Marketplace name to remove",
152+
),
153+
force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation"),
154+
app_type: str = typer.Option(
155+
"claude",
156+
"--app",
157+
"-a",
158+
help=f"App type ({', '.join(VALID_APP_TYPES)})",
159+
),
160+
):
161+
"""Remove a configured marketplace."""
162+
from code_assistant_manager.cli.option_utils import resolve_single_app
163+
164+
app = resolve_single_app(app_type, VALID_APP_TYPES, default="claude")
165+
handler = get_handler(app)
166+
167+
if not force:
168+
typer.confirm(f"Remove marketplace '{name}'?", abort=True)
169+
170+
typer.echo(f"{Colors.CYAN}Removing marketplace: {name}...{Colors.RESET}")
171+
success, msg = handler.marketplace_remove(name)
66172

67173
if success:
68174
typer.echo(f"{Colors.GREEN}{msg}{Colors.RESET}")
@@ -71,6 +177,114 @@ def marketplace_update(
71177
raise typer.Exit(1)
72178

73179

180+
@marketplace_app.command("update")
181+
def marketplace_update(
182+
name: Optional[str] = typer.Argument(
183+
None,
184+
help="Marketplace name to update (updates all if not specified)",
185+
),
186+
app_type: Optional[str] = typer.Option(
187+
None,
188+
"--app",
189+
"-a",
190+
help=f"App type to update marketplaces for ({', '.join(VALID_APP_TYPES)}). Updates all apps if not specified.",
191+
),
192+
):
193+
"""Update installed marketplace(s) from their source.
194+
195+
This command updates installed marketplaces by pulling the latest changes
196+
from their source repositories. If no name is specified, all marketplaces
197+
are updated. If no app is specified, marketplaces are updated for all apps.
198+
199+
Examples:
200+
cam plugin marketplace update # Update all marketplaces for all apps
201+
cam plugin marketplace update my-marketplace # Update specific marketplace for all apps
202+
cam plugin marketplace update --app claude # Update all marketplaces for Claude
203+
cam plugin marketplace update my-marketplace --app claude # Update specific marketplace for Claude
204+
"""
205+
from code_assistant_manager.plugins import VALID_APP_TYPES, get_handler
206+
207+
if app_type:
208+
# Update for specific app
209+
if app_type not in VALID_APP_TYPES:
210+
typer.echo(
211+
f"{Colors.RED}✗ Invalid app type: {app_type}. Valid: {', '.join(VALID_APP_TYPES)}{Colors.RESET}"
212+
)
213+
raise typer.Exit(1)
214+
215+
handler = get_handler(app_type)
216+
217+
if name:
218+
typer.echo(
219+
f"{Colors.CYAN}Updating marketplace '{name}' for {app_type}...{Colors.RESET}"
220+
)
221+
else:
222+
typer.echo(
223+
f"{Colors.CYAN}Updating all marketplaces for {app_type}...{Colors.RESET}"
224+
)
225+
226+
success, msg = handler.marketplace_update(name)
227+
228+
if success:
229+
typer.echo(f"{Colors.GREEN}{msg}{Colors.RESET}")
230+
else:
231+
typer.echo(f"{Colors.RED}{msg}{Colors.RESET}")
232+
raise typer.Exit(1)
233+
else:
234+
# Update for all apps
235+
if name:
236+
typer.echo(
237+
f"{Colors.CYAN}Updating marketplace '{name}' for all apps...{Colors.RESET}"
238+
)
239+
else:
240+
typer.echo(
241+
f"{Colors.CYAN}Updating all marketplaces for all apps...{Colors.RESET}"
242+
)
243+
244+
all_success = True
245+
results = []
246+
247+
for current_app in VALID_APP_TYPES:
248+
try:
249+
handler = get_handler(current_app)
250+
251+
if name:
252+
typer.echo(
253+
f" {Colors.CYAN}Updating '{name}' for {current_app}...{Colors.RESET}"
254+
)
255+
else:
256+
typer.echo(
257+
f" {Colors.CYAN}Updating all marketplaces for {current_app}...{Colors.RESET}"
258+
)
259+
260+
success, msg = handler.marketplace_update(name)
261+
262+
if success:
263+
typer.echo(f" {Colors.GREEN}{current_app}: {msg}{Colors.RESET}")
264+
results.append(f"{current_app}: {msg}")
265+
else:
266+
typer.echo(f" {Colors.RED}{current_app}: {msg}{Colors.RESET}")
267+
results.append(f"{current_app}: {msg}")
268+
all_success = False
269+
270+
except Exception as e:
271+
error_msg = f"Failed to update {current_app}: {e}"
272+
typer.echo(f" {Colors.RED}{error_msg}{Colors.RESET}")
273+
results.append(error_msg)
274+
all_success = False
275+
276+
if all_success:
277+
typer.echo(
278+
f"\n{Colors.GREEN}✓ All marketplace updates completed successfully{Colors.RESET}"
279+
)
280+
else:
281+
typer.echo(
282+
f"\n{Colors.YELLOW}⚠ Some marketplace updates failed{Colors.RESET}"
283+
)
284+
if not all_success:
285+
raise typer.Exit(1)
286+
287+
74288
# Add marketplace subcommand to plugin app
75289
plugin_app.add_typer(marketplace_app, name="marketplace")
76290

0 commit comments

Comments
 (0)