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,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())
|