@@ -2,24 +2,167 @@ package plugins
22
33import (
44 "fmt"
5+ "maps"
6+ "slices"
7+ "strings"
58
69 "github.com/spf13/cobra"
710
8- "github.com/mozilla-ai/mcpd/v2/internal/cmd"
9- "github.com/mozilla-ai/mcpd/v2/internal/cmd/options"
11+ internalcmd "github.com/mozilla-ai/mcpd/v2/internal/cmd"
12+ cmdopts "github.com/mozilla-ai/mcpd/v2/internal/cmd/options"
13+ "github.com/mozilla-ai/mcpd/v2/internal/config"
1014)
1115
12- // NewAddCmd creates the add command for plugins.
13- // TODO: Implement in a future PR.
14- func NewAddCmd (baseCmd * cmd.BaseCmd , opt ... options.CmdOption ) (* cobra.Command , error ) {
16+ const (
17+ flagFlow = "flow"
18+ flagRequired = "required"
19+ flagCommitHash = "commit-hash"
20+ )
21+
22+ // AddCmd represents the command for adding a new plugin entry.
23+ // NOTE: Use NewAddCmd to create instances of AddCmd.
24+ type AddCmd struct {
25+ * internalcmd.BaseCmd
26+
27+ // cfgLoader is used to load the configuration.
28+ cfgLoader config.Loader
29+
30+ // category is the category to add the plugin to.
31+ category config.Category
32+
33+ // flows is the list of flows.
34+ flows []string
35+
36+ // required indicates if the plugin is required.
37+ required bool
38+
39+ // commitHash is the optional commit hash for version validation.
40+ commitHash string
41+ }
42+
43+ // NewAddCmd creates a new add command for plugin entries.
44+ func NewAddCmd (baseCmd * internalcmd.BaseCmd , opt ... cmdopts.CmdOption ) (* cobra.Command , error ) {
45+ opts , err := cmdopts .NewOptions (opt ... )
46+ if err != nil {
47+ return nil , err
48+ }
49+
50+ c := & AddCmd {
51+ BaseCmd : baseCmd ,
52+ cfgLoader : opts .ConfigLoader ,
53+ }
54+
1555 cobraCmd := & cobra.Command {
16- Use : "add" ,
56+ Use : "add <plugin-name> " ,
1757 Short : "Add a new plugin entry to a category" ,
18- Long : "Add a new plugin entry to a category. The configuration is saved automatically." ,
19- RunE : func (cmd * cobra.Command , args []string ) error {
20- return fmt .Errorf ("not yet implemented" )
21- },
58+ Long : `Add a new plugin entry to a category. The configuration is saved automatically.
59+
60+ The plugin name must exactly match the name of the plugin binary file.
61+
62+ This command creates new plugin entries only. If a plugin with the same name already exists
63+ in the category, the command fails with an error. To update an existing plugin, use the 'set' command.` ,
64+ Example : ` # Add new plugin with all fields
65+ mcpd config plugins add jwt-auth --category=authentication --flow=request --required
66+
67+ # Add plugin with multiple flows
68+ mcpd config plugins add metrics --category=observability --flow=request --flow=response --commit-hash=abc123
69+
70+ # Add without required flag (defaults to false)
71+ mcpd config plugins add rbac --category=authorization --flow=response` ,
72+ RunE : c .run ,
73+ Args : cobra .ExactArgs (1 ), // plugin-name
2274 }
2375
76+ allowedCategories := config .OrderedCategories ()
77+ cobraCmd .Flags ().Var (
78+ & c .category ,
79+ flagCategory ,
80+ fmt .Sprintf ("Specify the category (one of: %s)" , allowedCategories .String ()),
81+ )
82+ _ = cobraCmd .MarkFlagRequired (flagCategory )
83+
84+ cobraCmd .Flags ().StringArrayVar (
85+ & c .flows ,
86+ flagFlow ,
87+ nil ,
88+ fmt .Sprintf (
89+ "Flow during which, the plugin should execute (%s) (can be repeated)" ,
90+ strings .Join (config .OrderedFlowNames (), ", " ),
91+ ),
92+ )
93+ _ = cobraCmd .MarkFlagRequired (flagFlow )
94+
95+ cobraCmd .Flags ().BoolVar (
96+ & c .required ,
97+ flagRequired ,
98+ false ,
99+ "Optional, mark plugin as required" ,
100+ )
101+
102+ cobraCmd .Flags ().StringVar (
103+ & c .commitHash ,
104+ flagCommitHash ,
105+ "" ,
106+ "Optional, commit hash for runtime version validation" ,
107+ )
108+
24109 return cobraCmd , nil
25110}
111+
112+ func (c * AddCmd ) run (cmd * cobra.Command , args []string ) error {
113+ pluginName := strings .TrimSpace (args [0 ])
114+ if pluginName == "" {
115+ return fmt .Errorf ("plugin name cannot be empty" )
116+ }
117+
118+ cfg , err := c .LoadConfig (c .cfgLoader )
119+ if err != nil {
120+ return err
121+ }
122+
123+ if _ , exists := cfg .Plugin (c .category , pluginName ); exists {
124+ return fmt .Errorf (
125+ "plugin '%s' already exists in category '%s'\n \n " +
126+ "To update an existing plugin, use: mcpd config plugins set %s --category=%s [flags]" ,
127+ pluginName ,
128+ c .category ,
129+ pluginName ,
130+ c .category ,
131+ )
132+ }
133+
134+ flows := config .ParseFlowsDistinct (c .flows )
135+ if len (flows ) == 0 {
136+ return fmt .Errorf (
137+ "at least one valid flow is required (%s)" ,
138+ strings .Join (config .OrderedFlowNames (), ", " ),
139+ )
140+ }
141+
142+ entry := config.PluginEntry {
143+ Name : pluginName ,
144+ Flows : slices .Sorted (maps .Keys (flows )),
145+ }
146+
147+ // Set optional fields only if they were provided.
148+ if cmd .Flags ().Changed (flagRequired ) {
149+ entry .Required = & c .required
150+ }
151+
152+ if c .commitHash != "" {
153+ entry .CommitHash = & c .commitHash
154+ }
155+
156+ if _ , err := cfg .UpsertPlugin (c .category , entry ); err != nil {
157+ return err
158+ }
159+
160+ _ , _ = fmt .Fprintf (
161+ cmd .OutOrStdout (),
162+ "✓ Plugin '%s' added to category '%s'\n " ,
163+ pluginName ,
164+ c .category ,
165+ )
166+
167+ return nil
168+ }
0 commit comments