shell.go 2.2 KB

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