mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
refactor: remove custom executable support and simplify Claude CLI resolution
Remove support for custom executable paths and bundled Claude versions, including deprecated environment variables HAPI_USE_BUNDLED_CLAUDE and HAPI_USE_GLOBAL_CLAUDE. This simplifies the codebase to rely only on the global Claude CLI installation. Updates Claude Code spawning logic to remove special handling for .js/.cjs files, adds configurable BUN_BE_BUN environment variable handling, and streamlines path resolution to throw an error when Claude Code CLI is not found on PATH.
This commit is contained in:
@@ -80,7 +80,7 @@ export async function claudeLocal(opts: {
|
||||
stdio: ['inherit', 'inherit', 'inherit'],
|
||||
signal: opts.abort,
|
||||
cwd: opts.path,
|
||||
env: withBunRuntimeEnv(env),
|
||||
env: withBunRuntimeEnv(env, { allowBunBeBun: false }),
|
||||
shell: process.platform === 'win32'
|
||||
});
|
||||
let settled = false;
|
||||
|
||||
@@ -120,7 +120,6 @@ export async function claudeRemote(opts: {
|
||||
allowedTools: initial.mode.allowedTools ? initial.mode.allowedTools.concat(opts.allowedTools) : opts.allowedTools,
|
||||
disallowedTools: initial.mode.disallowedTools,
|
||||
canCallTool: (toolName: string, input: unknown, options: { signal: AbortSignal }) => opts.canCallTool(toolName, input, mode, options),
|
||||
executable: process.execPath,
|
||||
abort: opts.signal,
|
||||
pathToClaudeCodeExecutable: 'claude',
|
||||
settingsPath: opts.hookSettingsPath,
|
||||
|
||||
@@ -263,8 +263,6 @@ export function query(config: {
|
||||
customSystemPrompt,
|
||||
cwd,
|
||||
disallowedTools = [],
|
||||
executable = process.execPath,
|
||||
executableArgs = [],
|
||||
maxTurns,
|
||||
mcpServers,
|
||||
pathToClaudeCodeExecutable = getDefaultClaudeCodePath(),
|
||||
@@ -323,10 +321,8 @@ export function query(config: {
|
||||
}
|
||||
|
||||
// Determine how to spawn Claude Code
|
||||
// - If it's a .js/.cjs file → spawn(current runtime, [path, ...args])
|
||||
// - If it's just 'claude' command → spawn('claude', args) with shell on Windows
|
||||
// - If it's a full path to binary → spawn(path, args)
|
||||
const isJsFile = pathToClaudeCodeExecutable.endsWith('.js') || pathToClaudeCodeExecutable.endsWith('.cjs')
|
||||
// - If it's a full path to binary or script → spawn(path, args)
|
||||
const isCommandOnly = pathToClaudeCodeExecutable === 'claude'
|
||||
|
||||
// Validate executable path (skip for command-only mode)
|
||||
@@ -334,15 +330,13 @@ export function query(config: {
|
||||
throw new ReferenceError(`Claude Code executable not found at ${pathToClaudeCodeExecutable}. Is options.pathToClaudeCodeExecutable set?`)
|
||||
}
|
||||
|
||||
const spawnCommand = isJsFile ? executable : pathToClaudeCodeExecutable
|
||||
const spawnArgs = isJsFile
|
||||
? [...executableArgs, pathToClaudeCodeExecutable, ...args]
|
||||
: args
|
||||
const spawnCommand = pathToClaudeCodeExecutable
|
||||
const spawnArgs = args
|
||||
|
||||
// Spawn Claude Code process
|
||||
// Use clean env for global claude to avoid local node_modules/.bin taking precedence
|
||||
const baseEnv = isCommandOnly ? getCleanEnv() : process.env
|
||||
const spawnEnv = withBunRuntimeEnv(baseEnv)
|
||||
const spawnEnv = withBunRuntimeEnv(baseEnv, { allowBunBeBun: false })
|
||||
logDebug(`Spawning Claude Code process: ${spawnCommand} ${spawnArgs.join(' ')} (using ${isCommandOnly ? 'clean' : 'normal'} env)`)
|
||||
|
||||
const child = spawn(spawnCommand, spawnArgs, {
|
||||
@@ -350,8 +344,8 @@ export function query(config: {
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
signal: config.options?.abort,
|
||||
env: spawnEnv,
|
||||
// Use shell on Windows for global binaries and command-only mode
|
||||
shell: !isJsFile && process.platform === 'win32'
|
||||
// Use shell on Windows for command resolution
|
||||
shell: process.platform === 'win32'
|
||||
}) as ChildProcessWithoutNullStreams
|
||||
|
||||
// Handle stdin
|
||||
|
||||
@@ -161,8 +161,6 @@ export interface QueryOptions {
|
||||
customSystemPrompt?: string
|
||||
cwd?: string
|
||||
disallowedTools?: string[]
|
||||
executable?: string
|
||||
executableArgs?: string[]
|
||||
maxTurns?: number
|
||||
mcpServers?: Record<string, unknown>
|
||||
pathToClaudeCodeExecutable?: string
|
||||
|
||||
@@ -3,41 +3,11 @@
|
||||
* Provides helper functions for path resolution and logging
|
||||
*/
|
||||
|
||||
import { join } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { existsSync, readFileSync } from 'node:fs'
|
||||
import { existsSync } from 'node:fs'
|
||||
import { execSync } from 'node:child_process'
|
||||
import { homedir } from 'node:os'
|
||||
import { logger } from '@/ui/logger'
|
||||
|
||||
/**
|
||||
* Get the directory path of the current module
|
||||
*/
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = join(__filename, '..')
|
||||
|
||||
/**
|
||||
* Get version of globally installed claude
|
||||
* Runs from home directory with clean PATH to avoid picking up local node_modules/.bin
|
||||
*/
|
||||
function getGlobalClaudeVersion(): string | null {
|
||||
try {
|
||||
const cleanEnv = getCleanEnv()
|
||||
const output = execSync('claude --version', {
|
||||
encoding: 'utf8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
cwd: homedir(),
|
||||
env: cleanEnv
|
||||
}).trim()
|
||||
// Output format: "2.0.54 (Claude Code)" or similar
|
||||
const match = output.match(/(\d+\.\d+\.\d+)/)
|
||||
logger.debug(`[Claude SDK] Global claude --version output: ${output}`)
|
||||
return match ? match[1] : null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a clean environment without local node_modules/.bin in PATH
|
||||
* This ensures we find the global claude, not the local one
|
||||
@@ -114,51 +84,23 @@ function findGlobalClaudePath(): string | null {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default path to Claude Code executable
|
||||
* Compares global and bundled versions, uses the newer one
|
||||
*
|
||||
* Get default path to Claude Code executable.
|
||||
*
|
||||
* Environment variables:
|
||||
* - HAPI_CLAUDE_PATH: Force a specific path to claude executable
|
||||
* - HAPI_USE_BUNDLED_CLAUDE=1: Force use of node_modules version (skip global search)
|
||||
* - HAPI_USE_GLOBAL_CLAUDE=1: Force use of global version (if available)
|
||||
*/
|
||||
export function getDefaultClaudeCodePath(): string {
|
||||
const nodeModulesPath = join(__dirname, '..', '..', '..', 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js')
|
||||
|
||||
// Allow explicit override via env var
|
||||
if (process.env.HAPI_CLAUDE_PATH) {
|
||||
logger.debug(`[Claude SDK] Using HAPI_CLAUDE_PATH: ${process.env.HAPI_CLAUDE_PATH}`)
|
||||
return process.env.HAPI_CLAUDE_PATH
|
||||
}
|
||||
|
||||
// Force bundled version if requested
|
||||
if (process.env.HAPI_USE_BUNDLED_CLAUDE === '1') {
|
||||
logger.debug(`[Claude SDK] Forced bundled version: ${nodeModulesPath}`)
|
||||
return nodeModulesPath
|
||||
}
|
||||
|
||||
// Find global claude
|
||||
const globalPath = findGlobalClaudePath()
|
||||
|
||||
|
||||
|
||||
// No global claude found - use bundled
|
||||
if (!globalPath) {
|
||||
logger.debug(`[Claude SDK] No global claude found, using bundled: ${nodeModulesPath}`)
|
||||
return nodeModulesPath
|
||||
throw new Error('Claude Code CLI not found on PATH. Install Claude Code or set HAPI_CLAUDE_PATH.')
|
||||
}
|
||||
|
||||
// Compare versions and use the newer one
|
||||
const globalVersion = getGlobalClaudeVersion()
|
||||
|
||||
logger.debug(`[Claude SDK] Global version: ${globalVersion || 'unknown'}`)
|
||||
|
||||
// If we can't determine versions, prefer global (user's choice to install it)
|
||||
if (!globalVersion) {
|
||||
logger.debug(`[Claude SDK] Cannot compare versions, using global: ${globalPath}`)
|
||||
return globalPath
|
||||
}
|
||||
|
||||
return globalPath
|
||||
}
|
||||
|
||||
@@ -185,4 +127,4 @@ export async function streamToStdin(
|
||||
stdin.write(JSON.stringify(message) + '\n')
|
||||
}
|
||||
stdin.end()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,21 @@
|
||||
export function withBunRuntimeEnv(env: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv {
|
||||
export type BunRuntimeEnvOptions = {
|
||||
allowBunBeBun?: boolean;
|
||||
};
|
||||
|
||||
function stripBunBeBun(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
|
||||
if (!('BUN_BE_BUN' in env)) {
|
||||
return env;
|
||||
}
|
||||
|
||||
const copy = { ...env };
|
||||
delete copy.BUN_BE_BUN;
|
||||
return copy;
|
||||
}
|
||||
|
||||
export function withBunRuntimeEnv(
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
options: BunRuntimeEnvOptions = {}
|
||||
): NodeJS.ProcessEnv {
|
||||
const bunRuntime = (globalThis as typeof globalThis & { Bun?: { isCompiled?: boolean } }).Bun;
|
||||
const argv1 = process.argv[1] ?? '';
|
||||
const isCompiled = Boolean(bunRuntime?.isCompiled) || argv1.includes('$bunfs');
|
||||
@@ -7,6 +24,10 @@ export function withBunRuntimeEnv(env: NodeJS.ProcessEnv = process.env): NodeJS.
|
||||
return env;
|
||||
}
|
||||
|
||||
if (options.allowBunBeBun === false) {
|
||||
return stripBunBeBun(env);
|
||||
}
|
||||
|
||||
return {
|
||||
...env,
|
||||
BUN_BE_BUN: '1'
|
||||
|
||||
Reference in New Issue
Block a user