Build Apple Shortcuts in Swift.
Result-builder DSL · signed .shortcut output · YAML & JSON frontends · live .shortcut decompiler.
Apple Shortcuts as code. A Swift package + CLI that compiles a typed DSL down to the same signed
.shortcutfile 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.
- Why
- Features
- Install
- Quick start
- Output chaining & string interpolation
- Control flow
- Declarative frontends — YAML & JSON
- Action discovery
- Decompiler
- On-device validation
- CLI
@Actionmacro- Architecture
- Requirements
- Documentation
- Contributing
- License
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.
| 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. |
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
]),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 --helpFull walkthrough: Getting Started.
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
#endifActions 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.
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.
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.shortcutSame 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.
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.swiftEverything runs locally. Re-run after macOS updates. See Discovery.
shortcuts-cli decompile MyShortcut.shortcut --output MyShortcut.swiftConverts 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.
shortcuts-cli validateCompiles + 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
#endifshortcuts-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.
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.
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
- 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
Full docs live in the docs folder:
- Getting Started — install, first shortcut, signing
- The DSL — every action, every Value form
- Control Flow — If/Else, Repeat, ForEach
- Interpolation & Outputs — magic-variable mechanics
- Declarative Frontends — YAML + JSON schema
- Discovery — extract the live action catalog
- Decompiler —
.shortcut-> Swift source - On-Device Validation — prove a shortcut runs
- CLI Reference — every subcommand
@ActionMacro — declare new typed actions
- .shortcut File Format — reverse-engineered WFWorkflow plist spec
- Parameter Typing Contract — slot rules for every action
- Diagnostics & Errors — error codes and how to react
- Changelog — what shipped in each release
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.
Apache-2.0. See LICENSE.