// // AdvancedSettingsTab.swift // Zyquo Agent // // Author: Simon-Pierre Boucher // Mail: contact@spboucher.ai // // Settings › Advanced — reveal the data/workspaces folders, export every // task's append-only audit log into one file, import/export tasks as JSON, // and the menu bar extra toggle. // import SwiftUI import UniformTypeIdentifiers struct AdvancedSettingsTab: View { @EnvironmentObject private var store: TaskStore @AppStorage("menuBarExtraEnabled") private var menuBarExtraEnabled = true @State private var statusMessage: String? var body: some View { Form { Section("Menu bar") { Toggle("Show Zyquo Agent in the menu bar", isOn: $menuBarExtraEnabled) Text("The menu bar extra shows running-task status and offers New Task, Quick Task (⌥Space), and quick access to running tasks.") .font(ZyquoFont.caption) .foregroundStyle(ZyquoColor.textTertiary) } Section("Data") { LabeledContent("Data folder") { Button("Reveal in Finder") { NSWorkspace.shared.activateFileViewerSelecting([ PersistenceService.shared.rootDirectory ]) } .controlSize(.small) } LabeledContent("Workspaces folder") { Button("Reveal in Finder") { NSWorkspace.shared.activateFileViewerSelecting([ PersistenceService.shared.workspacesDirectory ]) } .controlSize(.small) } Text("Tasks, settings, personas, templates, and the encrypted key vault live in ~/Library/Application Support/ZyquoAgent/. The vault (vault.zq) is bound to this Mac and can't be decrypted elsewhere.") .font(ZyquoFont.caption) .foregroundStyle(ZyquoColor.textTertiary) } Section("Audit logs") { LabeledContent("All executed actions, across every task") { Button("Export…") { exportAuditLogs() } .controlSize(.small) } Text("Concatenates each task workspace's append-only audit.jsonl (one JSON entry per executed action) into a single export.") .font(ZyquoFont.caption) .foregroundStyle(ZyquoColor.textTertiary) } Section("Tasks") { HStack { Button("Export All Tasks…") { exportTasks() } .controlSize(.small) Button("Import Tasks…") { importTasks() } .controlSize(.small) } Text("Exports task records (titles, prompts, run histories, settings) as JSON. Workspaces are folders on disk and are not embedded.") .font(ZyquoFont.caption) .foregroundStyle(ZyquoColor.textTertiary) } if let statusMessage { Text(statusMessage) .font(ZyquoFont.caption) .foregroundStyle(ZyquoColor.success) } } .formStyle(.grouped) } // MARK: - Audit export private func exportAuditLogs() { let panel = NSSavePanel() panel.allowedContentTypes = [UTType(filenameExtension: "jsonl") ?? .plainText] panel.nameFieldStringValue = "zyquo-agent-audit.jsonl" let tasks = store.tasks panel.begin { response in guard response == .OK, let url = panel.url else { return } var lines: [String] = [] var covered = 0 for task in tasks { guard let workspace = task.workspaceURL else { continue } let auditURL = workspace .appendingPathComponent(".zyquo") .appendingPathComponent("audit.jsonl") guard let content = try? String(contentsOf: auditURL, encoding: .utf8), !content.isEmpty else { continue } covered += 1 lines.append(contentsOf: content.split(separator: "\n").map(String.init)) } try? (lines.joined(separator: "\n") + "\n").write(to: url, atomically: true, encoding: .utf8) Task { @MainActor in statusMessage = "Exported \(lines.count) audit entr\(lines.count == 1 ? "y" : "ies") from \(covered) task\(covered == 1 ? "" : "s")." } } } // MARK: - Task import/export private func exportTasks() { let panel = NSSavePanel() panel.allowedContentTypes = [.json] panel.nameFieldStringValue = "zyquo-agent-tasks.json" let tasks = store.tasks panel.begin { response in guard response == .OK, let url = panel.url else { return } let encoder = JSONEncoder() encoder.outputFormatting = [.prettyPrinted, .sortedKeys] encoder.dateEncodingStrategy = .iso8601 guard let data = try? encoder.encode(tasks) else { return } try? data.write(to: url, options: .atomic) Task { @MainActor in statusMessage = "Exported \(tasks.count) task\(tasks.count == 1 ? "" : "s")." } } } private func importTasks() { let panel = NSOpenPanel() panel.allowedContentTypes = [.json] panel.allowsMultipleSelection = false panel.begin { response in guard response == .OK, let url = panel.url, let data = try? Data(contentsOf: url) else { return } let decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601 guard let imported = try? decoder.decode([AgentTask].self, from: data) else { Task { @MainActor in statusMessage = "Import failed — not a Zyquo Agent task export." } return } Task { @MainActor in let added = store.importTasks(imported) statusMessage = "Imported \(added) task\(added == 1 ? "" : "s") (\(imported.count - added) already present)." } } } }