diff --git a/internal/cmd/mail.go b/internal/cmd/mail.go
index 26525e18..858934ca 100644
--- a/internal/cmd/mail.go
+++ b/internal/cmd/mail.go
@@ -22,6 +22,8 @@ var (
mailInboxJSON bool
mailReadJSON bool
mailInboxUnread bool
+ mailCheckInject bool
+ mailCheckJSON bool
)
var mailCmd = &cobra.Command{
@@ -87,6 +89,25 @@ This closes the message in beads.`,
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() {
// Send flags
mailSendCmd.Flags().StringVarP(&mailSubject, "subject", "s", "", "Message subject (required)")
@@ -102,11 +123,16 @@ func init() {
// Read flags
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
mailCmd.AddCommand(mailSendCmd)
mailCmd.AddCommand(mailInboxCmd)
mailCmd.AddCommand(mailReadCmd)
mailCmd.AddCommand(mailDeleteCmd)
+ mailCmd.AddCommand(mailCheckCmd)
rootCmd.AddCommand(mailCmd)
}
@@ -363,3 +389,81 @@ func detectSender() string {
// Default to 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("")
+ 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 ' for a specific message.")
+ fmt.Println("")
+ }
+ 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
+}
diff --git a/internal/cmd/prime.go b/internal/cmd/prime.go
index 86929cea..fe358931 100644
--- a/internal/cmd/prime.go
+++ b/internal/cmd/prime.go
@@ -156,10 +156,15 @@ func outputMayorContext(ctx RoleContext) {
fmt.Println("- Monitor overall system health")
fmt.Println()
fmt.Println("## Key Commands")
+ fmt.Println("- `gt mail inbox` - Check your messages")
+ fmt.Println("- `gt mail read ` - Read a specific message")
fmt.Println("- `gt status` - Show overall town status")
fmt.Println("- `gt rigs` - List all rigs")
fmt.Println("- `bd ready` - Issues ready to work")
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))
}