Merge branch 'claudedo/ea798a952ab54bcc95dbb442168ee0dd'

This commit is contained in:
mika kuns
2026-09-10 13:59:50 +02:00
11 changed files with 135 additions and 56 deletions
@@ -1,5 +1,6 @@
using System.Text.Json; using System.Text.Json;
using ClaudeDo.Installer.Core; using ClaudeDo.Installer.Core;
using ClaudeDo.Installer.Localization;
namespace ClaudeDo.Installer.Checks; namespace ClaudeDo.Installer.Checks;
@@ -23,11 +24,12 @@ public sealed class ClaudeAuthCheck : IEnvironmentCheck
public async Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct) public async Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct)
{ {
var loc = TrExtension.Localizer;
var lookup = await _lookup.ResolveAsync(ctx, ct); var lookup = await _lookup.ResolveAsync(ctx, ct);
if (lookup.Resolved is null) if (lookup.Resolved is null)
{ {
return CheckResult.Unknown(Id, Severity, "checks.claudeAuth.title", return CheckResult.Unknown(Id, Severity, "checks.claudeAuth.title",
"The claude CLI was not found — login could not be checked."); loc?["checks.claudeAuth.notFound"] ?? "The claude CLI was not found — login could not be checked.");
} }
var (fileName, arguments) = ProcessCommand.For(lookup.Resolved, "auth", "status", "--json"); var (fileName, arguments) = ProcessCommand.For(lookup.Resolved, "auth", "status", "--json");
@@ -36,19 +38,19 @@ public sealed class ClaudeAuthCheck : IEnvironmentCheck
if (exitCode != 0) if (exitCode != 0)
{ {
return CheckResult.Unknown(Id, Severity, "checks.claudeAuth.title", return CheckResult.Unknown(Id, Severity, "checks.claudeAuth.title",
"Login status could not be determined.", output); loc?["checks.claudeAuth.undetermined"] ?? "Login status could not be determined.", output);
} }
var loggedIn = TryParseLoggedIn(output); var loggedIn = TryParseLoggedIn(output);
if (loggedIn is null) if (loggedIn is null)
{ {
return CheckResult.Unknown(Id, Severity, "checks.claudeAuth.title", return CheckResult.Unknown(Id, Severity, "checks.claudeAuth.title",
"Login status could not be determined.", output); loc?["checks.claudeAuth.undetermined"] ?? "Login status could not be determined.", output);
} }
return loggedIn.Value return loggedIn.Value
? CheckResult.Ok(Id, Severity, "checks.claudeAuth.title", "Logged in.") ? CheckResult.Ok(Id, Severity, "checks.claudeAuth.title", loc?["checks.claudeAuth.loggedIn"] ?? "Logged in.")
: CheckResult.Fail(Id, Severity, "checks.claudeAuth.title", "Not logged in.", "checks.claudeAuth.hint"); : CheckResult.Fail(Id, Severity, "checks.claudeAuth.title", loc?["checks.claudeAuth.notLoggedIn"] ?? "Not logged in.", "checks.claudeAuth.hint");
} }
private static bool? TryParseLoggedIn(string output) private static bool? TryParseLoggedIn(string output)
@@ -1,6 +1,7 @@
using System.IO; using System.IO;
using ClaudeDo.Data.Environment; using ClaudeDo.Data.Environment;
using ClaudeDo.Installer.Core; using ClaudeDo.Installer.Core;
using ClaudeDo.Installer.Localization;
namespace ClaudeDo.Installer.Checks; namespace ClaudeDo.Installer.Checks;
@@ -21,20 +22,21 @@ public sealed class ClaudeCliCheck : IEnvironmentCheck
public async Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct) public async Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct)
{ {
var loc = TrExtension.Localizer;
var lookup = await _lookup.ResolveAsync(ctx, ct); var lookup = await _lookup.ResolveAsync(ctx, ct);
if (lookup.Resolved is null) if (lookup.Resolved is null)
{ {
var searched = string.Join(", ", SearchedLocations()); var searched = string.Join(", ", SearchedLocations());
return CheckResult.Fail(Id, Severity, "checks.claudeCli.title", return CheckResult.Fail(Id, Severity, "checks.claudeCli.title",
$"'{ctx.ClaudeBin}' was not found on PATH. Searched: {searched}.", loc?.Get("checks.claudeCli.notFound", ctx.ClaudeBin, searched) ?? $"'{ctx.ClaudeBin}' was not found on PATH. Searched: {searched}.",
"checks.claudeCli.hint"); "checks.claudeCli.hint");
} }
if (lookup.ExitCode != 0) if (lookup.ExitCode != 0)
{ {
return CheckResult.Fail(Id, Severity, "checks.claudeCli.title", return CheckResult.Fail(Id, Severity, "checks.claudeCli.title",
$"'{lookup.Resolved.Path}' exited with code {lookup.ExitCode}.", loc?.Get("checks.claudeCli.exitCode", lookup.Resolved.Path, lookup.ExitCode) ?? $"'{lookup.Resolved.Path}' exited with code {lookup.ExitCode}.",
"checks.claudeCli.hint", detail: lookup.Output); "checks.claudeCli.hint", detail: lookup.Output);
} }
@@ -42,7 +44,8 @@ public sealed class ClaudeCliCheck : IEnvironmentCheck
var detail = lookup.Resolved.IsShim var detail = lookup.Resolved.IsShim
? "Resolved via a shim (.cmd/.bat/.ps1), not a native .exe." ? "Resolved via a shim (.cmd/.bat/.ps1), not a native .exe."
: null; : null;
return CheckResult.Ok(Id, Severity, "checks.claudeCli.title", $"{lookup.Resolved.Path} — {version}", detail); return CheckResult.Ok(Id, Severity, "checks.claudeCli.title",
loc?.Get("checks.claudeCli.resolvedFormat", lookup.Resolved.Path, version) ?? $"{lookup.Resolved.Path} — {version}", detail);
} }
private IEnumerable<string> SearchedLocations() private IEnumerable<string> SearchedLocations()
@@ -1,4 +1,5 @@
using ClaudeDo.Installer.Core; using ClaudeDo.Installer.Core;
using ClaudeDo.Installer.Localization;
namespace ClaudeDo.Installer.Checks; namespace ClaudeDo.Installer.Checks;
@@ -27,28 +28,29 @@ public sealed class ClaudeVersionCheck : IEnvironmentCheck
public async Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct) public async Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct)
{ {
var loc = TrExtension.Localizer;
var lookup = await _lookup.ResolveAsync(ctx, ct); var lookup = await _lookup.ResolveAsync(ctx, ct);
if (lookup.Resolved is null) if (lookup.Resolved is null)
{ {
return CheckResult.Unknown(Id, Severity, "checks.claudeVersion.title", return CheckResult.Unknown(Id, Severity, "checks.claudeVersion.title",
"The claude CLI was not found — version could not be checked."); loc?["checks.claudeVersion.notFound"] ?? "The claude CLI was not found — version could not be checked.");
} }
if (lookup.ExitCode != 0 || lookup.ParsedVersion is null) if (lookup.ExitCode != 0 || lookup.ParsedVersion is null)
{ {
return CheckResult.Unknown(Id, Severity, "checks.claudeVersion.title", return CheckResult.Unknown(Id, Severity, "checks.claudeVersion.title",
$"Could not parse a version from '{lookup.Output.Trim()}'."); loc?.Get("checks.claudeVersion.parseFailed", lookup.Output.Trim()) ?? $"Could not parse a version from '{lookup.Output.Trim()}'.");
} }
if (lookup.ParsedVersion < MinimumVersion) if (lookup.ParsedVersion < MinimumVersion)
{ {
return CheckResult.Fail(Id, Severity, "checks.claudeVersion.title", return CheckResult.Fail(Id, Severity, "checks.claudeVersion.title",
$"claude {lookup.ParsedVersion} is older than the required {MinimumVersion}.", loc?.Get("checks.claudeVersion.tooOld", lookup.ParsedVersion, MinimumVersion) ?? $"claude {lookup.ParsedVersion} is older than the required {MinimumVersion}.",
"checks.claudeVersion.hint"); "checks.claudeVersion.hint");
} }
return CheckResult.Ok(Id, Severity, "checks.claudeVersion.title", return CheckResult.Ok(Id, Severity, "checks.claudeVersion.title",
$"claude {lookup.ParsedVersion} meets the minimum ({MinimumVersion})."); loc?.Get("checks.claudeVersion.ok", lookup.ParsedVersion, MinimumVersion) ?? $"claude {lookup.ParsedVersion} meets the minimum ({MinimumVersion}).");
} }
} }
+8 -3
View File
@@ -2,6 +2,7 @@ using System.Text.RegularExpressions;
using ClaudeDo.Data.Environment; using ClaudeDo.Data.Environment;
using ClaudeDo.Installer.Core; using ClaudeDo.Installer.Core;
using ClaudeDo.Installer.Core.Interfaces; using ClaudeDo.Installer.Core.Interfaces;
using ClaudeDo.Installer.Localization;
namespace ClaudeDo.Installer.Checks; namespace ClaudeDo.Installer.Checks;
@@ -27,10 +28,12 @@ public sealed class GitCheck : IEnvironmentCheck
public async Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct) public async Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct)
{ {
var loc = TrExtension.Localizer;
var resolved = ExecutableResolver.Resolve("git", _pathOverride, _pathExtOverride); var resolved = ExecutableResolver.Resolve("git", _pathOverride, _pathExtOverride);
if (resolved is null) if (resolved is null)
{ {
return CheckResult.Fail(Id, Severity, "checks.git.title", "git was not found on PATH.", return CheckResult.Fail(Id, Severity, "checks.git.title",
loc?["checks.git.notFound"] ?? "git was not found on PATH.",
"checks.git.hint", "https://git-scm.com/downloads"); "checks.git.hint", "https://git-scm.com/downloads");
} }
@@ -39,11 +42,13 @@ public sealed class GitCheck : IEnvironmentCheck
if (exitCode != 0) if (exitCode != 0)
{ {
return CheckResult.Fail(Id, Severity, "checks.git.title", $"'{resolved.Path}' exited with code {exitCode}.", return CheckResult.Fail(Id, Severity, "checks.git.title",
loc?.Get("checks.git.exitCode", resolved.Path, exitCode) ?? $"'{resolved.Path}' exited with code {exitCode}.",
"checks.git.hint", "https://git-scm.com/downloads", output); "checks.git.hint", "https://git-scm.com/downloads", output);
} }
var version = VersionPattern.Match(output) is { Success: true } match ? match.Value : output.Trim(); var version = VersionPattern.Match(output) is { Success: true } match ? match.Value : output.Trim();
return CheckResult.Ok(Id, Severity, "checks.git.title", $"{resolved.Path} — {version}"); return CheckResult.Ok(Id, Severity, "checks.git.title",
loc?.Get("checks.git.resolvedFormat", resolved.Path, version) ?? $"{resolved.Path} — {version}");
} }
} }
@@ -1,6 +1,7 @@
using ClaudeDo.Data.Environment; using ClaudeDo.Data.Environment;
using ClaudeDo.Installer.Core; using ClaudeDo.Installer.Core;
using ClaudeDo.Installer.Core.Interfaces; using ClaudeDo.Installer.Core.Interfaces;
using ClaudeDo.Installer.Localization;
namespace ClaudeDo.Installer.Checks; namespace ClaudeDo.Installer.Checks;
@@ -25,10 +26,12 @@ public sealed class GitIdentityCheck : IEnvironmentCheck
public async Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct) public async Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct)
{ {
var loc = TrExtension.Localizer;
var resolved = ExecutableResolver.Resolve("git", _pathOverride, _pathExtOverride); var resolved = ExecutableResolver.Resolve("git", _pathOverride, _pathExtOverride);
if (resolved is null) if (resolved is null)
{ {
return CheckResult.Unknown(Id, Severity, "checks.gitIdentity.title", "git was not found — identity could not be checked."); return CheckResult.Unknown(Id, Severity, "checks.gitIdentity.title",
loc?["checks.gitIdentity.notFound"] ?? "git was not found — identity could not be checked.");
} }
var name = await ReadConfigAsync(resolved, "user.name", ct); var name = await ReadConfigAsync(resolved, "user.name", ct);
@@ -48,7 +51,8 @@ public sealed class GitIdentityCheck : IEnvironmentCheck
_ => "user.email", _ => "user.email",
}; };
return CheckResult.Fail(Id, Severity, "checks.gitIdentity.title", $"Missing git identity: {missing}.", "checks.gitIdentity.hint"); return CheckResult.Fail(Id, Severity, "checks.gitIdentity.title",
loc?.Get("checks.gitIdentity.missing", missing) ?? $"Missing git identity: {missing}.", "checks.gitIdentity.hint");
} }
private async Task<string> ReadConfigAsync(ResolvedExecutable resolved, string key, CancellationToken ct) private async Task<string> ReadConfigAsync(ResolvedExecutable resolved, string key, CancellationToken ct)
@@ -1,5 +1,6 @@
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using ClaudeDo.Installer.Core; using ClaudeDo.Installer.Core;
using ClaudeDo.Installer.Localization;
namespace ClaudeDo.Installer.Checks; namespace ClaudeDo.Installer.Checks;
@@ -28,11 +29,12 @@ public sealed class PermissionModeAutoCheck : IEnvironmentCheck
public async Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct) public async Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct)
{ {
var loc = TrExtension.Localizer;
var lookup = await _lookup.ResolveAsync(ctx, ct); var lookup = await _lookup.ResolveAsync(ctx, ct);
if (lookup.Resolved is null) if (lookup.Resolved is null)
{ {
return CheckResult.Unknown(Id, Severity, "checks.permissionModeAuto.title", return CheckResult.Unknown(Id, Severity, "checks.permissionModeAuto.title",
"The claude CLI was not found — permission-mode support could not be checked."); loc?["checks.permissionModeAuto.notFound"] ?? "The claude CLI was not found — permission-mode support could not be checked.");
} }
var (fileName, arguments) = ProcessCommand.For(lookup.Resolved, "--help"); var (fileName, arguments) = ProcessCommand.For(lookup.Resolved, "--help");
@@ -41,17 +43,17 @@ public sealed class PermissionModeAutoCheck : IEnvironmentCheck
if (exitCode != 0 || string.IsNullOrWhiteSpace(output)) if (exitCode != 0 || string.IsNullOrWhiteSpace(output))
{ {
return CheckResult.Unknown(Id, Severity, "checks.permissionModeAuto.title", return CheckResult.Unknown(Id, Severity, "checks.permissionModeAuto.title",
"Could not determine whether --permission-mode auto is supported."); loc?["checks.permissionModeAuto.undetermined"] ?? "Could not determine whether --permission-mode auto is supported.");
} }
if (AutoIsListed(output)) if (AutoIsListed(output))
{ {
return CheckResult.Ok(Id, Severity, "checks.permissionModeAuto.title", return CheckResult.Ok(Id, Severity, "checks.permissionModeAuto.title",
"--permission-mode auto is recognized by this CLI."); loc?["checks.permissionModeAuto.ok"] ?? "--permission-mode auto is recognized by this CLI.");
} }
return CheckResult.Fail(Id, Severity, "checks.permissionModeAuto.title", return CheckResult.Fail(Id, Severity, "checks.permissionModeAuto.title",
"This claude CLI version does not list 'auto' as a --permission-mode choice.", loc?["checks.permissionModeAuto.notListed"] ?? "This claude CLI version does not list 'auto' as a --permission-mode choice.",
"checks.permissionModeAuto.hint"); "checks.permissionModeAuto.hint");
} }
+9 -3
View File
@@ -2,6 +2,7 @@ using System.Net;
using System.Net.Sockets; using System.Net.Sockets;
using ClaudeDo.Installer.Checks.Interfaces; using ClaudeDo.Installer.Checks.Interfaces;
using ClaudeDo.Installer.Core; using ClaudeDo.Installer.Core;
using ClaudeDo.Installer.Localization;
namespace ClaudeDo.Installer.Checks; namespace ClaudeDo.Installer.Checks;
@@ -19,6 +20,7 @@ public sealed class PortCheck : IEnvironmentCheck
public async Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct) public async Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct)
{ {
var loc = TrExtension.Localizer;
var ports = new (string Label, int Port)[] var ports = new (string Label, int Port)[]
{ {
("SignalR", ctx.SignalRPort), ("SignalR", ctx.SignalRPort),
@@ -39,7 +41,9 @@ public sealed class PortCheck : IEnvironmentCheck
continue; continue;
} }
blocked.Add(owner is null ? $"{label} port {port} is in use." : $"{label} port {port} is in use by '{owner}'."); blocked.Add(owner is null
? loc?.Get("checks.ports.inUse", label, port) ?? $"{label} port {port} is in use."
: loc?.Get("checks.ports.inUseBy", label, port, owner) ?? $"{label} port {port} is in use by '{owner}'.");
} }
if (blocked.Count > 0) if (blocked.Count > 0)
@@ -49,10 +53,12 @@ public sealed class PortCheck : IEnvironmentCheck
if (ownWorker.Count > 0) if (ownWorker.Count > 0)
{ {
return CheckResult.Ok(Id, Severity, "checks.ports.title", $"In use by the running ClaudeDo Worker: {string.Join(", ", ownWorker)}."); var ownWorkerList = string.Join(", ", ownWorker);
return CheckResult.Ok(Id, Severity, "checks.ports.title",
loc?.Get("checks.ports.inUseByOwnWorker", ownWorkerList) ?? $"In use by the running ClaudeDo Worker: {ownWorkerList}.");
} }
return CheckResult.Ok(Id, Severity, "checks.ports.title", "Ports are available."); return CheckResult.Ok(Id, Severity, "checks.ports.title", loc?["checks.ports.available"] ?? "Ports are available.");
} }
private static bool IsFree(int port) private static bool IsFree(int port)
@@ -1,6 +1,7 @@
using System.IO; using System.IO;
using ClaudeDo.Data; using ClaudeDo.Data;
using ClaudeDo.Installer.Core; using ClaudeDo.Installer.Core;
using ClaudeDo.Installer.Localization;
namespace ClaudeDo.Installer.Checks; namespace ClaudeDo.Installer.Checks;
@@ -14,17 +15,19 @@ public sealed class WriteAccessCheck : IEnvironmentCheck
public Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct) public Task<CheckResult> RunAsync(InstallContext ctx, CancellationToken ct)
{ {
var loc = TrExtension.Localizer;
foreach (var target in new[] { ctx.InstallDirectory, Paths.AppDataRoot() }) foreach (var target in new[] { ctx.InstallDirectory, Paths.AppDataRoot() })
{ {
var error = TryWrite(target); var error = TryWrite(target);
if (error is not null) if (error is not null)
{ {
return Task.FromResult(CheckResult.Fail(Id, Severity, "checks.writeAccess.title", return Task.FromResult(CheckResult.Fail(Id, Severity, "checks.writeAccess.title",
$"Cannot write to '{target}': {error}", "checks.writeAccess.hint")); loc?.Get("checks.writeAccess.cannotWrite", target, error) ?? $"Cannot write to '{target}': {error}", "checks.writeAccess.hint"));
} }
} }
return Task.FromResult(CheckResult.Ok(Id, Severity, "checks.writeAccess.title", "Install directory and data directory are writable.")); return Task.FromResult(CheckResult.Ok(Id, Severity, "checks.writeAccess.title",
loc?["checks.writeAccess.writable"] ?? "Install directory and data directory are writable."));
} }
private static string? TryWrite(string path) private static string? TryWrite(string path)
+46 -20
View File
@@ -38,11 +38,11 @@
"maxParallelExecutions": "Max. parallele Ausführungen", "maxParallelExecutions": "Max. parallele Ausführungen",
"maxParallelExecutionsHint": "Wie viele Aufgaben aus der Warteschlange der Worker gleichzeitig ausführt.", "maxParallelExecutionsHint": "Wie viele Aufgaben aus der Warteschlange der Worker gleichzeitig ausführt.",
"usageGateHeading": "Usage-Limit-Stopp", "usageGateHeading": "Usage-Limit-Stopp",
"usageGateHint": "Ab dieser Auslastung holt die Queue keine neuen Tasks mehr; laufende Tasks laufen zu Ende. 0 schaltet den Stopp ab.", "usageGateHint": "Ab dieser Auslastung holt die Warteschlange keine neuen Aufgaben mehr; laufende Aufgaben laufen zu Ende. 0 schaltet den Stopp ab.",
"usageGateFiveHourPct": "5-Stunden-Fenster (%)", "usageGateFiveHourPct": "5-Stunden-Fenster (%)",
"usageGateSevenDayPct": "7-Tage-Fenster (%)", "usageGateSevenDayPct": "7-Tage-Fenster (%)",
"autoContinueOnUsageLimit": "Nach Limit-Reset fortsetzen", "autoContinueOnUsageLimit": "Nach Limit-Reset fortsetzen",
"autoContinueOnUsageLimitHint": "Ist ein Run am Nutzungslimit abgebrochen, wird er nach Ablauf des 5-Stunden-Fensters automatisch fortgesetzt. Weckt außerdem die pausierte Queue genau zum Reset-Zeitpunkt statt erst beim nächsten Backstop-Check.", "autoContinueOnUsageLimitHint": "Ist ein Run am Nutzungslimit abgebrochen, wird er nach Ablauf des 5-Stunden-Fensters automatisch fortgesetzt. Weckt außerdem die pausierte Warteschlange genau zum Reset-Zeitpunkt statt erst beim nächsten Backstop-Check.",
"listHandlerMaxHandoffs": "Handoff-Budget des List-Handlers", "listHandlerMaxHandoffs": "Handoff-Budget des List-Handlers",
"listHandlerMaxHandoffsHint": "Wie viele Wait/Merge-Handoffs ein „Let Claude handle it\"-Run verketten darf. 4 entspricht zwei Merge-Runden. Für Läufe, die stundenlang durchlaufen sollen, höher setzen; der Wert begrenzt eine entlaufene Kette und darf daher nicht 0 sein.", "listHandlerMaxHandoffsHint": "Wie viele Wait/Merge-Handoffs ein „Let Claude handle it\"-Run verketten darf. 4 entspricht zwei Merge-Runden. Für Läufe, die stundenlang durchlaufen sollen, höher setzen; der Wert begrenzt eine entlaufene Kette und darf daher nicht 0 sein.",
"powerUseHeading": "PowerUse", "powerUseHeading": "PowerUse",
@@ -54,7 +54,7 @@
"powerUseWindowHint": "Beide Zeiten leer = rund um die Uhr. Das Fenster wird bei jedem Start einer Handler-Phase geprüft — eine Kette, die über das Ende hinausläuft, macht ohne PowerUse weiter.", "powerUseWindowHint": "Beide Zeiten leer = rund um die Uhr. Das Fenster wird bei jedem Start einer Handler-Phase geprüft — eine Kette, die über das Ende hinausläuft, macht ohne PowerUse weiter.",
"powerUseWindowInvalid": "PowerUse-Zeiten müssen im Format HH:mm sein oder leer bleiben.", "powerUseWindowInvalid": "PowerUse-Zeiten müssen im Format HH:mm sein oder leer bleiben.",
"throttleStagesHeading": "Throttle-Stufen", "throttleStagesHeading": "Throttle-Stufen",
"throttleStagesHint": "Die Parallelität wird vor dem harten Gate begrenzt: soft auf 2 Slots, hard auf 1. Eingestellt wird das durch Ziehen der Marker an den Gauges im Usage-Monitor. Der Monitor schließt diese Einstellungen — vorher speichern, wenn du etwas geändert hast.", "throttleStagesHint": "Die Parallelität wird vor dem harten Limit-Stopp begrenzt: soft auf 2 Slots, hard auf 1. Eingestellt wird das durch Ziehen der Marker an den Gauges im Usage-Monitor. Der Monitor schließt diese Einstellungen — vorher speichern, wenn du etwas geändert hast.",
"throttleStagesValues": "5h: soft {0}% / hard {1}% · 7d: soft {2}% / hard {3}%", "throttleStagesValues": "5h: soft {0}% / hard {1}% · 7d: soft {2}% / hard {3}%",
"openUsageMonitor": "Usage-Monitor öffnen...", "openUsageMonitor": "Usage-Monitor öffnen...",
"reportExcludedPaths": "Bericht: ausgeschlossene Pfade (einer pro Zeile)", "reportExcludedPaths": "Bericht: ausgeschlossene Pfade (einer pro Zeile)",
@@ -236,7 +236,7 @@
"reasonIsPlanningParent": "Teil eines Plans", "reasonIsPlanningParent": "Teil eines Plans",
"reasonIsChildTask": "Teilaufgaben haben keine eigene Planungssitzung", "reasonIsChildTask": "Teilaufgaben haben keine eigene Planungssitzung",
"reasonPlanAlreadyFinalized": "Plan ist bereits finalisiert", "reasonPlanAlreadyFinalized": "Plan ist bereits finalisiert",
"reasonNoWorktree": "Kein Worktree für diesen Task", "reasonNoWorktree": "Kein Worktree für diese Aufgabe",
"cancel": "Abbrechen", "cancel": "Abbrechen",
"removeFromQueueTip": "Aus Warteschlange entfernen", "removeFromQueueTip": "Aus Warteschlange entfernen",
"toggleSubtasksTip": "Unteraufgaben ein-/ausklappen", "toggleSubtasksTip": "Unteraufgaben ein-/ausklappen",
@@ -268,13 +268,13 @@
"contextRemoveFromFolder": "Aus Ordner herausnehmen", "contextRemoveFromFolder": "Aus Ordner herausnehmen",
"newList": "Neue Liste", "newList": "Neue Liste",
"addReposTip": "Repos als Listen hinzufügen", "addReposTip": "Repos als Listen hinzufügen",
"noRepoBannerText": "Noch kein Repo verknüpft. ClaudeDo führt Tasks in einem Git-Repo aus.", "noRepoBannerText": "Noch kein Repo verknüpft. ClaudeDo führt Aufgaben in einem Git-Repo aus.",
"noRepoBannerButton": "Repo importieren", "noRepoBannerButton": "Repo importieren",
"importTicketsResult": "{0} Tickets geprüft, {1} neue Tasks." "importTicketsResult": "{0} Tickets geprüft, {1} neue Tasks."
}, },
"details": { "details": {
"deleteTaskTip": "Aufgabe löschen", "deleteTaskTip": "Aufgabe löschen",
"killSessionTip": "Laufende Sitzung beenden", "killSessionTip": "Laufende Sitzung hart beenden",
"sendToQueue": "In Warteschlange einreihen", "sendToQueue": "In Warteschlange einreihen",
"sendToQueueTip": "Diese Aufgabe in die Warteschlange einreihen, damit Claude sie beim nächsten Lauf übernimmt", "sendToQueueTip": "Diese Aufgabe in die Warteschlange einreihen, damit Claude sie beim nächsten Lauf übernimmt",
"copyTaskIdTip": "Aufgaben-ID kopieren", "copyTaskIdTip": "Aufgaben-ID kopieren",
@@ -379,7 +379,7 @@
"browseForFolder": "Ordner wählen…", "browseForFolder": "Ordner wählen…",
"focusMode": "Fokus", "focusMode": "Fokus",
"overviewMode": "Übersicht", "overviewMode": "Übersicht",
"closeSession": "Sitzung beenden", "closeSession": "Sitzung hart beenden",
"closeSessionConfirm": "Laufende Sitzung beenden? Der Claude-Prozess wird abgebrochen.", "closeSessionConfirm": "Laufende Sitzung beenden? Der Claude-Prozess wird abgebrochen.",
"conptyLaunchFailed": "Interaktive Sitzung konnte nicht geöffnet werden: {0}", "conptyLaunchFailed": "Interaktive Sitzung konnte nicht geöffnet werden: {0}",
"conptyStarting": "Sitzung wird gestartet…", "conptyStarting": "Sitzung wird gestartet…",
@@ -403,7 +403,7 @@
"queueRefreshFailed": "Warteschlange konnte nicht aktualisiert werden: {0}", "queueRefreshFailed": "Warteschlange konnte nicht aktualisiert werden: {0}",
"abandonRun": "Lauf abbrechen", "abandonRun": "Lauf abbrechen",
"abandonRunTip": "Die noch offenen Tasks dieses Laufs abbrechen und die Sitzung schließen", "abandonRunTip": "Die noch offenen Tasks dieses Laufs abbrechen und die Sitzung schließen",
"abandonRunConfirm": "{0} offene(n) Task(s) abbrechen und diese Sitzung schließen? Tasks im Review oder bereits erledigt bleiben unangetastet.", "abandonRunConfirm": "{0} offene Aufgabe(n) abbrechen und diese Sitzung schließen? Aufgaben im Review oder bereits erledigt bleiben unangetastet.",
"abandonRunFailed": "Offene Tasks konnten nicht abgebrochen werden: {0}", "abandonRunFailed": "Offene Tasks konnten nicht abgebrochen werden: {0}",
"question": { "question": {
"title": "Claude fragt nach", "title": "Claude fragt nach",
@@ -579,14 +579,14 @@
"noGauges": "Noch keine Nutzungslimits gemeldet.", "noGauges": "Noch keine Nutzungslimits gemeldet.",
"refreshHint": "Jetzt aktualisieren — Abruf alle 5 Min., während ein Task läuft, sonst alle 15 Min.", "refreshHint": "Jetzt aktualisieren — Abruf alle 5 Min., während ein Task läuft, sonst alle 15 Min.",
"staleFormat": "Werte veraltet (Stand {0})", "staleFormat": "Werte veraltet (Stand {0})",
"staleGateHint": "Das Gate greift in diesem Zustand nicht.", "staleGateHint": "Der Limit-Stopp greift in diesem Zustand nicht.",
"gateBlockedFormat": "Warteschlange pausiert — {0}", "gateBlockedFormat": "Warteschlange pausiert — {0}",
"throttleFormat": "Warteschlange gedrosselt: {0}/{1} Slots ({2})", "throttleFormat": "Warteschlange gedrosselt: {0}/{1} Slots ({2})",
"resetIn": "Reset in {0} ({1})", "resetIn": "Reset in {0} ({1})",
"dragHint": "Marker ziehen oder Wert unten eintragen.", "dragHint": "Marker ziehen oder Wert unten eintragen.",
"legendSoft": "Soft · 2 Slots", "legendSoft": "Soft · 2 Slots",
"legendHard": "Hard · 1 Slot", "legendHard": "Hard · 1 Slot",
"legendGate": "Gate · Pause", "legendGate": "Limit-Stopp · Pause",
"gaugeSession": "Session (5 Std.)", "gaugeSession": "Session (5 Std.)",
"gaugeWeeklyAll": "Woche (alle Modelle)", "gaugeWeeklyAll": "Woche (alle Modelle)",
"gaugeWeeklyScopedFormat": "Woche ({0})", "gaugeWeeklyScopedFormat": "Woche ({0})",
@@ -606,7 +606,7 @@
"columnOtherOut": "ÜBRIGES OUT", "columnOtherOut": "ÜBRIGES OUT",
"columnOtherCache": "ÜBRIGES CACHE", "columnOtherCache": "ÜBRIGES CACHE",
"columnShare": "ANTEIL", "columnShare": "ANTEIL",
"columnTask": "TASK", "columnTask": "AUFGABE",
"columnList": "LISTE", "columnList": "LISTE",
"columnModelShort": "MODELL", "columnModelShort": "MODELL",
"columnRuns": "RUNS", "columnRuns": "RUNS",
@@ -710,35 +710,61 @@
"checks": { "checks": {
"git": { "git": {
"title": "Git", "title": "Git",
"hint": "Installiere Git von https://git-scm.com/downloads und starte den Installer neu." "hint": "Installiere Git von https://git-scm.com/downloads und starte den Installer neu.",
"notFound": "git wurde nicht im PATH gefunden.",
"exitCode": "'{0}' wurde mit Exitcode {1} beendet.",
"resolvedFormat": "{0} — {1}"
}, },
"gitIdentity": { "gitIdentity": {
"title": "Git-Identität", "title": "Git-Identität",
"hint": "Führe aus: git config --global user.name \"Dein Name\" und git config --global user.email \"du@example.com\"" "hint": "Führe aus: git config --global user.name \"Dein Name\" und git config --global user.email \"du@example.com\"",
"notFound": "git wurde nicht gefunden — Identität konnte nicht geprüft werden.",
"missing": "Fehlende Git-Identität: {0}."
}, },
"ports": { "ports": {
"title": "Ports", "title": "Ports",
"hint": "Ändere die SignalR-/MCP-Ports auf der Dienste-Seite, oder beende den blockierenden Prozess." "hint": "Ändere die SignalR-/MCP-Ports auf der Dienste-Seite, oder beende den blockierenden Prozess.",
"inUse": "{0}-Port {1} ist belegt.",
"inUseBy": "{0}-Port {1} ist belegt von '{2}'.",
"inUseByOwnWorker": "Belegt vom laufenden ClaudeDo Worker: {0}.",
"available": "Ports sind verfügbar."
}, },
"writeAccess": { "writeAccess": {
"title": "Schreibzugriff", "title": "Schreibzugriff",
"hint": "Wähle ein anderes Installationsverzeichnis oder starte den Installer mit ausreichenden Rechten (z. B. als Administrator)." "hint": "Wähle ein anderes Installationsverzeichnis oder starte den Installer mit ausreichenden Rechten (z. B. als Administrator).",
"cannotWrite": "Kein Schreibzugriff auf '{0}': {1}",
"writable": "Installations- und Datenverzeichnis sind beschreibbar."
}, },
"claudeCli": { "claudeCli": {
"title": "Claude CLI", "title": "Claude CLI",
"hint": "Installiere die Claude CLI (npm install -g @anthropic-ai/claude-code) und starte danach dein Terminal/deine Anmeldesitzung neu, damit der aktualisierte PATH greift." "hint": "Installiere die Claude CLI (npm install -g @anthropic-ai/claude-code) und starte danach dein Terminal/deine Anmeldesitzung neu, damit der aktualisierte PATH greift.",
"notFound": "'{0}' wurde nicht im PATH gefunden. Durchsucht: {1}.",
"exitCode": "'{0}' wurde mit Exitcode {1} beendet.",
"resolvedFormat": "{0} — {1}"
}, },
"claudeVersion": { "claudeVersion": {
"title": "Claude-CLI-Version", "title": "Claude-CLI-Version",
"hint": "Aktualisiere die CLI: npm update -g @anthropic-ai/claude-code" "hint": "Aktualisiere die CLI: npm update -g @anthropic-ai/claude-code",
"notFound": "Die claude-CLI wurde nicht gefunden — Version konnte nicht geprüft werden.",
"parseFailed": "Konnte keine Version aus '{0}' auslesen.",
"tooOld": "claude {0} ist älter als die erforderliche Version {1}.",
"ok": "claude {0} erfüllt die Mindestanforderung ({1})."
}, },
"claudeAuth": { "claudeAuth": {
"title": "Claude-CLI-Login", "title": "Claude-CLI-Login",
"hint": "Starte 'claude' einmal und schließe den Login-Vorgang ab." "hint": "Starte 'claude' einmal und schließe den Login-Vorgang ab.",
"notFound": "Die claude-CLI wurde nicht gefunden — Login konnte nicht geprüft werden.",
"undetermined": "Login-Status konnte nicht ermittelt werden.",
"notLoggedIn": "Nicht angemeldet.",
"loggedIn": "Angemeldet."
}, },
"permissionModeAuto": { "permissionModeAuto": {
"title": "Berechtigungsmodus", "title": "Berechtigungsmodus",
"hint": "Aktualisiere die CLI: npm update -g @anthropic-ai/claude-code. ClaudeDo funktioniert auch im Modus acceptEdits/default, dann mit mehr Rückfragen." "hint": "Aktualisiere die CLI: npm update -g @anthropic-ai/claude-code. ClaudeDo funktioniert auch im Modus acceptEdits/default, dann mit mehr Rückfragen.",
"notFound": "Die claude-CLI wurde nicht gefunden — Unterstützung für permission-mode konnte nicht geprüft werden.",
"undetermined": "Konnte nicht ermitteln, ob --permission-mode auto unterstützt wird.",
"ok": "--permission-mode auto wird von dieser CLI erkannt.",
"notListed": "Diese claude-CLI-Version listet 'auto' nicht als --permission-mode-Option."
} }
}, },
"planning": { "planning": {
@@ -848,7 +874,7 @@
"mergeSection": { "openWorktreeFailed": "Worktree konnte nicht geöffnet werden: {0}" }, "mergeSection": { "openWorktreeFailed": "Worktree konnte nicht geöffnet werden: {0}" },
"about": { "openPathFailed": "Pfad konnte nicht geöffnet werden: {0}" }, "about": { "openPathFailed": "Pfad konnte nicht geöffnet werden: {0}" },
"lists": { "localSuffix": "{0} / lokal", "smartMyDay": "Mein Tag", "smartImportant": "Wichtig", "smartPlanned": "Geplant", "smartNotes": "Notizen", "virtualQueue": "Warteschlange", "virtualRunning": "Läuft", "virtualReview": "Prüfung", "newList": "Neue Liste", "findingsNotFound": "Für diese Liste gibt es noch keine Findings.", "findingsOpenFailed": "Findings-Ordner konnte nicht geöffnet werden: {0}", "openInExplorerFailed": "Pfad konnte nicht geöffnet werden: {0}" }, "lists": { "localSuffix": "{0} / lokal", "smartMyDay": "Mein Tag", "smartImportant": "Wichtig", "smartPlanned": "Geplant", "smartNotes": "Notizen", "virtualQueue": "Warteschlange", "virtualRunning": "Läuft", "virtualReview": "Prüfung", "newList": "Neue Liste", "findingsNotFound": "Für diese Liste gibt es noch keine Findings.", "findingsOpenFailed": "Findings-Ordner konnte nicht geöffnet werden: {0}", "openInExplorerFailed": "Pfad konnte nicht geöffnet werden: {0}" },
"repoImport": { "loadFailed": "Gespeicherte Ordner konnten nicht geladen werden: {0}", "saveFailed": "Ordner konnten nicht gespeichert werden: {0}" } "repoImport": { "loadFailed": "Gespeicherte Ordner konnten nicht geladen werden: {0}", "saveFailed": "Ordner konnten nicht gespeichert werden: {0}", "createButton": "{0} Liste(n) erstellen" }
}, },
"ops": { "ops": {
"stalledHint": "Läuft weiter — Worker antwortet noch", "stalledHint": "Läuft weiter — Worker antwortet noch",
+35 -9
View File
@@ -710,35 +710,61 @@
"checks": { "checks": {
"git": { "git": {
"title": "Git", "title": "Git",
"hint": "Install Git from https://git-scm.com/downloads, then restart the installer." "hint": "Install Git from https://git-scm.com/downloads, then restart the installer.",
"notFound": "git was not found on PATH.",
"exitCode": "'{0}' exited with code {1}.",
"resolvedFormat": "{0} — {1}"
}, },
"gitIdentity": { "gitIdentity": {
"title": "Git Identity", "title": "Git Identity",
"hint": "Run: git config --global user.name \"Your Name\" and git config --global user.email \"you@example.com\"" "hint": "Run: git config --global user.name \"Your Name\" and git config --global user.email \"you@example.com\"",
"notFound": "git was not found — identity could not be checked.",
"missing": "Missing git identity: {0}."
}, },
"ports": { "ports": {
"title": "Ports", "title": "Ports",
"hint": "Change the SignalR/MCP ports on the Service page, or stop the process using them." "hint": "Change the SignalR/MCP ports on the Service page, or stop the process using them.",
"inUse": "{0} port {1} is in use.",
"inUseBy": "{0} port {1} is in use by '{2}'.",
"inUseByOwnWorker": "In use by the running ClaudeDo Worker: {0}.",
"available": "Ports are available."
}, },
"writeAccess": { "writeAccess": {
"title": "Write Access", "title": "Write Access",
"hint": "Choose a different install directory, or run the installer with sufficient permissions (e.g. as administrator)." "hint": "Choose a different install directory, or run the installer with sufficient permissions (e.g. as administrator).",
"cannotWrite": "Cannot write to '{0}': {1}",
"writable": "Install directory and data directory are writable."
}, },
"claudeCli": { "claudeCli": {
"title": "Claude CLI", "title": "Claude CLI",
"hint": "Install the Claude CLI (npm install -g @anthropic-ai/claude-code), then restart your terminal/login session so the updated PATH takes effect." "hint": "Install the Claude CLI (npm install -g @anthropic-ai/claude-code), then restart your terminal/login session so the updated PATH takes effect.",
"notFound": "'{0}' was not found on PATH. Searched: {1}.",
"exitCode": "'{0}' exited with code {1}.",
"resolvedFormat": "{0} — {1}"
}, },
"claudeVersion": { "claudeVersion": {
"title": "Claude CLI Version", "title": "Claude CLI Version",
"hint": "Update the CLI: npm update -g @anthropic-ai/claude-code" "hint": "Update the CLI: npm update -g @anthropic-ai/claude-code",
"notFound": "The claude CLI was not found — version could not be checked.",
"parseFailed": "Could not parse a version from '{0}'.",
"tooOld": "claude {0} is older than the required {1}.",
"ok": "claude {0} meets the minimum ({1})."
}, },
"claudeAuth": { "claudeAuth": {
"title": "Claude CLI Login", "title": "Claude CLI Login",
"hint": "Run 'claude' once and complete the login flow." "hint": "Run 'claude' once and complete the login flow.",
"notFound": "The claude CLI was not found — login could not be checked.",
"undetermined": "Login status could not be determined.",
"notLoggedIn": "Not logged in.",
"loggedIn": "Logged in."
}, },
"permissionModeAuto": { "permissionModeAuto": {
"title": "Permission Mode", "title": "Permission Mode",
"hint": "Update the CLI: npm update -g @anthropic-ai/claude-code. ClaudeDo still works in acceptEdits/default mode, with more prompts." "hint": "Update the CLI: npm update -g @anthropic-ai/claude-code. ClaudeDo still works in acceptEdits/default mode, with more prompts.",
"notFound": "The claude CLI was not found — permission-mode support could not be checked.",
"undetermined": "Could not determine whether --permission-mode auto is supported.",
"ok": "--permission-mode auto is recognized by this CLI.",
"notListed": "This claude CLI version does not list 'auto' as a --permission-mode choice."
} }
}, },
"planning": { "planning": {
@@ -848,7 +874,7 @@
"mergeSection": { "openWorktreeFailed": "Couldn't open worktree: {0}" }, "mergeSection": { "openWorktreeFailed": "Couldn't open worktree: {0}" },
"about": { "openPathFailed": "Couldn't open path: {0}" }, "about": { "openPathFailed": "Couldn't open path: {0}" },
"lists": { "localSuffix": "{0} / local", "smartMyDay": "My Day", "smartImportant": "Important", "smartPlanned": "Planned", "smartNotes": "Notes", "virtualQueue": "Queue", "virtualRunning": "Running", "virtualReview": "Review", "newList": "New list", "findingsNotFound": "No findings yet for this list.", "findingsOpenFailed": "Couldn't open findings folder: {0}", "openInExplorerFailed": "Couldn't open path: {0}" }, "lists": { "localSuffix": "{0} / local", "smartMyDay": "My Day", "smartImportant": "Important", "smartPlanned": "Planned", "smartNotes": "Notes", "virtualQueue": "Queue", "virtualRunning": "Running", "virtualReview": "Review", "newList": "New list", "findingsNotFound": "No findings yet for this list.", "findingsOpenFailed": "Couldn't open findings folder: {0}", "openInExplorerFailed": "Couldn't open path: {0}" },
"repoImport": { "loadFailed": "Couldn't load remembered folders: {0}", "saveFailed": "Couldn't save folders: {0}" } "repoImport": { "loadFailed": "Couldn't load remembered folders: {0}", "saveFailed": "Couldn't save folders: {0}", "createButton": "Create {0} list(s)" }
}, },
"ops": { "ops": {
"stalledHint": "Still running — worker hasn't responded in a while", "stalledHint": "Still running — worker hasn't responded in a while",
@@ -44,7 +44,7 @@ public sealed partial class RepoImportModalViewModel : ViewModelBase
public int CreateCount => Repos.Count(r => r.IsChecked && !r.AlreadyAdded); public int CreateCount => Repos.Count(r => r.IsChecked && !r.AlreadyAdded);
public bool CanCreate => CreateCount > 0; public bool CanCreate => CreateCount > 0;
public string CreateButtonText => $"Create {CreateCount} list(s)"; public string CreateButtonText => Loc.T("vm.repoImport.createButton", CreateCount);
public bool HasFolders => _folders.Count > 0; public bool HasFolders => _folders.Count > 0;
public RepoImportModalViewModel(IDbContextFactory<ClaudeDoDbContext> dbFactory, IWorkerClient workerClient) public RepoImportModalViewModel(IDbContextFactory<ClaudeDoDbContext> dbFactory, IWorkerClient workerClient)