Commit 0af402d

bryfry <bryon@fryer.io>
2025-09-17 18:04:50
update table styles
1 parent 75a52f4
cmd/callbacks.go
@@ -7,9 +7,9 @@ import (
 	"os"
 	"sort"
 	"strings"
-	"text/tabwriter"
 	"time"
 
+	"github.com/jedib0t/go-pretty/v6/table"
 	"github.com/spf13/cobra"
 )
 
@@ -203,10 +203,37 @@ func runCallbacks(cmd *cobra.Command, args []string) error {
 
 	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")
-	fmt.Fprintln(w, "--\t----\t----\t----\t-------\t---\t------------\t-----------")
+	// Create a table for formatted output
+	t := table.NewWriter()
+	t.SetOutputMirror(os.Stdout)
+
+	// Configure table styling - header borders only
+	t.SetStyle(table.Style{
+		Name: "HeaderOnlyBorders",
+		Box:  table.StyleBoxDefault,
+		Options: table.Options{
+			DrawBorder:      false, // No outer border
+			SeparateColumns: false, // No column separators
+			SeparateFooter:  false, // No footer separator
+			SeparateHeader:  true,  // Keep header separator
+			SeparateRows:    false, // No row separators
+		},
+	})
+
+	// Define columns with priorities (1=highest, higher numbers can be dropped)
+	columns := []ColumnInfo{
+		{Header: "ID", Number: 1, MinWidth: 3, MaxWidth: 6, Priority: 1},         // Always show
+		{Header: "TYPE", Number: 2, MinWidth: 6, MaxWidth: 10, Priority: 3},      // High priority
+		{Header: "HOST", Number: 3, MinWidth: 8, MaxWidth: 16, Priority: 2},      // Very important
+		{Header: "USER", Number: 4, MinWidth: 6, MaxWidth: 12, Priority: 4},      // Important
+		{Header: "PROCESS", Number: 5, MinWidth: 8, MaxWidth: 12, Priority: 6},   // Can drop
+		{Header: "PID", Number: 6, MinWidth: 3, MaxWidth: 6, Priority: 7},        // Can drop
+		{Header: "LAST CHECKIN", Number: 7, MinWidth: 8, MaxWidth: 10, Priority: 5},
+		{Header: "DESCRIPTION", Number: 8, MinWidth: 10, MaxWidth: 25, Priority: 8}, // First to drop
+	}
+
+	// Configure table for current terminal width
+	dt := configureTableForTerminal(t, columns)
 
 	for _, callback := range callbacks {
 		agentType := callback.Payload.PayloadType.Name
@@ -216,7 +243,8 @@ func runCallbacks(cmd *cobra.Command, args []string) error {
 
 		lastCheckin := formatTimeSince(callback.LastCheckin)
 
-		fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\t%d\t%s\t%s\n",
+		// Prepare all column data
+		allData := []interface{}{
 			callback.DisplayID,
 			agentType,
 			callback.Host,
@@ -225,9 +253,12 @@ func runCallbacks(cmd *cobra.Command, args []string) error {
 			callback.PID,
 			lastCheckin,
 			callback.Description,
-		)
+		}
+
+		// Add row with only visible columns
+		dt.AppendRowForColumns(allData)
 	}
 
-	w.Flush()
+	t.Render()
 	return nil
 }
\ No newline at end of file
cmd/files.go
@@ -10,24 +10,25 @@ import (
 	"regexp"
 	"strconv"
 	"strings"
-	"text/tabwriter"
 	"time"
 
+	"github.com/jedib0t/go-pretty/v6/table"
 	"github.com/spf13/cobra"
 )
 
 var (
-	outputDir    string
-	showPayloads bool
-	showDownloads bool
+	outputDir       string
+	showPayloads    bool
+	showDownloads   bool
 	showScreenshots bool
+	showUploads     bool
 )
 
 var filesCmd = &cobra.Command{
 	Use:     "files",
 	Aliases: []string{"file"},
 	Short:   "Manage files",
-	Long:    "List and download files (payloads, downloads, screenshots) from Mythic. Defaults to listing all files if no subcommand is provided.",
+	Long:    "List and download files (payloads, downloads, uploads, screenshots) from Mythic. Defaults to listing all files if no subcommand is provided.",
 	RunE:    runFilesList, // Default to list command
 }
 
@@ -48,6 +49,7 @@ func init() {
 	filesCmd.Flags().BoolVarP(&showPayloads, "payload", "p", false, "Show only payload files")
 	filesCmd.Flags().BoolVarP(&showDownloads, "downloads", "d", false, "Show only downloaded files")
 	filesCmd.Flags().BoolVarP(&showScreenshots, "screenshots", "s", false, "Show only screenshot files")
+	filesCmd.Flags().BoolVarP(&showUploads, "uploads", "u", false, "Show only uploaded files (assemblies, etc)")
 
 	filesDownloadCmd.Flags().StringVarP(&outputDir, "output", "o", ".", "Output directory for downloaded files")
 }
@@ -67,12 +69,13 @@ func runFilesList(cmd *cobra.Command, args []string) error {
 	}
 
 	// Apply filters if any are specified
-	if showPayloads || showDownloads || showScreenshots {
+	if showPayloads || showDownloads || showScreenshots || showUploads {
 		var filteredFiles []mythic.File
 		for _, file := range files {
 			if (showPayloads && file.IsPayload) ||
 				(showDownloads && file.IsDownloadFromAgent && !file.IsPayload && !file.IsScreenshot) ||
-				(showScreenshots && file.IsScreenshot) {
+				(showScreenshots && file.IsScreenshot) ||
+				(showUploads && !file.IsDownloadFromAgent && !file.IsPayload && !file.IsScreenshot) {
 				filteredFiles = append(filteredFiles, file)
 			}
 		}
@@ -86,7 +89,7 @@ func runFilesList(cmd *cobra.Command, args []string) error {
 
 	// Determine filter description
 	filterDesc := ""
-	if showPayloads || showDownloads || showScreenshots {
+	if showPayloads || showDownloads || showScreenshots || showUploads {
 		var filters []string
 		if showPayloads {
 			filters = append(filters, "payloads")
@@ -97,15 +100,46 @@ func runFilesList(cmd *cobra.Command, args []string) error {
 		if showScreenshots {
 			filters = append(filters, "screenshots")
 		}
+		if showUploads {
+			filters = append(filters, "uploads")
+		}
 		filterDesc = fmt.Sprintf(" (%s)", strings.Join(filters, ", "))
 	}
 
 	fmt.Printf("Files%s (%d total):\n\n", filterDesc, len(files))
 
-	// Create a tab writer for formatted output
-	w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
-	fmt.Fprintln(w, "UUID\tTYPE\tFILENAME\tREMOTE PATH\tHOST\tSIZE\tCOMPLETE\tTASK\tTIMESTAMP")
-	fmt.Fprintln(w, "----\t----\t--------\t-----------\t----\t----\t--------\t----\t---------")
+	// Create a table for formatted output
+	t := table.NewWriter()
+	t.SetOutputMirror(os.Stdout)
+
+	// Configure table styling - header borders only
+	t.SetStyle(table.Style{
+		Name: "HeaderOnlyBorders",
+		Box:  table.StyleBoxDefault,
+		Options: table.Options{
+			DrawBorder:      false, // No outer border
+			SeparateColumns: false, // No column separators
+			SeparateFooter:  false, // No footer separator
+			SeparateHeader:  true,  // Keep header separator
+			SeparateRows:    false, // No row separators
+		},
+	})
+
+	// Define columns with priorities (1=highest, higher numbers can be dropped)
+	columns := []ColumnInfo{
+		{Header: "UUID", Number: 1, MinWidth: 36, MaxWidth: 36, Priority: 1}, // Always show full UUID
+		{Header: "TYPE", Number: 2, MinWidth: 6, MaxWidth: 8, Priority: 2},   // High priority
+		{Header: "FILENAME", Number: 3, MinWidth: 8, MaxWidth: 16, Priority: 3}, // Important
+		{Header: "REMOTE PATH", Number: 4, MinWidth: 10, MaxWidth: 20, Priority: 7}, // Lower priority
+		{Header: "HOST", Number: 5, MinWidth: 6, MaxWidth: 10, Priority: 5},
+		{Header: "SIZE", Number: 6, MinWidth: 5, MaxWidth: 8, Priority: 8},   // Can drop
+		{Header: "COMPLETE", Number: 7, MinWidth: 4, MaxWidth: 6, Priority: 4}, // Important
+		{Header: "TASK", Number: 8, MinWidth: 4, MaxWidth: 6, Priority: 9},   // Can drop
+		{Header: "TIMESTAMP", Number: 9, MinWidth: 8, MaxWidth: 12, Priority: 6}, // Can drop
+	}
+
+	// Configure table for current terminal width
+	dt := configureTableForTerminal(t, columns)
 
 	for _, file := range files {
 		// Show completion status
@@ -122,16 +156,9 @@ func runFilesList(cmd *cobra.Command, args []string) error {
 			}
 		}
 
-		// Truncate paths if too long
+		// Use full paths - table library will handle truncation
 		filename := file.Filename
-		if len(filename) > 25 {
-			filename = filename[:22] + "..."
-		}
-
 		remotePath := file.FullRemotePath
-		if len(remotePath) > 40 {
-			remotePath = "..." + remotePath[len(remotePath)-37:]
-		}
 
 		// Show file size info (chunks can give us a rough idea)
 		sizeInfo := "unknown"
@@ -145,10 +172,13 @@ func runFilesList(cmd *cobra.Command, args []string) error {
 			fileType = "payload"
 		} else if file.IsScreenshot {
 			fileType = "screenshot"
+		} else if !file.IsDownloadFromAgent {
+			fileType = "upload"
 		}
 
-		fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%d\t%s\n",
-			file.AgentFileID[:api.UUIDDisplayLength]+"...", // Show first 8 chars of UUID
+		// Prepare all column data
+		allData := []interface{}{
+			file.AgentFileID, // Show full UUID
 			fileType,
 			filename,
 			remotePath,
@@ -157,13 +187,15 @@ func runFilesList(cmd *cobra.Command, args []string) error {
 			status,
 			file.TaskDisplayID,
 			timestamp,
-		)
+		}
+
+		// Add row with only visible columns
+		dt.AppendRowForColumns(allData)
 	}
 
-	w.Flush()
+	t.Render()
 
 	fmt.Println("\nTo download a file: mysh files download <uuid>")
-	fmt.Println("To view full UUIDs: mysh files | grep -v UUID")
 
 	return nil
 }
cmd/grep.go
@@ -10,12 +10,18 @@ import (
 	"path/filepath"
 	"regexp"
 	"strings"
-	"text/tabwriter"
 	"time"
 
+	"github.com/jedib0t/go-pretty/v6/table"
 	"github.com/spf13/cobra"
 )
 
+type rawMatch struct {
+	taskID     int
+	sourceType string // "command" or "output"
+	content    string
+}
+
 var (
 	grepCallbackID      int
 	grepCaseInsensitive bool
@@ -25,6 +31,7 @@ var (
 	grepSearchCommands  bool
 	grepSearchOutput    bool
 	grepCacheOnly       bool
+	grepRaw             bool
 )
 
 var grepCmd = &cobra.Command{
@@ -49,6 +56,7 @@ func init() {
 	grepCmd.Flags().BoolVar(&grepSearchCommands, "commands-only", false, "Search only in command execution strings (command + params)")
 	grepCmd.Flags().BoolVar(&grepSearchOutput, "output-only", false, "Search only in task response outputs")
 	grepCmd.Flags().BoolVar(&grepCacheOnly, "cache-only", false, "Search only cached tasks (don't fetch new data from server)")
+	grepCmd.Flags().BoolVar(&grepRaw, "raw", false, "Display raw output with grep -R style single-line file delimiters")
 }
 
 
@@ -167,11 +175,13 @@ func runGrep(cmd *cobra.Command, args []string) error {
 
 	// Search through tasks in memory (much faster since we have all data)
 	var matchingTasks []mythic.Task
+	var rawMatches []rawMatch
 	processedTasks := 0
 
 	for _, task := range tasks {
 		processedTasks++
 		var matches bool
+		var taskMatches []rawMatch
 
 		// Check command string if enabled
 		if searchCommands {
@@ -179,12 +189,30 @@ func runGrep(cmd *cobra.Command, args []string) error {
 			if task.Params != "" {
 				commandString += " " + task.Params
 			}
-			matches = matcher(commandString)
+			if matcher(commandString) {
+				matches = true
+				if grepRaw {
+					taskMatches = append(taskMatches, rawMatch{
+						taskID:     task.DisplayID,
+						sourceType: "command",
+						content:    commandString,
+					})
+				}
+			}
 		}
 
-		// Check response output if enabled and not already matched
-		if !matches && searchOutput && task.Response != "" {
-			matches = matcher(task.Response)
+		// Check response output if enabled
+		if searchOutput && task.Response != "" {
+			if matcher(task.Response) {
+				matches = true
+				if grepRaw {
+					taskMatches = append(taskMatches, rawMatch{
+						taskID:     task.DisplayID,
+						sourceType: "output",
+						content:    task.Response,
+					})
+				}
+			}
 		}
 
 		// Apply invert logic
@@ -194,6 +222,9 @@ func runGrep(cmd *cobra.Command, args []string) error {
 
 		if matches {
 			matchingTasks = append(matchingTasks, task)
+			if grepRaw {
+				rawMatches = append(rawMatches, taskMatches...)
+			}
 		}
 	}
 
@@ -202,26 +233,73 @@ func runGrep(cmd *cobra.Command, args []string) error {
 		return nil
 	}
 
-	// Show results in table format
-	showMatchingTasksTable(matchingTasks)
+	// Show results
+	if grepRaw {
+		showRawMatches(rawMatches)
+	} else {
+		showMatchingTasksTable(matchingTasks)
+	}
 
 	fmt.Printf("\nFound %d matches in %d tasks searched\n", len(matchingTasks), processedTasks)
 	return nil
 }
 
 
+func showRawMatches(matches []rawMatch) {
+	for i, match := range matches {
+		// Format like tail -f: ==> filename <==
+		// Use task ID and source type as the "filename"
+		filename := fmt.Sprintf("task_%d_%s", match.taskID, match.sourceType)
+
+		// Add separator between files (except for the first one)
+		if i > 0 {
+			fmt.Println()
+		}
+
+		fmt.Printf("==> %s <==\n", filename)
+		fmt.Print(match.content)
+
+		// Ensure content ends with newline if it doesn't already
+		if !strings.HasSuffix(match.content, "\n") {
+			fmt.Println()
+		}
+	}
+}
+
 func showMatchingTasksTable(tasks []mythic.Task) {
-	// Create a tab writer for formatted output
-	w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
-	fmt.Fprintln(w, "TASK ID\tCOMMAND\tPARAMS\tSTATUS\tCALLBACK\tTIMESTAMP")
-	fmt.Fprintln(w, "-------\t-------\t------\t------\t--------\t---------")
+	// Create a table for formatted output
+	t := table.NewWriter()
+	t.SetOutputMirror(os.Stdout)
+
+	// Configure table styling - header borders only
+	t.SetStyle(table.Style{
+		Name: "HeaderOnlyBorders",
+		Box:  table.StyleBoxDefault,
+		Options: table.Options{
+			DrawBorder:      false, // No outer border
+			SeparateColumns: false, // No column separators
+			SeparateFooter:  false, // No footer separator
+			SeparateHeader:  true,  // Keep header separator
+			SeparateRows:    false, // No row separators
+		},
+	})
+
+	// Define columns with priorities (1=highest, higher numbers can be dropped)
+	columns := []ColumnInfo{
+		{Header: "TASK ID", Number: 1, MinWidth: 6, MaxWidth: 8, Priority: 1},   // Always show
+		{Header: "COMMAND", Number: 2, MinWidth: 8, MaxWidth: 12, Priority: 2},  // High priority
+		{Header: "PARAMS", Number: 3, MinWidth: 10, MaxWidth: 30, Priority: 5},  // Can drop
+		{Header: "STATUS", Number: 4, MinWidth: 6, MaxWidth: 10, Priority: 3},   // Important
+		{Header: "CALLBACK", Number: 5, MinWidth: 6, MaxWidth: 8, Priority: 4},  // Important
+		{Header: "TIMESTAMP", Number: 6, MinWidth: 10, MaxWidth: 14, Priority: 6}, // First to drop
+	}
+
+	// Configure table for current terminal width
+	dt := configureTableForTerminal(t, columns)
 
 	for _, task := range tasks {
-		// Truncate params if too long
+		// Use full params - table library will handle truncation
 		params := task.Params
-		if len(params) > 50 {
-			params = params[:47] + "..."
-		}
 
 		// Format timestamp
 		timestamp := task.Timestamp
@@ -231,17 +309,21 @@ func showMatchingTasksTable(tasks []mythic.Task) {
 			}
 		}
 
-		fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%d\t%s\n",
+		// Prepare all column data
+		allData := []interface{}{
 			task.DisplayID,
 			task.Command,
 			params,
 			task.Status,
 			task.CallbackID,
 			timestamp,
-		)
+		}
+
+		// Add row with only visible columns
+		dt.AppendRowForColumns(allData)
 	}
 
-	w.Flush()
+	t.Render()
 }
 
 // populateTaskResponses efficiently loads response data for tasks using cache when possible
cmd/payload.go
@@ -7,9 +7,9 @@ import (
 	"os"
 	"strconv"
 	"strings"
-	"text/tabwriter"
 	"time"
 
+	"github.com/jedib0t/go-pretty/v6/table"
 	"github.com/spf13/cobra"
 )
 
@@ -61,26 +61,46 @@ func showPayloadList(ctx context.Context, client *mythic.Client) error {
 
 	fmt.Printf("Payloads (%d total):\n\n", len(payloads))
 
-	// Create a tab writer for formatted output
-	w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
-	fmt.Fprintln(w, "ID\tTYPE\tDESCRIPTION\tFILENAME\tBUILD STATUS\tC2 PROFILES\tOPERATOR\tCREATED")
-	fmt.Fprintln(w, "--\t----\t-----------\t--------\t------------\t-----------\t--------\t-------")
+	// Create a table for formatted output
+	t := table.NewWriter()
+	t.SetOutputMirror(os.Stdout)
+
+	// Configure table styling - header borders only
+	t.SetStyle(table.Style{
+		Name: "HeaderOnlyBorders",
+		Box:  table.StyleBoxDefault,
+		Options: table.Options{
+			DrawBorder:      false, // No outer border
+			SeparateColumns: false, // No column separators
+			SeparateFooter:  false, // No footer separator
+			SeparateHeader:  true,  // Keep header separator
+			SeparateRows:    false, // No row separators
+		},
+	})
+
+	// Define columns with priorities (1=highest, higher numbers can be dropped)
+	columns := []ColumnInfo{
+		{Header: "ID", Number: 1, MinWidth: 3, MaxWidth: 6, Priority: 1},             // Always show
+		{Header: "TYPE", Number: 2, MinWidth: 6, MaxWidth: 10, Priority: 2},         // High priority
+		{Header: "DESCRIPTION", Number: 3, MinWidth: 10, MaxWidth: 20, Priority: 5}, // Can drop
+		{Header: "FILENAME", Number: 4, MinWidth: 8, MaxWidth: 16, Priority: 3},     // Important
+		{Header: "BUILD STATUS", Number: 5, MinWidth: 6, MaxWidth: 10, Priority: 4}, // Important
+		{Header: "C2 PROFILES", Number: 6, MinWidth: 8, MaxWidth: 12, Priority: 6},  // Can drop
+		{Header: "OPERATOR", Number: 7, MinWidth: 6, MaxWidth: 10, Priority: 7},     // Can drop
+		{Header: "CREATED", Number: 8, MinWidth: 10, MaxWidth: 14, Priority: 8},     // First to drop
+	}
+
+	// Configure table for current terminal width
+	dt := configureTableForTerminal(t, columns)
 
 	for _, payload := range payloads {
-		// Format description
+		// Use full values - table library will handle truncation
 		description := payload.Description
-		if len(description) > 30 {
-			description = description[:27] + "..."
-		}
 		if description == "" {
 			description = "-"
 		}
 
-		// Format filename
 		filename := payload.Filename
-		if len(filename) > 20 {
-			filename = filename[:17] + "..."
-		}
 		if filename == "" {
 			filename = "-"
 		}
@@ -93,9 +113,6 @@ func showPayloadList(ctx context.Context, client *mythic.Client) error {
 
 		// Format C2 profiles
 		c2ProfilesStr := strings.Join(payload.C2Profiles, ",")
-		if len(c2ProfilesStr) > 20 {
-			c2ProfilesStr = c2ProfilesStr[:17] + "..."
-		}
 		if c2ProfilesStr == "" {
 			c2ProfilesStr = "-"
 		}
@@ -114,7 +131,8 @@ func showPayloadList(ctx context.Context, client *mythic.Client) error {
 			typeStr += "*"
 		}
 
-		fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
+		// Prepare all column data
+		allData := []interface{}{
 			payload.ID,
 			typeStr,
 			description,
@@ -123,10 +141,13 @@ func showPayloadList(ctx context.Context, client *mythic.Client) error {
 			c2ProfilesStr,
 			payload.OperatorUsername,
 			created,
-		)
+		}
+
+		// Add row with only visible columns
+		dt.AppendRowForColumns(allData)
 	}
 
-	w.Flush()
+	t.Render()
 
 	fmt.Println("\n* = Auto-generated payload")
 	fmt.Println("\nTo view payload details: mysh payload <id>")
cmd/root.go
@@ -4,7 +4,9 @@ import (
 	"fmt"
 	"os"
 
+	"github.com/jedib0t/go-pretty/v6/table"
 	"github.com/spf13/cobra"
+	"golang.org/x/term"
 )
 
 var (
@@ -60,3 +62,187 @@ func validateConfig() error {
 	return nil
 }
 
+// getTerminalWidth returns the current terminal width, with a fallback to 80
+func getTerminalWidth() int {
+	if width, _, err := term.GetSize(int(os.Stdout.Fd())); err == nil && width > 0 {
+		return width
+	}
+	return 80 // fallback width
+}
+
+// ColumnInfo represents a table column with its properties
+type ColumnInfo struct {
+	Header   string
+	Number   int
+	MinWidth int
+	MaxWidth int
+	Priority int // 1 = highest priority (never drop), higher numbers can be dropped first
+}
+
+// DynamicTable wraps a table with information about visible columns
+type DynamicTable struct {
+	table.Writer
+	VisibleColumns []ColumnInfo
+}
+
+// configureTableForTerminal sets up a table with dynamic column sizing based on terminal width
+func configureTableForTerminal(t table.Writer, columns []ColumnInfo) *DynamicTable {
+	termWidth := getTerminalWidth()
+
+	// Reserve space for table library's internal padding and margins
+	availableWidth := termWidth - 4 // -4 to account for table library overhead
+
+	// Filter columns that can fit and distribute width
+	visibleColumns, columnWidths := distributeColumnWidths(columns, availableWidth)
+
+	// Create headers and column configs for visible columns
+	var headers []interface{}
+	var configs []table.ColumnConfig
+
+	for i, col := range visibleColumns {
+		headers = append(headers, col.Header)
+		configs = append(configs, table.ColumnConfig{
+			Number:           i + 1, // Use sequential numbers for visible columns
+			WidthMin:         columnWidths[i], // Force minimum width
+			WidthMax:         columnWidths[i], // Force maximum width
+			WidthMaxEnforcer: truncateWithEllipsis,
+		})
+	}
+
+	t.AppendHeader(table.Row(headers))
+	t.SetColumnConfigs(configs)
+	// Don't set AllowedRowLength since we're managing column widths precisely
+
+	return &DynamicTable{
+		Writer:         t,
+		VisibleColumns: visibleColumns,
+	}
+}
+
+// AppendRowForColumns adds a row to the table using only the visible columns
+func (dt *DynamicTable) AppendRowForColumns(allData []interface{}) {
+	var visibleData []interface{}
+	for _, col := range dt.VisibleColumns {
+		if col.Number-1 < len(allData) {
+			visibleData = append(visibleData, allData[col.Number-1])
+		}
+	}
+	dt.AppendRow(table.Row(visibleData))
+}
+
+// distributeColumnWidths filters columns by priority and distributes available width
+func distributeColumnWidths(columns []ColumnInfo, availableWidth int) ([]ColumnInfo, []int) {
+	// Sort by priority (lower number = higher priority)
+	sortedCols := make([]ColumnInfo, len(columns))
+	copy(sortedCols, columns)
+
+	// Simple bubble sort by priority
+	for i := 0; i < len(sortedCols)-1; i++ {
+		for j := 0; j < len(sortedCols)-i-1; j++ {
+			if sortedCols[j].Priority > sortedCols[j+1].Priority {
+				sortedCols[j], sortedCols[j+1] = sortedCols[j+1], sortedCols[j]
+			}
+		}
+	}
+
+	// Step 1: Find which columns can fit using minimum widths
+	// Try to fit columns in priority order (lowest priority number = highest priority)
+	var visibleColumns []ColumnInfo
+	totalMinWidth := 0
+
+	for _, col := range sortedCols {
+		// Check if adding this column would exceed available width
+		// Add 1 space per column for minimal padding
+		newTotal := totalMinWidth + col.MinWidth + 1
+		if newTotal <= availableWidth {
+			visibleColumns = append(visibleColumns, col)
+			totalMinWidth = newTotal
+		} else {
+			// Can't fit this column or any remaining lower priority columns
+			break
+		}
+	}
+
+	// Ensure we have at least one column (highest priority)
+	if len(visibleColumns) == 0 && len(sortedCols) > 0 {
+		visibleColumns = append(visibleColumns, sortedCols[0])
+		totalMinWidth = sortedCols[0].MinWidth + 1 // Include minimal padding
+	}
+
+	// Step 2: Distribute available width across visible columns
+	columnWidths := make([]int, len(visibleColumns))
+
+	// Start with minimum widths (totalMinWidth already includes padding)
+	actualContentWidth := 0
+	for i, col := range visibleColumns {
+		columnWidths[i] = col.MinWidth
+		actualContentWidth += col.MinWidth
+	}
+
+	// Calculate remaining width to distribute (subtract actual content width, not totalMinWidth with padding)
+	remainingWidth := availableWidth - totalMinWidth
+
+	if remainingWidth > 0 {
+		// Calculate total expansion capacity for proportional distribution
+		totalExpansion := 0
+		for _, col := range visibleColumns {
+			expansion := col.MaxWidth - col.MinWidth
+			if expansion > 0 {
+				totalExpansion += expansion
+			}
+		}
+
+		if totalExpansion > 0 {
+			// Distribute remaining width proportionally
+			for i, col := range visibleColumns {
+				expansion := col.MaxWidth - col.MinWidth
+				if expansion > 0 {
+					extraWidth := (remainingWidth * expansion) / totalExpansion
+					columnWidths[i] = col.MinWidth + extraWidth
+					// Ensure we don't exceed max width
+					if columnWidths[i] > col.MaxWidth {
+						columnWidths[i] = col.MaxWidth
+					}
+				}
+			}
+
+			// Distribute any leftover width (due to rounding or max width constraints)
+			actualUsed := 0
+			for _, width := range columnWidths {
+				actualUsed += width
+			}
+			leftover := availableWidth - actualUsed
+
+			// Only distribute leftover if it's positive and reasonable
+			if leftover > 0 && leftover < len(visibleColumns)*2 {
+				for i := 0; i < leftover && i < len(visibleColumns); i++ {
+					if columnWidths[i] < visibleColumns[i].MaxWidth {
+						columnWidths[i]++
+					}
+				}
+			}
+		}
+	}
+
+	return visibleColumns, columnWidths
+}
+
+// filterColumnsForWidth removes low-priority columns until the table fits in available width
+// Kept for backwards compatibility, but now uses distributeColumnWidths internally
+func filterColumnsForWidth(columns []ColumnInfo, availableWidth int) []ColumnInfo {
+	visibleColumns, _ := distributeColumnWidths(columns, availableWidth)
+	return visibleColumns
+}
+
+// truncateWithEllipsis is a helper function for table column width enforcement
+// It truncates text that exceeds maxLen and adds "..." at the end
+func truncateWithEllipsis(col string, maxLen int) string {
+	if len(col) <= maxLen {
+		return col
+	}
+	if maxLen <= 3 {
+		return col[:maxLen]
+	}
+	return col[:maxLen-3] + "..."
+}
+
cmd/task_list.go
@@ -6,9 +6,9 @@ import (
 	"mysh/pkg/mythic"
 	"os"
 	"strconv"
-	"text/tabwriter"
 	"time"
 
+	"github.com/jedib0t/go-pretty/v6/table"
 	"github.com/spf13/cobra"
 )
 
@@ -90,23 +90,51 @@ func runTaskList(cmd *cobra.Command, args []string) error {
 		fmt.Printf("Tasks for callback %s (showing %d, newest last):\n\n", args[0], len(tasks))
 	}
 
-	// Create a tab writer for formatted output
-	w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
-
+	// Create a table for formatted output
+	t := table.NewWriter()
+	t.SetOutputMirror(os.Stdout)
+
+	// Configure table styling - header borders only
+	t.SetStyle(table.Style{
+		Name: "HeaderOnlyBorders",
+		Box:  table.StyleBoxDefault,
+		Options: table.Options{
+			DrawBorder:      false, // No outer border
+			SeparateColumns: false, // No column separators
+			SeparateFooter:  false, // No footer separator
+			SeparateHeader:  true,  // Keep header separator
+			SeparateRows:    false, // No row separators
+		},
+	})
+
+	var columns []ColumnInfo
 	if showingAllCallbacks {
-		fmt.Fprintln(w, "TASK ID\tCALLBACK\tCOMMAND\tPARAMS\tSTATUS\tTIMESTAMP")
-		fmt.Fprintln(w, "-------\t--------\t-------\t------\t------\t---------")
+		// Define columns with priorities for all callbacks view
+		columns = []ColumnInfo{
+			{Header: "TASK ID", Number: 1, MinWidth: 6, MaxWidth: 8, Priority: 1},    // Always show
+			{Header: "CALLBACK", Number: 2, MinWidth: 6, MaxWidth: 8, Priority: 2},  // High priority
+			{Header: "COMMAND", Number: 3, MinWidth: 8, MaxWidth: 15, Priority: 3},  // Important
+			{Header: "PARAMS", Number: 4, MinWidth: 10, MaxWidth: 40, Priority: 5},  // Can drop
+			{Header: "STATUS", Number: 5, MinWidth: 8, MaxWidth: 12, Priority: 4},   // Important
+			{Header: "TIMESTAMP", Number: 6, MinWidth: 12, MaxWidth: 19, Priority: 6}, // First to drop
+		}
 	} else {
-		fmt.Fprintln(w, "TASK ID\tCOMMAND\tPARAMS\tSTATUS\tTIMESTAMP")
-		fmt.Fprintln(w, "-------\t-------\t------\t------\t---------")
+		// Define columns with priorities for single callback view
+		columns = []ColumnInfo{
+			{Header: "TASK ID", Number: 1, MinWidth: 6, MaxWidth: 8, Priority: 1},    // Always show
+			{Header: "COMMAND", Number: 2, MinWidth: 8, MaxWidth: 15, Priority: 2},   // High priority
+			{Header: "PARAMS", Number: 3, MinWidth: 10, MaxWidth: 50, Priority: 4},   // Can drop
+			{Header: "STATUS", Number: 4, MinWidth: 8, MaxWidth: 12, Priority: 3},    // Important
+			{Header: "TIMESTAMP", Number: 5, MinWidth: 12, MaxWidth: 19, Priority: 5}, // First to drop
+		}
 	}
 
+	// Configure table for current terminal width
+	dt := configureTableForTerminal(t, columns)
+
 	for _, task := range tasks {
-		// Truncate params if too long
+		// Use full params - table library will handle truncation
 		params := task.Params
-		if len(params) > 50 {
-			params = params[:47] + "..."
-		}
 
 		// Format timestamp
 		timestamp := task.Timestamp
@@ -116,27 +144,32 @@ func runTaskList(cmd *cobra.Command, args []string) error {
 			}
 		}
 
+		// Prepare all column data
+		var allData []interface{}
 		if showingAllCallbacks {
-			fmt.Fprintf(w, "%d\t%d\t%s\t%s\t%s\t%s\n",
+			allData = []interface{}{
 				task.DisplayID,
 				task.CallbackID,
 				task.Command,
 				params,
 				task.Status,
 				timestamp,
-			)
+			}
 		} else {
-			fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\n",
+			allData = []interface{}{
 				task.DisplayID,
 				task.Command,
 				params,
 				task.Status,
 				timestamp,
-			)
+			}
 		}
+
+		// Add row with only visible columns
+		dt.AppendRowForColumns(allData)
 	}
 
-	w.Flush()
+	t.Render()
 
 	return nil
 }
\ No newline at end of file
pkg/mythic/client.go
@@ -190,7 +190,7 @@ func decodeResponseText(responseText string) string {
 func (c *Client) GetCallbacks(ctx context.Context) ([]Callback, error) {
 	req := graphql.NewRequest(GetCallbacks)
 	
-	req.Header.Set("Authorization", "Bearer "+c.token)
+	req.Header.Set("apitoken", c.token)
 	
 	var resp struct {
 		Callback []Callback `json:"callback"`
@@ -226,7 +226,7 @@ func (c *Client) CreateTask(ctx context.Context, callbackID int, command, params
 	req.Var("command", command)
 	req.Var("params", params)
 	req.Var("tasking_location", "command_line")
-	req.Header.Set("Authorization", "Bearer "+c.token)
+	req.Header.Set("apitoken", c.token)
 	
 	var resp struct {
 		CreateTask struct {
@@ -259,7 +259,7 @@ func (c *Client) GetTaskResponse(ctx context.Context, taskID int) (*Task, error)
 	req := graphql.NewRequest(GetTask)
 	
 	req.Var("id", taskID)
-	req.Header.Set("Authorization", "Bearer "+c.token)
+	req.Header.Set("apitoken", c.token)
 	
 	var resp struct {
 		Task struct {
@@ -285,7 +285,7 @@ func (c *Client) GetTaskResponse(ctx context.Context, taskID int) (*Task, error)
 	responseReq := graphql.NewRequest(GetTaskResponses)
 	
 	responseReq.Var("task_display_id", resp.Task.DisplayID)
-	responseReq.Header.Set("Authorization", "Bearer "+c.token)
+	responseReq.Header.Set("apitoken", c.token)
 	
 	var responseResp struct {
 		Response []struct {
@@ -331,7 +331,7 @@ func (c *Client) GetTasksWithResponsesByIDs(ctx context.Context, taskIDs []int)
 
 	req := graphql.NewRequest(GetTasksWithResponsesByIDs)
 	req.Var("task_ids", taskIDs)
-	req.Header.Set("Authorization", "Bearer "+c.token)
+	req.Header.Set("apitoken", c.token)
 
 	var resp struct {
 		Task []struct {
@@ -395,7 +395,7 @@ func (c *Client) GetCallbackTasks(ctx context.Context, callbackID int) ([]Task,
 	req := graphql.NewRequest(GetCallbackTasks)
 
 	req.Var("callback_id", callbackID)
-	req.Header.Set("Authorization", "Bearer "+c.token)
+	req.Header.Set("apitoken", c.token)
 
 	var resp struct {
 		Task []struct {
@@ -443,7 +443,7 @@ func (c *Client) GetCallbackTasks(ctx context.Context, callbackID int) ([]Task,
 func (c *Client) GetDownloadedFiles(ctx context.Context) ([]File, error) {
 	req := graphql.NewRequest(GetDownloadedFiles)
 
-	req.Header.Set("Authorization", "Bearer "+c.token)
+	req.Header.Set("apitoken", c.token)
 
 	var resp struct {
 		Filemeta []struct {
@@ -507,7 +507,7 @@ func (c *Client) GetDownloadedFiles(ctx context.Context) ([]File, error) {
 func (c *Client) GetAllFiles(ctx context.Context) ([]File, error) {
 	req := graphql.NewRequest(GetAllFiles)
 
-	req.Header.Set("Authorization", "Bearer "+c.token)
+	req.Header.Set("apitoken", c.token)
 
 	var resp struct {
 		Filemeta []struct {
@@ -582,7 +582,7 @@ func (c *Client) DownloadFile(ctx context.Context, fileUUID string) ([]byte, err
 		return nil, fmt.Errorf("failed to create download request: %w", err)
 	}
 
-	req.Header.Set("Authorization", "Bearer "+c.token)
+	req.Header.Set("apitoken", c.token)
 
 	resp, err := c.httpClient.Do(req)
 	if err != nil {
@@ -611,7 +611,7 @@ func (c *Client) GetTasksWithResponses(ctx context.Context, callbackID int, limi
 	if limit > 0 {
 		req.Var("limit", limit)
 	}
-	req.Header.Set("Authorization", "Bearer "+c.token)
+	req.Header.Set("apitoken", c.token)
 
 	var resp struct {
 		Task []struct {
@@ -677,7 +677,7 @@ func (c *Client) GetAllTasks(ctx context.Context, limit int) ([]Task, error) {
 	if limit > 0 {
 		req.Var("limit", limit)
 	}
-	req.Header.Set("Authorization", "Bearer "+c.token)
+	req.Header.Set("apitoken", c.token)
 
 	var resp struct {
 		Task []struct {
@@ -733,7 +733,7 @@ func (c *Client) GetAllTasksWithResponses(ctx context.Context, limit int) ([]Tas
 	if limit > 0 {
 		req.Var("limit", limit)
 	}
-	req.Header.Set("Authorization", "Bearer "+c.token)
+	req.Header.Set("apitoken", c.token)
 
 	var resp struct {
 		Task []struct {
@@ -796,7 +796,7 @@ func (c *Client) GetAllTasksWithResponses(ctx context.Context, limit int) ([]Tas
 func (c *Client) GetPayloads(ctx context.Context) ([]Payload, error) {
 	req := graphql.NewRequest(GetPayloads)
 
-	req.Header.Set("Authorization", "Bearer "+c.token)
+	req.Header.Set("apitoken", c.token)
 
 	var resp struct {
 		Payload []struct {
.gitignore
@@ -1,4 +1,4 @@
 bin/*
 mythic.sh
-contrib/Mythic_Scripting
+contrib/*
 .env
go.mod
@@ -9,8 +9,14 @@ require (
 
 require (
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
+	github.com/jedib0t/go-pretty/v6 v6.6.8 // indirect
 	github.com/matryer/is v1.4.1 // indirect
+	github.com/mattn/go-runewidth v0.0.16 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
+	github.com/rivo/uniseg v0.4.7 // indirect
 	github.com/spf13/pflag v1.0.9 // indirect
 	golang.org/x/net v0.44.0 // indirect
+	golang.org/x/sys v0.36.0 // indirect
+	golang.org/x/term v0.35.0 // indirect
+	golang.org/x/text v0.29.0 // indirect
 )
go.sum
@@ -1,12 +1,19 @@
 github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+github.com/jedib0t/go-pretty/v6 v6.6.8 h1:JnnzQeRz2bACBobIaa/r+nqjvws4yEhcmaZ4n1QzsEc=
+github.com/jedib0t/go-pretty/v6 v6.6.8/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
 github.com/machinebox/graphql v0.2.2 h1:dWKpJligYKhYKO5A2gvNhkJdQMNZeChZYyBbrZkBZfo=
 github.com/machinebox/graphql v0.2.2/go.mod h1:F+kbVMHuwrQ5tYgU9JXlnskM8nOaFxCAEolaQybkjWA=
 github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
 github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
+github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
+github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
+github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
 github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
 github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
@@ -14,5 +21,11 @@ github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY=
 github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
 golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
+golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
+golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
+golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
+golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
+golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
TODO.md
@@ -2,6 +2,7 @@
 - MYTHIC_API_INSECURE= boolean --insecure flag - and invert default = false
 - update output table formats to respect terminal width
 - invert cobra-cli nto functions (no global vars)
+- ✅ grep --raw - only diplay of all matching items raw output follow grep -R style single-line file dilimeters in the output
 - ✅ use per-server cache dirs
 - ✅ make sure raw output has a newline at the end
 - ✅ make mysh cache default to info output
USAGE.md
@@ -1,155 +0,0 @@
-# `mysh` Usage Examples
-
-## Building
-
-### Using Makefile (Recommended)
-```bash
-# Build for current platform
-make build
-
-# Build for all platforms
-make build-all
-
-# Development build (faster)
-make dev
-
-# Clean build artifacts
-make clean
-
-# Show all available targets
-make help
-```
-
-### Manual Build
-```bash
-go build -o bin/mysh .
-```
-
-## Project Structure
-
-### GraphQL Queries Synchronization
-The project maintains a 1:1 copy of GraphQL queries from the Python Mythic_Scripting library:
-- **Source:** `contrib/Mythic_Scripting/mythic/graphql_queries.py` 
-- **Go Version:** `pkg/mythic/queries.go`
-- **Purpose:** Easier upstream synchronization and reduced raw strings in client code
-
-To sync with upstream updates:
-1. Check the Python file for changes
-2. Update the corresponding Go constants
-3. Test client functionality
-
-## Basic Commands
-
-### List Active Callbacks
-```bash
-./mysh callbacks
-```
-
-### Execute Commands
-```bash
-# Get help for all commands
-./mysh exec 1 help
-
-# Get detailed help for a specific command
-./mysh exec 1 help ps
-
-# List processes
-./mysh exec 1 ps
-
-# Set checkin frequency to 5 seconds
-./mysh exec 1 sleep 5
-```
-
-### Interactive Session
-```bash
-./mysh interact 1
-```
-
-## Using SOCKS5 Proxy
-
-The CLI supports SOCKS5 proxy routing for all network traffic, replacing the need for proxychains:
-
-```bash
-# Use SOCKS5 proxy (equivalent to proxychains)
-./mysh --socks 127.0.0.1:9050 callbacks
-
-# Execute command through proxy
-./mysh --socks 127.0.0.1:9050 exec 1 ps
-
-# Interactive session through proxy
-./mysh --socks 127.0.0.1:9050 interact 1
-```
-
-## Configuration
-
-**Both URL and authentication token are required.**
-
-### Quick Setup
-```bash
-# Set via environment variables (recommended)
-export MYTHIC_API_URL="https://your-mythic-server:7443/graphql/"
-export MYTHIC_API_TOKEN="your-jwt-token-here"
-./mysh callbacks
-
-# For testing with mythic.sh
-export MYTHIC_API_URL="https://mythic.adversarytactics.local:7443/graphql/"
-export MYTHIC_API_TOKEN="$(grep TOKEN mythic.sh | cut -d'=' -f2)"
-./mysh callbacks
-```
-
-### Individual Configuration Options
-
-#### Mythic Server URL
-```bash
-# Set via environment variable
-export MYTHIC_API_URL="https://your-mythic-server:7443/graphql/"
-
-# Set via command line flag
-./mysh --url "https://your-mythic-server:7443/graphql/" callbacks
-```
-
-#### Authentication Token
-```bash
-# Set via environment variable
-export MYTHIC_API_TOKEN="your-jwt-token-here"
-
-# Set via command line flag
-./mysh --token "your-jwt-token-here" callbacks
-
-# Get token from mythic.sh for testing
-export MYTHIC_API_TOKEN="$(grep TOKEN mythic.sh | cut -d'=' -f2)"
-```
-
-## Lab 01 Equivalents
-
-These commands accomplish the goals from labs/01.md:
-
-1. **View Active Callbacks** (equivalent to clicking Active Callbacks in UI):
-   ```bash
-   ./mysh callbacks
-   ```
-
-2. **Interact with Agent** (equivalent to right-click → Interact):
-   ```bash
-   ./mysh interact <callback_id>
-   ```
-
-3. **Get Help** (equivalent to typing "help" in UI console):
-   ```bash
-   ./mysh exec <callback_id> help
-   ```
-
-4. **Get Command Details** (equivalent to typing "help ps"):
-   ```bash
-   ./mysh exec <callback_id> help ps
-   ```
-
-5. **List Processes** (equivalent to typing "ps"):
-   ```bash
-   ./mysh exec <callback_id> ps
-   ```
-
-6. **Set Checkin Frequency** (equivalent to typing "sleep 5"):
-   ```bash
-   ./mysh exec <callback_id> sleep 5
-   ```