Commit bda978f

bryfry <bryon@fryer.io>
2025-09-17 13:13:23
cb and doc gif
1 parent 29e2bcb
cmd/callbacks.go
@@ -5,12 +5,25 @@ import (
 	"fmt"
 	"mysh/pkg/mythic"
 	"os"
+	"sort"
+	"strings"
 	"text/tabwriter"
 	"time"
 
 	"github.com/spf13/cobra"
 )
 
+var (
+	sortByID          bool
+	sortByType        bool
+	sortByHost        bool
+	sortByUser        bool
+	sortByProcess     bool
+	sortByLast        bool
+	sortByDescription bool
+	sortReverse       bool
+)
+
 var callbacksCmd = &cobra.Command{
 	Use:     "callbacks",
 	Aliases: []string{"cb", "callback"},
@@ -21,6 +34,18 @@ var callbacksCmd = &cobra.Command{
 
 func init() {
 	rootCmd.AddCommand(callbacksCmd)
+
+	// Sort flags - mutually exclusive
+	callbacksCmd.Flags().BoolVar(&sortByID, "id", false, "Sort by callback ID (default)")
+	callbacksCmd.Flags().BoolVar(&sortByType, "type", false, "Sort by agent type")
+	callbacksCmd.Flags().BoolVar(&sortByHost, "host", false, "Sort by hostname")
+	callbacksCmd.Flags().BoolVar(&sortByUser, "user", false, "Sort by username")
+	callbacksCmd.Flags().BoolVar(&sortByProcess, "process", false, "Sort by process name")
+	callbacksCmd.Flags().BoolVar(&sortByLast, "last", false, "Sort by last checkin time")
+	callbacksCmd.Flags().BoolVar(&sortByDescription, "description", false, "Sort by description")
+
+	// Reverse sort flag
+	callbacksCmd.Flags().BoolVar(&sortReverse, "rev", false, "Reverse sort order")
 }
 
 // formatTimeSince formats a timestamp into a human-readable "time ago" format
@@ -79,6 +104,75 @@ func formatTimeSince(timestampStr string) string {
 	}
 }
 
+// getSortField determines which field to sort by, returns "id" as default
+func getSortField() string {
+	if sortByType {
+		return "type"
+	}
+	if sortByHost {
+		return "host"
+	}
+	if sortByUser {
+		return "user"
+	}
+	if sortByProcess {
+		return "process"
+	}
+	if sortByLast {
+		return "last"
+	}
+	if sortByDescription {
+		return "description"
+	}
+	// Default to ID sort (newest/highest last)
+	return "id"
+}
+
+// sortCallbacks sorts callbacks based on the specified field
+func sortCallbacks(callbacks []mythic.Callback, sortField string, reverse bool) {
+	sort.Slice(callbacks, func(i, j int) bool {
+		var result bool
+
+		switch sortField {
+		case "type":
+			typeI := callbacks[i].Payload.PayloadType.Name
+			typeJ := callbacks[j].Payload.PayloadType.Name
+			if typeI == "" {
+				typeI = "unknown"
+			}
+			if typeJ == "" {
+				typeJ = "unknown"
+			}
+			result = strings.ToLower(typeI) < strings.ToLower(typeJ)
+		case "host":
+			result = strings.ToLower(callbacks[i].Host) < strings.ToLower(callbacks[j].Host)
+		case "user":
+			result = strings.ToLower(callbacks[i].User) < strings.ToLower(callbacks[j].User)
+		case "process":
+			result = strings.ToLower(callbacks[i].ProcessName) < strings.ToLower(callbacks[j].ProcessName)
+		case "last":
+			// Parse timestamps for comparison
+			timeI, errI := time.Parse(time.RFC3339, callbacks[i].LastCheckin)
+			timeJ, errJ := time.Parse(time.RFC3339, callbacks[j].LastCheckin)
+			if errI != nil || errJ != nil {
+				// Fallback to string comparison if parsing fails
+				result = callbacks[i].LastCheckin < callbacks[j].LastCheckin
+			} else {
+				result = timeI.Before(timeJ)
+			}
+		case "description":
+			result = strings.ToLower(callbacks[i].Description) < strings.ToLower(callbacks[j].Description)
+		default: // "id"
+			result = callbacks[i].DisplayID < callbacks[j].DisplayID
+		}
+
+		if reverse {
+			return !result
+		}
+		return result
+	})
+}
+
 func runCallbacks(cmd *cobra.Command, args []string) error {
 	if err := validateConfig(); err != nil {
 		return err
@@ -98,6 +192,17 @@ func runCallbacks(cmd *cobra.Command, args []string) error {
 		return nil
 	}
 
+	// Sort callbacks based on flags
+	sortField := getSortField()
+	// For ID sort, default to newest (highest) last, so reverse=true by default
+	// For other sorts, use the --rev flag
+	shouldReverse := sortReverse
+	if sortField == "id" && !sortReverse {
+		shouldReverse = true // Default for ID is newest last (reverse order)
+	}
+
+	sortCallbacks(callbacks, sortField, shouldReverse)
+
 	// Create a tab writer for formatted output
 	w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
 	fmt.Fprintln(w, "ID\tTYPE\tHOST\tUSER\tPROCESS\tPID\tLAST CHECKIN\tDESCRIPTION")
mysh.gif
Binary file
README.md
@@ -1,5 +1,7 @@
 # mysh
 
+![demo](./mysh.gif)
+
 `mysh` (mɪ-ʃh) is a command-line interface for Mythic C2 servers.
 
 ## Installation
TODO.md
@@ -6,7 +6,9 @@
 - ✅ task [callback] arg make it accept lists, comma seperated, or ranges e.g. 1,2,3-5,10
 - ✅ make cb and callback aliases for callbacks
 - ✅ update callbacks to show last-checkin as 0s or 0m00s ago
-- make tv --raw have short -r
+- ✅ make tv --raw have short -r
+- ✅ make tv have more task details in the header
+- ✅ make tv have a -d --details to only show the header
 - make callbacks sortable with (--rev reverse) --id, --type, --host, --user, --process, --last, --description all exclusive - default should be --id with newest (highest) last
 - create a .cache/mysh/ to store task result cached values for completed tasks - use whatever the right XDG env default dir should be 
 - invert cobra-cli nto functions (no global vars)