Address Copilot review feedback for Exa search + .env loading

- Remove unused SanitizeForLog function (dead code)
- Move .env loading before os.IsNotExist early return so fresh installs
  still get env-based configuration
- Differentiate IsNotExist vs real errors in .env loading log message
- Cap Exa results loop to requested count parameter
- Add missing providers to loadProviderEnvOverrides (VLLM, Cerebras,
  VolcEngine, Qwen)
- Run go mod tidy to fix godotenv indirect -> direct require

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
I Putu Eddy Irawan 2026-03-01 09:32:41 +07:00
parent 479958b250
commit da9bc0ee98
3 changed files with 33 additions and 28 deletions

6
go.mod
View file

@ -8,13 +8,16 @@ require (
github.com/bwmarrin/discordgo v0.29.0 github.com/bwmarrin/discordgo v0.29.0
github.com/caarlos0/env/v11 v11.3.1 github.com/caarlos0/env/v11 v11.3.1
github.com/chzyer/readline v1.5.1 github.com/chzyer/readline v1.5.1
github.com/gdamore/tcell/v2 v2.13.8
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.3 github.com/gorilla/websocket v1.5.3
github.com/joho/godotenv v1.5.1
github.com/larksuite/oapi-sdk-go/v3 v3.5.3 github.com/larksuite/oapi-sdk-go/v3 v3.5.3
github.com/mdp/qrterminal/v3 v3.2.1 github.com/mdp/qrterminal/v3 v3.2.1
github.com/mymmrac/telego v1.6.0 github.com/mymmrac/telego v1.6.0
github.com/open-dingtalk/dingtalk-stream-sdk-go v0.9.1 github.com/open-dingtalk/dingtalk-stream-sdk-go v0.9.1
github.com/openai/openai-go/v3 v3.22.0 github.com/openai/openai-go/v3 v3.22.0
github.com/rivo/tview v0.42.0
github.com/slack-go/slack v0.17.3 github.com/slack-go/slack v0.17.3
github.com/spf13/cobra v1.10.2 github.com/spf13/cobra v1.10.2
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.11.1
@ -34,9 +37,7 @@ require (
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/elliotchance/orderedmap/v3 v3.1.0 // indirect github.com/elliotchance/orderedmap/v3 v3.1.0 // indirect
github.com/gdamore/encoding v1.0.1 // indirect github.com/gdamore/encoding v1.0.1 // indirect
github.com/gdamore/tcell/v2 v2.13.8 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
@ -44,7 +45,6 @@ require (
github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 // indirect github.com/petermattis/goid v0.0.0-20260113132338-7c7de50cc741 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/tview v0.42.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/rs/zerolog v1.34.0 // indirect github.com/rs/zerolog v1.34.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect github.com/spf13/pflag v1.0.10 // indirect

View file

@ -6,7 +6,6 @@ import (
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"sync/atomic" "sync/atomic"
"github.com/caarlos0/env/v11" "github.com/caarlos0/env/v11"
@ -590,9 +589,28 @@ type ClawHubRegistryConfig struct {
func LoadConfig(path string) (*Config, error) { func LoadConfig(path string) (*Config, error) {
cfg := DefaultConfig() cfg := DefaultConfig()
// Load .env file from config directory (secrets, API keys, etc.)
// This runs before reading config.json so .env works even on fresh installs.
envFile := filepath.Join(filepath.Dir(path), ".env")
if err := godotenv.Load(envFile); err != nil {
if os.IsNotExist(err) {
log.Printf("[INFO] No .env file found at %s; skipping .env loading", envFile)
} else {
log.Printf("[WARN] Failed to load .env file from %s: %v", envFile, err)
}
}
data, err := os.ReadFile(path) data, err := os.ReadFile(path)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
// No config file — still apply env vars + overrides to default config
if err := env.Parse(cfg); err != nil {
return nil, err
}
loadProviderEnvOverrides(cfg)
if cfg.HasProvidersConfig() {
cfg.ModelList = ConvertProvidersToModelList(cfg)
}
return cfg, nil return cfg, nil
} }
return nil, err return nil, err
@ -616,12 +634,6 @@ func LoadConfig(path string) (*Config, error) {
return nil, err return nil, err
} }
// Load .env file from config directory (secrets, API keys, etc.)
envFile := filepath.Join(filepath.Dir(path), ".env")
if err := godotenv.Load(envFile); err != nil {
log.Printf("[INFO] No .env file loaded from %s: %v", envFile, err)
}
if err := env.Parse(cfg); err != nil { if err := env.Parse(cfg); err != nil {
return nil, err return nil, err
} }
@ -816,6 +828,12 @@ func loadProviderEnvOverrides(cfg *Config) {
{"SHENGSUANYUN", &cfg.Providers.ShengSuanYun.APIKey, &cfg.Providers.ShengSuanYun.APIBase}, {"SHENGSUANYUN", &cfg.Providers.ShengSuanYun.APIKey, &cfg.Providers.ShengSuanYun.APIBase},
{"DEEPSEEK", &cfg.Providers.DeepSeek.APIKey, &cfg.Providers.DeepSeek.APIBase}, {"DEEPSEEK", &cfg.Providers.DeepSeek.APIKey, &cfg.Providers.DeepSeek.APIBase},
{"MISTRAL", &cfg.Providers.Mistral.APIKey, &cfg.Providers.Mistral.APIBase}, {"MISTRAL", &cfg.Providers.Mistral.APIKey, &cfg.Providers.Mistral.APIBase},
{"VLLM", &cfg.Providers.VLLM.APIKey, &cfg.Providers.VLLM.APIBase},
{"CEREBRAS", &cfg.Providers.Cerebras.APIKey, &cfg.Providers.Cerebras.APIBase},
{"VOLCENGINE", &cfg.Providers.VolcEngine.APIKey, &cfg.Providers.VolcEngine.APIBase},
{"QWEN", &cfg.Providers.Qwen.APIKey, &cfg.Providers.Qwen.APIBase},
// Note: GitHubCopilot and Antigravity use different auth patterns (ConnectMode/AuthMethod),
// not standard APIKey/APIBase, so they are not included here.
} }
for _, p := range providers { for _, p := range providers {
if v := os.Getenv("PICOCLAW_PROVIDERS_" + p.name + "_API_KEY"); v != "" { if v := os.Getenv("PICOCLAW_PROVIDERS_" + p.name + "_API_KEY"); v != "" {
@ -826,20 +844,3 @@ func loadProviderEnvOverrides(cfg *Config) {
} }
} }
} }
// SanitizeForLog masks sensitive API key values for safe logging.
func SanitizeForLog(s string) string {
prefixes := []string{"sk-", "xoxb-", "xoxp-", "gsk_", "AIza"}
for _, p := range prefixes {
if strings.HasPrefix(s, p) {
if len(s) > len(p)+4 {
return s[:len(p)+4] + "****"
}
return p + "****"
}
}
if len(s) > 8 {
return s[:8] + "****"
}
return "****"
}

View file

@ -748,7 +748,11 @@ func (p *ExaSearchProvider) Search(ctx context.Context, query string, count int)
} }
var sb strings.Builder var sb strings.Builder
for i, r := range result.Results { maxResults := count
if maxResults > len(result.Results) {
maxResults = len(result.Results)
}
for i, r := range result.Results[:maxResults] {
sb.WriteString(fmt.Sprintf("%d. %s\n URL: %s\n", i+1, r.Title, r.URL)) sb.WriteString(fmt.Sprintf("%d. %s\n URL: %s\n", i+1, r.Title, r.URL))
if r.Text != "" { if r.Text != "" {
snippet := r.Text snippet := r.Text