Get the FREE Ultimate OpenClaw Setup Guide →

ax-app-intents

npx machina-cli add skill Kasempiternal/axiom-v2/ax-app-intents --openclaw
Files (1)
SKILL.md
13.9 KB

App Intents & App Shortcuts

Expose app functionality to Siri, Apple Intelligence, Shortcuts, Spotlight, Action Button, Control Center, widgets, and Live Activities. Covers AppIntent, AppEntity, AppEnum, AppShortcutsProvider, parameterized phrases, IndexedEntity, assistant schemas, and discovery UI.

Quick Patterns

AppIntent — Define an Action

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

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

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

    static var parameterSummary: some ParameterSummary {
        Summary("Order \(\.$quantity) \(\.$soup)")
    }

    func perform() async throws -> some IntentResult {
        guard let quantity, quantity > 0 else {
            throw $quantity.needsValue("How many soups?")
        }
        try await OrderService.shared.order(soup: soup, quantity: quantity)
        return .result(dialog: "Ordered \(quantity) \(soup.name)")
    }
}

AppEntity — Represent App Content

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

    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Soup"
    static var defaultQuery = SoupQuery()

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

struct SoupQuery: EntityQuery {
    func entities(for identifiers: [String]) async throws -> [SoupEntity] {
        try await SoupService.shared.fetch(ids: identifiers)
    }
    func suggestedEntities() async throws -> [SoupEntity] {
        try await SoupService.shared.popular(limit: 10)
    }
}

AppEnum — Enumeration Parameters

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

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

AppShortcutsProvider — Instant Discovery

struct MyAppShortcuts: AppShortcutsProvider {
    static var shortcutTileColor: ShortcutTileColor = .teal

    @AppShortcutsBuilder
    static var appShortcuts: [AppShortcut] {
        // Generic (asks for params)
        AppShortcut(
            intent: OrderSoupIntent(),
            phrases: ["Order soup in \(.applicationName)"],
            shortTitle: "Order Soup",
            systemImageName: "cup.and.saucer.fill"
        )
        // Parameterized (skips clarification)
        AppShortcut(
            intent: OrderSoupIntent(soup: .tomato, quantity: 1),
            phrases: ["Order tomato soup in \(.applicationName)"],
            shortTitle: "Tomato Soup",
            systemImageName: "flame.fill"
        )
    }

    // iOS 17+ — prevent false triggers
    static var negativePhrases: [NegativeAppShortcutPhrase] {
        NegativeAppShortcutPhrases {
            "Cancel soup order"
            "Stop ordering"
        }
    }
}

Entity String Search

extension SoupQuery: EntityStringQuery {
    func entities(matching string: String) async throws -> [SoupEntity] {
        try await SoupService.shared.search(query: string)
    }
}

Confirmation for Destructive Actions

func perform() async throws -> some IntentResult {
    try await requestConfirmation(
        result: .result(dialog: "Delete '\(task.title)'?"),
        confirmationActionName: .init(stringLiteral: "Delete")
    )
    try await TaskService.shared.delete(task: task)
    return .result(dialog: "Task deleted")
}

Foreground Continuation with opensIntent

struct CreateEventIntent: AppIntent {
    static var openAppWhenRun: Bool = false

