// // PersistenceService.swift // Zyquo Agent // // Author: Simon-Pierre Boucher // Mail: contact@spboucher.ai // // JSON persistence in ~/Library/Application Support/ZyquoAgent/: // vault.zq encrypted API-key vault (SecureKeyStore) // Workspaces/ per-task working directories (WorkspaceManager) // settings.json, personas.json, … generic documents // // Ported from Zyquo Cloud (root folder renamed ZyquoCloud → ZyquoAgent; // Cloud's per-conversation persistence is superseded by Agent's Task // persistence, which arrives with the Agent layer). // import Foundation struct PersistenceService { static let shared = PersistenceService() let rootDirectory: URL /// Per-task working directories root (Phase 2 Workspace layer). var workspacesDirectory: URL { rootDirectory.appendingPathComponent("Workspaces") } private let encoder: JSONEncoder private let decoder: JSONDecoder init(rootDirectory: URL? = nil) { let base = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0] self.rootDirectory = rootDirectory ?? base.appendingPathComponent("ZyquoAgent") encoder = JSONEncoder() encoder.outputFormatting = [.prettyPrinted, .sortedKeys] encoder.dateEncodingStrategy = .iso8601 decoder = JSONDecoder() decoder.dateDecodingStrategy = .iso8601 try? FileManager.default.createDirectory(at: workspacesDirectory, withIntermediateDirectories: true) } // MARK: - Generic documents (personas, templates, settings…) func load(_ type: T.Type, from fileName: String) -> T? { let url = rootDirectory.appendingPathComponent(fileName) guard let data = try? Data(contentsOf: url) else { return nil } return try? decoder.decode(type, from: data) } func save(_ value: T, to fileName: String) { let url = rootDirectory.appendingPathComponent(fileName) guard let data = try? encoder.encode(value) else { return } try? data.write(to: url, options: .atomic) } }