import AppKit import Foundation import SwiftUI private let workspaceRoot = URL(fileURLWithPath: "/Users/david/Developer/fidelity-ai-workspace", isDirectory: true) private let defaultProfile = "fidelity" @main struct AIWorkspaceApp: App { @StateObject private var model = ServiceStatusModel(profile: defaultProfile) var body: some Scene { MenuBarExtra { ServiceMenuView(model: model) .task { await model.refresh() } } label: { Label("AI Workspace", systemImage: model.menuBarSymbol) } .menuBarExtraStyle(.menu) } } @MainActor final class ServiceStatusModel: ObservableObject { @Published private(set) var report: StatusReport? @Published private(set) var lastError: String? @Published private(set) var isRefreshing = false let profile: String init(profile: String) { self.profile = profile } var menuBarSymbol: String { guard let report else { return "circle.dashed" } if report.services.contains(where: { $0.status == "unhealthy" || ($0.enabled && $0.status == "stopped") }) { return "exclamationmark.triangle" } if report.services.contains(where: { $0.status == "running" }) { return "checkmark.circle" } return "circle" } func refresh() async { isRefreshing = true defer { isRefreshing = false } do { let data = try await ServiceManager.run(["status", "--profile", profile, "--json"]) report = try JSONDecoder().decode(StatusReport.self, from: data) lastError = nil } catch { lastError = String(describing: error) } } func startProfile() { runAction(["start", "--profile", profile]) } func stopProfile() { runAction(["stop", "--profile", profile]) } func restartMCP() { runAction(["restart", "aiw-context-mcp", "--profile", profile]) } func runDoctor() { runAction(["doctor", "--profile", profile]) } func copyDoctorJSON() { Task { do { let data = try await ServiceManager.run(["doctor", "--profile", profile, "--json"]) copyToPasteboard(String(data: data, encoding: .utf8) ?? "") } catch { lastError = String(describing: error) } } } func copyPhotoInboxURL() { copyToPasteboard("http://127.0.0.1:8787/upload") } func openMCPHealth() { if let url = URL(string: "http://127.0.0.1:8765/health") { NSWorkspace.shared.open(url) } } func openLogsFolder() { NSWorkspace.shared.open(workspaceRoot.appendingPathComponent(".aiw/runtime/logs", isDirectory: true)) } func openProjectKnowledge() { NSWorkspace.shared.open(workspaceRoot.appendingPathComponent("project-knowledge", isDirectory: true)) } func openMattermost() { runAction(["start", "mattermost-desktop", "--profile", profile]) } private func copyToPasteboard(_ value: String) { NSPasteboard.general.clearContents() NSPasteboard.general.setString(value, forType: .string) } private func runAction(_ arguments: [String]) { Task { do { _ = try await ServiceManager.run(arguments) await refresh() } catch { lastError = String(describing: error) } } } } struct ServiceMenuView: View { @ObservedObject var model: ServiceStatusModel var body: some View { VStack(alignment: .leading) { Text("AI Workspace") .font(.headline) Text("Profile: \(model.profile)") .font(.caption) .foregroundStyle(.secondary) Divider() if let report = model.report { ForEach(report.services) { service in ServiceRow(service: service) } } else if let error = model.lastError { Label("Status unavailable", systemImage: "exclamationmark.triangle") Text(error) .font(.caption2) .foregroundStyle(.secondary) .lineLimit(3) } else { Label("Loading status...", systemImage: "hourglass") } Divider() Button("Refresh") { Task { await model.refresh() } } .keyboardShortcut("r") Button("Start Fidelity") { model.startProfile() } Button("Stop Fidelity") { model.stopProfile() } Button("Restart Context MCP") { model.restartMCP() } Button("Open Mattermost") { model.openMattermost() } Divider() Button("Run Doctor") { model.runDoctor() } Button("Copy Doctor JSON") { model.copyDoctorJSON() } Button("Copy Photo Inbox URL") { model.copyPhotoInboxURL() } Button("Open MCP Health") { model.openMCPHealth() } Button("Open Logs Folder") { model.openLogsFolder() } Button("Open Project Knowledge") { model.openProjectKnowledge() } Divider() Button("Quit") { NSApplication.shared.terminate(nil) } .keyboardShortcut("q") } } } struct ServiceRow: View { let service: ServiceStatus var body: some View { HStack(spacing: 10) { Image(systemName: symbol) .foregroundStyle(color) .frame(width: 16, alignment: .center) Text(service.displayName) .lineLimit(1) .frame(width: 170, alignment: .leading) Spacer() Text(service.compactStatus) .foregroundStyle(color) .font(.caption) .monospacedDigit() .frame(width: 86, alignment: .trailing) } .frame(minWidth: 300, alignment: .leading) .help(service.health.detail) } private var symbol: String { switch service.status { case "running": "checkmark.circle" case "launcher": "arrow.up.forward.app" case "externally running": "link.circle" case "unhealthy": "exclamationmark.triangle" case "disabled": "minus.circle" default: "xmark.circle" } } private var color: Color { switch service.status { case "running", "launcher", "externally running": .green case "unhealthy": .orange case "disabled": .secondary default: .red } } } enum ServiceManager { static func run(_ arguments: [String]) async throws -> Data { try await Task.detached(priority: .userInitiated) { let process = Process() process.currentDirectoryURL = workspaceRoot process.executableURL = URL(fileURLWithPath: "/usr/bin/env") process.arguments = ["python3", "scripts/aiw/services.py"] + arguments let output = Pipe() let error = Pipe() process.standardOutput = output process.standardError = error try process.run() process.waitUntilExit() let data = output.fileHandleForReading.readDataToEndOfFile() let errorData = error.fileHandleForReading.readDataToEndOfFile() guard process.terminationStatus == 0 else { let message = String(data: errorData.isEmpty ? data : errorData, encoding: .utf8) ?? "service command failed" throw ServiceManagerError.commandFailed(message.trimmingCharacters(in: .whitespacesAndNewlines)) } return data }.value } } enum ServiceManagerError: LocalizedError { case commandFailed(String) var errorDescription: String? { switch self { case .commandFailed(let message): message } } } struct StatusReport: Decodable { let profile: String let workspace: String let runtime: String let services: [ServiceStatus] } struct ServiceStatus: Decodable, Identifiable { var id: String { name } let name: String let enabled: Bool let kind: String let status: String let pid: Int? let command: [String] let health: Health let state: [String: JSONValue] var displayName: String { switch name { case "aiw-context-mcp": "Context MCP" case "mattermost-proxy": "Mattermost Proxy" case "mattermost-desktop": "Mattermost Desktop" case "photo-inbox": "Photo Inbox" default: name } } var compactStatus: String { switch status { case "externally running": "external" default: status } } } struct Health: Decodable { let ok: Bool? let detail: String } enum JSONValue: Decodable { case string(String) case number(Double) case bool(Bool) case object([String: JSONValue]) case array([JSONValue]) case null init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() if container.decodeNil() { self = .null } else if let value = try? container.decode(Bool.self) { self = .bool(value) } else if let value = try? container.decode(Double.self) { self = .number(value) } else if let value = try? container.decode(String.self) { self = .string(value) } else if let value = try? container.decode([String: JSONValue].self) { self = .object(value) } else { self = .array(try container.decode([JSONValue].self)) } } }