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.
Files changed (68) hide show
  1. package/README.md +54 -0
  2. package/agents/shortcut-builder.md +172 -0
  3. package/agents/shortcut-remixer.md +239 -0
  4. package/commands/build-shortcut.md +32 -0
  5. package/commands/remix-shortcut.md +34 -0
  6. package/opencode.json +30 -0
  7. package/package.json +47 -0
  8. package/plugin/validator.ts +179 -0
  9. package/skills/shortcuts-playground/ACTIONS.md +713 -0
  10. package/skills/shortcuts-playground/APPINTENTS.md +2760 -0
  11. package/skills/shortcuts-playground/AUTOMATION_TRIGGERS.md +133 -0
  12. package/skills/shortcuts-playground/BEST_PRACTICES.md +357 -0
  13. package/skills/shortcuts-playground/CHANGELOG.md +585 -0
  14. package/skills/shortcuts-playground/CONTROL_FLOW.md +777 -0
  15. package/skills/shortcuts-playground/DATE_TIME.md +75 -0
  16. package/skills/shortcuts-playground/EXAMPLES.md +738 -0
  17. package/skills/shortcuts-playground/FILTERS.md +697 -0
  18. package/skills/shortcuts-playground/HEALTHKIT.md +317 -0
  19. package/skills/shortcuts-playground/ICONS_AND_COLORS.md +89 -0
  20. package/skills/shortcuts-playground/JAVASCRIPT_WEBPAGE.md +51 -0
  21. package/skills/shortcuts-playground/PARAMETER_TYPES.md +1117 -0
  22. package/skills/shortcuts-playground/PLIST_FORMAT.md +296 -0
  23. package/skills/shortcuts-playground/README.md +77 -0
  24. package/skills/shortcuts-playground/SKILL.md +459 -0
  25. package/skills/shortcuts-playground/THIRD_PARTY_ACTIONS.md +77 -0
  26. package/skills/shortcuts-playground/TOOLKIT_SNAPSHOT.md +62 -0
  27. package/skills/shortcuts-playground/URL_SCHEMES.md +59 -0
  28. package/skills/shortcuts-playground/VARIABLES.md +569 -0
  29. package/skills/shortcuts-playground/assets/shortcuts-small.svg +14 -0
  30. package/skills/shortcuts-playground/assets/shortcuts.png +0 -0
  31. package/skills/shortcuts-playground/data/healthkit-ios26.2-reference.json +2603 -0
  32. package/skills/shortcuts-playground/data/macos27-shortpy-grounding.json +4085 -0
  33. package/skills/shortcuts-playground/data/macos27-workflow-trigger-samples.json +1 -0
  34. package/skills/shortcuts-playground/data/shortcuts-glyph-synonyms.json +5102 -0
  35. package/skills/shortcuts-playground/data/shortcuts-icon-colors.json +107 -0
  36. package/skills/shortcuts-playground/data/shortcuts-official-glyph-mapping.json +509 -0
  37. package/skills/shortcuts-playground/data/toolkit-v63-tool-ids.json +1806 -0
  38. package/skills/shortcuts-playground/data/toolkit-v78-first-party-enum-cases.json +1 -0
  39. package/skills/shortcuts-playground/data/toolkit-v78-first-party-parameter-keys.json +1 -0
  40. package/skills/shortcuts-playground/data/toolkit-v78-ios27-tool-ids.json +1222 -0
  41. package/skills/shortcuts-playground/data/toolkit-v78-tool-ids.json +2745 -0
  42. package/skills/shortcuts-playground/data/toolkit-v78-trigger-parameter-keys.json +1051 -0
  43. package/skills/shortcuts-playground/golden-shortcuts/index.jsonl +19 -0
  44. package/skills/shortcuts-playground/golden-shortcuts/xml/1be4dde95b794253bf82438e201b33e7.xml +174 -0
  45. package/skills/shortcuts-playground/golden-shortcuts/xml/2e0fb675e45948aaacee7e534f910492.xml +1016 -0
  46. package/skills/shortcuts-playground/golden-shortcuts/xml/332c12a0060043b388b22b806be7ab58.xml +1924 -0
  47. package/skills/shortcuts-playground/golden-shortcuts/xml/3dd4ee24e43f464f92adaa70a0311eaa.xml +1177 -0
  48. package/skills/shortcuts-playground/golden-shortcuts/xml/4d102301c6e646faa7a8a221f4f4ec98.xml +321 -0
  49. package/skills/shortcuts-playground/golden-shortcuts/xml/51cc4e26d1044893a0c3f2f3630cf2d2.xml +1933 -0
  50. package/skills/shortcuts-playground/golden-shortcuts/xml/623e7f1ca5f948e2bd53811fec63e544.xml +136 -0
  51. package/skills/shortcuts-playground/golden-shortcuts/xml/6a18b76843ac45c384ac3400f3740997.xml +1791 -0
  52. package/skills/shortcuts-playground/golden-shortcuts/xml/71f0cacb0f604b399b76c5dcb7286e7c.xml +2228 -0
  53. package/skills/shortcuts-playground/golden-shortcuts/xml/807525ed9f974829bc8494defac923a8.xml +259 -0
  54. package/skills/shortcuts-playground/golden-shortcuts/xml/8ab0d39826ab4249be58672763caa3ba.xml +420 -0
  55. package/skills/shortcuts-playground/golden-shortcuts/xml/91c45fd3fd3b427897d9ba485efb1227.xml +800 -0
  56. package/skills/shortcuts-playground/golden-shortcuts/xml/97be626bb25c41709d175646a7f6d8f2.xml +424 -0
  57. package/skills/shortcuts-playground/golden-shortcuts/xml/ae59e10d409348f9bd33894f03f9beb4.xml +568 -0
  58. package/skills/shortcuts-playground/golden-shortcuts/xml/afa83b6be811483b9c32189c41eb9312.xml +391 -0
  59. package/skills/shortcuts-playground/golden-shortcuts/xml/cfdb083b857e4ac189629fa386d27cdc.xml +193 -0
  60. package/skills/shortcuts-playground/golden-shortcuts/xml/e01cebe192d64b2fbca80204d03d92ab.xml +111 -0
  61. package/skills/shortcuts-playground/golden-shortcuts/xml/ef669bcf8bad489d9ef4b88bfaf5772f.xml +780 -0
  62. package/skills/shortcuts-playground/golden-shortcuts/xml/f44f5caf5e3e48d4817e73af450c4404.xml +570 -0
  63. package/skills/shortcuts-playground/scripts/generate_healthkit_reference.py +394 -0
  64. package/skills/shortcuts-playground/scripts/lookup_action_grounding.py +1164 -0
  65. package/skills/shortcuts-playground/scripts/select_shortcut_icon_color.py +597 -0
  66. package/skills/shortcuts-playground/scripts/test_random_mixed_shortcuts.py +1067 -0
  67. package/skills/shortcuts-playground/scripts/test_wiring_regressions.py +2247 -0
  68. package/skills/shortcuts-playground/scripts/validate_shortcut.py +4526 -0
