@@ -2,24 +2,163 @@ 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+ 		"Flow to execute plugin in: request, response (can be repeated)" ,
89+ 	)
90+ 	_  =  cobraCmd .MarkFlagRequired (flagFlow )
91+ 
92+ 	cobraCmd .Flags ().BoolVar (
93+ 		& c .required ,
94+ 		flagRequired ,
95+ 		false ,
96+ 		"Optional, mark plugin as required" ,
97+ 	)
98+ 
99+ 	cobraCmd .Flags ().StringVar (
100+ 		& c .commitHash ,
101+ 		flagCommitHash ,
102+ 		"" ,
103+ 		"Optional, commit hash for runtime version validation" ,
104+ 	)
105+ 
24106	return  cobraCmd , nil 
25107}
108+ 
109+ func  (c  * AddCmd ) run (cmd  * cobra.Command , args  []string ) error  {
110+ 	pluginName  :=  strings .TrimSpace (args [0 ])
111+ 	if  pluginName  ==  ""  {
112+ 		return  fmt .Errorf ("plugin name cannot be empty" )
113+ 	}
114+ 
115+ 	cfg , err  :=  c .LoadConfig (c .cfgLoader )
116+ 	if  err  !=  nil  {
117+ 		return  err 
118+ 	}
119+ 
120+ 	if  _ , exists  :=  cfg .Plugin (c .category , pluginName ); exists  {
121+ 		return  fmt .Errorf (
122+ 			"plugin '%s' already exists in category '%s'\n \n " + 
123+ 				"To update an existing plugin, use: mcpd config plugins set %s --category=%s [flags]" ,
124+ 			pluginName ,
125+ 			c .category ,
126+ 			pluginName ,
127+ 			c .category ,
128+ 		)
129+ 	}
130+ 
131+ 	flowsMap  :=  config .ParseFlows (c .flows )
132+ 	if  len (flowsMap ) ==  0  {
133+ 		return  fmt .Errorf ("at least one valid flow is required (must be 'request' or 'response')" )
134+ 	}
135+ 
136+ 	parsedFlows  :=  slices .Sorted (maps .Keys (flowsMap ))
137+ 
138+ 	entry  :=  config.PluginEntry {
139+ 		Name :  pluginName ,
140+ 		Flows : parsedFlows ,
141+ 	}
142+ 
143+ 	// Set optional fields only if they were provided. 
144+ 	if  cmd .Flags ().Changed (flagRequired ) {
145+ 		entry .Required  =  & c .required 
146+ 	}
147+ 
148+ 	if  c .commitHash  !=  ""  {
149+ 		entry .CommitHash  =  & c .commitHash 
150+ 	}
151+ 
152+ 	if  _ , err  :=  cfg .UpsertPlugin (c .category , entry ); err  !=  nil  {
153+ 		return  err 
154+ 	}
155+ 
156+ 	_ , _  =  fmt .Fprintf (
157+ 		cmd .OutOrStdout (),
158+ 		"✓ Plugin '%s' added to category '%s'\n " ,
159+ 		pluginName ,
160+ 		c .category ,
161+ 	)
162+ 
163+ 	return  nil 
164+ }
0 commit comments