SPB Git

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%
6.2 KB · 151 lines swift
Raw Blame History
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