import Foundation import AppIntents // MARK: - Open /// Opens a bookmark in the in-app browser. Conforms to `OpenIntent` — the /// system-blessed "open" type Siri understands. (iOS 26 has no separate /// `system.open` App Schema; `OpenIntent` with an `AppEntity` target *is* the /// supported mechanism, so no schema macro is needed here.) struct OpenBookmarkIntent: OpenIntent { static let title: LocalizedStringResource = "Open Bookmark" static let openAppWhenRun = true @Parameter(title: "Bookmark") var target: BookmarkEntity @MainActor func perform() async throws -> some IntentResult { IntentRouter.shared.openBookmark(url: target.url.absoluteString) return .result() } } // MARK: - Search /// Searches Marks and shows the results in the app's search tab. /// /// Conforms to the formal `system.search` App Schema (`ShowInAppSearchResultsIntent`), /// which is what makes it executable by conversational Siri / Apple Intelligence: /// Siri hands over the spoken term as `criteria` and the app re-runs it in its /// own search UI. `searchScopes` defaults to `[.general]` for string criteria. @AppIntent(schema: .system.search) struct SearchMarksIntent { static let title: LocalizedStringResource = "Search Marks" static let description = IntentDescription("Search your Marks bookmarks.") @Parameter(title: "Search", requestValueDialog: "What do you want to search for?") var criteria: StringSearchCriteria @MainActor func perform() async throws -> some IntentResult { IntentRouter.shared.search(criteria.term) return .result() } } // MARK: - Add /// Saves a link to Marks without opening the app. Mirrors the Share Extension's /// headless save, but goes through the typed `LinkdingAPI`. struct AddBookmarkIntent: AppIntent { static let title: LocalizedStringResource = "Add Bookmark to Marks" static let description = IntentDescription("Save a link to your Marks bookmarks.") static let openAppWhenRun = false @Parameter(title: "URL") var url: URL @Parameter(title: "Title") var name: String? @Parameter(title: "Tags") var tags: [String]? static var parameterSummary: some ParameterSummary { Summary("Add \(\.$url) to Marks") { \.$name \.$tags } } func perform() async throws -> some IntentResult & ReturnsValue & ProvidesDialog & ShowsSnippetView { let api = try MarksIntent.api() let create = BookmarkCreate( url: url.absoluteString, title: name ?? "", tagNames: tags ?? [], isArchived: false, unread: true, shared: false ) let bookmark = try await api.createBookmark(create) let entity = BookmarkEntity(from: bookmark) MarksIntent.refreshWidgets(adding: bookmark) return .result( value: entity, dialog: IntentDialog("Saved \(entity.title) to Marks."), view: BookmarkSnippetView(entity: entity) ) } } // MARK: - Show unread /// Returns the user's unread bookmarks — "what's unread in Marks?" struct ShowUnreadIntent: AppIntent { static let title: LocalizedStringResource = "Show Unread Bookmarks" static let description = IntentDescription("List the bookmarks you haven't read yet.") func perform() async throws -> some IntentResult & ReturnsValue<[BookmarkEntity]> & ProvidesDialog { let api = try MarksIntent.api() let response = try await api.fetchBookmarks(limit: 10, unread: true) let entities = response.results.map(BookmarkEntity.init(from:)) let dialog: IntentDialog = entities.isEmpty ? "You have no unread bookmarks." : "You have \(entities.count) unread bookmark\(entities.count == 1 ? "" : "s")." return .result(value: entities, dialog: dialog) } } // MARK: - Summarize (AI) /// Produces an AI summary of a bookmark via the bundled ClaudeService. struct SummarizeBookmarkIntent: AppIntent { static let title: LocalizedStringResource = "Summarize Bookmark" static let description = IntentDescription("Get an AI summary of a saved bookmark.") @Parameter(title: "Bookmark") var target: BookmarkEntity static var parameterSummary: some ParameterSummary { Summary("Summarize \(\.$target)") } func perform() async throws -> some IntentResult & ReturnsValue & ProvidesDialog & ShowsSnippetView { let claude = ClaudeService() let (summary, _) = try await claude.enrich(bookmark: target.makeBookmark()) return .result( value: summary, dialog: IntentDialog("\(summary)"), view: SummarySnippetView(title: target.title, host: target.host, summary: summary) ) } } // MARK: - App Shortcuts struct MarksShortcuts: AppShortcutsProvider { static var appShortcuts: [AppShortcut] { AppShortcut( intent: AddBookmarkIntent(), phrases: [ "Add a bookmark to \(.applicationName)", "Save this link to \(.applicationName)" ], shortTitle: "Add Bookmark", systemImageName: "bookmark" ) AppShortcut( intent: SearchMarksIntent(), phrases: [ "Search \(.applicationName)", "Search my bookmarks in \(.applicationName)" ], shortTitle: "Search", systemImageName: "magnifyingglass" ) AppShortcut( intent: ShowUnreadIntent(), phrases: [ "Show unread in \(.applicationName)", "What's unread in \(.applicationName)" ], shortTitle: "Unread", systemImageName: "envelope.badge" ) AppShortcut( intent: SummarizeBookmarkIntent(), phrases: [ "Summarize a bookmark in \(.applicationName)", "Summarize this with \(.applicationName)" ], shortTitle: "Summarize", systemImageName: "sparkles" ) } }