Commit 07d2f70
Changed files (2)
cmd
cmd/task_list.go
@@ -20,11 +20,11 @@ var (
)
var taskListCmd = &cobra.Command{
- Use: "task-list <callback_id>",
+ Use: "task-list [callback_id]",
Aliases: []string{"tl"},
- Short: "List tasks for a specific agent",
- Long: "Display task history for a specific agent callback, equivalent to viewing the tasking tab for an agent in the UI.",
- Args: cobra.ExactArgs(1),
+ Short: "List tasks for a specific agent or all agents",
+ 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.",
+ Args: cobra.MaximumNArgs(1),
RunE: runTaskList,
}
@@ -40,40 +40,76 @@ func runTaskList(cmd *cobra.Command, args []string) error {
return err
}
- callbackID, err := strconv.Atoi(args[0])
- if err != nil {
- return fmt.Errorf("invalid callback ID: %s", args[0])
- }
-
client := mythic.NewClient(mythicURL, token, insecure, socksProxy)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
- tasks, err := client.GetCallbackTasks(ctx, callbackID)
- if err != nil {
- return fmt.Errorf("failed to get tasks for callback %d: %w", callbackID, err)
+ var tasks []mythic.Task
+ var err error
+ var showingAllCallbacks bool
+
+ // Determine if we're showing tasks for a specific callback or all callbacks
+ if len(args) == 0 {
+ // No callback_id provided, show tasks from all callbacks
+ showingAllCallbacks = true
+
+ // Set limit for all tasks query
+ limit := listTaskLimit
+ if listShowAll {
+ limit = 0
+ }
+
+ tasks, err = client.GetAllTasksWithResponses(ctx, limit)
+ if err != nil {
+ return fmt.Errorf("failed to get all tasks: %w", err)
+ }
+ } else {
+ // Specific callback_id provided
+ callbackID, parseErr := strconv.Atoi(args[0])
+ if parseErr != nil {
+ return fmt.Errorf("invalid callback ID: %s", args[0])
+ }
+
+ tasks, err = client.GetCallbackTasks(ctx, callbackID)
+ if err != nil {
+ return fmt.Errorf("failed to get tasks for callback %d: %w", callbackID, err)
+ }
+
+ // Apply limit from the end (keep newest tasks if limiting)
+ if listShowAll {
+ listTaskLimit = 0
+ }
+ if listTaskLimit > 0 && len(tasks) > listTaskLimit {
+ tasks = tasks[len(tasks)-listTaskLimit:]
+ }
}
if len(tasks) == 0 {
- fmt.Printf("No tasks found for callback %d\n", callbackID)
+ if showingAllCallbacks {
+ fmt.Println("No tasks found")
+ } else {
+ fmt.Printf("No tasks found for callback %s\n", args[0])
+ }
return nil
}
- // Apply limit from the end (keep newest tasks if limiting)
- // If --all flag is set, show all tasks (equivalent to --limit 0)
- if listShowAll {
- listTaskLimit = 0
- }
- if listTaskLimit > 0 && len(tasks) > listTaskLimit {
- tasks = tasks[len(tasks)-listTaskLimit:]
+ // Display header
+ if showingAllCallbacks {
+ fmt.Printf("All tasks (showing %d, newest first):\n\n", len(tasks))
+ } else {
+ fmt.Printf("Tasks for callback %s (showing %d, newest last):\n\n", args[0], len(tasks))
}
- fmt.Printf("Tasks for callback %d (showing %d, newest last):\n\n", callbackID, len(tasks))
-
// Create a tab writer for formatted output
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
- fmt.Fprintln(w, "TASK ID\tCOMMAND\tPARAMS\tSTATUS\tTIMESTAMP")
- fmt.Fprintln(w, "-------\t-------\t------\t------\t---------")
+
+ if showingAllCallbacks {
+ fmt.Fprintln(w, "TASK ID\tCALLBACK\tCOMMAND\tPARAMS\tSTATUS\tTIMESTAMP")
+ fmt.Fprintln(w, "-------\t--------\t-------\t------\t------\t---------")
+ } else {
+ fmt.Fprintln(w, "TASK ID\tCOMMAND\tPARAMS\tSTATUS\tTIMESTAMP")
+ fmt.Fprintln(w, "-------\t-------\t------\t------\t---------")
+ }
for _, task := range tasks {
// Truncate params if too long
@@ -90,13 +126,24 @@ func runTaskList(cmd *cobra.Command, args []string) error {
}
}
- fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\n",
- task.DisplayID,
- task.Command,
- params,
- task.Status,
- timestamp,
- )
+ if showingAllCallbacks {
+ fmt.Fprintf(w, "%d\t%d\t%s\t%s\t%s\t%s\n",
+ task.DisplayID,
+ task.CallbackID,
+ task.Command,
+ params,
+ task.Status,
+ timestamp,
+ )
+ } else {
+ fmt.Fprintf(w, "%d\t%s\t%s\t%s\t%s\n",
+ task.DisplayID,
+ task.Command,
+ params,
+ task.Status,
+ timestamp,
+ )
+ }
}
w.Flush()
@@ -111,18 +158,29 @@ func runTaskList(cmd *cobra.Command, args []string) error {
break
}
- fmt.Printf("\nTask %d (%s):\n", task.DisplayID, task.Command)
- fmt.Println(strings.Repeat("-", 30))
-
- // Get full task with response
- fullTask, err := client.GetTaskResponse(ctx, task.ID)
- if err != nil {
- fmt.Printf("Error getting response: %v\n", err)
- continue
+ if showingAllCallbacks {
+ fmt.Printf("\nTask %d from callback %d (%s):\n", task.DisplayID, task.CallbackID, task.Command)
+ } else {
+ fmt.Printf("\nTask %d (%s):\n", task.DisplayID, task.Command)
}
+ fmt.Println(strings.Repeat("-", 30))
- if fullTask.Response != "" {
- fmt.Println(fullTask.Response)
+ // For all callbacks mode, task already includes response from GetAllTasksWithResponses
+ if showingAllCallbacks && task.Response != "" {
+ fmt.Println(task.Response)
+ } else if !showingAllCallbacks {
+ // For specific callback, get full task with response
+ fullTask, err := client.GetTaskResponse(ctx, task.ID)
+ if err != nil {
+ fmt.Printf("Error getting response: %v\n", err)
+ continue
+ }
+
+ if fullTask.Response != "" {
+ fmt.Println(fullTask.Response)
+ } else {
+ fmt.Println("(No response yet)")
+ }
} else {
fmt.Println("(No response yet)")
}
TODO.md
@@ -1,60 +1,9 @@
- ✅ grep should also look at the command execution strings too, not just output
- ✅ Renamed project from go-mythic to mysh (pronounced mɪʃh)
-- task-list --all (same as --limit 0)
+- ✅ task-list --all (same as --limit 0)
- 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
- invert cobra-cli nto functions (no global vars)
+- update output table formats to respect terminal width
- forge payloads - equivalency with execute_assembly and inline_assembly
-
-## Code Quality Improvements
-
-### High Priority
-1. **Extract callback lookup logic to shared utility** - Remove duplication in task.go and interact.go
-2. **Standardize task polling with configurable timeouts** - Create reusable polling mechanism
-3. **Create constants file for magic numbers** - Replace hardcoded values (timeouts, status strings)
-4. **Add basic unit tests for client functions** - Test GraphQL operations and response handling
-
-### Medium Priority
-1. **Implement proper error types with context** - Create MythicError, CallbackNotFoundError, TaskTimeoutError
-2. **Add configuration management** - Centralized config with proper defaults
-3. **Create interfaces for better testability** - MythicClient interface for mocking
-4. **Improve logging and debugging** - Add structured logging with slog
-
-### Low Priority
-1. **Refactor large functions into smaller components** - Break down extractUUIDFromTaskResponse
-2. **Add comprehensive integration tests** - Test CLI commands with fixtures
-3. **Implement retry mechanisms for network operations** - Handle transient failures
-4. **Add performance monitoring/metrics** - Track operation success/failure rates
-
-## Code Duplication Issues
-- **Callback validation**: task.go:54-69 and interact.go:42-57 duplicate callback lookup
-- **Task polling**: Similar patterns in task.go:88-116 and interact.go:119-144
-- **Client creation**: Repeated client instantiation across all commands
-- **Config validation**: validateConfig() called in every command
-
-## Suggested Package Structure
-```
-pkg/
-├── mythic/ # Existing client
-├── utils/ # New shared utilities
-│ ├── callbacks.go # Callback lookup helpers
-│ ├── tasks.go # Task polling utilities
-│ ├── constants.go # Shared constants
-│ └── validation.go # Common validation
-├── config/ # Configuration management
-│ └── config.go
-└── errors/ # Custom error types
- └── errors.go
-```
-
-## Error Handling Improvements
-- Inconsistent timeout handling (some return nil, others error)
-- Missing error context for network/GraphQL failures
-- Raw output mode silently fails on timeout (task.go:118-124)
-
-## Testing Strategy
-- No Go tests currently exist
-- Reference Python tests in contrib/Mythic_Scripting/tests/
-- Need unit tests for client, integration tests for CLI
-- Missing coverage for base64 decoding, UUID extraction, error paths