diff --git a/README.md b/README.md index 39564fd..68c0d1e 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ go install code.jakeyoungdev.com/jake/mctl@main #it is recommended to use a tagg ## Setup ### Configuring mctl -mctl requires a one-time setup via the 'config' command, password is entered securely from the terminal +mctl requires a one-time setup via the 'config' command before interacting with any servers, password is entered securely from the terminal ```bash mctl config -s -p ``` @@ -65,6 +65,12 @@ mctl run test jake #will run: tp jake 0 0 0 on remote server ``` +### Delete saved command +Commands can be deleted with: +```bash +mctl delete +``` + ## Documentation ### Commands |Command|Description| @@ -73,6 +79,7 @@ mctl run test jake |login|makes connection request to the server using saved configuration and enters command loop| |save \|saves specific command for reuse| |view \|displays saved command| +|delete \|deletes saved command| |run \ args...|runs saved command filling placeholders with supplied args| ### Flags diff --git a/cmd/config.go b/cmd/config.go index 7657368..4e1cec4 100644 --- a/cmd/config.go +++ b/cmd/config.go @@ -86,6 +86,11 @@ func initConfig() { _, err := rand.Read(uu) cobra.CheckErr(err) + //create custom command map + cmdMap := make(map[string]any, 0) + + //write config + viper.Set("customcmd", cmdMap) viper.Set("device", string(uu)) viper.SafeWriteConfig() } diff --git a/cmd/delete.go b/cmd/delete.go new file mode 100644 index 0000000..d0116cb --- /dev/null +++ b/cmd/delete.go @@ -0,0 +1,42 @@ +/* +Copyright © 2025 Jake jake.young.dev@gmail.com +*/ +package cmd + +import ( + "errors" + + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// deleteCmd represents the delete command +var deleteCmd = &cobra.Command{ + Use: "delete ", + Example: "mctl delete newcmd", + Short: "Delete a saved command", + Long: `Deletes a command stored using the save command`, + Run: func(cmd *cobra.Command, args []string) { + var cm map[string]any + cmdMap := viper.Get("customcmd") + if cmdMap == nil { + cm = make(map[string]any, 0) + } else { + cm = cmdMap.(map[string]any) + } + delete(cm, args[0]) + viper.Set("customcmd", cmdMap) + viper.WriteConfig() + }, + PreRunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return errors.New("name argument is required") + } + + return nil + }, +} + +func init() { + rootCmd.AddCommand(deleteCmd) +} diff --git a/cmd/login.go b/cmd/login.go index a0a5e97..6221594 100644 --- a/cmd/login.go +++ b/cmd/login.go @@ -5,6 +5,7 @@ package cmd import ( "bufio" + "errors" "fmt" "os" @@ -15,9 +16,10 @@ import ( // loginCmd represents the login command var loginCmd = &cobra.Command{ - Use: "login", - Example: "mctl login", - Short: "Login to server and send commands", + Use: "login", + Example: "mctl login", + SilenceUsage: true, + Short: "Login to server and send commands", Long: `Login to server using saved config and enter command loop sending commands to server and printing the response.`, Run: func(cmd *cobra.Command, args []string) { @@ -54,6 +56,13 @@ var loginCmd = &cobra.Command{ fmt.Printf("Disconnected from %s\n", server) }, + PreRunE: func(cmd *cobra.Command, args []string) error { + //ensure config command has been run + if viper.Get("server") == "" || viper.Get("password") == "" || viper.Get("port") == 0 { + return errors.New("the 'config' command must be run before you can interact with servers") + } + return nil + }, } func init() { diff --git a/cmd/root.go b/cmd/root.go index fa5d4f3..f0ceee9 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -28,6 +28,5 @@ func Execute() { } func init() { - //dont show completion subcommand in help message, makes the syntax confusing - rootCmd.Root().CompletionOptions.DisableDefaultCmd = true + } diff --git a/cmd/run.go b/cmd/run.go index 5c84158..3cbae72 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -15,14 +15,28 @@ import ( // runCmd represents the run command var runCmd = &cobra.Command{ - Use: "run args...", - Example: "mctl run savedcmd 63 jake", - Short: "Runs a previously saved command with supplied arguments on remote server", + Use: "run args...", + Example: "mctl run savedcmd 63 jake", + SilenceUsage: true, + Short: "Runs a previously saved command with supplied arguments on remote server", Long: `Loads a saved command, injects the supplied arguments into the command, and sends the command to the remove server printing the response`, Run: func(cmd *cobra.Command, args []string) { - //grab saved command - cmdName := viper.Get(fmt.Sprintf("customCmd-%s", args[0])).(string) + //check for command map + var cm map[string]any + cmdMap := viper.Get("customcmd") + if cmdMap == nil { + cm = make(map[string]any, 0) + } else { + cm = cmdMap.(map[string]any) + } + //is this an existing command + cmdRun, ok := cm[args[0]] + if !ok { + fmt.Printf("command %s not found", args[0]) + return + } + //convert arguments to interface var nargs []any for _, a := range args[1:] { @@ -30,7 +44,7 @@ var runCmd = &cobra.Command{ } //inject arguments - fixed := fmt.Sprintf(cmdName, nargs...) + fixed := fmt.Sprintf(cmdRun.(string), nargs...) fmt.Printf("Running saved command %s\n", fixed) //create client and send command @@ -44,13 +58,20 @@ var runCmd = &cobra.Command{ fmt.Println(res) }, PreRunE: func(cmd *cobra.Command, args []string) error { + //ensure configuration has been setup + if viper.Get("server") == "" || viper.Get("password") == "" || viper.Get("port") == 0 { + return errors.New("the 'config' command must be run before you can interact with servers") + } + //ensure we have a command name al := len(args) if al == 0 { return errors.New("name argument is required") } - cmdCheck := viper.Get(fmt.Sprintf("customCmd-%s", args[0])) - count := strings.Count(cmdCheck.(string), "%s") + + cmdMap := viper.Get("customcmd").(map[string]any) + count := strings.Count(cmdMap[args[0]].(string), "%s") + //make sure enough arguments are sent to fill command placeholders if al < count+1 { return fmt.Errorf("not enough arguments to populate command. Supplied: %d, Needed: %d", al-1, count) diff --git a/cmd/save.go b/cmd/save.go index 24fff42..1b034a2 100644 --- a/cmd/save.go +++ b/cmd/save.go @@ -27,10 +27,17 @@ var saveCmd = &cobra.Command{ if sc.Scan() { txt := sc.Text() if txt != "" { - viper.Set(fmt.Sprintf("customCmd-%s", args[0]), txt) + var cmdMap map[string]any + cm := viper.Get("customcmd") + if cmdMap == nil { + cmdMap = make(map[string]any, 0) + } else { + cmdMap = cm.(map[string]any) + } + cmdMap[args[0]] = txt + viper.Set("customcmd", cmdMap) viper.WriteConfig() fmt.Println("\nSaved!") - return } } }, diff --git a/cmd/view.go b/cmd/view.go index 260f8d4..dbe73f8 100644 --- a/cmd/view.go +++ b/cmd/view.go @@ -18,7 +18,21 @@ var viewCmd = &cobra.Command{ Short: "View saved commands", Long: `Load command using the supplied name and displays it in the terminal`, Run: func(cmd *cobra.Command, args []string) { - fmt.Printf("\nCommand: %s\n", viper.Get(args[0])) + var cm map[string]any + cmdMap := viper.Get("customcmd") + if cmdMap == nil { + fmt.Println("no custom commands found") + return + } + + cm = cmdMap.(map[string]any) + custom, ok := cm[args[0]] + if !ok { + fmt.Println("command not found") + return + } + + fmt.Printf("Command: %s", custom.(string)) }, PreRunE: func(cmd *cobra.Command, args []string) error { if len(args) == 0 {