Direct phone CLI
The healthmd CLI connects directly to an open Health.md app on iPhone or Android. The standalone CLI never requires or routes through Health.md for Mac, and it has no backend selection. The phone reads its platform health store: HealthKit on iPhone or Health Connect on Android. It stages the result in protected storage and transfers validated partitions to the CLI.
healthmd on the computer <-> authenticated encrypted Manual IP, Tailscale, or supported Nearby channelHealth.md on iPhone or Android -> HealthKit / Health Connect -> protected bounded spool -> raw snapshots, production-generated files, or (iPhone) canonical and typed query dataThe bundled Swift direct backend is available on macOS and pairs with iPhone. Android application protocol v2 is part of the publicly packaged cross-platform Rust preview. Current iOS and Android releases use the same selector-3 universal QR for new portable pairing. Basic physical connectivity is confirmed on both phone platforms, but the full exact-build release matrix remains pending, so this is still an explicitly unqualified workflow.
Mobile compatibility for 0.1.0-alpha.6
Section titled “Mobile compatibility for 0.1.0-alpha.6”This standalone compatibility table is the actionable matrix for the explicitly unqualified preview. Basic iPhone and Android connectivity is physically confirmed. No public CLI/mobile pair has completed and retained the full qualification matrix yet.
| Mobile source | Protocol | Exact tag-SHA counterpart / unqualified compatibility floor | Portable Rust operations | Public status |
|---|---|---|---|---|
| Export-capable iPhone | pairing selector 3 current (1 legacy) / application v1 | iOS 3.3.0 (build 202609032317) / iOS 3.0.3 | Status, raw, extract, files, resume, cancel | Connectivity confirmed; full qualification pending |
| Query-capable iPhone | pairing selector 3 current (1 legacy) / application v1 + query v3 | iOS 3.3.0 (build 202609032317) / iOS 3.0.3 | V1 plus 19-tool local MCP/query | Connectivity confirmed; full qualification pending |
| Android | pairing selector 3 current (2 legacy) / application v2 | Android 1.8.2 (versionCode 31) / Android 1.5.4 (versionCode 25) |
Status, native raw, files, resume, cancel | Connectivity confirmed; full qualification pending |
| Android typed MCP query | N/A | Not implemented | Query tools require iPhone v3 | Unsupported |
What direct mode supports
Section titled “What direct mode supports”- shared selector-3 one-time pairing and trusted reconnect with iPhone (application v1) or Android (application v2) sources.
- local trusted-device inspection and unpairing.
- live phone readiness.
- strict raw export, schema-v8
healthmd.health_dataon iPhone, provider-native Health Connect snapshots on Android. - selected canonical extraction (iPhone only).
- production-generated file export on both phone platforms.
- durable local job status and resume.
- explicit cancellation.
- the same-executable
healthmd mcp servestdio server with direct typed queries, metric catalog, evidence, MCP Apps UI, and PNG fallback (iPhone only).
The bundled Swift helper that ships inside Health.md for Mac also offers a compatible direct mode. Select it with --backend direct. The standalone Rust CLI shown on this page is direct-only and accepts no backend flag. Mac-oriented doctor, query, evidence, and refresh subcommands belong to that bundled helper. They return backend_unsupported in its direct mode. They do not exist in the standalone Rust grammar and never switch to the Mac app. Use healthmd mcp serve for fresh direct-iPhone typed analysis. Run healthmd setup codex to configure and pair Codex automatically. healthmd mcp schema [TOOL] prints the exact nested MCP input schema and local examples. Use healthmd_sleep_sessions directly for sleep. Canonical extract output is not the typed query API.
Requirements
Section titled “Requirements”- A direct-capable
healthmdbinary and a matching Health.md build: iPhone (application v1) or Android (application v2). Android pairing requires the portable Rust client. The bundled macOS helper pairs with iPhone only. - Health.md open in the foreground on the phone for pairing and new commands.
- Settings > Mac Sync > Direct CLI Access enabled on iPhone, or Settings → Direct CLI on Android.
- Platform health permission (HealthKit or Health Connect), protected data, local network permission, and export quota available.
- A reachable computer address and TCP port
17647for Manual IP. A Tailscale address works. - An existing absolute destination for generated-file mode.
The CLI is the listener. The phone connects to the computer address entered in Direct CLI Access.
Transport support
Section titled “Transport support”| Transport | Bundled Swift helper on macOS | Portable Rust client |
|---|---|---|
| Manual IP on a LAN | Yes | macOS, Linux, Windows |
| Tailscale address | Yes | macOS, Linux, Windows |
| Nearby / MultipeerConnectivity | Yes | No |
Nearby uses Apple’s encrypted Multipeer session plus the same Health.md application authentication and encryption used by Manual IP. The portable client returns transport_unsupported for Nearby.
Pair once with Manual IP
Section titled “Pair once with Manual IP”Start the listener on the computer:
healthmd direct pair --transport manual-ipThe portable Rust client renders one universal iOS/Android QR. It writes its shared 20-digit code, candidate computer addresses, listener port, and six-digit legacy-iOS fallback to stderr. The bundled macOS helper prints only its legacy six-digit iPhone code. stdout stays reserved for the final JSON result.
On iPhone:
- Open Health.md > Sync > CLI > Direct CLI Access.
- Tap Scan Pairing QR and scan the universal QR shown by the portable CLI. A valid in-app scan starts pairing immediately. Do not open it as a custom URL.
- If scanning is unavailable, enable Manual IP and enter the LAN/Tailscale address, port, and shared 20-digit code. Six-digit entry remains available only for the bundled or another legacy Apple client.
- Keep the app open until both sides report success.
Portable pairing codes expire when their bounded listener closes (after two minutes by default, at most ten minutes). They are never sent over the network or persisted.
Pair an Android phone
Section titled “Pair an Android phone”Android pairing uses the portable Rust client’s same universal selector-3 QR and 20-digit (~66-bit) code. Android never downgrades its application protocol to iPhone v1.
- Open Health.md > Settings → Direct CLI on the Android phone.
- Tap Scan pairing QR. A valid in-app scan starts pairing immediately.
- If camera access or hardware is unavailable, enter the same LAN/Tailscale address, port, and 20-digit code manually.
- Keep the app open. Android runs a visible, user-started data-sync foreground service for an active direct session. Both Play and F-Droid builds use CameraX and ZXing Core rather than a Google-only scanner service.
After the one-time code is consumed, reconnect trust is Keystore-backed.
Use a different port when needed:
healthmd --port 18000 direct pair --transport manual-iphealthmd --port 18000 statusKeep using the same explicit port for later status, export, resume, and cancel commands.
Pair with Nearby
Section titled “Pair with Nearby”Nearby is available only in the bundled Swift helper:
healthmd direct pair --transport nearbySelect Nearby in Direct CLI Access on iPhone, enter the displayed code, and keep both devices open until pairing finishes. No failed Nearby operation switches to Manual IP.
Trusted devices
Section titled “Trusted devices”Pairing creates trust separate from the Health.md Mac app’s sync relationship.
healthmd direct deviceshealthmd direct unpair DEVICE_UUIDThese commands read or modify local trust and do not contact the phone. On iPhone, use Forget Paired CLI to remove the other side. On Android, remove the pairing from Settings → Direct CLI.
When more than one phone is trusted, select the intended installation explicitly:
healthmd --device DEVICE_UUID statusUse healthmd direct reset-trust --confirm only when local trust is corrupt or belongs to a replaced installation. It removes all local direct pairings. Forget those pairings on the phone before starting over.
Check live readiness
Section titled “Check live readiness”healthmd --transport manual-ip statusA direct status response reports connection and safety state without health values. The portable client reports the source under source with a platform of ios or android. iPhone sources repeat the same data under iphone. Check these fields before starting work (iPhone source shown):
| Field | Ready state |
|---|---|
direct_cli.paired |
true |
iphone.connected |
true |
iphone.app_active |
true for new work |
iphone.protected_data_available |
true |
iphone.can_trigger_raw_exports |
true for raw and extract |
iphone.can_trigger_exports |
true for generated files |
Direct status reports no selected destination. File mode uses only the explicit --destination supplied to the command.
An Android source reports platform: "android" with app_active, protected_data_available, export_in_progress, and its available raw products instead of the iPhone trigger flags.
Strict raw export (iPhone)
Section titled “Strict raw export (iPhone)”Choose one range selector:
healthmd export --yesterday --raw --output yesterday.jsonhealthmd export --last 7 --raw --output week.jsonhealthmd export \ --from 2026-07-01 --to 2026-07-07 --raw --output range.jsonhealthmd export --all --raw --output complete-health-corpus.jsonOmit --output to stream validated JSON to stdout. An output file is safer for sensitive or large responses.
iPhone strict raw returns healthmd.raw_result v1 containing ordinary schema-v8 healthmd.health_data days and their canonical source archives. It temporarily requests lossless detail without changing saved iPhone settings. The CLI validates the exact dates, profile, schema, archive, manifests, digest chain, final body digest, and completion state before exposing the result.
A complete-empty day is successful. Missing, partial, failed, cancelled, unsupported, or skipped requested data produces partial_success and a nonzero exit unless --allow-partial is explicit.
Provider-native raw export (Android)
Section titled “Provider-native raw export (Android)”The portable Rust client has no backend flag, so Android raw commands use the same grammar:
healthmd export --last 7 --raw --provider health_connect \ --raw-format ndjson --output health-connect.ndjson--provider names one explicit provider and defaults to health_connect. --raw-format defaults to NDJSON, the recommended shape for large snapshots. In-memory JSON validation is capped at 64 MiB. Metric selection supports --metric and --all-metrics, but not canonical or generated-file selectors, those remain iPhone capabilities.
Android raw snapshots keep their Health Connect provider-native contract. They are never converted into HealthKit-shaped healthmd.health_data days, and related-but-different statistics keep their own identities.
Canonical extraction
Section titled “Canonical extraction”Direct extraction uses the same durable raw transport but returns selected source-shaped data instead of the transport wrapper. It is an iPhone capability:
healthmd extract \ --category Sleep --last 7 --output sleep.json
healthmd extract \ --metric workouts --last 14 --object records \ --detail lossless --output workout-records.jsonMetric, category, source, and detail selection reaches iPhone before HealthKit reads. See Canonical extraction for object selectors, JSON Pointers, JSONL, and receipts.
Production-generated files
Section titled “Production-generated files”Direct file mode asks the phone to run Health.md’s production exporters, then transfers the resulting files to an explicit computer destination.
mkdir -p "$HOME/Documents/HealthVault"
healthmd export --yesterday \ --destination "$HOME/Documents/HealthVault"
healthmd export --last 7 \ --category Sleep --detail summary \ --destination "$HOME/Documents/HealthVault"
healthmd export --yesterday --use-iphone-settings \ --destination "$HOME/Documents/HealthVault"The destination must already exist, be absolute, and not resolve through a symlink. Direct mode never guesses or uses a Mac app bookmark. --output is for raw or extraction output. --destination is for generated files.
By default, a request keeps saved formats, Health subfolder, filenames, templates, write mode, Daily Note Injection, and Daily Notes Only. It suppresses roll-ups and summary-only mode for that job. Repeatable --metric or --category options plus --detail replace only the job’s metric and detail scope. --use-iphone-settings mirrors all saved settings and cannot combine with selectors.
The iPhone can stage JSON, CSV, Markdown, ZIP, data dictionaries, roll-ups, individual records, daily notes, and provider sidecars. The CLI validates each relative path, byte count, digest, file manifest, destination identity, and request fingerprint before committing. It rejects traversal, symlink ancestors, root mutation, path collisions, and digest changes. Overwrite is atomic. Append and Markdown merge use persisted plans so a replay does not duplicate content.
Generated-file destinations support iPhone protocol v1 and Android protocol v2. They work on macOS, Linux, and Windows. Android limits each generated job to 4,096 files.
Android protocol v2 file jobs use the device’s saved export selections or --profile PROFILE_ID. Android file jobs reject CLI metric, category, and detail selectors. On either phone platform, --profile resolves frozen output settings. The required --destination remains the explicit computer folder. For stable IDs and fail-closed profile behavior, see Export profiles.
Foreground and background behavior
Section titled “Foreground and background behavior”Pairing and new work require the phone app in the foreground. Direct CLI Access does not turn the phone into a headless export server or authorize background capture.
For query, export, extract, resume, and cancel, the portable CLI keeps an unavailable request open
for a bounded 120-second wake window. Unlock and open Health.md before it expires and the same
request continues without a re-run. Use --wake-timeout SECONDS per command (0 disables). MCP
uses HEALTHMD_WAKE_TIMEOUT and emits health-free progress when the caller supplied a progress
token. Published alpha.6 binaries are wait-only. In subsequent official builds, an enrolled iPhone
also receives one best-effort APNs notification through Health.md’s notification-only wake service.
Android and unenrolled iPhones remain wait-only. The notification can restore user presence but
never authorizes a HealthKit read or sends health scope through the Worker.
On iPhone, if an export is already connected when the app moves to the background, Health.md requests finite iOS background execution time. The export may finish during that allowance. If iOS expires the allowance, the connection closes and the durable job pauses. Reopen Health.md and resume the same job.
On Android, an active direct session runs a visible, user-started data-sync foreground service. Keep the app in the foreground for pairing and new work.
On iPhone, a global activity banner shows the capture and transfer phase, completed days, byte progress, and job status. It does not display health values.
While the phone app remains foreground, a trusted direct session can reconnect automatically after a transient disconnect. It retries with short, capped backoff delays. The host wake window waits for that reconnect. It does not launch a suspended app, bypass unlock, or promise background access.
Durable resume and cancel
Section titled “Durable resume and cancel”Direct jobs expire seven days after creation. Timeout, Ctrl-C, process death, disconnect, and background expiration do not cancel them.
healthmd status --job JOB_UUIDhealthmd resume JOB_UUID --timeout 300 --output recovered.jsonhealthmd cancel JOB_UUIDResume keeps the original dates, settings, destination, request fingerprint, device, and partition frontier. You cannot point a file job at a different destination during resume.
Cancel records a durable request, but cancellation becomes terminal only after the paired phone acknowledges it. If the phone is unavailable, status remains cancellation_pending. Reopen the same phone and retry cancel.
Security model
Section titled “Security model”- Current portable onboarding uses ephemeral key agreement and selector-3 transcript proofs bound to one shared high-entropy 20-digit (~66-bit) iOS/Android code. Legacy Apple selector 1 and Android selector 2 remain byte-compatible.
- QR handoffs are accepted only by explicit in-app scanners for canonical private-LAN/Tailscale addresses. External custom-URL opens cannot authorize pairing.
- Reconnect proves a random stored secret and both installation identities.
- Each connection derives fresh keys and nonces.
- Messages and binary frames use ChaCha20-Poly1305 with monotonic sequence checks.
- Partitions use SHA-256 manifests and a chained digest frontier.
- iPhone trust is stored in Keychain. Android reconnect trust is Keystore-backed.
- Portable trust uses Keychain, Secret Service, or Windows Credential Manager and never falls back to plaintext.
- Spools and journals use private application storage and exclude backups where the platform supports it.
Manual IP remains encrypted on a local network or Tailscale. Tailscale protects the network path too, but it does not replace Health.md’s application authentication.
Common errors
Section titled “Common errors”| Error | Action |
|---|---|
direct_not_paired |
Pair this CLI installation with the intended mobile source. |
direct_device_selection_required |
Pass the intended trusted --device. |
direct_trust_invalid |
Preserve diagnostics. Reset trust only when recovery is impossible. |
direct_iphone_unavailable |
Check the paired phone’s foreground state, access toggle, address, port, permission, and LAN or Tailscale reachability. |
direct_export_paused |
Inspect the job, reopen the paired phone, and resume it. |
direct_cancellation_pending |
Reopen the paired phone and retry cancel. |
transport_unsupported |
Use Manual IP or Tailscale in the portable client. |
backend_unsupported |
Bundled Swift helper only: use its default Mac loopback mode for query, evidence, doctor, or metrics. The standalone CLI uses healthmd mcp serve instead. |
invalid_direct_raw_response |
Do not consume the output. Keep validation diagnostics. |
invalid_direct_file_receipt |
Do not repair files manually. Inspect and resume the job. |
job_expired |
The seven-day state lifetime ended. Confirm before starting new work. |