Token导航 LogoToken导航

Agent Skill · 研究检索

axiom-app-intents-ref(axiom 应用意图参考)

axiom-app-intents-ref 用于查找、检索和筛选相关信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要根据关键词、任务场景或来源线索快速定位候选结果时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

axiom-app-intents-ref 是研究检索类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为需要联网;许可证为 MIT,第三方扫描有提醒项,按安装量在研究检索类的 73,675 个 Skill 里排第 22,855。

总安装 4,440周安装 185GitHub Stars 873下载 1,480核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
MIT

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:axiom-app-intents-ref(axiom 应用意图参考)
来源仓库:https://github.com/charleswiltgen/axiom
仓库路径:skills/axiom-app-intents-ref
安装命令:
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-app-intents-ref
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。不同来源提供的安装方式可能略有差异;本站展示可直接复制的安装命令,安装前请核对来源页面。

skills.shnpx skills
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-app-intents-ref

这个 Skill 是做什么的

研究检索类 Skill 主要解决「找资料、核对事实、把来源整理清楚」的问题,通常会让助手先检索再总结。

全面指南将应用功能暴露给 Siri、Apple Intelligence 和 Shortcuts。取代旧版 SiriKit,采用 Swift-first API 设计。使能在 Spotlight 搜索、快捷指令和系统体验中调用应用动作。

安装命令:npx skills add https://github.com/charleswiltgen/axiom --skill axiom-app-intents-ref 适用宿主包括 Codex、Claude、Cursor、Gemini CLI,接入前应确认版本、权限和运行环境要求。

来源仓库内路径:skills/axiom-app-intents-ref

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/charleswiltgen/axiom --skill axiom-app-intents…;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它需要需要联网,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

axiom-app-intents-ref 怎么安装?

在终端执行 npx skills add https://github.com/charleswiltgen/axiom --skill axiom-app-intents-ref。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

axiom-app-intents-ref 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

axiom-app-intents-ref 需要什么权限?

权限级别是需要联网。该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

axiom-app-intents-ref 是免费的吗?

来源标注的许可证是 MIT。本站不代收任何费用,也不托管安装包;能否商用、要不要署名,以许可证原文为准。

axiom-app-intents-ref 用的人多吗?

公开数据是累计安装 4,440 次,近一周安装 185 次,下载 1,480 次,GitHub 873 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

axiom-app-intents-ref 安全吗?

第三方扫描有提醒项,说明存在需要人工确认的行为。安装前请打开源码看清它读了什么、联网到哪里。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 3 个目录同时收录,说明这个 Skill 流传比较广。
  • 许可证明确为 MIT,商用和分发边界好判断。

先想清楚的地方

  • 第三方扫描存在提醒项或未通过项,需要人工逐条确认。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理研究检索类任务、又不想每次都手写提示词的人。

不太适合

  • 需要厂商级技术支持和 SLA 的生产系统。

研究检索类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
find-skills

寻找技能 · Codex、Claude

31,199,81316,486操作浏览器MIT有提醒
self-improving-agent

自我改进 Agent · OpenClaw

16,464,0023,396接触敏感数据MIT-0通过
soultrace

灵魂追踪 · Codex、Claude

7,588,94412需要联网未明确有提醒
self-improving

自我完善 · OpenClaw

6,829,2841,046需要联网MIT-0通过
microsoft-foundry

微软代工厂 · Codex、Claude

6,614,144764调用外部服务未明确有提醒
axiom-app-intents-ref

axiom 应用意图参考 · 本页

4,440873需要联网MIT有提醒

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

App Intents Integration

Overview

Comprehensive guide to App Intents framework for exposing app functionality to Siri, Apple Intelligence, Shortcuts, Spotlight, and other system experiences. Replaces older SiriKit custom intents with modern Swift-first API.

Core principle App Intents make your app's actions discoverable across Apple's ecosystem. Well-designed intents feel natural in Siri conversations, Shortcuts automation, and Spotlight search.

When to Use This Skill

  • Exposing app functionality to Siri and Apple Intelligence
  • Making app actions available in Shortcuts app
  • Enabling Spotlight search for app content
  • Integrating with Focus filters, widgets, Live Activities
  • Adding Action button support (Apple Watch Ultra)
  • Debugging intent resolution or parameter validation failures
  • Testing intents with Shortcuts app
  • Implementing entity queries for app content

Related Skills

  • app-shortcuts-ref — App Shortcuts for instant Siri/Spotlight availability without user setup
  • core-spotlight-ref — Core Spotlight and NSUserActivity integration for content indexing
  • app-discoverability — Strategic guide for making apps surface system-wide across all APIs

System Experiences Supported

App Intents integrate with:

  • Siri — Voice commands and Apple Intelligence
  • Shortcuts — Automation workflows
  • App Shortcuts — Pre-configured actions available instantly (see app-shortcuts-ref)
  • Spotlight — Search discovery
  • Focus Filters — Contextual filtering
  • Action Button — Quick actions (Apple Watch Ultra)
  • Control Center — Custom controls
  • WidgetKit — Interactive widgets
  • Live Activities — Dynamic Island updates
  • Visual Intelligence — Image-based interactions

Visual Intelligence Integration

IntentValueQuery

Allow users to circle objects in the Visual Intelligence camera and see matching results from your app:

@UnionValue
enum VisualSearchResult {
    case landmark(LandmarkEntity)
    case collection(CollectionEntity)
}

struct LandmarkIntentValueQuery: IntentValueQuery {
    func values(for input: SemanticContentDescriptor) async throws -> [VisualSearchResult] {
        // Match visual input to app entities
    }
}

// Each entity type needs an OpenIntent
struct OpenLandmarkIntent: OpenIntent { /* ... */ }
struct OpenCollectionIntent: OpenIntent { /* ... */ }

Onscreen Entities

Associate app entities with visible content so users can ask Siri or ChatGPT about what's on screen:

struct LandmarkDetailView: View {
    let landmark: LandmarkEntity

    var body: some View {
        Group { /* View content */ }
        .userActivity("com.landmarks.ViewingLandmark") { activity in
            activity.title = "Viewing \(landmark.name)"
            activity.appEntityIdentifier = EntityIdentifier(for: landmark)
        }
    }
}

Core Concepts

The Three Building Blocks

1. AppIntent — Executable actions with parameters

struct OrderSoupIntent: AppIntent {
    static var title: LocalizedStringResource = "Order Soup"
    static var description: IntentDescription = "Orders soup from the restaurant"

    @Parameter(title: "Soup")
    var soup: SoupEntity

    @Parameter(title: "Quantity")
    var quantity: Int?

    func perform() async throws -> some IntentResult {
        guard let quantity = quantity, quantity < 10 else {
            throw $quantity.needsValue("Please specify how many soups")
        }

        try await OrderService.shared.order(soup: soup, quantity: quantity)
        return .result()
    }
}

2. AppEntity — Objects users interact with

struct SoupEntity: AppEntity {
    var id: String
    var name: String
    var price: Decimal

    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Soup"

    var displayRepresentation: DisplayRepresentation {
        DisplayRepresentation(title: "\(name)", subtitle: "$\(price)")
    }

    static var defaultQuery = SoupQuery()
}

3. AppEnum — Enumeration types for parameters

enum SoupSize: String, AppEnum {
    case small
    case medium
    case large

    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Size"
    static var caseDisplayRepresentations: [SoupSize: DisplayRepresentation] = [
        .small: "Small (8 oz)",
        .medium: "Medium (12 oz)",
        .large: "Large (16 oz)"
    ]
}

AppIntent: Defining Actions

Essential Properties

struct SendMessageIntent: AppIntent {
    // REQUIRED: Short verb-noun phrase
    static var title: LocalizedStringResource = "Send Message"

    // REQUIRED: Purpose explanation
    static var description: IntentDescription = "Sends a message to a contact"

    // OPTIONAL: Discovery in Shortcuts/Spotlight
    static var isDiscoverable: Bool = true

    // OPTIONAL: Launch app when run
    static var openAppWhenRun: Bool = false

    // OPTIONAL: Authentication requirement
    static var authenticationPolicy: IntentAuthenticationPolicy = .requiresAuthentication
}

Parameter Declaration

struct BookAppointmentIntent: AppIntent {
    // Required parameter (non-optional)
    @Parameter(title: "Service")
    var service: ServiceEntity

    // Optional parameter
    @Parameter(title: "Preferred Date")
    var preferredDate: Date?

    // Parameter with requestValueDialog for disambiguation
    @Parameter(title: "Location",
               requestValueDialog: "Which location would you like to visit?")
    var location: LocationEntity

    // Parameter with default value
    @Parameter(title: "Duration")
    var duration: Int = 60
}

Parameter Summary (Siri Phrasing)

struct OrderIntent: AppIntent {
    @Parameter(title: "Item")
    var item: MenuItem

    @Parameter(title: "Quantity")
    var quantity: Int

    static var parameterSummary: some ParameterSummary {
        Summary("Order \(\.$quantity) \(\.$item)") {
            \.$quantity
            \.$item
        }
    }
}
// Siri: "Order 2 lattes"

The perform() Method

func perform() async throws -> some IntentResult {
    // 1. Validate parameters
    guard quantity > 0 && quantity < 100 else {
        throw ValidationError.invalidQuantity
    }

    // 2. Execute action
    let order = try await orderService.placeOrder(
        item: item,
        quantity: quantity
    )

    // 3. Donate for learning (optional)
    await donation()

    // 4. Return result
    return .result(
        value: order,
        dialog: "Your order for \(quantity) \(item.name) has been placed"
    )
}

Error Handling

enum OrderError: Error, CustomLocalizedStringResourceConvertible {
    case outOfStock(itemName: String)
    case paymentFailed
    case networkError

    var localizedStringResource: LocalizedStringResource {
        switch self {
        case .outOfStock(let name):
            return "Sorry, \(name) is out of stock"
        case .paymentFailed:
            return "Payment failed. Please check your payment method"
        case .networkError:
            return "Network error. Please try again"
        }
    }
}

func perform() async throws -> some IntentResult {
    if !item.isInStock {
        throw OrderError.outOfStock(itemName: item.name)
    }
    // ...
}

AppEntity: Representing App Content

Entity Definition

struct BookEntity: AppEntity {
    // REQUIRED: Unique, persistent identifier
    var id: UUID

    // App data properties
    var title: String
    var author: String
    var coverImageURL: URL?

    // REQUIRED: Type display name
    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Book"

    // REQUIRED: Instance display
    var displayRepresentation: DisplayRepresentation {
        DisplayRepresentation(
            title: "\(title)",
            subtitle: "by \(author)",
            image: coverImageURL.map { .init(url: $0) }
        )
    }

    // REQUIRED: Query for resolution
    static var defaultQuery = BookQuery()
}

Exposing Properties

struct TaskEntity: AppEntity {
    var id: UUID

