stacks/ts-health
publicClone
Push over the same URL. A password will not work: create a token under access tokens and use it in place of one.
- .config
- .github
- .vscode
- docs
- packages
- scripts
- .editorconfig 147 B
- .gitattributes 12 B
- .gitignore 117 B
- bun.lock 36.0 KB
- bunfig.toml 88 B
- bunpress.config.ts 3.5 KB
- CHANGELOG.md 12 B
- CLAUDE.md 1.6 KB
- deps.yaml 32 B
- health 58 B
- health.config.ts 233 B
- LICENSE.md 1.1 KB
- package.json 2.0 KB
- pantry.lock 336 B
- README.md 23.9 KB
- tsconfig.json 664 B

ts-health
A comprehensive TypeScript library for health, fitness, and smartwatch data. Unified access to Oura Ring, WHOOP, Apple Health, Fitbit, Withings, Renpho, Garmin, Polar, Suunto, Coros, and Wahoo — with smart scale body composition, FIT file parsing, training metrics, data export, and more. Built for training apps.
Features
Health Platform APIs
- Oura Ring - Full API v2 support: sleep, readiness, activity, heart rate, HRV, SpO2, stress, body temperature, VO2 max, sessions, tags, rest mode
- WHOOP - Recovery, strain, sleep, workouts, HRV, SpO2, skin temperature
- Apple Health - XML export parsing for sleep stages, heart rate, HRV, steps, workouts, SpO2, VO2 max, body composition
- Fitbit - Sleep stages, activity summaries, intraday heart rate, HRV, SpO2, skin temperature, cardio score, Aria scale data
Smart Scales & Body Composition
- Withings - Full API support for Body, Body+, Body Cardio, Body Comp, Body Scan: weight, body fat %, muscle mass, bone mass, water %, visceral fat, BMR, heart rate
- Renpho - Weight, body fat %, muscle mass, bone mass, water %, visceral fat, BMR, protein %, subcutaneous fat, skeletal muscle
- Fitbit Aria - Weight and body fat via the Fitbit driver
- Apple Health - Weight, BMI, body fat %, lean body mass from synced scale data
Smartwatch & Device Support
- Garmin - USB download, device detection, FIT file parsing, Garmin Connect API
- Polar - Device data download and parsing
- Suunto - Device data download and parsing
- Coros - Device data download and parsing
- Wahoo - Device data download and parsing
- Apple Watch - Activity and health data from device exports
FIT File Parsing
- Binary FIT protocol parser for activity files from any device
- Full support for activity records, laps, sessions, GPS tracks
- Monitoring data: daily HR, steps, stress, sleep, body battery
- Sport type and sub-sport type classification
Training Analysis
- Training Load - TSS (Training Stress Score), NP (Normalized Power), IF (Intensity Factor)
- Training Readiness - HRV trends, sleep quality, recovery, resting HR, activity balance, sleep debt
- Recovery Analysis - Sleep-based, HRV trend, resting HR trend, strain balance scoring
- Zone Calculator - HR and power zones based on thresholds
- Race Predictor - Race time predictions from performance data
- CTL/ATL/TSB - Chronic Training Load, Acute Training Load, Training Stress Balance
Health Monitoring
- Sleep - Duration, stages (deep, light, REM, awake), efficiency, latency, quality scoring, debt tracking
- Readiness - Daily readiness scores with contributor breakdowns
- Heart Rate - Continuous monitoring, resting HR, intraday data
- HRV - Heart rate variability (RMSSD, SDNN) from sleep and throughout the day
- Activity - Steps, calories, active minutes, movement metrics, strain
- SpO2 - Blood oxygen saturation
- Stress - Stress levels and recovery periods
- Body Temperature - Skin temperature deviations and trends
- VO2 Max - Cardio fitness estimates
- Workouts - Activity type, duration, calories, distance, heart rate, GPS tracks
- Body Composition - Weight, body fat %, muscle mass, bone mass, water %, visceral fat, BMR, metabolic age
- Weight Tracking - Weight measurements with BMI from smart scales
Data Export
- GPX - GPS Exchange Format for route/track sharing
- TCX - Training Center XML for workout uploads
- CSV - Spreadsheet-compatible data export
- GeoJSON - Geographic data for mapping applications
Real-time Sensors
- ANT+ - Connect to ANT+ heart rate monitors, power meters, speed/cadence sensors
- BLE - Bluetooth Low Energy sensor connectivity
- Live Tracking - Real-time data streaming from connected sensors
Developer Experience
- TypeScript - Fully typed APIs with comprehensive type exports
- CLI Tool - Sync, view, and analyze health data from the command line
- Unified Interface - Same
HealthDriverinterface across all health platforms - Single Package - All device + platform support in one import
Install
bun install ts-healthQuick Start
Health Platform Data (Oura, WHOOP, Fitbit)
import { createOuraDriver } from 'ts-health'
const oura = createOuraDriver('your-personal-access-token')
// Get last week's sleep data
const sleep = await oura.getSleep({
startDate: '2025-01-01',
endDate: '2025-01-07',
})
for (const session of sleep) {
const hours = Math.floor(session.totalSleepDuration / 3600)
const mins = Math.floor((session.totalSleepDuration % 3600) / 60)
console.log(`${session.day}: ${hours}h ${mins}m | efficiency: ${session.efficiency}%`)
}Smart Scale Data (Withings, Renpho)
import { createWithingsDriver, createRenphoDriver } from 'ts-health'
// Withings (Body, Body+, Body Cardio, Body Comp, Body Scan)
const withings = createWithingsDriver('your-access-token')
const bodyComp = await withings.getBodyComposition({
startDate: '2025-01-01',
endDate: '2025-01-31',
})
for (const measurement of bodyComp) {
console.log(`${measurement.day}: ${measurement.weight.toFixed(1)} kg`)
console.log(` Body fat: ${measurement.bodyFatPercentage?.toFixed(1)}%`)
console.log(` Muscle: ${measurement.muscleMass?.toFixed(1)} kg`)
}
// Renpho smart scales
const renpho = createRenphoDriver({ email: 'you@example.com', password: 'your-password' })
const weights = await renpho.getWeightMeasurements({ startDate: '2025-01-01' })Smartwatch Device Data (Garmin, Polar, Suunto, Coros, Wahoo)
import { createGarminDriver, parseFITFile } from 'ts-health'
// Download directly from a connected Garmin device
const garmin = createGarminDriver()
const devices = await garmin.detectDevices()
if (devices.length > 0) {
const result = await garmin.downloadData(devices[0], {
includeActivities: true,
includeMonitoring: true,
})
console.log(`Downloaded ${result.activities.length} activities`)
}
// Or parse any FIT file
const activity = await parseFITFile('/path/to/activity.fit')
console.log(`Sport: ${activity.sport} | Distance: ${(activity.totalDistance / 1000).toFixed(1)}km`)Training Load & Metrics
import { calculateTSS, calculateNormalizedPower, ZoneCalculator } from 'ts-health'
// Calculate training metrics from activity data
const tss = calculateTSS(activity, { ftp: 250 })
const np = calculateNormalizedPower(activity)
console.log(`TSS: ${tss} | NP: ${np}W`)
// Power and HR zone calculations
const zones = new ZoneCalculator({ maxHR: 185, restingHR: 50, ftp: 250 })Data Export
import { exportToGPX, exportToTCX, exportToCSV } from 'ts-health'
// Export activity to various formats
await exportToGPX(activity, '/path/to/output.gpx')
await exportToTCX(activity, '/path/to/output.tcx')
await exportToCSV(activity, '/path/to/output.csv')Cloud Integrations
import { createGarminConnectClient, createStravaClient } from 'ts-health'
// Sync from Garmin Connect
const garminConnect = createGarminConnectClient({ username: '...', password: '...' })
const activities = await garminConnect.getActivities({ start: 0, limit: 10 })
// Upload to Strava
const strava = createStravaClient({ accessToken: '...' })
await strava.uploadActivity('/path/to/activity.fit')Usage Examples
Oura Ring - Sleep & Readiness
import { createOuraDriver } from 'ts-health'
const oura = createOuraDriver('your-token')
const range = { startDate: '2025-01-01', endDate: '2025-01-14' }
// Detailed sleep sessions with HR and HRV time series
const sleep = await oura.getSleep(range)
// Daily sleep scores with contributor breakdown
const dailySleep = await oura.getDailySleep(range)
// Training readiness
const readiness = await oura.getReadiness(range)
for (const r of readiness) {
console.log(`${r.day}: score ${r.score} | HRV balance: ${r.contributors.hrvBalance}`)
}
// Heart rate, HRV, SpO2, stress
const hr = await oura.getHeartRate(range)
const hrv = await oura.getHRV(range)
const spo2 = await oura.getSpO2(range)
const stress = await oura.getStress(range)
// Body temperature and VO2 max
const temp = await oura.getBodyTemperature(range)
const vo2 = await oura.getVO2Max(range)
// Ring configuration and personal info
const rings = await oura.getRingConfiguration()
const profile = await oura.getPersonalInfo()WHOOP - Recovery & Strain
import { createWhoopDriver } from 'ts-health'
const whoop = createWhoopDriver('your-access-token')
// Recovery scores (maps to readiness interface)
const recovery = await whoop.getReadiness({ startDate: '2025-01-01' })
for (const r of recovery) {
console.log(`${r.day}: recovery ${r.score}% | HRV: ${r.contributors.hrvBalance}ms`)
}
// Sleep data with stage breakdown
const sleep = await whoop.getSleep({ startDate: '2025-01-01' })
// Workout strain
const workouts = await whoop.getWorkouts({ startDate: '2025-01-01' })Apple Health - Export Parsing
import { createAppleHealthDriver } from 'ts-health'
// Point to your Apple Health export XML file
const apple = createAppleHealthDriver('/path/to/export.xml')
const sleep = await apple.getSleep({ startDate: '2025-01-01' })
const activity = await apple.getDailyActivity({ startDate: '2025-01-01' })
const heartRate = await apple.getHeartRate({ startDate: '2025-01-01' })
const hrv = await apple.getHRV({ startDate: '2025-01-01' })
const vo2 = await apple.getVO2Max({ startDate: '2025-01-01' })Fitbit - Activity & Sleep
import { createFitbitDriver } from 'ts-health'
const fitbit = createFitbitDriver('your-access-token')
const sleep = await fitbit.getSleep({ startDate: '2025-01-01', endDate: '2025-01-07' })
const activity = await fitbit.getDailyActivity({ startDate: '2025-01-01', endDate: '2025-01-07' })
const hrv = await fitbit.getHRV({ startDate: '2025-01-01', endDate: '2025-01-07' })Withings Smart Scale - Body Composition
import { createWithingsDriver } from 'ts-health'
const withings = createWithingsDriver('your-access-token')
const range = { startDate: '2025-01-01', endDate: '2025-01-31' }
// Full body composition data
const body = await withings.getBodyComposition(range)
for (const m of body) {
console.log(`${m.day}: ${m.weight.toFixed(1)} kg | fat: ${m.bodyFatPercentage?.toFixed(1)}%`)
console.log(` Muscle: ${m.muscleMass?.toFixed(1)} kg | Bone: ${m.boneMass?.toFixed(1)} kg`)
console.log(` Water: ${m.waterPercentage?.toFixed(1)}% | Visceral fat: ${m.visceralFat}`)
}
// Simple weight tracking
const weights = await withings.getWeightMeasurements(range)
// Heart rate from scales with HR sensor (Body Cardio, Body Comp)
const hr = await withings.getHeartRate(range)Renpho Smart Scale - Body Composition
import { createRenphoDriver } from 'ts-health'
// Renpho uses email/password authentication
const renpho = createRenphoDriver({
email: 'your-email@example.com',
password: 'your-password',
})
const body = await renpho.getBodyComposition({ startDate: '2025-01-01' })
for (const m of body) {
console.log(`${m.day}: ${m.weight.toFixed(1)} kg | fat: ${m.bodyFatPercentage?.toFixed(1)}%`)
console.log(` Protein: ${m.proteinPercentage?.toFixed(1)}% | Skeletal muscle: ${m.skeletalMuscle?.toFixed(1)}%`)
console.log(` BMR: ${m.basalMetabolicRate} kcal`)
}Training Readiness Analysis
import { createOuraDriver, createReadinessAnalyzer } from 'ts-health'
const oura = createOuraDriver('your-token')
const analyzer = createReadinessAnalyzer()
const range = { startDate: '2025-01-01', endDate: '2025-01-14' }
const [sleep, readiness, hrv, activity] = await Promise.all([
oura.getSleep(range),
oura.getReadiness(range),
oura.getHRV(range),
oura.getDailyActivity(range),
])
const result = analyzer.calculateTrainingReadiness({
sleep,
readiness,
hrv,
activity,
})
console.log(`Readiness: ${result.score}/100`)
console.log(`Recommendation: ${result.recommendation}`)
// => "go_hard" | "moderate" | "easy_day" | "rest"
console.log(result.details)Combine Device + Platform Data
import {
createGarminDriver,
createOuraDriver,
createRecoveryAnalyzer,
calculateTSS,
} from 'ts-health'
// Training stress from your Garmin watch
const garmin = createGarminDriver()
const devices = await garmin.detectDevices()
const data = await garmin.downloadData(devices[0], { includeActivities: true })
const tss = calculateTSS(data.activities[0], { ftp: 250 })
// Recovery status from Oura Ring
const oura = createOuraDriver('oura-token')
const recovery = createRecoveryAnalyzer()
const range = { startDate: '2025-01-01', endDate: '2025-01-14' }
const [sleep, hrv] = await Promise.all([oura.getSleep(range), oura.getHRV(range)])
const status = recovery.calculateRecovery({ sleep, hrv })
console.log(`TSS: ${tss} | Recovery: ${status.status} (${status.score}/100)`)
if (tss > 100 && status.status !== 'fully_recovered') {
console.log('High training load + incomplete recovery — consider an easy day')
}Race Predictions
import { RacePredictor } from 'ts-health'
const predictor = new RacePredictor()
const predictions = predictor.predictFromPerformance(5000, 20 * 60) // 5K in 20min
console.log(`Predicted marathon: ${Math.floor(predictions.marathon / 60)}:${(predictions.marathon % 60).toFixed(0).padStart(2, '0')}`)Sleep Quality Analysis
import { createOuraDriver, createSleepAnalyzer } from 'ts-health'
const oura = createOuraDriver('your-token')
const analyzer = createSleepAnalyzer()
const sessions = await oura.getSleep({ startDate: '2025-01-01' })
for (const session of sessions) {
const quality = analyzer.scoreSleepQuality(session)
console.log(`${session.day}: ${quality.overall}/100 (${quality.rating})`)
console.log(` Duration: ${quality.durationScore} | Deep: ${quality.deepSleepScore} | REM: ${quality.remSleepScore}`)
}
// Sleep consistency across multiple nights
const consistency = analyzer.scoreSleepConsistency(sessions)
console.log(`Sleep consistency: ${consistency}/100`)
// Sleep debt analysis
const debt = analyzer.analyzeSleepDebt(sessions)
console.log(`Sleep debt: ${debt.currentDebtMinutes} minutes | Trend: ${debt.trend}`)Health Trends
import { createTrendAnalyzer } from 'ts-health'
const trends = createTrendAnalyzer()
// Analyze HRV trend
const hrvTrend = trends.analyzeTrend('hrv', hrvData.map(h => ({
day: h.timestamp.slice(0, 10),
value: h.hrv,
})))
console.log(`HRV trend: ${hrvTrend.direction} (${hrvTrend.percentChange}%)`)
// Detect anomalies
const anomalies = trends.detectAnomalies(dataPoints, 2)
// Moving average
const smoothed = trends.calculateMovingAverage(dataPoints, 7)CLI
The CLI provides 22 commands for syncing, viewing, analyzing, and exporting health data from all supported platforms.
# Sync all health data from any platform
health sync --driver oura --token YOUR_TOKEN --days 30 --output ./data
health sync --driver withings --token YOUR_TOKEN --days 60
# View specific health metrics
health sleep --driver oura --token YOUR_TOKEN --days 7
health activity --driver oura --token YOUR_TOKEN --days 7
health workouts --driver oura --token YOUR_TOKEN --days 30
health hr --driver oura --token YOUR_TOKEN --days 1
health hrv --driver oura --token YOUR_TOKEN --days 14
health readiness --driver oura --token YOUR_TOKEN --days 7
health spo2 --driver oura --token YOUR_TOKEN --days 7
health stress --driver oura --token YOUR_TOKEN --days 7
health body-temp --driver oura --token YOUR_TOKEN --days 14
health vo2max --driver oura --token YOUR_TOKEN --days 30
# Smart scale data
health weight --driver withings --token YOUR_TOKEN --days 30
health body --driver withings --token YOUR_TOKEN --days 30
health body --driver renpho --email you@example.com --password pass123
# Analysis commands
health analyze --driver oura --token YOUR_TOKEN --days 14
health recovery --driver oura --token YOUR_TOKEN --days 14
health sleep-quality --driver oura --token YOUR_TOKEN --days 7
health sleep-debt --driver oura --token YOUR_TOKEN --days 14 --target 480
health trends --driver oura --token YOUR_TOKEN --days 30
health trends --driver withings --token YOUR_TOKEN --days 60 --metrics weight
# Overview & comparison
health dashboard --driver oura --token YOUR_TOKEN
health compare --driver oura --token YOUR_TOKEN --days 14
# Export & profile
health export --driver oura --token YOUR_TOKEN --days 90 --output ./export.json
health profile --driver oura --token YOUR_TOKEN
# JSON output for any command
health sleep --driver oura --token YOUR_TOKEN --format jsonSupported Platforms & Metrics
Health Platform APIs
| Metric | Oura | WHOOP | Apple Health | Fitbit |
|---|---|---|---|---|
| Sleep Stages | Deep, Light, REM, Awake | Deep, Light, REM, Awake | Deep, Core, REM, Awake | Deep, Light, REM, Wake |
| Sleep Score | Daily score + contributors | Efficiency % | Efficiency % | Efficiency + stages |
| Readiness | Score + 8 contributors | Recovery score | - | - |
| Heart Rate | Continuous | Via cycles | Continuous | Continuous + intraday |
| HRV | Sleep-based RMSSD | RMSSD | SDNN | Daily + deep RMSSD |
| SpO2 | Daily average | Via recovery | Spot checks | Daily avg/min/max |
| Stress | High/recovery/summary | Via strain | - | - |
| Body Temp | Deviation + trend | Skin temp | Wrist temp | Nightly relative |
| VO2 Max | Estimated | - | From workouts | Cardio score |
| Activity | Steps, calories, MET | Strain, kilojoules | Steps, calories, distance | Steps, calories, zones |
| Workouts | Type, duration, intensity | Type, strain, HR zones | Type, duration, distance | Type, duration, HR |
| Body Composition | - | - | Weight, fat %, lean mass | Weight, fat % (Aria) |
Smart Scales
| Metric | Withings | Renpho |
|---|---|---|
| Weight | Yes | Yes |
| BMI | Yes | Yes |
| Body Fat % | Yes | Yes |
| Muscle Mass | Yes | Yes |
| Bone Mass | Yes | Yes |
| Water % | Yes | Yes |
| Visceral Fat | Yes | Yes |
| BMR | Yes | Yes |
| Protein % | - | Yes |
| Subcutaneous Fat | - | Yes |
| Skeletal Muscle % | - | Yes |
| Heart Rate | Yes (Body Cardio/Comp) | - |
Smartwatch Devices
| Capability | Garmin | Polar | Suunto | Coros | Wahoo | Apple Watch |
|---|---|---|---|---|---|---|
| USB Download | Yes | Yes | Yes | Yes | Yes | - |
| FIT Parsing | Yes | Yes | Yes | Yes | Yes | - |
| GPS Tracks | Yes | Yes | Yes | Yes | Yes | Yes |
| HR Data | Yes | Yes | Yes | Yes | Yes | Yes |
| Cloud Sync | Garmin Connect | - | - | - | - | - |
| Activities | Full records + laps | Records + laps | Records + laps | Records + laps | Records + laps | Workout summaries |
| Monitoring | HR, stress, sleep, body battery | HR, sleep | HR, sleep | HR, sleep | HR | HR, sleep |
| Export | GPX, TCX, CSV, GeoJSON | GPX, TCX, CSV | GPX, TCX, CSV | GPX, TCX, CSV | GPX, TCX, CSV | - |
Training Metrics
| Metric | Description |
|---|---|
| TSS | Training Stress Score — normalized training load per session |
| NP | Normalized Power — weighted average power accounting for variability |
| IF | Intensity Factor — ratio of NP to FTP |
| CTL | Chronic Training Load — long-term fitness trend |
| ATL | Acute Training Load — short-term fatigue |
| TSB | Training Stress Balance — freshness (CTL - ATL) |
| HR Zones | Heart rate training zones (5 or 7 zone models) |
| Power Zones | Power-based training zones from FTP |
Platform Setup
Oura Ring
- Go to Oura Developer Portal
- Create a Personal Access Token
- Use the token with
createOuraDriver(token)
WHOOP
- Register at WHOOP Developer Portal
- Create an OAuth application
- Complete the OAuth flow to get an access token
- Use with
createWhoopDriver(accessToken)
Apple Health
- Open Health app on iPhone
- Tap profile > Export All Health Data
- Extract the ZIP file
- Use with
createAppleHealthDriver('/path/to/export.xml')
Fitbit
- Register at Fitbit Developer Portal
- Create an OAuth 2.0 application
- Complete the OAuth flow to get an access token
- Use with
createFitbitDriver(accessToken)
Withings Smart Scales
- Register at Withings Developer Portal
- Create an OAuth 2.0 application
- Complete the OAuth flow to get an access token
- Use with
createWithingsDriver(accessToken)
Renpho Smart Scales
- Use your existing Renpho account credentials
- Use with
createRenphoDriver({ email: '...', password: '...' })
Garmin (USB)
- Connect your Garmin watch via USB
- Use
createGarminDriver()to detect and download
Garmin Connect (Cloud)
- Use your Garmin Connect credentials
- Use
createGarminConnectClient({ username, password })
Strava
- Register at Strava Developer Portal
- Create an OAuth application
- Use
createStravaClient({ accessToken })
Configuration
// health.config.ts
import type { HealthConfig } from 'ts-health'
const config: HealthConfig = {
verbose: true,
outputDir: './health-data',
drivers: ['oura', 'withings'],
oura: {
personalAccessToken: 'your-token',
},
withings: {
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
accessToken: 'your-access-token',
},
renpho: {
email: 'your-email@example.com',
password: 'your-password',
},
}
export default configTesting
bun testChangelog
Please see our releases page for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Community
For help, discussion about best practices, or any other conversation that would benefit from being searchable:
For casual chit-chat with others using this package:
Join the Stacks Discord Server
Postcardware
"Software that is free, but hopes for a postcard." We love receiving postcards from around the world showing where Stacks is being used! We showcase them on our website too.
Our address: Stacks.js, 12665 Village Ln #2306, Playa Vista, CA 90094, United States
Sponsors
We would like to extend our thanks to the following sponsors for funding Stacks development. If you are interested in becoming a sponsor, please reach out to us.
License
The MIT License (MIT). Please see LICENSE for more information.
Made with 💙
[codecov-href]: https://codecov.io/gh/stacksjs/ts-health -->