delete added, more details below (#3)

- readme updates
- delete command added
- commands now saved in a map vs fields
- login and run now ensure config has been run to prevent errors

Reviewed-on: #3
Co-authored-by: jake <jake.young.dev@gmail.com>
Co-committed-by: jake <jake.young.dev@gmail.com>
This commit is contained in:
jake 2025-04-17 21:25:54 +00:00 committed by jake
parent a9c6400761
commit f8a9528e0f
8 changed files with 121 additions and 17 deletions

View File

@ -10,7 +10,7 @@ go install code.jakeyoungdev.com/jake/mctl@main #it is recommended to use a tagg
## Setup ## Setup
### Configuring mctl ### 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 ```bash
mctl config -s <serveraddress> -p <rconport> mctl config -s <serveraddress> -p <rconport>
``` ```
@ -65,6 +65,12 @@ mctl run test jake
#will run: tp jake 0 0 0 on remote server #will run: tp jake 0 0 0 on remote server
``` ```
### Delete saved command
Commands can be deleted with:
```bash
mctl delete <name>
```
## Documentation ## Documentation
### Commands ### Commands
|Command|Description| |Command|Description|
@ -73,6 +79,7 @@ mctl run test jake
|login|makes connection request to the server using saved configuration and enters command loop| |login|makes connection request to the server using saved configuration and enters command loop|
|save \<name>|saves specific command for reuse| |save \<name>|saves specific command for reuse|
|view \<name>|displays saved command| |view \<name>|displays saved command|
|delete \<name>|deletes saved command|
|run \<name> args...|runs saved command filling placeholders with supplied args| |run \<name> args...|runs saved command filling placeholders with supplied args|
### Flags ### Flags

View File

@ -86,6 +86,11 @@ func initConfig() {
_, err := rand.Read(uu) _, err := rand.Read(uu)
cobra.CheckErr(err) 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.Set("device", string(uu))
viper.SafeWriteConfig() viper.SafeWriteConfig()
} }

42
cmd/delete.go Normal file
View File

@ -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 <name>",
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)
}

View File

@ -5,6 +5,7 @@ package cmd
import ( import (
"bufio" "bufio"
"errors"
"fmt" "fmt"
"os" "os"
@ -15,9 +16,10 @@ import (
// loginCmd represents the login command // loginCmd represents the login command
var loginCmd = &cobra.Command{ var loginCmd = &cobra.Command{
Use: "login", Use: "login",
Example: "mctl login", Example: "mctl login",
Short: "Login to server and send commands", SilenceUsage: true,
Short: "Login to server and send commands",
Long: `Login to server using saved config and enter command loop Long: `Login to server using saved config and enter command loop
sending commands to server and printing the response.`, sending commands to server and printing the response.`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
@ -54,6 +56,13 @@ var loginCmd = &cobra.Command{
fmt.Printf("Disconnected from %s\n", server) 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() { func init() {

View File

@ -28,6 +28,5 @@ func Execute() {
} }
func init() { func init() {
//dont show completion subcommand in help message, makes the syntax confusing
rootCmd.Root().CompletionOptions.DisableDefaultCmd = true
} }

View File

@ -15,14 +15,28 @@ import (
// runCmd represents the run command // runCmd represents the run command
var runCmd = &cobra.Command{ var runCmd = &cobra.Command{
Use: "run <name> args...", Use: "run <name> args...",
Example: "mctl run savedcmd 63 jake", Example: "mctl run savedcmd 63 jake",
Short: "Runs a previously saved command with supplied arguments on remote server", 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 Long: `Loads a saved command, injects the supplied arguments into the command, and sends the command to the remove server
printing the response`, printing the response`,
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
//grab saved command //check for command map
cmdName := viper.Get(fmt.Sprintf("customCmd-%s", args[0])).(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)
}
//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 //convert arguments to interface
var nargs []any var nargs []any
for _, a := range args[1:] { for _, a := range args[1:] {
@ -30,7 +44,7 @@ var runCmd = &cobra.Command{
} }
//inject arguments //inject arguments
fixed := fmt.Sprintf(cmdName, nargs...) fixed := fmt.Sprintf(cmdRun.(string), nargs...)
fmt.Printf("Running saved command %s\n", fixed) fmt.Printf("Running saved command %s\n", fixed)
//create client and send command //create client and send command
@ -44,13 +58,20 @@ var runCmd = &cobra.Command{
fmt.Println(res) fmt.Println(res)
}, },
PreRunE: func(cmd *cobra.Command, args []string) error { 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 //ensure we have a command name
al := len(args) al := len(args)
if al == 0 { if al == 0 {
return errors.New("name argument is required") 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 //make sure enough arguments are sent to fill command placeholders
if al < count+1 { if al < count+1 {
return fmt.Errorf("not enough arguments to populate command. Supplied: %d, Needed: %d", al-1, count) return fmt.Errorf("not enough arguments to populate command. Supplied: %d, Needed: %d", al-1, count)

View File

@ -27,10 +27,17 @@ var saveCmd = &cobra.Command{
if sc.Scan() { if sc.Scan() {
txt := sc.Text() txt := sc.Text()
if txt != "" { 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() viper.WriteConfig()
fmt.Println("\nSaved!") fmt.Println("\nSaved!")
return
} }
} }
}, },

View File

@ -18,7 +18,21 @@ var viewCmd = &cobra.Command{
Short: "View saved commands", Short: "View saved commands",
Long: `Load command using the supplied name and displays it in the terminal`, Long: `Load command using the supplied name and displays it in the terminal`,
Run: func(cmd *cobra.Command, args []string) { 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 { PreRunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 { if len(args) == 0 {