feat: add CLI swarm commands (create, start, status, list, land, cancel)
- gt swarm create: Create swarm from beads epic with workers - gt swarm start: Activate a created swarm - gt swarm status: Show detailed swarm info with task progress - gt swarm list: List swarms with filtering by rig/status - gt swarm land: Manually trigger landing to target branch - gt swarm cancel: Cancel an active swarm - SwarmStore: JSON-based persistence for swarm state - Support for --json output on status and list commands Closes gt-kmn.7 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
663
internal/cmd/swarm.go
Normal file
663
internal/cmd/swarm.go
Normal file
@@ -0,0 +1,663 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/steveyegge/gastown/internal/config"
|
||||
"github.com/steveyegge/gastown/internal/git"
|
||||
"github.com/steveyegge/gastown/internal/rig"
|
||||
"github.com/steveyegge/gastown/internal/style"
|
||||
"github.com/steveyegge/gastown/internal/swarm"
|
||||
"github.com/steveyegge/gastown/internal/workspace"
|
||||
)
|
||||
|
||||
// Swarm command flags
|
||||
var (
|
||||
swarmEpic string
|
||||
swarmTasks []string
|
||||
swarmWorkers []string
|
||||
swarmStart bool
|
||||
swarmStatusJSON bool
|
||||
swarmListRig string
|
||||
swarmListStatus string
|
||||
swarmListJSON bool
|
||||
swarmTarget string
|
||||
)
|
||||
|
||||
var swarmCmd = &cobra.Command{
|
||||
Use: "swarm",
|
||||
Short: "Manage multi-agent swarms",
|
||||
Long: `Manage coordinated multi-agent work units (swarms).
|
||||
|
||||
A swarm coordinates multiple polecats working on related tasks from a shared
|
||||
base commit. Work is merged to an integration branch, then landed to main.`,
|
||||
}
|
||||
|
||||
var swarmCreateCmd = &cobra.Command{
|
||||
Use: "create <rig>",
|
||||
Short: "Create a new swarm",
|
||||
Long: `Create a new swarm in a rig.
|
||||
|
||||
Creates a swarm that coordinates multiple polecats working on tasks from
|
||||
a beads epic. All workers branch from the same base commit.
|
||||
|
||||
Examples:
|
||||
gt swarm create gastown --epic gt-abc --worker Toast --worker Nux
|
||||
gt swarm create gastown --epic gt-abc --worker Toast --start`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runSwarmCreate,
|
||||
}
|
||||
|
||||
var swarmStatusCmd = &cobra.Command{
|
||||
Use: "status <swarm-id>",
|
||||
Short: "Show swarm status",
|
||||
Long: `Show detailed status for a swarm.
|
||||
|
||||
Displays swarm metadata, task progress, worker assignments, and integration
|
||||
branch status.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runSwarmStatus,
|
||||
}
|
||||
|
||||
var swarmListCmd = &cobra.Command{
|
||||
Use: "list [rig]",
|
||||
Short: "List swarms",
|
||||
Long: `List swarms, optionally filtered by rig or status.
|
||||
|
||||
Examples:
|
||||
gt swarm list
|
||||
gt swarm list gastown
|
||||
gt swarm list --status=active
|
||||
gt swarm list gastown --status=landed`,
|
||||
Args: cobra.MaximumNArgs(1),
|
||||
RunE: runSwarmList,
|
||||
}
|
||||
|
||||
var swarmLandCmd = &cobra.Command{
|
||||
Use: "land <swarm-id>",
|
||||
Short: "Land a swarm to main",
|
||||
Long: `Manually trigger landing for a completed swarm.
|
||||
|
||||
Merges the integration branch to the target branch (usually main).
|
||||
Normally this is done automatically by the Refinery.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runSwarmLand,
|
||||
}
|
||||
|
||||
var swarmCancelCmd = &cobra.Command{
|
||||
Use: "cancel <swarm-id>",
|
||||
Short: "Cancel a swarm",
|
||||
Long: `Cancel an active swarm.
|
||||
|
||||
Marks the swarm as cancelled and optionally cleans up branches.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runSwarmCancel,
|
||||
}
|
||||
|
||||
var swarmStartCmd = &cobra.Command{
|
||||
Use: "start <swarm-id>",
|
||||
Short: "Start a created swarm",
|
||||
Long: `Start a swarm that was created without --start.
|
||||
|
||||
Transitions the swarm from 'created' to 'active' state.`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runSwarmStart,
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Create flags
|
||||
swarmCreateCmd.Flags().StringVar(&swarmEpic, "epic", "", "Beads epic ID for this swarm (required)")
|
||||
swarmCreateCmd.Flags().StringSliceVar(&swarmWorkers, "worker", nil, "Polecat names to assign (repeatable)")
|
||||
swarmCreateCmd.Flags().BoolVar(&swarmStart, "start", false, "Start swarm immediately after creation")
|
||||
swarmCreateCmd.Flags().StringVar(&swarmTarget, "target", "main", "Target branch for landing")
|
||||
swarmCreateCmd.MarkFlagRequired("epic")
|
||||
|
||||
// Status flags
|
||||
swarmStatusCmd.Flags().BoolVar(&swarmStatusJSON, "json", false, "Output as JSON")
|
||||
|
||||
// List flags
|
||||
swarmListCmd.Flags().StringVar(&swarmListStatus, "status", "", "Filter by status (active, landed, cancelled, failed)")
|
||||
swarmListCmd.Flags().BoolVar(&swarmListJSON, "json", false, "Output as JSON")
|
||||
|
||||
// Add subcommands
|
||||
swarmCmd.AddCommand(swarmCreateCmd)
|
||||
swarmCmd.AddCommand(swarmStartCmd)
|
||||
swarmCmd.AddCommand(swarmStatusCmd)
|
||||
swarmCmd.AddCommand(swarmListCmd)
|
||||
swarmCmd.AddCommand(swarmLandCmd)
|
||||
swarmCmd.AddCommand(swarmCancelCmd)
|
||||
|
||||
rootCmd.AddCommand(swarmCmd)
|
||||
}
|
||||
|
||||
// SwarmStore manages persistent swarm state.
|
||||
type SwarmStore struct {
|
||||
path string
|
||||
Swarms map[string]*swarm.Swarm `json:"swarms"`
|
||||
}
|
||||
|
||||
// LoadSwarmStore loads swarm state from disk.
|
||||
func LoadSwarmStore(rigPath string) (*SwarmStore, error) {
|
||||
storePath := filepath.Join(rigPath, ".gastown", "swarms.json")
|
||||
store := &SwarmStore{
|
||||
path: storePath,
|
||||
Swarms: make(map[string]*swarm.Swarm),
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(storePath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return store, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(data, store); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
store.path = storePath
|
||||
|
||||
return store, nil
|
||||
}
|
||||
|
||||
// Save persists swarm state to disk.
|
||||
func (s *SwarmStore) Save() error {
|
||||
// Ensure directory exists
|
||||
dir := filepath.Dir(s.path)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(s, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(s.path, data, 0644)
|
||||
}
|
||||
|
||||
// getSwarmRig gets a rig by name.
|
||||
func getSwarmRig(rigName string) (*rig.Rig, string, error) {
|
||||
townRoot, err := workspace.FindFromCwdOrError()
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("not in a Gas Town workspace: %w", err)
|
||||
}
|
||||
|
||||
rigsConfigPath := filepath.Join(townRoot, "config", "rigs.json")
|
||||
rigsConfig, err := config.LoadRigsConfig(rigsConfigPath)
|
||||
if err != nil {
|
||||
rigsConfig = &config.RigsConfig{Rigs: make(map[string]config.RigEntry)}
|
||||
}
|
||||
|
||||
g := git.NewGit(townRoot)
|
||||
rigMgr := rig.NewManager(townRoot, rigsConfig, g)
|
||||
r, err := rigMgr.GetRig(rigName)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("rig '%s' not found", rigName)
|
||||
}
|
||||
|
||||
return r, townRoot, nil
|
||||
}
|
||||
|
||||
// getAllRigs returns all discovered rigs.
|
||||
func getAllRigs() ([]*rig.Rig, string, error) {
|
||||
townRoot, err := workspace.FindFromCwdOrError()
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("not in a Gas Town workspace: %w", err)
|
||||
}
|
||||
|
||||
rigsConfigPath := filepath.Join(townRoot, "config", "rigs.json")
|
||||
rigsConfig, err := config.LoadRigsConfig(rigsConfigPath)
|
||||
if err != nil {
|
||||
rigsConfig = &config.RigsConfig{Rigs: make(map[string]config.RigEntry)}
|
||||
}
|
||||
|
||||
g := git.NewGit(townRoot)
|
||||
rigMgr := rig.NewManager(townRoot, rigsConfig, g)
|
||||
rigs, err := rigMgr.DiscoverRigs()
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return rigs, townRoot, nil
|
||||
}
|
||||
|
||||
func runSwarmCreate(cmd *cobra.Command, args []string) error {
|
||||
rigName := args[0]
|
||||
|
||||
r, _, err := getSwarmRig(rigName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load swarm store
|
||||
store, err := LoadSwarmStore(r.Path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading swarm store: %w", err)
|
||||
}
|
||||
|
||||
// Check if swarm already exists
|
||||
if _, exists := store.Swarms[swarmEpic]; exists {
|
||||
return fmt.Errorf("swarm for epic '%s' already exists", swarmEpic)
|
||||
}
|
||||
|
||||
// Create swarm manager to use its Create logic
|
||||
mgr := swarm.NewManager(r)
|
||||
sw, err := mgr.Create(swarmEpic, swarmWorkers, swarmTarget)
|
||||
if err != nil {
|
||||
return fmt.Errorf("creating swarm: %w", err)
|
||||
}
|
||||
|
||||
// Start if requested
|
||||
if swarmStart {
|
||||
if err := mgr.Start(swarmEpic); err != nil {
|
||||
return fmt.Errorf("starting swarm: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Get the updated swarm
|
||||
sw, _ = mgr.GetSwarm(swarmEpic)
|
||||
|
||||
// Save to store
|
||||
store.Swarms[swarmEpic] = sw
|
||||
if err := store.Save(); err != nil {
|
||||
return fmt.Errorf("saving swarm store: %w", err)
|
||||
}
|
||||
|
||||
// Output
|
||||
fmt.Printf("%s Created swarm %s\n\n", style.Bold.Render("✓"), sw.ID)
|
||||
fmt.Printf(" Epic: %s\n", sw.EpicID)
|
||||
fmt.Printf(" Rig: %s\n", sw.RigName)
|
||||
fmt.Printf(" Base commit: %s\n", truncate(sw.BaseCommit, 8))
|
||||
fmt.Printf(" Integration: %s\n", sw.Integration)
|
||||
fmt.Printf(" Target: %s\n", sw.TargetBranch)
|
||||
fmt.Printf(" State: %s\n", sw.State)
|
||||
fmt.Printf(" Workers: %s\n", strings.Join(sw.Workers, ", "))
|
||||
fmt.Printf(" Tasks: %d\n", len(sw.Tasks))
|
||||
|
||||
if !swarmStart {
|
||||
fmt.Printf("\n %s\n", style.Dim.Render("Use --start or 'gt swarm start' to activate"))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func runSwarmStart(cmd *cobra.Command, args []string) error {
|
||||
swarmID := args[0]
|
||||
|
||||
// Find the swarm
|
||||
rigs, _, err := getAllRigs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var store *SwarmStore
|
||||
|
||||
for _, r := range rigs {
|
||||
s, err := LoadSwarmStore(r.Path)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, exists := s.Swarms[swarmID]; exists {
|
||||
store = s
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if store == nil {
|
||||
return fmt.Errorf("swarm '%s' not found", swarmID)
|
||||
}
|
||||
|
||||
sw := store.Swarms[swarmID]
|
||||
|
||||
if sw.State != swarm.SwarmCreated {
|
||||
return fmt.Errorf("swarm is not in 'created' state (current: %s)", sw.State)
|
||||
}
|
||||
|
||||
sw.State = swarm.SwarmActive
|
||||
sw.UpdatedAt = time.Now()
|
||||
|
||||
if err := store.Save(); err != nil {
|
||||
return fmt.Errorf("saving state: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s Swarm %s started\n", style.Bold.Render("✓"), swarmID)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runSwarmStatus(cmd *cobra.Command, args []string) error {
|
||||
swarmID := args[0]
|
||||
|
||||
// Find the swarm across all rigs
|
||||
rigs, _, err := getAllRigs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var foundSwarm *swarm.Swarm
|
||||
var foundRig *rig.Rig
|
||||
|
||||
for _, r := range rigs {
|
||||
store, err := LoadSwarmStore(r.Path)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if sw, exists := store.Swarms[swarmID]; exists {
|
||||
foundSwarm = sw
|
||||
foundRig = r
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if foundSwarm == nil {
|
||||
return fmt.Errorf("swarm '%s' not found", swarmID)
|
||||
}
|
||||
|
||||
// JSON output
|
||||
if swarmStatusJSON {
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc.SetIndent("", " ")
|
||||
return enc.Encode(foundSwarm)
|
||||
}
|
||||
|
||||
// Human-readable output
|
||||
sw := foundSwarm
|
||||
summary := sw.Summary()
|
||||
|
||||
fmt.Printf("%s %s\n\n", style.Bold.Render("Swarm:"), sw.ID)
|
||||
fmt.Printf(" Rig: %s\n", foundRig.Name)
|
||||
fmt.Printf(" Epic: %s\n", sw.EpicID)
|
||||
fmt.Printf(" State: %s\n", stateStyle(sw.State))
|
||||
fmt.Printf(" Created: %s\n", sw.CreatedAt.Format(time.RFC3339))
|
||||
fmt.Printf(" Updated: %s\n", sw.UpdatedAt.Format(time.RFC3339))
|
||||
fmt.Printf(" Base commit: %s\n", truncate(sw.BaseCommit, 8))
|
||||
fmt.Printf(" Integration: %s\n", sw.Integration)
|
||||
fmt.Printf(" Target: %s\n", sw.TargetBranch)
|
||||
|
||||
fmt.Printf("\n%s\n", style.Bold.Render("Workers:"))
|
||||
if len(sw.Workers) == 0 {
|
||||
fmt.Printf(" %s\n", style.Dim.Render("(none assigned)"))
|
||||
} else {
|
||||
for _, w := range sw.Workers {
|
||||
fmt.Printf(" • %s\n", w)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("\n%s %d%% (%d/%d tasks merged)\n",
|
||||
style.Bold.Render("Progress:"),
|
||||
sw.Progress(),
|
||||
summary.MergedTasks,
|
||||
summary.TotalTasks)
|
||||
|
||||
fmt.Printf("\n%s\n", style.Bold.Render("Tasks:"))
|
||||
if len(sw.Tasks) == 0 {
|
||||
fmt.Printf(" %s\n", style.Dim.Render("(no tasks loaded)"))
|
||||
} else {
|
||||
for _, task := range sw.Tasks {
|
||||
status := taskStateIcon(task.State)
|
||||
assignee := ""
|
||||
if task.Assignee != "" {
|
||||
assignee = fmt.Sprintf(" [%s]", task.Assignee)
|
||||
}
|
||||
fmt.Printf(" %s %s: %s%s\n", status, task.IssueID, task.Title, assignee)
|
||||
}
|
||||
}
|
||||
|
||||
if sw.Error != "" {
|
||||
fmt.Printf("\n%s %s\n", style.Bold.Render("Error:"), sw.Error)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func runSwarmList(cmd *cobra.Command, args []string) error {
|
||||
rigs, _, err := getAllRigs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Filter by rig if specified
|
||||
if len(args) > 0 {
|
||||
rigName := args[0]
|
||||
var filtered []*rig.Rig
|
||||
for _, r := range rigs {
|
||||
if r.Name == rigName {
|
||||
filtered = append(filtered, r)
|
||||
}
|
||||
}
|
||||
if len(filtered) == 0 {
|
||||
return fmt.Errorf("rig '%s' not found", rigName)
|
||||
}
|
||||
rigs = filtered
|
||||
}
|
||||
|
||||
// Collect all swarms
|
||||
type swarmEntry struct {
|
||||
Swarm *swarm.Swarm
|
||||
Rig string
|
||||
}
|
||||
var allSwarms []swarmEntry
|
||||
|
||||
for _, r := range rigs {
|
||||
store, err := LoadSwarmStore(r.Path)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, sw := range store.Swarms {
|
||||
// Filter by status if specified
|
||||
if swarmListStatus != "" {
|
||||
if !matchesStatus(sw.State, swarmListStatus) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
allSwarms = append(allSwarms, swarmEntry{Swarm: sw, Rig: r.Name})
|
||||
}
|
||||
}
|
||||
|
||||
// JSON output
|
||||
if swarmListJSON {
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc.SetIndent("", " ")
|
||||
return enc.Encode(allSwarms)
|
||||
}
|
||||
|
||||
// Human-readable output
|
||||
if len(allSwarms) == 0 {
|
||||
fmt.Println("No swarms found.")
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("%s\n\n", style.Bold.Render("Swarms"))
|
||||
for _, entry := range allSwarms {
|
||||
sw := entry.Swarm
|
||||
summary := sw.Summary()
|
||||
fmt.Printf(" %s %s [%s]\n",
|
||||
stateStyle(sw.State),
|
||||
sw.ID,
|
||||
entry.Rig)
|
||||
fmt.Printf(" %d workers, %d/%d tasks merged (%d%%)\n",
|
||||
summary.WorkerCount,
|
||||
summary.MergedTasks,
|
||||
summary.TotalTasks,
|
||||
sw.Progress())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func runSwarmLand(cmd *cobra.Command, args []string) error {
|
||||
swarmID := args[0]
|
||||
|
||||
// Find the swarm
|
||||
rigs, _, err := getAllRigs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var foundRig *rig.Rig
|
||||
var store *SwarmStore
|
||||
|
||||
for _, r := range rigs {
|
||||
s, err := LoadSwarmStore(r.Path)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, exists := s.Swarms[swarmID]; exists {
|
||||
foundRig = r
|
||||
store = s
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if foundRig == nil {
|
||||
return fmt.Errorf("swarm '%s' not found", swarmID)
|
||||
}
|
||||
|
||||
sw := store.Swarms[swarmID]
|
||||
|
||||
// Check state
|
||||
if sw.State != swarm.SwarmMerging {
|
||||
return fmt.Errorf("swarm must be in 'merging' state to land (current: %s)", sw.State)
|
||||
}
|
||||
|
||||
// Create manager and land
|
||||
mgr := swarm.NewManager(foundRig)
|
||||
// Reload swarm into manager
|
||||
mgr.Create(sw.EpicID, sw.Workers, sw.TargetBranch)
|
||||
mgr.UpdateState(sw.ID, sw.State)
|
||||
|
||||
fmt.Printf("Landing swarm %s to %s...\n", swarmID, sw.TargetBranch)
|
||||
|
||||
if err := mgr.LandToMain(swarmID); err != nil {
|
||||
return fmt.Errorf("landing swarm: %w", err)
|
||||
}
|
||||
|
||||
// Update state
|
||||
sw.State = swarm.SwarmLanded
|
||||
sw.UpdatedAt = time.Now()
|
||||
if err := store.Save(); err != nil {
|
||||
return fmt.Errorf("saving state: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s Swarm %s landed to %s\n", style.Bold.Render("✓"), swarmID, sw.TargetBranch)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runSwarmCancel(cmd *cobra.Command, args []string) error {
|
||||
swarmID := args[0]
|
||||
|
||||
// Find the swarm
|
||||
rigs, _, err := getAllRigs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var store *SwarmStore
|
||||
|
||||
for _, r := range rigs {
|
||||
s, err := LoadSwarmStore(r.Path)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, exists := s.Swarms[swarmID]; exists {
|
||||
store = s
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if store == nil {
|
||||
return fmt.Errorf("swarm '%s' not found", swarmID)
|
||||
}
|
||||
|
||||
sw := store.Swarms[swarmID]
|
||||
|
||||
if sw.State.IsTerminal() {
|
||||
return fmt.Errorf("swarm already in terminal state: %s", sw.State)
|
||||
}
|
||||
|
||||
sw.State = swarm.SwarmCancelled
|
||||
sw.UpdatedAt = time.Now()
|
||||
|
||||
if err := store.Save(); err != nil {
|
||||
return fmt.Errorf("saving state: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%s Swarm %s cancelled\n", style.Bold.Render("✓"), swarmID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
func truncate(s string, n int) string {
|
||||
if len(s) <= n {
|
||||
return s
|
||||
}
|
||||
return s[:n]
|
||||
}
|
||||
|
||||
func stateStyle(state swarm.SwarmState) string {
|
||||
switch state {
|
||||
case swarm.SwarmCreated:
|
||||
return style.Dim.Render("○ created")
|
||||
case swarm.SwarmActive:
|
||||
return style.Bold.Render("● active")
|
||||
case swarm.SwarmMerging:
|
||||
return style.Bold.Render("⟳ merging")
|
||||
case swarm.SwarmLanded:
|
||||
return style.Bold.Render("✓ landed")
|
||||
case swarm.SwarmFailed:
|
||||
return style.Dim.Render("✗ failed")
|
||||
case swarm.SwarmCancelled:
|
||||
return style.Dim.Render("⊘ cancelled")
|
||||
default:
|
||||
return string(state)
|
||||
}
|
||||
}
|
||||
|
||||
func taskStateIcon(state swarm.TaskState) string {
|
||||
switch state {
|
||||
case swarm.TaskPending:
|
||||
return style.Dim.Render("○")
|
||||
case swarm.TaskAssigned:
|
||||
return style.Dim.Render("◐")
|
||||
case swarm.TaskInProgress:
|
||||
return style.Bold.Render("●")
|
||||
case swarm.TaskReview:
|
||||
return style.Bold.Render("◉")
|
||||
case swarm.TaskMerged:
|
||||
return style.Bold.Render("✓")
|
||||
case swarm.TaskFailed:
|
||||
return style.Dim.Render("✗")
|
||||
default:
|
||||
return "?"
|
||||
}
|
||||
}
|
||||
|
||||
func matchesStatus(state swarm.SwarmState, filter string) bool {
|
||||
filter = strings.ToLower(filter)
|
||||
switch filter {
|
||||
case "active":
|
||||
return state.IsActive()
|
||||
case "landed":
|
||||
return state == swarm.SwarmLanded
|
||||
case "cancelled":
|
||||
return state == swarm.SwarmCancelled
|
||||
case "failed":
|
||||
return state == swarm.SwarmFailed
|
||||
case "terminal":
|
||||
return state.IsTerminal()
|
||||
default:
|
||||
return string(state) == filter
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user