@@ -0,0 +1,394 @@
1
+ #!/usr/bin/env python3
2
+ """Generate the bundled HealthKit reference used by the validator tests.
3
+
4
+ The plist syntax for Shortcuts Health actions comes from anonymized iOS
5
+ Shortcuts XML examples captured while building this reference. This script
6
+ supplements that with the installed iPhoneOS SDK so the test suite can cover
7
+ every HealthKit type/value family available on the generator machine.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ import plistlib
14
+ import re
15
+ import subprocess
16
+ from datetime import date
17
+ from pathlib import Path
18
+
19
+
20
+ TYPE_PREFIXES = {
21
+ "HKQuantityTypeIdentifier": "quantity_types",
22
+ "HKCategoryTypeIdentifier": "category_types",
23
+ "HKCharacteristicTypeIdentifier": "characteristic_types",
24
+ "HKCorrelationTypeIdentifier": "correlation_types",
25
+ "HKDocumentTypeIdentifier": "document_types",
26
+ }
27
+
28
+ LABEL_OVERRIDES = {
29
+ "ActiveEnergyBurned": "Active Calories",
30
+ "DietaryCaffeine": "Caffeine",
31
+ "DistanceWalkingRunning": "Walking + Running Distance",
32
+ "FlightsClimbed": "Flights Climbed",
33
+ "GeneralizedBodyAche": "Body and Muscle Ache",
34
+ "StepCount": "Step Count",
35
+ "VO2Max": "VO2 Max",
36
+ }
37
+
38
+ OBSERVED_SHORTCUTS_LABELS = {
39
+ "HKCategoryTypeIdentifierBloating": ["Bloating"],
40
+ "HKCategoryTypeIdentifierCervicalMucusQuality": ["Cervical Mucus Quality"],
41
+ "HKCategoryTypeIdentifierGeneralizedBodyAche": ["Body and Muscle Ache"],
42
+ "HKQuantityTypeIdentifierActiveEnergyBurned": ["Active Calories"],
43
+ "HKQuantityTypeIdentifierBodyMassIndex": ["Body Mass Index"],
44
+ "HKQuantityTypeIdentifierDietaryCaffeine": ["Caffeine"],
45
+ "HKQuantityTypeIdentifierDistanceWalkingRunning": ["Walking + Running Distance"],
46
+ "HKQuantityTypeIdentifierFlightsClimbed": ["Flights Climbed"],
47
+ "HKQuantityTypeIdentifierStepCount": ["Step Count"],
48
+ }
49
+
50
+ OBSERVED_FIND_SAMPLES_LABELS = {
51
+ "HKCategoryTypeIdentifierSleepAnalysis": ["Sleep"],
52
+ "HKQuantityTypeIdentifierActiveEnergyBurned": ["Active Calories"],
53
+ "HKQuantityTypeIdentifierAppleExerciseTime": ["Exercise Minutes"],
54
+ "HKQuantityTypeIdentifierStepCount": ["Steps"],
55
+ }
56
+
57
+ ACTIONKIT_HEALTH_CONSTANTS = Path(
58
+ "/System/Library/PrivateFrameworks/ActionKit.framework/Versions/A/Resources/"
59
+ "WFHealthKitConstants.plist"
60
+ )
61
+
62
+ SAMPLE_DETAIL_PROPERTIES = [
63
+ "Type",
64
+ "Value",
65
+ "Unit",
66
+ "Start Date",
67
+ "End Date",
68
+ "Duration",
69
+ "Source",
70
+ "Name",
71
+ ]
72
+
73
+ BUNDLED_XML_EVIDENCE = [
74
+ {
75
+ "evidence_id": "find_health_samples_quantity_filter",
76
+ "coverage": [
77
+ "is.workflow.actions.filter.health.quantity",
78
+ "WFContentItemFilter",
79
+ "Type filter row",
80
+ "WFContentItemLimitEnabled",
81
+ ],
82
+ },
83
+ {
84
+ "evidence_id": "log_health_sample_category_without_visible_value",
85
+ "coverage": [
86
+ "is.workflow.actions.health.quantity.log",
87
+ "category type with no visible value row",
88
+ "WFQuantitySampleType",
89
+ "WFQuantitySampleQuantity",
90
+ "WFQuantitySampleAdditionalQuantity",
91
+ ],
92
+ "observed_parameters": {
93
+ "WFQuantitySampleType": "Bloating",
94
+ "WFQuantitySampleQuantity": "WFQuantityFieldValue retained even when the editor hides Value",
95
+ "WFQuantitySampleAdditionalQuantity": "WFQuantityFieldValue with Unit only",
96
+ },
97
+ },
98
+ {
99
+ "evidence_id": "log_health_sample_category_with_enum_value",
100
+ "coverage": [
101
+ "is.workflow.actions.health.quantity.log",
102
+ "category value picker",
103
+ "WFCategorySampleEnumeration",
104
+ "WFQuantitySampleQuantity",
105
+ "WFQuantitySampleAdditionalQuantity",
106
+ ],
107
+ "observed_parameters": {
108
+ "WFQuantitySampleType": "Cervical Mucus Quality",
109
+ "WFCategorySampleEnumeration": "Dry",
110
+ "WFQuantitySampleQuantity": "WFQuantityFieldValue with Magnitude + Unit",
111
+ "WFQuantitySampleAdditionalQuantity": "WFQuantityFieldValue with Unit only",
112
+ },
113
+ },
114
+ {
115
+ "evidence_id": "log_workout_identifier_only",
116
+ "coverage": ["is.workflow.actions.health.workout.log"],
117
+ "note": "Exported before workout permission/configuration was granted; UUID-only shape is not enough for generated shortcuts.",
118
+ },
119
+ {
120
+ "evidence_id": "get_details_of_health_sample",
121
+ "coverage": [
122
+ "is.workflow.actions.properties.health.quantity",
123
+ "WFContentItemPropertyName",
124
+ "WFInput",
125
+ ],
126
+ },
127
+ ]
128
+
129
+ BUNDLED_OBSERVED_SHAPES = [
130
+ {
131
+ "evidence_id": "log_health_sample_quantity",
132
+ "note": (
133
+ "An anonymized iOS XML example established this quantity schema; "
134
+ "the preserved reference keeps only the parameter shape, not the "
135
+ "user's source path."
136
+ ),
137
+ "observed_parameters": {
138
+ "WFQuantitySampleType": "Caffeine",
139
+ "WFQuantitySampleQuantity": "WFQuantityFieldValue with Magnitude + Unit",
140
+ "WFQuantitySampleAdditionalQuantity": "WFQuantityFieldValue with Unit only",
141
+ },
142
+ }
143
+ ]
144
+
145
+
146
+ def sdk_path() -> Path:
147
+ raw = subprocess.check_output(["xcrun", "--sdk", "iphoneos", "--show-sdk-path"], text=True)
148
+ return Path(raw.strip())
149
+
150
+
151
+ def actionkit_units() -> list[dict[str, object]]:
152
+ if not ACTIONKIT_HEALTH_CONSTANTS.exists():
153
+ return []
154
+ try:
155
+ with ACTIONKIT_HEALTH_CONSTANTS.open("rb") as handle:
156
+ payload = plistlib.load(handle)
157
+ except (OSError, plistlib.InvalidFileException):
158
+ return []
159
+ units = payload.get("Units")
160
+ if not isinstance(units, list):
161
+ return []
162
+ out: list[dict[str, object]] = []
163
+ for row in units:
164
+ if not isinstance(row, dict):
165
+ continue
166
+ unit_string = row.get("unitString")
167
+ if not isinstance(unit_string, str) or unit_string == "":
168
+ continue
169
+ out.append(
170
+ {
171
+ "unit": unit_string,
172
+ "group": row.get("group"),
173
+ "important": bool(row.get("important", False)),
174
+ }
175
+ )
176
+ return out
177
+
178
+
179
+ def split_camel(name: str) -> str:
180
+ if name in LABEL_OVERRIDES:
181
+ return LABEL_OVERRIDES[name]
182
+ working = name
183
+ if working.startswith("Dietary") and len(working) > len("Dietary"):
184
+ working = working[len("Dietary") :]
185
+ working = re.sub(r"(?<=[a-z0-9])(?=[A-Z])", " ", working)
186
+ working = re.sub(r"(?<=[A-Z])(?=[A-Z][a-z])", " ", working)
187
+ working = working.replace(" VO 2 ", " VO2 ")
188
+ working = working.replace(" S D N N", " SDNN")
189
+ return working.strip()
190
+
191
+
192
+ def parse_types(header: Path) -> dict[str, list[dict[str, str]]]:
193
+ out: dict[str, list[dict[str, str]]] = {bucket: [] for bucket in TYPE_PREFIXES.values()}
194
+ line_re = re.compile(
195
+ r"HK_EXTERN\s+"
196
+ r"(?P<prefix>HK(?:Quantity|Category|Characteristic|Correlation|Document)TypeIdentifier)"
197
+ r"\s+const\s+"
198
+ r"(?P<symbol>HK(?:Quantity|Category|Characteristic|Correlation|Document)TypeIdentifier(?P<suffix>[A-Za-z0-9]+))"
199
+ r"\s+(?P<availability>.*?);\s*(?://\s*(?P<comment>.*))?$"
200
+ )
201
+ for raw_line in header.read_text(encoding="utf-8").splitlines():
202
+ match = line_re.search(raw_line)
203
+ if not match:
204
+ continue
205
+ bucket = TYPE_PREFIXES[match.group("prefix")]
206
+ suffix = match.group("suffix")
207
+ symbol = match.group("symbol")
208
+ row = {
209
+ "symbol": symbol,
210
+ "sdk_suffix": suffix,
211
+ "shortcut_label_guess": split_camel(suffix),
212
+ "availability": match.group("availability").strip(),
213
+ "unit_and_aggregation": (match.group("comment") or "").strip(),
214
+ }
215
+ if symbol in OBSERVED_SHORTCUTS_LABELS:
216
+ row["observed_shortcuts_labels"] = OBSERVED_SHORTCUTS_LABELS[symbol]
217
+ if symbol in OBSERVED_FIND_SAMPLES_LABELS:
218
+ row["observed_find_samples_labels"] = OBSERVED_FIND_SAMPLES_LABELS[symbol]
219
+ out[bucket].append(row)
220
+ return out
221
+
222
+
223
+ def value_label(enum_name: str, constant: str) -> str:
224
+ suffix = constant
225
+ if constant.startswith(enum_name):
226
+ suffix = constant[len(enum_name) :]
227
+ elif constant.startswith("HKCategoryValue"):
228
+ suffix = constant[len("HKCategoryValue") :]
229
+ return split_camel(suffix)
230
+
231
+
232
+ def parse_category_values(header: Path) -> dict[str, list[dict[str, object]]]:
233
+ text = header.read_text(encoding="utf-8")
234
+ enum_re = re.compile(
235
+ r"typedef NS_ENUM\(NSInteger,\s*(?P<enum>HKCategoryValue[A-Za-z0-9]+)\)\s*\{(?P<body>.*?)\}",
236
+ re.S,
237
+ )
238
+ out: dict[str, list[dict[str, object]]] = {}
239
+ for enum_match in enum_re.finditer(text):
240
+ enum_name = enum_match.group("enum")
241
+ values: list[dict[str, object]] = []
242
+ previous_int: int | None = None
243
+ for raw_line in enum_match.group("body").splitlines():
244
+ line = raw_line.strip()
245
+ if not line.startswith("HKCategoryValue"):
246
+ continue
247
+ const = line.split(None, 1)[0].rstrip(",")
248
+ expr = None
249
+ if "=" in line:
250
+ expr = line.rsplit("=", 1)[1].split(",", 1)[0].strip()
251
+ if expr is None:
252
+ raw_value: int | str | None = previous_int + 1 if previous_int is not None else None
253
+ else:
254
+ try:
255
+ raw_value = int(expr, 0)
256
+ previous_int = raw_value
257
+ except ValueError:
258
+ raw_value = expr
259
+ if isinstance(raw_value, int):
260
+ previous_int = raw_value
261
+ values.append(
262
+ {
263
+ "symbol": const,
264
+ "shortcut_label_guess": value_label(enum_name, const),
265
+ "value": raw_value,
266
+ }
267
+ )
268
+ out[enum_name] = values
269
+ return out
270
+
271
+
272
+ def parse_workout_activity_types(header: Path) -> list[dict[str, object]]:
273
+ text = header.read_text(encoding="utf-8")
274
+ match = re.search(
275
+ r"typedef NS_ENUM\(NSUInteger,\s*HKWorkoutActivityType\)\s*\{(?P<body>.*?)\}",
276
+ text,
277
+ re.S,
278
+ )
279
+ if not match:
280
+ return []
281
+ values: list[dict[str, object]] = []
282
+ previous_int: int | None = None
283
+ for raw_line in match.group("body").splitlines():
284
+ line = raw_line.strip()
285
+ if not line.startswith("HKWorkoutActivityType"):
286
+ continue
287
+ const = line.split(None, 1)[0].rstrip(",")
288
+ suffix = const[len("HKWorkoutActivityType") :]
289
+ expr = None
290
+ if "=" in line:
291
+ expr = line.rsplit("=", 1)[1].split(",", 1)[0].strip()
292
+ if expr is None:
293
+ raw_value: int | str | None = previous_int + 1 if previous_int is not None else None
294
+ else:
295
+ try:
296
+ raw_value = int(expr, 0)
297
+ previous_int = raw_value
298
+ except ValueError:
299
+ raw_value = expr
300
+ if isinstance(raw_value, int):
301
+ previous_int = raw_value
302
+ values.append(
303
+ {
304
+ "symbol": const,
305
+ "shortcut_label_guess": split_camel(suffix),
306
+ "value": raw_value,
307
+ }
308
+ )
309
+ return values
310
+
311
+
312
+ def quantity_unit_hints(quantity_types: list[dict[str, object]]) -> list[str]:
313
+ out: set[str] = set()
314
+ for row in quantity_types:
315
+ raw = str(row.get("unit_and_aggregation") or "").split(",", 1)[0].strip()
316
+ if raw:
317
+ out.add(raw)
318
+ return sorted(out)
319
+
320
+
321
+ def main() -> int:
322
+ root = Path(__file__).resolve().parents[1]
323
+ sdk = sdk_path()
324
+ headers = sdk / "System/Library/Frameworks/HealthKit.framework/Headers"
325
+ types = parse_types(headers / "HKTypeIdentifiers.h")
326
+ category_values = parse_category_values(headers / "HKCategoryValues.h")
327
+ workouts = parse_workout_activity_types(headers / "HKWorkout.h")
328
+ units = actionkit_units()
329
+
330
+ payload = {
331
+ "generated_on": date.today().isoformat(),
332
+ "source": {
333
+ "sdk_path": str(sdk),
334
+ "headers": [
335
+ "HealthKit/HKTypeIdentifiers.h",
336
+ "HealthKit/HKCategoryValues.h",
337
+ "HealthKit/HKWorkout.h",
338
+ ],
339
+ "shortcut_xml_syntax_source": (
340
+ "Anonymized iOS Shortcuts XML examples captured while building "
341
+ "this reference; user-specific source paths are intentionally omitted."
342
+ ),
343
+ },
344
+ "bundled_xml_evidence": BUNDLED_XML_EVIDENCE,
345
+ "bundled_observed_shapes": BUNDLED_OBSERVED_SHAPES,
346
+ "shortcuts_actions": {
347
+ "find_health_samples": {
348
+ "identifier": "is.workflow.actions.filter.health.quantity",
349
+ "sample_output_name": "Health Samples",
350
+ "required_parameters": ["WFContentItemFilter"],
351
+ },
352
+ "get_health_sample_detail": {
353
+ "identifier": "is.workflow.actions.properties.health.quantity",
354
+ "required_parameters": ["WFInput", "WFContentItemPropertyName"],
355
+ "properties": SAMPLE_DETAIL_PROPERTIES,
356
+ },
357
+ "log_health_sample": {
358
+ "identifier": "is.workflow.actions.health.quantity.log",
359
+ "required_parameters": ["WFQuantitySampleType"],
360
+ "quantity_parameters": [
361
+ "WFQuantitySampleQuantity",
362
+ "WFQuantitySampleAdditionalQuantity",
363
+ ],
364
+ "category_parameters": [
365
+ "WFCategorySampleEnumeration",
366
+ "WFCategorySampleAdditionalEnumerationKey",
367
+ "WFQuantitySampleAdditionalEnumeration",
368
+ ],
369
+ "date_parameters": ["WFQuantitySampleDate", "WFSampleEndDate"],
370
+ },
371
+ "log_workout": {
372
+ "identifier": "is.workflow.actions.health.workout.log",
373
+ "required_parameters": ["WFWorkoutReadableActivityType"],
374
+ "quantity_parameters": [
375
+ "WFWorkoutDuration",
376
+ "WFWorkoutCaloriesQuantity",
377
+ "WFWorkoutDistanceQuantity",
378
+ ],
379
+ "date_parameters": ["WFWorkoutDate"],
380
+ },
381
+ },
382
+ **types,
383
+ "quantity_units": units,
384
+ "quantity_unit_hints": quantity_unit_hints(types.get("quantity_types", [])),
385
+ "category_values": category_values,
386
+ "workout_activity_types": workouts,
387
+ }
388
+ out_path = root / "data/healthkit-ios26.2-reference.json"
389
+ out_path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
390
+ return 0
391
+
392
+
393
+ if __name__ == "__main__":
394
+ raise SystemExit(main())