    @Property(title: "Title")
    var title: String

    @Property(title: "Due Date")
    var dueDate: Date?

    @Property(title: "Priority")
    var priority: TaskPriority

    @Property(title: "Completed")
    var isCompleted: Bool

    // Properties exposed to system for filtering/sorting
}

Computed and Deferred Properties

@ComputedProperty

Computed properties that read directly from a source of truth (no stored value):

struct SettingsEntity: UniqueAppEntity {
    @ComputedProperty
    var defaultPlace: PlaceDescriptor {
        UserDefaults.standard.defaultPlace
    }

    init() { }
}
@DeferredProperty

Properties that are expensive to calculate, only fetched when explicitly requested:

struct LandmarkEntity: IndexedEntity {
    @DeferredProperty
    var crowdStatus: Int {
        get async throws {
            await modelData.getCrowdStatus(self)
        }
    }
}

Entity Query

struct BookQuery: EntityQuery {
    func entities(for identifiers: [UUID]) async throws -> [BookEntity] {
        // Fetch entities by IDs
        return try await BookService.shared.fetchBooks(ids: identifiers)
    }

    func suggestedEntities() async throws -> [BookEntity] {
        // Provide suggestions (recent, favorites, etc.)
        return try await BookService.shared.recentBooks(limit: 10)
    }
}

// Optional: Enable string-based search
extension BookQuery: EntityStringQuery {
    func entities(matching string: String) async throws -> [BookEntity] {
        return try await BookService.shared.searchBooks(query: string)
    }
}

Separating Entities from Models

❌ DON'T: Modify core data models
// DON'T make your model conform to AppEntity
struct Book: AppEntity { // Bad - couples model to intents
    var id: UUID
    var title: String
    // ...
}
✅ DO: Create dedicated entities
// Your core model
struct Book {
    var id: UUID
    var title: String
    var isbn: String
    var pages: Int
    // ... lots of internal properties
}

// Separate entity for intents
struct BookEntity: AppEntity {
    var id: UUID
    var title: String
    var author: String

    // Convert from model
    init(from book: Book) {
        self.id = book.id
        self.title = book.title
        self.author = book.author.name
    }
}

Authentication & Security

Authentication Policies

struct ViewAccountIntent: AppIntent {
    // No authentication required
    static var authenticationPolicy: IntentAuthenticationPolicy = .alwaysAllowed
}

struct TransferMoneyIntent: AppIntent {
    // Requires user to be logged in
    static var authenticationPolicy: IntentAuthenticationPolicy = .requiresAuthentication
}

struct UnlockVaultIntent: AppIntent {
    // Requires device unlock (Face ID/Touch ID/passcode)
    static var authenticationPolicy: IntentAuthenticationPolicy = .requiresLocalDeviceAuthentication
}

Background vs Foreground Execution

Intent Modes

Use supportedModes for granular control over execution context instead of the boolean openAppWhenRun:

struct GetCrowdStatusIntent: AppIntent {
    static let supportedModes: IntentModes = [.background, .foreground(.dynamic)]

    func perform() async throws -> some ReturnsValue<Int> & ProvidesDialog {
        guard await modelData.isOpen(landmark) else {
            return .result(value: 0, dialog: "The landmark is currently closed.")
        }

        if systemContext.currentMode.canContinueInForeground {
            do {
                try await continueInForeground(alwaysConfirm: false)
                await navigator.navigateToCrowdStatus(landmark)
            } catch {
                // Opening app was denied
            }
        }

        let status = await modelData.getCrowdStatus(landmark)
        return .result(value: status, dialog: "Current crowd level: \(status)")
    }
}
Available Modes
ModeBehavior
.backgroundPerforms entirely in background
.foreground(.immediate)App foregrounded before perform() runs
.foreground(.dynamic)Can request foreground during execution
.foreground(.deferred)Background initially, foreground before completion
Common Combinations
CombinationUse When
[.background,.foreground]Foreground default, background fallback
[.background,.foreground(.dynamic)]Background default, can request foreground
[.background,.foreground(.deferred)]Background initially, guaranteed foreground when requested

Continuing in Foreground

Request foreground transition at runtime when using .foreground(.dynamic):

// Normal transition
try await continueInForeground(alwaysConfirm: false)

// Transition after an error
throw needsToContinueInForegroundError(
    IntentDialog("Need to open app to complete this action"),
    alwaysConfirm: true
)

Background Execution (Legacy)

struct QuickToggleIntent: AppIntent {
    static var openAppWhenRun: Bool = false // Runs in background

    func perform() async throws -> some IntentResult {
        // Executes without opening app
        await SettingsService.shared.toggle(setting: .darkMode)
        return .result()
    }
}

Foreground Continuation (Legacy)

struct EditDocumentIntent: AppIntent {
    @Parameter(title: "Document")
    var document: DocumentEntity

    func perform() async throws -> some IntentResult {
        // Open app to continue in UI
        return .result(opensIntent: OpenDocumentIntent(document: document))
    }
}

struct OpenDocumentIntent: AppIntent {
    static var openAppWhenRun: Bool = true

    @Parameter(title: "Document")
    var document: DocumentEntity

    func perform() async throws -> some IntentResult {
        // App is now foreground, safe to update UI
        await MainActor.run {
            DocumentCoordinator.shared.open(document: document)
        }
        return .result()
    }
}

Confirmation Dialogs

Requesting Confirmation

struct DeleteTaskIntent: AppIntent {
    @Parameter(title: "Task")
    var task: TaskEntity

