@@ -0,0 +1,74 @@
1+package config
2+
3+import (
4+ "context"
5+ "fmt"
6+ "os"
7+ "strings"
8+ "time"
9+
10+ "github.com/charmbracelet/crush/internal/llm/tools/shell"
11+ "github.com/charmbracelet/crush/internal/logging"
12+)
13+
14+// ExecuteCommand executes a shell command and returns the output
15+// This is a shared utility that can be used by both provider config and tools
16+func ExecuteCommand(ctx context.Context, command string, workingDir string) (string, error) {
17+ if workingDir == "" {
18+ workingDir = WorkingDirectory()
19+ }
20+
21+ persistentShell := shell.GetPersistentShell(workingDir)
22+
23+ stdout, stderr, err := persistentShell.Exec(ctx, command)
24+ if err != nil {
25+ logging.Debug("Command execution failed", "command", command, "error", err, "stderr", stderr)
26+ return "", fmt.Errorf("command execution failed: %w", err)
27+ }
28+
29+ return strings.TrimSpace(stdout), nil
30+}
31+
32+// ResolveAPIKey resolves an API key that can be either:
33+// - A direct string value
34+// - An environment variable (prefixed with $)
35+// - A shell command (wrapped in $(...))
36+func ResolveAPIKey(apiKey string) (string, error) {
37+ if !strings.HasPrefix(apiKey, "$") {
38+ return apiKey, nil
39+ }
40+
41+ if strings.HasPrefix(apiKey, "$(") && strings.HasSuffix(apiKey, ")") {
42+ command := strings.TrimSuffix(strings.TrimPrefix(apiKey, "$("), ")")
43+ logging.Debug("Resolving API key from command", "command", command)
44+ return resolveCommandAPIKey(command)
45+ }
46+
47+ envVar := strings.TrimPrefix(apiKey, "$")
48+ if value := os.Getenv(envVar); value != "" {
49+ logging.Debug("Resolved environment variable", "envVar", envVar, "value", value)
50+ return value, nil
51+ }
52+
53+ logging.Debug("Environment variable not found", "envVar", envVar)
54+
55+ return "", fmt.Errorf("environment variable %s not found", envVar)
56+}
57+
58+// resolveCommandAPIKey executes a command to get an API key, with caching support
59+func resolveCommandAPIKey(command string) (string, error) {
60+ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
61+ defer cancel()
62+
63+ logging.Debug("Executing command for API key", "command", command)
64+
65+ workingDir := WorkingDirectory()
66+
67+ result, err := ExecuteCommand(ctx, command, workingDir)
68+ if err != nil {
69+ return "", fmt.Errorf("failed to execute API key command: %w", err)
70+ }
71+ logging.Debug("Command executed successfully", "command", command, "result", result)
72+ return result, nil
73+}
74+