Using "greenplace" (The Green Place from Mad Max: Fury Road) as the canonical example project/rig name in documentation and help text. This provides a clearer distinction from the actual gastown repo name. Changes: - docs/*.md: Updated all example paths and commands - internal/cmd/*.go: Updated help text examples - internal/templates/: Updated example references - Tests: Updated to use greenplace in example session names Note: Import paths (github.com/steveyegge/gastown) and actual code paths referencing the gastown repo structure are unchanged. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
151 lines
4.0 KiB
Go
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 greenplace "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
|
|
}
|