Skip to content

Commit b7789b6

Browse files
committed
feat(plugins): add fallback to settings for enable/disable/uninstall
When Claude CLI commands fail (common for marketplace plugins), fall back to directly editing ~/.claude/settings.json: - uninstall: removes plugin from enabledPlugins - disable: sets plugin to false in enabledPlugins - enable: sets plugin to true in enabledPlugins This handles marketplace plugins that Claude CLI can't manage directly.
1 parent dda838c commit b7789b6

1 file changed

Lines changed: 159 additions & 7 deletions

File tree

code_assistant_manager/cli/plugin_commands.py

Lines changed: 159 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,11 @@ def uninstall_plugin(
252252
plugin: str = typer.Argument(..., help="Plugin name to uninstall"),
253253
force: bool = typer.Option(False, "--force", "-f", help="Skip confirmation"),
254254
):
255-
"""Uninstall an installed plugin."""
255+
"""Uninstall an installed plugin.
256+
257+
For marketplace plugins, this removes the plugin from enabled plugins settings.
258+
For standalone plugins, this uses Claude CLI to fully uninstall.
259+
"""
256260
_check_claude_cli()
257261
handler = _get_handler()
258262

@@ -268,8 +272,128 @@ def uninstall_plugin(
268272
f"\n{Colors.YELLOW}Note: Restart Claude Code to apply changes.{Colors.RESET}"
269273
)
270274
else:
271-
typer.echo(f"{Colors.RED}{msg}{Colors.RESET}")
272-
raise typer.Exit(1)
275+
# Claude CLI failed - try to remove from settings directly
276+
# This handles marketplace plugins which can't be "uninstalled" via CLI
277+
typer.echo(
278+
f"{Colors.YELLOW}Claude CLI uninstall failed, trying to remove from settings...{Colors.RESET}"
279+
)
280+
281+
removed = _remove_plugin_from_settings(handler, plugin)
282+
if removed:
283+
typer.echo(
284+
f"{Colors.GREEN}✓ Removed '{plugin}' from enabled plugins{Colors.RESET}"
285+
)
286+
typer.echo(
287+
f"\n{Colors.YELLOW}Note: Restart Claude Code to apply changes.{Colors.RESET}"
288+
)
289+
else:
290+
typer.echo(
291+
f"{Colors.RED}✗ Plugin '{plugin}' not found in settings{Colors.RESET}"
292+
)
293+
raise typer.Exit(1)
294+
295+
296+
def _remove_plugin_from_settings(handler, plugin: str) -> bool:
297+
"""Remove a plugin from Claude's settings.json.
298+
299+
Args:
300+
handler: Claude plugin handler
301+
plugin: Plugin name (with or without @marketplace suffix)
302+
303+
Returns:
304+
True if plugin was found and removed, False otherwise
305+
"""
306+
import json
307+
308+
settings_file = handler.settings_file
309+
if not settings_file.exists():
310+
return False
311+
312+
try:
313+
with open(settings_file, "r") as f:
314+
settings = json.load(f)
315+
except Exception:
316+
return False
317+
318+
enabled = settings.get("enabledPlugins", {})
319+
if not enabled:
320+
return False
321+
322+
# Find matching plugin key(s)
323+
keys_to_remove = []
324+
plugin_lower = plugin.lower()
325+
for key in enabled:
326+
# Match exact key or plugin name part (before @)
327+
key_name = key.split("@")[0] if "@" in key else key
328+
if key.lower() == plugin_lower or key_name.lower() == plugin_lower:
329+
keys_to_remove.append(key)
330+
331+
if not keys_to_remove:
332+
return False
333+
334+
# Remove the plugin(s)
335+
for key in keys_to_remove:
336+
del enabled[key]
337+
338+
settings["enabledPlugins"] = enabled
339+
340+
# Write back
341+
try:
342+
with open(settings_file, "w") as f:
343+
json.dump(settings, f, indent=2)
344+
return True
345+
except Exception:
346+
return False
347+
348+
349+
def _set_plugin_enabled(handler, plugin: str, enabled: bool) -> bool:
350+
"""Set a plugin's enabled state in Claude's settings.json.
351+
352+
Args:
353+
handler: Claude plugin handler
354+
plugin: Plugin name (with or without @marketplace suffix)
355+
enabled: True to enable, False to disable
356+
357+
Returns:
358+
True if plugin was found and updated, False otherwise
359+
"""
360+
import json
361+
362+
settings_file = handler.settings_file
363+
if not settings_file.exists():
364+
return False
365+
366+
try:
367+
with open(settings_file, "r") as f:
368+
settings = json.load(f)
369+
except Exception:
370+
return False
371+
372+
enabled_plugins = settings.get("enabledPlugins", {})
373+
374+
# Find matching plugin key
375+
plugin_lower = plugin.lower()
376+
matching_key = None
377+
for key in enabled_plugins:
378+
key_name = key.split("@")[0] if "@" in key else key
379+
if key.lower() == plugin_lower or key_name.lower() == plugin_lower:
380+
matching_key = key
381+
break
382+
383+
if not matching_key:
384+
return False
385+
386+
# Update the enabled state
387+
enabled_plugins[matching_key] = enabled
388+
settings["enabledPlugins"] = enabled_plugins
389+
390+
# Write back
391+
try:
392+
with open(settings_file, "w") as f:
393+
json.dump(settings, f, indent=2)
394+
return True
395+
except Exception:
396+
return False
273397

