Fix Windows Claude CLI detection (#2145)
This commit is contained in:
parent
673e558f49
commit
8370d78e71
|
|
@ -158,6 +158,16 @@ describe('resolveClaudeCommand', () => {
|
|||
expect(resolveClaudeCommand({ platform: 'darwin', pathEnv: '', homePath: root })).toBe(bunPath)
|
||||
})
|
||||
|
||||
it('finds native Windows claude.exe in user-local bin', () => {
|
||||
const root = mkdtempSync(join(tmpdir(), 'orca-claude-command-'))
|
||||
const nativePath = join(root, '.local', 'bin', 'claude.exe')
|
||||
makeExecutable(nativePath)
|
||||
|
||||
expect(resolveClaudeCommand({ platform: 'win32', pathEnv: '', homePath: root })).toBe(
|
||||
nativePath
|
||||
)
|
||||
})
|
||||
|
||||
it('returns the bare command when no filesystem candidate exists', () => {
|
||||
const root = mkdtempSync(join(tmpdir(), 'orca-claude-command-'))
|
||||
|
||||
|
|
@ -195,4 +205,12 @@ describe('getVersionManagerBinPaths', () => {
|
|||
expect(paths).toContain(join(root, '.local', 'share', 'pnpm'))
|
||||
expect(paths).not.toContain(join(root, 'Library', 'pnpm'))
|
||||
})
|
||||
|
||||
it('includes Windows user-local bin for native CLI installers', () => {
|
||||
const root = mkdtempSync(join(tmpdir(), 'orca-vm-paths-'))
|
||||
const paths = getVersionManagerBinPaths({ platform: 'win32', pathEnv: '', homePath: root })
|
||||
|
||||
expect(paths).toContain(join(root, '.local', 'bin'))
|
||||
expect(paths).toContain(join(root, 'AppData', 'Roaming', 'npm'))
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -98,6 +98,9 @@ function getVersionManagerDirectories(
|
|||
}
|
||||
|
||||
if (platform === 'win32') {
|
||||
// Why: Anthropic's native Windows installer places claude.exe here, and
|
||||
// GUI-launched Orca may not inherit the user's PATH entry for it.
|
||||
directories.push(join(homePath, '.local', 'bin'))
|
||||
directories.push(join(homePath, 'AppData', 'Roaming', 'npm'))
|
||||
directories.push(join(homePath, 'AppData', 'Local', 'pnpm'))
|
||||
directories.push(join(homePath, 'AppData', 'Local', 'Yarn', 'bin'))
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { homedir } from 'os'
|
||||
import { join } from 'path'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
|
|
@ -70,9 +71,9 @@ describe('patchPackagedProcessPath', () => {
|
|||
// fallback install locations for the opencode and Pi CLI install scripts.
|
||||
// Without them on PATH, GUI-launched Orca reports both as "Not installed"
|
||||
// even when `which` resolves them in the user's shell.
|
||||
expect(segments).toContain('/Users/tester/.opencode/bin')
|
||||
expect(segments).toContain('/Users/tester/.vite-plus/bin')
|
||||
expect(segments).toContain('/Users/tester/bin')
|
||||
expect(segments).toContain(join('/Users/tester', '.opencode/bin'))
|
||||
expect(segments).toContain(join('/Users/tester', '.vite-plus/bin'))
|
||||
expect(segments).toContain(join('/Users/tester', 'bin'))
|
||||
})
|
||||
|
||||
it('leaves PATH untouched when the app is not packaged', async () => {
|
||||
|
|
@ -88,6 +89,23 @@ describe('patchPackagedProcessPath', () => {
|
|||
|
||||
expect(process.env.PATH).toBe('/usr/bin:/bin')
|
||||
})
|
||||
|
||||
it('prepends Windows user-local CLI dirs for packaged Start Menu launches', async () => {
|
||||
const { app } = await import('electron')
|
||||
const { patchPackagedProcessPath } = await import('./configure-process')
|
||||
|
||||
setPlatform('win32')
|
||||
Object.defineProperty(app, 'isPackaged', { configurable: true, value: true })
|
||||
const pathDelimiter = process.platform === 'win32' ? ';' : ':'
|
||||
process.env.PATH = `C:\\Windows\\System32${pathDelimiter}C:\\Windows`
|
||||
|
||||
patchPackagedProcessPath()
|
||||
|
||||
const segments = (process.env.PATH ?? '').split(pathDelimiter)
|
||||
const userLocalBin = join(homedir(), '.local', 'bin')
|
||||
expect(segments).toContain(userLocalBin)
|
||||
expect(segments.indexOf(userLocalBin)).toBeLessThan(segments.indexOf('C:\\Windows\\System32'))
|
||||
})
|
||||
})
|
||||
|
||||
describe('configureDevUserDataPath', () => {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@ import { getMainE2EConfig } from '../e2e-config'
|
|||
|
||||
const DEV_PARENT_SHUTDOWN_GRACE_MS = 3000
|
||||
|
||||
function getProcessPathDelimiter(): string {
|
||||
return process.platform === 'win32' ? ';' : ':'
|
||||
}
|
||||
|
||||
function requestDevParentShutdown(): void {
|
||||
app.quit()
|
||||
|
||||
|
|
@ -36,35 +40,39 @@ export function installUncaughtPipeErrorGuard(): void {
|
|||
}
|
||||
|
||||
export function patchPackagedProcessPath(): void {
|
||||
if (!app.isPackaged || process.platform === 'win32') {
|
||||
if (!app.isPackaged) {
|
||||
return
|
||||
}
|
||||
|
||||
const home = process.env.HOME ?? ''
|
||||
const extraPaths = [
|
||||
'/opt/homebrew/bin',
|
||||
'/opt/homebrew/sbin',
|
||||
'/usr/local/bin',
|
||||
'/usr/local/sbin',
|
||||
'/snap/bin',
|
||||
'/home/linuxbrew/.linuxbrew/bin',
|
||||
'/nix/var/nix/profiles/default/bin'
|
||||
]
|
||||
const extraPaths: string[] = []
|
||||
|
||||
if (home) {
|
||||
if (process.platform !== 'win32') {
|
||||
extraPaths.push(
|
||||
join(home, 'bin'),
|
||||
join(home, '.local/bin'),
|
||||
join(home, '.nix-profile/bin'),
|
||||
// Why: several agent CLIs ship install scripts that drop binaries into
|
||||
// tool-specific ~/.<name>/bin directories (opencode's documented fallback,
|
||||
// Pi's vite-plus installer). GUI-launched Electron inherits a minimal PATH
|
||||
// without shell rc files, so these stay invisible to `which` probes — and
|
||||
// the Agents settings page reports them as "Not installed" even when the
|
||||
// user can run them from Terminal. See stablyai/orca#829.
|
||||
join(home, '.opencode/bin'),
|
||||
join(home, '.vite-plus/bin')
|
||||
'/opt/homebrew/bin',
|
||||
'/opt/homebrew/sbin',
|
||||
'/usr/local/bin',
|
||||
'/usr/local/sbin',
|
||||
'/snap/bin',
|
||||
'/home/linuxbrew/.linuxbrew/bin',
|
||||
'/nix/var/nix/profiles/default/bin'
|
||||
)
|
||||
|
||||
if (home) {
|
||||
extraPaths.push(
|
||||
join(home, 'bin'),
|
||||
join(home, '.local/bin'),
|
||||
join(home, '.nix-profile/bin'),
|
||||
// Why: several agent CLIs ship install scripts that drop binaries into
|
||||
// tool-specific ~/.<name>/bin directories (opencode's documented fallback,
|
||||
// Pi's vite-plus installer). GUI-launched Electron inherits a minimal PATH
|
||||
// without shell rc files, so these stay invisible to `which` probes — and
|
||||
// the Agents settings page reports them as "Not installed" even when the
|
||||
// user can run them from Terminal. See stablyai/orca#829.
|
||||
join(home, '.opencode/bin'),
|
||||
join(home, '.vite-plus/bin')
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Why: CLI tools installed via Node version managers (nvm, volta, asdf, fnm,
|
||||
|
|
@ -72,14 +80,20 @@ export function patchPackagedProcessPath(): void {
|
|||
// resolveCodexCommand() can locate the codex binary in these directories, but
|
||||
// spawning it still fails if node itself isn't in PATH. Adding version manager
|
||||
// bin paths here fixes all spawn sites (login, rate limits, usage tracking).
|
||||
// On Windows this also seeds user-local installer dirs, since shell hydration
|
||||
// is POSIX-only and Start Menu launches can miss user-level PATH updates.
|
||||
extraPaths.push(...getVersionManagerBinPaths())
|
||||
|
||||
const currentPath = process.env.PATH ?? ''
|
||||
const existing = new Set(currentPath.split(':'))
|
||||
const pathKey = process.platform === 'win32' && process.env.Path !== undefined ? 'Path' : 'PATH'
|
||||
const currentPath = process.env[pathKey] ?? ''
|
||||
const pathDelimiter = getProcessPathDelimiter()
|
||||
const existing = new Set(currentPath.split(pathDelimiter))
|
||||
const missing = extraPaths.filter((path) => !existing.has(path))
|
||||
|
||||
if (missing.length > 0) {
|
||||
process.env.PATH = [...missing, ...currentPath.split(':').filter(Boolean)].join(':')
|
||||
process.env[pathKey] = [...missing, ...currentPath.split(pathDelimiter).filter(Boolean)].join(
|
||||
pathDelimiter
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue