diff --git a/packages/cli/package.json b/packages/cli/package.json index 1bafad0f..e6961dc7 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@openrouter/spawn", - "version": "0.25.25", + "version": "0.25.26", "type": "module", "bin": { "spawn": "cli.js" diff --git a/packages/cli/src/aws/aws.ts b/packages/cli/src/aws/aws.ts index 425c05c2..2d96ec1d 100644 --- a/packages/cli/src/aws/aws.ts +++ b/packages/cli/src/aws/aws.ts @@ -1213,7 +1213,7 @@ export async function interactiveSession(cmd: string): Promise { throw new Error("Invalid command: must be non-empty and must not contain null bytes"); } const term = sanitizeTermValue(process.env.TERM || "xterm-256color"); - const fullCmd = `export TERM='${term}' PATH="$HOME/.npm-global/bin:$HOME/.claude/local/bin:$HOME/.local/bin:$HOME/.bun/bin:$PATH" && exec bash -l -c ${shellQuote(cmd)}`; + const fullCmd = `export TERM='${term}' LANG='C.UTF-8' PATH="$HOME/.npm-global/bin:$HOME/.claude/local/bin:$HOME/.local/bin:$HOME/.bun/bin:$PATH" && exec bash -l -c ${shellQuote(cmd)}`; const keyOpts = getSshKeyOpts(await ensureSshKeys()); const exitCode = spawnInteractive([ "ssh", diff --git a/packages/cli/src/digitalocean/digitalocean.ts b/packages/cli/src/digitalocean/digitalocean.ts index b8031290..49eb58e8 100644 --- a/packages/cli/src/digitalocean/digitalocean.ts +++ b/packages/cli/src/digitalocean/digitalocean.ts @@ -1471,7 +1471,7 @@ export async function interactiveSession(cmd: string, ip?: string): Promise { const username = resolveUsername(); const term = sanitizeTermValue(process.env.TERM || "xterm-256color"); // Use shellQuote for consistent single-quote escaping (prevents shell expansion of $variables in cmd) - const fullCmd = `export TERM='${term}' PATH="$HOME/.npm-global/bin:$HOME/.claude/local/bin:$HOME/.local/bin:$HOME/.bun/bin:$PATH" && exec bash -l -c ${shellQuote(cmd)}`; + const fullCmd = `export TERM='${term}' LANG='C.UTF-8' PATH="$HOME/.npm-global/bin:$HOME/.claude/local/bin:$HOME/.local/bin:$HOME/.bun/bin:$PATH" && exec bash -l -c ${shellQuote(cmd)}`; const keyOpts = getSshKeyOpts(await ensureSshKeys()); const exitCode = spawnInteractive([ diff --git a/packages/cli/src/hetzner/hetzner.ts b/packages/cli/src/hetzner/hetzner.ts index 58bc9816..142cc175 100644 --- a/packages/cli/src/hetzner/hetzner.ts +++ b/packages/cli/src/hetzner/hetzner.ts @@ -929,7 +929,7 @@ export async function interactiveSession(cmd: string, ip?: string): Promise { `if [ -f ~/.bash_profile ] && grep -q 'spawn:env\\|Claude Code PATH\\|spawn:path' ~/.bash_profile 2>/dev/null; then rm -f ~/.bash_profile; fi`, `if command -v claude >/dev/null 2>&1; then ${finalize}; exit 0; fi`, `echo "==> Installing Claude Code (method 1/2: curl installer)..."`, - "curl --proto '=https' -fsSL https://claude.ai/install.sh | bash || true", + "curl --proto '=https' -fsSL https://claude.ai/install.sh | bash >/dev/null 2>&1 || true", `export PATH="${claudePath}:$PATH"`, `if command -v claude >/dev/null 2>&1; then ${finalize}; exit 0; fi`, "if ! command -v node >/dev/null 2>&1; then export N_PREFIX=$HOME/.n; curl --proto '=https' -fsSL https://raw.githubusercontent.com/tj/n/master/bin/n | bash -s install 22 || true; export PATH=$N_PREFIX/bin:$PATH; fi", diff --git a/packages/cli/src/shared/agents.ts b/packages/cli/src/shared/agents.ts index 68ba5351..f7e8d65b 100644 --- a/packages/cli/src/shared/agents.ts +++ b/packages/cli/src/shared/agents.ts @@ -185,6 +185,8 @@ export function generateEnvConfig(pairs: string[]): string { "", "# [spawn:env]", "export IS_SANDBOX='1'", + "# UTF-8 locale — required for agent TUIs that use Unicode (e.g. Claude Code)", + "export LANG='C.UTF-8'", "# Ensure agent binaries are in PATH on reconnect", 'export PATH="$HOME/.npm-global/bin:$HOME/.bun/bin:$HOME/.local/bin:$HOME/.cargo/bin:$HOME/.claude/local/bin:/usr/local/bin:$PATH"', ];