using System.Text.Json; using System.Text.Json.Serialization; using ClaudeDo.Data; namespace ClaudeDo.Worker.Config; public sealed class WorkerConfig { [JsonPropertyName("db_path")] public string DbPath { get; set; } = "~/.todo-app/todo.db"; [JsonPropertyName("sandbox_root")] public string SandboxRoot { get; set; } = "~/.todo-app/sandbox"; [JsonPropertyName("log_root")] public string LogRoot { get; set; } = "~/.todo-app/logs"; /// "sibling" → place worktrees next to the target repo; "central" → under . [JsonPropertyName("worktree_root_strategy")] public string WorktreeRootStrategy { get; set; } = "sibling"; [JsonPropertyName("central_worktree_root")] public string CentralWorktreeRoot { get; set; } = "~/.todo-app/worktrees"; [JsonPropertyName("queue_backstop_interval_ms")] public int QueueBackstopIntervalMs { get; set; } = 30_000; [JsonPropertyName("signalr_port")] public int SignalRPort { get; set; } = 47_821; [JsonPropertyName("claude_bin")] public string ClaudeBin { get; set; } = "claude"; /// Port for the external MCP endpoint. 0 disables the external listener entirely. [JsonPropertyName("external_mcp_port")] public int ExternalMcpPort { get; set; } = 47_822; /// Optional API key clients must pass via X-ClaudeDo-Key header. Null/empty = loopback trust only. [JsonPropertyName("external_mcp_api_key")] public string? ExternalMcpApiKey { get; set; } public static string DefaultConfigPath => Path.Combine(Paths.AppDataRoot(), "worker.config.json"); /// /// Loads the config from (defaults to ). /// Missing file → returns defaults. Resolves all path-typed fields to absolute paths. /// public static WorkerConfig Load(string? path = null) { path ??= DefaultConfigPath; WorkerConfig cfg; if (File.Exists(path)) { var json = File.ReadAllText(path); cfg = JsonSerializer.Deserialize(json, JsonOpts) ?? throw new InvalidOperationException($"Failed to parse {path}"); } else { cfg = new WorkerConfig(); } cfg.DbPath = Paths.Expand(cfg.DbPath); cfg.SandboxRoot = Paths.Expand(cfg.SandboxRoot); cfg.LogRoot = Paths.Expand(cfg.LogRoot); cfg.CentralWorktreeRoot = Paths.Expand(cfg.CentralWorktreeRoot); return cfg; } private static readonly JsonSerializerOptions JsonOpts = new() { ReadCommentHandling = JsonCommentHandling.Skip, AllowTrailingCommas = true, }; }