From 270d6645f791bf9200ddfc0cf645b0528af0b37d Mon Sep 17 00:00:00 2001 From: Anthony Merlo Date: Sat, 22 Aug 2026 23:16:48 +0100 Subject: [PATCH] fix v7: wrap in async main() to avoid top-level await parse error Scripting app's JavaScriptCore doesn't support top-level await in file scope. Wrapped everything in async main() + .then(Script.exit). Converted function declarations to arrow functions. --- scripts/health-sync.tsx | 313 ++++++++++++++++++++-------------------- 1 file changed, 157 insertions(+), 156 deletions(-) diff --git a/scripts/health-sync.tsx b/scripts/health-sync.tsx index d8543dd..bca3c50 100644 --- a/scripts/health-sync.tsx +++ b/scripts/health-sync.tsx @@ -1,5 +1,5 @@ // @ts-nocheck -// Health Sync v6 — reads Apple Health via Scripting app and POSTs to health-mcp server +// Health Sync v7 — reads Apple Health via Scripting app and POSTs to health-mcp server // Run via Scripting app automation at 9:30am daily // Server: http://chronos:9432/ingest // @@ -17,174 +17,175 @@ import { Script, Notification } from "scripting" // icon-color: deep-blue; icon-glyph: heart; -const SERVER_URL = "http://chronos:9432/ingest" -const API_KEY = "333f09ce904e9cbbe3b578fda393be92869122c7222dbccd" +async function main() { + const SERVER_URL = "http://chronos:9432/ingest" + const API_KEY = "333f09ce904e9cbbe3b578fda393be92869122c7222dbccd" -// --- Units for each quantity type --- -const U = { - kg: HealthUnit.gramUnit(HealthUnitPrefix.kilo), - mmHg: HealthUnit.millimeterOfMercury(), - bpm: HealthUnit.count().divided(HealthUnit.minute()), - count: HealthUnit.count(), - percent: HealthUnit.percent() -} + // --- Units for each quantity type --- + const U = { + kg: HealthUnit.gramUnit(HealthUnitPrefix.kilo), + mmHg: HealthUnit.millimeterOfMercury(), + bpm: HealthUnit.count().divided(HealthUnit.minute()), + count: HealthUnit.count(), + percent: HealthUnit.percent() + } -// Read the most recent sample of a type. Returns { value, startDate } or null. -async function readLatest(type, unit) { - try { - const samples = await Health.queryQuantitySamples(type, { - limit: 1, - sortDescriptors: [{ key: "startDate", order: "reverse" }] - }) - if (!samples || samples.length === 0) { - console.log(`readLatest(${type}): no samples`) + // Read the most recent sample of a type. Returns { value, startDate } or null. + const readLatest = async (type, unit) => { + try { + const samples = await Health.queryQuantitySamples(type, { + limit: 1, + sortDescriptors: [{ key: "startDate", order: "reverse" }] + }) + if (!samples || samples.length === 0) { + console.log(`readLatest(${type}): no samples`) + return null + } + const s = samples[0] + const value = s.quantityValue(unit) + console.log(`readLatest(${type}): value=${value} date=${s.startDate}`) + return { value, startDate: s.startDate } + } catch (e) { + console.log(`readLatest(${type}): error ${e}`) return null } - const s = samples[0] - const value = s.quantityValue(unit) - console.log(`readLatest(${type}): value=${value} date=${s.startDate}`) - return { value, startDate: s.startDate } - } catch (e) { - console.log(`readLatest(${type}): error ${e}`) - return null } -} -// Sum all samples of a type since midnight. Returns a number or null. -async function readTodaySum(type, unit) { - try { - const start = new Date() - start.setHours(0, 0, 0, 0) - const samples = await Health.queryQuantitySamples(type, { - startDate: start, - endDate: new Date() - }) - if (!samples || samples.length === 0) { - console.log(`readTodaySum(${type}): no samples`) + // Sum all samples of a type since midnight. Returns a number or null. + const readTodaySum = async (type, unit) => { + try { + const start = new Date() + start.setHours(0, 0, 0, 0) + const samples = await Health.queryQuantitySamples(type, { + startDate: start, + endDate: new Date() + }) + if (!samples || samples.length === 0) { + console.log(`readTodaySum(${type}): no samples`) + return null + } + let total = 0 + for (const s of samples) { + total += s.quantityValue(unit) + } + console.log(`readTodaySum(${type}): total=${total}`) + return total + } catch (e) { + console.log(`readTodaySum(${type}): error ${e}`) return null } - let total = 0 - for (const s of samples) { - total += s.quantityValue(unit) - } - console.log(`readTodaySum(${type}): total=${total}`) - return total - } catch (e) { - console.log(`readTodaySum(${type}): error ${e}`) - return null } -} -function todayDate() { - return new Date().toISOString().slice(0, 10) -} + const todayDate = () => { + return new Date().toISOString().slice(0, 10) + } -async function notify(body) { - console.log(`notify: ${body}`) - await Notification.schedule({ - title: "Health Sync", - body: body - }) -} - -// --- Read all health metrics --- -console.log("=== Health Sync starting ===") - -const weight = await readLatest("bodyMass", U.kg) -const bpSys = await readLatest("bloodPressureSystolic", U.mmHg) -const bpDia = await readLatest("bloodPressureDiastolic", U.mmHg) -const hr = await readLatest("heartRate", U.bpm) -const restingHr = await readLatest("restingHeartRate", U.bpm) -const steps = await readTodaySum("stepCount", U.count) -const spo2 = await readLatest("oxygenSaturation", U.percent) - -console.log(`Results: weight=${weight?.value} bpSys=${bpSys?.value} bpDia=${bpDia?.value} hr=${hr?.value} rhr=${restingHr?.value} steps=${steps} spo2=${spo2?.value}`) - -// --- Build samples array — only include metrics that have data --- -const samples = [] - -if (weight) { - samples.push({ - type: "weight", - timestamp_utc: weight.startDate.toISOString(), - value: weight.value, - source: "apple_health" - }) -} - -if (bpSys && bpDia) { - const bpValue = { systolic: bpSys.value, diastolic: bpDia.value } - if (hr) bpValue.pulse = hr.value - samples.push({ - type: "blood_pressure", - timestamp_utc: bpSys.startDate.toISOString(), - value: bpValue, - source: "omron" - }) -} - -if (hr && !bpSys) { - samples.push({ - type: "heart_rate", - timestamp_utc: hr.startDate.toISOString(), - value: hr.value, - source: "apple_health" - }) -} - -if (restingHr) { - samples.push({ - type: "resting_heart_rate", - timestamp_utc: restingHr.startDate.toISOString(), - value: restingHr.value, - source: "apple_health" - }) -} - -if (steps !== null) { - samples.push({ - type: "steps", - timestamp_utc: todayDate(), - value: steps, - source: "apple_health" - }) -} - -if (spo2) { - samples.push({ - type: "spo2", - timestamp_utc: spo2.startDate.toISOString(), - value: spo2.value * 100, // HealthKit stores 0-1, server expects % - source: "apple_health" - }) -} - -console.log(`Total samples to send: ${samples.length}`) - -// --- POST to server --- -if (samples.length === 0) { - await notify("No health data found today") -} else { - - try { - const response = await fetch(SERVER_URL, { - method: "POST", - headers: { - "Authorization": `Bearer ${API_KEY}`, - "Content-Type": "application/json" - }, - body: JSON.stringify({ samples }), - allowInsecureRequest: true + const notify = async (body) => { + console.log(`notify: ${body}`) + await Notification.schedule({ + title: "Health Sync", + body: body }) - const res = await response.json() - console.log(`Server response: ${JSON.stringify(res)}`) - await notify(`Sent ${res.ingested || samples.length} samples`) - } catch (e) { - console.log(`POST error: ${e}`) - await notify(`Error: ${e.message || e}`) } + // --- Read all health metrics --- + console.log("=== Health Sync starting ===") + + const weight = await readLatest("bodyMass", U.kg) + const bpSys = await readLatest("bloodPressureSystolic", U.mmHg) + const bpDia = await readLatest("bloodPressureDiastolic", U.mmHg) + const hr = await readLatest("heartRate", U.bpm) + const restingHr = await readLatest("restingHeartRate", U.bpm) + const steps = await readTodaySum("stepCount", U.count) + const spo2 = await readLatest("oxygenSaturation", U.percent) + + console.log(`Results: weight=${weight?.value} bpSys=${bpSys?.value} bpDia=${bpDia?.value} hr=${hr?.value} rhr=${restingHr?.value} steps=${steps} spo2=${spo2?.value}`) + + // --- Build samples array — only include metrics that have data --- + const samples = [] + + if (weight) { + samples.push({ + type: "weight", + timestamp_utc: weight.startDate.toISOString(), + value: weight.value, + source: "apple_health" + }) + } + + if (bpSys && bpDia) { + const bpValue = { systolic: bpSys.value, diastolic: bpDia.value } + if (hr) bpValue.pulse = hr.value + samples.push({ + type: "blood_pressure", + timestamp_utc: bpSys.startDate.toISOString(), + value: bpValue, + source: "omron" + }) + } + + if (hr && !bpSys) { + samples.push({ + type: "heart_rate", + timestamp_utc: hr.startDate.toISOString(), + value: hr.value, + source: "apple_health" + }) + } + + if (restingHr) { + samples.push({ + type: "resting_heart_rate", + timestamp_utc: restingHr.startDate.toISOString(), + value: restingHr.value, + source: "apple_health" + }) + } + + if (steps !== null) { + samples.push({ + type: "steps", + timestamp_utc: todayDate(), + value: steps, + source: "apple_health" + }) + } + + if (spo2) { + samples.push({ + type: "spo2", + timestamp_utc: spo2.startDate.toISOString(), + value: spo2.value * 100, // HealthKit stores 0-1, server expects % + source: "apple_health" + }) + } + + console.log(`Total samples to send: ${samples.length}`) + + // --- POST to server --- + if (samples.length === 0) { + await notify("No health data found today") + } else { + try { + const response = await fetch(SERVER_URL, { + method: "POST", + headers: { + "Authorization": `Bearer ${API_KEY}`, + "Content-Type": "application/json" + }, + body: JSON.stringify({ samples }), + allowInsecureRequest: true + }) + const res = await response.json() + console.log(`Server response: ${JSON.stringify(res)}`) + await notify(`Sent ${res.ingested || samples.length} samples`) + } catch (e) { + console.log(`POST error: ${e}`) + await notify(`Error: ${e.message || e}`) + } + } + + console.log("=== Health Sync done ===") } -console.log("=== Health Sync done ===") -Script.exit() \ No newline at end of file +main().then(() => Script.exit()) \ No newline at end of file