Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/claude/claudeRemote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { getProjectPath } from "./utils/path";
import { awaitFileExist } from "@/modules/watcher/awaitFileExist";
import { systemPrompt } from "./utils/systemPrompt";
import { PermissionResult } from "./sdk/types";
import type { JsRuntime } from "./runClaude";

export async function claudeRemote(opts: {

Expand All @@ -24,6 +25,8 @@ export async function claudeRemote(opts: {
canCallTool: (toolName: string, input: unknown, mode: EnhancedMode, options: { signal: AbortSignal }) => Promise<PermissionResult>,
/** Path to temporary settings file with SessionStart hook (required for session tracking) */
hookSettingsPath: string,
/** JavaScript runtime to use for spawning Claude Code (default: 'node') */
jsRuntime?: JsRuntime,

// Dynamic parameters
nextMessage: () => Promise<{ message: string, mode: EnhancedMode } | null>,
Expand Down Expand Up @@ -121,7 +124,7 @@ 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: 'node',
executable: opts.jsRuntime ?? 'node',
abort: opts.signal,
pathToClaudeCodeExecutable: (() => {
return resolve(join(projectPath(), 'scripts', 'claude_remote_launcher.cjs'));
Expand Down
1 change: 1 addition & 0 deletions src/claude/claudeRemoteLauncher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ export async function claudeRemoteLauncher(session: Session): Promise<'switch' |
allowedTools: session.allowedTools ?? [],
mcpServers: session.mcpServers,
hookSettingsPath: session.hookSettingsPath,
jsRuntime: session.jsRuntime,
canCallTool: permissionHandler.handleToolCall,
isAborted: (toolCallId: string) => {
return permissionHandler.isAborted(toolCallId);
Expand Down
6 changes: 5 additions & 1 deletion src/claude/loop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Session } from "./session"
import { claudeLocalLauncher } from "./claudeLocalLauncher"
import { claudeRemoteLauncher } from "./claudeRemoteLauncher"
import { ApiClient } from "@/lib"
import type { JsRuntime } from "./runClaude"

export type PermissionMode = 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan';

Expand Down Expand Up @@ -34,6 +35,8 @@ interface LoopOptions {
onSessionReady?: (session: Session) => void
/** Path to temporary settings file with SessionStart hook (required for session tracking) */
hookSettingsPath: string
/** JavaScript runtime to use for spawning Claude Code (default: 'node') */
jsRuntime?: JsRuntime
}

export async function loop(opts: LoopOptions) {
Expand All @@ -52,7 +55,8 @@ export async function loop(opts: LoopOptions) {
messageQueue: opts.messageQueue,
allowedTools: opts.allowedTools,
onModeChange: opts.onModeChange,
hookSettingsPath: opts.hookSettingsPath
hookSettingsPath: opts.hookSettingsPath,
jsRuntime: opts.jsRuntime
});

// Notify that session is ready
Expand Down
8 changes: 7 additions & 1 deletion src/claude/runClaude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ import { projectPath } from '../projectPath';
import { resolve } from 'node:path';
import { Session } from './session';

/** JavaScript runtime to use for spawning Claude Code */
export type JsRuntime = 'node' | 'bun'

export interface StartOptions {
model?: string
permissionMode?: 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan'
Expand All @@ -33,6 +36,8 @@ export interface StartOptions {
claudeEnvVars?: Record<string, string>
claudeArgs?: string[]
startedBy?: 'daemon' | 'terminal'
/** JavaScript runtime to use for spawning Claude Code (default: 'node') */
jsRuntime?: JsRuntime
}

export async function runClaude(credentials: Credentials, options: StartOptions = {}): Promise<void> {
Expand Down Expand Up @@ -405,7 +410,8 @@ export async function runClaude(credentials: Credentials, options: StartOptions
session,
claudeEnvVars: options.claudeEnvVars,
claudeArgs: options.claudeArgs,
hookSettingsPath
hookSettingsPath,
jsRuntime: options.jsRuntime
});

// Send session death message
Expand Down
6 changes: 6 additions & 0 deletions src/claude/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ApiClient, ApiSessionClient } from "@/lib";
import { MessageQueue2 } from "@/utils/MessageQueue2";
import { EnhancedMode } from "./loop";
import { logger } from "@/ui/logger";
import type { JsRuntime } from "./runClaude";

export class Session {
readonly path: string;
Expand All @@ -16,6 +17,8 @@ export class Session {
readonly _onModeChange: (mode: 'local' | 'remote') => void;
/** Path to temporary settings file with SessionStart hook (required for session tracking) */
readonly hookSettingsPath: string;
/** JavaScript runtime to use for spawning Claude Code (default: 'node') */
readonly jsRuntime: JsRuntime;

sessionId: string | null;
mode: 'local' | 'remote' = 'local';
Expand All @@ -41,6 +44,8 @@ export class Session {
allowedTools?: string[],
/** Path to temporary settings file with SessionStart hook (required for session tracking) */
hookSettingsPath: string,
/** JavaScript runtime to use for spawning Claude Code (default: 'node') */
jsRuntime?: JsRuntime,
}) {
this.path = opts.path;
this.api = opts.api;
Expand All @@ -54,6 +59,7 @@ export class Session {
this.allowedTools = opts.allowedTools;
this._onModeChange = opts.onModeChange;
this.hookSettingsPath = opts.hookSettingsPath;
this.jsRuntime = opts.jsRuntime ?? 'node';

// Start keep alive
this.client.keepAlive(this.thinking, this.mode);
Expand Down
8 changes: 8 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,13 @@ ${chalk.bold('To clean up runaway processes:')} Use ${chalk.cyan('happy doctor c
unknownArgs.push('--dangerously-skip-permissions')
} else if (arg === '--started-by') {
options.startedBy = args[++i] as 'daemon' | 'terminal'
} else if (arg === '--js-runtime') {
const runtime = args[++i]
if (runtime !== 'node' && runtime !== 'bun') {
console.error(chalk.red(`Invalid --js-runtime value: ${runtime}. Must be 'node' or 'bun'`))
process.exit(1)
}
options.jsRuntime = runtime
} else if (arg === '--claude-env') {
// Parse KEY=VALUE environment variable to pass to Claude
const envArg = args[++i]
Expand Down Expand Up @@ -454,6 +461,7 @@ ${chalk.bold('Examples:')}
happy Start session
happy --yolo Start with bypassing permissions
happy sugar for --dangerously-skip-permissions
happy --js-runtime bun Use bun instead of node to spawn Claude Code
happy --claude-env ANTHROPIC_BASE_URL=http://127.0.0.1:3456
Use a custom API endpoint (e.g., claude-code-router)
happy auth login --force Authenticate
Expand Down