    func perform() async throws -> some IntentResult {
        let event = try await EventService.shared.create(title: title)
        return .result(
            value: EventEntity(from: event),
            opensIntent: OpenEventIntent(event: EventEntity(from: 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.show(event.id) }
        return .result()
    }
}

Decision Tree

Starting App Intents integration?
├── Need action available in Siri/Shortcuts?
│   ├── Simple action, no parameters → AppIntent + AppShortcut (basic phrases)
│   ├── Action with entity parameter → AppIntent + AppEntity + EntityQuery
│   ├── Action with enum parameter → AppIntent + AppEnum
│   └── Common combos users repeat → Parameterized AppShortcut (skip clarification)
├── Need content searchable in Spotlight/Shortcuts?
│   ├── Small bounded list → EnumerableEntityQuery (allEntities)
│   ├── Large/unbounded list → EntityQuery + suggestedEntities + EntityStringQuery
│   └── Already using Spotlight → IndexedEntity (auto-generates Find action)
├── Need instant discovery (no user setup)?
│   └── AppShortcutsProvider with 3-5 core shortcuts
├── Need Apple Intelligence model integration?
│   └── Expose @Property on entities + use AttributedString for rich text
├── Need assistant schema (books/email/photos)?
│   └── Adopt pre-built schema protocol (BooksOpenBookIntent, etc.)
├── Need to prevent false Siri triggers?
│   └── NegativeAppShortcutPhrase (iOS 17+)
├── Need to promote shortcuts in UI?
│   ├── After action completion → SiriTipView
│   └── Settings/help screen → ShortcutsLink
└── Running on macOS?
    ├── Spotlight shows intents if all required params in parameterSummary
    └── Automations work automatically (no extra code)

Anti-Patterns

Coupling models to AppEntity

// BAD: Core model conforms to AppEntity
struct Book: AppEntity { ... }

// GOOD: Separate entity wrapping model
struct BookEntity: AppEntity {
    init(from book: Book) { self.id = book.id; self.title = book.title }
}

Returning all entities as suggestions

// BAD: Could be thousands
func suggestedEntities() async throws -> [TaskEntity] {
    try await TaskService.shared.allTasks()
}

// GOOD: Recent/relevant subset
func suggestedEntities() async throws -> [TaskEntity] {
    try await TaskService.shared.recentTasks(limit: 10)
}

Using String instead of AttributedString for model output

// BAD: Loses rich text from Apple Intelligence
@Parameter(title: "Content") var content: String

// GOOD: Preserves bold, lists, tables
@Parameter(title: "Content") var content: AttributedString

Too many App Shortcuts

// BAD: Combinatorial explosion
for size in CoffeeSize.allCases {
    for type in CoffeeType.allCases {
        AppShortcut(intent: OrderIntent(type: type, size: size), ...)
    }
}

// GOOD: Generic + 2-3 common parameterized cases
AppShortcut(intent: OrderIntent(), ...)           // Generic
AppShortcut(intent: OrderIntent(type: .latte), ...)  // Common case

Accessing MainActor from background intent

// BAD: Crash when openAppWhenRun = false
func perform() async throws -> some IntentResult {
    UIApplication.shared.open(url) // MainActor only!
}

// GOOD: Wrap in MainActor or use opensIntent
func perform() async throws -> some IntentResult {
    await MainActor.run { UIApplication.shared.open(url) }
    return .result()
}

Missing parameterSummary required params (macOS Spotlight)

// BAD: Won't appear in Spotlight — notes is required but not in summary
@Parameter(title: "Notes") var notes: String
static var parameterSummary: some ParameterSummary {
    Summary("Create '\(\.$title)'")  // Missing notes!
}

// GOOD: Make optional, provide default, or include in summary
@Parameter(title: "Notes") var notes: String?  // Option 1: optional
@Parameter(title: "Notes") var notes: String = ""  // Option 2: default

Long complex Siri phrases

// BAD: Too wordy, users won't remember
"I would like to order a coffee from \(.applicationName) please"

// GOOD: 3-6 words, verb-first
"Order coffee in \(.applicationName)"

Deep Patterns

IndexedEntity — Auto-Generated Find Actions

Adopt IndexedEntity to auto-generate Find actions from Spotlight integration. Maps @Property to Spotlight attributes via indexing keys.

struct EventEntity: AppEntity, IndexedEntity {
    var id: UUID

    @Property(title: "Title", indexingKey: \.eventTitle)
    var title: String

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

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

    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Event"

    var displayRepresentation: DisplayRepresentation {
        DisplayRepresentation(title: "\(title)", subtitle: "\(startDate.formatted())")
    }
}
// Users get: "Find Events where Title contains 'Team' and Start Date is today"

Standard indexing keys: \.eventTitle, \.startDate, \.endDate, \.eventLocation Custom keys: customIndexingKey: "myCustomAttribute" for non-standard attributes

Authentication Policies

static var authenticationPolicy: IntentAuthenticationPolicy = .alwaysAllowed           // Public info
static var authenticationPolicy: IntentAuthenticationPolicy = .requiresAuthentication   // Logged-in user
static var authenticationPolicy: IntentAuthenticationPolicy = .requiresLocalDeviceAuthentication  // Face ID/Touch ID

Entity Property Exposure for Apple Intelligence

Models receive JSON of exposed @Property values + displayRepresentation + typeDisplayRepresentation. Expose all properties you want the model to reason over.

Output types from Use Model action: Text (AttributedString), Number, Boolean, Dictionary, Date, App Entities. The runtime auto-converts types when connected to If/other actions.

Assistant Schemas

Pre-built intent protocols for common app categories:

SchemaUse Cases
BooksIntentsNavigate pages, open books, play audiobooks
BrowserIntentsBookmarks, history, window management
CameraIntentsCapture modes, device switching
EmailIntentsDraft, reply, forward, archive
PhotosIntentsAlbum/asset management, editing
PresentationsIntentsSlide creation, media, playback
SpreadsheetsIntentsSheet management, content
DocumentsIntentsFile management, page manipulation
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()
    }
}