274398

275399
@plugin_app.command("enable")
@@ -289,8 +413,22 @@ def enable_plugin(
289413
f"\n{Colors.YELLOW}Note: Restart Claude Code to apply changes.{Colors.RESET}"
290414
)
291415
else:
292-
typer.echo(f"{Colors.RED}{msg}{Colors.RESET}")
293-
raise typer.Exit(1)
416+
# Claude CLI failed - try to enable in settings directly
417+
typer.echo(
418+
f"{Colors.YELLOW}Claude CLI enable failed, trying to update settings...{Colors.RESET}"
419+
)
420+
421+
enabled = _set_plugin_enabled(handler, plugin, True)
422+
if enabled:
423+
typer.echo(f"{Colors.GREEN}✓ Enabled '{plugin}' in settings{Colors.RESET}")
424+
typer.echo(
425+
f"\n{Colors.YELLOW}Note: Restart Claude Code to apply changes.{Colors.RESET}"
426+
)
427+
else:
428+
typer.echo(
429+
f"{Colors.RED}✗ Plugin '{plugin}' not found in settings{Colors.RESET}"
430+
)
431+
raise typer.Exit(1)
294432

295433

296434
@plugin_app.command("disable")
@@ -310,8 +448,22 @@ def disable_plugin(
310448
f"\n{Colors.YELLOW}Note: Restart Claude Code to apply changes.{Colors.RESET}"
311449
)
312450
else:
313-
typer.echo(f"{Colors.RED}{msg}{Colors.RESET}")
314-
raise typer.Exit(1)
451+
# Claude CLI failed - try to disable in settings directly
452+
typer.echo(
453+
f"{Colors.YELLOW}Claude CLI disable failed, trying to update settings...{Colors.RESET}"
454+
)
455+
456+
disabled = _set_plugin_enabled(handler, plugin, False)
457+
if disabled:
458+
typer.echo(f"{Colors.GREEN}✓ Disabled '{plugin}' in settings{Colors.RESET}")
459+
typer.echo(
460+
f"\n{Colors.YELLOW}Note: Restart Claude Code to apply changes.{Colors.RESET}"
461+
)
462+
else:
463+
typer.echo(
464+
f"{Colors.RED}✗ Plugin '{plugin}' not found in settings{Colors.RESET}"
465+
)
466+
raise typer.Exit(1)
315467

316468

317469
@plugin_app.command("validate")

0 commit comments

Comments
 (0)