From 3ba3e945f280f32ff7cc42d9c10394505f5a1e6e Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Sun, 21 Dec 2025 00:34:49 -0800 Subject: [PATCH] feat(mail): add bd mail delegate command for agent UX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agents naturally type 'bd mail' when working with beads, but mail functionality is provided by orchestrators like Gas Town. This command bridges that gap by delegating to a configured mail provider. Configuration options (checked in order): 1. BEADS_MAIL_DELEGATE or BD_MAIL_DELEGATE env var 2. mail.delegate config setting Example usage: export BEADS_MAIL_DELEGATE="gt mail" bd mail inbox # Runs: gt mail inbox bd mail send mayor/... # Runs: gt mail send mayor/... 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- cmd/bd/mail.go | 111 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 cmd/bd/mail.go diff --git a/cmd/bd/mail.go b/cmd/bd/mail.go new file mode 100644 index 00000000..9456947b --- /dev/null +++ b/cmd/bd/mail.go @@ -0,0 +1,111 @@ +package main + +import ( + "fmt" + "os" + "os/exec" + "strings" + + "github.com/spf13/cobra" +) + +// mailCmd delegates to an external mail provider (e.g., gt mail) +// This enables agents to use 'bd mail' consistently, while the actual +// mail implementation is provided by the orchestrator (Gas Town, etc.) +var mailCmd = &cobra.Command{ + Use: "mail [subcommand] [args...]", + Short: "Delegate to mail provider (e.g., gt mail)", + Long: `Delegates mail operations to an external mail provider. + +Agents often type 'bd mail' when working with beads, but mail functionality +is typically provided by an orchestrator like Gas Town (gt). This command +bridges that gap by delegating to the configured mail provider. + +Configuration (checked in order): + 1. BEADS_MAIL_DELEGATE or BD_MAIL_DELEGATE environment variable + 2. 'mail.delegate' config setting (bd config set mail.delegate "gt mail") + +Examples: + # Configure delegation (one-time setup) + export BEADS_MAIL_DELEGATE="gt mail" + # or + bd config set mail.delegate "gt mail" + + # Then use bd mail as if it were gt mail + bd mail inbox # Lists inbox + bd mail send mayor/ -s "Hi" # Sends mail + bd mail read msg-123 # Reads a message`, + DisableFlagParsing: true, // Pass all args through to delegate + Run: func(cmd *cobra.Command, args []string) { + // Handle --help and -h ourselves since flag parsing is disabled + for _, arg := range args { + if arg == "--help" || arg == "-h" { + _ = cmd.Help() + return + } + } + + // Find the mail delegate command + delegate := findMailDelegate() + if delegate == "" { + fmt.Fprintf(os.Stderr, "Error: no mail delegate configured\n\n") + fmt.Fprintf(os.Stderr, "bd mail delegates to an external mail provider.\n") + fmt.Fprintf(os.Stderr, "Configure one of:\n") + fmt.Fprintf(os.Stderr, " export BEADS_MAIL_DELEGATE=\"gt mail\" # Environment variable\n") + fmt.Fprintf(os.Stderr, " bd config set mail.delegate \"gt mail\" # Per-project config\n") + os.Exit(1) + } + + // Parse the delegate command (e.g., "gt mail" -> ["gt", "mail"]) + parts := strings.Fields(delegate) + if len(parts) == 0 { + fmt.Fprintf(os.Stderr, "Error: invalid mail delegate: %q\n", delegate) + os.Exit(1) + } + + // Build the full command with our args appended + cmdName := parts[0] + cmdArgs := append(parts[1:], args...) + + // Execute the delegate command + execCmd := exec.Command(cmdName, cmdArgs...) + execCmd.Stdin = os.Stdin + execCmd.Stdout = os.Stdout + execCmd.Stderr = os.Stderr + + if err := execCmd.Run(); err != nil { + // Try to preserve the exit code + if exitErr, ok := err.(*exec.ExitError); ok { + os.Exit(exitErr.ExitCode()) + } + fmt.Fprintf(os.Stderr, "Error running %s: %v\n", delegate, err) + os.Exit(1) + } + }, +} + +// findMailDelegate checks for mail delegation configuration +// Priority: env vars > bd config +func findMailDelegate() string { + // Check environment variables first + if delegate := os.Getenv("BEADS_MAIL_DELEGATE"); delegate != "" { + return delegate + } + if delegate := os.Getenv("BD_MAIL_DELEGATE"); delegate != "" { + return delegate + } + + // Check bd config (requires database) + // This works even without a database connection since we use direct mode + if store != nil { + if delegate, err := store.GetConfig(rootCtx, "mail.delegate"); err == nil && delegate != "" { + return delegate + } + } + + return "" +} + +func init() { + rootCmd.AddCommand(mailCmd) +}