    func perform() async throws -> some IntentResult {
        // Request confirmation before destructive action
        try await requestConfirmation(
            result: .result(dialog: "Are you sure you want to delete '\(task.title)'?"),
            confirmationActionName: .init(stringLiteral: "Delete")
        )

        // User confirmed, proceed
        try await TaskService.shared.delete(task: task)
        return .result(dialog: "Task deleted")
    }
}

Multiple Choice API

Request user input with structured options:

let options = [
    IntentChoiceOption(title: "Option 1", subtitle: "Description 1"),
    IntentChoiceOption(title: "Option 2", subtitle: "Description 2"),
    IntentChoiceOption.cancel(title: "Not now")
]

let choice = try await requestChoice(
    between: options,
    dialog: IntentDialog("Please select an option")
)

switch choice.id {
case options[0].id: // Option 1 selected
case options[1].id: // Option 2 selected
default: // Cancelled
}

Interactive Snippets

Static Snippets

Return a SwiftUI view showing the outcome of an intent:

func perform() async throws -> some IntentResult {
    return .result(view: Text("Order placed!").font(.title))
}

SnippetIntent

Return interactive snippets with follow-up action buttons:

func perform() async throws -> some IntentResult {
    let landmark = await findNearestLandmark()

    return .result(
        value: landmark,
        opensIntent: OpenLandmarkIntent(landmark: landmark),
        snippetIntent: LandmarkSnippetIntent(landmark: landmark)
    )
}

struct LandmarkSnippetIntent: SnippetIntent {
    @Parameter var landmark: LandmarkEntity

    var snippet: some View {
        VStack {
            Text(landmark.name).font(.headline)
            Text(landmark.description).font(.body)

            HStack {
                Button("Add to Favorites") { /* action */ }
                Button("Search Tickets") { /* action */ }
            }
        }
        .padding()
    }
}

Swift Package Support

AppIntentsPackage

Include App Intents in Swift Packages and static libraries:

// In your framework or dynamic library
public struct LandmarksKitPackage: AppIntentsPackage { }

// In your app target
struct LandmarksPackage: AppIntentsPackage {
    static var includedPackages: [any AppIntentsPackage.Type] {
        [LandmarksKitPackage.self]
    }
}

This enables modular intent definitions across package boundaries. The app target aggregates all packages via includedPackages.


Apple Intelligence: Use Model Action

Overview

The Use Model action in Shortcuts (iOS 18.1+) allows users to incorporate Apple Intelligence models into their automation workflows. Your app's entities can be passed to language models for filtering, transformation, and reasoning.

Key capability Under the hood, the action passes a JSON representation of your entity to the model, so you'll want to make sure to expose any information you want it to be able to reason over, in the entity definition.

Three Output Types

1. Text (AttributedString)
  • Models often respond with Rich Text (bold, italic, lists, tables)
  • Use AttributedString type for text parameters to preserve formatting
  • Enables lossless transfer from model to your app
2. Dictionary
  • Structured data extraction from unstructured input
  • Useful for parsing PDFs, emails, documents
  • Example: Extract vendor, amount, date from invoice
3. App Entities (Your Types)
  • Pass lists of entities to models for filtering/reasoning
  • Model receives JSON representation of entities
  • Example: "Filter calendar events related to my trip"

Exposing Entities to Models

Models receive a JSON representation of your entities including:

1. All exposed properties (converted to strings)

struct EventEntity: AppEntity {
    var id: UUID

    @Property(title: "Title")
    var title: String

    @Property(title: "Start Date")
    var startDate: Date

    @Property(title: "End Date")
    var endDate: Date

    @Property(title: "Notes")
    var notes: String?

    // All @Property values included in JSON for model
}

2. Type display representation (hints what entity represents)

static var typeDisplayRepresentation: TypeDisplayRepresentation = "Calendar Event"

3. Display representation (title and subtitle)

var displayRepresentation: DisplayRepresentation {
    DisplayRepresentation(
        title: "\(title)",
        subtitle: "\(startDate.formatted())"
    )
}
Example JSON sent to model
{
  "type": "Calendar Event",
  "title": "Team Meeting",
  "subtitle": "Jan 15, 2025 at 2:00 PM",
  "properties": {
    "Title": "Team Meeting",
    "Start Date": "2025-01-15T14:00:00Z",
    "End Date": "2025-01-15T15:00:00Z",
    "Notes": "Discuss Q1 roadmap"
  }
}

Supporting Rich Text with AttributedString

Why it matters If your app supports Rich Text content, now is the time to make sure your app intents use the attributed string type for text parameters where appropriate.

❌ DON'T: Use plain String
struct CreateNoteIntent: AppIntent {
    @Parameter(title: "Content")
    var content: String // Loses formatting from model
}
✅ DO: Use AttributedString
struct CreateNoteIntent: AppIntent {
    @Parameter(title: "Content")
    var content: AttributedString // Preserves Rich Text

    func perform() async throws -> some IntentResult {
        let note = Note(content: content) // Rich Text preserved
        try await NoteService.shared.save(note)
        return .result()
    }
}
Real-world example from WWDC

Bear app's Create Note accepts AttributedString, allowing diary templates from ChatGPT to include:

  • Bold headings
  • Mood logging tables
  • Formatted lists
  • All preserved losslessly

Automatic Type Conversion

When Use Model output connects to another action, the runtime automatically converts types:

Example: Boolean for If actions
// User's shortcut:
// 1. Get notes created today
// 2. For each note:
//    - Use Model: "Is this note related to developing features for Shortcuts?"
//    - If [model output] = yes:
//      - Add to Shortcuts Projects folder

Instead of returning verbose text like "Yes, this note seems to be about developing features for the Shortcuts app", the model automatically returns a Boolean (true/false) when connected to an If action.

Explicit output types available
  • Text (AttributedString)
  • Number
  • Boolean
  • Dictionary
  • Date
  • App Entities

Follow-Up Feature

Enable iterative refinement before passing to next action:

// User runs shortcut:
// 1. Get recipe from Safari
// 2. Use Model: "Extract ingredients list"
//    - Follow Up: enabled
//    - User types: "Double the recipe"
//    - Model adjusts: 800g flour instead of 400g
// 3. Add to Grocery List in Things app
When to use
  • Recipe modifications (scale servings, substitute ingredients)
  • Content refinement (adjust tone, length, style)
  • Data validation (confirm extracted values before saving)

IndexedEntity: Automatic Find Actions

Overview

IndexedEntity dramatically reduces boilerplate by auto-generating Find actions from your Spotlight integration. Instead of manually implementing EntityQuery and EntityPropertyQuery, adopt IndexedEntity to get:

  • Automatic Find action in Shortcuts
  • Property-based filtering
  • Search support
  • Minimal code required

Basic Implementation

struct EventEntity: AppEntity, IndexedEntity {
    var id: UUID

    // 1. Properties with indexing keys
    @Property(title: "Title", indexingKey: \.eventTitle)
    var title: String

    @Property(title: "Start Date", indexingKey: \.startDate)
    var startDate: Date

    @Property(title: "End Date", indexingKey: \.endDate)
    var endDate: Date

    // 2. Custom key for properties without standard Spotlight attribute
    @Property(title: "Notes", customIndexingKey: "eventNotes")
    var notes: String?

    // Display representation automatically maps to Spotlight
    var displayRepresentation: DisplayRepresentation {
        DisplayRepresentation(
            title: "\(title)",
            subtitle: "\(startDate.formatted())"
            // title → kMDItemTitle
            // subtitle → kMDItemDescription
            // image → kMDItemContentType (if provided)
        )
    }

    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Event"
}

Indexing Key Mapping

Standard Spotlight attribute keys
// Common Spotlight keys for events
@Property(title: "Title", indexingKey: \.eventTitle)
var title: String

@Property(title: "Start Date", indexingKey: \.startDate)
var startDate: Date

@Property(title: "Location", indexingKey: \.eventLocation)
var location: String?
Custom keys for non-standard attributes
@Property(title: "Notes", customIndexingKey: "eventNotes")
var notes: String?

@Property(title: "Attendee Count", customIndexingKey: "attendeeCount")
var attendeeCount: Int

Auto-Generated Find Action

With IndexedEntity conformance, users get this Find action automatically:

In Shortcuts app
Find Events where:
  - Title contains "Team"
  - Start Date is today
  - Location is "San Francisco"
Without IndexedEntity, you'd need to manually implement
  • EnumerableEntityQuery protocol
  • EntityPropertyQuery protocol
  • Property filters for each searchable field
  • Search/suggestion logic

With IndexedEntity Just add indexing keys, done!

Search Support

Enable string-based search by implementing EntityStringQuery:

extension EventEntityQuery: EntityStringQuery {
    func entities(matching string: String) async throws -> [EventEntity] {
        return try await EventService.shared.search(query: string)
    }
}

Or rely on IndexedEntity + Spotlight for automatic search.

Explicit Spotlight Indexing

For entities that need custom searchable attributes or manual index management:

extension LandmarkEntity {
    var searchableAttributes: CSSearchableItemAttributeSet {
        let attributes = CSSearchableItemAttributeSet()
        attributes.title = name
        attributes.namedLocation = regionDescription
        attributes.keywords = activities
        attributes.latitude = NSNumber(value: coordinate.latitude)
        attributes.longitude = NSNumber(value: coordinate.longitude)
        attributes.supportsNavigation = true
        return attributes
    }
}

// Add entities to index
func indexLandmarks() async {
    let landmarks = await fetchLandmarks()
    try await CSSearchableIndex.default().indexAppEntities(landmarks, priority: .normal)
}

// Remove from index when deleted
func deleteLandmark(_ landmark: LandmarkEntity) async {
    await dataStore.delete(landmark)
    try await CSSearchableIndex.default().deleteAppEntities(
        identifiedBy: [landmark.id],
        ofType: LandmarkEntity.self
    )
}

Example: Travel Tracking App

Apple's sample code (App Intents Travel Tracking App) demonstrates IndexedEntity:

struct TripEntity: AppEntity, IndexedEntity {
    var id: UUID

    @Property(title: "Name", indexingKey: \.title)
    var name: String

    @Property(title: "Start Date", indexingKey: \.startDate)
    var startDate: Date

    @Property(title: "End Date", indexingKey: \.endDate)
    var endDate: Date

    @Property(title: "Destination", customIndexingKey: "destination")
    var destination: String

    // Auto-generated Find Trips action with filters for all properties
}

Spotlight on Mac

Overview

Spotlight on Mac (macOS Sequoia+) allows users to run your app's intents directly from system search. Intents that work in Shortcuts automatically work in Spotlight with proper configuration.

Key principle Spotlight is all about running things quickly. To do that, people need to be able to provide all the information your intent needs to run directly in Spotlight.

Requirements for Spotlight Visibility

1. Parameter Summary Must Include All Required Parameters