PredictableIntent — Spotlight Suggestions

struct OrderCoffeeIntent: AppIntent, PredictableIntent {
    // Spotlight learns usage patterns and surfaces suggestions proactively
}

Dynamic Shortcut Updates

func markAsFavorite(_ session: Session) {
    favoriteSessions.append(session)
    MeditationAppShortcuts.updateAppShortcutParameters()  // Refresh stored shortcuts
}

Discovery UI Components

SiriTipView — show after user completes an action:

SiriTipView(intent: ReorderIntent(), isVisible: $showTip)
    .siriTipViewStyle(.dark)

ShortcutsLink — link to Shortcuts app from settings:

ShortcutsLink()  // Opens app's page in Shortcuts app

ShortcutTileColor options: .blue, .grape, .grayBlue, .grayBrown, .grayGreen, .lightBlue, .lime, .navy, .orange, .pink, .purple, .red, .tangerine, .teal, .yellow

Error Handling

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

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

Diagnostics

SymptomLikely CauseFix
Intent not in Shortcuts appisDiscoverable = false or missingSet isDiscoverable = true (default)
Parameter won't resolveMissing defaultQuery on entityAdd static var defaultQuery = MyQuery()
Intent crashes in backgroundAccessing MainActor without isolationWrap in await MainActor.run {} or set openAppWhenRun = true
Entity query returns emptyentities(for:) not implementedImplement required EntityQuery methods
Shortcut not in SpotlightMissing from AppShortcutsProviderAdd AppShortcut entry with phrases
Siri doesn't recognize phraseMissing \(.applicationName) in phraseInclude app name interpolation
Siri triggers wrong appAmbiguous phrasesAdd NegativeAppShortcutPhrase (iOS 17+)
Not visible in macOS SpotlightRequired param missing from parameterSummaryMake optional, add default, or include in summary
App Shortcuts not updatingData changed but shortcuts staleCall updateAppShortcutParameters()
SiriTipView shows emptyIntent not in AppShortcutsProviderAdd intent to appShortcuts array

Related

  • ax-widgets — WidgetKit interactive widgets, Live Activities
  • ax-app-intents (this skill) covers both App Intents and App Shortcuts
  • ax-privacy — Privacy manifests, deep link debugging, Spotlight indexing
  • ax-swiftui — SwiftUI views for SiriTipView/ShortcutsLink integration

Source

git clone https://github.com/Kasempiternal/axiom-v2/blob/main/axiom-plugin/skills/ax-app-intents/SKILL.mdView on GitHub

Overview

Expose app actions to Siri, Shortcuts, Spotlight, Action Button, and widgets using AppIntent, AppEntity, AppEnum, and AppShortcutsProvider. It supports parameterized phrases, IndexedEntity, and discovery UI for fast user access.

How This Skill Works

Define an AppIntent with @Parameter properties and a parameterSummary to describe the action, then implement perform to return a result. Model your domain with AppEntity and AppEnum types, and use AppShortcutsProvider to publish generic and parameterized shortcuts for instant discovery.

When to Use It

  • Expose core app actions to Siri and Shortcuts with parameterized phrases
  • Model app content with AppEntity and provide queries for suggestions
  • Offer instant discovery with AppShortcutsProvider including both generic and parameterized shortcuts
  • Define constrained options with AppEnum for clear user selections
  • Support entity search and safe actions with entity queries and destructive action confirmations

Quick Start

  1. Step 1: Define an AppIntent with parameters and a summary
  2. Step 2: Create AppEntity and AppEnum types to model your data and options
  3. Step 3: Implement AppShortcutsProvider to publish generic and parameterized shortcuts and add negative phrases

Best Practices

  • Model your domain with AppEntity and AppQuery to enable discovery
  • Use parameterized AppIntent and a clear parameterSummary to minimize clarifications
  • Provide both generic and parameterized shortcuts in AppShortcutsProvider
  • Define AppEnum with typeDisplayRepresentation and caseDisplayRepresentations for clarity
  • Add confirmation flows using requestConfirmation and negative phrases to avoid false triggers

Example Use Cases

  • OrderSoupIntent demonstrates parameterized AppIntent with soup and quantity
  • SoupEntity struct models items with id, name and price and a displayRepresentation
  • SoupSize AppEnum defines small, medium and large with distinct display labels
  • MyAppShortcuts shows generic and parameterized shortcuts including a tomato soup example
  • SoupQuery implements EntityStringQuery for searching soups

Frequently Asked Questions

Add this skill to your agents
Sponsor this space

Reach thousands of developers