feat: add gt mail mark-read command for desire path (bd-rjuu6)

Adds mark-read and mark-unread commands that allow marking messages
as read without archiving them. Uses a "read" label to track status.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
beads/crew/fang
2026-01-11 00:03:35 -08:00
committed by Steve Yegge
parent 2c73cf35f1
commit 1418b1123a
4 changed files with 196 additions and 9 deletions

View File

@@ -191,6 +191,38 @@ Examples:
RunE: runMailArchive,
}
var mailMarkReadCmd = &cobra.Command{
Use: "mark-read <message-id> [message-id...]",
Short: "Mark messages as read without archiving",
Long: `Mark one or more messages as read without removing them from inbox.
This adds a 'read' label to the message, which is reflected in the inbox display.
The message remains in your inbox (unlike archive which closes/removes it).
Use case: You've read a message but want to keep it visible in your inbox
for reference or follow-up.
Examples:
gt mail mark-read hq-abc123
gt mail mark-read hq-abc123 hq-def456`,
Args: cobra.MinimumNArgs(1),
RunE: runMailMarkRead,
}
var mailMarkUnreadCmd = &cobra.Command{
Use: "mark-unread <message-id> [message-id...]",
Short: "Mark messages as unread",
Long: `Mark one or more messages as unread.
This removes the 'read' label from the message.
Examples:
gt mail mark-unread hq-abc123
gt mail mark-unread hq-abc123 hq-def456`,
Args: cobra.MinimumNArgs(1),
RunE: runMailMarkUnread,
}
var mailCheckCmd = &cobra.Command{
Use: "check",
Short: "Check for new mail (for hooks)",
@@ -438,6 +470,8 @@ func init() {
mailCmd.AddCommand(mailPeekCmd)
mailCmd.AddCommand(mailDeleteCmd)
mailCmd.AddCommand(mailArchiveCmd)
mailCmd.AddCommand(mailMarkReadCmd)
mailCmd.AddCommand(mailMarkUnreadCmd)
mailCmd.AddCommand(mailCheckCmd)
mailCmd.AddCommand(mailThreadCmd)
mailCmd.AddCommand(mailReplyCmd)

View File

@@ -292,6 +292,98 @@ func runMailArchive(cmd *cobra.Command, args []string) error {
return nil
}
func runMailMarkRead(cmd *cobra.Command, args []string) error {
// Determine which inbox
address := detectSender()
// All mail uses town beads (two-level architecture)
workDir, err := findMailWorkDir()
if err != nil {
return fmt.Errorf("not in a Gas Town workspace: %w", err)
}
// Get mailbox
router := mail.NewRouter(workDir)
mailbox, err := router.GetMailbox(address)
if err != nil {
return fmt.Errorf("getting mailbox: %w", err)
}
// Mark all specified messages as read
marked := 0
var errors []string
for _, msgID := range args {
if err := mailbox.MarkReadOnly(msgID); err != nil {
errors = append(errors, fmt.Sprintf("%s: %v", msgID, err))
} else {
marked++
}
}
// Report results
if len(errors) > 0 {
fmt.Printf("%s Marked %d/%d messages as read\n",
style.Bold.Render("⚠"), marked, len(args))
for _, e := range errors {
fmt.Printf(" Error: %s\n", e)
}
return fmt.Errorf("failed to mark %d messages", len(errors))
}
if len(args) == 1 {
fmt.Printf("%s Message marked as read\n", style.Bold.Render("✓"))
} else {
fmt.Printf("%s Marked %d messages as read\n", style.Bold.Render("✓"), marked)
}
return nil
}
func runMailMarkUnread(cmd *cobra.Command, args []string) error {
// Determine which inbox
address := detectSender()
// All mail uses town beads (two-level architecture)
workDir, err := findMailWorkDir()
if err != nil {
return fmt.Errorf("not in a Gas Town workspace: %w", err)
}
// Get mailbox
router := mail.NewRouter(workDir)
mailbox, err := router.GetMailbox(address)
if err != nil {
return fmt.Errorf("getting mailbox: %w", err)
}
// Mark all specified messages as unread
marked := 0
var errors []string
for _, msgID := range args {
if err := mailbox.MarkUnreadOnly(msgID); err != nil {
errors = append(errors, fmt.Sprintf("%s: %v", msgID, err))
} else {
marked++
}
}
// Report results
if len(errors) > 0 {
fmt.Printf("%s Marked %d/%d messages as unread\n",
style.Bold.Render("⚠"), marked, len(args))
for _, e := range errors {
fmt.Printf(" Error: %s\n", e)
}
return fmt.Errorf("failed to mark %d messages", len(errors))
}
if len(args) == 1 {
fmt.Printf("%s Message marked as unread\n", style.Bold.Render("✓"))
} else {
fmt.Printf("%s Marked %d messages as unread\n", style.Bold.Render("✓"), marked)
}
return nil
}
func runMailClear(cmd *cobra.Command, args []string) error {
// Determine which inbox to clear (target arg or auto-detect)
address := ""