11package cmd
22
33import (
4+ "errors"
45 "fmt"
56 "os"
67 "path/filepath"
@@ -10,13 +11,15 @@ import (
1011 uicli "github.com/alperdrsnn/clime"
1112 "github.com/git-hulk/clime/internal/installer"
1213 "github.com/git-hulk/clime/internal/plugin"
14+ "github.com/git-hulk/clime/internal/prompt"
1315 "github.com/spf13/cobra"
1416)
1517
1618var (
17- pluginInstall plugin.Plugin
18- pluginUpdateRepo string
19- pluginUpdateForce bool
19+ pluginInstall plugin.Plugin
20+ pluginUpdateRepo string
21+ pluginUpdateForce bool
22+ pluginInstallRunner = executePluginInstall
2023)
2124
2225func init () {
@@ -149,17 +152,25 @@ func pluginListColumns(p plugin.DiscoveredPlugin, manifest *plugin.Manifest, hom
149152}
150153
151154var pluginInstallCmd = & cobra.Command {
152- Use : "install < name> " ,
155+ Use : "install [ name] " ,
153156 Short : "Install a plugin from GitHub Releases, npm, Homebrew, or an install script" ,
154- Long : "Downloads and installs a plugin. By default , looks for git-hulk/clime-<name> on GitHub. Use --npm to install from an npm package , --brew to install from a Homebrew formula , or --script to run a remote install script ." ,
155- Args : cobra .ExactArgs (1 ),
157+ Long : "Downloads and installs a plugin. Run without arguments for an interactive wizard. With a name argument , looks for git-hulk/clime-<name> on GitHub by default . Use --npm, --brew, --repo , or --script to specify an alternative source ." ,
158+ Args : cobra .MaximumNArgs (1 ),
156159 ValidArgsFunction : func (cmd * cobra.Command , args []string , toComplete string ) ([]string , cobra.ShellCompDirective ) {
157160 if len (args ) != 0 {
158161 return nil , cobra .ShellCompDirectiveNoFileComp
159162 }
160163 return nil , cobra .ShellCompDirectiveNoFileComp
161164 },
162165 RunE : func (cmd * cobra.Command , args []string ) error {
166+ if len (args ) == 0 {
167+ if pluginInstall .Npm != "" || pluginInstall .Brew != "" ||
168+ pluginInstall .Repo != "" || pluginInstall .Script != "" {
169+ return fmt .Errorf ("plugin name argument is required when using --npm, --brew, --repo, or --script flags" )
170+ }
171+ return runInteractivePluginInstall ()
172+ }
173+
163174 name := args [0 ]
164175
165176 sources := 0
@@ -187,34 +198,147 @@ var pluginInstallCmd = &cobra.Command{
187198 return err
188199 }
189200
190- inst , err := installer .FromPlugin (pluginInstall )
201+ pluginInstall .Name = name
202+ return pluginInstallRunner (manifest , name , pluginInstall )
203+ },
204+ }
205+
206+ func runInteractivePluginInstall () error {
207+ // Step 1: Enter plugin name.
208+ fmt .Println ()
209+ name , err := inputPrompt ("Enter plugin name" )
210+ if err != nil {
211+ return err
212+ }
213+ name = strings .TrimSpace (name )
214+ if name == "" {
215+ return fmt .Errorf ("plugin name cannot be empty" )
216+ }
217+
218+ manifest , err := plugin .LoadManifest ()
219+ if err != nil {
220+ manifest = & plugin.Manifest {}
221+ }
222+ if err := ensureInstallNameAvailable (manifest , name ); err != nil {
223+ return err
224+ }
225+
226+ // Step 2: Select install type.
227+ installTypes := []string {
228+ "Script (curl | sh)" ,
229+ "npm (global package)" ,
230+ "Homebrew (formula)" ,
231+ "GitHub Release" ,
232+ }
233+
234+ fmt .Println ()
235+ typeIdx , err := selectPrompt (prompt.SelectConfig {
236+ Label : "Select install type" ,
237+ Options : installTypes ,
238+ })
239+ if err != nil {
240+ if errors .Is (err , prompt .ErrBack ) {
241+ terminal .Info ("Installation cancelled." )
242+ return nil
243+ }
244+ return err
245+ }
246+
247+ // Step 3: Collect source details based on install type.
248+ var p plugin.Plugin
249+ p .Name = name
250+
251+ fmt .Println ()
252+ switch typeIdx {
253+ case 0 : // Script
254+ url , err := inputPrompt ("Enter install script URL" )
191255 if err != nil {
192256 return err
193257 }
258+ url = strings .TrimSpace (url )
259+ if url == "" {
260+ return fmt .Errorf ("script URL cannot be empty" )
261+ }
262+ p .Script = url
194263
195- spinner := uicli . NewSpinner ().
196- WithStyle ( uicli . SpinnerDots ).
197- WithColor ( uicli . CyanColor ).
198- WithMessage ( fmt . Sprintf ( "Installing plugin %q..." , name )).
199- Start ( )
264+ binPath , err := inputPrompt ( "Enter binary path after install (leave empty to auto-detect)" )
265+ if err != nil {
266+ return err
267+ }
268+ p . BinaryPath = strings . TrimSpace ( binPath )
200269
201- version , err := inst .Install (name )
270+ case 1 : // npm
271+ pkg , err := inputPrompt ("Enter npm package name" )
202272 if err != nil {
203- spinner .Error (fmt .Sprintf ("Failed to install plugin %q" , name ))
204- return fmt .Errorf ("failed to install plugin %q: %w" , name , err )
273+ return err
205274 }
275+ pkg = strings .TrimSpace (pkg )
276+ if pkg == "" {
277+ return fmt .Errorf ("npm package name cannot be empty" )
278+ }
279+ p .Npm = pkg
206280
207- manifest .Add (name , version , inst .PluginType (), inst .Source (), "" )
208- if pluginInstall .Description != "" {
209- manifest .SetDescription (name , pluginInstall .Description )
281+ case 2 : // Homebrew
282+ formula , err := inputPrompt ("Enter Homebrew formula" )
283+ if err != nil {
284+ return err
210285 }
211- if err := manifest .Save (); err != nil {
212- return fmt .Errorf ("plugin installed but failed to update manifest: %w" , err )
286+ formula = strings .TrimSpace (formula )
287+ if formula == "" {
288+ return fmt .Errorf ("Homebrew formula cannot be empty" )
213289 }
290+ p .Brew = formula
214291
215- spinner .Success (fmt .Sprintf ("Installed plugin %q (%s)" , name , version ))
216- return nil
217- },
292+ case 3 : // GitHub Release
293+ repo , err := inputPrompt ("Enter GitHub repository (owner/repo)" )
294+ if err != nil {
295+ return err
296+ }
297+ repo = strings .TrimSpace (repo )
298+ if repo == "" {
299+ return fmt .Errorf ("GitHub repository cannot be empty" )
300+ }
301+ p .Repo = repo
302+ }
303+
304+ // Step 4: Optional description.
305+ desc , err := inputPrompt ("Enter description (leave empty to skip)" )
306+ if err != nil {
307+ return err
308+ }
309+ p .Description = strings .TrimSpace (desc )
310+
311+ return pluginInstallRunner (manifest , name , p )
312+ }
313+
314+ func executePluginInstall (manifest * plugin.Manifest , name string , p plugin.Plugin ) error {
315+ inst , err := installer .FromPlugin (p )
316+ if err != nil {
317+ return err
318+ }
319+
320+ spinner := uicli .NewSpinner ().
321+ WithStyle (uicli .SpinnerDots ).
322+ WithColor (uicli .CyanColor ).
323+ WithMessage (fmt .Sprintf ("Installing plugin %q..." , name )).
324+ Start ()
325+
326+ version , err := inst .Install (name )
327+ if err != nil {
328+ spinner .Error (fmt .Sprintf ("Failed to install plugin %q" , name ))
329+ return fmt .Errorf ("failed to install plugin %q: %w" , name , err )
330+ }
331+
332+ manifest .Add (name , version , inst .PluginType (), inst .Source (), p .BinaryPath )
333+ if p .Description != "" {
334+ manifest .SetDescription (name , p .Description )
335+ }
336+ if err := manifest .Save (); err != nil {
337+ return fmt .Errorf ("plugin installed but failed to update manifest: %w" , err )
338+ }
339+
340+ spinner .Success (fmt .Sprintf ("Installed plugin %q (%s)" , name , version ))
341+ return nil
218342}
219343
220344var pluginUninstallCmd = & cobra.Command {
0 commit comments