Skip to content

About

Build Apple Shortcuts in Swift. Result-builder DSL that compiles to signed .shortcut files for iPhone, iPad, Mac, and Apple Watch. YAML + JSON frontends, decompiler, on-device validator, live action discovery, and an @action macro.

Topics

Resources

Stars

15 stars

Watchers

0 watching

Forks

Repository files navigation

clean-shortcuts

Build Apple Shortcuts in Swift.
Result-builder DSL · signed .shortcut output · YAML & JSON frontends · live .shortcut decompiler.

Swift 6.0+ SwiftPM supported Platforms Apache 2.0 0.1.0


Apple Shortcuts as code. A Swift package + CLI that compiles a typed DSL down to the same signed .shortcut file format Shortcuts.app reads. Import the result on iPhone, iPad, Apple Watch, or Mac. No XML, no drag-and-drop, no external action registry to keep in sync — just Swift.

import Shortcuts

let shortcut = Shortcut(name: "Greeting", color: .purple, icon: .star) {
    let name = Ask("What is your name?", as: "name")
    name
    ShowResult("Hello, \(name.output)!")
}

try shortcut.compile(to: "Greeting.shortcut", sign: true)

That's the whole story: the Swift compiler does parsing and type-checking, the library does Apple's WFWorkflow plist generation, and Apple's own /usr/bin/shortcuts sign does the signing.


Table of contents


Why

Shortcuts.app's drag-and-drop editor is fine for simple flows and brutal for anything you want to version-control, code-review, generate, diff, or share at scale.

clean-shortcuts makes the host language Swift. You get IDE autocomplete, the Swift type-checker, refactoring tools, package management, and a mature compiler toolchain for free. The library is small because it doesn't reinvent any of that — it only does the work Apple doesn't publish: WFWorkflow plist generation, control-flow bracket markers, and signed output.


Features

Result-builder DSL Every action is a typed Swift value. Compile-time errors for missing params, wrong types, dangling references.
String interpolation for outputs "\(action.output)" produces Apple's WFTextTokenString with attachmentsByRange markers in UTF-16 offsets — exactly what Shortcuts.app writes.
Control flow If / Else, Repeat, ForEach with matching GroupingIdentifier bracket markers. Nests freely.
14 typed actions Text, Ask, ShowResult, SpeakText, Notification, GetClipboard, SetClipboard, GetCurrentDate, GetBattery, GetUpcomingEvents, GetDictionaryValue, DownloadURL (all HTTP methods + JSON/form/raw bodies), UseModel (Apple Intelligence), Comment.
ExternalAction escape hatch Any third-party app intent or untyped built-in by identifier + parameter dict.
@Action macro Declare new typed actions with one attribute. The macro generates identifier, outputUUID, and parameters().
YAML + JSON frontends Author shortcuts declaratively when Swift is overkill. Both lower to the same DSL.
Decompiler .shortcut / .wflow → readable Swift source. Full control-flow reconstruction.
Action catalog discovery Extracts the live action set from WorkflowKit.framework binary + Gallery.bundle + your installed shortcuts. No stale bundled lists.
On-device validation runOnDevice() compiles, installs, runs, and captures output via the live Shortcuts.app runtime. The only signal that proves a shortcut actually executes.
Signing Uses Apple's /usr/bin/shortcuts sign. Output imports on iOS 15+ without warnings.
Cross-platform library macOS 13+, iOS 16+, tvOS 16+, watchOS 9+. Signing requires a macOS build host.
Sendable by default Strict concurrency mode. Every public type is Sendable.
71 tests Snapshot, property, determinism, decompiler round-trip, macro expansion, per-action typing, plus a gated E2E suite.

Install

Swift Package Manager

In your Package.swift:

.package(url: "https://github.com/damionrashford/clean-shortcuts.git", from: "0.1.0"),

Link the products you need:

.target(name: "MyTarget", dependencies: [
    .product(name: "Shortcuts",     package: "clean-shortcuts"),
    .product(name: "ShortcutsJSON", package: "clean-shortcuts"),    // optional
    .product(name: "ShortcutsYAML", package: "clean-shortcuts"),    // optional
]),

