11# -*- coding: utf-8 -*-
2- """Location: ./cforge/commands/resources/plugins.py
3- Copyright 2025
2+ """
43SPDX-License-Identifier: Apache-2.0
5- Authors: Matthew Grigsby
64
75CLI command group: plugins
86
1614"""
1715
1816# Standard
19- from enum import Enum
2017from typing import Any , Dict , Optional
2118
2219# Third-Party
2320import typer
2421
2522# First-Party
26- from cforge .common import (
27- AuthenticationError ,
28- CLIError ,
29- get_console ,
30- handle_exception ,
31- make_authenticated_request ,
32- print_json ,
33- print_table ,
34- )
35-
36-
37- class _CaseInsensitiveEnum (str , Enum ):
38- """Enum that supports case-insensitive parsing for CLI options."""
39-
40- @classmethod
41- def _missing_ (cls , value : object ) -> Optional ["_CaseInsensitiveEnum" ]:
42- """Resolve unknown values by matching enum values case-insensitively.
43-
44- Typer converts CLI strings into Enum members. Implementing `_missing_`
45- allows `--mode EnFoRcE` to resolve to `PluginMode.ENFORCE`, while still
46- rejecting unknown values.
47- """
48- if not isinstance (value , str ):
49- return None
50- value_folded = value .casefold ()
51- for member in cls :
52- if member .value .casefold () == value_folded :
53- return member
54- return None
23+ from cforge .common .console import get_console
24+ from cforge .common .errors import AuthenticationError , CaseInsensitiveEnum , CLIError , handle_exception
25+ from cforge .common .http import make_authenticated_request
26+ from cforge .common .render import print_json , print_table
5527
5628
57- class PluginMode (_CaseInsensitiveEnum ):
29+ class PluginMode (CaseInsensitiveEnum ):
5830 """Valid plugin mode filters supported by the gateway admin API."""
5931
6032 ENFORCE = "enforce"
6133 PERMISSIVE = "permissive"
6234 DISABLED = "disabled"
6335
6436
65- def _handle_plugins_exception (exception : Exception ) -> None :
37+ def _parse_plugin_mode (mode : Optional [str ]) -> Optional [PluginMode ]:
38+ """Parse plugin mode with case-insensitive enum matching."""
39+ if mode is None :
40+ return None
41+ try :
42+ return PluginMode (mode )
43+ except ValueError as exc :
44+ choices = ", " .join (member .value for member in PluginMode )
45+ raise CLIError (f"Invalid value for '--mode': { mode !r} . Must be one of: { choices } ." ) from exc
46+
47+
48+ def _handle_plugins_exception (exception : Exception , operation : str , plugin_name : Optional [str ] = None ) -> None :
6649 """Provide plugin-specific hints and raise a CLI error."""
6750 console = get_console ()
6851
6952 if isinstance (exception , AuthenticationError ):
7053 console .print ("[yellow]Access denied. Requires admin.plugins permission.[/yellow]" )
71- elif isinstance (exception , CLIError ) and "(404)" in str (exception ):
72- console .print ("[yellow]Admin plugin API unavailable. Ensure MCPGATEWAY_ADMIN_API_ENABLED=true and gateway version supports /admin/plugins.[/yellow]" )
54+ elif isinstance (exception , CLIError ):
55+ error_str = str (exception )
56+ if "(404)" in error_str :
57+ error_str_folded = error_str .casefold ()
58+ if operation == "get" and "plugin" in error_str_folded and "not found" in error_str_folded :
59+ plugin_label = plugin_name or "requested plugin"
60+ console .print (f"[yellow]Plugin not found: { plugin_label } [/yellow]" )
61+ else :
62+ console .print ("[yellow]Admin plugin API unavailable. Ensure MCPGATEWAY_ADMIN_API_ENABLED=true and gateway version supports /admin/plugins.[/yellow]" )
7363
7464 handle_exception (exception )
7565
7666
7767def plugins_list (
7868 search : Optional [str ] = typer .Option (None , "--search" , help = "Search by plugin name, description, or author" ),
79- mode : Optional [PluginMode ] = typer .Option (None , "--mode" , help = "Filter by mode" ),
69+ mode : Optional [str ] = typer .Option (None , "--mode" , help = "Filter by mode" ),
8070 hook : Optional [str ] = typer .Option (None , "--hook" , help = "Filter by hook type" ),
8171 tag : Optional [str ] = typer .Option (None , "--tag" , help = "Filter by plugin tag" ),
8272 json_output : bool = typer .Option (False , "--json" , help = "Output as JSON" ),
@@ -88,8 +78,9 @@ def plugins_list(
8878 params : Dict [str , Any ] = {}
8979 if search :
9080 params ["search" ] = search
91- if mode :
92- params ["mode" ] = mode .value
81+ parsed_mode = _parse_plugin_mode (mode )
82+ if parsed_mode :
83+ params ["mode" ] = parsed_mode .value
9384 if hook :
9485 params ["hook" ] = hook
9586 if tag :
@@ -108,7 +99,7 @@ def plugins_list(
10899 console .print ("[yellow]No plugins found[/yellow]" )
109100
110101 except Exception as e :
111- _handle_plugins_exception (e )
102+ _handle_plugins_exception (e , operation = "list" )
112103
113104
114105def plugins_get (
@@ -120,7 +111,7 @@ def plugins_get(
120111 print_json (result , f"Plugin { name } " )
121112
122113 except Exception as e :
123- _handle_plugins_exception (e )
114+ _handle_plugins_exception (e , operation = "get" , plugin_name = name )
124115
125116
126117def plugins_stats () -> None :
@@ -130,4 +121,4 @@ def plugins_stats() -> None:
130121 print_json (result , "Plugin Statistics" )
131122
132123 except Exception as e :
133- _handle_plugins_exception (e )
124+ _handle_plugins_exception (e , operation = "stats" )
0 commit comments