Imported from rohanvinaik/ShortcutForge (
docs/SKILL.md). Install upstream withnpx skills add rohanvinaik/ShortcutForge --skill docs. Copyright stays with the author.
Apple Shortcuts Compiler
You generate Apple Shortcuts by writing Python scripts that use the shortcuts_compiler library. The compiler handles ALL plist mechanics — UUIDs, variable wiring, string interpolation positions, conditional wrapping, and serialization. You never touch raw plist.
Quick Start
from shortcuts_compiler import Shortcut, actions
s = Shortcut("My Shortcut")
s.add(actions.make("comment", WFCommentActionText="Hello from the compiler"))
s.add(actions.make("notification", WFNotificationActionTitle="It works!", WFNotificationActionBody="Shortcut generated by AI"))
s.save("my_shortcut.shortcut")
Core Concepts
1. actions.make() — The Universal Action Builder
Every action uses actions.make(name, **params). The first argument is the action name — it accepts short names, dotted names, or full identifiers. Parameters use Apple's WF* names.
# Short name
s.add(actions.make("comment", WFCommentActionText="Hello"))
# Dotted short name
s.add(actions.make("text.split", WFTextSeparator="Custom", WFTextCustomSeparator=","))
# Full identifier (third-party actions)
s.add(actions.make("com.apple.Pages.TSADocumentCreateIntent"))
You MUST look up parameter names before using any action. Call actions.info("name") to get the catalog entry with verified parameter names, or consult references/action_catalog.json directly. Never rely on memory or guessing — parameter names are symbolic identifiers that must be exact.
2. ActionHandles
Every s.add() returns an ActionHandle you can use to wire outputs downstream:
data = s.add(actions.make("detect.dictionary"))
value = s.add(actions.make("getvalueforkey", WFDictionaryKey="myKey", WFInput=data))
s.add(actions.make("showresult", Text=value.in_string("Result: \ufffc")))
ActionHandle values passed as parameters are auto-wrapped — no need to call .as_input() manually.
3. Variables
For values needed in multiple places, use Set/Get Variable:
s.add(actions.make("setvariable", WFVariableName="MyData", WFInput=data))
val = s.add(actions.make("getvariable", WFVariable=ref_variable("MyData")))
4. Control Flow
Use Python context managers — the compiler handles GroupingIdentifiers:
with s.if_block(value, condition="has_any_value"):
s.add(actions.make("notification", WFNotificationActionTitle="Has value"))
with s.if_else_block(value, condition="equals_string", compare_value="ok") as otherwise:
s.add(actions.make("notification", WFNotificationActionTitle="OK!"))
otherwise()
s.add(actions.make("notification", WFNotificationActionTitle="Not OK"))
with s.repeat_block(5):
s.add(actions.make("notification", WFNotificationActionTitle="Loop"))
with s.repeat_each_block(list_handle):
s.add(actions.make("notification", WFNotificationActionTitle="Item"))
with s.menu_block("Choose:", ["Option A", "Option B"]) as cases:
cases["Option A"]()
s.add(actions.make("notification", WFNotificationActionTitle="You chose A"))
cases["Option B"]()
s.add(actions.make("notification", WFNotificationActionTitle="You chose B"))
5. String Interpolation
Embed variable refs inside strings using \ufffc as placeholder:
value.in_string("Hello ", "!") # "Hello {value}!"
wrap_token_string("URL: ", ref) # For non-handle refs
wrap_token_string_multi("A: \ufffc B: \ufffc", [ref1, ref2]) # Multiple refs
6. Builder Helpers
For complex parameter structures, use the builder methods:
# Dictionary items
actions.make("dictionary",
WFItems={"Value": {"WFDictionaryFieldValueItems":
actions.build_dict_items({"key": "value", "num": 42})
}})
# List items
actions.make("list", WFItems=actions.build_list(["a", "b", "c"]))
# HTTP headers
actions.make("downloadurl",
WFHTTPMethod="POST",
WFHTTPHeaders=actions.build_headers({"Authorization": "Bearer xyz"}))
# Quantities (durations, measurements)
actions.make("adjustdate",
WFDuration=actions.build_quantity(7, "days"))
# Token strings (text with embedded variables)
actions.make("gettext",
WFTextActionText=actions.build_token_string("Hello world"))
7. Conditions (for if_block / if_else_block)
Valid condition strings (derived from 245 real conditionals): has_any_value (100), does_not_have_any_value (101), equals_number / == (0), is_greater_than / > (2), is_less_than / < (3), equals_string (4), not_equal_string / != (5), contains (99), does_not_contain (999), is_before (1002). These are the ONLY valid condition strings — the integer codes are shown for reference.
Available Actions
All actions in the current catalog snapshot are available via actions.make(). The catalog includes both Apple system actions and third-party app intents. The catalog auto-resolves short names. Use actions.info("name") to look up any action's parameters, or read references/action_catalog.json.
Common categories and their short names:
Data flow: gettext, number, url, downloadurl, detect.dictionary, getvalueforkey, setvariable, getvariable, dictionary, list, getitemfromlist
User interaction: notification, showresult, alert, ask, choosefromlist, speaktext, plus menu_block() context manager
Control: exit, output, runworkflow, delay, nothing
Utility: math, count, format.date, base64encode, urlencode, getclipboard, setclipboard, openurl, openapp, vibrate, setbrightness
Health: loghealthsample (Apple Health / HealthKit)
Files: documentpicker.open, documentpicker.save, file.append, file.delete, file.createfolder, filter.files
Text processing: text.match, text.replace, text.split, text.combine, text.changecase, trimwhitespace
Date/Time: date, adjustdate, format.date, converttimezone
Device/Hardware: getdevicedetails, getipaddress, flashlight, vibrate, setbrightness
Communication: sendmessage, sendemail, selectcontacts, filter.contacts
AI: askllm (Apple Intelligence on-device LLM — iOS 18.1+), or use downloadurl with API headers for cloud AI
Meta: runworkflow, properties.workflow
Math: randomnumber, round, math, measurement.create
Third-party app intents (use full identifier with actions.make()):
- Charty:
com.brogrammers.charty.NewChartIntent,...AddSeriesToChartIntent,...StyleLineSeriesIntent,...StyleAxisIntent,...ExportChartAsImageIntent, etc. - Data Jar:
dk.simonbs.DataJar.GetValueIntent,...SetValueIntent,...DeleteValueIntent,...GetKeysIntent,...CheckIfValueExistsIntent - Console:
com.alexhay.Console.LogMessageIntent - Toolbox Pro:
com.alexhay.ToolboxProForShortcuts.SortListIntent,...CheckGVIntent - Claude:
com.anthropic.claude.ClaudeAppIntentsExtension - OpenAI:
com.openai.chat.AskIntent - Actions (Sindre Sorhus):
com.sindresorhus.Actions.GlobalVariableGetText,...GlobalVariableSetText - a-Shell:
AsheKube.app.a-Shell-mini.GetFileIntent
Workflow
When the user asks for a shortcut, build it and deliver it. Don't ask about signing or import mechanics — deliver() handles everything. The compiler auto-detects macOS and uses the shortcuts CLI for signing and import.
from shortcuts_compiler import Shortcut, actions
s = Shortcut("My Shortcut")
# ... build the shortcut ...
result = s.deliver(output_dir="./output", auto_import=True)
# On macOS: validates → saves → signs → imports into Shortcuts app. Done.
# Off macOS: validates → saves unsigned file → returns signing instructions.
If auto_import fails (Shortcuts app not set up, first-time iCloud sign-in needed, etc.), fall back to open_file=True which triggers the native import dialog with the signed file.
deliver() auto-validates before saving and raises ValueError on structural errors (unclosed control flow, duplicate UUIDs, empty menus) that would crash on import.
Signing Details
Signing is an Apple DRM wrapper around the shortcut plist — it doesn't change the content. Signing is REQUIRED for import. Unsigned .shortcut files cannot be imported on macOS or iOS — the system will reject them with "Importing unsigned shortcut files is not supported." On macOS 12+, deliver() calls shortcuts sign automatically, so this is handled for you. If signing fails, the compiler returns manual signing instructions.
Prerequisite (one-time): Settings → Shortcuts → Private Sharing → ON (both macOS and iOS).
Manual signing (if not using deliver()):
shortcuts sign -m anyone -i MyShortcut.shortcut -o MyShortcut_signed.shortcut
If running in a non-macOS environment (e.g., Linux VM, Docker): deliver() will save the unsigned file and return a result["instructions"] string with the exact shortcuts sign command. Copy the file to a Mac and run the command, or have a macOS-native LLM agent run it.
Setup utilities: generate_setup_script() (one-time Mac environment verification + convenience aliases) and generate_signing_script() (persistent auto-signing watcher folder using fswatch). See scripts/setup_mac.sh and scripts/auto_sign.sh.
Important Rules
- Prefer explicit
setvariableover implicit input chaining when actions aren't adjacent or the flow is complex. Implicit chaining (where an action auto-consumes the previous output) breaks silently if anything is inserted between them. - The compiler handles conditional wrapping automatically. Never manually construct WFInput for conditionals — use
if_block()/if_else_block()context managers. - Test simple first. When using a new action type, generate a minimal shortcut with just that action to verify it works before composing into a larger flow.
- UUIDs are auto-generated. Never hardcode UUIDs. The compiler manages them.
- ALWAYS look up parameters before constructing any action. Call
actions.info("name")or consultreferences/action_catalog.jsonfor every action you use. Never guess parameter names from training data — this is a symbolic tool, not a vibes-based system. Every parameter name must come from the catalog. - For HealthKit logging, use exact type names: "Caffeine", "Vitamin C", "Dietary Energy", etc. Wrong names fail silently.
- Media-specific input keys are NOT generic. Apple uses family-specific parameter names —
WFImagefor image.resize/rotate,WFMusicfor music actions,WFDocumentfor markup,WFInputGIFfor GIF operations — NOT a genericWFInput. Always checkaction_catalog.jsonfor verified keys. - Parameter values can be static OR variable references. A parameter like
WFDelayTimecan be a plain integer (static) or a dict containing a variable reference (dynamic). Both are valid. - File saves default to overwrite. 77% of real shortcuts use
WFSaveFileOverwrite: True. Set this explicitly or users get prompted every run.
Implementation Grammar (Layer 2 Details)
These are the concrete idioms that real-world shortcuts use — the "grammar" of how actions compose. Derived from analysis of 19,265 actions across 1,799 production shortcuts (1,772 Cassinelli library + 27 advanced community shortcuts). Average shortcut has 10.7 actions (range 1–1,253). 61.8% are simple linear sequences (≤5 actions, no control flow); the remaining 38.2% use branching, menus, or loops.
API Call Idiom
Only 1.9% of shortcuts use downloadurl, making API integration a niche but powerful pattern. The standard sequence is url → downloadurl → detect.dictionary → getvalueforkey → conditional. POST calls (16% of API usage) typically include WFHTTPHeaders for auth and WFHTTPBodyType of "JSON", "Form", or "File". Follow with detect.dictionary to parse JSON responses.
api_url = s.add(actions.make("url", WFURLActionURL="https://api.example.com/v1/endpoint"))
result = s.add(actions.make("downloadurl",
WFHTTPMethod="POST",
WFHTTPHeaders=actions.build_headers({"Authorization": "Bearer " + api_key}),
WFHTTPBodyType="JSON",
WFJSONValues={"Value": {"WFDictionaryFieldValueItems":
actions.build_dict_items({"prompt": user_input})}}))
parsed = s.add(actions.make("detect.dictionary"))
value = s.add(actions.make("getvalueforkey", WFDictionaryKey="result", WFInput=parsed))
File I/O Idiom
Only 2.3% of shortcuts use persistent file storage, but those that do follow a consistent pattern. Open with error handling disabled for optional files (first run — 84% of file opens use WFFileErrorIfNotFound: False), always save with overwrite (77% use WFSaveFileOverwrite: True). Use the Shortcuts/ iCloud folder for persistence.
existing = s.add(actions.make("documentpicker.open",
WFGetFilePath="Shortcuts/MyApp/data.json",
WFShowFilePicker=False,
WFFileErrorIfNotFound=False))
# ... modify data ...
s.add(actions.make("documentpicker.save",
WFFileDestinationPath="Shortcuts/MyApp/data.json",
WFSaveFileOverwrite=True,
WFInput=updated_data))
Loop + Accumulate Idiom
repeat.each (4.7%) is far more common than repeat.count (1.1%) — iteration over collections dominates. 40.9% of loops contain conditionals (filter pattern). The standard accumulate: repeat_each over input → conditionally appendvariable → use accumulated list after loop. Average loop body is 15.1 actions. Control flow nesting reaches depth 12 in the most complex shortcuts (avg 0.43 overall; 71.5% of shortcuts have zero nesting).
s.add(actions.make("setvariable", WFVariableName="results"))
with s.repeat_each_block(items):
with s.if_block(repeat_item, condition="contains", compare_value="match"):
s.add(actions.make("appendvariable", WFVariableName="results"))
# results now contains only matching items
Nested Menu Idiom
9.1% of shortcuts use menus (301 total menu blocks, avg 4.3 items). Distribution: 71.1% small (2-4 items), 15.0% medium (5-7), 12.6% large (8+). For feature-rich shortcuts with many options, use nested menus: an outer menu for categories, inner menus for specific features. Include a "Go back ↩️" option in inner menus that self-referentially calls the shortcut itself to return to the main menu.
with s.menu_block("Main Menu", ["Feature A", "Feature B 🔒", "Settings"]) as cases:
cases["Feature A"]()
# ... feature A actions ...
cases["Feature B 🔒"]()
code = s.add(actions.make("ask", WFAskActionPrompt="Enter code:", WFInputType="Number"))
with s.if_else_block(code, condition="equals_number", compare_value=1234) as otherwise:
with s.menu_block("Feature B", ["Sub-option 1", "Sub-option 2", "Go back ↩️"]) as sub:
sub["Sub-option 1"]()
# ...
sub["Go back ↩️"]()
s.add(actions.make("runworkflow", WFWorkflowName="Main Menu"))
otherwise()
s.add(actions.make("notification", WFNotificationActionTitle="Wrong code"))
Menu Dispatcher Idiom
71.1% of menus are small (2-4 items). Build menus from user-facing emoji+text labels. Each branch is self-contained. Only 2.7% of shortcuts qualify as large-menu dispatchers (6+ items).
Input Collection Idiom
12.7% of shortcuts collect user input (404 total ask actions). Input types: Text (71.0%), Number (16.1%), Date/Time (remainder), URL (<2%). 16.1% provide defaults. After ask, 9.2% immediately enter a conditional (validation pattern). Standard flow: ask → conditional (validate) → setvariable (store) or exit (invalid).
Variable Usage
8.6% of shortcuts use setvariable (852 total sets vs only 42 explicit getvariable calls — a 20:1 ratio). This confirms that Shortcuts' implicit magic variable piping system makes explicit variable reads rarely necessary. Only 1.7% of shortcuts use mutable variables (same name set multiple times as state evolves).
Guard Clause Idiom
2.6% of shortcuts use the guard pattern: validation conditionals in the first 30 actions with an exit branch. Common conditions: WFCondition: 101 (does not have any value), WFCondition: 999 (less than), WFCondition: 8 (does not contain). Follow immediately with exit + error message.
Hub Delegation Idiom
When a shortcut has many features, keep the hub lightweight (just menus + routing) and delegate all real work to sub-shortcuts via runworkflow. Each sub-shortcut is independently testable.
s = Shortcut("My Hub")
with s.menu_block("Main", ["Feature A 🧠", "Feature B ✍️", "Feature C 🖼️"]) as cases:
cases["Feature A 🧠"]()
s.add(actions.make("runworkflow", WFWorkflowName="Feature A"))
cases["Feature B ✍️"]()
s.add(actions.make("runworkflow", WFWorkflowName="Feature B"))
cases["Feature C 🖼️"]()
s.add(actions.make("runworkflow", WFWorkflowName="Feature C"))
Variable Naming Convention
Real shortcuts use descriptive CamelCase names: "Baseimg", "ReportDate", "Prompt", "First Request". Only 1.7% of all shortcuts use variables mutably (same name set multiple times as state evolves), but when they do, setvariable serves as a state update mechanism rather than simple storage.
Composition Patterns (Layer 2)
Complex shortcuts combine actions into recurring architectural patterns. When designing a shortcut, choose patterns from this list and compose them. Full details and skeleton code in references/composition_patterns.json.
Architecture Archetypes
Pick the archetype that matches the user's request, then combine the listed patterns:
-
Utility App (150-800+ actions): Menu-driven multi-feature tool. Patterns: MENU_ROUTER + CONFIG_DICTIONARY + FILE_PERSISTENCE + SELF_UPDATE + CONDITIONAL_FEEDBACK. Examples: Pro_AI (829 actions), iOS (332 actions), Lucy (712 actions, AI assistant with 9 sub-shortcuts).
-
Data Pipeline (50-300 actions): Fetch, process, display data. Patterns: FETCH_PARSE_BRANCH + ACCUMULATE_LOOP + COMPUTED_VALUE + CONDITIONAL_FEEDBACK. Examples: Sleep_Widget (1253 actions, health data + Charty charts).
-
Hardware Manipulator (30-150 actions): Software actions creating physical effects. Patterns: SAVE_RESTORE_STATE + DATA_EMBEDDING + TIMED_REPEAT + HARDWARE_CONTROL + COMPUTED_VALUE. Examples: Water_Eject (frequency tone to eject water), Morse_Code_Torch (flashlight morse code signaling with computed delays).
-
Installer/Updater (100-250 actions): First-run setup + version checking. Patterns: FILE_PERSISTENCE + SELF_UPDATE + GUARD_CLAUSE + CONFIG_DICTIONARY + PROGRESSIVE_DISCLOSURE.
-
Simple Controller (15-50 actions): Lightweight menu dispatching to built-in actions. Pattern: MENU_ROUTER only.
-
Meta Tool (200-850+ actions): Shortcuts that operate on the Shortcuts system itself. Patterns: META_SHORTCUT + SELF_REFERENTIAL + FILE_PERSISTENCE + TEXT_PROCESSING_PIPELINE. Examples: MergeCuts (850 actions, merges/modifies shortcut plists), Swing_Updater (293 actions, batch-updates from RoutineHub).
-
AI Assistant (30-700+ actions): AI-powered shortcuts using cloud APIs or on-device Apple Intelligence. Patterns: API_INTEGRATION + MENU_ROUTER + PROGRESSIVE_DISCLOSURE + CONFIG_DICTIONARY. Examples: AI_Facial_Recognition (34 actions, DeepAI vision API), Birthday_Text_Blast (52 actions, Apple Intelligence for personalized messages).
-
Device Diagnostic (60-200 actions): Reads device internals and generates reports. Patterns: DEVICE_INTERROGATION + TEXT_PROCESSING_PIPELINE + FILE_PERSISTENCE + COMPUTED_VALUE. Examples: Battery_Report (179 actions, parses battery health logs with 32 regex patterns).
-
Communication Bot (30-100 actions): Automated messaging workflows. Patterns: CONTACT_AUTOMATION + PROGRESSIVE_DISCLOSURE + MENU_ROUTER. Examples: Birthday_Text_Blast (select contacts → AI-generate messages → batch-send iMessages).
-
Hub App (40-80 actions): Lightweight hub providing menu-based navigation to multiple sub-shortcuts or apps. Minimal logic in the hub itself — all complexity lives in delegates. Patterns: MENU_ROUTER + NESTED_MENU + SUB_SHORTCUT_DELEGATION + SELF_REFERENTIAL + APP_LAUNCHER. Examples: Apple_Intelligence_BETA (60 actions, nested menus delegating to 6+ AI sub-shortcuts with password-gated hidden apps).
Key Patterns (by frequency across 1,799 real shortcuts, 19,265 actions)
| Pattern | Freq | What it does |
|---|---|---|
| LINEAR_SIMPLE | 61.8% | Short sequential macro (≤5 actions, no control flow) — the dominant archetype |
| THIRD_PARTY_INTENTS | 32.9% | Uses non-workflow identifiers (Apple app intents, third-party apps) |
| CONDITIONAL_BRANCHING | 19.8% | If/then/else logic gates (1,264 blocks, avg 3.6 per branching shortcut) |
| LINEAR_PIPELINE | 9.7% | Longer sequential chain (6+ actions, no branching) |
| MENU_DRIVEN_APP | 9.1% | choosefrommenu as main dispatcher — each branch is a feature |
| CLIPBOARD_PIPELINE | 6.3% | getclipboard/setclipboard for data passing |
| DISPLAY_RESULT | 6.0% | Shows results to user via showresult |
| TEXT_PROCESSING | 5.9% | Regex/replace/split chains for data parsing |
| APP_LAUNCHER | 4.4% | openapp to launch apps by bundle ID |
| SUB_SHORTCUT | 4.4% | Hub delegates features to sub-shortcuts via runworkflow |
| DEVICE_SETTINGS | 3.3% | Brightness, volume, Wi-Fi, Bluetooth, Focus toggles |
| MENU_DISPATCHER | 2.7% | Large menus (6+ items) acting as command centers |
| INPUT_GUARD | 2.6% | Early conditional + exit for validation |
| VARIABLE_STATE_MACHINE | 2.6% | 3+ setvariable calls for complex state management |
| HEALTH_INTEGRATION | 2.2% | Reads/writes Apple Health (sleep, steps, heart rate, caffeine) |
| HARDWARE_CONTROL | 1.8% | Flashlight, vibrate, brightness, volume — physical effects |
| NOTIFICATION_FEEDBACK | 1.7% | notification/alert inside conditionals — status/error reporting |
| BINARY_DATA | 1.4% | Base64-encoded assets embedded in dictionaries |
| HUB_DELEGATION | 1.3% | Menu + runworkflow dispatch to sub-shortcuts |
| COMMUNICATION | 1.1% | sendmessage/sendemail — automated messaging |
| SHARE_SHEET | 1.1% | Share action for system share sheet |
| API_DATA_PIPELINE | 0.8% | url → downloadurl → detect.dictionary chain |
| FILE_PERSISTED_STATE | 0.8% | documentpicker.open + save — state between runs |
| MEDIA_CAPTURE | 0.7% | Camera/photo selection |
| LOOP_ACCUMULATOR | 0.6% | repeat.each + appendvariable for collection building |
| CALENDAR_OPS | 0.6% | Creates or queries calendar events and reminders |
| BATCH_API | 0.4% | downloadurl inside repeat loop — bulk API calls |
Pattern Combination Examples
Mood tracker with weekly trends — combines PROGRESSIVE_DISCLOSURE + FILE_PERSISTENCE + ACCUMULATE_LOOP + COMPUTED_VALUE + CONDITIONAL_FEEDBACK:
s = Shortcut("Mood Tracker")
mood = s.add(actions.make("choosefromlist")) # after adding a list of mood options
note = s.add(actions.make("ask", WFAskActionPrompt="Optional note?", WFAskActionDefaultAnswer=""))
log_file = s.add(actions.make("documentpicker.open",
WFGetFilePath="Shortcuts/MoodTracker/log.json",
WFShowFilePicker=False))
# ... parse, append today's entry, save back, compute weekly average, show trend ...
AI-powered birthday messenger — combines CONTACT_AUTOMATION + ON_DEVICE_AI + PROGRESSIVE_DISCLOSURE:
s = Shortcut("Birthday Blast")
contacts = s.add(actions.make("selectcontacts", WFSelectMultiple=True))
with s.repeat_each_block(contacts):
prompt = repeat_item.in_string("Write a fun birthday message for ", "")
msg = s.add(actions.make("askllm",
WFLLMPrompt=prompt, WFLLMModel="Apple Intelligence", WFGenerativeResultType="Text"))
s.add(actions.make("sendmessage", WFSendMessageContent=msg, ShowWhenRun=False))
Morse code flashlight — combines HARDWARE_CONTROL + TIMED_REPEAT + COMPUTED_VALUE + CONFIG_DICTIONARY:
s = Shortcut("Morse Torch")
config = s.add(actions.make("dictionary",
WFItems={"Value": {"WFDictionaryFieldValueItems":
actions.build_dict_items({"unit_length": 0.2, "message": "SOS"})}}))
# ... convert text to morse, iterate dots/dashes ...
s.add(actions.make("flashlight", WFFlashlightSetting=1)) # ON
s.add(actions.make("delay", WFDelayTime=dot_duration))
s.add(actions.make("flashlight", WFFlashlightSetting=0)) # OFF
Creative Strategy (Layer 3)
Layer 3 is about inventing — combining primitives in unexpected ways to solve novel problems. There is no "eject water" action, no "morse code" action, no "merge shortcuts" action. These capabilities emerge from creative combination of existing primitives.
Key Principles
-
Physical effects from digital primitives. Water_Eject plays a specific-frequency audio tone at max volume to vibrate water from speaker grilles. Morse_Code_Torch converts text to dots/dashes and pulses the flashlight with computed delays. The insight: any timed sequence of hardware state changes can create physical-world effects.
-
Meta-programming via plist manipulation. MergeCuts (850 actions) treats shortcuts as data — parsing their plist XML with text.replace/text.match chains, modifying action lists, and reassembling. Any shortcut can be read, modified, and rewritten.
-
AI as a composable building block. Birthday_Text_Blast feeds each contact's name into Apple Intelligence (askllm) to generate personalized messages, then batch-sends them. AI_Facial_Recognition sends photos to DeepAI, parses bounding box coordinates, and overlays visual markers.
-
Self-modifying systems. Lucy calls itself recursively (7 self-referential runworkflow calls). Swing_Updater enumerates all installed shortcuts and batch-updates them from RoutineHub.
-
Device as sensor. Battery_Report parses system logs with 32 regex patterns to extract battery health data Apple doesn't expose through any official action.
Creative Briefs (What Real Creators Build)
These are the actual intents behind real shortcuts from the 1,799-shortcut corpus — study these to understand what users want and how builders think:
- "Eject water from speakers" → No water action exists. Solution: play a specific-frequency audio file at max volume. Physical acoustics as a software primitive.
- "Morse code flashlight" → No morse action exists. Solution: dictionary mapping A-Z to dots/dashes, computed delays, flashlight ON/OFF loop.
- "AI facial recognition" → 34 actions. Send photo to DeepAI API, parse bounding box coordinates from JSON, overlay border image at computed positions.
- "Merge/edit shortcuts programmatically" → 850 actions. Treat .shortcut files as plists (XML), parse with text.match/text.replace, modify action lists, reassemble.
- "AI personal assistant with tools" → 712 actions. Lucy: natural-language-driven, calls 9 sub-shortcuts as "tools", self-updates, logs to Console.
- "Batch birthday messages with AI personalization" → 52 actions. Select contacts, filter by birthday, feed each name to Apple Intelligence, auto-send personalized iMessages.
- "Battery health diagnostics" → 179 actions. Read iOS analytics files, extract battery data with 32 regex patterns, compute statistics, generate HTML report.
- "Household inventory tracker" → 205 actions. Multilingual (DE/EN), file-persisted JSON database, menu-driven CRUD operations.
- "All-in-one AI hub with hidden apps" → 60 actions. Nested menus, password-gated hidden app launcher, music recognition, translation, weather, delegates to 6+ AI sub-shortcuts.
- "Sleep analytics with Charty charts" → 1,253 actions. Queries HealthKit for sleep data, filters by date ranges, computes statistics, renders visual charts via Charty third-party intents. The largest shortcut in the corpus.
- "Medical logging system" → 795 actions. Multi-category health logging (medications, symptoms, vitals) with file-persisted JSON database, 11 repeat-each loops, and menu-driven CRUD. Depth 8 nesting.
- "Shortcut merger/editor" → 850 actions. MergeCuts: reads .shortcut plist files as XML, parses and modifies action lists via text processing chains, reassembles and saves. 14 menus, 92 conditionals.
- "Smart caffeine tracker" → 60 actions. Logs caffeine to HealthKit, reads recent entries, tracks running daily total with time-decay estimates.
- "Image editor app" → 269 actions. Full image processing pipeline: resize, rotate, flip, mask, overlay text/images, convert formats, encode media. 14 menus for tool selection.
- "Emoji encoder/decoder" → 218 actions. Converts text to emoji representation and back using dictionary-based lookup tables with nested loops.
- "Google API OAuth flow" → 176 actions. Full OAuth2 authentication: generates auth URL, handles callback, exchanges code for token, refreshes tokens, persists credentials to file. 9 levels of nesting.
When Designing Creative Shortcuts
Ask: "What actions produce the raw materials I need?" then "What processing chain transforms those materials into the desired result?" The magic is always in the chain, never in a single action.
When a user asks for something that seems impossible in Shortcuts, think through these escape hatches: text processing can parse any structured data, downloadurl can call any API, runworkflow can delegate to specialized helpers, base64encode can embed any binary data, hardware actions (flashlight, vibrate, volume/brightness) can create physical effects, and third-party app intents (a-Shell, Scriptable, Charty) can access capabilities Apple doesn't expose.
Examples
references/examples/linear_example.py— Simple 8-action pipeline (URL → API call → parse → display)references/examples/branching_example.py— 15-action conditional + error handling with notificationsscripts/test_reconstruct_qotd.py— Full 55-action reconstruction using every major compiler feature (if/else, menus, repeat-each, variables, third-party actions, string interpolation)references/composition_patterns.json— Full pattern catalog with skeleton code and real-world examples
Debugging
If a generated shortcut doesn't work:
- Load it with
plistlib.load()and dump suspect actions as XML - Compare against a known-working shortcut that does the same thing
- The difference is your bug
- Common issues: wrong OutputUUID (action handle wiring), implicit input chain broken by inserted action, unrecognized action identifier or parameter name
Reliability
Current baseline (verified locally on February 15, 2026):
| Test Suite | Result |
|---|---|
Unit tests (scripts/test_compiler_unit.py) |
24/24 pass |
Catalog coverage on downloaded corpus (scripts/test_catalog_coverage.py) |
1,772 files, 12,050 actions, 28,252 params, 100% resolution + 100% parameter coverage |
Round-trip reconstruction (scripts/test_roundtrip.py) |
3/3 pass |
Full reconstruction (scripts/test_reconstruct_qotd.py) |
55 actions generated, 25 unique action types |
Known caveat:
scripts/test_xml_validator.pycurrently reports FAIL on the local XML set due to one parse error and one duplicate-UUID warning in source data. The strict failure behavior is intentional.
The round-trip tests verify identifier order, parameter keys, scalar values, and control-flow grouping structure (excluding compiler-generated UUIDs). The Quote of the Day reconstruction exercises conditionals, menus, repeat-each loops, variable wiring, third-party actions, and token-string interpolation.
Extending the Catalog
To add a new action type:
- Find a working shortcut that uses the action
- Export and load with
plistlib.load() - Dump the action's full parameters
- Add an entry to
action_catalog.json(the compiler will auto-resolve it) - The new action is immediately available via
actions.make("name")