main
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}