Files
beads/cmd/bd/mail.go
Steve Yegge 1e22cd0bc5 fix(lint): add nosec for mail delegate exec.Command
gosec G204 warning - the cmdName comes from user configuration
(mail_delegate setting), not untrusted input.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-21 15:29:58 -08:00

113 lines
3.4 KiB
Go

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
// #nosec G204 - cmdName comes from user configuration (mail_delegate setting)
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)
}