diff --git a/Services/ClaudeCliService.cs b/Services/ClaudeCliService.cs index c553c19..99e80f2 100644 --- a/Services/ClaudeCliService.cs +++ b/Services/ClaudeCliService.cs @@ -39,7 +39,6 @@ public class ClaudeCliService { var psi = new ProcessStartInfo { - FileName = _cliPath, RedirectStandardInput = redirectInput, RedirectStandardOutput = true, RedirectStandardError = true, @@ -49,12 +48,91 @@ public class ClaudeCliService StandardErrorEncoding = Encoding.UTF8 }; - foreach (var arg in args) - psi.ArgumentList.Add(arg); + if (OperatingSystem.IsWindows()) + { + // Auf Windows: über cmd.exe wrappen und Codepage 65001 (UTF-8) erzwingen. + // Sonst kann der Child-Prozess (claude.exe) Output mit CP1252/CP850 schreiben, + // was beim UTF-8-Decode zu Replacement-Chars (�) für Umlaute / Em-Dash führt. + var sb = new StringBuilder(); + sb.Append("/S /C \"chcp 65001 >nul && "); + sb.Append(QuoteArgForCmd(_cliPath)); + foreach (var a in args) + { + sb.Append(' '); + sb.Append(QuoteArgForCmd(a)); + } + sb.Append('"'); + + psi.FileName = Environment.GetEnvironmentVariable("ComSpec") ?? "cmd.exe"; + psi.Arguments = sb.ToString(); + } + else + { + psi.FileName = _cliPath; + foreach (var arg in args) + psi.ArgumentList.Add(arg); + } + + // UTF-8-Locale für den Child setzen (Node.js / diverse Tools respektieren das). + // Schadet auf Windows nicht, hilft auf manchen Linux-Setups (z.B. systemd-Diensten ohne LANG). + psi.Environment["LANG"] = "en_US.UTF-8"; + psi.Environment["LC_ALL"] = "en_US.UTF-8"; + psi.Environment["PYTHONIOENCODING"] = "utf-8"; return psi; } + /// + /// Quotet ein Argument im Stil von Microsoft CRT (CommandLineToArgvW), + /// passend für die Verwendung innerhalb von cmd.exe /C "...". + /// + private static string QuoteArgForCmd(string arg) + { + if (string.IsNullOrEmpty(arg)) + return "\"\""; + + bool needsQuoting = false; + foreach (var c in arg) + { + if (c == ' ' || c == '\t' || c == '"' || c == '\n' || c == '\v') + { + needsQuoting = true; + break; + } + } + + if (!needsQuoting) + return arg; + + var sb = new StringBuilder(); + sb.Append('"'); + int backslashes = 0; + foreach (var c in arg) + { + if (c == '\\') + { + backslashes++; + } + else if (c == '"') + { + // 2n+1 Backslashes vor einem Anführungszeichen + sb.Append('\\', backslashes * 2 + 1); + sb.Append('"'); + backslashes = 0; + } + else + { + sb.Append('\\', backslashes); + sb.Append(c); + backslashes = 0; + } + } + // 2n Backslashes am Ende (vor dem schließenden ") + sb.Append('\\', backslashes * 2); + sb.Append('"'); + return sb.ToString(); + } + private static string FindCliPath() { // 1. Expliziter Pfad über Umgebungsvariable (höchste Priorität)