Privacy-first Apple Health analyzer — cross-metric insights, behavioral patterns, and health reports. Agent skill for Claude Code, Codex & Cursor.
SaferSkills independently audited apple-health-analyst (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Use this skill when a user wants to analyze an Apple Health export ZIP. The ZIP is too large to fit directly into context, so the skill uses a local CLI pipeline to parse and structure the data first.
This is one skill that ships two complementary reports:
output/ folder and cross-linked via an in-page link in the topbar. prepare runs once, then render runs twice.Detect the user's language from their message:
--lang zh--lang enThe narrative language must match the language declared in insights.json:
narrativeContext.languagetraining.narrativeContext.languageDefault: generate both health + training reports. Only drop one when the user is explicit.
Analyze my Apple Health export帮我分析 Apple Health 导出Generate a report from my Apple Health exportOnly generate the health report只要健康报告不用生成运动报告Only generate the training report只要运动报告只生成运动报告,重点分析拳击Only generate the training report for running and cycling重点分析拳击训练状态分析跑步和骑行训练趋势Ambiguous-but-lean-training keywords still default to both reports (the training report alone is rarely enough context). The keywords below only matter as hints — they do NOT suppress the health report unless the user also says "只" / "only":
training, workout, 运动, 训练, 专项boxing, running, cycling, walking, hiking, strength training, 拳击, 跑步, 骑行, 力量训练Two roles share the same pipeline:
HealthData as its root node.prepare once with the correct --lang, producing summary.json and insights.json.Raw ZIP/XML parsing stays local. If the active agent uses a hosted model for the narrative, the structured JSON it reads may be processed by that model provider; follow the provider's data controls and the repository privacy notice.
summary.json, then insights.json.report.llm.jsontraining.report.llm.jsonrender for each selected report:renderrender --type trainingoutput/ folder. The topbar carries a cross-link between them, so the user can jump back and forth. File names are fixed (report.html ↔ training.report.html) — do not rename.insights.json Keys You Must Use| Key | What it contains |
|---|---|
metadata | tool, version, language, schemaVersion, generatedAt |
historicalContext | Recent 30d, baseline 90d, trailing 180d, all-time context |
charts[] | Health chart groups |
crossMetric | Cross-metric health reasoning |
riskFlags[] | Health risks with evidence |
notableChanges[] | Significant changes |
dataGaps[] | Missing or sparse data warnings |
sourceConfidence[] | Device/source reliability signals |
| Key | What it contains |
|---|---|
analysis.sleep | Sleep duration, stages, timing, regularity |
analysis.recovery | RHR, HRV, blood oxygen, respiratory rate, VO2 max |
analysis.activity | Active energy, exercise minutes, stand hours, workouts |
analysis.bodyComposition | Weight, body fat % |
analysis.menstrualCycle | Cycle analysis if present |
narrativeContext | Health-report audience, goal, schema version, boundaries |
| Key | What it contains |
|---|---|
training.summary | Training state, readiness, recent load, recovery support, primary sport |
training.summary.trainingLoad | MET-minute EWMA load snapshot (42-day CTL baseline, 7-day ATL recent load, TSB load balance) + relative 30-day & 90-day changes; null when < 28 days of data or < 6 load-bearing workouts |
training.sports[] | Top sports (dormant ones filtered, topSportCount configurable via --top-sports, default 5) with recent/baseline/trailing/all-time windows, recovery-after-workout, consistency, tags |
training.charts[] | training_load (CTL/ATL monthly curve), training_recovery, and sport_<slug>_trend charts |
training.narrativeContext | Training-report audience, goal, schema version, boundaries |
Default flow — prepare once, render twice so the folder contains both report sets. Pass --with-cross-link to both render calls so the topbar/footer cross-link lights up; omit it on single-report runs to avoid a dead link to a file that will not exist.
# 1. Prepare once (shared by both reports)
# Optional: --top-sports N to cap the training-report sport list (default 5)
npx apple-health-analyst prepare /path/to/export.zip --lang en --out ./output
# 2. Health render (fixed file name: report.html)
npx apple-health-analyst render \
--insights ./output/insights.json \
--narrative ./output/report.llm.json \
--with-cross-link \
--out ./output
# 3. Training render (fixed file name: training.report.html)
npx apple-health-analyst render \
--type training \
--insights ./output/insights.json \
--narrative ./output/training.report.llm.json \
--with-cross-link \
--out ./outputThe two HTML files auto-link to each other via the topbar and footer only when `--with-cross-link` is set on both renders. Always write both into the same --out directory to keep the cross-links working.
Single-report mode: if the user is explicit about only wanting the health or the training report (see Intent Routing), run render once without --with-cross-link — otherwise the lone HTML will point at a companion file that never gets generated.
Use the existing health schema in references/report-llm-json.md.
Prioritize:
crossMetric.sleepRecoveryLinkcrossMetric.sleepConsistencycrossMetric.activityRecoveryBalancecrossMetric.recoveryCoherencecrossMetric.patternsriskFlags, notableChanges, and dataGapscrossMetric.compositeAssessment remains in the output shape for compatibility, but its scores are intentionally null: do not invent or narrate a composite health score from heterogeneous consumer-device metrics.
Health writing rules:
summary.json or insights.jsonkey_findings must be cross-metric, not single-metric triviaactions_next_2_weeks must specify time, frequency, or numeric targetsquestions_for_doctor must be data-driven and specificassessment, overview, findings, and chart callouts
when_to_seek_care and questions_for_doctor empty when there is norisk, persistent change, or symptom-contingent follow-up supported by the data
Use the training schema in references/training-report-llm-json.md.
Prioritize:
training.summary.trainingState and training.summary.readinesstraining.summary.trainingLoad — the scale-relative MET-minute EWMA trend; interpret together with recovery evidencetraining.summary.loadTrend and training.summary.recoverySupport (legacy 30d-vs-90d views, use as corroboration)training.sports[] in descending importancetraining.charts[]dataGaps[] and missing metric coverageTraining writing rules:
training.summary.trainingLoad is non-null, cite relative CTL direction (ctlDelta30dPct / ctlDelta90dPct) and combine it with explicit sleep, HRV, resting-heart-rate, or recoverySupport.adequate evidence.trainingLoad is null, fall back to loadTrend and say so explicitly (e.g. "数据覆盖不足 28 天,暂以 30 天对比为准")training.sports[]do not infer that the user wants to restart it
explicitly requested a program; otherwise change one training variable at a time
summary.jsoninsights.jsonsummary.json and insights.jsoninsufficient_data, say so plainlyrenderprepare cannot find the HealthData XML, verify the user provided the official Apple Health export ZIP. The main XML filename is not fixed and may be localized (for example 导出.xml) or appear as mojibake. export_cda.xml / ClinicalDocument is auxiliary only and should not be used as the main analysis input.--from and --to constrain analysis output after parsing and do not currently reduce peak parse memory; use a machine with more memory or pre-filter a copy of the export with a trusted local toolreport.llm.json matches schema v3training.report.llm.json matches schema v2 and only references existing sport/chart IDsnpm_config_cache=./.npm-cacheprepare / render; create directories separately if neededAlways produced by prepare:
summary.jsoninsights.jsonHealth render (file names are fixed; do not rename):
report.llm.jsonreport.mdreport.htmlTraining render (file names are fixed; do not rename):
training.report.llm.jsontraining.report.mdtraining.report.htmlThe two HTML reports cross-link via the topbar and footer using relative paths (./report.html ↔ ./training.report.html). Keep both in the same output/ directory for the links to work.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.