feat(mail): add bd mail delegate command for agent UX
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 <noreply@anthropic.com>
This commit is contained in:
111
cmd/bd/mail.go
Normal file
111
cmd/bd/mail.go
Normal file
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user