feat: add bd mail commands and identity configuration (bd-kwro.6, bd-kwro.7)
- Add `bd mail send/inbox/read/ack` commands for inter-agent messaging - Implement GetIdentity() with priority chain: flag > BEADS_IDENTITY env > config.yaml > git user.name > hostname - Messages are stored as issues with type=message for git-native communication - Support both daemon and direct mode for all mail operations 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ package config
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -89,10 +90,12 @@ func Initialize() error {
|
||||
// These are bound explicitly for backward compatibility
|
||||
_ = v.BindEnv("flush-debounce", "BEADS_FLUSH_DEBOUNCE")
|
||||
_ = v.BindEnv("auto-start-daemon", "BEADS_AUTO_START_DAEMON")
|
||||
_ = v.BindEnv("identity", "BEADS_IDENTITY")
|
||||
|
||||
// Set defaults for additional settings
|
||||
v.SetDefault("flush-debounce", "30s")
|
||||
v.SetDefault("auto-start-daemon", true)
|
||||
v.SetDefault("identity", "")
|
||||
|
||||
// Routing configuration defaults
|
||||
v.SetDefault("routing.mode", "auto")
|
||||
@@ -195,15 +198,50 @@ func GetMultiRepoConfig() *MultiRepoConfig {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// Check if repos.primary is set (indicates multi-repo mode)
|
||||
primary := v.GetString("repos.primary")
|
||||
if primary == "" {
|
||||
return nil // Single-repo mode
|
||||
}
|
||||
|
||||
|
||||
return &MultiRepoConfig{
|
||||
Primary: primary,
|
||||
Additional: v.GetStringSlice("repos.additional"),
|
||||
}
|
||||
}
|
||||
|
||||
// GetIdentity resolves the user's identity for messaging.
|
||||
// Priority chain:
|
||||
// 1. flagValue (if non-empty, from --identity flag)
|
||||
// 2. BEADS_IDENTITY env var / config.yaml identity field (via viper)
|
||||
// 3. git config user.name
|
||||
// 4. hostname
|
||||
//
|
||||
// This is used as the sender field in bd mail commands.
|
||||
func GetIdentity(flagValue string) string {
|
||||
// 1. Command-line flag takes precedence
|
||||
if flagValue != "" {
|
||||
return flagValue
|
||||
}
|
||||
|
||||
// 2. BEADS_IDENTITY env var or config.yaml identity (viper handles both)
|
||||
if identity := GetString("identity"); identity != "" {
|
||||
return identity
|
||||
}
|
||||
|
||||
// 3. git config user.name
|
||||
cmd := exec.Command("git", "config", "user.name")
|
||||
if output, err := cmd.Output(); err == nil {
|
||||
if gitUser := strings.TrimSpace(string(output)); gitUser != "" {
|
||||
return gitUser
|
||||
}
|
||||
}
|
||||
|
||||
// 4. hostname
|
||||
if hostname, err := os.Hostname(); err == nil && hostname != "" {
|
||||
return hostname
|
||||
}
|
||||
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
@@ -408,3 +408,92 @@ func TestNilViperBehavior(t *testing.T) {
|
||||
// Set should not panic
|
||||
Set("any-key", "any-value") // Should be a no-op
|
||||
}
|
||||
|
||||
func TestGetIdentity(t *testing.T) {
|
||||
// Initialize viper
|
||||
err := Initialize()
|
||||
if err != nil {
|
||||
t.Fatalf("Initialize() returned error: %v", err)
|
||||
}
|
||||
|
||||
// Test 1: Flag value takes precedence over everything
|
||||
got := GetIdentity("flag-identity")
|
||||
if got != "flag-identity" {
|
||||
t.Errorf("GetIdentity(flag-identity) = %q, want \"flag-identity\"", got)
|
||||
}
|
||||
|
||||
// Test 2: Empty flag falls back to BEADS_IDENTITY env
|
||||
oldEnv := os.Getenv("BEADS_IDENTITY")
|
||||
_ = os.Setenv("BEADS_IDENTITY", "env-identity")
|
||||
defer func() {
|
||||
if oldEnv == "" {
|
||||
_ = os.Unsetenv("BEADS_IDENTITY")
|
||||
} else {
|
||||
_ = os.Setenv("BEADS_IDENTITY", oldEnv)
|
||||
}
|
||||
}()
|
||||
|
||||
// Re-initialize to pick up env var
|
||||
_ = Initialize()
|
||||
got = GetIdentity("")
|
||||
if got != "env-identity" {
|
||||
t.Errorf("GetIdentity(\"\") with BEADS_IDENTITY = %q, want \"env-identity\"", got)
|
||||
}
|
||||
|
||||
// Test 3: Without flag or env, should fall back to git user.name or hostname
|
||||
_ = os.Unsetenv("BEADS_IDENTITY")
|
||||
_ = Initialize()
|
||||
got = GetIdentity("")
|
||||
// We can't predict the exact value (depends on git config and hostname)
|
||||
// but it should not be empty or "unknown" on most systems
|
||||
if got == "" {
|
||||
t.Error("GetIdentity(\"\") without flag or env returned empty string")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetIdentityFromConfig(t *testing.T) {
|
||||
// Create a temporary directory for config file
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Create a config file with identity
|
||||
configContent := `identity: config-identity`
|
||||
beadsDir := filepath.Join(tmpDir, ".beads")
|
||||
if err := os.MkdirAll(beadsDir, 0750); err != nil {
|
||||
t.Fatalf("failed to create .beads directory: %v", err)
|
||||
}
|
||||
|
||||
configPath := filepath.Join(beadsDir, "config.yaml")
|
||||
if err := os.WriteFile(configPath, []byte(configContent), 0600); err != nil {
|
||||
t.Fatalf("failed to write config file: %v", err)
|
||||
}
|
||||
|
||||
// Clear BEADS_IDENTITY env var
|
||||
oldEnv := os.Getenv("BEADS_IDENTITY")
|
||||
_ = os.Unsetenv("BEADS_IDENTITY")
|
||||
defer func() {
|
||||
if oldEnv != "" {
|
||||
_ = os.Setenv("BEADS_IDENTITY", oldEnv)
|
||||
}
|
||||
}()
|
||||
|
||||
// Change to tmp directory
|
||||
t.Chdir(tmpDir)
|
||||
|
||||
// Initialize viper
|
||||
err := Initialize()
|
||||
if err != nil {
|
||||
t.Fatalf("Initialize() returned error: %v", err)
|
||||
}
|
||||
|
||||
// Test that identity from config file is used
|
||||
got := GetIdentity("")
|
||||
if got != "config-identity" {
|
||||
t.Errorf("GetIdentity(\"\") with config file = %q, want \"config-identity\"", got)
|
||||
}
|
||||
|
||||
// Test that flag still takes precedence
|
||||
got = GetIdentity("flag-override")
|
||||
if got != "flag-override" {
|
||||
t.Errorf("GetIdentity(flag-override) = %q, want \"flag-override\"", got)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user