The parameter summary, which is what people will see in Spotlight UI, must contain all required parameters that don't have a default value.

❌ WON'T SHOW in Spotlight
struct CreateEventIntent: AppIntent {
    static var title: LocalizedStringResource = "Create Event"

    @Parameter(title: "Title")
    var title: String

    @Parameter(title: "Start Date")
    var startDate: Date

    @Parameter(title: "End Date")
    var endDate: Date

    @Parameter(title: "Notes") // Required, no default
    var notes: String

    static var parameterSummary: some ParameterSummary {
        Summary("Create '\(\.$title)' from \(\.$startDate) to \(\.$endDate)")
        // Missing 'notes' parameter!
    }
}
✅ WILL SHOW in Spotlight (Option 1: Make optional)
@Parameter(title: "Notes")
var notes: String? // Optional - can omit from summary
✅ WILL SHOW in Spotlight (Option 2: Provide default)
@Parameter(title: "Notes")
var notes: String = "" // Has default - can omit from summary
✅ WILL SHOW in Spotlight (Option 3: Include in summary)
static var parameterSummary: some ParameterSummary {
    Summary("Create '\(\.$title)' from \(\.$startDate) to \(\.$endDate)") {
        \.$notes // All required params included
    }
}
2. Intent Must Not Be Hidden

Intents hidden from Shortcuts won't appear in Spotlight:

// ❌ Hidden from Spotlight
static var isDiscoverable: Bool = false

// ❌ Hidden from Spotlight
static var assistantOnly: Bool = true

// ❌ Hidden from Spotlight
// Intent with no perform() method (widget configuration only)

Providing Suggestions

Make parameter filling quick with suggestions:

Option 1: Suggested Entities (Subset of Large List)
struct EventEntityQuery: EntityQuery {
    func entities(for identifiers: [UUID]) async throws -> [EventEntity] {
        return try await EventService.shared.fetchEvents(ids: identifiers)
    }

    // Provide upcoming events, not all past/present events
    func suggestedEntities() async throws -> [EventEntity] {
        return try await EventService.shared.upcomingEvents(limit: 10)
    }
}
Option 2: All Entities (Small, Bounded List)
struct TimezoneQuery: EnumerableEntityQuery {
    func allEntities() async throws -> [TimezoneEntity] {
        // Small list - provide all
        return TimezoneEntity.allTimezones
    }
}

Use suggested entities when List is large or unbounded (calendar events, notes, contacts) Use all entities when List is small and bounded (timezones, priority levels, categories)

On-Screen Content Tagging

Suggest currently active content:

// In your detail view controller
func showEventDetail(_ event: Event) {
    let activity = NSUserActivity(activityType: "com.myapp.viewEvent")
    activity.persistentIdentifier = event.id.uuidString

    // Spotlight suggests this event for parameters
    activity.appEntityIdentifier = event.id.uuidString

    userActivity = activity
}

For more details on on-screen content tagging, see the "Exploring New Advances in App Intents" session.

Search Beyond Suggestions

Basic filtering (automatic): If you provide suggestions, Spotlight automatically filters them as user types.

Deep search (requires implementation): For searching beyond suggestions:

Option 1: EntityStringQuery
extension EventQuery: EntityStringQuery {
    func entities(matching string: String) async throws -> [EventEntity] {
        return try await EventService.shared.search(query: string)
    }
}
Option 2: IndexedEntity
struct EventEntity: AppEntity, IndexedEntity {
    // Spotlight search automatically supported
}

Background vs Foreground Intents

Pattern: Paired Intents with opensIntent
// Background intent - runs without opening app
struct CreateEventIntent: AppIntent {
    static var openAppWhenRun: Bool = false

    @Parameter(title: "Title")
    var title: String

    @Parameter(title: "Start Date")
    var startDate: Date

    func perform() async throws -> some IntentResult {
        let event = try await EventService.shared.createEvent(
            title: title,
            startDate: startDate
        )

        // Optionally open app to view created event
        return .result(
            value: EventEntity(from: event),
            opensIntent: OpenEventIntent(event: EventEntity(from: event))
        )
    }
}

// Foreground intent - opens app to specific event
struct OpenEventIntent: AppIntent {
    static var openAppWhenRun: Bool = true

    @Parameter(title: "Event")
    var event: EventEntity

    func perform() async throws -> some IntentResult {
        await MainActor.run {
            EventCoordinator.shared.showEvent(id: event.id)
        }
        return .result()
    }
}
User experience
  1. User runs "Create Event" in Spotlight (background)
  2. Event created without opening app
  3. Spotlight shows "Open in App" button (opensIntent)
  4. User taps button → App opens to event detail

Predictable Intent Protocol

Enable Spotlight suggestions based on usage patterns:

struct OrderCoffeeIntent: AppIntent, PredictableIntent {
    static var title: LocalizedStringResource = "Order Coffee"

    @Parameter(title: "Coffee Type")
    var coffeeType: CoffeeType

    @Parameter(title: "Size")
    var size: CoffeeSize

    func perform() async throws -> some IntentResult {
        // Order logic
        return .result()
    }
}

Spotlight learns when/how user runs this intent and surfaces suggestions proactively.


Automations on Mac

Overview

Personal Automations arrive on macOS (macOS Sequoia+) with Mac-specific triggers:

New Mac Automation Types
  • Folder Automation — Trigger when files added/removed from folder
  • External Drive Automation — Trigger when drive connected/disconnected
  • Time of Day (from iOS)
  • Bluetooth (from iOS)
  • And more...

