Skip to content

Commit a3a0d96

Browse files
authored
Merge pull request #128 from shelltime/claude/issue-127-20251005-0754
fix(model): use user shell for ccusage command execution
2 parents 08a72b9 + 3212ab9 commit a3a0d96

1 file changed

Lines changed: 46 additions & 4 deletions

File tree

model/ccusage_service.go

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"os"
1010
"os/exec"
1111
"os/user"
12+
"runtime"
13+
"strings"
1214
"time"
1315
)
1416

@@ -231,16 +233,21 @@ func (s *ccUsageService) collectData(ctx context.Context, since time.Time) (*CCU
231233
slog.Debug("Using since parameter", "sinceDate", sinceDate, "since", since)
232234
}
233235

236+
// Get user's shell to run command with proper environment
237+
shell := getUserShell()
238+
234239
var cmd *exec.Cmd
235240
if bunxErr == nil {
236241
// Use bunx if available
237-
cmd = exec.CommandContext(ctx, bunxPath, args...)
238-
slog.Debug("Using bunx to collect ccusage data")
242+
cmdStr := bunxPath + " " + shellEscapeArgs(args)
243+
cmd = exec.CommandContext(ctx, shell, "-c", cmdStr)
244+
slog.Debug("Using bunx to collect ccusage data", "shell", shell)
239245
} else {
240246
// Fall back to npx with --yes flag to auto-accept prompts
241247
npxArgs := append([]string{"--yes"}, args...)
242-
cmd = exec.CommandContext(ctx, npxPath, npxArgs...)
243-
slog.Debug("Using npx to collect ccusage data")
248+
cmdStr := npxPath + " " + shellEscapeArgs(npxArgs)
249+
cmd = exec.CommandContext(ctx, shell, "-c", cmdStr)
250+
slog.Debug("Using npx to collect ccusage data", "shell", shell)
244251
}
245252

246253
// Execute the command
@@ -410,3 +417,38 @@ func (s *ccUsageService) sendData(ctx context.Context, endpoint Endpoint, data *
410417
slog.Debug("CCUsage data sent successfully", "successCount", resp.SuccessCount, "totalCount", resp.TotalCount)
411418
return nil
412419
}
420+
421+
// getUserShell returns the user's shell executable path
422+
// It checks the SHELL environment variable first, then falls back to sensible defaults
423+
func getUserShell() string {
424+
// Try to get the shell from environment variable
425+
shell := os.Getenv("SHELL")
426+
if shell != "" {
427+
return shell
428+
}
429+
430+
// Fall back to platform-specific defaults
431+
if runtime.GOOS == "windows" {
432+
// On Windows, prefer PowerShell, fall back to cmd
433+
if pwsh, err := exec.LookPath("pwsh"); err == nil {
434+
return pwsh
435+
}
436+
if powershell, err := exec.LookPath("powershell"); err == nil {
437+
return powershell
438+
}
439+
return "cmd"
440+
}
441+
442+
// On Unix-like systems, default to sh (POSIX shell)
443+
return "/bin/sh"
444+
}
445+
446+
// shellEscapeArgs joins arguments with spaces and escapes them for safe shell execution
447+
func shellEscapeArgs(args []string) string {
448+
escaped := make([]string, len(args))
449+
for i, arg := range args {
450+
// Simple shell escaping: wrap in single quotes and escape single quotes
451+
escaped[i] = "'" + strings.ReplaceAll(arg, "'", "'\"'\"'") + "'"
452+
}
453+
return strings.Join(escaped, " ")
454+
}

0 commit comments

Comments
 (0)