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.
This commit is contained in:
Anthony Merlo 2026-08-22 23:16:48 +01:00
parent 9c8dac7ec4
commit 270d6645f7

View file

@ -1,5 +1,5 @@
// @ts-nocheck // @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 // Run via Scripting app automation at 9:30am daily
// Server: http://chronos:9432/ingest // Server: http://chronos:9432/ingest
// //
@ -17,20 +17,21 @@ import { Script, Notification } from "scripting"
// icon-color: deep-blue; icon-glyph: heart; // icon-color: deep-blue; icon-glyph: heart;
const SERVER_URL = "http://chronos:9432/ingest" async function main() {
const API_KEY = "333f09ce904e9cbbe3b578fda393be92869122c7222dbccd" const SERVER_URL = "http://chronos:9432/ingest"
const API_KEY = "333f09ce904e9cbbe3b578fda393be92869122c7222dbccd"
// --- Units for each quantity type --- // --- Units for each quantity type ---
const U = { const U = {
kg: HealthUnit.gramUnit(HealthUnitPrefix.kilo), kg: HealthUnit.gramUnit(HealthUnitPrefix.kilo),
mmHg: HealthUnit.millimeterOfMercury(), mmHg: HealthUnit.millimeterOfMercury(),
bpm: HealthUnit.count().divided(HealthUnit.minute()), bpm: HealthUnit.count().divided(HealthUnit.minute()),
count: HealthUnit.count(), count: HealthUnit.count(),
percent: HealthUnit.percent() percent: HealthUnit.percent()
} }
// Read the most recent sample of a type. Returns { value, startDate } or null. // Read the most recent sample of a type. Returns { value, startDate } or null.
async function readLatest(type, unit) { const readLatest = async (type, unit) => {
try { try {
const samples = await Health.queryQuantitySamples(type, { const samples = await Health.queryQuantitySamples(type, {
limit: 1, limit: 1,
@ -48,10 +49,10 @@ async function readLatest(type, unit) {
console.log(`readLatest(${type}): error ${e}`) console.log(`readLatest(${type}): error ${e}`)
return null return null
} }
} }
// Sum all samples of a type since midnight. Returns a number or null. // Sum all samples of a type since midnight. Returns a number or null.
async function readTodaySum(type, unit) { const readTodaySum = async (type, unit) => {
try { try {
const start = new Date() const start = new Date()
start.setHours(0, 0, 0, 0) start.setHours(0, 0, 0, 0)
@ -73,46 +74,46 @@ async function readTodaySum(type, unit) {
console.log(`readTodaySum(${type}): error ${e}`) console.log(`readTodaySum(${type}): error ${e}`)
return null return null
} }
} }
function todayDate() { const todayDate = () => {
return new Date().toISOString().slice(0, 10) return new Date().toISOString().slice(0, 10)
} }
async function notify(body) { const notify = async (body) => {
console.log(`notify: ${body}`) console.log(`notify: ${body}`)
await Notification.schedule({ await Notification.schedule({
title: "Health Sync", title: "Health Sync",
body: body body: body
}) })
} }
// --- Read all health metrics --- // --- Read all health metrics ---
console.log("=== Health Sync starting ===") console.log("=== Health Sync starting ===")
const weight = await readLatest("bodyMass", U.kg) const weight = await readLatest("bodyMass", U.kg)
const bpSys = await readLatest("bloodPressureSystolic", U.mmHg) const bpSys = await readLatest("bloodPressureSystolic", U.mmHg)
const bpDia = await readLatest("bloodPressureDiastolic", U.mmHg) const bpDia = await readLatest("bloodPressureDiastolic", U.mmHg)
const hr = await readLatest("heartRate", U.bpm) const hr = await readLatest("heartRate", U.bpm)
const restingHr = await readLatest("restingHeartRate", U.bpm) const restingHr = await readLatest("restingHeartRate", U.bpm)
const steps = await readTodaySum("stepCount", U.count) const steps = await readTodaySum("stepCount", U.count)
const spo2 = await readLatest("oxygenSaturation", U.percent) 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}`) 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 --- // --- Build samples array — only include metrics that have data ---
const samples = [] const samples = []
if (weight) { if (weight) {
samples.push({ samples.push({
type: "weight", type: "weight",
timestamp_utc: weight.startDate.toISOString(), timestamp_utc: weight.startDate.toISOString(),
value: weight.value, value: weight.value,
source: "apple_health" source: "apple_health"
}) })
} }
if (bpSys && bpDia) { if (bpSys && bpDia) {
const bpValue = { systolic: bpSys.value, diastolic: bpDia.value } const bpValue = { systolic: bpSys.value, diastolic: bpDia.value }
if (hr) bpValue.pulse = hr.value if (hr) bpValue.pulse = hr.value
samples.push({ samples.push({
@ -121,51 +122,50 @@ if (bpSys && bpDia) {
value: bpValue, value: bpValue,
source: "omron" source: "omron"
}) })
} }
if (hr && !bpSys) { if (hr && !bpSys) {
samples.push({ samples.push({
type: "heart_rate", type: "heart_rate",
timestamp_utc: hr.startDate.toISOString(), timestamp_utc: hr.startDate.toISOString(),
value: hr.value, value: hr.value,
source: "apple_health" source: "apple_health"
}) })
} }
if (restingHr) { if (restingHr) {
samples.push({ samples.push({
type: "resting_heart_rate", type: "resting_heart_rate",
timestamp_utc: restingHr.startDate.toISOString(), timestamp_utc: restingHr.startDate.toISOString(),
value: restingHr.value, value: restingHr.value,
source: "apple_health" source: "apple_health"
}) })
} }
if (steps !== null) { if (steps !== null) {
samples.push({ samples.push({
type: "steps", type: "steps",
timestamp_utc: todayDate(), timestamp_utc: todayDate(),
value: steps, value: steps,
source: "apple_health" source: "apple_health"
}) })
} }
if (spo2) { if (spo2) {
samples.push({ samples.push({
type: "spo2", type: "spo2",
timestamp_utc: spo2.startDate.toISOString(), timestamp_utc: spo2.startDate.toISOString(),
value: spo2.value * 100, // HealthKit stores 0-1, server expects % value: spo2.value * 100, // HealthKit stores 0-1, server expects %
source: "apple_health" source: "apple_health"
}) })
} }
console.log(`Total samples to send: ${samples.length}`) console.log(`Total samples to send: ${samples.length}`)
// --- POST to server --- // --- POST to server ---
if (samples.length === 0) { if (samples.length === 0) {
await notify("No health data found today") await notify("No health data found today")
} else { } else {
try { try {
const response = await fetch(SERVER_URL, { const response = await fetch(SERVER_URL, {
method: "POST", method: "POST",
@ -183,8 +183,9 @@ if (samples.length === 0) {
console.log(`POST error: ${e}`) console.log(`POST error: ${e}`)
await notify(`Error: ${e.message || e}`) await notify(`Error: ${e.message || e}`)
} }
}
console.log("=== Health Sync done ===")
} }
console.log("=== Health Sync done ===") main().then(() => Script.exit())
Script.exit()