Commit 2fc7b4a

bryfry <bryon@fryer.io>
2025-09-17 12:34:40
callbacks
- ✅ make cb and callback aliases for callbacks - ✅ update callbacks to show last-checkin as 0s or 0m00s ago
1 parent d77163d
Changed files (2)
cmd/callbacks.go
@@ -12,16 +12,73 @@ import (
 )
 
 var callbacksCmd = &cobra.Command{
-	Use:   "callbacks",
-	Short: "List active callbacks",
-	Long:  "Display all active callbacks from the Mythic server, equivalent to the Active Callbacks view in the UI.",
-	RunE:  runCallbacks,
+	Use:     "callbacks",
+	Aliases: []string{"cb", "callback"},
+	Short:   "List active callbacks",
+	Long:    "Display all active callbacks from the Mythic server, equivalent to the Active Callbacks view in the UI.",
+	RunE:    runCallbacks,
 }
 
 func init() {
 	rootCmd.AddCommand(callbacksCmd)
 }
 
+// formatTimeSince formats a timestamp into a human-readable "time ago" format
+// like "0s ago", "1m 30s ago", "2h 15m ago", etc.
+func formatTimeSince(timestampStr string) string {
+	if timestampStr == "" {
+		return "unknown"
+	}
+
+	// Parse the timestamp - try common formats
+	var parsedTime time.Time
+	var err error
+
+	// Try RFC3339 format first (most common)
+	parsedTime, err = time.Parse(time.RFC3339, timestampStr)
+	if err != nil {
+		// Try RFC3339Nano format
+		parsedTime, err = time.Parse(time.RFC3339Nano, timestampStr)
+		if err != nil {
+			// Try other common formats
+			parsedTime, err = time.Parse("2006-01-02T15:04:05", timestampStr)
+			if err != nil {
+				// If all parsing fails, return the original string
+				return timestampStr
+			}
+		}
+	}
+
+	duration := time.Since(parsedTime)
+
+	// Format based on duration
+	if duration < time.Minute {
+		seconds := int(duration.Seconds())
+		return fmt.Sprintf("%ds ago", seconds)
+	} else if duration < time.Hour {
+		minutes := int(duration.Minutes())
+		seconds := int(duration.Seconds()) % 60
+		if seconds == 0 {
+			return fmt.Sprintf("%dm ago", minutes)
+		}
+		return fmt.Sprintf("%dm %02ds ago", minutes, seconds)
+	} else if duration < 24*time.Hour {
+		hours := int(duration.Hours())
+		minutes := int(duration.Minutes()) % 60
+		if minutes == 0 {
+			return fmt.Sprintf("%dh ago", hours)
+		}
+		return fmt.Sprintf("%dh %02dm ago", hours, minutes)
+	} else {
+		days := int(duration.Hours() / 24)
+		hours := int(duration.Hours()) % 24
+		if hours == 0 {
+			return fmt.Sprintf("%dd ago", days)
+		}
+		return fmt.Sprintf("%dd %02dh ago", days, hours)
+	}
+}
+
 func runCallbacks(cmd *cobra.Command, args []string) error {
 	if err := validateConfig(); err != nil {
 		return err
@@ -52,6 +109,8 @@ func runCallbacks(cmd *cobra.Command, args []string) error {
 			agentType = "unknown"
 		}
 
+		lastCheckin := formatTimeSince(callback.LastCheckin)
+
 		fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\t%d\t%s\t%s\n",
 			callback.DisplayID,
 			agentType,
@@ -59,7 +118,7 @@ func runCallbacks(cmd *cobra.Command, args []string) error {
 			callback.User,
 			callback.ProcessName,
 			callback.PID,
-			callback.LastCheckin,
+			lastCheckin,
 			callback.Description,
 		)
 	}
TODO.md
@@ -4,10 +4,12 @@
 - ✅ task-list without callback lists all tasks and shows callback column
 - ✅ task with --no-wait, doesn't wait for output
 - ✅ 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
+- ✅ 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 callbacks sortable with (--rev reverse) --id, --type, --host, --user, --process, --last, --description all exclusive - default should be --id
 - invert cobra-cli nto functions (no global vars)
 - update output table formats to respect terminal width
-- create a .cache/mysh/ for task result cached values
+- create a .cache/mysh/ to store task result cached values for completed tasks - use whatever the right XDG env default dir should be 
 - MYTHIC_API_INSECURE= boolean --insecure flag - and invert default = false
 - forge payloads - equivalency with execute_assembly and inline_assembly