- Add --all flag to restart all running crew sessions - Add --dry-run flag to preview without restarting - Add --rig filter to target specific rig - Extract restartCrewSession helper for reuse 🤝 Filed gt-1kljv for adding tests 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
309 lines
11 KiB
Go
309 lines
11 KiB
Go
package cmd
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
// Crew command flags
|
|
var (
|
|
crewRig string
|
|
crewBranch bool
|
|
crewJSON bool
|
|
crewForce bool
|
|
crewNoTmux bool
|
|
crewDetached bool
|
|
crewMessage string
|
|
crewAccount string
|
|
crewAll bool
|
|
crewDryRun bool
|
|
)
|
|
|
|
var crewCmd = &cobra.Command{
|
|
Use: "crew",
|
|
GroupID: GroupWorkspace,
|
|
Short: "Manage crew workspaces (user-managed persistent workspaces)",
|
|
Long: `Crew workers are user-managed persistent workspaces within a rig.
|
|
|
|
Unlike polecats which are witness-managed and transient, crew workers are:
|
|
- Persistent: Not auto-garbage-collected
|
|
- User-managed: Overseer controls lifecycle
|
|
- Long-lived identities: recognizable names like dave, emma, fred
|
|
- Gas Town integrated: Mail, handoff mechanics work
|
|
- Tmux optional: Can work in terminal directly
|
|
|
|
Commands:
|
|
gt crew start <name> Start a crew workspace (creates if needed)
|
|
gt crew add <name> Create a new crew workspace
|
|
gt crew list List crew workspaces with status
|
|
gt crew at <name> Attach to crew workspace session
|
|
gt crew remove <name> Remove a crew workspace
|
|
gt crew refresh <name> Context cycling with mail-to-self handoff
|
|
gt crew restart <name> Kill and restart session fresh (alias: rs)
|
|
gt crew status [<name>] Show detailed workspace status`,
|
|
}
|
|
|
|
var crewAddCmd = &cobra.Command{
|
|
Use: "add <name>",
|
|
Short: "Create a new crew workspace",
|
|
Long: `Create a new crew workspace with a clone of the rig repository.
|
|
|
|
The workspace is created at <rig>/crew/<name>/ with:
|
|
- A full git clone of the project repository
|
|
- Mail directory for message delivery
|
|
- CLAUDE.md with crew worker prompting
|
|
- Optional feature branch (crew/<name>)
|
|
|
|
Examples:
|
|
gt crew add dave # Create in current rig
|
|
gt crew add emma --rig gastown # Create in specific rig
|
|
gt crew add fred --branch # Create with feature branch`,
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: runCrewAdd,
|
|
}
|
|
|
|
var crewListCmd = &cobra.Command{
|
|
Use: "list",
|
|
Short: "List crew workspaces with status",
|
|
Long: `List all crew workspaces in a rig with their status.
|
|
|
|
Shows git branch, session state, and git status for each workspace.
|
|
|
|
Examples:
|
|
gt crew list # List in current rig
|
|
gt crew list --rig gastown # List in specific rig
|
|
gt crew list --json # JSON output`,
|
|
RunE: runCrewList,
|
|
}
|
|
|
|
var crewAtCmd = &cobra.Command{
|
|
Use: "at [name]",
|
|
Aliases: []string{"attach"},
|
|
Short: "Attach to crew workspace session",
|
|
Long: `Start or attach to a tmux session for a crew workspace.
|
|
|
|
Creates a new tmux session if none exists, or attaches to existing.
|
|
Use --no-tmux to just print the directory path instead.
|
|
|
|
When run from inside tmux, the session is started but you stay in your
|
|
current pane. Use C-b s to switch to the new session.
|
|
|
|
When run from outside tmux, you are attached to the session (unless
|
|
--detached is specified).
|
|
|
|
Role Discovery:
|
|
If no name is provided, attempts to detect the crew workspace from the
|
|
current directory. If you're in <rig>/crew/<name>/, it will attach to
|
|
that workspace automatically.
|
|
|
|
Examples:
|
|
gt crew at dave # Attach to dave's session
|
|
gt crew at # Auto-detect from cwd
|
|
gt crew at dave --detached # Start session without attaching
|
|
gt crew at dave --no-tmux # Just print path`,
|
|
Args: cobra.MaximumNArgs(1),
|
|
RunE: runCrewAt,
|
|
}
|
|
|
|
var crewRemoveCmd = &cobra.Command{
|
|
Use: "remove <name>",
|
|
Short: "Remove a crew workspace",
|
|
Long: `Remove a crew workspace from the rig.
|
|
|
|
Checks for uncommitted changes and running sessions before removing.
|
|
Use --force to skip checks and remove anyway.
|
|
|
|
Examples:
|
|
gt crew remove dave # Remove with safety checks
|
|
gt crew remove dave --force # Force remove`,
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: runCrewRemove,
|
|
}
|
|
|
|
var crewRefreshCmd = &cobra.Command{
|
|
Use: "refresh <name>",
|
|
Short: "Context cycling with mail-to-self handoff",
|
|
Long: `Cycle a crew workspace session with handoff.
|
|
|
|
Sends a handoff mail to the workspace's own inbox, then restarts the session.
|
|
The new session reads the handoff mail and resumes work.
|
|
|
|
Examples:
|
|
gt crew refresh dave # Refresh with auto-generated handoff
|
|
gt crew refresh dave -m "Working on gt-123" # Add custom message`,
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: runCrewRefresh,
|
|
}
|
|
|
|
var crewStatusCmd = &cobra.Command{
|
|
Use: "status [<name>]",
|
|
Short: "Show detailed workspace status",
|
|
Long: `Show detailed status for crew workspace(s).
|
|
|
|
Displays session state, git status, branch info, and mail inbox status.
|
|
If no name given, shows status for all crew workers.
|
|
|
|
Examples:
|
|
gt crew status # Status of all crew workers
|
|
gt crew status dave # Status of specific worker
|
|
gt crew status --json # JSON output`,
|
|
RunE: runCrewStatus,
|
|
}
|
|
|
|
var crewRestartCmd = &cobra.Command{
|
|
Use: "restart [name]",
|
|
Aliases: []string{"rs"},
|
|
Short: "Kill and restart crew workspace session",
|
|
Long: `Kill the tmux session and restart fresh with Claude.
|
|
|
|
Useful when a crew member gets confused or needs a clean slate.
|
|
Unlike 'refresh', this does NOT send handoff mail - it's a clean start.
|
|
|
|
The command will:
|
|
1. Kill existing tmux session if running
|
|
2. Start fresh session with Claude
|
|
3. Run gt prime to reinitialize context
|
|
|
|
Use --all to restart all running crew sessions across all rigs.
|
|
|
|
Examples:
|
|
gt crew restart dave # Restart dave's session
|
|
gt crew rs emma # Same, using alias
|
|
gt crew restart --all # Restart all running crew sessions
|
|
gt crew restart --all --rig beads # Restart all crew in beads rig
|
|
gt crew restart --all --dry-run # Preview what would be restarted`,
|
|
Args: func(cmd *cobra.Command, args []string) error {
|
|
if crewAll {
|
|
if len(args) > 0 {
|
|
return fmt.Errorf("cannot specify both --all and a name")
|
|
}
|
|
return nil
|
|
}
|
|
if len(args) != 1 {
|
|
return fmt.Errorf("requires exactly 1 argument (or --all)")
|
|
}
|
|
return nil
|
|
},
|
|
RunE: runCrewRestart,
|
|
}
|
|
|
|
var crewRenameCmd = &cobra.Command{
|
|
Use: "rename <old-name> <new-name>",
|
|
Short: "Rename a crew workspace",
|
|
Long: `Rename a crew workspace.
|
|
|
|
Kills any running session, renames the directory, and updates state.
|
|
The new session will use the new name (gt-<rig>-crew-<new-name>).
|
|
|
|
Examples:
|
|
gt crew rename dave david # Rename dave to david
|
|
gt crew rename madmax max # Rename madmax to max`,
|
|
Args: cobra.ExactArgs(2),
|
|
RunE: runCrewRename,
|
|
}
|
|
|
|
var crewPristineCmd = &cobra.Command{
|
|
Use: "pristine [<name>]",
|
|
Short: "Sync crew workspaces with remote",
|
|
Long: `Ensure crew workspace(s) are up-to-date.
|
|
|
|
Runs git pull and bd sync for the specified crew, or all crew workers.
|
|
Reports any uncommitted changes that may need attention.
|
|
|
|
Examples:
|
|
gt crew pristine # Pristine all crew workers
|
|
gt crew pristine dave # Pristine specific worker
|
|
gt crew pristine --json # JSON output`,
|
|
RunE: runCrewPristine,
|
|
}
|
|
|
|
var crewNextCmd = &cobra.Command{
|
|
Use: "next",
|
|
Short: "Switch to next crew session in same rig",
|
|
Hidden: true, // Internal command for tmux keybindings
|
|
RunE: runCrewNext,
|
|
}
|
|
|
|
var crewPrevCmd = &cobra.Command{
|
|
Use: "prev",
|
|
Short: "Switch to previous crew session in same rig",
|
|
Hidden: true, // Internal command for tmux keybindings
|
|
RunE: runCrewPrev,
|
|
}
|
|
|
|
var crewStartCmd = &cobra.Command{
|
|
Use: "start <name>",
|
|
Short: "Start a crew workspace (creates if needed)",
|
|
Long: `Start a crew workspace, creating it if it doesn't exist.
|
|
|
|
This is an alias for 'gt start crew'. It combines 'gt crew add' and 'gt crew at --detached'.
|
|
The crew session starts in the background with Claude running and ready.
|
|
|
|
The name can include the rig in slash format (e.g., gastown/joe).
|
|
If not specified, the rig is inferred from the current directory.
|
|
|
|
Examples:
|
|
gt crew start joe # Start joe in current rig
|
|
gt crew start gastown/joe # Start joe in gastown rig
|
|
gt crew start joe --rig beads # Start joe in beads rig`,
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: runCrewStart,
|
|
}
|
|
|
|
func init() {
|
|
// Add flags
|
|
crewAddCmd.Flags().StringVar(&crewRig, "rig", "", "Rig to create crew workspace in")
|
|
crewAddCmd.Flags().BoolVar(&crewBranch, "branch", false, "Create a feature branch (crew/<name>)")
|
|
|
|
crewListCmd.Flags().StringVar(&crewRig, "rig", "", "Filter by rig name")
|
|
crewListCmd.Flags().BoolVar(&crewJSON, "json", false, "Output as JSON")
|
|
|
|
crewAtCmd.Flags().StringVar(&crewRig, "rig", "", "Rig to use")
|
|
crewAtCmd.Flags().BoolVar(&crewNoTmux, "no-tmux", false, "Just print directory path")
|
|
crewAtCmd.Flags().BoolVarP(&crewDetached, "detached", "d", false, "Start session without attaching")
|
|
crewAtCmd.Flags().StringVar(&crewAccount, "account", "", "Claude Code account handle to use (overrides default)")
|
|
|
|
crewRemoveCmd.Flags().StringVar(&crewRig, "rig", "", "Rig to use")
|
|
crewRemoveCmd.Flags().BoolVar(&crewForce, "force", false, "Force remove (skip safety checks)")
|
|
|
|
crewRefreshCmd.Flags().StringVar(&crewRig, "rig", "", "Rig to use")
|
|
crewRefreshCmd.Flags().StringVarP(&crewMessage, "message", "m", "", "Custom handoff message")
|
|
|
|
crewStatusCmd.Flags().StringVar(&crewRig, "rig", "", "Filter by rig name")
|
|
crewStatusCmd.Flags().BoolVar(&crewJSON, "json", false, "Output as JSON")
|
|
|
|
crewRenameCmd.Flags().StringVar(&crewRig, "rig", "", "Rig to use")
|
|
|
|
crewPristineCmd.Flags().StringVar(&crewRig, "rig", "", "Filter by rig name")
|
|
crewPristineCmd.Flags().BoolVar(&crewJSON, "json", false, "Output as JSON")
|
|
|
|
crewRestartCmd.Flags().StringVar(&crewRig, "rig", "", "Rig to use (filter when using --all)")
|
|
crewRestartCmd.Flags().BoolVar(&crewAll, "all", false, "Restart all running crew sessions")
|
|
crewRestartCmd.Flags().BoolVar(&crewDryRun, "dry-run", false, "Show what would be restarted without restarting")
|
|
|
|
crewStartCmd.Flags().StringVar(&crewRig, "rig", "", "Rig to use")
|
|
crewStartCmd.Flags().StringVar(&crewAccount, "account", "", "Claude Code account handle to use")
|
|
|
|
// Add subcommands
|
|
crewCmd.AddCommand(crewAddCmd)
|
|
crewCmd.AddCommand(crewListCmd)
|
|
crewCmd.AddCommand(crewAtCmd)
|
|
crewCmd.AddCommand(crewRemoveCmd)
|
|
crewCmd.AddCommand(crewRefreshCmd)
|
|
crewCmd.AddCommand(crewStatusCmd)
|
|
crewCmd.AddCommand(crewRenameCmd)
|
|
crewCmd.AddCommand(crewPristineCmd)
|
|
crewCmd.AddCommand(crewRestartCmd)
|
|
|
|
// Add --session flag to next/prev commands for tmux key binding support
|
|
// When run via run-shell, tmux session context may be wrong, so we pass it explicitly
|
|
crewNextCmd.Flags().StringVarP(&crewCycleSession, "session", "s", "", "tmux session name (for key bindings)")
|
|
crewPrevCmd.Flags().StringVarP(&crewCycleSession, "session", "s", "", "tmux session name (for key bindings)")
|
|
crewCmd.AddCommand(crewNextCmd)
|
|
crewCmd.AddCommand(crewPrevCmd)
|
|
crewCmd.AddCommand(crewStartCmd)
|
|
|
|
rootCmd.AddCommand(crewCmd)
|
|
}
|