{"repo": "ajnunezg/richter", "files": {"apps/macos/RichterApp/RichterApp/Views/DoctorView.swift": {"eb0411ce6bf4cc4db57425dfeccb7a5818825b41": {"t": 1785745903.532207, "n": 8057, "h": "a796e8a626393717", "c": "import SwiftUI\nimport AppKit\n\nstruct DoctorView: View {\n @EnvironmentObject var state: AppState\n @State private var isRunningDiagnostic = false\n @State private var results: DiagnosticResults?\n\n struct DiagnosticResults {\n var daemonOk: Bool = false\n var daemonPid: String = \"unknown\"\n var daemonUptime: String = \"unknown\"\n var shimStatus: [(String, Bool)] = []\n var hookStatus: [(String, Bool)] = []\n var mcpOk: Bool = false\n var mcpPort: String = \"9777\"\n var providerStatus: [(String, Bool)] = []\n }\n\n var body: some View {\n ScrollView {\n VStack(alignment: .leading, spacing: 16) {\n HStack {\n Text(\"Doctor\")\n .font(.title)\n .fontWeight(.bold)\n Spacer()\n Button(action: runDiagnostic) {\n if isRunningDiagnostic {\n ProgressView()\n .controlSize(.small)\n Text(\"Running diagnostics…\")\n } else {\n Image(systemName: \"stethoscope\")\n Text(\"Run Diagnostic\")\n }\n }\n .disabled(isRunningDiagnostic)\n }\n\n if let r = results {\n DiagnosticGroup(title: \"Daemon\", icon: \"network\") {\n DiagnosticRow(label: \"Status\", value: r.daemonOk ? \"Running\" : \"Not running\", ok: r.daemonOk)\n DiagnosticRow(label: \"PID\", value: r.daemonPid, ok: r.daemonOk)\n DiagnosticRow(label: \"Uptime\", value: r.daemonUptime, ok: r.daemonOk)\n }\n\n DiagnosticGroup(title: \"Shims\", icon: \"link\") {\n ForEach(r.shimStatus, id: \\.0) { tool, ok in\n DiagnosticRow(label: tool, value: ok ? \"installed\" : \"missing\", ok: ok)\n }\n if r.shimStatus.isEmpty {\n Text(\"No shims installed. Run `richter install shims`.\")\n .font(.caption)\n .foregroundColor(.secondary)\n }\n }\n\n DiagnosticGroup(title: \"Hooks\", icon: \"gearshape.2\") {\n ForEach(r.hookStatus, id: \\.0) { agent, ok in\n DiagnosticRow(label: agent, value: ok ? \"configured\" : \"not configured\", ok: ok)\n }\n }\n\n DiagnosticGroup(title: \"MCP Server\", icon: \"antenna.radiowaves.left.and.right\") {\n DiagnosticRow(label: \"Status\", value: r.mcpOk ? \"Listening\" : \"Not reachable\", ok: r.mcpOk)\n DiagnosticRow(label: \"Port\", value: r.mcpPort, ok: r.mcpOk)\n }\n\n DiagnosticGroup(title: \"Permissions\", icon: \"lock.shield\") {\n PermissionRow(label: \"Notifications\", identifier: \"x-apple.systempreferences:com.apple.preference.notifications\")\n PermissionRow(label: \"Full Disk Access\", identifier: \"x-apple.systempreferences:com.apple.settings.Privacy\")\n PermissionRow(label: \"Accessibility\", identifier: \"x-apple.systempreferences:com.apple.settings.Accessibility\")\n }\n\n DiagnosticGroup(title: \"Model Providers\", icon: \"brain\") {\n ForEach(r.providerStatus, id: \\.0) { provider, ok in\n DiagnosticRow(label: provider, value: ok ? \"available\" : \"unavailable\", ok: ok)\n }\n }\n } else if !isRunningDiagnostic {\n Text(\"Press \\\"Run Diagnostic\\\" to check your Richter installation.\")\n .foregroundColor(.secondary)\n .padding(.top, 40)\n }\n\n Spacer()\n }\n .padding()\n }\n .background(.ultraThinMaterial)\n }\n\n private func runDiagnostic() {\n isRunningDiagnostic = true\n results = nil\n\n Task {\n // Allow spinner to show briefly\n try? await Task.sleep(nanoseconds: 500_000_000)\n\n let daemonReachable = await DaemonClient.shared.isReachable()\n\n // Check shims by looking at ~/.richter/shims/\n let shimDir = \"\\(NSHomeDirectory())/.richter/shims\"\n let fm = FileManager.default\n let shimTools = [\"cargo\", \"npm\", \"pnpm\", \"yarn\", \"go\", \"python\", \"pytest\", \"make\", \"cmake\", \"bazel\"]\n var shimStatus: [(String, Bool)] = []\n if fm.fileExists(atPath: shimDir) {\n for tool in shimTools {\n shimStatus.append((tool, fm.fileExists(atPath: \"\\(shimDir)/\\(tool)\")))\n }\n }\n\n // Check hooks\n let hooksDir = \"\\(NSHomeDirectory())/.richter/hooks\"\n var hookStatus: [(String, Bool)] = []\n for agent in [\"claude\", \"codex\", \"droid\"] {\n hookStatus.append((agent, fm.fileExists(atPath: \"\\(hooksDir)/\\(agent).toml\")))\n }\n\n // Check MCP\n let mcpOk = fm.fileExists(atPath: \"\\(NSHomeDirectory())/.richter/mcp.json\")\n\n // Check for API keys (redacted check)\n var providerStatus: [(String, Bool)] = [\n (\"OpenAI\", ProcessInfo.processInfo.environment[\"OPENAI_API_KEY\"] != nil),\n (\"Anthropic\", ProcessInfo.processInfo.environment[\"ANTHROPIC_API_KEY\"] != nil),\n (\"DeepSeek\", ProcessInfo.processInfo.environment[\"DEEPSEEK_API_KEY\"] != nil),\n ]\n\n let r = DiagnosticResults(\n daemonOk: daemonReachable,\n daemonPid: daemonReachable ? \"running\" : \"N/A\",\n daemonUptime: daemonReachable ? (try? await fetchDaemonUptime()) ?? \"unknown\" : \"N/A\",\n shimStatus: shimStatus,\n hookStatus: hookStatus,\n mcpOk: mcpOk,\n mcpPort: \"9777\",\n providerStatus: providerStatus\n )\n\n await MainActor.run {\n self.results = r\n self.isRunningDiagnostic = false\n }\n }\n }\n\n private func fetchDaemonUptime() async throws -> String {\n let resp: HealthResponse = try await DaemonClient.shared.sendRequest(\"/health\")\n return resp.timestamp\n }\n}\n\n// MARK: - Subviews\n\nstruct DiagnosticGroup: View {\n let title: String\n let icon: String\n @ViewBuilder let content: () -> Content\n\n var body: some View {\n GroupBox {\n VStack(alignment: .leading, spacing: 8) {\n Label(title, systemImage: icon)\n .font(.headline)\n content()\n }\n .padding(4)\n }\n }\n}\n\nstruct DiagnosticRow: View {\n let label: String\n let value: String\n let ok: Bool\n\n var body: some View {\n HStack {\n Image(systemName: ok ? \"checkmark.circle.fill\" : \"xmark.circle.fill\")\n .foregroundColor(ok ? .green : .red)\n Text(label)\n .foregroundColor(.secondary)\n Spacer()\n Text(value)\n .foregroundColor(ok ? .primary : .red)\n }\n .font(.callout)\n }\n}\n\nstruct PermissionRow: View {\n let label: String\n let identifier: String\n\n var body: some View {\n HStack {\n Image(systemName: \"circle\")\n .foregroundColor(.orange)\n Text(label)\n .foregroundColor(.secondary)\n Spacer()\n Button(\"Open Settings\") {\n if let url = URL(string: identifier) {\n NSWorkspace.shared.open(url)\n }\n }\n .buttonStyle(.link)\n }\n .font(.callout)\n }\n}\n"}, "0f5b6c48436020556f1f11f529b6d1471156a87e": {"t": 1785757777.9800808, "n": 8057, "h": "a796e8a626393717", "c": "import SwiftUI\nimport AppKit\n\nstruct DoctorView: View {\n @EnvironmentObject var state: AppState\n @State private var isRunningDiagnostic = false\n @State private var results: DiagnosticResults?\n\n struct DiagnosticResults {\n var daemonOk: Bool = false\n var daemonPid: String = \"unknown\"\n var daemonUptime: String = \"unknown\"\n var shimStatus: [(String, Bool)] = []\n var hookStatus: [(String, Bool)] = []\n var mcpOk: Bool = false\n var mcpPort: String = \"9777\"\n var providerStatus: [(String, Bool)] = []\n }\n\n var body: some View {\n ScrollView {\n VStack(alignment: .leading, spacing: 16) {\n HStack {\n Text(\"Doctor\")\n .font(.title)\n .fontWeight(.bold)\n Spacer()\n Button(action: runDiagnostic) {\n if isRunningDiagnostic {\n ProgressView()\n .controlSize(.small)\n Text(\"Running diagnostics…\")\n } else {\n Image(systemName: \"stethoscope\")\n Text(\"Run Diagnostic\")\n }\n }\n .disabled(isRunningDiagnostic)\n }\n\n if let r = results {\n DiagnosticGroup(title: \"Daemon\", icon: \"network\") {\n DiagnosticRow(label: \"Status\", value: r.daemonOk ? \"Running\" : \"Not running\", ok: r.daemonOk)\n DiagnosticRow(label: \"PID\", value: r.daemonPid, ok: r.daemonOk)\n DiagnosticRow(label: \"Uptime\", value: r.daemonUptime, ok: r.daemonOk)\n }\n\n DiagnosticGroup(title: \"Shims\", icon: \"link\") {\n ForEach(r.shimStatus, id: \\.0) { tool, ok in\n DiagnosticRow(label: tool, value: ok ? \"installed\" : \"missing\", ok: ok)\n }\n if r.shimStatus.isEmpty {\n Text(\"No shims installed. Run `richter install shims`.\")\n .font(.caption)\n .foregroundColor(.secondary)\n }\n }\n\n DiagnosticGroup(title: \"Hooks\", icon: \"gearshape.2\") {\n ForEach(r.hookStatus, id: \\.0) { agent, ok in\n DiagnosticRow(label: agent, value: ok ? \"configured\" : \"not configured\", ok: ok)\n }\n }\n\n DiagnosticGroup(title: \"MCP Server\", icon: \"antenna.radiowaves.left.and.right\") {\n DiagnosticRow(label: \"Status\", value: r.mcpOk ? \"Listening\" : \"Not reachable\", ok: r.mcpOk)\n DiagnosticRow(label: \"Port\", value: r.mcpPort, ok: r.mcpOk)\n }\n\n DiagnosticGroup(title: \"Permissions\", icon: \"lock.shield\") {\n PermissionRow(label: \"Notifications\", identifier: \"x-apple.systempreferences:com.apple.preference.notifications\")\n PermissionRow(label: \"Full Disk Access\", identifier: \"x-apple.systempreferences:com.apple.settings.Privacy\")\n PermissionRow(label: \"Accessibility\", identifier: \"x-apple.systempreferences:com.apple.settings.Accessibility\")\n }\n\n DiagnosticGroup(title: \"Model Providers\", icon: \"brain\") {\n ForEach(r.providerStatus, id: \\.0) { provider, ok in\n DiagnosticRow(label: provider, value: ok ? \"available\" : \"unavailable\", ok: ok)\n }\n }\n } else if !isRunningDiagnostic {\n Text(\"Press \\\"Run Diagnostic\\\" to check your Richter installation.\")\n .foregroundColor(.secondary)\n .padding(.top, 40)\n }\n\n Spacer()\n }\n .padding()\n }\n .background(.ultraThinMaterial)\n }\n\n private func runDiagnostic() {\n isRunningDiagnostic = true\n results = nil\n\n Task {\n // Allow spinner to show briefly\n try? await Task.sleep(nanoseconds: 500_000_000)\n\n let daemonReachable = await DaemonClient.shared.isReachable()\n\n // Check shims by looking at ~/.richter/shims/\n let shimDir = \"\\(NSHomeDirectory())/.richter/shims\"\n let fm = FileManager.default\n let shimTools = [\"cargo\", \"npm\", \"pnpm\", \"yarn\", \"go\", \"python\", \"pytest\", \"make\", \"cmake\", \"bazel\"]\n var shimStatus: [(String, Bool)] = []\n if fm.fileExists(atPath: shimDir) {\n for tool in shimTools {\n shimStatus.append((tool, fm.fileExists(atPath: \"\\(shimDir)/\\(tool)\")))\n }\n }\n\n // Check hooks\n let hooksDir = \"\\(NSHomeDirectory())/.richter/hooks\"\n var hookStatus: [(String, Bool)] = []\n for agent in [\"claude\", \"codex\", \"droid\"] {\n hookStatus.append((agent, fm.fileExists(atPath: \"\\(hooksDir)/\\(agent).toml\")))\n }\n\n // Check MCP\n let mcpOk = fm.fileExists(atPath: \"\\(NSHomeDirectory())/.richter/mcp.json\")\n\n // Check for API keys (redacted check)\n var providerStatus: [(String, Bool)] = [\n (\"OpenAI\", ProcessInfo.processInfo.environment[\"OPENAI_API_KEY\"] != nil),\n (\"Anthropic\", ProcessInfo.processInfo.environment[\"ANTHROPIC_API_KEY\"] != nil),\n (\"DeepSeek\", ProcessInfo.processInfo.environment[\"DEEPSEEK_API_KEY\"] != nil),\n ]\n\n let r = DiagnosticResults(\n daemonOk: daemonReachable,\n daemonPid: daemonReachable ? \"running\" : \"N/A\",\n daemonUptime: daemonReachable ? (try? await fetchDaemonUptime()) ?? \"unknown\" : \"N/A\",\n shimStatus: shimStatus,\n hookStatus: hookStatus,\n mcpOk: mcpOk,\n mcpPort: \"9777\",\n providerStatus: providerStatus\n )\n\n await MainActor.run {\n self.results = r\n self.isRunningDiagnostic = false\n }\n }\n }\n\n private func fetchDaemonUptime() async throws -> String {\n let resp: HealthResponse = try await DaemonClient.shared.sendRequest(\"/health\")\n return resp.timestamp\n }\n}\n\n// MARK: - Subviews\n\nstruct DiagnosticGroup: View {\n let title: String\n let icon: String\n @ViewBuilder let content: () -> Content\n\n var body: some View {\n GroupBox {\n VStack(alignment: .leading, spacing: 8) {\n Label(title, systemImage: icon)\n .font(.headline)\n content()\n }\n .padding(4)\n }\n }\n}\n\nstruct DiagnosticRow: View {\n let label: String\n let value: String\n let ok: Bool\n\n var body: some View {\n HStack {\n Image(systemName: ok ? \"checkmark.circle.fill\" : \"xmark.circle.fill\")\n .foregroundColor(ok ? .green : .red)\n Text(label)\n .foregroundColor(.secondary)\n Spacer()\n Text(value)\n .foregroundColor(ok ? .primary : .red)\n }\n .font(.callout)\n }\n}\n\nstruct PermissionRow: View {\n let label: String\n let identifier: String\n\n var body: some View {\n HStack {\n Image(systemName: \"circle\")\n .foregroundColor(.orange)\n Text(label)\n .foregroundColor(.secondary)\n Spacer()\n Button(\"Open Settings\") {\n if let url = URL(string: identifier) {\n NSWorkspace.shared.open(url)\n }\n }\n .buttonStyle(.link)\n }\n .font(.callout)\n }\n}\n"}}}}