CLI

git clone https://github.com/damionrashford/clean-shortcuts.git
cd clean-shortcuts
swift build -c release
ln -s "$PWD/.build/release/shortcuts-cli" /usr/local/bin/shortcuts-cli
shortcuts-cli --help

Full walkthrough: Getting Started.


Quick start

import Shortcuts

let s = Shortcut(name: "Battery Check", color: .green, icon: .bolt) {
    let level = GetBattery()
    level
    Notification(body: "Battery is at \(level.output)%")
}

try s.compile(to: "BatteryCheck.shortcut", sign: true)

Double-click the resulting .shortcut file (or AirDrop it to your iPhone), confirm import in Shortcuts.app, and run it.

For one-call install on the same Mac, see Shortcut.install(replace:):

#if os(macOS)
let url = try s.install(replace: true)   // signs + imports into Shortcuts.app
#endif

Output chaining & string interpolation

Actions that produce data return a typed Output. Downstream actions consume it directly or via string interpolation:

let q = Ask("Question?", as: "question")
let answer = UseModel(prompt: "Reflect on: \(q.output)", as: "reflection")
ShowResult(answer.output.value)

\(q.output) inside a Value produces the correct WFTextTokenString plist with attachmentsByRange markers in UTF-16 offsets. See Interpolation & Outputs.


Control flow

Shortcut(name: "Loop", color: .blue, icon: .gear) {
    Repeat(3) {
        Notification(body: "ping")
    }

    If(.literal("yes"), .equals, .literal("yes")) {
        SpeakText(.literal("matched"))
    } else: {
        SpeakText(.literal("no match"))
    }
}

The library emits Apple's is.workflow.actions.conditional and is.workflow.actions.repeat.count bracket actions with matching GroupingIdentifier UUIDs. See Control Flow.


Declarative frontends — YAML & JSON

name: "Battery Check"
color: green
icon: bolt
surfaces: [widget, watchKit]
actions:
  - battery:
      as: level
  - notification:
      body: "Battery is at {{level}}%"
shortcuts-cli yaml BatteryCheck.yaml --output BatteryCheck.shortcut

Same schema in JSON:

{
  "name": "Battery Check",
  "color": "green",
  "icon": "bolt",
  "actions": [
    { "battery": { "as": "level" } },
    { "notification": { "body": "Battery is at {{level}}%" } }
  ]
}
shortcuts-cli json BatteryCheck.json --output BatteryCheck.shortcut

{{name}} resolves to a typed output reference. Both frontends lower to the same DocumentBuilder in the core library. See Declarative Frontends.


Action discovery

Apple does not publish the action catalog. It varies across macOS versions and includes third-party app intents that differ per machine. shortcuts-cli discover reads three on-device sources and emits a catalog matching your exact OS state:

