shortcuts-playground 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -0
- package/agents/shortcut-builder.md +172 -0
- package/agents/shortcut-remixer.md +239 -0
- package/commands/build-shortcut.md +32 -0
- package/commands/remix-shortcut.md +34 -0
- package/opencode.json +30 -0
- package/package.json +47 -0
- package/plugin/validator.ts +179 -0
- package/skills/shortcuts-playground/ACTIONS.md +713 -0
- package/skills/shortcuts-playground/APPINTENTS.md +2760 -0
- package/skills/shortcuts-playground/AUTOMATION_TRIGGERS.md +133 -0
- package/skills/shortcuts-playground/BEST_PRACTICES.md +357 -0
- package/skills/shortcuts-playground/CHANGELOG.md +585 -0
- package/skills/shortcuts-playground/CONTROL_FLOW.md +777 -0
- package/skills/shortcuts-playground/DATE_TIME.md +75 -0
- package/skills/shortcuts-playground/EXAMPLES.md +738 -0
- package/skills/shortcuts-playground/FILTERS.md +697 -0
- package/skills/shortcuts-playground/HEALTHKIT.md +317 -0
- package/skills/shortcuts-playground/ICONS_AND_COLORS.md +89 -0
- package/skills/shortcuts-playground/JAVASCRIPT_WEBPAGE.md +51 -0
- package/skills/shortcuts-playground/PARAMETER_TYPES.md +1117 -0
- package/skills/shortcuts-playground/PLIST_FORMAT.md +296 -0
- package/skills/shortcuts-playground/README.md +77 -0
- package/skills/shortcuts-playground/SKILL.md +459 -0
- package/skills/shortcuts-playground/THIRD_PARTY_ACTIONS.md +77 -0
- package/skills/shortcuts-playground/TOOLKIT_SNAPSHOT.md +62 -0
- package/skills/shortcuts-playground/URL_SCHEMES.md +59 -0
- package/skills/shortcuts-playground/VARIABLES.md +569 -0
- package/skills/shortcuts-playground/assets/shortcuts-small.svg +14 -0
- package/skills/shortcuts-playground/assets/shortcuts.png +0 -0
- package/skills/shortcuts-playground/data/healthkit-ios26.2-reference.json +2603 -0
- package/skills/shortcuts-playground/data/macos27-shortpy-grounding.json +4085 -0
- package/skills/shortcuts-playground/data/macos27-workflow-trigger-samples.json +1 -0
- package/skills/shortcuts-playground/data/shortcuts-glyph-synonyms.json +5102 -0
- package/skills/shortcuts-playground/data/shortcuts-icon-colors.json +107 -0
- package/skills/shortcuts-playground/data/shortcuts-official-glyph-mapping.json +509 -0
- package/skills/shortcuts-playground/data/toolkit-v63-tool-ids.json +1806 -0
- package/skills/shortcuts-playground/data/toolkit-v78-first-party-enum-cases.json +1 -0
- package/skills/shortcuts-playground/data/toolkit-v78-first-party-parameter-keys.json +1 -0
- package/skills/shortcuts-playground/data/toolkit-v78-ios27-tool-ids.json +1222 -0
- package/skills/shortcuts-playground/data/toolkit-v78-tool-ids.json +2745 -0
- package/skills/shortcuts-playground/data/toolkit-v78-trigger-parameter-keys.json +1051 -0
- package/skills/shortcuts-playground/golden-shortcuts/index.jsonl +19 -0
- package/skills/shortcuts-playground/golden-shortcuts/xml/1be4dde95b794253bf82438e201b33e7.xml +174 -0
- package/skills/shortcuts-playground/golden-shortcuts/xml/2e0fb675e45948aaacee7e534f910492.xml +1016 -0
- package/skills/shortcuts-playground/golden-shortcuts/xml/332c12a0060043b388b22b806be7ab58.xml +1924 -0
- package/skills/shortcuts-playground/golden-shortcuts/xml/3dd4ee24e43f464f92adaa70a0311eaa.xml +1177 -0
- package/skills/shortcuts-playground/golden-shortcuts/xml/4d102301c6e646faa7a8a221f4f4ec98.xml +321 -0
- package/skills/shortcuts-playground/golden-shortcuts/xml/51cc4e26d1044893a0c3f2f3630cf2d2.xml +1933 -0
- package/skills/shortcuts-playground/golden-shortcuts/xml/623e7f1ca5f948e2bd53811fec63e544.xml +136 -0
- package/skills/shortcuts-playground/golden-shortcuts/xml/6a18b76843ac45c384ac3400f3740997.xml +1791 -0
- package/skills/shortcuts-playground/golden-shortcuts/xml/71f0cacb0f604b399b76c5dcb7286e7c.xml +2228 -0
- package/skills/shortcuts-playground/golden-shortcuts/xml/807525ed9f974829bc8494defac923a8.xml +259 -0
- package/skills/shortcuts-playground/golden-shortcuts/xml/8ab0d39826ab4249be58672763caa3ba.xml +420 -0
- package/skills/shortcuts-playground/golden-shortcuts/xml/91c45fd3fd3b427897d9ba485efb1227.xml +800 -0
- package/skills/shortcuts-playground/golden-shortcuts/xml/97be626bb25c41709d175646a7f6d8f2.xml +424 -0
- package/skills/shortcuts-playground/golden-shortcuts/xml/ae59e10d409348f9bd33894f03f9beb4.xml +568 -0
- package/skills/shortcuts-playground/golden-shortcuts/xml/afa83b6be811483b9c32189c41eb9312.xml +391 -0
- package/skills/shortcuts-playground/golden-shortcuts/xml/cfdb083b857e4ac189629fa386d27cdc.xml +193 -0
- package/skills/shortcuts-playground/golden-shortcuts/xml/e01cebe192d64b2fbca80204d03d92ab.xml +111 -0
- package/skills/shortcuts-playground/golden-shortcuts/xml/ef669bcf8bad489d9ef4b88bfaf5772f.xml +780 -0
- package/skills/shortcuts-playground/golden-shortcuts/xml/f44f5caf5e3e48d4817e73af450c4404.xml +570 -0
- package/skills/shortcuts-playground/scripts/generate_healthkit_reference.py +394 -0
- package/skills/shortcuts-playground/scripts/lookup_action_grounding.py +1164 -0
- package/skills/shortcuts-playground/scripts/select_shortcut_icon_color.py +597 -0
- package/skills/shortcuts-playground/scripts/test_random_mixed_shortcuts.py +1067 -0
- package/skills/shortcuts-playground/scripts/test_wiring_regressions.py +2247 -0
- package/skills/shortcuts-playground/scripts/validate_shortcut.py +4526 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Automation Triggers
|
|
2
|
+
|
|
3
|
+
OS 27 shortcuts can include native automation headers directly in exported shortcut XML. The portable carrier is the top-level `WFWorkflowTriggers` key, not a write to the user's live Shortcuts database.
|
|
4
|
+
|
|
5
|
+
Use this only when the user explicitly asks for an OS 27 automation shortcut or provides/exported automation samples. For ordinary shortcuts, omit `WFWorkflowTriggers`.
|
|
6
|
+
|
|
7
|
+
## Packaged Metadata
|
|
8
|
+
|
|
9
|
+
- ToolKit trigger metadata: `data/toolkit-v78-trigger-parameter-keys.json`
|
|
10
|
+
- Exported workflow trigger samples: `data/macos27-workflow-trigger-samples.json`
|
|
11
|
+
- ToolKit trigger variants: 42
|
|
12
|
+
- Variants with exported `WFWorkflowTriggers` samples: 42
|
|
13
|
+
- Missing exported samples: 0
|
|
14
|
+
- Minimum target: macOS/iOS 27
|
|
15
|
+
|
|
16
|
+
The lookup helper surfaces both ToolKit trigger parameters and exported plist samples:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
python3 scripts/lookup_action_grounding.py --python-name when_low_power_mode_changes --target-macos 27
|
|
20
|
+
python3 scripts/lookup_action_grounding.py --identifier com.apple.shortcuts.WFTimeOfDayTrigger.at_time_on_recurring_day --target-macos 27 --json
|
|
21
|
+
python3 scripts/lookup_action_grounding.py --query "wi-fi trigger" --target-macos 27 --json
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Plist Shape
|
|
25
|
+
|
|
26
|
+
Add `WFWorkflowTriggers` as a root key beside `WFWorkflowActions`:
|
|
27
|
+
|
|
28
|
+
```xml
|
|
29
|
+
<key>WFWorkflowTriggers</key>
|
|
30
|
+
<array>
|
|
31
|
+
<dict>
|
|
32
|
+
<key>WFTriggerIdentifier</key>
|
|
33
|
+
<string>WFLowPowerModeTrigger</string>
|
|
34
|
+
<key>WFTriggerSerializedParameters</key>
|
|
35
|
+
<dict>
|
|
36
|
+
<key>WFLowPowerModeType</key>
|
|
37
|
+
<string>both</string>
|
|
38
|
+
</dict>
|
|
39
|
+
<key>WFTriggerUUID</key>
|
|
40
|
+
<string>GENERATE-A-FRESH-UPPERCASE-UUID</string>
|
|
41
|
+
</dict>
|
|
42
|
+
</array>
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Rules:
|
|
46
|
+
|
|
47
|
+
- Always generate a fresh `WFTriggerUUID` with `uuidgen | tr '[:lower:]' '[:upper:]'`.
|
|
48
|
+
- Validate and sign with `--target-macos 27`.
|
|
49
|
+
- Do not ship catalog placeholders such as `$placeholder`; they mark redacted local picker values.
|
|
50
|
+
- Raw exported automation XML can contain user-local contact, Mail, Messages, account, app, location, device, file, folder, and drive payloads. Sanitize those values before adding samples to the plugin.
|
|
51
|
+
- Do not infer a trigger header from ToolKit metadata alone. Use `workflowTriggerSample` from the lookup helper or an exported shortcut from the user.
|
|
52
|
+
- Local picker values such as apps, contacts, locations, alarms, devices, networks, Focus modes, Wallet merchants, sounds, files, folders, and drives must come from a complete user export format or be selected manually in Shortcuts. Current macOS 27 file/folder/drive exports are lossy and do not carry enough picker state in `WFWorkflowTriggers` alone.
|
|
53
|
+
|
|
54
|
+
## Observed Defaults
|
|
55
|
+
|
|
56
|
+
- Change-style triggers serialize on/off or connect/disconnect as `both`.
|
|
57
|
+
- Display uses `WFConnectionType` values `connect`, `disconnect`, or `both`.
|
|
58
|
+
- Stage Manager uses `WFStageManagerType` values `on`, `off`, or `both`.
|
|
59
|
+
- External Drive, File Modified, and Folder Changed export as bare trigger identifiers with empty serialized-parameters dictionaries, but those bare imports render as invalid automations in macOS 27 Shortcuts.
|
|
60
|
+
- External Drive automation exports appear buggy or under-specified in the current OS 27 build: specific disconnected exports serialize only the generic `WFDiskMountTrigger` carrier, an Any Drive Connected export contained no trigger header, and importing the generic carrier rendered invalid despite visible default choices.
|
|
61
|
+
- File Modified imports from the empty `WFFileTrigger` carrier render with no selected file.
|
|
62
|
+
- Folder Item Added, Item Removed, Item Modified, and Ignore Subfolders choices exported with the same `WFFolderTrigger` header; those UI choices are not represented in the observed `WFWorkflowTriggers` payload. After manual editor configuration, folder path/bookmark state and event choices are stored outside the portable trigger dictionary.
|
|
63
|
+
- The exported Any Drive Connected file did not include `WFWorkflowTriggers`, so the plugin should not infer a connected-only drive payload from it.
|
|
64
|
+
- Wi-Fi connect-to-any can omit serialized parameters; Wi-Fi disconnect uses `WFConnectionType = disconnected`.
|
|
65
|
+
- Sleep Bedtime Begins uses `WFSleepMode = bedtime`.
|
|
66
|
+
- Time of Day sunrise/sunset use `WFTimeEvent = sunrise` / `sunset`; the observed at-time sample stores `WFTime` as a plist date.
|
|
67
|
+
- Notification predicates use `Conditions`; Email and Message predicates use `WFEmailConditions` / `WFMessageConditions`.
|
|
68
|
+
- Battery Equal exported with an empty serialized-parameters dictionary even though ToolKit exposes threshold/comparison keys.
|
|
69
|
+
|
|
70
|
+
## Current Support
|
|
71
|
+
|
|
72
|
+
These variants have exported `WFWorkflowTriggers` samples and can be generated for OS 27 targets, subject to picker-value availability:
|
|
73
|
+
|
|
74
|
+
Observed support means the shortcut header can be generated and imported from exported XML. Runtime support is still device-specific. For example, a Low Power Mode trigger imported into macOS Shortcuts can render as "not supported on Mac" even though the same header is valid for iPhone/iPad automation testing. When the user asks for a Mac-only automation, prefer triggers verified in the macOS editor/runtime and avoid treating ToolKit platform provenance as a runtime guarantee.
|
|
75
|
+
|
|
76
|
+
| Trigger | Python Name | Template Status |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| Airplane Mode changes | `when_airplane_mode_changes` | copyable with fresh UUID |
|
|
79
|
+
| Alarm any alarm | `when_alarm_any_alarm` | copyable with fresh UUID |
|
|
80
|
+
| Alarm specific alarm | `when_alarm_specific_alarm` | requires user values |
|
|
81
|
+
| App opened | `when_app_opened` | requires user values |
|
|
82
|
+
| Apple Watch Workout any | `when_apple_watch_workout_any` | copyable with fresh UUID |
|
|
83
|
+
| Apple Watch Workout specific | `when_apple_watch_workout_specific` | requires user values |
|
|
84
|
+
| Arrive location | `when_arrive_enter_location` | requires user values |
|
|
85
|
+
| Arrive location between times | `when_arrive_enter_location_between` | requires user values |
|
|
86
|
+
| Battery Level equal | `when_battery_level_equal` | copyable with fresh UUID |
|
|
87
|
+
| Bluetooth any connection changes | `when_bluetooth_any_connection_changes` | copyable with fresh UUID |
|
|
88
|
+
| Bluetooth selected connection changes | `when_bluetooth_selected_connection_changes` | requires user values |
|
|
89
|
+
| CarPlay changes | `when_car_play_changes` | copyable with fresh UUID |
|
|
90
|
+
| Charger changes | `when_charger_changes` | copyable with fresh UUID |
|
|
91
|
+
| Display / external display connected, disconnected, or changes | `when_display_wfexternaldisplaytrigger` | copyable with fresh UUID |
|
|
92
|
+
| Email senders are | `when_email_senders_are` | requires user values |
|
|
93
|
+
| Email senders are and subject contains | `when_email_senders_are_and_subject_contains` | requires user values |
|
|
94
|
+
| Email subject contains | `when_email_subject_contains` | requires user values |
|
|
95
|
+
| External Drive | `when_external_drive_external_drive` | requires user values |
|
|
96
|
+
| File Modified | `when_file_file_modified` | requires user values |
|
|
97
|
+
| Focus enable | `when_focus_enable` | requires user values |
|
|
98
|
+
| Folder Changed | `when_folder_folder_changed` | requires user values |
|
|
99
|
+
| Keyboard connection changes | `when_keyboard_connection_changes` | copyable with fresh UUID |
|
|
100
|
+
| Leave location | `when_leave_leave_location` | requires user values |
|
|
101
|
+
| Leave location between times | `when_leave_leave_location_between` | requires user values |
|
|
102
|
+
| Low Power Mode changes | `when_low_power_mode_changes` | copyable with fresh UUID |
|
|
103
|
+
| Message contains | `when_message_contains` | copyable with fresh UUID |
|
|
104
|
+
| Message senders are | `when_message_senders_are` | requires user values |
|
|
105
|
+
| Message senders are and contains | `when_message_senders_are_and_contains` | requires user values |
|
|
106
|
+
| NFC scan tag | `when_nfc_scan_tag` | copyable with fresh UUID |
|
|
107
|
+
| Notification received | `when_notification_received` | requires user values |
|
|
108
|
+
| Screenshot saved | `when_screenshot_saved` | copyable with fresh UUID |
|
|
109
|
+
| Sleep | `when_sleep_wfsleeptrigger` | copyable with fresh UUID |
|
|
110
|
+
| Sound Recognition | `when_sound_recognition_sound_recognition` | requires user values |
|
|
111
|
+
| Stage Manager turns on, turns off, or changes | `when_stage_manager_on` | copyable with fresh UUID |
|
|
112
|
+
| Time of Day around sunrise | `when_time_of_day_around_sunrise_on_recurring_day` | copyable with fresh UUID |
|
|
113
|
+
| Time of Day around sunset | `when_time_of_day_around_sunset_on_recurring_day` | copyable with fresh UUID |
|
|
114
|
+
| Time of Day at time | `when_time_of_day_at_time_on_recurring_day` | copyable with fresh UUID |
|
|
115
|
+
| Wallet tap | `when_wallet_tap` | requires user values |
|
|
116
|
+
| Wi-Fi connect to any | `when_wi_fi_connect_to_any` | copyable with fresh UUID |
|
|
117
|
+
| Wi-Fi connect to selected | `when_wi_fi_connect_to_selected` | requires user values |
|
|
118
|
+
| Wi-Fi disconnect from any | `when_wi_fi_disconnect_from_any` | copyable with fresh UUID |
|
|
119
|
+
| Wi-Fi disconnect from selected | `when_wi_fi_disconnect_from_selected` | requires user values |
|
|
120
|
+
|
|
121
|
+
All 42 ToolKit trigger variants now have at least one exported `WFWorkflowTriggers` sample. Some user-local picker choices are intentionally absent from the portable header and must still come from a complete export format or manual Shortcuts selection. In the current macOS 27 build, External Drive, File Modified, and Folder Changed are observed but not copyable from `WFWorkflowTriggers` alone.
|
|
122
|
+
|
|
123
|
+
## Validation
|
|
124
|
+
|
|
125
|
+
`validate_shortcut.py` checks `WFWorkflowTriggers` when present:
|
|
126
|
+
|
|
127
|
+
- OS target must be 27 or later.
|
|
128
|
+
- The root value must be an array of dictionaries.
|
|
129
|
+
- Each trigger must include `WFTriggerIdentifier`, `WFTriggerSerializedParameters`, and `WFTriggerUUID`.
|
|
130
|
+
- UUIDs must be uppercase real UUIDs, not placeholders.
|
|
131
|
+
- Serialized parameters must be dictionaries.
|
|
132
|
+
- Placeholder values from the static catalog are rejected.
|
|
133
|
+
- Known trigger identifiers and serialized parameter keys are checked against the exported sample catalog.
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
# Best Practices (Required)
|
|
2
|
+
|
|
3
|
+
These guidelines are mandatory for every shortcut built with this skill. If guidance in other docs appears to conflict, this file is the source of truth.
|
|
4
|
+
|
|
5
|
+
## APIs
|
|
6
|
+
|
|
7
|
+
- Use your best available web search and page-fetch tools to research official API docs before building requests (for example, SerpAPI + Firecrawl when available).
|
|
8
|
+
- For complex or unfamiliar external APIs, verify auth, endpoints, parameters, and payload formats against the latest official docs before drafting request actions.
|
|
9
|
+
- If the URL is static, set it directly in **Get Contents of URL** (`WFURL`). If it needs variables or query parameters, build the URL in a **URL/Text** action with tokenized placeholders and pass it into **Get Contents of URL**.
|
|
10
|
+
- Store API tokens in Text actions; add a Comment with a link to the service's API key page before token use.
|
|
11
|
+
- Never hardcode real credentials/tokens in shell snippets, examples, logs, or transcripts. Use env vars or credential placeholders.
|
|
12
|
+
- Do not load API tokens from files unless the user explicitly asks. If you must read from a file, add a Comment containing `ALLOW_TOKEN_FILE` and explain why.
|
|
13
|
+
- For file uploads, use the API's official direct upload method. Do **not** upload to third-party CDNs unless the user explicitly asks.
|
|
14
|
+
- Do not add third-party services, extra import questions, or hidden dependencies unless the user explicitly requests them.
|
|
15
|
+
- For Notion uploads, use the `file_uploads` endpoints and `file_upload` block type by default; avoid `external` image blocks unless the user provides a URL.
|
|
16
|
+
- Sanity-check API strings for obvious gaps before output: no `//` in URLs beyond the protocol, and no empty JSON fields where variables are expected (e.g., `equals:""`, `id:""`, `url:""`, `filename:""`).
|
|
17
|
+
- For endpoints with path parameters (e.g., `/tasks/{id}`), ensure the variable placeholder is present immediately after the trailing slash.
|
|
18
|
+
- When a request URL includes user input, ensure the **Ask for Input** variable is inserted into the query string; never leave blank query parameters.
|
|
19
|
+
- For Authorization headers, use `Basic {base64(user:pass)}` or `Bearer {token}` as required by the API.
|
|
20
|
+
- After **Get Contents of URL**, parse structured responses with **Detect Dictionary** and **Get Dictionary Value** (dot notation for nested keys) instead of string parsing; guard optional parents before reading nested keys (for example, do not call `error.message` directly before checking `error` exists).
|
|
21
|
+
- JSON booleans extracted via **Detect Dictionary** + **Get Dictionary Value** are often coerced to numeric `1`/`0` in Shortcuts, not string `true`/`false`; do not use string checks like `Contains "true"` on those values.
|
|
22
|
+
- For API boolean branching, prefer numeric conditions (`Is Greater Than 0` or `Equals 1`) or normalize through a **Text** action and compare to `"1"`/`"0"`.
|
|
23
|
+
- JSON `null` values are coerced to empty/nothing in Shortcuts; do not compare to the string `"null"` and always guard optional fields before branching.
|
|
24
|
+
- For JSON API responses, keep `ShowHeaders` disabled unless explicitly needed; if enabled, account for changed output shape before dictionary extraction.
|
|
25
|
+
- For JSON request bodies, use `WFJSONValues` for flat key/value payloads so the body is preserved in the Shortcuts editor.
|
|
26
|
+
- If `WFJSONValues` renders as `Number 0`, empty rows, or otherwise mangles nested objects/arrays, fall back to a JSON Text action and connect it via `WFRequestVariable`.
|
|
27
|
+
- When using JSON Text + `WFRequestVariable`, set the request body type to **File** and keep `Content-Type: application/json` so the variable is visible (especially on iOS).
|
|
28
|
+
- Never leave orphan JSON/Text actions: every JSON Text must be the `WFRequestVariable` of a request, or be removed.
|
|
29
|
+
- Do not inject raw Dictionary/List action outputs directly into JSON Text templates (for example, API `content` arrays). Shortcuts can stringify them as newline-separated pseudo-JSON that fails `Detect Dictionary`.
|
|
30
|
+
- When appending to JSON arrays with **Replace Text** (`\]$`), always include the closing `]` in `WFReplaceTextReplace`; omitting it silently corrupts continuation payloads.
|
|
31
|
+
- When interpolating user/assistant freeform text into JSON Text templates, sanitize it first (replace backslashes, normalize/escape newlines and tabs, and handle `"` safely) before insertion.
|
|
32
|
+
- For multipart form uploads, file fields must use `WFTokenAttachmentParameterState` with an inner `WFTextTokenAttachment` so the file variable shows in the UI.
|
|
33
|
+
- When using **Get Name** for file uploads, append the proper extension in the JSON payload (e.g., `.png`) instead of relying on the base name alone.
|
|
34
|
+
- For file names, set **Get Web Page Title** to **Off** in **Get Name** so the action returns the file name, not a URL title.
|
|
35
|
+
|
|
36
|
+
## Actions
|
|
37
|
+
|
|
38
|
+
- Prefer default, first-party Apple actions unless the user explicitly requests third-party actions.
|
|
39
|
+
- Use modern, non-deprecated Shortcuts actions.
|
|
40
|
+
- Always set shortcut icon metadata using `scripts/select_shortcut_icon_color.py` and write both `WFWorkflowIconGlyphNumber` + `WFWorkflowIconStartColor`.
|
|
41
|
+
- If the user explicitly requests an icon/color, honor it (name, synonym, glyph number, integer value, or hex); if not requested, infer from the prompt.
|
|
42
|
+
- Add Comment actions before major blocks, loops, and functional sections to explain intent.
|
|
43
|
+
- **Comment discipline is mandatory**: always include the first two Comment actions, plus section comments. For any control‑flow start (Repeat/If/Menu), the immediately preceding action must be a Comment with a short bulleted wiring list (lines starting with `-` or `•`). Do not skip descriptive comments.
|
|
44
|
+
- In Comment text, use human-readable Shortcuts wording: action names as shown in the app and input labels like `Input`, `Date`, `Provided Input`, `Repeat Item`. Do not use plist key jargon such as `WFInput`, `WFDate`, or `WFImage`. **NEVER include UUIDs, OutputUUID references, or any technical plist details in Comment text.** Comments must contain ONLY descriptive natural language — describe WHAT the action does and WHY, not HOW it's wired internally.
|
|
45
|
+
- Example style: `- Input uses the formatted date from Format Date above` and `• Date uses the user's answer from Ask for Input`.
|
|
46
|
+
- Good comment: `"Format the current date as 'March 24, 2026' style"`
|
|
47
|
+
- Bad comment: `"Format Date output (UUID: A1B2C3D4-0001-0001-0001-000000000001)"`
|
|
48
|
+
- Never use vCard/VCF list formatting unless the user explicitly requests it. Prefer dictionaries + **Choose from List** or plain lists. If vCard is required, add a Comment containing `ALLOW_VCARD`.
|
|
49
|
+
- If a parameter cannot be filled confidently, leave it empty **only if safe**, add a Comment explaining the gap, and tell the user in your response.
|
|
50
|
+
- Before final output, validate every `WFTextTokenString`: each `attachmentsByRange` key must point to a `` placeholder and the counts must match.
|
|
51
|
+
- When editing text templates, re-check `attachmentsByRange` offsets; a single character change can break variable insertion.
|
|
52
|
+
- Before final output, scan for actions that **require input** and ensure required input keys are wired to a variable/action output (not an empty string). Common offenders: **Get URLs from Input** (`is.workflow.actions.detect.link`), **Open URL**, **Choose from List**, **Get Dictionary Value**, **Convert Image**, **Base64 Encode/Decode**, **Get Detail of Weather Conditions**, **Get Time Between Dates**, **Extract Text from Image**, and **Replace Text**.
|
|
53
|
+
- For **Get URLs from Input** (`is.workflow.actions.detect.link`), set `WFInput` as a `WFTextTokenString` with a `` placeholder (not a bare `WFTextTokenAttachment`) to keep the editor field visible.
|
|
54
|
+
- For **Store Content** (`is.workflow.actions.setstoredcontent`), a bare or wrapped token attachment imports as an empty `Content` placeholder. Use Apple's saved shape: `WFInput` must be a `WFTextTokenString` with a single `` placeholder and a `{0, 1}` `attachmentsByRange` entry pointing at the content output. `WFStoredContentGlobalValue` only controls the global/iCloud scope and is unrelated to whether the `Content` field renders. If Apple's `shortcuts sign` rejects the XML plist with "isn't in the correct format", sign the final `.shortcut` copy after `plutil -convert binary1`; the bundled signer wrapper retries this automatically.
|
|
55
|
+
- For **Convert Image** (`is.workflow.actions.image.convert`), always set explicit `WFInput` to the image/media output.
|
|
56
|
+
- For **Base64 Encode/Decode** (`is.workflow.actions.base64encode`), always set an explicit `WFInput`; do not rely on implicit previous-action input.
|
|
57
|
+
- For **Get Detail of Weather Conditions** (`is.workflow.actions.properties.weather.conditions`), set both `WFInput` and `WFContentItemPropertyName` (for example, `Sunrise Time`). Use only these supported detail names: `Date`, `Location`, `Temperature`, `Low`, `High`, `Feels Like`, `Condition`, `Visibility`, `Dewpoint`, `Humidity`, `Pressure`, `Precipitation Amount`, `Precipitation Chance`, `Wind Speed`, `Wind Direction`, `UV Index`, `Sunrise Time`, `Sunset Time`, `Air Quality Index`, `Air Quality Category`, `Air Pollutants`, `Name`.
|
|
58
|
+
- For **Get Detail of Weather Conditions**, `WFInput` must reference the **direct action output** from **Get Current Weather** (`is.workflow.actions.weather.currentconditions`) or **Get Weather Forecast** (`is.workflow.actions.weather.forecast`). Do not feed this action through named variables; that can degrade the field to generic “Detail”.
|
|
59
|
+
- For **Get Detail of Weather Conditions**, never leave `WFContentItemPropertyName` at placeholder values such as `Detail`; use an explicit weather property.
|
|
60
|
+
- **Sunrise/sunset list handling**: `Sunrise Time` and `Sunset Time` from a Daily forecast are lists. Add **Get Item from List** before formatting or displaying them: use **First Item** for `Sunrise Time` (first light) and **Last Item** for `Sunset Time` (last light). Do not feed these weather-detail outputs directly into **Format Date**.
|
|
61
|
+
- For **HealthKit actions**, use [HEALTHKIT.md](HEALTHKIT.md) and `data/healthkit-ios26.2-reference.json`; do not guess schemas from action names. These actions are iOS/iPadOS Health actions, and macOS cannot fully configure or run their Health UI.
|
|
62
|
+
- For **Find Health Samples** (`is.workflow.actions.filter.health.quantity`), do **not** set `WFHealthQuantityType`. Current iOS Shortcuts ignores that obsolete top-level key and renders a generic Health Samples action. Put the sample kind inside `WFContentItemFilter` as a non-removable `Type` predicate row (`Operator` `4`, `Bounded` true, `Removable` false, `Values.Enumeration.WFSerializationType = WFStringSubstitutableState`) with the Find Health Samples picker label such as `Steps`, plus date filters as needed. Do not use `Property = Value` with a plain string such as `Step Count`; it imports as an editable text filter, not the Health type picker.
|
|
63
|
+
- For **Health dashboard summaries**, use observed Find Health Samples picker labels: `Sleep` (not `Sleep Analysis`), `Exercise Minutes` (not `Apple Exercise Time` or `Exercise Time`), and `Active Calories` (not `Active Energy` or `Active Energy Burned`) for active-energy queries. Treat Sleep `Duration` as a duration that Math coerces to seconds: divide by `3600` for decimal hours, or divide by `60` only when reporting minutes. Do not divide sleep duration by `60` and label it hours. Do not divide Walking + Running Distance by `1000`; sum the Health `Value` directly or use Convert Measurement with an explicit source unit.
|
|
64
|
+
- For **Log Health Sample** (`is.workflow.actions.health.quantity.log`), set `WFQuantitySampleType` plus `WFQuantitySampleQuantity` as `WFQuantityFieldValue`. `WFQuantitySampleAdditionalQuantity` may be unit-only. Category picker values can add `WFCategorySampleEnumeration` and still retain the count-based quantity scaffold.
|
|
65
|
+
- For **Get Details of Health Sample** (`is.workflow.actions.properties.health.quantity`), set `WFContentItemPropertyName` to one of `Type`, `Value`, `Unit`, `Start Date`, `End Date`, `Duration`, `Source`, or `Name`, and wire `WFInput` to Find Health Samples or a variable sourced from it.
|
|
66
|
+
- For **Log Workout** (`is.workflow.actions.health.workout.log`), set an explicit `WFWorkoutReadableActivityType` and use `WFQuantityFieldValue` for duration, calories, and distance when present.
|
|
67
|
+
- Do not run Health-writing shortcuts only to test syntax. Import/edit/export and validate them unless the user explicitly asks you to run them.
|
|
68
|
+
- For location-bearing parameters (`WFLocation`, `WFWeatherCustomLocation`, legacy `WFWeatherLocation`), use `WFTextTokenAttachment` (not `WFTextTokenString`) and wire to **Get Current Location**/`Location` outputs directly, or variables sourced from those outputs. Never emit these keys as empty dictionaries/strings.
|
|
69
|
+
- For **Find Calendar Events** date filters, use date operators, not numeric If operators. For the common "next event after now" filter, use `Start Date is today` (`Operator: 1002`, empty `Values`) plus `Start Date is after Current Date` (`Operator: 2`, `Values.Date` as a `CurrentDate` token). Do not use `Operator: 3`; it imports as a red invalid date condition.
|
|
70
|
+
- For **Get Time Between Dates** (`is.workflow.actions.gettimebetweendates`), set `WFInput` and **exactly one** non-empty date operand (`WFDate` or `WFTimeUntilCustomDate` or `WFTimeUntilFromDate`). Both `WFInput` and the date operand must be `WFTextTokenString` placeholders referencing action outputs. Prefer `WFTimeUntilFromDate` for date-vs-date deltas. `WFTimeUntilUnit` can be omitted only when relying on the action default; never emit unused empty date keys.
|
|
71
|
+
- For **Get Time Between Dates**, do not use the `CurrentDate` magic token directly. Insert a **Date** action set to **Current Date** first, then reference that Date action's output.
|
|
72
|
+
- For **Extract from Image / Extract Text from Image** (`is.workflow.actions.extracttextfromimage`), set exactly one non-empty image input key. On OS 27+, prefer `imageFile` from ToolKit v78. For older exported shortcuts, `WFImage` remains valid and `WFInput` is an existing-pattern fallback. Do not include an empty second image-input key.
|
|
73
|
+
- For API error handling with **Get Dictionary Value**, avoid direct optional key paths such as `error.message` on raw responses; extract `error` first, guard with **If Has Any Value**, then read nested keys inside that branch.
|
|
74
|
+
- Do not output placeholder actions with empty parameters. If a Text/Ask action would be blank, remove it or replace with a Comment. Omit `WFAskActionDefaultAnswer` entirely when there is no default.
|
|
75
|
+
- **All conditional codes use the same `WFInput` wrapper** (verified against an Apple-built sample shortcut covering codes 0, 1, 2, 3, 4, 5, 8, 9, 99, 100, 101, 999, 1003 plus the multi-condition pattern):
|
|
76
|
+
- **Always set `WFInput` explicitly** as `{ Type: "Variable", Variable: { Value: <ActionOutput or Variable>, WFSerializationType: "WFTextTokenAttachment" } }`. This is the only valid input pattern. There is no "implicit input" mode — the older docs that told you numeric codes 0–3 use implicit input were incorrect, and the validator now rejects implicit input for every condition code.
|
|
77
|
+
- **Per-code literal field** (in addition to `WFInput`):
|
|
78
|
+
- String codes (`4`, `5`, `8`, `9`, `99`, `999`) → set `WFConditionalActionString` with the literal text to match.
|
|
79
|
+
- Numeric codes (`0`, `1`, `2`, `3`) → set `WFNumberValue` with the literal number as a string.
|
|
80
|
+
- Numeric "is between" code (`1003`) → set `WFNumberValue` (lower bound, literal string) AND `WFAnotherNumber` (upper bound, token attachment that can hold a literal or a variable reference).
|
|
81
|
+
- Existence codes (`100`, `101`) → set neither `WFConditionalActionString` nor `WFNumberValue` — only `WFInput`. The validator rejects extras.
|
|
82
|
+
- **Multi-condition If** (Any are true / All are true) uses `WFConditions` with `WFSerializationType = WFContentPredicateTableTemplate` instead of top-level `WFCondition` + `WFInput`. See CONTROL_FLOW.md "Multi-condition If" for the full template; `WFActionParameterFilterPrefix = 0` means Any (OR), `1` means All (AND). Each row inside `WFActionParameterFilterTemplates` carries its own `WFCondition`, `WFInput`, and per-code literal field. Do not mix the two patterns on the same If — the validator rejects an action that has both `WFConditions` and top-level `WFCondition`.
|
|
83
|
+
- **Otherwise If** on macOS 27+ is mode 1 with condition fields (`WFCondition`, `WFInput`, and the relevant literal field). Plain Otherwise is mode 1 with no condition fields. End If is mode 2.
|
|
84
|
+
- For **If** actions, do not set `WFInput` to a bare `WFTextTokenAttachment`; that imports as a blank input chip on iOS. Always use the `Type=Variable` wrapper, even when wrapping an `ActionOutput`.
|
|
85
|
+
- For **If** actions, wrapping an **ActionOutput** directly inside the `Type=Variable` wrapper is valid and matches Apple's own serialization in the reference sample. Set Variable hops are NOT required.
|
|
86
|
+
- For **list `contains` If** checks on macOS 27, do not repeatedly assign the same named list variable and then test that name. Shortcuts 27 imports that shape with a blank comparison value even though `WFConditionalActionString` is present in SQLite. Reference the final **List/Add to List** action output directly, or assign the final list once to a fresh variable name (for example, mutate `Working Fruit List`, then set `Fruit List` once before the If).
|
|
87
|
+
- Prefer integer `WFCondition` codes for conditionals (`100` for Has Any Value, `2` for Is Greater Than) instead of condition name strings; string names may import but degrade at runtime.
|
|
88
|
+
- `is.workflow.actions.input` should not be emitted as a runtime action. Reference shortcut input via an `ExtensionInput` attachment instead.
|
|
89
|
+
- For “Clipboard or Ask for Input” patterns, avoid numeric **Count → If**. Use a single **If** that checks whether the clipboard **has any value** (set `WFCondition` to integer `100` and point `WFInput` to the Clipboard variable).
|
|
90
|
+
- Avoid generating **Unknown Action** blocks. AppIntent/action identifiers and OS 27-era parameter keys must exist for the target in the bundled ToolKit snapshots (`data/toolkit-v*-tool-ids.json`) and references (`APPINTENTS.md`, `ACTIONS.md`); static `data/macos27-shortpy-grounding.json` can add macOS 27 schema confidence but does not override target availability. Local ToolKit DB checks are optional and additive for audits only. Do not invent identifiers or use OS 27-only parameters on macOS 26 targets. If an action or parameter is unavailable on the target OS/version, replace it with a supported fallback and note it in a Comment.
|
|
91
|
+
- Only use AppIntent actions when their **parameter keys are verified** (from `APPINTENTS.md`, a golden example, or the OS 27 `toolkit-v78-first-party-parameter-keys.json` catalog). For OS 27 targets, the validator rejects unknown top-level keys on first-party `com.apple.*` AppIntent-style actions, filters those keys by target platform, and checks simple literal enum/boolean values on both `com.apple.*` and classic `is.workflow.actions.*` rows when ToolKit v78 has a single clear type. It still does not prove complex picker value serialization or runtime behavior. If unsure, prefer WF actions. For Apple Music search/play, favor `is.workflow.actions.filter.music` + `is.workflow.actions.playmusic` over `PlayMusicTopHitAction`.
|
|
92
|
+
- For unit conversions, prefer built‑in **Convert Measurement** (`measurement.create` + `measurement.convert`) instead of manual math. Use math only for unsupported units or custom formulas.
|
|
93
|
+
- Common identifier gotchas:
|
|
94
|
+
- **Text** action is `is.workflow.actions.gettext` (never `is.workflow.actions.text`).
|
|
95
|
+
- Avoid emitting the **Shortcut Input** action (`is.workflow.actions.input`) in generated shortcuts; use `ExtensionInput` token attachments where input is needed.
|
|
96
|
+
- **Translate Text** is `is.workflow.actions.text.translate` (not `is.workflow.actions.translate`).
|
|
97
|
+
- **Send Email** is `is.workflow.actions.sendemail` (never `sendmail`).
|
|
98
|
+
- **Run Script** actions are `runapplescript`, `runshellscript`, `runsshscript`, `runjavascriptforautomation`, or `runjavascriptonwebpage` (there is no `runscript`).
|
|
99
|
+
- **Podcast details** use `is.workflow.actions.properties.podcast` or `is.workflow.actions.properties.podcastshow` (there is no `properties.podcastepisode`).
|
|
100
|
+
- Do not simplify or omit user requirements on your own. If the scope feels too large or uncertain, ask for clarification or propose a reduced version and wait for approval.
|
|
101
|
+
- If you extract multiple fields from a dictionary/API response, make sure **each** value is reused in later output or remove the unused extraction.
|
|
102
|
+
- For nested dictionary responses, use dot notation (including array indexes) in **Get Dictionary Value** keys (e.g., `results.tracks.items`, `artists.1.name`), but guard optional branches first (especially `error.*`).
|
|
103
|
+
- When composing display strings with multiple variables (e.g., `Track – Artist`), include a placeholder for each variable and update `attachmentsByRange` positions accordingly.
|
|
104
|
+
- For **Set Dictionary Value**, always connect the target dictionary via `WFDictionary`; do not rely on implicit input.
|
|
105
|
+
- Do not leave **Text**, **Number**, **URL**, **Math**, **Count**, or **Format Date** outputs unused. Wire them explicitly into the next action or a **Set Variable**.
|
|
106
|
+
- For **Adjust Date**, always wire the source date explicitly via `WFDate` (and optionally mirror it in `WFInput`), plus a non-empty `WFDuration`. `WFAdjustOperation` may be omitted when using the default Add behavior, but include it when you need explicit Add/Subtract control. Offset-picker-only payloads can import as `Add 0 seconds` on iOS and are not reliable.
|
|
107
|
+
- For **Format Date** with custom formats: set `WFDateFormatStyle=Custom`, set `WFDateFormat=Custom`, and put the pattern in `WFDateFormatString` (e.g., `MMMM d, yyyy`, `yyyy-MM-dd`, `yyyy-MM-dd'T'HH:mm:ssXXXXX`). Always set `WFDate` (not `WFInput`) with the source Date. See `DATE_TIME.md` for UNIX timestamp, ISO 8601, RFC 2822, and Unicode TR35 guidance.
|
|
108
|
+
- For **Shortcuts URL schemes**, use only Apple-documented routes from `URL_SCHEMES.md`: `shortcuts://`, `create-shortcut`, `open-shortcut`, `run-shortcut`, `gallery`, `gallery/search`, and `x-callback-url/run-shortcut`. URL-encode query values and callbacks; do not invent import/install routes.
|
|
109
|
+
- For **Run JavaScript on Webpage**, include `ActionExtension`, scope input to `WFSafariWebPageContentItem`, require Safari/share-sheet execution, call `completion(...)` or `completion()`, return JSON-compatible values, and avoid `alert`/`prompt`/`confirm` or long timers. The user's **Allow Running Scripts** setting cannot be encoded in the plist.
|
|
110
|
+
- For **Measurement** conversions, use the built‑ins:
|
|
111
|
+
- **Create Measurement**: `is.workflow.actions.measurement.create` (outputs `Measurement`)
|
|
112
|
+
- **Convert Measurement**: `is.workflow.actions.measurement.convert` with `WFInput` wired to the Measurement output
|
|
113
|
+
- Do **not** do manual math unless the unit is unsupported. If you must, add a Comment containing `ALLOW_MANUAL_UNIT_CONVERSION` and explain why.
|
|
114
|
+
- For **Send Email**, use `is.workflow.actions.sendemail` and attach content using `WFSendEmailActionInputAttachments` with a `WFTextTokenString` placeholder.
|
|
115
|
+
- For **Translate Text** (`is.workflow.actions.text.translate`), use `WFInputText` (not `WFInput`), and set `WFSelectedFromLanguage` / `WFSelectedLanguage` to **display names** (e.g., “Spanish”, “French”, “Japanese”, “Chinese (Simplified)”) or a variable that outputs those names (avoid ISO codes like `es`, `fr`, `zh-Hans`).
|
|
116
|
+
- Never use **Repeat Results** inside a **Repeat with Each** loop; only use it after the loop ends.
|
|
117
|
+
- Inside **Repeat (count)** loops, use the **Repeat Index** named variable (Type=Variable) and never reference **Repeat Results** or ActionOutput from the repeat end inside the loop.
|
|
118
|
+
- If you reference **Shortcut Input**, set `WFWorkflowInputContentItemClasses` to non-empty input types. Do not use Shortcut Input when the shortcut receives no input (avoids “Stop and Respond”).
|
|
119
|
+
- If you do **not** reference Shortcut Input, keep `WFWorkflowInputContentItemClasses` empty.
|
|
120
|
+
- Notes create actions must include both a **title/name** parameter and a **content/markdown** parameter; do not leave them empty.
|
|
121
|
+
- For `com.apple.Notes.CreateNoteFromMarkdownLinkAction`, use `markdownContents` (camelCase) as the content parameter key — this is the official AppIntent parameter name from the toolkit and the runtime-required key. Do not use `markdown` as a substitute; it can pass older validation but produce an empty note body at runtime.
|
|
122
|
+
|
|
123
|
+
## Critical Variable Wiring Rules
|
|
124
|
+
|
|
125
|
+
**ALWAYS use Variable type for Repeat Index (not ActionOutput):**
|
|
126
|
+
- Inside Repeat Count loops, reference `Repeat Index` as `Type=Variable` with `VariableName="Repeat Index"`
|
|
127
|
+
- Never use `Type=ActionOutput` referencing the end action's UUID for Repeat Index
|
|
128
|
+
- Common mistake: Using ActionOutput causes the variable to appear as "Repeat Results" in the UI and fails at runtime
|
|
129
|
+
- Repeat Item in Repeat Each loops has the same pattern: `Type=Variable` with `VariableName="Repeat Item"`
|
|
130
|
+
|
|
131
|
+
**WFTextTokenString for display parameters vs WFTextTokenAttachment for data flow (CRITICAL — runtime-verified):**
|
|
132
|
+
- **Display parameters** (text shown to the user in UI) MUST use `WFTextTokenString` even when the value is a single variable. The `string` field contains `` (U+FFFC) and `attachmentsByRange` maps `{0, 1}` to the variable. These parameters include:
|
|
133
|
+
- `WFAlertActionMessage`, `WFAlertActionTitle` (Show Alert)
|
|
134
|
+
- `WFNotificationActionBody`, `WFNotificationActionTitle` (Notification)
|
|
135
|
+
- `Text` (Show Result)
|
|
136
|
+
- `WFTextActionText` (Text action content)
|
|
137
|
+
- **Non-display parameters** (data flow between actions) can use `WFTextTokenAttachment`:
|
|
138
|
+
- `WFInput` (Set Variable, Math, Count, If condition, etc.)
|
|
139
|
+
- `WFDate` (Format Date)
|
|
140
|
+
- `WFVariable` (Get Variable)
|
|
141
|
+
- Using `WFTextTokenAttachment` for display parameters causes the field to show default/empty text at runtime even though the variable reference is structurally valid.
|
|
142
|
+
- **Evidence:** All 46 Show Alert instances and all 41 Notification instances across 127 real shortcuts use `WFTextTokenString` for their message/body parameters.
|
|
143
|
+
|
|
144
|
+
**Text outputs inside If branches must be consumed:**
|
|
145
|
+
- If an If block produces text output via a Text action, that output must be consumed by the next action
|
|
146
|
+
- Unused outputs inside If branches can cause the branch logic to not wire correctly
|
|
147
|
+
- Consume with: Set Variable, Show Alert, Show Result, or pass to next action explicitly
|
|
148
|
+
|
|
149
|
+
**Every action that will be referenced needs a UUID:**
|
|
150
|
+
- Any action whose output is consumed by a later action must have a `UUID` key
|
|
151
|
+
- Text, Number, Format Date, Get Dictionary Value, etc. all need UUIDs if their output is wired elsewhere
|
|
152
|
+
- Visible symptom of missing UUID: variable appears as "Unknown" in the UI
|
|
153
|
+
|
|
154
|
+
**Comment actions before every control flow block:**
|
|
155
|
+
- Add a Comment action immediately before If, Repeat, and Menu blocks
|
|
156
|
+
- Include a bulleted wiring list describing what feeds into the block
|
|
157
|
+
- Example: `- Input uses the formatted date string from Format Date above`, `• Condition checks whether Repeat Item has a value`
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Preflight Validation (Required)
|
|
162
|
+
|
|
163
|
+
After generating a shortcut, run the local validator and loop until it passes. This is a lightweight generate → validate → fix/regenerate loop.
|
|
164
|
+
|
|
165
|
+
**Checklist (must pass):**
|
|
166
|
+
- No empty parameters for **If**, **Ask**, **Text**, **Choose from Menu**, and other required fields.
|
|
167
|
+
- No empty `WFInput` where explicitly set.
|
|
168
|
+
- No missing `WFInput` for required-input actions (e.g., **Open URL**, **Get URLs from Input**, **Choose from List**, **Set Clipboard**, **Convert Image**, **Get Detail of Weather Conditions**, **Get Time Between Dates**).
|
|
169
|
+
- For **Adjust Date**, require a non-empty date source (`WFDate` or `WFInput`) and non-empty `WFDuration`; reject empty date keys when present.
|
|
170
|
+
- For **Get Time Between Dates**, require exactly one non-empty date operand, reject empty `WFDate` / `WFTimeUntilCustomDate` / `WFTimeUntilFromDate` keys when present, and use `WFTextTokenString` placeholders for both date inputs.
|
|
171
|
+
- For **Extract Text from Image**, require exactly one non-empty image input key (`WFImage` preferred; `WFInput` allowed when intentionally used) and reject empty image-input keys.
|
|
172
|
+
- For **Change Case** and **Split Text**, input must be in the `text` parameter (not `WFInput`).
|
|
173
|
+
- For **Replace Text**, input must be in `WFInput` (not `text`).
|
|
174
|
+
- For **Split Text** with `WFTextSeparator=Custom`, `WFTextCustomSeparator` must be present; a single space (`" "`) is a valid separator.
|
|
175
|
+
- For **Find Notes**, `WFContentItemFilter` must be `WFContentPredicateTableTemplate` with non-empty templates; Name uses `Values.String` (`WFTextTokenString`), Folder uses `Values.Enumeration` (`WFLinkDynamicOptionSubstitutableState`).
|
|
176
|
+
- For **HealthKit**, validate Find Health Samples, Get Details of Health Sample, Log Health Sample, and Log Workout against [HEALTHKIT.md](HEALTHKIT.md). Health sample quantity fields must use `WFQuantityFieldValue`; sample details must use a Health Samples input source.
|
|
177
|
+
- No Unknown Action identifiers (must exist in ToolKit snapshots, `ACTIONS.md`, `APPINTENTS.md`, `THIRD_PARTY_ACTIONS.md`, or target-appropriate static grounding).
|
|
178
|
+
- No mismatched text token placeholders.
|
|
179
|
+
- No unused outputs from **Text**, **Number**, **URL**, **Math**, **Count**, or **Format Date** actions.
|
|
180
|
+
- No `WFInput` values that wrap **ActionOutput** inside a `Type=Variable` wrapper for non-conditional actions. For **If** starts, wrapped ActionOutput is expected.
|
|
181
|
+
- Confirm API date formats. If an API/user requires a specific date format, use **Format Date → Custom** and set the exact format string (e.g., `yyyy-MM-dd`). Avoid hard‑coding `00:00:00` or `23:59:59`; use date‑only strings for start/end parameters. Only add `ALLOW_DATETIME_FORMAT` if the user explicitly requests a date‑time format.
|
|
182
|
+
- When using **Format Date** with a custom format string, set **Format Style = Custom** (i.e., include `WFDateFormatStyle=Custom`). Leaving it unset can display as “Short” in the UI.
|
|
183
|
+
- Keep a **raw Date variable** for calculations. Use **Adjust Date** on the raw date, then **Format Date** into strings for API parameters.
|
|
184
|
+
- For nested repeats, ensure the correct **Repeat Item N** and **Repeat Index N** are used (inner loop uses Repeat Item 2, etc.).
|
|
185
|
+
- Inside Repeat loops, **reference Repeat Item/Index as Variables** (Type=Variable with `VariableName: "Repeat Item"` or `"Repeat Item 2"`). Do not use ActionOutput references for Repeat Item/Index; they can show as Repeat Results in the UI.
|
|
186
|
+
- Avoid **Get Variable** actions unless their output is used immediately. Prefer inserting variables directly into action parameters.
|
|
187
|
+
- For result summaries, ensure **Text** actions interleave variables with placeholders (don’t leave them as plain text).
|
|
188
|
+
- Two required Comment blocks at the top, plus section comments for longer shortcuts.
|
|
189
|
+
- Comment density: for shortcuts with 8+ actions, require at least 3 comments; 16+ actions require 4; 24+ actions require 5. Every control‑flow start must be preceded by a Comment with a short bulleted wiring list.
|
|
190
|
+
- No internal `WF*` parameter names inside Comment text; use UI wording instead.
|
|
191
|
+
- No vCard/VCF content unless explicitly allowed (`ALLOW_VCARD` comment).
|
|
192
|
+
- No API token loaded from file unless explicitly allowed (`ALLOW_TOKEN_FILE` comment).
|
|
193
|
+
- For Todoist updates, ensure `/rest/v2/tasks/` includes the task ID placeholder.
|
|
194
|
+
- For If conditions, `WFInput` must be a proper token attachment (`WFTextTokenAttachment` / `WFTextTokenString`), not an empty or raw variable dict.
|
|
195
|
+
- For Set Dictionary Value, `WFDictionary` must be populated with the target dictionary variable.
|
|
196
|
+
- For Set Variable, `WFInput` must be present and a token attachment (never leave it blank).
|
|
197
|
+
- For Set Variable, if the source is a constant (string/number), use **Text**/**Number** then wire its output into `WFInput` (do not leave the Set Variable action unconnected).
|
|
198
|
+
- **If condition codes (DEFINITIVE — verified against an Apple-built sample shortcut covering every code):**
|
|
199
|
+
|
|
200
|
+
| Code | UI label | Category | Required extras (in addition to `WFInput`) |
|
|
201
|
+
|------|----------|----------|---------------------------------------------|
|
|
202
|
+
| `0` | is less than | numeric | `WFNumberValue` |
|
|
203
|
+
| `1` | is less than or equal to | numeric | `WFNumberValue` |
|
|
204
|
+
| `2` | is greater than | numeric | `WFNumberValue` |
|
|
205
|
+
| `3` | is greater than or equal to | numeric | `WFNumberValue` |
|
|
206
|
+
| `4` | is (string equals) | string | `WFConditionalActionString` |
|
|
207
|
+
| `5` | is not (string inequality) | string | `WFConditionalActionString` |
|
|
208
|
+
| `8` | begins with | string | `WFConditionalActionString` |
|
|
209
|
+
| `9` | ends with | string | `WFConditionalActionString` |
|
|
210
|
+
| `99` | contains (substring) | string | `WFConditionalActionString` |
|
|
211
|
+
| `100`| has any value | existence | (none) |
|
|
212
|
+
| `101`| does not have any value | existence | (none) |
|
|
213
|
+
| `999`| does not contain | string | `WFConditionalActionString` |
|
|
214
|
+
| `1003` | is between | numeric | `WFNumberValue` (lower) + `WFAnotherNumber` (upper, attachment) |
|
|
215
|
+
|
|
216
|
+
**Common confusions to avoid:**
|
|
217
|
+
- Code `0` is `is less than` — there is no numeric "equals" code in the modern conditional. To compare two numbers for equality, either use code `4` (string equals) on text-coerced numbers, or build an Any-of-two block with `is greater than or equal to N` AND `is less than or equal to N`.
|
|
218
|
+
- Code `2` (`is greater than`) and code `3` (`is greater than or equal to`) differ by inclusivity — easy to swap.
|
|
219
|
+
- Code `4` (string equals) and code `99` (substring contains) are NOT interchangeable.
|
|
220
|
+
|
|
221
|
+
**`WFInput` is uniform across all codes.** Set it as `{ Type: "Variable", Variable: { Value: <ActionOutput or Variable>, WFSerializationType: "WFTextTokenAttachment" } }`. The previously documented "implicit input for numeric codes 0–3" rule was incorrect; verified against the Apple sample, every conditional including codes 0/1/2/3/1003 sets `WFInput` explicitly.
|
|
222
|
+
|
|
223
|
+
**Multi-condition Ifs** (Any are true / All are true) use `WFConditions` with `WFSerializationType = WFContentPredicateTableTemplate` and `WFActionParameterFilterTemplates` instead of top-level `WFCondition` + `WFInput`. See CONTROL_FLOW.md "Multi-condition If" for the full template. Mixing the two patterns on one action (both `WFConditions` AND top-level `WFCondition`) is a validator error.
|
|
224
|
+
- For unit conversions, require `measurement.create` + `measurement.convert` unless a Comment includes `ALLOW_MANUAL_UNIT_CONVERSION`.
|
|
225
|
+
|
|
226
|
+
**Command (must run):**
|
|
227
|
+
```
|
|
228
|
+
python3 scripts/validate_shortcut.py /path/to/Shortcut.xml
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Run the **Craig Loop**: validate → read errors → make targeted fixes → re-validate. Max 5 iterations. If the same error persists across 2 consecutive attempts, stop and report to the user. Do not re-run without making a real code change (see SKILL.md "Craig Loop Protocol" for full rules).
|
|
232
|
+
|
|
233
|
+
The validator is mandatory. Do **not** skip it or claim it is optional; if it fails to run, fix the validator or the environment and re-run. Only allow empty parameters for user-configurable fields (e.g., HomeKit accessories or user contacts), and document those with a Comment.
|
|
234
|
+
|
|
235
|
+
## Comment Guidance (Manual Fix Hints)
|
|
236
|
+
|
|
237
|
+
Because some variable wiring may require manual correction, **insert a concise Comment before each major block** with a short **bulleted list** describing the expected variable wiring. Keep it brief, action-oriented, and specific.
|
|
238
|
+
|
|
239
|
+
**No UUIDs in Comments (mandatory):** Comment actions must contain ONLY descriptive natural language. Describe WHAT the action does and WHY, not HOW it's wired internally. Never include UUIDs, OutputUUID references, or technical plist details. If someone reading only the Comment text sees anything resembling `A1B2C3D4-...` or `(UUID: ...)`, the comment is wrong.
|
|
240
|
+
|
|
241
|
+
Example:
|
|
242
|
+
- Set **Start Date Raw** from the **Adjust Date** output above
|
|
243
|
+
- Format **Start Date** with `yyyy-MM-dd`
|
|
244
|
+
- Use **Repeat Item 2** for inner loop dictionary values
|
|
245
|
+
|
|
246
|
+
## Control Flow & Menus
|
|
247
|
+
|
|
248
|
+
- Keep `GroupingIdentifier` consistent across the start/middle/end actions of **If**, **Repeat**, and **Choose from Menu** blocks; always close control-flow groups.
|
|
249
|
+
- Add an **Otherwise** branch when you need explicit false handling; use **Nothing** as a placeholder output if a branch should intentionally return no result.
|
|
250
|
+
- Guard empty lists with **Count → If** before **Choose from List** or **Repeat with Each**.
|
|
251
|
+
- Inside **Repeat with Each**, use **Repeat Item** for per-item extraction (**Get Dictionary Value**, **Get Name**, file uploads); avoid **Repeat Results** inside the loop.
|
|
252
|
+
- For **Count**, include both `WFInput` and `Input` pointing to the same variable so the UI shows the selected list.
|
|
253
|
+
- For **Add Item to List**, use `WFInsertPosition` values `Beginning`, `End`, or `Index`; include `WFItemIndex` only for `Index`, and use a 1-based integer.
|
|
254
|
+
- For **Choose from Menu**, ensure the number of menu item actions matches `WFMenuItems`, and keep titles consistent between the menu list and each case.
|
|
255
|
+
- For confirmations (Yes/No), use **Choose from Menu** or **Choose from List** with explicit options; **Show Alert does not return a choice** and should be used only for informational messages or if the user explicitly wants OK/Cancel.
|
|
256
|
+
- When the user asks for manual date entry with a date picker, default **Ask for Input** to **Date and Time** (not just Date) unless they explicitly request date-only.
|
|
257
|
+
- For string comparisons in **If**, especially after **Get Dictionary Value → Text**, set a named variable first (e.g., **Set Variable: Project Name Text**) instead of referencing a raw action output.
|
|
258
|
+
- For string comparisons in **If**, prefer **Get Dictionary Value → Text → Set Variable → If** and insert the named variable directly in the If input field. Do not add a redundant **Get Variable** hop unless you need a specific aggrandizement.
|
|
259
|
+
- If validator rules still conflict for a string comparison, replace the string If with `Match Text` + `Count` + numeric If (e.g., Count > 0) to avoid wrapper conflicts.
|
|
260
|
+
- For nested **Repeat with Each**, use **Repeat Item 2** (and **Repeat Index 2**) inside the inner loop; deeper nesting uses 3, 4, etc.
|
|
261
|
+
|
|
262
|
+
## Lists & Dictionaries
|
|
263
|
+
|
|
264
|
+
- Initialize a list before **Append to Variable**; use **Append Variable** inside **Repeat with Each** to build lists.
|
|
265
|
+
- After building a list, use **Combine Text** or **Choose from List** for output/selection. On OS 26.4+, Choose from List can take dictionaries and named text directly again; do not fall back to vCard/VCF formatting unless the user explicitly asks for contact-card formatting. Choose from List multi-select toggles must be booleans; only enable **Select All Initially** when **Select Multiple** is also enabled.
|
|
266
|
+
- If JSON arrives as text, run **Detect Dictionary** before **Get Dictionary Value**.
|
|
267
|
+
- Use dot-notation keys (and tokenized keys when dynamic) in **Get Dictionary Value** to access nested data, but avoid direct optional paths like `error.message` before guarding the parent key.
|
|
268
|
+
- When constructing **Dictionary** actions, use `WFDictionaryFieldValue` wrappers (not raw arrays) and the correct item types for strings, nested dictionaries, and booleans.
|
|
269
|
+
- Shortcuts bug: when comparing a **Dictionary Value** (text) in an **If** condition, you must first pass it through a **Text** action, then compare the Text variable. Direct comparisons against Dictionary Value often appear blank and fail.
|
|
270
|
+
|
|
271
|
+
## Messages & Send Message Content
|
|
272
|
+
|
|
273
|
+
- **Send Message content must reference a named variable built with at least 2 Append Variable actions**, even when sending only one content type such as photos, files, or text.
|
|
274
|
+
- Do **not** wire Send Message content directly to an action output, and do **not** use `WFSendMessageAttachments` together with `WFSendMessageContent`.
|
|
275
|
+
- Use **Add to Variable** (`is.workflow.actions.appendvariable`) to build the message payload:
|
|
276
|
+
- For mixed content, append the file/audio/photo variable, then append the text/location text.
|
|
277
|
+
- For single-type content, append the source variable, then append an empty Text action output to the same variable. This preserves the multi-item payload shape that Send Message imports reliably and that the validator enforces.
|
|
278
|
+
- Do **not** initialize the variable first; just keep appending.
|
|
279
|
+
- Pass the named variable to **Send Message** as `WFSendMessageContent`.
|
|
280
|
+
- Send Message content must use `WFTextTokenString` with a variable placeholder (not `WFTextTokenAttachment`), otherwise the message field can appear blank in the editor.
|
|
281
|
+
|
|
282
|
+
Single-type example:
|
|
283
|
+
|
|
284
|
+
```text
|
|
285
|
+
Find Photos -> Append Variable "Message Items"
|
|
286
|
+
Text "" -> Append Variable "Message Items"
|
|
287
|
+
Send Message content -> WFTextTokenString placeholder for "Message Items"
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## Text & Parsing
|
|
291
|
+
|
|
292
|
+
- Use **Match Text → Get Group** to extract values with regex; follow with **Replace Text**/**Split Text**/**Combine Text** to normalize output.
|
|
293
|
+
- For **Match Text** (`is.workflow.actions.text.match`), wire source text using the `text` parameter (typically a `WFTextTokenString` placeholder), not `WFInput`.
|
|
294
|
+
- For **Replace Text** (`is.workflow.actions.text.replace`), use `WFInput` for source text. Do not use a `text` key for this action.
|
|
295
|
+
- For **Replace Text** (`is.workflow.actions.text.replace`), wire `WFInput` using a `WFTextTokenString` placeholder (or wrapped variable form), not a bare `WFTextTokenAttachment`, to avoid empty-looking inputs on iOS.
|
|
296
|
+
- For **Change Case** (`is.workflow.actions.text.changecase`), wire source text via the `text` parameter. Do not use `WFInput`.
|
|
297
|
+
- For **Split Text** (`is.workflow.actions.text.split`), wire source text via the `text` parameter. Do not use `WFInput`. If `WFTextSeparator` is `Custom`, you must also set `WFTextCustomSeparator` (space is valid).
|
|
298
|
+
- For Replace Text delete-match operations, prefer omitting `WFReplaceTextReplace`; explicit empty replacement is allowed, but omission is cleaner.
|
|
299
|
+
- For **Find Notes** (`is.workflow.actions.filter.notes`), keep `WFContentItemFilter` as `WFContentPredicateTableTemplate` with non-empty `WFActionParameterFilterTemplates`.
|
|
300
|
+
- For **Find Notes** name filters, use `Values.String` as a `WFTextTokenString` state so variable text remains visible in the editor.
|
|
301
|
+
- For **Find Notes** folder filters, use `Values.Enumeration` with `WFLinkDynamicOptionSubstitutableState`, wrapping an inner `WFTextTokenAttachment` variable value.
|
|
302
|
+
- Use **Get Rich Text from Markdown** or **Get Markdown from Rich Text** when moving between plain text and rich-text apps.
|
|
303
|
+
- When presenting results with **Text**, interleave literals and variables in one Text action using placeholders and correct `attachmentsByRange` for each variable.
|
|
304
|
+
|
|
305
|
+
## Files & Output
|
|
306
|
+
|
|
307
|
+
- Prefer **Select File** (`is.workflow.actions.file.select`) for user file input; use **Document Picker Open/Save** when you need explicit Files UI flows.
|
|
308
|
+
- Use **Set Name** (`is.workflow.actions.setitemname`) before **Save File** or **Share** when you need a renamed copy/new file object.
|
|
309
|
+
- For **Set Name**, always provide both `WFInput` (the source file/item) and `WFName` (for example, `Test.txt`). The action outputs **Renamed Item**; wire that output into **Save File** (`is.workflow.actions.documentpicker.save`) or **Share**.
|
|
310
|
+
- Do **not** use **Rename File** (`is.workflow.actions.file.rename`) for "rename, save/share elsewhere, then delete original" workflows. Rename File mutates the original file at its existing path in place; it does not create a separate renamed file object for saving elsewhere.
|
|
311
|
+
- For rename-and-save workflows, keep the original file in a named variable (for example, **Original File**), use **Set Name** on that variable to produce **Renamed Item**, save/share **Renamed Item**, then delete **Original File** only if the user explicitly asked to remove the source after saving.
|
|
312
|
+
- For file outputs, preview with **Quick Look** before **Save/Share** when appropriate.
|
|
313
|
+
- For PDFs, a common pattern is **Make PDF → Preview Document → Save/Share**.
|
|
314
|
+
- For **Notes** output, use the supported Notes action for the target OS (AppIntent on iOS/iPadOS). If Notes isn’t available, fall back to **Share** or **Save File** and add a Comment explaining the fallback.
|
|
315
|
+
|
|
316
|
+
## Signing & Install Naming
|
|
317
|
+
|
|
318
|
+
- **Duplicate shortcut names cause silent skips**: If a shortcut with the same name already exists in the user's library, importing via `open -a Shortcuts` or `shortcuts run` on the signed file will silently skip it. The `shortcuts` CLI does not have `import` or `delete` subcommands — instruct the user to delete the old shortcut manually before importing the new one.
|
|
319
|
+
- Sign using a canonical filename that matches the intended shortcut name; do not append `_signed`.
|
|
320
|
+
- If `shortcuts sign` says `The file doesn't exist` for an existing file, retry from a clean XML copy to a `.shortcut` path (for example, `cp source.xml /tmp/MyShortcut.shortcut`), then sign again.
|
|
321
|
+
- If `shortcuts sign` says `The file couldn't be opened because it isn't in the correct format` while the XML passes `validate-shortcut` and `plutil -lint`, retry after `plutil -convert binary1` on the final `.shortcut` copy. The bundled signer wrapper performs this retry automatically; if both XML and binary signing fail, retry from an unrestricted shell and check filesystem permissions before treating the plist as malformed.
|
|
322
|
+
- `shortcuts` automation should rely only on supported subcommands: `run`, `list`, `view`, and `sign`.
|
|
323
|
+
- During install verification, treat a `_signed` library name as failure and reinstall using a canonical filename.
|
|
324
|
+
|
|
325
|
+
## Numbers & Accumulators
|
|
326
|
+
|
|
327
|
+
- Inside a **Repeat with Each**, do not **Set Variable** directly to a running total each pass. Use a temp variable for the current value, then **Math** (add/subtract) into a master total variable, and **Set Variable** to the master total.
|
|
328
|
+
- To convert cents to dollars, divide by **100** first, then round to hundredths if needed (do not “round to hundredths” as a substitute for division).
|
|
329
|
+
- If a **Math** action uses operand **100** on a cents value, the operator must be **÷** (not + or −).
|
|
330
|
+
- Prefer **com.apple.Notes.CreateNoteFromMarkdownLinkAction** for markdown output on older targets. On OS 27+, `com.apple.mobilenotes.SharingExtension` exposes `interpretAsMarkdown`; use it only when the target OS supports that parameter. Plain Create Note AppIntents remain fine for rich/plain text.
|
|
331
|
+
|
|
332
|
+
## Shortcut Building Techniques
|
|
333
|
+
|
|
334
|
+
- Start every shortcut with:
|
|
335
|
+
1) A detailed Comment describing what the shortcut does.
|
|
336
|
+
2) A second Comment with the original user prompt in this exact format:
|
|
337
|
+
|
|
338
|
+
```
|
|
339
|
+
Shortcuts generated by Shortcuts Playground. May contain mistakes. Always check the shortcut's actions first.
|
|
340
|
+
|
|
341
|
+
This shortcut was created via the following user prompt:
|
|
342
|
+
|
|
343
|
+
> {User Prompt}
|
|
344
|
+
```
|
|
345
|
+
- For any shortcut longer than ~20 actions, add section headers as Comment blocks (e.g., `--- FETCH TASKS ---`, `--- BUILD LIST ---`, `--- UPDATE TASKS ---`).
|
|
346
|
+
|
|
347
|
+
- Use variable names with spaces (e.g., `PDF Pages`).
|
|
348
|
+
- Prefer actions that work on both iOS and macOS unless the user explicitly wants macOS-only behavior.
|
|
349
|
+
- Example: Instead of **Get Parent Folder** (macOS-only), use **Get Details of File** and extract the path.
|
|
350
|
+
- To get PDF pages, use **Split PDF Into Pages**, then **Count** the resulting items.
|
|
351
|
+
- For selecting videos, default to **Select Photos** configured for videos. If both Photos and Files are requested, use **Choose from Menu** with both pickers.
|
|
352
|
+
|
|
353
|
+
## Known Validator Gaps
|
|
354
|
+
|
|
355
|
+
There are no current false-positive exemptions. Do not waive validator failures unless this section lists an explicit, runtime-verified exception.
|
|
356
|
+
|
|
357
|
+
(The previously-listed "code 0 truthiness bug" and "numeric If implicit input" gaps were both fixed when the conditional system was rewritten against the Apple sample shortcut. The validator now correctly handles code 0 via `is None` checks and rejects implicit input for every condition code, matching the documented behavior.)
|