Example use case Invoice processing shortcut runs automatically every time a new invoice is added to ~/Documents/Invoices folder.

Automatic Availability

As long as your intent is available on macOS, they will also be available to use in Shortcuts to run as a part of Automations on Mac. This includes iOS apps that are installable on macOS.

No additional code required — your existing intents work in automations automatically.

Platform Support

struct ProcessInvoiceIntent: AppIntent {
    static var title: LocalizedStringResource = "Process Invoice"

    // Available on macOS automatically
    // Also works: iOS apps installed on Mac (Catalyst, Mac Catalyst)

    @Parameter(title: "Invoice")
    var invoice: FileEntity

    func perform() async throws -> some IntentResult {
        // Extract data, add to spreadsheet, etc.
        return .result()
    }
}

Additional System Integration Points

With automations, your intents are now accessible from:

  • Siri — Voice commands
  • Shortcuts app — Manual workflows
  • Spotlight — Quick actions
  • Automations — Triggered workflows
  • Action Button — Hardware trigger (Apple Watch Ultra)
  • Control Center — Quick controls
  • Widgets — Interactive elements
  • Live Activities — Dynamic Island

Assistant Schemas (Pre-built Intents)

Apple provides pre-built schemas for common app categories:

Books App Example

import AppIntents
import BooksIntents

struct OpenBookIntent: BooksOpenBookIntent {
    @Parameter(title: "Book")
    var target: BookEntity

    func perform() async throws -> some IntentResult {
        await MainActor.run {
            BookReader.shared.open(book: target)
        }
        return .result()
    }
}

Available Assistant Schemas

  • BooksIntents — Navigate pages, open books, play audiobooks, search
  • BrowserIntents — Bookmark tabs, clear history, manage windows
  • CameraIntents — Capture modes, device switching, start/stop
  • EmailIntents — Draft management, reply, forward, archive
  • PhotosIntents — Album/asset management, editing, filtering
  • PresentationsIntents — Slide creation, media insertion, playback
  • SpreadsheetsIntents — Sheet management, content addition
  • DocumentsIntents — File management, page manipulation, search

Testing & Debugging

Testing with Shortcuts App

  1. Add intent to Shortcuts:

- Open Shortcuts app - Tap "+" to create new shortcut - Search for your app name - Select your intent

  1. Test parameter resolution:

- Fill in parameters - Run shortcut - Check Xcode console for logs

  1. Test with Siri:

- "Hey Siri, [your intent name]" - Siri should prompt for parameters - Verify dialog text and results

Xcode Intent Testing

// In your app target, not tests
#if DEBUG
extension OrderSoupIntent {
    static func testIntent() async throws {
        let intent = OrderSoupIntent()
        intent.soup = SoupEntity(id: "1", name: "Tomato", price: 8.99)
        intent.quantity = 2

        let result = try await intent.perform()
        print("Result: \(result)")
    }
}
#endif

Common Debugging Issues

Issue 1: Intent not appearing in Shortcuts
// ❌ Problem: isDiscoverable = false or missing
struct MyIntent: AppIntent {
    // Missing isDiscoverable
}

// ✅ Solution: Make discoverable
struct MyIntent: AppIntent {
    static var isDiscoverable: Bool = true
}
Issue 2: Parameter not resolving
// ❌ Problem: Missing defaultQuery
struct ProductEntity: AppEntity {
    var id: String
    // Missing defaultQuery
}

// ✅ Solution: Add query
struct ProductEntity: AppEntity {
    var id: String
    static var defaultQuery = ProductQuery()
}
Issue 3: Intent crashes in background
// ❌ Problem: Accessing MainActor from background
func perform() async throws -> some IntentResult {
    UIApplication.shared.open(url) // Crash! MainActor only
    return .result()
}

// ✅ Solution: Use MainActor or openAppWhenRun
func perform() async throws -> some IntentResult {
    await MainActor.run {
        UIApplication.shared.open(url)
    }
    return .result()
}
Issue 4: Entity query returns empty results
// ❌ Problem: entities(for:) not implemented
struct BookQuery: EntityQuery {
    // Missing entities(for:) implementation
}

// ✅ Solution: Implement required methods
struct BookQuery: EntityQuery {
    func entities(for identifiers: [UUID]) async throws -> [BookEntity] {
        return try await BookService.shared.fetchBooks(ids: identifiers)
    }

    func suggestedEntities() async throws -> [BookEntity] {
        return try await BookService.shared.recentBooks(limit: 10)
    }
}

Best Practices

1. Intent Naming

❌ DON'T: Generic or unclear
static var title: LocalizedStringResource = "Do Thing"
static var title: LocalizedStringResource = "Process"
✅ DO: Verb-noun, specific
static var title: LocalizedStringResource = "Send Message"
static var title: LocalizedStringResource = "Book Appointment"
static var title: LocalizedStringResource = "Start Workout"

2. Parameter Summary

❌ DON'T: Technical or confusing
static var parameterSummary: some ParameterSummary {
    Summary("Execute \(\.$action) with \(\.$target)")
}
✅ DO: Natural language
static var parameterSummary: some ParameterSummary {
    Summary("Send \(\.$message) to \(\.$contact)")
}
// Siri: "Send 'Hello' to John"

3. Error Messages

❌ DON'T: Technical jargon
throw MyError.validationFailed("Invalid parameter state")
✅ DO: User-friendly
throw MyError.outOfStock("Sorry, this item is currently unavailable")

4. Entity Suggestions

