Files
gastown/internal/cmd/broadcast.go
Steve Yegge f86a73c2f0 feat: Add gcloud-style command grouping to gt help output
Organize 43 commands into 7 logical groups using cobra's built-in
AddGroup/GroupID feature:

- Work Management: spawn, sling, hook, handoff, done, mol, mq, etc.
- Agent Management: mayor, witness, refinery, deacon, polecat, etc.
- Communication: mail, nudge, broadcast, peek
- Services: daemon, start, stop, up, down, shutdown
- Workspace: rig, crew, init, install, git-init, namepool
- Configuration: account, theme, hooks, issue, completion
- Diagnostics: status, doctor, prime, version, help

Also renamed molecule to mol as the primary command name
(molecule is now an alias).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-25 02:32:01 -08:00

151 lines
4.0 KiB
Go

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 <message>",
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
}