refactor: remove CommonJS launchers and use claude command directly

Remove local Claude launcher scripts and refactor to invoke the global
claude command from PATH. This simplifies the launcher logic and removes
the need to maintain separate CommonJS entry points.

Changes:
- Delete cli/scripts/claude_local_launcher.cjs, claude_remote_launcher.cjs,
  and claude_version_utils.cjs
- Update claudeLocal to spawn 'claude' command directly instead of using
  a launcher script
- Remove runtimePath dependency and launcher existence checks
- Add DISABLE_AUTOUPDATER environment variable for local mode
- Improve error messages when claude command is not found
- Add shell option for Windows compatibility in process spawning
- Remove unused imports from claudeLocal and index files
This commit is contained in:
weishu
2025-12-23 10:21:50 +08:00
parent f132a7dc79
commit 88cce9c28d
8 changed files with 28 additions and 462 deletions
+22 -17
View File
@@ -1,18 +1,12 @@
import { spawn } from "node:child_process";
import { resolve, join } from "node:path";
import { mkdirSync, existsSync } from "node:fs";
import { mkdirSync } from "node:fs";
import { randomUUID } from "node:crypto";
import { logger } from "@/ui/logger";
import { claudeCheckSession } from "./utils/claudeCheckSession";
import { getProjectPath } from "./utils/path";
import { runtimePath } from "@/projectPath";
import { systemPrompt } from "./utils/systemPrompt";
import { withBunRuntimeEnv } from "@/utils/bunRuntime";
// Get Claude CLI path from project root
export const claudeCliPath = resolve(join(runtimePath(), 'scripts', 'claude_local_launcher.cjs'))
export async function claudeLocal(opts: {
abort: AbortSignal,
sessionId: string | null,
@@ -79,37 +73,48 @@ export async function claudeLocal(opts: {
args.push(...opts.claudeArgs)
}
if (!claudeCliPath || !existsSync(claudeCliPath)) {
throw new Error('Claude local launcher not found. Please ensure HAPI_PROJECT_ROOT is set correctly for development.');
}
// Prepare environment variables
// Note: Local mode uses global Claude installation with --session-id flag
const env = {
...process.env,
DISABLE_AUTOUPDATER: '1',
...opts.claudeEnvVars
}
logger.debug(`[ClaudeLocal] Spawning launcher: ${claudeCliPath}`);
logger.debug('[ClaudeLocal] Spawning claude');
logger.debug(`[ClaudeLocal] Args: ${JSON.stringify(args)}`);
const child = spawn(process.execPath, [claudeCliPath, ...args], {
const child = spawn('claude', args, {
stdio: ['inherit', 'inherit', 'inherit'],
signal: opts.abort,
cwd: opts.path,
env: withBunRuntimeEnv(env),
shell: process.platform === 'win32'
});
let settled = false;
const finalize = (error?: Error) => {
if (settled) {
return;
}
settled = true;
if (error) {
reject(error);
} else {
r();
}
};
child.on('error', (error) => {
// Ignore
const message = error instanceof Error ? error.message : String(error);
finalize(new Error(`Failed to spawn claude: ${message}. Is Claude installed and on PATH?`));
});
child.on('exit', (code, signal) => {
if (signal === 'SIGTERM' && opts.abort.aborted) {
// Normal termination due to abort signal
r();
finalize();
} else if (signal) {
reject(new Error(`Process terminated with signal: ${signal}`));
finalize(new Error(`Process terminated with signal: ${signal}`));
} else {
r();
finalize();
}
});
});
+3 -5
View File
@@ -1,8 +1,7 @@
import { EnhancedMode, PermissionMode } from "./loop";
import { query, type QueryOptions as Options, type SDKMessage, type SDKSystemMessage, AbortError, SDKUserMessage } from '@/claude/sdk'
import { claudeCheckSession } from "./utils/claudeCheckSession";
import { join, resolve } from 'node:path';
import { runtimePath } from "@/projectPath";
import { join } from 'node:path';
import { parseSpecialCommand } from "@/parsers/specialCommands";
import { logger } from "@/lib";
import { PushableAsyncIterable } from "@/utils/PushableAsyncIterable";
@@ -74,6 +73,7 @@ export async function claudeRemote(opts: {
process.env[key] = value;
});
}
process.env.DISABLE_AUTOUPDATER = '1';
// Get initial message
const initial = await opts.nextMessage();
@@ -121,9 +121,7 @@ export async function claudeRemote(opts: {
canCallTool: (toolName: string, input: unknown, options: { signal: AbortSignal }) => opts.canCallTool(toolName, input, mode, options),
executable: process.execPath,
abort: opts.signal,
pathToClaudeCodeExecutable: (() => {
return resolve(join(runtimePath(), 'scripts', 'claude_remote_launcher.cjs'));
})(),
pathToClaudeCodeExecutable: 'claude',
}
// Track thinking state
+3 -5
View File
@@ -25,7 +25,6 @@ import { listDaemonSessions, stopDaemonSession } from './daemon/controlClient'
import { handleAuthCommand } from './commands/auth'
import { handleConnectCommand } from './commands/connect'
import { spawnHappyCLI } from './utils/spawnHappyCLI'
import { claudeCliPath } from './claude/claudeLocal'
import { execFileSync } from 'node:child_process'
import { initializeToken } from './ui/tokenInit'
import { ensureRuntimeAssets } from './runtime/assets'
@@ -391,12 +390,11 @@ ${chalk.bold.cyan('Claude Code Options (from `claude --help`):')}
`)
// Run claude --help and display its output
// Use execFileSync with the current Node executable for cross-platform compatibility
try {
const claudeHelp = execFileSync(
process.execPath,
[claudeCliPath, '--help'],
{ encoding: 'utf8', env: withBunRuntimeEnv() }
'claude',
['--help'],
{ encoding: 'utf8', env: withBunRuntimeEnv(), shell: process.platform === 'win32' }
)
console.log(claudeHelp)
} catch (e) {
-3
View File
@@ -116,9 +116,6 @@ function unpackTools(runtimeRoot: string): void {
function runtimeAssetsReady(runtimeRoot: string): boolean {
const requiredScripts = [
join(runtimeRoot, 'scripts', 'claude_local_launcher.cjs'),
join(runtimeRoot, 'scripts', 'claude_remote_launcher.cjs'),
join(runtimeRoot, 'scripts', 'claude_version_utils.cjs'),
join(runtimeRoot, 'scripts', 'ripgrep_launcher.cjs')
];
-6
View File
@@ -1,8 +1,5 @@
import { feature } from 'bun:bundle';
import claudeLocalLauncher from '../../scripts/claude_local_launcher.cjs' assert { type: 'file' };
import claudeRemoteLauncher from '../../scripts/claude_remote_launcher.cjs' assert { type: 'file' };
import claudeVersionUtils from '../../scripts/claude_version_utils.cjs' assert { type: 'file' };
import ripgrepLauncher from '../../scripts/ripgrep_launcher.cjs' assert { type: 'file' };
import difftasticArchiveLicense from '../../tools/archives/difftastic-LICENSE' assert { type: 'file' };
@@ -23,9 +20,6 @@ function asset(relativePath: string, sourcePath: string): EmbeddedAsset {
}
const COMMON_ASSETS: EmbeddedAsset[] = [
asset('scripts/claude_local_launcher.cjs', claudeLocalLauncher),
asset('scripts/claude_remote_launcher.cjs', claudeRemoteLauncher),
asset('scripts/claude_version_utils.cjs', claudeVersionUtils),
asset('scripts/ripgrep_launcher.cjs', ripgrepLauncher),
asset('tools/archives/difftastic-LICENSE', difftasticArchiveLicense),
asset('tools/archives/ripgrep-LICENSE', ripgrepArchiveLicense),