{"allowContribute":false,"item":{"content":"此文件是給 Claude 的**生成規格**，請依此表產生 `.shortcut` 二進位 plist 檔案。\n\n---\n\n## API 端點\n\n| 欄位 | 值 |\n|------|-----|\n| URL | `https://api.cubelv.com/plugin/快速飲食記錄/api/ingest-health` |\n| Method | POST |\n| Header | `x-api-key: cubelv-health-b182cb87` |\n| Content-Type | `application/json` |\n| Body | `{\"steps\":\u003cnum\u003e,\"restingHR\":\u003cnum\u003e,\"activeEnergy\":\u003cnum\u003e,\"walkDistance\":\u003cnum\u003e,\"sleepMinutes\":\u003cnum\u003e,\"date\":\"\u003cYYYY/MM/DD\u003e\"}` |\n\n---\n\n## 動作規格表（共 20 個動作）\n\n### UUID 前綴：`HEALTH-SYNC-`\n\n| # | 動作名稱 | WFWorkflowActionIdentifier | 鍵參數 | 值 | 輸入 UUID | 輸出說明 |\n|---|---------|---------------------------|--------|-----|-----------|---------|\n| 0 | 目前日期 | `is.workflow.actions.date` | — | — | — | → Date |\n| 1 | 調整日期 | `is.workflow.actions.date.adjust` | `WFAdjustDate` | `-1` (天) | UUID-0 | → Date(昨天) |\n| 2 | 格式化日期 | `is.workflow.actions.date.format` | `WFDateFormat` | `Custom` | UUID-1 | → Text |\n|  |  |  | `WFDateCustomFormat` | `yyyy/MM/dd` |  |  |\n| 3 | 設定變數 logDate | `is.workflow.actions.setvariable` | `WFVariableName` | `logDate` | UUID-2 |  |\n| 4 | 尋找步數 | `is.workflow.actions.health.find` | `WFHealthSampleType` | `Steps` | UUID-1（開始日期） | → [HealthSample] |\n|  |  |  | `WFHealthSampleStartDateMode` | `Relative` |  |  |\n|  |  |  | `WFHealthSampleGrouping` | `Day` |  |  |\n| 5 | 取第一項 | `is.workflow.actions.getitemfromlist` | `WFItemSpecifier` | `First Item` | UUID-4 | → HealthSample |\n| 6 | 取數值 | `is.workflow.actions.health.getdetail` | `WFHealthSampleDetail` | `Value` | UUID-5 | → Number |\n| 7 | 設定變數 steps | `is.workflow.actions.setvariable` | `WFVariableName` | `steps` | UUID-6 |  |\n| 8 | 尋找靜止心率 | `is.workflow.actions.health.find` | `WFHealthSampleType` | `Resting Heart Rate` | UUID-1（開始日期） | → [HealthSample] |\n|  |  |  | `WFHealthSampleStartDateMode` | `Relative` |  |  |\n|  |  |  | `WFHealthSampleGrouping` | `Day` |  |  |\n| 9 | 取第一項 | `is.workflow.actions.getitemfromlist` | `WFItemSpecifier` | `First Item` | UUID-8 | → HealthSample |\n| 10 | 取數值 | `is.workflow.actions.health.getdetail` | `WFHealthSampleDetail` | `Value` | UUID-9 | → Number |\n| 11 | 設定變數 restingHR | `is.workflow.actions.setvariable` | `WFVariableName` | `restingHR` | UUID-10 |  |\n| 12 | 尋找活動能量 | `is.workflow.actions.health.find` | `WFHealthSampleType` | `Active Energy` | UUID-1（開始日期） | → [HealthSample] |\n|  |  |  | `WFHealthSampleStartDateMode` | `Relative` |  |  |\n|  |  |  | `WFHealthSampleGrouping` | `Day` |  |  |\n| 13 | 取第一項 | `is.workflow.actions.getitemfromlist` | `WFItemSpecifier` | `First Item` | UUID-12 | → HealthSample |\n| 14 | 取數值 | `is.workflow.actions.health.getdetail` | `WFHealthSampleDetail` | `Value` | UUID-13 | → Number |\n| 15 | 設定變數 activeEnergy | `is.workflow.actions.setvariable` | `WFVariableName` | `activeEnergy` | UUID-14 |  |\n| 16 | 尋找步行距離 | `is.workflow.actions.health.find` | `WFHealthSampleType` | `Walking + Running Distance` | UUID-1（開始日期） | → [HealthSample] |\n|  |  |  | `WFHealthSampleStartDateMode` | `Relative` |  |  |\n|  |  |  | `WFHealthSampleGrouping` | `Day` |  |  |\n| 17 | 取第一項 | `is.workflow.actions.getitemfromlist` | `WFItemSpecifier` | `First Item` | UUID-16 | → HealthSample |\n| 18 | 取數值 | `is.workflow.actions.health.getdetail` | `WFHealthSampleDetail` | `Value` | UUID-17 | → Number |\n| 19 | 設定變數 walkDistance | `is.workflow.actions.setvariable` | `WFVariableName` | `walkDistance` | UUID-18 |  |\n| 20 | 尋找睡眠 | `is.workflow.actions.health.find` | `WFHealthSampleType` | `Sleep` | UUID-1（開始日期） | → [HealthSample] |\n|  |  |  | `WFHealthSampleStartDateMode` | `Relative` | UUID-0（結束日期） |  |\n|  |  |  | `WFHealthSampleGrouping` | `Day` |  |  |\n| 21 | 取第一項 | `is.workflow.actions.getitemfromlist` | `WFItemSpecifier` | `First Item` | UUID-20 | → HealthSample |\n| 22 | 取時間間隔 | `is.workflow.actions.health.gettime` | `WFTimeUnit` | `Minutes` | UUID-21 | → Number |\n| 23 | 設定變數 sleepMinutes | `is.workflow.actions.setvariable` | `WFVariableName` | `sleepMinutes` | UUID-22 |  |\n| 24 | 辭典 | `is.workflow.actions.dictionary` | `WFItems` | Dict (見下方) | — | → Dictionary |\n| 25 | 取得 URL 內容 | `is.workflow.actions.getcontentsofurl` | `WFHTTPMethod` | `POST` | UUID-24 | → HTTP Response |\n|  |  |  | `WFHTTPBodyType` | `JSON` |  |  |\n|  |  |  | `WFHTTPHeaders` | `{\"x-api-key\":\"cubelv-health-b182cb87\"}` |  |  |\n|  |  |  | `WFURL` | `https://api.cubelv.com/plugin/快速飲食記錄/api/ingest-health` |  |  |\n\n---\n\n## 動作 #24 辭典結構\n\n```json\n{\n  \"WFItems\": [\n    {\"WFItemKey\": \"steps\",        \"WFItemValue\": {\"Variable\": {\"VariableName\": \"steps\"}}},\n    {\"WFItemKey\": \"restingHR\",    \"WFItemValue\": {\"Variable\": {\"VariableName\": \"restingHR\"}}},\n    {\"WFItemKey\": \"activeEnergy\", \"WFItemValue\": {\"Variable\": {\"VariableName\": \"activeEnergy\"}}},\n    {\"WFItemKey\": \"walkDistance\", \"WFItemValue\": {\"Variable\": {\"VariableName\": \"walkDistance\"}}},\n    {\"WFItemKey\": \"sleepMinutes\", \"WFItemValue\": {\"Variable\": {\"VariableName\": \"sleepMinutes\"}}},\n    {\"WFItemKey\": \"date\",         \"WFItemValue\": {\"Variable\": {\"VariableName\": \"logDate\"}}}\n  ]\n}\n```\n\n---\n\n## 生成腳本（Python，在 macOS 執行）\n\n```python\nimport plistlib\nimport uuid\n\n# 產 UUID（每次生成都唯一）\ndef gen_uuid():\n    return str(uuid.uuid4()).upper()\n\n# 建立所有 26 個動作的 UUID\nuids = [gen_uuid() for _ in range(26)]\n\n# 輔助：包裝變數參照\ndef var(name):\n    return {\"Variable\": {\"VariableName\": name}}\n\n# 輔助：包裝 action 輸出參照（用上一個 action 的 UUID）\ndef ref(idx):\n    return {\"OutputUUID\": uids[idx]}\n\n# 輔助：產生日期參照\n# （日期動作的輸出若非特定欄位，Shortcuts 用 ActionOutput 或 OutputName）\n# 實際 plist 中調整日期的輸出通常直接用 UUID 參照\n\nactions = []\n\n# 0: Date\nactions.append({\n    \"WFWorkflowActionIdentifier\": \"is.workflow.actions.date\",\n    \"WFWorkflowActionParameters\": {},\n    \"UUID\": uids[0]\n})\n\n# 1: Adjust Date (-1 day)\nactions.append({\n    \"WFWorkflowActionIdentifier\": \"is.workflow.actions.date.adjust\",\n    \"WFWorkflowActionParameters\": {\n        \"WFAdjustDate\": -1,\n        \"WFInput\": {\"OutputUUID\": uids[0]}\n    },\n    \"UUID\": uids[1]\n})\n\n# 2: Format Date (yyyy/MM/dd)\nactions.append({\n    \"WFWorkflowActionIdentifier\": \"is.workflow.actions.date.format\",\n    \"WFWorkflowActionParameters\": {\n        \"WFDateFormat\": \"Custom\",\n        \"WFDateCustomFormat\": \"yyyy/MM/dd\",\n        \"WFInput\": {\"OutputUUID\": uids[1]}\n    },\n    \"UUID\": uids[2]\n})\n\n# 3: Set Variable \"logDate\"\nactions.append({\n    \"WFWorkflowActionIdentifier\": \"is.workflow.actions.setvariable\",\n    \"WFWorkflowActionParameters\": {\n        \"WFVariableName\": \"logDate\",\n        \"WFInput\": {\"OutputUUID\": uids[2]}\n    },\n    \"UUID\": uids[3]\n})\n\n# 健康數據區塊：使用輔助函數產生 Find → GetItem → GetDetail → SetVariable\ndef make_health_block(idx_offset, sample_type, var_name, detail=\"Value\"):\n    \"\"\"產生 4 個 action 的健康數據區塊\"\"\"\n    i = idx_offset\n    day_input = {\"OutputUUID\": uids[1]}  # 引用調整日期\n    \n    # Find Health Samples\n    actions.append({\n        \"WFWorkflowActionIdentifier\": \"is.workflow.actions.health.find\",\n        \"WFWorkflowActionParameters\": {\n            \"WFHealthSampleType\": sample_type,\n            \"WFHealthSampleStartDateMode\": \"Relative\",\n            \"WFHealthSampleStartDate\": day_input,\n            \"WFHealthSampleGrouping\": \"Day\"\n        },\n        \"UUID\": uids[i]\n    })\n    \n    # Get Item from List (First)\n    actions.append({\n        \"WFWorkflowActionIdentifier\": \"is.workflow.actions.getitemfromlist\",\n        \"WFWorkflowActionParameters\": {\n            \"WFItemSpecifier\": \"First Item\",\n            \"WFInput\": {\"OutputUUID\": uids[i]}\n        },\n        \"UUID\": uids[i+1]\n    })\n    \n    # Get Detail of Health Sample\n    actions.append({\n        \"WFWorkflowActionIdentifier\": \"is.workflow.actions.health.getdetail\",\n        \"WFWorkflowActionParameters\": {\n            \"WFHealthSampleDetail\": detail,\n            \"WFInput\": {\"OutputUUID\": uids[i+1]}\n        },\n        \"UUID\": uids[i+2]\n    })\n    \n    # Set Variable\n    actions.append({\n        \"WFWorkflowActionIdentifier\": \"is.workflow.actions.setvariable\",\n        \"WFWorkflowActionParameters\": {\n            \"WFVariableName\": var_name,\n            \"WFInput\": {\"OutputUUID\": uids[i+2]}\n        },\n        \"UUID\": uids[i+3]\n    })\n\n# 4-7: Steps\nmake_health_block(4, \"Steps\", \"steps\")\n\n# 8-11: Resting Heart Rate\nmake_health_block(8, \"Resting Heart Rate\", \"restingHR\")\n\n# 12-15: Active Energy\nmake_health_block(12, \"Active Energy\", \"activeEnergy\")\n\n# 16-19: Walking + Running Distance\nmake_health_block(16, \"Walking + Running Distance\", \"walkDistance\")\n\n# 20-23: Sleep（使用 Get Time Between 而非 Get Detail）\ndef make_sleep_block(idx_offset):\n    i = idx_offset\n    \n    # Find Health Samples\n    actions.append({\n        \"WFWorkflowActionIdentifier\": \"is.workflow.actions.health.find\",\n        \"WFWorkflowActionParameters\": {\n            \"WFHealthSampleType\": \"Sleep\",\n            \"WFHealthSampleStartDateMode\": \"Relative\",\n            \"WFHealthSampleStartDate\": {\"OutputUUID\": uids[1]},\n            \"WFHealthSampleEndDate\": {\"OutputUUID\": uids[0]},\n            \"WFHealthSampleGrouping\": \"Day\"\n        },\n        \"UUID\": uids[i]\n    })\n    \n    # Get First Item\n    actions.append({\n        \"WFWorkflowActionIdentifier\": \"is.workflow.actions.getitemfromlist\",\n        \"WFWorkflowActionParameters\": {\n            \"WFItemSpecifier\": \"First Item\",\n            \"WFInput\": {\"OutputUUID\": uids[i]}\n        },\n        \"UUID\": uids[i+1]\n    })\n    \n    # Get Time Between (Minutes)\n    actions.append({\n        \"WFWorkflowActionIdentifier\": \"is.workflow.actions.health.gettime\",\n        \"WFWorkflowActionParameters\": {\n            \"WFTimeUnit\": \"Minutes\",\n            \"WFInput\": {\"OutputUUID\": uids[i+1]}\n        },\n        \"UUID\": uids[i+2]\n    })\n    \n    # Set Variable\n    actions.append({\n        \"WFWorkflowActionIdentifier\": \"is.workflow.actions.setvariable\",\n        \"WFWorkflowActionParameters\": {\n            \"WFVariableName\": \"sleepMinutes\",\n            \"WFInput\": {\"OutputUUID\": uids[i+2]}\n        },\n        \"UUID\": uids[i+3]\n    })\n\nmake_sleep_block(20)\n\n# 24: Dictionary\nactions.append({\n    \"WFWorkflowActionIdentifier\": \"is.workflow.actions.dictionary\",\n    \"WFWorkflowActionParameters\": {\n        \"WFItems\": [\n            {\"WFItemKey\": \"steps\",        \"WFItemValue\": {\"Variable\": {\"VariableName\": \"steps\"}}},\n            {\"WFItemKey\": \"restingHR\",    \"WFItemValue\": {\"Variable\": {\"VariableName\": \"restingHR\"}}},\n            {\"WFItemKey\": \"activeEnergy\", \"WFItemValue\": {\"Variable\": {\"VariableName\": \"activeEnergy\"}}},\n            {\"WFItemKey\": \"walkDistance\", \"WFItemValue\": {\"Variable\": {\"VariableName\": \"walkDistance\"}}},\n            {\"WFItemKey\": \"sleepMinutes\", \"WFItemValue\": {\"Variable\": {\"VariableName\": \"sleepMinutes\"}}},\n            {\"WFItemKey\": \"date\",         \"WFItemValue\": {\"Variable\": {\"VariableName\": \"logDate\"}}}\n        ]\n    },\n    \"UUID\": uids[24]\n})\n\n# 25: Get Contents of URL (POST)\nactions.append({\n    \"WFWorkflowActionIdentifier\": \"is.workflow.actions.getcontentsofurl\",\n    \"WFWorkflowActionParameters\": {\n        \"WFHTTPMethod\": \"POST\",\n        \"WFHTTPBodyType\": \"JSON\",\n        \"WFHTTPHeaders\": {\"x-api-key\": \"cubelv-health-b182cb87\"},\n        \"WFURL\": \"https://api.cubelv.com/plugin/快速飲食記錄/api/ingest-health\",\n        \"WFInput\": {\"OutputUUID\": uids[24]}\n    },\n    \"UUID\": uids[25]\n})\n\n# 組裝 plist 根結構\nshortcut = {\n    \"WFWorkflowActions\": actions,\n    \"WFWorkflowClientVersion\": \"900.7.2\",\n    \"WFWorkflowClientRelease\": \"20.1\",\n    \"WFWorkflowIcon\": {\n        \"WFWorkflowIconStartColor\": 3779095551,\n        \"WFWorkflowIconGlyphNumber\": 61442\n    },\n    \"WFWorkflowImportQuestions\": [],\n    \"WFWorkflowTypes\": [],\n    \"WFWorkflowInputContentItemClasses\": [],\n    \"WFWorkflowMinimumClientVersion\": 900\n}\n\n# 寫出二進位 plist\nwith open(\"健康同步.shortcut\", \"wb\") as f:\n    plistlib.dump(shortcut, f, fmt=plistlib.FMT_BINARY)\n\nprint(\"Done: 健康同步.shortcut\")\n```\n\n### 執行方式\n\n在 macOS 終端機執行：\n```bash\npython3 -c \"\n$(cat \u003c\u003c'PYEOF'\n# 上述完整 Python script\nPYEOF\n)\n\"\n```\n\n或存成 `gen_shortcut.py` 後執行 `python3 gen_shortcut.py`。\n\n---\n\n## 關鍵注意事項（先前生成失敗的教訓）\n\n1. **使用 Python `plistlib`（內建模組）**：需 macOS 環境（Python 3），`plistlib.dump(data, file, fmt=plistlib.FMT_BINARY)` 產出的二進位格式與 iOS Shortcuts 相容。Node.js 的 `bplist-creator` 與 Python 的 `biplist` 產出的 byte 順序不同導致空白。\n\n2. **UUID 必須為大寫**：`str(uuid.uuid4()).upper()`，不可小寫。\n\n3. **每個 action 必須有 `UUID` 頂層欄位**：不是放在 `WFWorkflowActionParameters` 裡面。\n\n4. **動作之間的串接**：透過 `{\"OutputUUID\": \"\u003c上個動作的UUID\u003e\"}` 包在 `WFInput` 或特定參數（如 `WFHealthSampleStartDate`）內。\n\n5. **健康數據類型名稱必須精確**：`Steps`、`Resting Heart Rate`、`Active Energy`、`Walking + Running Distance`、`Sleep`（大小寫與空格完全一致）。\n\n6. **睡眠使用 `is.workflow.actions.health.gettime` 而非 `getdetail`**：睡眠樣本的 duration 必須透過 Get Time Between 取得，單位設 `Minutes`。\n\n7. **`WFHTTPHeaders` 的格式**：是 dict，鍵值皆為字串，例如 `{\"x-api-key\": \"cubelv-health-b182cb87\"}`。\n\n8. **生成後驗證**：在 macOS 用 `plutil -lint 健康同步.shortcut` 確認格式正確，再 AirDrop 到 iPhone。\n","createdAt":1784616173938,"deletedAt":null,"id":"31332857f13f373723a83042","isNew":false,"isPublic":true,"itemType":"NOTE","name":"Shortcut 生成規格 — 健康同步（給 Claude 用）","parents":{"cd67e18db19aaec1127da6fb":1784616173938},"preParentID":null,"updatedAt":1784616245070,"version":5,"websiteContent":"[{\"originUrl\":\"https://api.cubelv.com/plugin/\",\"title\":null,\"content\":null,\"imageUrl\":null}]"},"ownerName":"sam wi","subtree":[{"content":"此文件是給 Claude 的**生成規格**，請依此表產生 `.shortcut` 二進位 plist 檔案。\n\n---\n\n## API 端點\n\n| 欄位 | 值 |\n|------|-----|\n| URL | `https://api.cubelv.com/plugin/快速飲食記錄/api/ingest-health` |\n| Method | POST |\n| Header | `x-api-key: cubelv-health-b182cb87` |\n| Content-Type | `application/json` |\n| Body | `{\"steps\":\u003cnum\u003e,\"restingHR\":\u003cnum\u003e,\"activeEnergy\":\u003cnum\u003e,\"walkDistance\":\u003cnum\u003e,\"sleepMinutes\":\u003cnum\u003e,\"date\":\"\u003cYYYY/MM/DD\u003e\"}` |\n\n---\n\n## 動作規格表（共 20 個動作）\n\n### UUID 前綴：`HEALTH-SYNC-`\n\n| # | 動作名稱 | WFWorkflowActionIdentifier | 鍵參數 | 值 | 輸入 UUID | 輸出說明 |\n|---|---------|---------------------------|--------|-----|-----------|---------|\n| 0 | 目前日期 | `is.workflow.actions.date` | — | — | — | → Date |\n| 1 | 調整日期 | `is.workflow.actions.date.adjust` | `WFAdjustDate` | `-1` (天) | UUID-0 | → Date(昨天) |\n| 2 | 格式化日期 | `is.workflow.actions.date.format` | `WFDateFormat` | `Custom` | UUID-1 | → Text |\n|  |  |  | `WFDateCustomFormat` | `yyyy/MM/dd` |  |  |\n| 3 | 設定變數 logDate | `is.workflow.actions.setvariable` | `WFVariableName` | `logDate` | UUID-2 |  |\n| 4 | 尋找步數 | `is.workflow.actions.health.find` | `WFHealthSampleType` | `Steps` | UUID-1（開始日期） | → [HealthSample] |\n|  |  |  | `WFHealthSampleStartDateMode` | `Relative` |  |  |\n|  |  |  | `WFHealthSampleGrouping` | `Day` |  |  |\n| 5 | 取第一項 | `is.workflow.actions.getitemfromlist` | `WFItemSpecifier` | `First Item` | UUID-4 | → HealthSample |\n| 6 | 取數值 | `is.workflow.actions.health.getdetail` | `WFHealthSampleDetail` | `Value` | UUID-5 | → Number |\n| 7 | 設定變數 steps | `is.workflow.actions.setvariable` | `WFVariableName` | `steps` | UUID-6 |  |\n| 8 | 尋找靜止心率 | `is.workflow.actions.health.find` | `WFHealthSampleType` | `Resting Heart Rate` | UUID-1（開始日期） | → [HealthSample] |\n|  |  |  | `WFHealthSampleStartDateMode` | `Relative` |  |  |\n|  |  |  | `WFHealthSampleGrouping` | `Day` |  |  |\n| 9 | 取第一項 | `is.workflow.actions.getitemfromlist` | `WFItemSpecifier` | `First Item` | UUID-8 | → HealthSample |\n| 10 | 取數值 | `is.workflow.actions.health.getdetail` | `WFHealthSampleDetail` | `Value` | UUID-9 | → Number |\n| 11 | 設定變數 restingHR | `is.workflow.actions.setvariable` | `WFVariableName` | `restingHR` | UUID-10 |  |\n| 12 | 尋找活動能量 | `is.workflow.actions.health.find` | `WFHealthSampleType` | `Active Energy` | UUID-1（開始日期） | → [HealthSample] |\n|  |  |  | `WFHealthSampleStartDateMode` | `Relative` |  |  |\n|  |  |  | `WFHealthSampleGrouping` | `Day` |  |  |\n| 13 | 取第一項 | `is.workflow.actions.getitemfromlist` | `WFItemSpecifier` | `First Item` | UUID-12 | → HealthSample |\n| 14 | 取數值 | `is.workflow.actions.health.getdetail` | `WFHealthSampleDetail` | `Value` | UUID-13 | → Number |\n| 15 | 設定變數 activeEnergy | `is.workflow.actions.setvariable` | `WFVariableName` | `activeEnergy` | UUID-14 |  |\n| 16 | 尋找步行距離 | `is.workflow.actions.health.find` | `WFHealthSampleType` | `Walking + Running Distance` | UUID-1（開始日期） | → [HealthSample] |\n|  |  |  | `WFHealthSampleStartDateMode` | `Relative` |  |  |\n|  |  |  | `WFHealthSampleGrouping` | `Day` |  |  |\n| 17 | 取第一項 | `is.workflow.actions.getitemfromlist` | `WFItemSpecifier` | `First Item` | UUID-16 | → HealthSample |\n| 18 | 取數值 | `is.workflow.actions.health.getdetail` | `WFHealthSampleDetail` | `Value` | UUID-17 | → Number |\n| 19 | 設定變數 walkDistance | `is.workflow.actions.setvariable` | `WFVariableName` | `walkDistance` | UUID-18 |  |\n| 20 | 尋找睡眠 | `is.workflow.actions.health.find` | `WFHealthSampleType` | `Sleep` | UUID-1（開始日期） | → [HealthSample] |\n|  |  |  | `WFHealthSampleStartDateMode` | `Relative` | UUID-0（結束日期） |  |\n|  |  |  | `WFHealthSampleGrouping` | `Day` |  |  |\n| 21 | 取第一項 | `is.workflow.actions.getitemfromlist` | `WFItemSpecifier` | `First Item` | UUID-20 | → HealthSample |\n| 22 | 取時間間隔 | `is.workflow.actions.health.gettime` | `WFTimeUnit` | `Minutes` | UUID-21 | → Number |\n| 23 | 設定變數 sleepMinutes | `is.workflow.actions.setvariable` | `WFVariableName` | `sleepMinutes` | UUID-22 |  |\n| 24 | 辭典 | `is.workflow.actions.dictionary` | `WFItems` | Dict (見下方) | — | → Dictionary |\n| 25 | 取得 URL 內容 | `is.workflow.actions.getcontentsofurl` | `WFHTTPMethod` | `POST` | UUID-24 | → HTTP Response |\n|  |  |  | `WFHTTPBodyType` | `JSON` |  |  |\n|  |  |  | `WFHTTPHeaders` | `{\"x-api-key\":\"cubelv-health-b182cb87\"}` |  |  |\n|  |  |  | `WFURL` | `https://api.cubelv.com/plugin/快速飲食記錄/api/ingest-health` |  |  |\n\n---\n\n## 動作 #24 辭典結構\n\n```json\n{\n  \"WFItems\": [\n    {\"WFItemKey\": \"steps\",        \"WFItemValue\": {\"Variable\": {\"VariableName\": \"steps\"}}},\n    {\"WFItemKey\": \"restingHR\",    \"WFItemValue\": {\"Variable\": {\"VariableName\": \"restingHR\"}}},\n    {\"WFItemKey\": \"activeEnergy\", \"WFItemValue\": {\"Variable\": {\"VariableName\": \"activeEnergy\"}}},\n    {\"WFItemKey\": \"walkDistance\", \"WFItemValue\": {\"Variable\": {\"VariableName\": \"walkDistance\"}}},\n    {\"WFItemKey\": \"sleepMinutes\", \"WFItemValue\": {\"Variable\": {\"VariableName\": \"sleepMinutes\"}}},\n    {\"WFItemKey\": \"date\",         \"WFItemValue\": {\"Variable\": {\"VariableName\": \"logDate\"}}}\n  ]\n}\n```\n\n---\n\n## 生成腳本（Python，在 macOS 執行）\n\n```python\nimport plistlib\nimport uuid\n\n# 產 UUID（每次生成都唯一）\ndef gen_uuid():\n    return str(uuid.uuid4()).upper()\n\n# 建立所有 26 個動作的 UUID\nuids = [gen_uuid() for _ in range(26)]\n\n# 輔助：包裝變數參照\ndef var(name):\n    return {\"Variable\": {\"VariableName\": name}}\n\n# 輔助：包裝 action 輸出參照（用上一個 action 的 UUID）\ndef ref(idx):\n    return {\"OutputUUID\": uids[idx]}\n\n# 輔助：產生日期參照\n# （日期動作的輸出若非特定欄位，Shortcuts 用 ActionOutput 或 OutputName）\n# 實際 plist 中調整日期的輸出通常直接用 UUID 參照\n\nactions = []\n\n# 0: Date\nactions.append({\n    \"WFWorkflowActionIdentifier\": \"is.workflow.actions.date\",\n    \"WFWorkflowActionParameters\": {},\n    \"UUID\": uids[0]\n})\n\n# 1: Adjust Date (-1 day)\nactions.append({\n    \"WFWorkflowActionIdentifier\": \"is.workflow.actions.date.adjust\",\n    \"WFWorkflowActionParameters\": {\n        \"WFAdjustDate\": -1,\n        \"WFInput\": {\"OutputUUID\": uids[0]}\n    },\n    \"UUID\": uids[1]\n})\n\n# 2: Format Date (yyyy/MM/dd)\nactions.append({\n    \"WFWorkflowActionIdentifier\": \"is.workflow.actions.date.format\",\n    \"WFWorkflowActionParameters\": {\n        \"WFDateFormat\": \"Custom\",\n        \"WFDateCustomFormat\": \"yyyy/MM/dd\",\n        \"WFInput\": {\"OutputUUID\": uids[1]}\n    },\n    \"UUID\": uids[2]\n})\n\n# 3: Set Variable \"logDate\"\nactions.append({\n    \"WFWorkflowActionIdentifier\": \"is.workflow.actions.setvariable\",\n    \"WFWorkflowActionParameters\": {\n        \"WFVariableName\": \"logDate\",\n        \"WFInput\": {\"OutputUUID\": uids[2]}\n    },\n    \"UUID\": uids[3]\n})\n\n# 健康數據區塊：使用輔助函數產生 Find → GetItem → GetDetail → SetVariable\ndef make_health_block(idx_offset, sample_type, var_name, detail=\"Value\"):\n    \"\"\"產生 4 個 action 的健康數據區塊\"\"\"\n    i = idx_offset\n    day_input = {\"OutputUUID\": uids[1]}  # 引用調整日期\n    \n    # Find Health Samples\n    actions.append({\n        \"WFWorkflowActionIdentifier\": \"is.workflow.actions.health.find\",\n        \"WFWorkflowActionParameters\": {\n            \"WFHealthSampleType\": sample_type,\n            \"WFHealthSampleStartDateMode\": \"Relative\",\n            \"WFHealthSampleStartDate\": day_input,\n            \"WFHealthSampleGrouping\": \"Day\"\n        },\n        \"UUID\": uids[i]\n    })\n    \n    # Get Item from List (First)\n    actions.append({\n        \"WFWorkflowActionIdentifier\": \"is.workflow.actions.getitemfromlist\",\n        \"WFWorkflowActionParameters\": {\n            \"WFItemSpecifier\": \"First Item\",\n            \"WFInput\": {\"OutputUUID\": uids[i]}\n        },\n        \"UUID\": uids[i+1]\n    })\n    \n    # Get Detail of Health Sample\n    actions.append({\n        \"WFWorkflowActionIdentifier\": \"is.workflow.actions.health.getdetail\",\n        \"WFWorkflowActionParameters\": {\n            \"WFHealthSampleDetail\": detail,\n            \"WFInput\": {\"OutputUUID\": uids[i+1]}\n        },\n        \"UUID\": uids[i+2]\n    })\n    \n    # Set Variable\n    actions.append({\n        \"WFWorkflowActionIdentifier\": \"is.workflow.actions.setvariable\",\n        \"WFWorkflowActionParameters\": {\n            \"WFVariableName\": var_name,\n            \"WFInput\": {\"OutputUUID\": uids[i+2]}\n        },\n        \"UUID\": uids[i+3]\n    })\n\n# 4-7: Steps\nmake_health_block(4, \"Steps\", \"steps\")\n\n# 8-11: Resting Heart Rate\nmake_health_block(8, \"Resting Heart Rate\", \"restingHR\")\n\n# 12-15: Active Energy\nmake_health_block(12, \"Active Energy\", \"activeEnergy\")\n\n# 16-19: Walking + Running Distance\nmake_health_block(16, \"Walking + Running Distance\", \"walkDistance\")\n\n# 20-23: Sleep（使用 Get Time Between 而非 Get Detail）\ndef make_sleep_block(idx_offset):\n    i = idx_offset\n    \n    # Find Health Samples\n    actions.append({\n        \"WFWorkflowActionIdentifier\": \"is.workflow.actions.health.find\",\n        \"WFWorkflowActionParameters\": {\n            \"WFHealthSampleType\": \"Sleep\",\n            \"WFHealthSampleStartDateMode\": \"Relative\",\n            \"WFHealthSampleStartDate\": {\"OutputUUID\": uids[1]},\n            \"WFHealthSampleEndDate\": {\"OutputUUID\": uids[0]},\n            \"WFHealthSampleGrouping\": \"Day\"\n        },\n        \"UUID\": uids[i]\n    })\n    \n    # Get First Item\n    actions.append({\n        \"WFWorkflowActionIdentifier\": \"is.workflow.actions.getitemfromlist\",\n        \"WFWorkflowActionParameters\": {\n            \"WFItemSpecifier\": \"First Item\",\n            \"WFInput\": {\"OutputUUID\": uids[i]}\n        },\n        \"UUID\": uids[i+1]\n    })\n    \n    # Get Time Between (Minutes)\n    actions.append({\n        \"WFWorkflowActionIdentifier\": \"is.workflow.actions.health.gettime\",\n        \"WFWorkflowActionParameters\": {\n            \"WFTimeUnit\": \"Minutes\",\n            \"WFInput\": {\"OutputUUID\": uids[i+1]}\n        },\n        \"UUID\": uids[i+2]\n    })\n    \n    # Set Variable\n    actions.append({\n        \"WFWorkflowActionIdentifier\": \"is.workflow.actions.setvariable\",\n        \"WFWorkflowActionParameters\": {\n            \"WFVariableName\": \"sleepMinutes\",\n            \"WFInput\": {\"OutputUUID\": uids[i+2]}\n        },\n        \"UUID\": uids[i+3]\n    })\n\nmake_sleep_block(20)\n\n# 24: Dictionary\nactions.append({\n    \"WFWorkflowActionIdentifier\": \"is.workflow.actions.dictionary\",\n    \"WFWorkflowActionParameters\": {\n        \"WFItems\": [\n            {\"WFItemKey\": \"steps\",        \"WFItemValue\": {\"Variable\": {\"VariableName\": \"steps\"}}},\n            {\"WFItemKey\": \"restingHR\",    \"WFItemValue\": {\"Variable\": {\"VariableName\": \"restingHR\"}}},\n            {\"WFItemKey\": \"activeEnergy\", \"WFItemValue\": {\"Variable\": {\"VariableName\": \"activeEnergy\"}}},\n            {\"WFItemKey\": \"walkDistance\", \"WFItemValue\": {\"Variable\": {\"VariableName\": \"walkDistance\"}}},\n            {\"WFItemKey\": \"sleepMinutes\", \"WFItemValue\": {\"Variable\": {\"VariableName\": \"sleepMinutes\"}}},\n            {\"WFItemKey\": \"date\",         \"WFItemValue\": {\"Variable\": {\"VariableName\": \"logDate\"}}}\n        ]\n    },\n    \"UUID\": uids[24]\n})\n\n# 25: Get Contents of URL (POST)\nactions.append({\n    \"WFWorkflowActionIdentifier\": \"is.workflow.actions.getcontentsofurl\",\n    \"WFWorkflowActionParameters\": {\n        \"WFHTTPMethod\": \"POST\",\n        \"WFHTTPBodyType\": \"JSON\",\n        \"WFHTTPHeaders\": {\"x-api-key\": \"cubelv-health-b182cb87\"},\n        \"WFURL\": \"https://api.cubelv.com/plugin/快速飲食記錄/api/ingest-health\",\n        \"WFInput\": {\"OutputUUID\": uids[24]}\n    },\n    \"UUID\": uids[25]\n})\n\n# 組裝 plist 根結構\nshortcut = {\n    \"WFWorkflowActions\": actions,\n    \"WFWorkflowClientVersion\": \"900.7.2\",\n    \"WFWorkflowClientRelease\": \"20.1\",\n    \"WFWorkflowIcon\": {\n        \"WFWorkflowIconStartColor\": 3779095551,\n        \"WFWorkflowIconGlyphNumber\": 61442\n    },\n    \"WFWorkflowImportQuestions\": [],\n    \"WFWorkflowTypes\": [],\n    \"WFWorkflowInputContentItemClasses\": [],\n    \"WFWorkflowMinimumClientVersion\": 900\n}\n\n# 寫出二進位 plist\nwith open(\"健康同步.shortcut\", \"wb\") as f:\n    plistlib.dump(shortcut, f, fmt=plistlib.FMT_BINARY)\n\nprint(\"Done: 健康同步.shortcut\")\n```\n\n### 執行方式\n\n在 macOS 終端機執行：\n```bash\npython3 -c \"\n$(cat \u003c\u003c'PYEOF'\n# 上述完整 Python script\nPYEOF\n)\n\"\n```\n\n或存成 `gen_shortcut.py` 後執行 `python3 gen_shortcut.py`。\n\n---\n\n## 關鍵注意事項（先前生成失敗的教訓）\n\n1. **使用 Python `plistlib`（內建模組）**：需 macOS 環境（Python 3），`plistlib.dump(data, file, fmt=plistlib.FMT_BINARY)` 產出的二進位格式與 iOS Shortcuts 相容。Node.js 的 `bplist-creator` 與 Python 的 `biplist` 產出的 byte 順序不同導致空白。\n\n2. **UUID 必須為大寫**：`str(uuid.uuid4()).upper()`，不可小寫。\n\n3. **每個 action 必須有 `UUID` 頂層欄位**：不是放在 `WFWorkflowActionParameters` 裡面。\n\n4. **動作之間的串接**：透過 `{\"OutputUUID\": \"\u003c上個動作的UUID\u003e\"}` 包在 `WFInput` 或特定參數（如 `WFHealthSampleStartDate`）內。\n\n5. **健康數據類型名稱必須精確**：`Steps`、`Resting Heart Rate`、`Active Energy`、`Walking + Running Distance`、`Sleep`（大小寫與空格完全一致）。\n\n6. **睡眠使用 `is.workflow.actions.health.gettime` 而非 `getdetail`**：睡眠樣本的 duration 必須透過 Get Time Between 取得，單位設 `Minutes`。\n\n7. **`WFHTTPHeaders` 的格式**：是 dict，鍵值皆為字串，例如 `{\"x-api-key\": \"cubelv-health-b182cb87\"}`。\n\n8. **生成後驗證**：在 macOS 用 `plutil -lint 健康同步.shortcut` 確認格式正確，再 AirDrop 到 iPhone。\n","createdAt":1784616173938,"deletedAt":null,"id":"31332857f13f373723a83042","isNew":false,"isPublic":true,"itemType":"NOTE","name":"Shortcut 生成規格 — 健康同步（給 Claude 用）","parents":{"cd67e18db19aaec1127da6fb":1784616173938},"preParentID":null,"updatedAt":1784616245070,"version":5,"websiteContent":"[{\"originUrl\":\"https://api.cubelv.com/plugin/\",\"title\":null,\"content\":null,\"imageUrl\":null}]"}]}