main
Raw Download raw file
  1package cmd
  2
  3import (
  4	"context"
  5	"fmt"
  6	"mysh/pkg/mythic"
  7	"os"
  8	"strconv"
  9	"time"
 10
 11	"github.com/jedib0t/go-pretty/v6/table"
 12	"github.com/spf13/cobra"
 13)
 14
 15var (
 16	listTaskLimit int
 17	listShowAll   bool
 18)
 19
 20var taskListCmd = &cobra.Command{
 21	Use:     "task-list [callback_id]",
 22	Aliases: []string{"tl"},
 23	Short:   "List tasks for a specific agent or all agents",
 24	Long:    "Display task history for a specific agent callback or all callbacks. When no callback_id is provided, shows tasks from all agents with a callback column.",
 25	Args:    cobra.MaximumNArgs(1),
 26	RunE:    runTaskList,
 27}
 28
 29func init() {
 30	rootCmd.AddCommand(taskListCmd)
 31	taskListCmd.Flags().IntVar(&listTaskLimit, "limit", 10, "Maximum number of tasks to display")
 32	taskListCmd.Flags().BoolVarP(&listShowAll, "all", "a", false, "Show all tasks (same as --limit 0)")
 33}
 34
 35func runTaskList(cmd *cobra.Command, args []string) error {
 36	if err := validateConfig(); err != nil {
 37		return err
 38	}
 39
 40	client := mythic.NewClient(mythicURL, token, insecure, socksProxy)
 41	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
 42	defer cancel()
 43
 44	var tasks []mythic.Task
 45	var showingAllCallbacks bool
 46
 47	// Determine if we're showing tasks for a specific callback or all callbacks
 48	if len(args) == 0 {
 49		// No callback_id provided, show tasks from all callbacks
 50		showingAllCallbacks = true
 51
 52		// Set limit for all tasks query
 53		limit := listTaskLimit
 54		if listShowAll {
 55			limit = 0
 56		}
 57
 58		var err error
 59		tasks, err = client.GetAllTasks(ctx, limit)
 60		if err != nil {
 61			return fmt.Errorf("failed to get all tasks: %w", err)
 62		}
 63	} else {
 64		// Specific callback_id provided
 65		callbackID, parseErr := strconv.Atoi(args[0])
 66		if parseErr != nil {
 67			return fmt.Errorf("invalid callback ID: %s", args[0])
 68		}
 69
 70		var err error
 71		tasks, err = client.GetCallbackTasks(ctx, callbackID)
 72		if err != nil {
 73			return fmt.Errorf("failed to get tasks for callback %d: %w", callbackID, err)
 74		}
 75	}
 76
 77	if len(tasks) == 0 {
 78		if showingAllCallbacks {
 79			fmt.Println("No tasks found")
 80		} else {
 81			fmt.Printf("No tasks found for callback %s\n", args[0])
 82		}
 83		return nil
 84	}
 85
 86	// Display header
 87	if showingAllCallbacks {
 88		fmt.Printf("All tasks (showing %d, newest last):\n\n", len(tasks))
 89	} else {
 90		fmt.Printf("Tasks for callback %s (showing %d, newest last):\n\n", args[0], len(tasks))
 91	}
 92
 93	// Create a table for formatted output
 94	t := table.NewWriter()
 95	t.SetOutputMirror(os.Stdout)
 96
 97	// Configure table styling - header borders only
 98	t.SetStyle(table.Style{
 99		Name: "HeaderOnlyBorders",
100		Box:  table.StyleBoxDefault,
101		Options: table.Options{
102			DrawBorder:      false, // No outer border
103			SeparateColumns: false, // No column separators
104			SeparateFooter:  false, // No footer separator
105			SeparateHeader:  true,  // Keep header separator
106			SeparateRows:    false, // No row separators
107		},
108	})
109
110	var columns []ColumnInfo
111	if showingAllCallbacks {
112		// Define columns with priorities for all callbacks view
113		columns = []ColumnInfo{
114			{Header: "TASK ID", Number: 1, MinWidth: 6, MaxWidth: 8, Priority: 1},     // Always show
115			{Header: "CALLBACK", Number: 2, MinWidth: 6, MaxWidth: 8, Priority: 2},    // High priority
116			{Header: "COMMAND", Number: 3, MinWidth: 8, MaxWidth: 15, Priority: 3},    // Important
117			{Header: "PARAMS", Number: 4, MinWidth: 10, MaxWidth: 40, Priority: 5},    // Can drop
118			{Header: "STATUS", Number: 5, MinWidth: 8, MaxWidth: 12, Priority: 4},     // Important
119			{Header: "TIMESTAMP", Number: 6, MinWidth: 12, MaxWidth: 19, Priority: 6}, // First to drop
120		}
121	} else {
122		// Define columns with priorities for single callback view
123		columns = []ColumnInfo{
124			{Header: "TASK ID", Number: 1, MinWidth: 6, MaxWidth: 8, Priority: 1},     // Always show
125			{Header: "COMMAND", Number: 2, MinWidth: 8, MaxWidth: 15, Priority: 2},    // High priority
126			{Header: "PARAMS", Number: 3, MinWidth: 10, MaxWidth: 50, Priority: 4},    // Can drop
127			{Header: "STATUS", Number: 4, MinWidth: 8, MaxWidth: 12, Priority: 3},     // Important
128			{Header: "TIMESTAMP", Number: 5, MinWidth: 12, MaxWidth: 19, Priority: 5}, // First to drop
129		}
130	}
131
132	// Configure table for current terminal width
133	dt := configureTableForTerminal(t, columns)
134
135	for _, task := range tasks {
136		// Use full params - table library will handle truncation
137		params := task.Params
138
139		// Format timestamp
140		timestamp := task.Timestamp
141		if timestamp != "" {
142			if t, err := time.Parse(time.RFC3339, timestamp); err == nil {
143				timestamp = t.Format("2006-01-02 15:04:05")
144			}
145		}
146
147		// Prepare all column data
148		var allData []interface{}
149		if showingAllCallbacks {
150			allData = []interface{}{
151				task.DisplayID,
152				task.CallbackID,
153				task.Command,
154				params,
155				task.Status,
156				timestamp,
157			}
158		} else {
159			allData = []interface{}{
160				task.DisplayID,
161				task.Command,
162				params,
163				task.Status,
164				timestamp,
165			}
166		}
167
168		// Add row with only visible columns
169		dt.AppendRowForColumns(allData)
170	}
171
172	t.Render()
173
174	return nil
175}