package cmd import ( "fmt" "time" "github.com/spf13/cobra" "github.com/steveyegge/gastown/internal/style" "github.com/steveyegge/gastown/internal/tmux" ) var ( broadcastRig string broadcastAll bool broadcastDryRun bool ) func init() { broadcastCmd.Flags().StringVar(&broadcastRig, "rig", "", "Only broadcast to workers in this rig") broadcastCmd.Flags().BoolVar(&broadcastAll, "all", false, "Include all agents (mayor, witness, etc.), not just workers") broadcastCmd.Flags().BoolVar(&broadcastDryRun, "dry-run", false, "Show what would be sent without sending") rootCmd.AddCommand(broadcastCmd) } var broadcastCmd = &cobra.Command{ Use: "broadcast ", GroupID: GroupComm, Short: "Send a nudge message to all workers", Long: `Broadcasts a message to all active workers (polecats and crew). By default, only workers (polecats and crew) receive the message. Use --all to include infrastructure agents (mayor, deacon, witness, refinery). The message is sent as a nudge to each worker's Claude Code session. Examples: gt broadcast "Check your mail" gt broadcast --rig gastown "New priority work available" gt broadcast --all "System maintenance in 5 minutes" gt broadcast --dry-run "Test message"`, Args: cobra.ExactArgs(1), RunE: runBroadcast, } func runBroadcast(cmd *cobra.Command, args []string) error { message := args[0] if message == "" { return fmt.Errorf("message cannot be empty") } // Get all agent sessions (including polecats) agents, err := getAgentSessions(true) if err != nil { return fmt.Errorf("listing sessions: %w", err) } // Filter to target agents var targets []*AgentSession for _, agent := range agents { // Filter by rig if specified if broadcastRig != "" && agent.Rig != broadcastRig { continue } // Unless --all, only include workers (crew + polecats) if !broadcastAll { if agent.Type != AgentCrew && agent.Type != AgentPolecat { continue } } targets = append(targets, agent) } if len(targets) == 0 { fmt.Println("No workers running to broadcast to.") if broadcastRig != "" { fmt.Printf(" (filtered by rig: %s)\n", broadcastRig) } return nil } // Dry run - just show what would be sent if broadcastDryRun { fmt.Printf("Would broadcast to %d agent(s):\n\n", len(targets)) for _, agent := range targets { fmt.Printf(" %s %s\n", AgentTypeIcons[agent.Type], formatAgentName(agent)) } fmt.Printf("\nMessage: %s\n", message) return nil } // Send nudges t := tmux.NewTmux() var succeeded, failed int var failures []string fmt.Printf("Broadcasting to %d agent(s)...\n\n", len(targets)) for i, agent := range targets { agentName := formatAgentName(agent) if err := t.NudgeSession(agent.Name, message); err != nil { failed++ failures = append(failures, fmt.Sprintf("%s: %v", agentName, err)) fmt.Printf(" %s %s %s\n", style.ErrorPrefix, AgentTypeIcons[agent.Type], agentName) } else { succeeded++ fmt.Printf(" %s %s %s\n", style.SuccessPrefix, AgentTypeIcons[agent.Type], agentName) } // Small delay between nudges to avoid overwhelming tmux if i < len(targets)-1 { time.Sleep(100 * time.Millisecond) } } fmt.Println() if failed > 0 { fmt.Printf("%s Broadcast complete: %d succeeded, %d failed\n", style.WarningPrefix, succeeded, failed) for _, f := range failures { fmt.Printf(" %s\n", style.Dim.Render(f)) } return fmt.Errorf("%d nudge(s) failed", failed) } fmt.Printf("%s Broadcast complete: %d agent(s) nudged\n", style.SuccessPrefix, succeeded) return nil } // formatAgentName returns a display name for an agent. func formatAgentName(agent *AgentSession) string { switch agent.Type { case AgentMayor: return "mayor" case AgentDeacon: return "deacon" case AgentWitness: return fmt.Sprintf("%s/witness", agent.Rig) case AgentRefinery: return fmt.Sprintf("%s/refinery", agent.Rig) case AgentCrew: return fmt.Sprintf("%s/crew/%s", agent.Rig, agent.AgentName) case AgentPolecat: return fmt.Sprintf("%s/%s", agent.Rig, agent.AgentName) } return agent.Name }