spb/zyquo-agent Public MIT
The autonomous agent that actually operates your Mac — plans, runs real commands, verifies its own work.
Swift 94.7%
Shell 4.1%
Python 0.7%
Makefile 0.5%
1//2// AdvancedSettingsTab.swift3// Zyquo Agent4//5// Author: Simon-Pierre Boucher6// Mail: contact@spboucher.ai7//8// Settings › Advanced — reveal the data/workspaces folders, export every9// task's append-only audit log into one file, import/export tasks as JSON,10// and the menu bar extra toggle.11//1213import SwiftUI14import UniformTypeIdentifiers1516struct AdvancedSettingsTab: View {17 @EnvironmentObject private var store: TaskStore18 @AppStorage("menuBarExtraEnabled") private var menuBarExtraEnabled = true19 @State private var statusMessage: String?2021 var body: some View {22 Form {23 Section("Menu bar") {24 Toggle("Show Zyquo Agent in the menu bar", isOn: $menuBarExtraEnabled)25 Text("The menu bar extra shows running-task status and offers New Task, Quick Task (⌥Space), and quick access to running tasks.")26 .font(ZyquoFont.caption)27 .foregroundStyle(ZyquoColor.textTertiary)28 }2930 Section("Data") {31 LabeledContent("Data folder") {32 Button("Reveal in Finder") {33 NSWorkspace.shared.activateFileViewerSelecting([34 PersistenceService.shared.rootDirectory35 ])36 }37 .controlSize(.small)38 }39 LabeledContent("Workspaces folder") {40 Button("Reveal in Finder") {41 NSWorkspace.shared.activateFileViewerSelecting([42 PersistenceService.shared.workspacesDirectory43 ])44 }45 .controlSize(.small)46 }47 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.")48 .font(ZyquoFont.caption)49 .foregroundStyle(ZyquoColor.textTertiary)50 }5152 Section("Audit logs") {53 LabeledContent("All executed actions, across every task") {54 Button("Export…") { exportAuditLogs() }55 .controlSize(.small)56 }57 Text("Concatenates each task workspace's append-only audit.jsonl (one JSON entry per executed action) into a single export.")58 .font(ZyquoFont.caption)59 .foregroundStyle(ZyquoColor.textTertiary)60 }6162 Section("Tasks") {63 HStack {64 Button("Export All Tasks…") { exportTasks() }65 .controlSize(.small)66 Button("Import Tasks…") { importTasks() }67 .controlSize(.small)68 }69 Text("Exports task records (titles, prompts, run histories, settings) as JSON. Workspaces are folders on disk and are not embedded.")70 .font(ZyquoFont.caption)71 .foregroundStyle(ZyquoColor.textTertiary)72 }7374 if let statusMessage {75 Text(statusMessage)76 .font(ZyquoFont.caption)77 .foregroundStyle(ZyquoColor.success)78 }79 }80 .formStyle(.grouped)81 }8283 // MARK: - Audit export8485 private func exportAuditLogs() {86 let panel = NSSavePanel()87 panel.allowedContentTypes = [UTType(filenameExtension: "jsonl") ?? .plainText]88 panel.nameFieldStringValue = "zyquo-agent-audit.jsonl"89 let tasks = store.tasks90 panel.begin { response in91 guard response == .OK, let url = panel.url else { return }92 var lines: [String] = []93 var covered = 094 for task in tasks {95 guard let workspace = task.workspaceURL else { continue }96 let auditURL = workspace97 .appendingPathComponent(".zyquo")98 .appendingPathComponent("audit.jsonl")99 guard let content = try? String(contentsOf: auditURL, encoding: .utf8),100 !content.isEmpty else { continue }101 covered += 1102 lines.append(contentsOf: content.split(separator: "\n").map(String.init))103 }104 try? (lines.joined(separator: "\n") + "\n").write(to: url, atomically: true, encoding: .utf8)105 Task { @MainActor in106 statusMessage = "Exported \(lines.count) audit entr\(lines.count == 1 ? "y" : "ies") from \(covered) task\(covered == 1 ? "" : "s")."107 }108 }109 }110111 // MARK: - Task import/export112113 private func exportTasks() {114 let panel = NSSavePanel()115 panel.allowedContentTypes = [.json]116 panel.nameFieldStringValue = "zyquo-agent-tasks.json"117 let tasks = store.tasks118 panel.begin { response in119 guard response == .OK, let url = panel.url else { return }120 let encoder = JSONEncoder()121 encoder.outputFormatting = [.prettyPrinted, .sortedKeys]122 encoder.dateEncodingStrategy = .iso8601123 guard let data = try? encoder.encode(tasks) else { return }124 try? data.write(to: url, options: .atomic)125 Task { @MainActor in126 statusMessage = "Exported \(tasks.count) task\(tasks.count == 1 ? "" : "s")."127 }128 }129 }130131 private func importTasks() {132 let panel = NSOpenPanel()133 panel.allowedContentTypes = [.json]134 panel.allowsMultipleSelection = false135 panel.begin { response in136 guard response == .OK, let url = panel.url,137 let data = try? Data(contentsOf: url) else { return }138 let decoder = JSONDecoder()139 decoder.dateDecodingStrategy = .iso8601140 guard let imported = try? decoder.decode([AgentTask].self, from: data) else {141 Task { @MainActor in statusMessage = "Import failed — not a Zyquo Agent task export." }142 return143 }144 Task { @MainActor in145 let added = store.importTasks(imported)146 statusMessage = "Imported \(added) task\(added == 1 ? "" : "s") (\(imported.count - added) already present)."147 }148 }149 }150}151