Add Windows support to Claude Code Usage (ccusage) extension#28449
Add Windows support to Claude Code Usage (ccusage) extension#28449owendavidprice wants to merge 4 commits into
Conversation
The extension shells out to `npx ccusage` via Raycast's useExec, which
calls child_process.spawn directly. Three things broke on the Raycast
for Windows beta:
- spawn("npx", ...) throws ENOENT because Windows needs the npx.cmd
shim, only found when run through a shell. Pass shell: true on
Windows so cmd.exe resolves it via PATHEXT.
- The PATH was built with the Unix ':' separator and hard-coded
Homebrew/nvm paths. Use path.delimiter and os.homedir(); add a
Windows branch that includes %APPDATA%\npm.
- It relied on $HOME (unset on Windows). Use os.homedir() throughout.
Unix version-manager (nvm/fnm/n/volta) probing and env vars are gated
to non-Windows. Declares Windows in the platforms array.
Verified: ray build succeeds and ray develop loads the extension into
Raycast on Windows, showing live ccusage data across all views.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Thank you for your contribution! 🎉 🔔 @nyatinte @zhuravel @GarrickZ2 @pernielsentikaer @raulgg @ridemountainpig @bendrucker you might want to have a look. You can use this guide to learn how to check out the Pull Request locally in order to test it. 📋 Quick checkout commandsBRANCH="ext/ccusage-windows-support"
FORK_URL="https://github.com/owendavidprice/extensions.git"
EXTENSION_NAME="ccusage"
REPO_NAME="extensions"
git clone -n --depth=1 --filter=tree:0 -b $BRANCH $FORK_URL
cd $REPO_NAME
git sparse-checkout set --no-cone "extensions/$EXTENSION_NAME"
git checkout
cd "extensions/$EXTENSION_NAME"
npm install && npm run devWe're currently experiencing a high volume of incoming requests. As a result, the initial review may take up to 10-15 business days. |
Greptile SummaryThis PR adds Windows support to the ccusage extension by fixing three platform-specific spawn failures with no changes to macOS behavior. The approach is well-scoped: a new
Confidence Score: 5/5Safe to merge; all platform-specific code is gated behind process.platform === win32 and macOS behaviour is unchanged. The changes are surgical and well-contained: every Windows-specific branch is guarded by isWindows, dependency version choices (execa@5, path-key@3) correctly target the last CJS-compatible releases for this non-ESM project, and the consolidation of CLI invocation into run-ccusage.ts removes duplication without altering logic. No regressions were identified on the macOS path. No files require special attention. Important Files Changed
Reviews (4): Last reviewed commit: "fix(ccusage): enable shell on Windows so..." | Re-trigger Greptile |
child_process.exec (used by the no-view tools and claude-code-stats) types ExecOptions.shell as string only, so shell: true failed tsc. exec already runs via a shell; only useExec's spawn needed the hint. Point both at process.env.ComSpec (a string) on Windows, which is type-compatible with exec and still resolves the npx.cmd shim. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
bendrucker
left a comment
There was a problem hiding this comment.
The Windows changes here mostly re-implement, on a second code path, behavior that a dependency already in the tree provides. The inline comment describes that in more detail. I'm not a Windows user so any references to behavior there is just from public sources, please do verify them.
There are other code quality comments I'd leave but most of them will fall away when cross-platform details are delegated to dependencies.
|
|
||
| const isWindows = process.platform === "win32"; | ||
|
|
||
| export const getExecOptions = () => { |
There was a problem hiding this comment.
The extension has two exec paths that behave differently on Windows. The views call useExec, which wraps execa and uses cross-spawn to resolve .cmd / PATHEXT shims and to read PATH case-insensitively via path-key. The AI tools in src/tools/ call raw child_process.exec, which goes through none of that. Every Windows branch in this PR exists to hand-build, on the second path, what the first path already gets from the dependency.
I'd suggest routing the AI tools through the same execa call the views use (execa("npx", ["ccusage@latest", ...]), or cross-spawn directly) instead of child_process.exec with a manually assembled environment. That removes the divergence at the source. Concretely, it lets this PR drop:
- The
shell: cmd.exeoption (line 49).child_process.execalready runs through cmd.exe viaComSpec, and execa resolves.cmdshims throughPATHEXT, so forcing a shell is redundant and only adds cmd.exe quoting risk (cmd command reference). - The
{ ...process.env, PATH }casing hazard (line 9). On Windows the variable is conventionallyPath, names are case-insensitive, and the environment block is sorted case-insensitively, so spreadingprocess.envand then adding a secondPATHkey leaves two entries for one logical variable with ambiguous precedence (environment variable names are case-insensitive on Windows, the environment block is sorted case-insensitively).cross-spawnhandles this for you; if you still inject PATH yourself, write it back to the existing key (for example viapath-key) rather than adding a new one. - The
isWindowsbranches innode-path-resolver.tsand thegetWindowsNodePathshelper, including itsProgramFiles\nodejsentry (which the inherited PATH already covers, and which the helper's own comment does not mention).
The one Windows detail likely worth keeping is %APPDATA%\npm, the npm global prefix where global .cmd shims live, if Raycast does not inherit the full user PATH. If you find that's needed, prepend just that one directory rather than reconstructing the PATH per platform.
I'd want the env-casing behavior confirmed on a real Windows box regardless of which approach we take, since it determines whether the tools resolve ccusage.cmd at all.
|
Heads-up for anyone skimming the run history: the first CI run on this PR went red on a TypeScript error ( All checks are green on the current head commit (build, ESLint, Prettier, |
Addresses @bendrucker's review. The AI tools and claude-code-stats used child_process.exec, a second exec path that didn't get the cross-platform handling useExec already has. They now go through execa (which useExec also uses): cross-spawn resolves .cmd shims via PATHEXT, so the manual Windows handling is removed. - New runCcusage() helper invokes the CLI via execa with an args array, honouring useDirectCcusageCommand and customNpxPath; tools and claude-code-stats use it. Removes child_process.exec usage. - Drops the shell option entirely (execa needs no shell; exec already ran through cmd.exe). - Fixes the PATH-casing hazard: writes PATH to its real key via path-key instead of spreading process.env and adding a second PATH entry that on Windows (conventionally Path) left two ambiguous entries. - node-path-resolver Windows branch reduced to just the npm global prefix (%APPDATA%\npm); removes getWindowsNodePaths and the ProgramFiles entry. Verified on Windows: execa resolves npx.cmd with no shell; tsc, ray build, eslint, prettier, and the jest suite (15 tests) all pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Thanks for the thorough review — you're right that the cleaner fix is to remove the second exec path rather than re-implement cross-platform behaviour on it. Pushed What changed
Verified on a real Windows box (the env-casing behaviour you asked about specifically): with You mentioned other code-quality points that would fall away once this was delegated to the dependency — happy to take those on if any remain. |
Real-world testing on the Raycast for Windows beta showed the views still failing with 'ccusage command failed'. Runtime diagnostics proved: Raycast does inherit the full user PATH (npx resolves) and the command runs fine via execa — but the views use useExec, which spawns through child_process.spawn. On Windows raw spawn throws ENOENT for the npx.cmd/ccusage.cmd shims unless run through a shell (PATHEXT). Enable shell on Windows in getExecOptions. Verified: the views now load live data in Raycast. tsc/build/eslint/prettier and 15 jest tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Update after testing on a real Windows box (Raycast for Windows beta) — there's a wrinkle worth flagging. After the execa refactor, the views still failed with "ccusage command failed", so I added runtime diagnostics inside Raycast's extension process to capture exactly what happens. Findings:
So the cross-spawn delegation we were counting on for
If you'd prefer the |
|
Hmm seems like this could be worth fixing upstream versus working around in the extension, even in this narrower way |
|
Drafting this until we have time to look into the other PR you opened @bendrucker |
Description
Adds Windows support to the Claude Code Usage (ccusage) extension so it runs on the Raycast for Windows beta. No new features — purely platform-compatibility fixes, and macOS behaviour is unchanged.
The extension shells out to
npx ccusagevia Raycast'suseExec, which callschild_process.spawndirectly. Three things broke on Windows:spawn("npx", …)throwsENOENT— Windows needs thenpx.cmdshim, which is only found when the command runs through a shell. Now passesshell: trueon Windows socmd.exeresolves it viaPATHEXT.PATHwas Unix-only — built with the:separator and hard-coded Homebrew/nvm paths. Now usespath.delimiterandos.homedir(), with a Windows branch that adds%APPDATA%\npm.$HOME(unset on Windows) — switched toos.homedir()throughout.Unix-only version-manager (nvm/fnm/n/volta) probing and env vars are gated to non-Windows.
keychain-accessalready fell back to reading~/.claude/.credentials.json(works on Windows viahomedir()), and the usage-limits client usesfetch, so both were already cross-platform.Changes
src/utils/exec-options.ts—shell: trueon Windows;path.delimiter;os.homedir(); gate nvm/fnm env vars to Unixsrc/utils/node-path-resolver.ts— Windows PATH branch (%APPDATA%\npm, nodejs dir);path.delimiter/homedir(); skip Unix version-manager probing on Windowspackage.json— addWindowstoplatformsCHANGELOG.md— new entryChecklist
npm run buildsucceeds (verified on Windows)ray developloads the extension into Raycast on Windows and shows live ccusage data across all viewsCHANGELOG.mdwith a new entry using the{PR_MERGE_DATE}placeholderprocess.platform === "win32"