Source Provides
WorkflowKit.framework binary (read via dyld_info) every is.workflow.actions.* identifier the system knows
WorkflowKit.framework/.../Gallery.bundle/*.wflow parameter shapes from Apple's featured shortcuts
~/Library/Shortcuts/Shortcuts.sqlite actions used by shortcuts you've installed
shortcuts-cli discover --emit json --output catalog.json
shortcuts-cli discover --emit swift --output Actions/Generated.swift

Everything runs locally. Re-run after macOS updates. See Discovery.


Decompiler

shortcuts-cli decompile MyShortcut.shortcut --output MyShortcut.swift

Converts an unsigned .shortcut or .wflow file back to readable Swift source. Reconstructs If / Repeat / ForEach blocks from the flat bracket-marker representation. Unknown identifiers become ExternalAction(...). See Decompiler.


On-device validation

shortcuts-cli validate

Compiles + signs + installs + runs a known shortcut, then verifies its captured output. The only signal that proves a compiled shortcut actually executes in Shortcuts.app. See On-Device Validation.

#if os(macOS)
let result = try s.runOnDevice(keepAfterRun: false)
print(result.output)   // captured stdout from the live runtime
#endif

CLI

shortcuts-cli demo                  hello-world compile (smoke test)
shortcuts-cli webhook               chained-action reference example
shortcuts-cli info FILE             pretty-print an unsigned .shortcut
shortcuts-cli sign FILE             sign via /usr/bin/shortcuts
shortcuts-cli decompile FILE        .shortcut/.wflow -> Swift source
shortcuts-cli diff A B              semantic diff (UUID-stable)
shortcuts-cli lint FILE             dangling refs, mismatched brackets
shortcuts-cli yaml FILE             compile a YAML shortcut
shortcuts-cli json FILE             compile a JSON shortcut
shortcuts-cli watch FILE            recompile on save (.swift / .yaml / .json)
shortcuts-cli discover              extract the action catalog
shortcuts-cli init NAME             scaffold a new shortcut project
shortcuts-cli validate              end-to-end: compile -> install -> run -> verify
shortcuts-cli harvest-glyphs        extract real (name, glyph, color) triples

shortcuts-cli <command> --help for full flags. Full reference: CLI Reference.


@Action macro

Declare a custom typed action with one attribute:

import Shortcuts
import Foundation

@Action("is.workflow.actions.gettext")
public struct MyText {
    public let WFTextActionText: Value
}

Expands to full Action conformance with identifier, outputUUID, and parameters() synthesized. See the @Action macro guide.


Architecture

clean-shortcuts/
├── Sources/
│   ├── Shortcuts/                  core library
│   │   ├── Actions/                one file per typed action
│   │   ├── Internal/               private helpers (HexDecoding, etc.)
│   │   ├── Shortcut.swift          top-level container + @ActionBuilder
│   │   ├── Action.swift            Action protocol + default extensions
│   │   ├── Value.swift             ExpressibleByStringInterpolation
│   │   ├── Encoding.swift          encodePlistValue / encodePlistDictionary
│   │   ├── Metadata.swift          ShortcutColor / ShortcutIcon / ShortcutSurface
│   │   ├── Compiler.swift          buildPlist + compile(to:sign:)
│   │   ├── Installation.swift      install() / airdrop() (macOS)
│   │   ├── Validation.swift        runOnDevice() (macOS)
│   │   ├── Discovery.swift         WorkflowKit + Gallery + Shortcuts.sqlite scan
│   │   ├── Decompiler.swift        .shortcut -> Swift source
│   │   ├── EmpiricalGlyphs.swift   harvest (name, glyph) from installed shortcuts
│   │   ├── DocumentBuilder.swift   shared YAML/JSON action builder
│   │   ├── Diagnostics.swift       Diagnostic struct + Code enum
│   │   ├── Macros.swift            @Action macro declaration
│   │   └── Version.swift
│   ├── ShortcutsMacros/            @Action macro implementation (compiler plugin)
│   ├── ShortcutsJSON/              declarative JSON frontend (Foundation only)
│   ├── ShortcutsYAML/              declarative YAML frontend (uses Yams)
│   └── ShortcutsCLI/               shortcuts-cli binary
├── Tests/ShortcutsTests/           71 tests across 12 suites
├── Documentation/                  full doc set — see below
├── .github/workflows/test.yml      CI: swift test --parallel + CLI smoke test
├── AGENTS.md                       contributing + AI-assistant rules
├── CLAUDE.md                       symlink -> AGENTS.md
├── LICENSE                         Apache-2.0
├── Package.swift
└── README.md

Requirements

  • Library: Swift 6.0+, macOS 13+, iOS 16+, tvOS 16+, watchOS 9+
  • CLI: macOS 12+ (signing requires /usr/bin/shortcuts)
  • Build host for signed output: macOS

Documentation

Full docs live in the docs folder:

Guides

Reference


Contributing

See AGENTS.md — contributor guide for both humans and AI coding assistants (Claude Code, Codex, Cursor, Copilot CLI). CLAUDE.md is a symlink to the same file.


License

Apache-2.0. See LICENSE.

About

Build Apple Shortcuts in Swift. Result-builder DSL that compiles to signed .shortcut files for iPhone, iPad, Mac, and Apple Watch. YAML + JSON frontends, decompiler, on-device validator, live action discovery, and an @action macro.

Topics

Resources

Stars

15 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages