Files
oai-swift/oAI/Services/AgentSkillFilesService.swift
T
rune c3abc5a748 Update remaining oai.pm references to confab.no
Covers the per-file license-header comment (~80 Swift files) plus
the contact/website links in README.md, PRIVACY.md, and SECURITY.md.
2026-08-03 08:44:35 +02:00

88 lines
3.0 KiB
Swift

//
// AgentSkillFilesService.swift
// Confab
//
// Manages per-skill file directories in Application Support/oAI/skills/<uuid>/
//
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
// Copyright (C) 2026 Rune Olsen
//
// This file is part of Confab.
//
// Confab is licensed under the PolyForm Noncommercial License 1.0.0.
// You may use, study, modify, and share it for any noncommercial
// purpose. Commercial use — including selling Confab or any part of
// it, standalone or bundled into another product or service —
// requires a separate commercial license from the copyright holder.
//
// See the LICENSE file or
// <https://polyformproject.org/licenses/noncommercial/1.0.0> for
// the full license text. For commercial licensing, contact Rune
// Olsen via <https://confab.no>.
import Foundation
import UniformTypeIdentifiers
final class AgentSkillFilesService {
static let shared = AgentSkillFilesService()
private let baseDirectory: URL = {
let appSupport = FileManager.default.urls(for: .applicationSupportDirectory,
in: .userDomainMask).first!
return appSupport.appendingPathComponent("oAI/skills", isDirectory: true)
}()
func skillDirectory(for id: UUID) -> URL {
baseDirectory.appendingPathComponent(id.uuidString, isDirectory: true)
}
func ensureDirectory(for id: UUID) {
try? FileManager.default.createDirectory(
at: skillDirectory(for: id), withIntermediateDirectories: true)
}
func listFiles(for id: UUID) -> [URL] {
guard let contents = try? FileManager.default.contentsOfDirectory(
at: skillDirectory(for: id),
includingPropertiesForKeys: [.fileSizeKey],
options: .skipsHiddenFiles)
else { return [] }
return contents.sorted { $0.lastPathComponent < $1.lastPathComponent }
}
func addFile(from sourceURL: URL, to id: UUID) throws {
ensureDirectory(for: id)
let dest = skillDirectory(for: id).appendingPathComponent(sourceURL.lastPathComponent)
if FileManager.default.fileExists(atPath: dest.path) {
try FileManager.default.removeItem(at: dest)
}
try FileManager.default.copyItem(at: sourceURL, to: dest)
}
func deleteFile(at url: URL) {
try? FileManager.default.removeItem(at: url)
}
func deleteAll(for id: UUID) {
try? FileManager.default.removeItem(at: skillDirectory(for: id))
}
func hasFiles(for id: UUID) -> Bool {
!listFiles(for: id).isEmpty
}
/// Returns (filename, content) for all readable text files
func readTextFiles(for id: UUID) -> [(name: String, content: String)] {
listFiles(for: id).compactMap { url in
guard let content = try? String(contentsOf: url, encoding: .utf8) else { return nil }
return (url.lastPathComponent, content)
}
}
/// Returns file size in bytes, or nil if unavailable
func fileSize(at url: URL) -> Int? {
(try? url.resourceValues(forKeys: [.fileSizeKey]))?.fileSize
}
}