❌ DON'T: Return all entities
func suggestedEntities() async throws -> [TaskEntity] {
    return try await TaskService.shared.allTasks() // Could be thousands!
}
✅ DO: Limit to recent/relevant
func suggestedEntities() async throws -> [TaskEntity] {
    return try await TaskService.shared.recentTasks(limit: 10)
}

5. Async Operations

❌ DON'T: Block main thread
func perform() async throws -> some IntentResult {
    let data = URLSession.shared.synchronousDataTask(url) // Blocks!
    return .result()
}
✅ DO: Use async/await
func perform() async throws -> some IntentResult {
    let data = try await URLSession.shared.data(from: url)
    return .result()
}

Real-World Examples

Example 1: Start Workout Intent

struct StartWorkoutIntent: AppIntent {
    static var title: LocalizedStringResource = "Start Workout"
    static var description: IntentDescription = "Starts a new workout session"
    static var openAppWhenRun: Bool = true

    @Parameter(title: "Workout Type")
    var workoutType: WorkoutType

    @Parameter(title: "Duration (minutes)")
    var duration: Int?

    static var parameterSummary: some ParameterSummary {
        Summary("Start \(\.$workoutType)") {
            \.$duration
        }
    }

    func perform() async throws -> some IntentResult {
        let workout = Workout(
            type: workoutType,
            duration: duration.map { TimeInterval($0 * 60) }
        )

        await MainActor.run {
            WorkoutCoordinator.shared.start(workout)
        }

        return .result(
            dialog: "Starting \(workoutType.displayName) workout"
        )
    }
}

enum WorkoutType: String, AppEnum {
    case running
    case cycling
    case swimming
    case yoga

    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Workout Type"
    static var caseDisplayRepresentations: [WorkoutType: DisplayRepresentation] = [
        .running: "Running",
        .cycling: "Cycling",
        .swimming: "Swimming",
        .yoga: "Yoga"
    ]

    var displayName: String {
        switch self {
        case .running: return "running"
        case .cycling: return "cycling"
        case .swimming: return "swimming"
        case .yoga: return "yoga"
        }
    }
}

Example 2: Add Task with Entity Query

struct AddTaskIntent: AppIntent {
    static var title: LocalizedStringResource = "Add Task"
    static var description: IntentDescription = "Creates a new task"
    static var isDiscoverable: Bool = true

    @Parameter(title: "Title")
    var title: String

    @Parameter(title: "List")
    var list: TaskListEntity?

    @Parameter(title: "Due Date")
    var dueDate: Date?

    static var parameterSummary: some ParameterSummary {
        Summary("Add '\(\.$title)'") {
            \.$list
            \.$dueDate
        }
    }

    func perform() async throws -> some IntentResult {
        let task = try await TaskService.shared.createTask(
            title: title,
            list: list?.id,
            dueDate: dueDate
        )

        return .result(
            value: TaskEntity(from: task),
            dialog: "Task '\(title)' added"
        )
    }
}

struct TaskListEntity: AppEntity {
    var id: UUID
    var name: String
    var color: String

    static var typeDisplayRepresentation: TypeDisplayRepresentation = "List"

    var displayRepresentation: DisplayRepresentation {
        DisplayRepresentation(
            title: "\(name)",
            image: .init(systemName: "list.bullet")
        )
    }

    static var defaultQuery = TaskListQuery()
}

struct TaskListQuery: EntityQuery, EntityStringQuery {
    func entities(for identifiers: [UUID]) async throws -> [TaskListEntity] {
        return try await TaskService.shared.fetchLists(ids: identifiers)
    }

    func suggestedEntities() async throws -> [TaskListEntity] {
        // Provide user's favorite lists
        return try await TaskService.shared.favoriteLists(limit: 5)
    }

    func entities(matching string: String) async throws -> [TaskListEntity] {
        return try await TaskService.shared.searchLists(query: string)
    }
}

App Intents Checklist

Before Submitting to App Store

  • ☐ All intents have clear, localized titles and descriptions
  • ☐ Parameter summaries use natural language phrasing
  • ☐ Error messages are user-friendly, not technical
  • ☐ Authentication policies match data sensitivity
  • ☐ Entity queries return reasonable suggestion counts (< 20)
  • ☐ Intents marked isDiscoverable appear in Shortcuts
  • ☐ Destructive actions request confirmation
  • ☐ Background intents don't access MainActor
  • ☐ Foreground intents set openAppWhenRun = true
  • ☐ Entity displayRepresentation shows meaningful info
  • ☐ Tested with Siri voice commands
  • ☐ Tested in Shortcuts app
  • ☐ Tested with different parameter combinations
  • ☐ Verified localization for all supported languages

Resources

WWDC: 2025-244, 2025-275, 2025-260

Docs: /appintents, /appintents/appintent, /appintents/appentity, /Updates/AppIntents

Skills: axiom-app-shortcuts-ref, axiom-core-spotlight-ref, axiom-app-discoverability


Remember App Intents are how users interact with your app through Siri, Shortcuts, and system features. Well-designed intents feel like a natural extension of your app's functionality and provide value across Apple's ecosystem.

平台分布

Claude Code

27.2%
按下载量换算403

Codex

25.83%
按下载量换算382

OpenCode

18.52%
按下载量换算274

Antigravity

13.95%
按下载量换算206

Cursor

7.41%
按下载量换算110

Gemini CLI

3.72%
按下载量换算55

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

可疑

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在提醒或未通过项,安装前请自行复核。

继续浏览同类 Skills