feat: add gt mail check command and update prime for GGT cutover
- Add 'gt mail check' with --inject flag for Claude Code hooks - Update Mayor prime output to include mail commands and startup info - Enables full cutover from PGT 'town' commands to GGT 'gt' commands 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,8 @@ var (
|
|||||||
mailInboxJSON bool
|
mailInboxJSON bool
|
||||||
mailReadJSON bool
|
mailReadJSON bool
|
||||||
mailInboxUnread bool
|
mailInboxUnread bool
|
||||||
|
mailCheckInject bool
|
||||||
|
mailCheckJSON bool
|
||||||
)
|
)
|
||||||
|
|
||||||
var mailCmd = &cobra.Command{
|
var mailCmd = &cobra.Command{
|
||||||
@@ -87,6 +89,25 @@ This closes the message in beads.`,
|
|||||||
RunE: runMailDelete,
|
RunE: runMailDelete,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mailCheckCmd = &cobra.Command{
|
||||||
|
Use: "check",
|
||||||
|
Short: "Check for new mail (for hooks)",
|
||||||
|
Long: `Check for new mail - useful for Claude Code hooks.
|
||||||
|
|
||||||
|
Exit codes (normal mode):
|
||||||
|
0 - New mail available
|
||||||
|
1 - No new mail
|
||||||
|
|
||||||
|
Exit codes (--inject mode):
|
||||||
|
0 - Always (hooks should never block)
|
||||||
|
Output: system-reminder if mail exists, silent if no mail
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
gt mail check # Simple check
|
||||||
|
gt mail check --inject # For hooks`,
|
||||||
|
RunE: runMailCheck,
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// Send flags
|
// Send flags
|
||||||
mailSendCmd.Flags().StringVarP(&mailSubject, "subject", "s", "", "Message subject (required)")
|
mailSendCmd.Flags().StringVarP(&mailSubject, "subject", "s", "", "Message subject (required)")
|
||||||
@@ -102,11 +123,16 @@ func init() {
|
|||||||
// Read flags
|
// Read flags
|
||||||
mailReadCmd.Flags().BoolVar(&mailReadJSON, "json", false, "Output as JSON")
|
mailReadCmd.Flags().BoolVar(&mailReadJSON, "json", false, "Output as JSON")
|
||||||
|
|
||||||
|
// Check flags
|
||||||
|
mailCheckCmd.Flags().BoolVar(&mailCheckInject, "inject", false, "Output format for Claude Code hooks")
|
||||||
|
mailCheckCmd.Flags().BoolVar(&mailCheckJSON, "json", false, "Output as JSON")
|
||||||
|
|
||||||
// Add subcommands
|
// Add subcommands
|
||||||
mailCmd.AddCommand(mailSendCmd)
|
mailCmd.AddCommand(mailSendCmd)
|
||||||
mailCmd.AddCommand(mailInboxCmd)
|
mailCmd.AddCommand(mailInboxCmd)
|
||||||
mailCmd.AddCommand(mailReadCmd)
|
mailCmd.AddCommand(mailReadCmd)
|
||||||
mailCmd.AddCommand(mailDeleteCmd)
|
mailCmd.AddCommand(mailDeleteCmd)
|
||||||
|
mailCmd.AddCommand(mailCheckCmd)
|
||||||
|
|
||||||
rootCmd.AddCommand(mailCmd)
|
rootCmd.AddCommand(mailCmd)
|
||||||
}
|
}
|
||||||
@@ -363,3 +389,81 @@ func detectSender() string {
|
|||||||
// Default to mayor
|
// Default to mayor
|
||||||
return "mayor/"
|
return "mayor/"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runMailCheck(cmd *cobra.Command, args []string) error {
|
||||||
|
// Find workspace
|
||||||
|
workDir, err := findBeadsWorkDir()
|
||||||
|
if err != nil {
|
||||||
|
if mailCheckInject {
|
||||||
|
// Inject mode: always exit 0, silent on error
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("not in a Gas Town workspace: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine which inbox
|
||||||
|
address := detectSender()
|
||||||
|
|
||||||
|
// Get mailbox
|
||||||
|
router := mail.NewRouter(workDir)
|
||||||
|
mailbox, err := router.GetMailbox(address)
|
||||||
|
if err != nil {
|
||||||
|
if mailCheckInject {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("getting mailbox: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count unread
|
||||||
|
_, unread, err := mailbox.Count()
|
||||||
|
if err != nil {
|
||||||
|
if mailCheckInject {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("counting messages: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSON output
|
||||||
|
if mailCheckJSON {
|
||||||
|
result := map[string]interface{}{
|
||||||
|
"address": address,
|
||||||
|
"unread": unread,
|
||||||
|
"has_new": unread > 0,
|
||||||
|
}
|
||||||
|
enc := json.NewEncoder(os.Stdout)
|
||||||
|
enc.SetIndent("", " ")
|
||||||
|
return enc.Encode(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inject mode: output system-reminder if mail exists
|
||||||
|
if mailCheckInject {
|
||||||
|
if unread > 0 {
|
||||||
|
// Get subjects for context
|
||||||
|
messages, _ := mailbox.ListUnread()
|
||||||
|
var subjects []string
|
||||||
|
for _, msg := range messages {
|
||||||
|
subjects = append(subjects, fmt.Sprintf("- From %s: %s", msg.From, msg.Subject))
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("<system-reminder>")
|
||||||
|
fmt.Printf("You have %d unread message(s) in your inbox.\n\n", unread)
|
||||||
|
for _, s := range subjects {
|
||||||
|
fmt.Println(s)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Println("Run 'gt mail inbox' to see your messages, or 'gt mail read <id>' for a specific message.")
|
||||||
|
fmt.Println("</system-reminder>")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal mode
|
||||||
|
if unread > 0 {
|
||||||
|
fmt.Printf("%s %d unread message(s)\n", style.Bold.Render("📬"), unread)
|
||||||
|
os.Exit(0)
|
||||||
|
} else {
|
||||||
|
fmt.Println("No new mail")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -156,10 +156,15 @@ func outputMayorContext(ctx RoleContext) {
|
|||||||
fmt.Println("- Monitor overall system health")
|
fmt.Println("- Monitor overall system health")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
fmt.Println("## Key Commands")
|
fmt.Println("## Key Commands")
|
||||||
|
fmt.Println("- `gt mail inbox` - Check your messages")
|
||||||
|
fmt.Println("- `gt mail read <id>` - Read a specific message")
|
||||||
fmt.Println("- `gt status` - Show overall town status")
|
fmt.Println("- `gt status` - Show overall town status")
|
||||||
fmt.Println("- `gt rigs` - List all rigs")
|
fmt.Println("- `gt rigs` - List all rigs")
|
||||||
fmt.Println("- `bd ready` - Issues ready to work")
|
fmt.Println("- `bd ready` - Issues ready to work")
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
fmt.Println("## Startup")
|
||||||
|
fmt.Println("Check for handoff messages with 🤝 HANDOFF in subject - continue predecessor's work.")
|
||||||
|
fmt.Println()
|
||||||
fmt.Printf("Town root: %s\n", style.Dim.Render(ctx.TownRoot))
|
fmt.Printf("Town root: %s\n", style.Dim.Render(ctx.TownRoot))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user