shell.go

 1package config
 2
 3import (
 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
16func 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 $(...))
36func 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
59func 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