{
  "format" : 1,
  "manifest" : {
    "capabilities" : [
      {
        "clipboard" : {

        }
      }
    ],
    "homepage" : "https://github.com/navbytes/stackbar-plugins/tree/main/Productivity/Clipboard",
    "hotkey" : "cmd+shift+v",
    "id" : "io.navbytes.stackbar.clipboard",
    "minimumStackbar" : "0.11.0",
    "name" : "Clipboard History",
    "summary" : "Everything you copy, searchable and one keystroke from pasting back.",
    "surfaces" : [
      "menuBar",
      "panel",
      "window",
      "settings"
    ],
    "triggers" : [
      {
        "launch" : {

        }
      },
      {
        "clipboard" : {

        }
      }
    ],
    "version" : "1.1.1"
  },
  "publisher" : {
    "name" : "navbytes",
    "publicKey" : "4dpLtXEpFXdi+6cviY/KdYuUulJPbEmCxxIK0EEN2/w="
  },
  "signature" : "LwbnlYJRLNgHBEca7OT42xiIlCHmCTbe6HqU1FDf/zf9GkpYIL+ce/lOqJKld2g3yWQIMCkZ8Ysxso0p4nslCw==",
  "source" : "import StackbarKit\nimport Foundation\n\n/// Clipboard history: everything you copy, searchable, pinnable and one\n/// keystroke from being pasted back.\n///\n/// ⌘⇧V opens it from anywhere. Type to filter, ↑↓ and Return (or ⌘1...⌘9)\n/// to paste into the app you were in, ⌥Return to paste as plain text, ⌘P to\n/// pin, ⌥⌫ to delete. Formatted text keeps its formatting. Pinned items stay\n/// at the top and are never trimmed. Passwords are skipped: the host never hands out items an app\n/// marks as concealed or transient, and items from password managers (or any\n/// app you add) are ignored. Images are kept by the host and pasted back.\n@main\nfinal class Clipboard: StackbarPlugin {\n    static let manifest = Manifest(\n        id: \"io.navbytes.stackbar.clipboard\",\n        name: \"Clipboard History\",\n        version: \"1.1.1\",\n        summary: \"Everything you copy, searchable and one keystroke from pasting back.\",\n        surfaces: [.menuBar, .panel, .window, .settings],\n        triggers: [.launch, .clipboard],\n        capabilities: [.clipboard],\n        hotkey: \"cmd+shift+v\",\n        minimumStackbar: \"0.11.0\",\n        homepage: \"https://github.com/navbytes/stackbar-plugins/tree/main/Productivity/Clipboard\"\n    )\n\n    struct Entry: Codable, Equatable {\n        var id: String\n        var kind: ClipboardItem.Kind\n        var text: String?\n        var files: [String] = []\n        var imageID: String?\n        var imageSize: [Int]?\n        /// The host's copy of the original, formatting included.\n        var contentID: String?\n        var appID: String?\n        var appName: String?\n        var firstCopied: Date\n        var lastCopied: Date\n        var count: Int = 1\n        var pinned = false\n\n        /// What makes two copies the same item.\n        var fingerprint: String {\n            switch kind {\n            case .image: return \"image:\" + (imageID ?? id)\n            case .files: return \"files:\" + files.joined(separator: \"\\n\")\n            case .text, .url: return \"text:\" + (text ?? \"\")\n            }\n        }\n\n        var content: ClipboardContent {\n            switch kind {\n            case .image: return .image(id: imageID ?? \"\")\n            case .files: return .files(files)\n            case .text, .url: return contentID.map { .stored(id: $0) } ?? .text(text ?? \"\")\n            }\n        }\n\n        var plainText: String? {\n            switch kind {\n            case .text, .url, .files: return text\n            case .image: return nil\n            }\n        }\n\n        var title: String {\n            switch kind {\n            case .image:\n                let size = imageSize.map { \"\\($0.first ?? 0)×\\($0.last ?? 0)\" } ?? \"\"\n                return \"Image \\(size)\".trimmingCharacters(in: .whitespaces)\n            case .files:\n                let names = files.map { ($0 as NSString).lastPathComponent }\n                return names.count == 1 ? names[0] : \"\\(names.count) files: \" + names.prefix(3).joined(separator: \", \")\n            case .text, .url:\n                let line = (text ?? \"\").split(whereSeparator: \\.isNewline).first.map(String.init) ?? \"\"\n                let trimmed = line.trimmingCharacters(in: .whitespaces)\n                let shown = trimmed.isEmpty ? \"(whitespace)\" : trimmed\n                return shown.count > 90 ? String(shown.prefix(89)) + \"…\" : shown\n            }\n        }\n\n        var symbol: String {\n            switch kind {\n            case .text: return contentID == nil ? \"doc.plaintext\" : \"doc.richtext\"\n            case .url: return \"link\"\n            case .files: return \"doc.on.doc\"\n            case .image: return \"photo\"\n            }\n        }\n\n        var bytes: Int { (text?.utf8.count ?? 0) + files.reduce(0) { $0 + $1.utf8.count } + 200 }\n    }\n\n    enum Order: String, CaseIterable, Codable {\n        case recent = \"Last copied\"\n        case first = \"First copied\"\n        case frequent = \"Most used\"\n    }\n\n    /// Password managers whose copies are never recorded, on top of the\n    /// concealed-type marker most of them already set.\n    static let defaultIgnored = [\n        \"com.1password.1password\", \"com.agilebits.onepassword7\", \"com.bitwarden.desktop\",\n        \"com.apple.keychainaccess\", \"com.apple.Passwords\", \"com.dashlane.dashlanephonefinal\", \"org.keepassxc.keepassxc\",\n    ]\n\n    /// The plugin's 1 MB of storage, less room for settings.\n    static let storageBudget = 850_000\n    static let maxEntryCharacters = 100_000\n\n    @Stored(\"limit\") var limit = 200.0\n    @Stored(\"order\") var order = Order.recent\n    @Stored(\"pasteDirectly\") var pasteDirectly = true\n    @Stored(\"keepImages\") var keepImages = true\n    @Stored(\"ignoredApps\") var ignoredApps = Clipboard.defaultIgnored\n    @Stored(\"paused\") var paused = false\n    @Stored(\"plainByDefault\") var plainByDefault = false\n    /// Regular expressions; a copy whose text matches one is not recorded\n    /// (tokens, one-time codes).\n    @Stored(\"ignoredPatterns\") var ignoredPatterns: [String] = []\n    /// Copies recorded per day (yyyy-MM-dd), for the last 30 days.\n    @Stored(\"copiesByDay\") var copiesByDay: [String: Int] = [:]\n\n    /// The history, read from storage once and written back when it changes\n    /// (a `@Stored` array would be decoded on every render).\n    private var cached: [Entry]?\n    private var lastChange: Int?\n    private var ignoreNext = false\n    /// When this plugin last put something on the clipboard, so its own\n    /// writes don't count as new copies from whichever app is frontmost.\n    private var lastWrite: Date?\n    var lastError: String?\n\n    var entries: [Entry] {\n        get {\n            if let cached { return cached }\n            let loaded = Stackbar.storage.value(\"history\", as: [Entry].self) ?? []\n            cached = loaded\n            return loaded\n        }\n        set {\n            cached = newValue\n            Stackbar.storage.set(\"history\", newValue)\n        }\n    }\n\n    // MARK: Recording\n\n    func refresh() async throws {\n        guard let item = try await Stackbar.clipboard() else { return }\n        // The first read after a launch records what is on the clipboard\n        // now; if it is already in the history it just moves to the top.\n        defer { lastChange = item.changeCount }\n        guard item.changeCount != lastChange else { return }\n        if ignoreNext {\n            ignoreNext = false\n            return\n        }\n        guard !paused, !item.concealed else { return }\n        if let app = item.sourceAppID, isIgnored(app: app, name: item.sourceAppName) { return }\n        if item.kind == .image && !keepImages { return }\n        if let text = item.text, matchesIgnoredPattern(text) { return }\n        record(item)\n    }\n\n    func record(_ item: ClipboardItem) {\n        var entry = Entry(\n            id: UUID().uuidString, kind: item.kind, text: item.text.map { String($0.prefix(Self.maxEntryCharacters)) }, files: item.files,\n            imageID: item.imageID, imageSize: item.imageSize, contentID: item.contentID, appID: item.sourceAppID, appName: item.sourceAppName,\n            firstCopied: item.copiedAt, lastCopied: item.copiedAt\n        )\n        if entry.kind != .image && entry.kind != .files && (entry.text ?? \"\").trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { return }\n        // Our own paste coming back says nothing new about where it came from.\n        let ownWrite = item.sourceAppID == \"io.navbytes.stackbar\" || (lastWrite.map { item.copiedAt.timeIntervalSince($0) < 3 } ?? false)\n        var history = entries\n        if let index = history.firstIndex(where: { $0.fingerprint == entry.fingerprint }) {\n            var existing = history.remove(at: index)\n            existing.lastCopied = item.copiedAt\n            existing.contentID = item.contentID ?? existing.contentID\n            if !ownWrite {\n                existing.count += 1\n                existing.appID = item.sourceAppID ?? existing.appID\n                existing.appName = item.sourceAppName ?? existing.appName\n            }\n            entry = existing\n        }\n        history.insert(entry, at: 0)\n        entries = trimmed(history)\n        if !ownWrite { countCopy(on: item.copiedAt) }\n    }\n\n    func countCopy(on date: Date) {\n        var days = copiesByDay\n        days[Self.day(date), default: 0] += 1\n        let oldest = Self.day(date.addingTimeInterval(-29 * 86400))\n        copiesByDay = days.filter { $0.key >= oldest }\n    }\n\n    static func day(_ date: Date) -> String {\n        let parts = Calendar.current.dateComponents([.year, .month, .day], from: date)\n        return String(format: \"%04d-%02d-%02d\", parts.year ?? 0, parts.month ?? 0, parts.day ?? 0)\n    }\n\n    /// Drops the oldest unpinned items past the limit or the storage budget.\n    func trimmed(_ history: [Entry]) -> [Entry] {\n        var kept: [Entry] = []\n        var unpinned = 0\n        var bytes = 0\n        for entry in history {\n            bytes += entry.bytes\n            if entry.pinned {\n                kept.append(entry)\n                continue\n            }\n            guard unpinned < Int(limit), bytes <= Self.storageBudget else { continue }\n            unpinned += 1\n            kept.append(entry)\n        }\n        return kept\n    }\n\n    func matchesIgnoredPattern(_ text: String) -> Bool {\n        ignoredPatterns.contains { pattern in\n            (try? Regex(pattern)).map { text.contains($0) } ?? false\n        }\n    }\n\n    func isIgnored(app id: String, name: String?) -> Bool {\n        ignoredApps.contains { ignored in\n            ignored.caseInsensitiveCompare(id) == .orderedSame || (name.map { ignored.caseInsensitiveCompare($0) == .orderedSame } ?? false)\n        }\n    }\n\n    // MARK: Acting on an entry\n\n    func use(_ entry: Entry, paste: Bool, plain: Bool = false) async {\n        let content: ClipboardContent = plain || plainByDefault ? entry.plainText.map { .text($0) } ?? entry.content : entry.content\n        lastWrite = Date()\n        do {\n            if paste {\n                try await Stackbar.paste(content)\n            } else if case .text(let text) = content {\n                Stackbar.copy(text)\n            } else {\n                try await Stackbar.setClipboard(content)\n            }\n            lastError = nil\n        } catch {\n            lastError = \"\\(error)\"\n        }\n    }\n\n    func update(_ id: String, _ change: (inout Entry) -> Void) {\n        var history = entries\n        guard let index = history.firstIndex(where: { $0.id == id }) else { return }\n        change(&history[index])\n        entries = history\n    }\n\n    func delete(_ id: String) {\n        entries = entries.filter { $0.id != id }\n    }\n\n    func clear(keepPinned: Bool = true) {\n        entries = keepPinned ? entries.filter(\\.pinned) : []\n    }\n\n    var sorted: [Entry] {\n        let history = entries\n        switch order {\n        case .recent: return history.sorted { $0.lastCopied > $1.lastCopied }\n        case .first: return history.sorted { $0.firstCopied > $1.firstCopied }\n        case .frequent: return history.sorted { ($0.count, $0.lastCopied) > ($1.count, $1.lastCopied) }\n        }\n    }\n\n    // MARK: Surfaces\n\n    var menuBar: ViewContent {\n        Label(\"\", symbol: paused ? \"pause.circle\" : \"list.clipboard\").role(paused ? .warning : .primary)\n    }\n\n    var panel: ViewContent {\n        let all = sorted\n        let pinned = all.filter(\\.pinned)\n        let history = all.filter { !$0.pinned }\n        HStack {\n            Text(\"Clipboard\").style(.headline)\n            Badge(\"\\(all.count)\")\n            if paused { Badge(\"Paused\", role: .warning) }\n        }\n        if let lastError {\n            Text(lastError).style(.caption).role(.warning)\n        }\n        if all.isEmpty {\n            ContentUnavailableView(\"Nothing copied yet\", symbol: \"list.clipboard\", message: paused ? \"Recording is paused.\" : \"Copy something in any app and it shows up here.\")\n        } else {\n            List(searchable: true) {\n                if !pinned.isEmpty {\n                    Section(\"Pinned\") {\n                        ForEach(Array(pinned.enumerated())) { index, entry in\n                            row(entry)\n                        }\n                    }\n                }\n                if pinned.isEmpty {\n                    ForEach(history) { entry in row(entry) }\n                } else {\n                    Section(\"History\") {\n                        ForEach(history) { entry in row(entry) }\n                    }\n                }\n            }\n        }\n        HStack {\n            Button(paused ? \"Resume\" : \"Pause\", symbol: paused ? \"play.fill\" : \"pause.fill\") { self.paused.toggle() }\n            Button(\"Ignore next copy\", symbol: \"eye.slash\") { self.ignoreNext = true }\n            Button(\"Clear history\", symbol: \"trash\", role: .critical) { self.clear() }\n        }\n    }\n\n    /// The window has room for the history and what it says about you.\n    var window: ViewContent {\n        panel\n        insights\n    }\n\n    @ViewBuilder var insights: ViewContent {\n        let all = entries\n        if !all.isEmpty {\n            Section(\"Insights\") {\n                let calendar = Calendar.current\n                let dates = (0..<14).reversed().map { Date().addingTimeInterval(Double(-$0) * 86400) }\n                Chart(\"Copies, last 14 days\", values: dates.map { Double(copiesByDay[Self.day($0)] ?? 0) }, kind: .bar, labels: dates.map { String(calendar.component(.day, from: $0)) }, highlight: 13)\n                let byApp = Dictionary(grouping: all, by: { $0.appName ?? \"Unknown\" }).mapValues { $0.reduce(0) { $0 + $1.count } }.sorted { $0.value > $1.value }\n                let top = byApp.prefix(5)\n                let rest = byApp.dropFirst(5).reduce(0) { $0 + $1.value }\n                Chart(\"Where your copies come from\", values: top.map { Double($0.value) } + (rest > 0 ? [Double(rest)] : []), kind: .donut, labels: top.map(\\.key) + (rest > 0 ? [\"Other\"] : []))\n                let kinds: [(String, Int)] = [(\"Text\", all.filter { $0.kind == .text }.count), (\"Links\", all.filter { $0.kind == .url }.count), (\"Images\", all.filter { $0.kind == .image }.count), (\"Files\", all.filter { $0.kind == .files }.count)].filter { $0.1 > 0 }\n                Chart(\"What you copy\", values: kinds.map { Double($0.1) }, kind: .donut, role: .success, labels: kinds.map(\\.0))\n            }\n        }\n    }\n\n    /// Stackbar's Settings window, so the panel stays about the history.\n    var settings: ViewContent {\n        Section(\"Pasting\") {\n            Toggle(\"Paste into the app on click\", isOn: pasteDirectly) { self.pasteDirectly = $0 }\n            Toggle(\"Always paste as plain text\", isOn: plainByDefault) { self.plainByDefault = $0 }\n        }\n        Section(\"History\") {\n            Picker(\"Order\", options: Order.allCases.map(\\.rawValue), selection: order.rawValue) { self.order = Order(rawValue: $0) ?? .recent }\n            Slider(\"Keep \\(Int(limit)) items\", value: limit, in: 25...1000, step: 25) { value in\n                self.limit = value\n                self.entries = self.trimmed(self.entries)\n            }\n            Toggle(\"Keep images\", isOn: keepImages) { self.keepImages = $0 }\n        }\n        Section(\"Ignored apps\") {\n            ignoredAppsContent\n        }\n        Section(\"Ignored text\") {\n            ignoredText\n        }\n        Section {\n            Button(\"Clear everything, including pinned\", symbol: \"trash.slash\", role: .critical) { self.clear(keepPinned: false) }\n        }\n    }\n\n    @ViewBuilder var ignoredAppsContent: ViewContent {\n        TextField(\"Ignore an app (bundle id or name)\") { app in\n            let app = app.trimmingCharacters(in: .whitespaces)\n            guard !app.isEmpty, !self.ignoredApps.contains(app) else { return }\n            self.ignoredApps.append(app)\n        }\n        if let recent = entries.first(where: { $0.appID != nil && $0.appID != \"io.navbytes.stackbar\" }), let app = recent.appID, !isIgnored(app: app, name: recent.appName) {\n            Button(\"Ignore \\(recent.appName ?? app)\", symbol: \"hand.raised\", key: \"ignore-recent\") { self.ignoredApps.append(app) }\n        }\n        ForEach(ignoredApps) { app in\n            Row(app, subtitle: \"Ignored\", symbol: \"hand.raised\", key: \"ignored-\\(app)\")\n                .action(\"Stop ignoring\") { self.ignoredApps.removeAll { $0 == app } }\n        }\n    }\n\n    @ViewBuilder var ignoredText: ViewContent {\n        TextField(\"Ignore copies matching a pattern (regex)\") { pattern in\n            let pattern = pattern.trimmingCharacters(in: .whitespaces)\n            guard !pattern.isEmpty, !self.ignoredPatterns.contains(pattern) else { return }\n            guard (try? Regex(pattern)) != nil else {\n                self.lastError = \"\\\"\\(pattern)\\\" isn't a valid regular expression\"\n                return\n            }\n            self.ignoredPatterns.append(pattern)\n            self.entries = self.entries.filter { $0.pinned || !self.matchesIgnoredPattern($0.text ?? \"\") }\n        }\n        ForEach(ignoredPatterns) { pattern in\n            Row(pattern, subtitle: \"Copies matching this aren't recorded\", symbol: \"line.3.horizontal.decrease\", key: \"pattern-\\(pattern)\")\n                .action(\"Remove\") { self.ignoredPatterns.removeAll { $0 == pattern } }\n        }\n    }\n\n    /// `paste` picks the primary action: the panel follows the setting, the\n    /// palette always pastes.\n    func row(_ entry: Entry) -> Row {\n        row(entry, paste: pasteDirectly)\n    }\n\n    func row(_ entry: Entry, paste pasteDirectly: Bool) -> Row {\n        let source = [entry.appName, Self.relative(entry.lastCopied), entry.count > 1 ? \"×\\(entry.count)\" : nil, entry.kind == .text ? \"\\(entry.text?.count ?? 0) chars\" : nil]\n            .compactMap { $0 }.joined(separator: \" · \")\n        var row = Row(entry.title, subtitle: source, symbol: entry.pinned ? \"pin.fill\" : entry.symbol, key: entry.id)\n        if let preview = preview(entry) { row = preview(row) }\n        row = row.action(pasteDirectly ? \"Paste\" : \"Copy\") { await self.use(entry, paste: self.pasteDirectly) }\n        if entry.plainText != nil, entry.contentID != nil || entry.kind == .files {\n            row = row.action(pasteDirectly ? \"Paste as plain text\" : \"Copy as plain text\", shortcut: \"option+return\") { await self.use(entry, paste: self.pasteDirectly, plain: true) }\n        }\n        row = row.action(pasteDirectly ? \"Copy\" : \"Paste\") { await self.use(entry, paste: !self.pasteDirectly) }\n            .action(entry.pinned ? \"Unpin\" : \"Pin\", shortcut: \"cmd+p\") { self.update(entry.id) { $0.pinned.toggle() } }\n        if entry.kind == .url, let url = entry.text {\n            row = row.action(\"Open link\") { Stackbar.open(url.trimmingCharacters(in: .whitespacesAndNewlines)) }\n        }\n        return row.action(\"Delete\", shortcut: \"option+delete\", role: .critical) { self.delete(entry.id) }\n    }\n\n    /// The selected row's preview: the text past its first line, the image,\n    /// or the files, with when and how often it was copied.\n    func preview(_ entry: Entry) -> ((Row) -> Row)? {\n        var caption = [\"First copied \\(Self.relative(entry.firstCopied))\"]\n        if entry.count > 1 { caption.append(\"copied \\(entry.count) times\") }\n        switch entry.kind {\n        case .image:\n            if let size = entry.imageSize, size.count == 2 { caption.insert(\"\\(size[0]) × \\(size[1])\", at: 0) }\n            return { $0.preview(imageID: entry.imageID, caption: caption.joined(separator: \" · \")) }\n        case .files:\n            return { $0.preview(text: entry.files.joined(separator: \"\\n\"), caption: caption.joined(separator: \" · \")) }\n        case .text, .url:\n            guard let text = entry.text, text.count > 60 || text.contains(\"\\n\") else { return nil }\n            let lines = text.split(separator: \"\\n\", omittingEmptySubsequences: false).count\n            caption.insert(\"\\(text.count) characters\" + (lines > 1 ? \", \\(lines) lines\" : \"\"), at: 0)\n            return { $0.preview(text: String(text.prefix(2000)), caption: caption.joined(separator: \" · \")) }\n        }\n    }\n\n    static func relative(_ date: Date, now: Date = Date()) -> String {\n        let seconds = now.timeIntervalSince(date)\n        if seconds < 60 { return \"just now\" }\n        if seconds < 3600 { return \"\\(Int(seconds / 60))m ago\" }\n        if seconds < 86400 { return \"\\(Int(seconds / 3600))h ago\" }\n        return date.formatted(date: .abbreviated, time: .omitted)\n    }\n\n    // MARK: Commands\n\n    var commands: [Command] {\n        Command(\"search\", title: \"Search Clipboard History\", subtitle: \"Paste anything you copied\", symbol: \"list.clipboard\", keywords: [\"paste\", \"copy\", \"history\"])\n            .view { _ in\n                let all = self.sorted\n                if all.isEmpty {\n                    ContentUnavailableView(\"Nothing copied yet\", symbol: \"list.clipboard\", message: self.paused ? \"Recording is paused.\" : \"Copy something in any app and it shows up here.\")\n                } else {\n                    List(searchable: true) {\n                        let pinned = all.filter(\\.pinned)\n                        if !pinned.isEmpty {\n                            Section(\"Pinned\") {\n                                ForEach(pinned) { entry in self.row(entry, paste: true) }\n                            }\n                        }\n                        Section(pinned.isEmpty ? nil : \"History\") {\n                            ForEach(all.filter { !$0.pinned }) { entry in self.row(entry, paste: true) }\n                        }\n                    }\n                }\n            }\n        Command(\"paste-plain\", title: \"Paste Last Copy as Plain Text\", symbol: \"doc.plaintext\", keywords: [\"clipboard\", \"format\"]) { _ in\n            guard let latest = self.sorted.first(where: { !$0.pinned }) ?? self.sorted.first else { return .message(\"Nothing copied yet\") }\n            await self.use(latest, paste: true, plain: true)\n            if let error = self.lastError { return .failure(error) }\n            return .done\n        }\n        Command(\"pause\", title: self.paused ? \"Resume Clipboard History\" : \"Pause Clipboard History\", symbol: self.paused ? \"play.fill\" : \"pause.fill\", keywords: [\"clipboard\", \"record\"]) { _ in\n            self.paused.toggle()\n            return .message(self.paused ? \"Clipboard history paused\" : \"Clipboard history recording\")\n        }\n        Command(\"ignore-next\", title: \"Ignore Next Copy\", symbol: \"eye.slash\", keywords: [\"clipboard\", \"password\", \"secret\"]) { _ in\n            self.ignoreNext = true\n            return .message(\"The next copy won't be kept\")\n        }\n    }\n\n    // MARK: Agents\n\n    var tools: [AgentTool] {\n        AgentTool(\"recent_clips\", description: \"The most recent clipboard items, newest first\", parameters: [ToolParameter(\"count\", .number, description: \"How many (default 10)\", required: false)]) { arguments in\n            let count = Int(arguments.number(\"count\") ?? 10)\n            return self.sorted.prefix(max(1, min(count, 50))).enumerated().map { \"\\($0.offset + 1). \\($0.element.title)\" }.joined(separator: \"\\n\")\n        }\n        AgentTool(\"search_clips\", description: \"Clipboard items containing the text, newest first, in full\", parameters: [ToolParameter(\"query\", description: \"Text to look for\")]) { arguments in\n            let query = arguments.string(\"query\") ?? \"\"\n            let found = self.sorted.filter { ($0.text ?? \"\").localizedCaseInsensitiveContains(query) }.prefix(10)\n            return found.isEmpty ? \"Nothing in the history contains \\\"\\(query)\\\".\" : found.map { $0.text ?? $0.title }.joined(separator: \"\\n---\\n\")\n        }\n        AgentTool(\"copy_clip\", description: \"Put a history item back on the clipboard, by its position in recent_clips\", parameters: [ToolParameter(\"position\", .number, description: \"1 is the newest\")]) { arguments in\n            let position = Int(arguments.number(\"position\") ?? 0)\n            let items = self.sorted\n            guard position >= 1, position <= items.count else { return \"There is no item \\(position).\" }\n            await self.use(items[position - 1], paste: false)\n            return \"Copied \\(items[position - 1].title)\"\n        }\n    }\n}\n",
  "sourceDigest" : "623cd67d66eb193aa6074ef19d041b8e3fed95c85c66fe1b211fda2894bfd5b0"
}