@wix/himalaya-cli 0.1230.0 → 0.1232.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,93 @@
1
+ /** A readable health data type. */
2
+ export type HealthType = "steps" | "heartRate" | "activeEnergy" | "distance" | "workouts";
3
+ /** A writable health data type. v1 writes workouts only. */
4
+ export type HealthWriteType = "workouts";
5
+ export type HealthPermissionState = "granted" | "denied" | "notDetermined";
6
+ /** Aggregation bucket for `query`. Omitted ⇒ one total (cumulative types) or raw samples. */
7
+ export type HealthBucket = "hour" | "day";
8
+ export type WorkoutActivity = "running" | "walking" | "cycling" | "strength" | "other";
9
+ export type HealthErrorCode = "unavailable" | "not_authorized" | "invalid_argument" | "failed";
10
+ export declare const HEALTH_TYPES: readonly HealthType[];
11
+ export declare const WORKOUT_ACTIVITIES: readonly WorkoutActivity[];
12
+ /** The fixed unit each type's `value` is reported in. */
13
+ export declare const HEALTH_UNITS: Readonly<Record<HealthType, string>>;
14
+ export interface HealthAvailability {
15
+ available: boolean;
16
+ /** Why not, when `available` is false — e.g. "web has no health store",
17
+ * "Health Connect is not installed". */
18
+ reason?: string;
19
+ }
20
+ export interface HealthSample {
21
+ /** ISO-8601. */
22
+ start: string;
23
+ /** ISO-8601. */
24
+ end: string;
25
+ value: number;
26
+ unit: string;
27
+ /** Workouts only: the activity, mapped back onto the v1 set ("other" for anything else). */
28
+ activity?: WorkoutActivity;
29
+ }
30
+ export interface HealthQuery {
31
+ type: HealthType;
32
+ /** ISO-8601, inclusive. */
33
+ from: string;
34
+ /** ISO-8601, exclusive. */
35
+ to: string;
36
+ /** steps / activeEnergy / distance are SUMMED per bucket; heartRate is AVERAGED per bucket;
37
+ * workouts ignore it (one sample per workout). */
38
+ bucket?: HealthBucket;
39
+ }
40
+ export interface Workout {
41
+ activity: WorkoutActivity;
42
+ /** ISO-8601. */
43
+ start: string;
44
+ /** ISO-8601, after `start`. */
45
+ end: string;
46
+ energyKcal?: number;
47
+ distanceM?: number;
48
+ }
49
+ /** The typed rejection every verb throws. A plain Error carrying `code` — worker bundles are
50
+ * compiled to a target without class syntax, so this is a factory, not a subclass. */
51
+ export interface HealthError extends Error {
52
+ name: "HealthError";
53
+ code: HealthErrorCode;
54
+ }
55
+ /** Build a [HealthError] — what the binding throws for every native `{ error }` reply. */
56
+ export declare function healthError(code: HealthErrorCode, message: string): HealthError;
57
+ /** Narrow a caught value: `catch (e) { if (isHealthError(e) && e.code === "unavailable") … }`. */
58
+ export declare function isHealthError(e: unknown): e is HealthError;
59
+ /** Whether this device has a health store the app can reach. Call it first and branch. */
60
+ export declare function availability(): Promise<HealthAvailability>;
61
+ /**
62
+ * Prompt for read (and optionally write) access. Resolves with the types the platform
63
+ * reports as granted — on iOS a read type counts as granted once asked (see header).
64
+ * Writing workouts also covers the energy + distance samples a saved workout carries.
65
+ */
66
+ export declare function requestPermission(p: {
67
+ read: HealthType[];
68
+ write?: HealthWriteType[];
69
+ }): Promise<{
70
+ granted: HealthType[];
71
+ }>;
72
+ /** Current READ permission per type, without prompting. iOS reads are always "notDetermined". */
73
+ export declare function getPermissionStatus(p: {
74
+ types: HealthType[];
75
+ }): Promise<Record<HealthType, HealthPermissionState>>;
76
+ /** Read samples of one type over [from, to). */
77
+ export declare function query(p: HealthQuery): Promise<{
78
+ samples: HealthSample[];
79
+ }>;
80
+ /** Save a completed workout (with its energy/distance, when given). Resolves with its store id. */
81
+ export declare function saveWorkout(w: Workout): Promise<{
82
+ id: string;
83
+ }>;
84
+ export declare const version = 1;
85
+ /** The whole surface as one namespace: `import { health } from "@wix/himalaya/health"`. */
86
+ export declare const health: {
87
+ availability: typeof availability;
88
+ requestPermission: typeof requestPermission;
89
+ getPermissionStatus: typeof getPermissionStatus;
90
+ query: typeof query;
91
+ saveWorkout: typeof saveWorkout;
92
+ version: number;
93
+ };
@@ -14,6 +14,7 @@ export declare const CAPABILITY_REGISTRY: {
14
14
  readonly files: readonly ["list", "readText", "remove", "stat", "write"];
15
15
  readonly godot: readonly ["loadPack", "release", "send"];
16
16
  readonly haptic: readonly ["play"];
17
+ readonly health: readonly ["availability", "getPermissionStatus", "query", "requestPermission", "saveWorkout"];
17
18
  readonly himi_apps: readonly ["canOpen", "current", "hostInfo", "open"];
18
19
  readonly iap: readonly ["consume", "entitlements", "finishTransaction", "getPaywallConfig", "getProducts", "getUnfinishedTransactions", "purchase", "purchaseForVerification", "restore"];
19
20
  readonly keyboard: readonly ["dismiss"];
@@ -39,7 +40,7 @@ export declare const CAPABILITY_REGISTRY: {
39
40
  readonly wix_cashier: readonly ["isAvailable", "proceedToPayment"];
40
41
  };
41
42
  /** A capability id backed by an @himalaya/* SDK binding. */
42
- export type SdkCapability = "app_info" | "audio_record" | "base44" | "biometrics" | "browser_auth" | "calendar" | "calls" | "camera" | "clipboard" | "contacts" | "file_picker" | "files" | "godot" | "haptic" | "himi_apps" | "iap" | "keyboard" | "live_activity" | "local_database" | "location" | "media_playback" | "motion" | "on_device_ai" | "open_url" | "pdf" | "photos" | "presigned_upload" | "push" | "release_control" | "secure_storage" | "share" | "speaker_diarization" | "speech" | "system_intents" | "tasks" | "ui" | "wix_cashier";
43
+ export type SdkCapability = "app_info" | "audio_record" | "base44" | "biometrics" | "browser_auth" | "calendar" | "calls" | "camera" | "clipboard" | "contacts" | "file_picker" | "files" | "godot" | "haptic" | "health" | "himi_apps" | "iap" | "keyboard" | "live_activity" | "local_database" | "location" | "media_playback" | "motion" | "on_device_ai" | "open_url" | "pdf" | "photos" | "presigned_upload" | "push" | "release_control" | "secure_storage" | "share" | "speaker_diarization" | "speech" | "system_intents" | "tasks" | "ui" | "wix_cashier";
43
44
  /** All `capability.<id>.<verb>` action ids, flattened (the worker-bridge wire vocabulary). */
44
45
  export declare const CAPABILITY_ACTION_IDS: readonly string[];
45
46
  /** Capability id → the @himalaya/* package(s) a worker imports to reach it — every dispatch
@@ -734,7 +734,7 @@ export interface OrientationEnvelope {
734
734
  export declare const DEFAULT_ORIENTATION_ENVELOPE: Required<OrientationEnvelope>;
735
735
  /** Fill in the absent families. Total, so callers never branch on `undefined`. */
736
736
  export declare function resolveOrientationEnvelope(env?: OrientationEnvelope): Required<OrientationEnvelope>;
737
- export type Capability = "camera" | "barcode_scanner" | "biometrics" | "secure_storage" | "share" | "push" | "file_picker" | "files" | "open_url" | "pdf" | "clipboard" | "photos" | "presigned_upload" | "ui" | "calendar" | "contacts" | "tasks" | "audio_record" | "audio_record_background" | "calls" | "location" | "location_background" | "motion" | "media_playback" | "webview" | "browser_auth" | "haptic" | "himi_apps" | "iap" | "keyboard" | "wix_cashier" | "live_activity" | "local_database" | "speech" | "speaker_diarization" | "system_intents" | "on_device_ai" | "app_info" | "release_control" | "godot" | "base44";
737
+ export type Capability = "camera" | "barcode_scanner" | "biometrics" | "secure_storage" | "share" | "push" | "file_picker" | "files" | "open_url" | "pdf" | "clipboard" | "photos" | "presigned_upload" | "ui" | "calendar" | "contacts" | "tasks" | "audio_record" | "audio_record_background" | "calls" | "location" | "location_background" | "motion" | "health" | "media_playback" | "webview" | "browser_auth" | "haptic" | "himi_apps" | "iap" | "keyboard" | "wix_cashier" | "live_activity" | "local_database" | "speech" | "speaker_diarization" | "system_intents" | "on_device_ai" | "app_info" | "release_control" | "godot" | "base44";
738
738
  /** `defineApp({ base44 })` — which Base44 app `@himalaya/base44` talks to. */
739
739
  export interface Base44Config {
740
740
  /** The Base44 app id (24 hex chars, e.g. `69725232345f461f64ad1623`). */
@@ -29,6 +29,7 @@ var CAPABILITY_REGISTRY = {
29
29
  "files": ["list", "readText", "remove", "stat", "write"],
30
30
  "godot": ["loadPack", "release", "send"],
31
31
  "haptic": ["play"],
32
+ "health": ["availability", "getPermissionStatus", "query", "requestPermission", "saveWorkout"],
32
33
  "himi_apps": ["canOpen", "current", "hostInfo", "open"],
33
34
  "iap": ["consume", "entitlements", "finishTransaction", "getPaywallConfig", "getProducts", "getUnfinishedTransactions", "purchase", "purchaseForVerification", "restore"],
34
35
  "keyboard": ["dismiss"],
@@ -29,6 +29,7 @@ var CAPABILITY_REGISTRY = {
29
29
  "files": ["list", "readText", "remove", "stat", "write"],
30
30
  "godot": ["loadPack", "release", "send"],
31
31
  "haptic": ["play"],
32
+ "health": ["availability", "getPermissionStatus", "query", "requestPermission", "saveWorkout"],
32
33
  "himi_apps": ["canOpen", "current", "hostInfo", "open"],
33
34
  "iap": ["consume", "entitlements", "finishTransaction", "getPaywallConfig", "getProducts", "getUnfinishedTransactions", "purchase", "purchaseForVerification", "restore"],
34
35
  "keyboard": ["dismiss"],
@@ -29,6 +29,7 @@ var CAPABILITY_REGISTRY = {
29
29
  "files": ["list", "readText", "remove", "stat", "write"],
30
30
  "godot": ["loadPack", "release", "send"],
31
31
  "haptic": ["play"],
32
+ "health": ["availability", "getPermissionStatus", "query", "requestPermission", "saveWorkout"],
32
33
  "himi_apps": ["canOpen", "current", "hostInfo", "open"],
33
34
  "iap": ["consume", "entitlements", "finishTransaction", "getPaywallConfig", "getProducts", "getUnfinishedTransactions", "purchase", "purchaseForVerification", "restore"],
34
35
  "keyboard": ["dismiss"],
@@ -29,6 +29,7 @@ var CAPABILITY_REGISTRY = {
29
29
  "files": ["list", "readText", "remove", "stat", "write"],
30
30
  "godot": ["loadPack", "release", "send"],
31
31
  "haptic": ["play"],
32
+ "health": ["availability", "getPermissionStatus", "query", "requestPermission", "saveWorkout"],
32
33
  "himi_apps": ["canOpen", "current", "hostInfo", "open"],
33
34
  "iap": ["consume", "entitlements", "finishTransaction", "getPaywallConfig", "getProducts", "getUnfinishedTransactions", "purchase", "purchaseForVerification", "restore"],
34
35
  "keyboard": ["dismiss"],
@@ -0,0 +1,127 @@
1
+ // ../../core/sdk/dispatch/src/index.ts
2
+ function dispatch(handlerId, params, _options) {
3
+ const himalaya = globalThis.himalaya;
4
+ if (!himalaya) {
5
+ return Promise.reject(
6
+ new Error("@himalaya/dispatch: called outside a Himalaya runtime. This bundle must run inside a JSWorker (Tier 5).")
7
+ );
8
+ }
9
+ const stringParams = {};
10
+ if (params) {
11
+ for (const [k, v] of Object.entries(params)) stringParams[k] = String(v);
12
+ }
13
+ return himalaya.dispatch(handlerId, stringParams);
14
+ }
15
+
16
+ // ../../core/sdk/health/src/index.ts
17
+ var HEALTH_TYPES = ["steps", "heartRate", "activeEnergy", "distance", "workouts"];
18
+ var WORKOUT_ACTIVITIES = ["running", "walking", "cycling", "strength", "other"];
19
+ var HEALTH_UNITS = {
20
+ steps: "count",
21
+ heartRate: "bpm",
22
+ activeEnergy: "kcal",
23
+ distance: "m",
24
+ workouts: "s"
25
+ };
26
+ function healthError(code, message) {
27
+ const e = new Error(message);
28
+ e.name = "HealthError";
29
+ e.code = code;
30
+ return e;
31
+ }
32
+ function isHealthError(e) {
33
+ return e instanceof Error && e.name === "HealthError" && typeof e.code === "string";
34
+ }
35
+ var ERROR_CODES = ["unavailable", "not_authorized", "invalid_argument", "failed"];
36
+ function unwrap(r) {
37
+ const err = r?.error;
38
+ if (err) {
39
+ const code = ERROR_CODES.includes(err.code ?? "") ? err.code : "failed";
40
+ throw healthError(code, err.message ?? code);
41
+ }
42
+ return r ?? {};
43
+ }
44
+ function assertTypes(types, allowed, what) {
45
+ for (const t of types) {
46
+ if (!allowed.includes(t)) throw healthError("invalid_argument", `health: unknown ${what} "${t}"`);
47
+ }
48
+ }
49
+ function assertRange(from, to) {
50
+ const a = Date.parse(from), b = Date.parse(to);
51
+ if (Number.isNaN(a) || Number.isNaN(b)) throw healthError("invalid_argument", "health: from/to must be ISO-8601");
52
+ if (b <= a) throw healthError("invalid_argument", "health: `to` must be after `from`");
53
+ }
54
+ async function availability() {
55
+ const r = await dispatch("capability.health.availability");
56
+ const out = { available: r?.available === true };
57
+ if (typeof r?.reason === "string" && r.reason) out.reason = r.reason;
58
+ return out;
59
+ }
60
+ async function requestPermission(p) {
61
+ const read = p.read ?? [];
62
+ const write = p.write ?? [];
63
+ assertTypes(read, HEALTH_TYPES, "read type");
64
+ assertTypes(write, ["workouts"], "write type");
65
+ const r = unwrap(
66
+ await dispatch("capability.health.requestPermission", { read: read.join(","), write: write.join(",") })
67
+ );
68
+ const granted = Array.isArray(r.granted) ? r.granted : [];
69
+ return { granted: granted.filter((t) => HEALTH_TYPES.includes(t)) };
70
+ }
71
+ async function getPermissionStatus(p) {
72
+ const types = p.types ?? [];
73
+ assertTypes(types, HEALTH_TYPES, "type");
74
+ const r = unwrap(
75
+ await dispatch("capability.health.getPermissionStatus", { types: types.join(",") })
76
+ );
77
+ const out = {};
78
+ for (const t of types) {
79
+ const s = r.statuses?.[t];
80
+ out[t] = s === "granted" || s === "denied" ? s : "notDetermined";
81
+ }
82
+ return out;
83
+ }
84
+ async function query(p) {
85
+ assertTypes([p.type], HEALTH_TYPES, "type");
86
+ assertRange(p.from, p.to);
87
+ if (p.bucket !== void 0 && p.bucket !== "hour" && p.bucket !== "day") {
88
+ throw healthError("invalid_argument", `health: unknown bucket "${String(p.bucket)}"`);
89
+ }
90
+ const params = { type: p.type, from: p.from, to: p.to };
91
+ if (p.bucket) params.bucket = p.bucket;
92
+ const r = unwrap(await dispatch("capability.health.query", params));
93
+ return { samples: Array.isArray(r.samples) ? r.samples : [] };
94
+ }
95
+ async function saveWorkout(w) {
96
+ assertTypes([w.activity], WORKOUT_ACTIVITIES, "activity");
97
+ assertRange(w.start, w.end);
98
+ const params = { activity: w.activity, start: w.start, end: w.end };
99
+ if (typeof w.energyKcal === "number") {
100
+ if (!Number.isFinite(w.energyKcal) || w.energyKcal < 0) throw healthError("invalid_argument", "health: energyKcal must be a finite number >= 0");
101
+ params.energyKcal = w.energyKcal;
102
+ }
103
+ if (typeof w.distanceM === "number") {
104
+ if (!Number.isFinite(w.distanceM) || w.distanceM < 0) throw healthError("invalid_argument", "health: distanceM must be a finite number >= 0");
105
+ params.distanceM = w.distanceM;
106
+ }
107
+ const r = unwrap(await dispatch("capability.health.saveWorkout", params));
108
+ if (!r.id) throw healthError("failed", "health.saveWorkout: native returned no id");
109
+ return { id: r.id };
110
+ }
111
+ var version = 1;
112
+ var health = { availability, requestPermission, getPermissionStatus, query, saveWorkout, version };
113
+ export {
114
+ HEALTH_TYPES,
115
+ HEALTH_UNITS,
116
+ WORKOUT_ACTIVITIES,
117
+ availability,
118
+ getPermissionStatus,
119
+ health,
120
+ healthError,
121
+ isHealthError,
122
+ query,
123
+ requestPermission,
124
+ saveWorkout,
125
+ version
126
+ };
127
+ //# sourceMappingURL=health.mjs.map
@@ -29,6 +29,7 @@ var CAPABILITY_REGISTRY = {
29
29
  "files": ["list", "readText", "remove", "stat", "write"],
30
30
  "godot": ["loadPack", "release", "send"],
31
31
  "haptic": ["play"],
32
+ "health": ["availability", "getPermissionStatus", "query", "requestPermission", "saveWorkout"],
32
33
  "himi_apps": ["canOpen", "current", "hostInfo", "open"],
33
34
  "iap": ["consume", "entitlements", "finishTransaction", "getPaywallConfig", "getProducts", "getUnfinishedTransactions", "purchase", "purchaseForVerification", "restore"],
34
35
  "keyboard": ["dismiss"],
@@ -29,6 +29,7 @@ var CAPABILITY_REGISTRY = {
29
29
  "files": ["list", "readText", "remove", "stat", "write"],
30
30
  "godot": ["loadPack", "release", "send"],
31
31
  "haptic": ["play"],
32
+ "health": ["availability", "getPermissionStatus", "query", "requestPermission", "saveWorkout"],
32
33
  "himi_apps": ["canOpen", "current", "hostInfo", "open"],
33
34
  "iap": ["consume", "entitlements", "finishTransaction", "getPaywallConfig", "getProducts", "getUnfinishedTransactions", "purchase", "purchaseForVerification", "restore"],
34
35
  "keyboard": ["dismiss"],
@@ -29,6 +29,7 @@ var CAPABILITY_REGISTRY = {
29
29
  "files": ["list", "readText", "remove", "stat", "write"],
30
30
  "godot": ["loadPack", "release", "send"],
31
31
  "haptic": ["play"],
32
+ "health": ["availability", "getPermissionStatus", "query", "requestPermission", "saveWorkout"],
32
33
  "himi_apps": ["canOpen", "current", "hostInfo", "open"],
33
34
  "iap": ["consume", "entitlements", "finishTransaction", "getPaywallConfig", "getProducts", "getUnfinishedTransactions", "purchase", "purchaseForVerification", "restore"],
34
35
  "keyboard": ["dismiss"],
@@ -14,6 +14,7 @@ var CAPABILITY_REGISTRY = {
14
14
  "files": ["list", "readText", "remove", "stat", "write"],
15
15
  "godot": ["loadPack", "release", "send"],
16
16
  "haptic": ["play"],
17
+ "health": ["availability", "getPermissionStatus", "query", "requestPermission", "saveWorkout"],
17
18
  "himi_apps": ["canOpen", "current", "hostInfo", "open"],
18
19
  "iap": ["consume", "entitlements", "finishTransaction", "getPaywallConfig", "getProducts", "getUnfinishedTransactions", "purchase", "purchaseForVerification", "restore"],
19
20
  "keyboard": ["dismiss"],
@@ -29,6 +29,7 @@ var CAPABILITY_REGISTRY = {
29
29
  "files": ["list", "readText", "remove", "stat", "write"],
30
30
  "godot": ["loadPack", "release", "send"],
31
31
  "haptic": ["play"],
32
+ "health": ["availability", "getPermissionStatus", "query", "requestPermission", "saveWorkout"],
32
33
  "himi_apps": ["canOpen", "current", "hostInfo", "open"],
33
34
  "iap": ["consume", "entitlements", "finishTransaction", "getPaywallConfig", "getProducts", "getUnfinishedTransactions", "purchase", "purchaseForVerification", "restore"],
34
35
  "keyboard": ["dismiss"],
@@ -114,6 +114,10 @@
114
114
  "types": "./dist/_types/core/sdk/haptic/src/index.d.ts",
115
115
  "import": "./dist/haptic.mjs"
116
116
  },
117
+ "./health": {
118
+ "types": "./dist/_types/core/sdk/health/src/index.d.ts",
119
+ "import": "./dist/health.mjs"
120
+ },
117
121
  "./himi-apps": {
118
122
  "types": "./dist/_types/core/sdk/himi-apps/src/index.d.ts",
119
123
  "import": "./dist/himi-apps.mjs"
package/dist/cli.mjs CHANGED
@@ -5002,6 +5002,7 @@ var init_build_lib = __esm({
5002
5002
  // module comments — so the committed artifact differed per machine (a clone whose
5003
5003
  // node_modules points elsewhere emitted /Users/<name>/... into a tracked file).
5004
5004
  "@himalaya/motion": resolve8(REPO_ROOT4, "core/sdk/motion/src/index.ts"),
5005
+ "@himalaya/health": resolve8(REPO_ROOT4, "core/sdk/health/src/index.ts"),
5005
5006
  // Pure TypeScript (GPX parse/serialize + track stats) — no dispatch, so no capability
5006
5007
  // grant, same class as @himalaya/state.
5007
5008
  "@himalaya/gpx": resolve8(REPO_ROOT4, "core/sdk/gpx/src/index.ts"),
@@ -6877,6 +6878,7 @@ function resolveTranslation(value, catalog, sourceCatalog) {
6877
6878
  var BINDING_SIGILS, BINDING_SIGIL_RE, TRANSLATION_PREFIX, TRANSLATION_KEY_RE;
6878
6879
  var init_bindings = __esm({
6879
6880
  "../../core/ts/src/bindings.ts"() {
6881
+ "use strict";
6880
6882
  BINDING_SIGILS = ["service", "flag", "item", "t"];
6881
6883
  BINDING_SIGIL_RE = new RegExp(
6882
6884
  `^\\$(?:${BINDING_SIGILS.filter((s2) => s2 !== "t").join("|")})\\b|^\\$t:`
@@ -17164,6 +17166,86 @@ var init_native_requirements = __esm({
17164
17166
  ]
17165
17167
  }
17166
17168
  },
17169
+ health: {
17170
+ // Apple Health (HealthKit) / Health Connect (@himalaya/health): read steps, heart rate,
17171
+ // active energy, distance and workouts; write a completed workout back. A different
17172
+ // privacy class from `motion` (the platform HEALTH store, not raw sensors), so its own row.
17173
+ ios: {
17174
+ // HealthKit throws (and App Review rejects) when either key is missing for the
17175
+ // direction the app uses. Both are declared because the surface is read + write.
17176
+ usageStrings: [
17177
+ { key: "NSHealthShareUsageDescription", suggested: "Read your steps, heart rate, energy and workouts from Apple Health to show your activity." },
17178
+ { key: "NSHealthUpdateUsageDescription", suggested: "Save the workouts you complete to Apple Health." }
17179
+ ],
17180
+ // The App ID must have the HealthKit capability enabled for a DEVICE / TestFlight
17181
+ // build to sign with this — see docs/capability-native-config.md. Simulator builds
17182
+ // do not need the App ID step.
17183
+ entitlements: { "com.apple.developer.healthkit": true }
17184
+ },
17185
+ // macOS has no Health store (HKHealthStore.isHealthDataAvailable() is false on Mac), so
17186
+ // the capability answers `available: false` there and contributes no config.
17187
+ android: {
17188
+ // Health Connect permissions are runtime-granted through Health Connect's own dialog,
17189
+ // but the dialog only offers a permission the manifest declares.
17190
+ permissions: [
17191
+ "android.permission.health.READ_ACTIVE_CALORIES_BURNED",
17192
+ "android.permission.health.READ_DISTANCE",
17193
+ "android.permission.health.READ_EXERCISE",
17194
+ "android.permission.health.READ_HEART_RATE",
17195
+ "android.permission.health.READ_STEPS",
17196
+ // A saved workout carries its energy + distance as their OWN records alongside the
17197
+ // ExerciseSessionRecord, so writing them needs their write permissions too.
17198
+ "android.permission.health.WRITE_ACTIVE_CALORIES_BURNED",
17199
+ "android.permission.health.WRITE_DISTANCE",
17200
+ "android.permission.health.WRITE_EXERCISE"
17201
+ ],
17202
+ // Health Connect will not show its permission dialog to an app with no privacy-
17203
+ // rationale screen. Android ≤13 finds it by the ACTION_SHOW_PERMISSIONS_RATIONALE
17204
+ // intent-filter; Android 14+ by a VIEW_PERMISSION_USAGE alias that only the system's
17205
+ // permission controller may start (START_VIEW_PERMISSION_USAGE).
17206
+ activities: [
17207
+ {
17208
+ name: "io.himalaya.health.HealthPermissionsRationaleActivity",
17209
+ exported: true,
17210
+ intentFilters: [{ actions: ["androidx.health.ACTION_SHOW_PERMISSIONS_RATIONALE"] }]
17211
+ },
17212
+ {
17213
+ name: "io.himalaya.health.HealthViewPermissionUsageActivity",
17214
+ targetActivity: "io.himalaya.health.HealthPermissionsRationaleActivity",
17215
+ exported: true,
17216
+ permission: "android.permission.START_VIEW_PERMISSION_USAGE",
17217
+ intentFilters: [
17218
+ {
17219
+ actions: ["android.intent.action.VIEW_PERMISSION_USAGE"],
17220
+ categories: ["android.intent.category.HEALTH_PERMISSIONS"]
17221
+ }
17222
+ ]
17223
+ }
17224
+ ],
17225
+ // On Android ≤13 Health Connect is a separate APK; without package visibility the
17226
+ // client reports it as not installed even when it is.
17227
+ queries: ["com.google.android.apps.healthdata"]
17228
+ },
17229
+ privacy: {
17230
+ // Health = heart rate; Fitness = steps / energy / distance / workouts. NOT linked and
17231
+ // NOT tracking: read on device for the app's own display. An app that syncs these to
17232
+ // a server must mark them linked in its native-config overrides.
17233
+ collectedDataTypes: [
17234
+ { type: "NSPrivacyCollectedDataTypeHealth", linked: false, tracking: false, purposes: [APP_FUNCTIONALITY] },
17235
+ { type: "NSPrivacyCollectedDataTypeFitness", linked: false, tracking: false, purposes: [APP_FUNCTIONALITY] }
17236
+ ],
17237
+ dataSafety: [
17238
+ { category: "Health and fitness", dataType: "Health info", purpose: "App functionality" },
17239
+ { category: "Health and fitness", dataType: "Fitness info", purpose: "App functionality" }
17240
+ ],
17241
+ reviewNotes: [
17242
+ "health: HealthKit (iOS) / Health Connect (Android). Apple Guideline 5.1.3 and Google Play's Health Connect policy both forbid using health data for advertising or selling it, and both require a privacy policy that names the health data read.",
17243
+ "Google Play requires a Health Connect declaration form in the Play Console for every android.permission.health.* permission requested; an app that ships these permissions without it is rejected.",
17244
+ "iOS hides READ denials: HealthKit never reports whether read access was refused, so `getPermissionStatus` answers `notDetermined` for reads and a denied read simply returns no samples.",
17245
+ "The HealthKit entitlement requires the HealthKit capability on the App ID for device and TestFlight builds."
17246
+ ]
17247
+ }
17248
+ },
17167
17249
  media_playback: {
17168
17250
  ios: {
17169
17251
  // Background audio for SDUI MediaPlayer sessions that opt into background playback.
@@ -17402,17 +17484,27 @@ function androidPlanFor(ids, ov = {}) {
17402
17484
  const perms = /* @__PURE__ */ new Set();
17403
17485
  const feats = /* @__PURE__ */ new Map();
17404
17486
  const svcs = /* @__PURE__ */ new Map();
17487
+ const acts = /* @__PURE__ */ new Map();
17488
+ const queries = /* @__PURE__ */ new Set();
17405
17489
  const add = (req) => {
17406
17490
  for (const p2 of req?.permissions ?? []) perms.add(p2);
17407
17491
  for (const f2 of req?.features ?? []) feats.set(f2.name, (feats.get(f2.name) ?? false) || f2.required);
17408
17492
  for (const s2 of req?.services ?? []) svcs.set(s2.name, s2);
17493
+ for (const a2 of req?.activities ?? []) acts.set(a2.name, a2);
17494
+ for (const q2 of req?.queries ?? []) queries.add(q2);
17409
17495
  };
17410
17496
  for (const id of ids) add(nativeRequirementsFor(id)?.android);
17411
17497
  add(ov);
17412
17498
  return {
17413
17499
  permissions: [...perms].sort(),
17414
17500
  features: [...feats.entries()].sort((a2, b2) => a2[0].localeCompare(b2[0])).map(([name, required3]) => ({ name, required: required3 })),
17415
- services: [...svcs.values()].sort((a2, b2) => a2.name.localeCompare(b2.name))
17501
+ services: [...svcs.values()].sort((a2, b2) => a2.name.localeCompare(b2.name)),
17502
+ // An <activity-alias> must come AFTER the activity it targets, or the manifest merger
17503
+ // rejects it — so aliases sort after plain activities, then by name.
17504
+ activities: [...acts.values()].sort(
17505
+ (a2, b2) => Number(!!a2.targetActivity) - Number(!!b2.targetActivity) || a2.name.localeCompare(b2.name)
17506
+ ),
17507
+ queries: [...queries].sort()
17416
17508
  };
17417
17509
  }
17418
17510
  var init_plan = __esm({
@@ -17447,6 +17539,9 @@ var init_builtins = __esm({
17447
17539
  "files",
17448
17540
  "godot",
17449
17541
  "haptic",
17542
+ // Registered on every arm. Web (and the macOS build of the iOS arm) answer
17543
+ // `availability → { available: false }` and refuse the rest with `unavailable`.
17544
+ "health",
17450
17545
  "himi_apps",
17451
17546
  "iap",
17452
17547
  "keyboard",
@@ -17560,6 +17655,7 @@ var init_known = __esm({
17560
17655
  local_database: "registering it without the app's dataSchema writes declared-encrypted entities in plaintext (#1896) \u2014 the shell has to install the schema first",
17561
17656
  live_activity: "needs a WidgetKit extension target in the shell's project, which a build cannot add",
17562
17657
  base44: "needs the opt-in HimalayaCoreBase44 / :core-runtime-base44 SDK linked into the shell",
17658
+ health: "needs the opt-in HimalayaCoreHealth / :core-runtime-health store linked into the shell (unlinked, every verb answers `unavailable`)",
17563
17659
  godot: "needs the opt-in Godot SPM product linked into the shell",
17564
17660
  wix_platform: "needs the shell's boot to configure it with a Wix API client and launch services",
17565
17661
  himi_apps: "is a multi-app host surface (Himi Player), not something one app's binary serves",
@@ -18355,12 +18451,34 @@ function renderAndroidXml(plan, indent) {
18355
18451
  const lines = [];
18356
18452
  for (const p2 of plan.permissions) lines.push(`${indent}<uses-permission android:name="${p2}" />`);
18357
18453
  for (const f2 of plan.features) lines.push(`${indent}<uses-feature android:name="${f2.name}" android:required="${f2.required}" />`);
18454
+ const queries = plan.queries ?? [];
18455
+ if (queries.length > 0) {
18456
+ lines.push(`${indent}<queries>`);
18457
+ for (const q2 of queries) lines.push(`${indent} <package android:name="${q2}" />`);
18458
+ lines.push(`${indent}</queries>`);
18459
+ }
18358
18460
  return lines;
18359
18461
  }
18360
18462
  function renderAndroidServicesXml(plan, indent) {
18361
- return plan.services.map(
18463
+ const lines = plan.services.map(
18362
18464
  (s2) => `${indent}<service android:name="${s2.name}" android:exported="false" android:foregroundServiceType="${s2.foregroundServiceType}" />`
18363
18465
  );
18466
+ for (const a2 of plan.activities ?? []) {
18467
+ const tag = a2.targetActivity ? "activity-alias" : "activity";
18468
+ const attrs = [`android:name="${a2.name}"`];
18469
+ if (a2.targetActivity) attrs.push(`android:targetActivity="${a2.targetActivity}"`);
18470
+ attrs.push(`android:exported="${a2.exported}"`);
18471
+ if (a2.permission) attrs.push(`android:permission="${a2.permission}"`);
18472
+ lines.push(`${indent}<${tag} ${attrs.join(" ")}>`);
18473
+ for (const f2 of a2.intentFilters) {
18474
+ lines.push(`${indent} <intent-filter>`);
18475
+ for (const act of f2.actions) lines.push(`${indent} <action android:name="${act}" />`);
18476
+ for (const c2 of f2.categories ?? []) lines.push(`${indent} <category android:name="${c2}" />`);
18477
+ lines.push(`${indent} </intent-filter>`);
18478
+ }
18479
+ lines.push(`${indent}</${tag}>`);
18480
+ }
18481
+ return lines;
18364
18482
  }
18365
18483
  function isBegin(line, kind, label2) {
18366
18484
  const s2 = line.trim();
@@ -22503,6 +22621,7 @@ var init_capabilities_generated = __esm({
22503
22621
  "files": ["list", "readText", "remove", "stat", "write"],
22504
22622
  "godot": ["loadPack", "release", "send"],
22505
22623
  "haptic": ["play"],
22624
+ "health": ["availability", "getPermissionStatus", "query", "requestPermission", "saveWorkout"],
22506
22625
  "himi_apps": ["canOpen", "current", "hostInfo", "open"],
22507
22626
  "iap": ["consume", "entitlements", "finishTransaction", "getPaywallConfig", "getProducts", "getUnfinishedTransactions", "purchase", "purchaseForVerification", "restore"],
22508
22627
  "keyboard": ["dismiss"],
@@ -22545,6 +22664,7 @@ var init_capabilities_generated = __esm({
22545
22664
  "files": ["@himalaya/files"],
22546
22665
  "godot": ["@himalaya/godot"],
22547
22666
  "haptic": ["@himalaya/haptic"],
22667
+ "health": ["@himalaya/health"],
22548
22668
  "himi_apps": ["@himalaya/himi-apps"],
22549
22669
  "iap": ["@himalaya/iap"],
22550
22670
  "keyboard": ["@himalaya/keyboard"],
@@ -22604,8 +22724,8 @@ var init_shell_capabilities_generated = __esm({
22604
22724
  appDir: "apps/demo-app",
22605
22725
  shared: false,
22606
22726
  extensible: false,
22607
- built: ["base44", "biometrics", "calendar", "calls", "contacts", "haptic", "keyboard", "live_activity", "location", "media_playback", "on_device_ai", "photos", "push", "speech", "tasks", "ui"],
22608
- serves: ["base44", "biometrics", "calendar", "calls", "contacts", "haptic", "keyboard", "live_activity", "location", "media_playback", "on_device_ai", "photos", "push", "speech", "tasks", "ui"]
22727
+ built: ["base44", "biometrics", "calendar", "calls", "contacts", "haptic", "health", "keyboard", "live_activity", "location", "media_playback", "on_device_ai", "photos", "push", "speech", "tasks", "ui"],
22728
+ serves: ["base44", "biometrics", "calendar", "calls", "contacts", "haptic", "health", "keyboard", "live_activity", "location", "media_playback", "on_device_ai", "photos", "push", "speech", "tasks", "ui"]
22609
22729
  },
22610
22730
  "owner-lite": {
22611
22731
  appDir: "apps/himi-owner-lite",
@@ -143328,23 +143448,23 @@ var init_preview_assets_generated = __esm({
143328
143448
  "../../packages/serve/src/preview-assets.generated.ts"() {
143329
143449
  init_preview_assets_format();
143330
143450
  PREVIEW_ASSET_MANIFEST = [
143331
- { key: "assets/index-DiC0HNa3.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1187655, sha256: "08fb74d8752e2f7ae5dcffd520eb03d2eb3def8db9d8fa4b9d5de1a7ae293100" },
143332
- { key: "assets/pdf.worker.min-BmVo14Nb.mjs", contentType: "application/octet-stream", offset: 1187663, length: 1317034, sha256: "a33cfe728c584fdba4fcc1fd54bcdc2f9f2f13889ddbb5b2bd1d0f8cbe49b84e" },
143333
- { key: "assets/pdfDocument-CXSifjpc.js", contentType: "application/javascript; charset=utf-8", offset: 2504697, length: 489166, sha256: "7dd04afebfbeeede2222a8beb5d87e58391ed36493b57a190a3e2eed4da35356" },
143334
- { key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 2993863, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
143335
- { key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 2998473, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
143336
- { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 3487057, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
143337
- { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 3577981, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
143338
- { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 3668965, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
143339
- { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 3759945, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
143340
- { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 3850817, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
143341
- { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 3941797, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
143342
- { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 4050433, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
143343
- { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 4159517, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
143344
- { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 4268565, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
143345
- { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 4376265, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
143346
- { key: "index.html", contentType: "text/html; charset=utf-8", offset: 4464373, length: 4947, sha256: "e46144d08f1a73203c2313ca0ed8b607fd383f4889a82701d6c37f00e4720c95" },
143347
- { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 4469320, length: 17344, sha256: "d057298d75e7373e5e2c4d11b7d1bcbea88e03af555ba63d97641c78509c58e4" }
143451
+ { key: "assets/index-DcqakqyW.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1191096, sha256: "83e1d51efc75dc63a19cd5bd9072d50e8b831d6c39152922cdc491225f43d696" },
143452
+ { key: "assets/pdf.worker.min-BmVo14Nb.mjs", contentType: "application/octet-stream", offset: 1191104, length: 1317034, sha256: "a33cfe728c584fdba4fcc1fd54bcdc2f9f2f13889ddbb5b2bd1d0f8cbe49b84e" },
143453
+ { key: "assets/pdfDocument-CITrNgUQ.js", contentType: "application/javascript; charset=utf-8", offset: 2508138, length: 489166, sha256: "202328a260c149a8bdf211a58a04cb74114e40510a4c0dd0511045b3a6279b47" },
143454
+ { key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 2997304, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
143455
+ { key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 3001914, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
143456
+ { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 3490498, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
143457
+ { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 3581422, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
143458
+ { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 3672406, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
143459
+ { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 3763386, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
143460
+ { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 3854258, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
143461
+ { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 3945238, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
143462
+ { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 4053874, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
143463
+ { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 4162958, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
143464
+ { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 4272006, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
143465
+ { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 4379706, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
143466
+ { key: "index.html", contentType: "text/html; charset=utf-8", offset: 4467814, length: 4947, sha256: "2189ce1438ed15f3a041d5e9950c92b27a18d288103ac04249b184c369137721" },
143467
+ { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 4472761, length: 17344, sha256: "d057298d75e7373e5e2c4d11b7d1bcbea88e03af555ba63d97641c78509c58e4" }
143348
143468
  ];
143349
143469
  }
143350
143470
  });
Binary file
@@ -115,7 +115,7 @@ with a native footprint only arrives with a new binary. `himi docs capabilities`
115
115
  loop — discover, declare, validate, ship — and `himi capabilities <id>` shows the import and which
116
116
  shells' binaries serve it.
117
117
 
118
- App-level capability vocabulary: `camera`, `barcode_scanner`, `biometrics`, `secure_storage`, `share`, `push`, `file_picker`, `files`, `open_url`, `pdf`, `clipboard`, `photos`, `presigned_upload`, `ui`, `calendar`, `contacts`, `tasks`, `audio_record`, `audio_record_background`, `calls`, `location`, `location_background`, `motion`, `media_playback`, `webview`, `browser_auth`, `haptic`, `himi_apps`, `iap`, `keyboard`, `wix_cashier`, `live_activity`, `local_database`, `speech`, `speaker_diarization`, `system_intents`, `on_device_ai`, `app_info`, `release_control`, `godot`, `base44`.
118
+ App-level capability vocabulary: `camera`, `barcode_scanner`, `biometrics`, `secure_storage`, `share`, `push`, `file_picker`, `files`, `open_url`, `pdf`, `clipboard`, `photos`, `presigned_upload`, `ui`, `calendar`, `contacts`, `tasks`, `audio_record`, `audio_record_background`, `calls`, `location`, `location_background`, `motion`, `health`, `media_playback`, `webview`, `browser_auth`, `haptic`, `himi_apps`, `iap`, `keyboard`, `wix_cashier`, `live_activity`, `local_database`, `speech`, `speaker_diarization`, `system_intents`, `on_device_ai`, `app_info`, `release_control`, `godot`, `base44`.
119
119
 
120
120
  ## What `himi validate` checks (7 passes)
121
121
 
@@ -31,6 +31,7 @@ package in one greppable file) or `GET /v1/sdk/<name>` over HTTP; in the CLI,
31
31
  - `@himalaya/godot` — Tier-5 typed binding for a Godot game surface.
32
32
  - `@himalaya/gpx` — read and write GPX 1.1, and compute track statistics.
33
33
  - `@himalaya/haptic` — Tier-5 typed binding for the platform's haptic capability.
34
+ - `@himalaya/health` — Tier-5 typed binding for the platform HEALTH store.
34
35
  - `@himalaya/himi-apps` — Tier-5 typed binding for cross-app open / host queries.
35
36
  - `@himalaya/iap` — Tier-5 typed binding to the platform's in-app-purchase capability.
36
37
  - `@himalaya/keyboard` — Tier-5 typed binding for the platform's keyboard capability.
@@ -1368,6 +1368,151 @@
1368
1368
  }
1369
1369
  ]
1370
1370
  },
1371
+ {
1372
+ "name": "@himalaya/health",
1373
+ "source": "core/sdk/health/src/index.ts",
1374
+ "description": "Tier-5 typed binding for the platform HEALTH store.",
1375
+ "api": [
1376
+ {
1377
+ "name": "HealthType",
1378
+ "kind": "type",
1379
+ "jsdoc": "/** A readable health data type. */",
1380
+ "decl": "export type HealthType = \"steps\" | \"heartRate\" | \"activeEnergy\" | \"distance\" | \"workouts\";"
1381
+ },
1382
+ {
1383
+ "name": "HealthWriteType",
1384
+ "kind": "type",
1385
+ "jsdoc": "/** A writable health data type. v1 writes workouts only. */",
1386
+ "decl": "export type HealthWriteType = \"workouts\";"
1387
+ },
1388
+ {
1389
+ "name": "HealthPermissionState",
1390
+ "kind": "type",
1391
+ "jsdoc": "",
1392
+ "decl": "export type HealthPermissionState = \"granted\" | \"denied\" | \"notDetermined\";"
1393
+ },
1394
+ {
1395
+ "name": "HealthBucket",
1396
+ "kind": "type",
1397
+ "jsdoc": "/** Aggregation bucket for `query`. Omitted ⇒ one total (cumulative types) or raw samples. */",
1398
+ "decl": "export type HealthBucket = \"hour\" | \"day\";"
1399
+ },
1400
+ {
1401
+ "name": "WorkoutActivity",
1402
+ "kind": "type",
1403
+ "jsdoc": "",
1404
+ "decl": "export type WorkoutActivity = \"running\" | \"walking\" | \"cycling\" | \"strength\" | \"other\";"
1405
+ },
1406
+ {
1407
+ "name": "HealthErrorCode",
1408
+ "kind": "type",
1409
+ "jsdoc": "",
1410
+ "decl": "export type HealthErrorCode = \"unavailable\" | \"not_authorized\" | \"invalid_argument\" | \"failed\";"
1411
+ },
1412
+ {
1413
+ "name": "HEALTH_TYPES",
1414
+ "kind": "const",
1415
+ "jsdoc": "",
1416
+ "decl": "export const HEALTH_TYPES: readonly HealthType[] = [\"steps\", \"heartRate\", \"activeEnergy\", \"distance\", \"workouts\"];"
1417
+ },
1418
+ {
1419
+ "name": "WORKOUT_ACTIVITIES",
1420
+ "kind": "const",
1421
+ "jsdoc": "",
1422
+ "decl": "export const WORKOUT_ACTIVITIES: readonly WorkoutActivity[] = [\"running\", \"walking\", \"cycling\", \"strength\", \"other\"];"
1423
+ },
1424
+ {
1425
+ "name": "HEALTH_UNITS",
1426
+ "kind": "const",
1427
+ "jsdoc": "/** The fixed unit each type's `value` is reported in. */",
1428
+ "decl": "export const HEALTH_UNITS: Readonly<Record<HealthType, string>> = {\n steps: \"count\",\n heartRate: \"bpm\",\n activeEnergy: \"kcal\",\n distance: \"m\",\n workouts: \"s\",\n};"
1429
+ },
1430
+ {
1431
+ "name": "HealthAvailability",
1432
+ "kind": "interface",
1433
+ "jsdoc": "",
1434
+ "decl": "export interface HealthAvailability {\n available: boolean;\n /** Why not, when `available` is false — e.g. \"web has no health store\",\n * \"Health Connect is not installed\". */\n reason?: string;\n}"
1435
+ },
1436
+ {
1437
+ "name": "HealthSample",
1438
+ "kind": "interface",
1439
+ "jsdoc": "",
1440
+ "decl": "export interface HealthSample {\n /** ISO-8601. */\n start: string;\n /** ISO-8601. */\n end: string;\n value: number;\n unit: string;\n /** Workouts only: the activity, mapped back onto the v1 set (\"other\" for anything else). */\n activity?: WorkoutActivity;\n}"
1441
+ },
1442
+ {
1443
+ "name": "HealthQuery",
1444
+ "kind": "interface",
1445
+ "jsdoc": "",
1446
+ "decl": "export interface HealthQuery {\n type: HealthType;\n /** ISO-8601, inclusive. */\n from: string;\n /** ISO-8601, exclusive. */\n to: string;\n /** steps / activeEnergy / distance are SUMMED per bucket; heartRate is AVERAGED per bucket;\n * workouts ignore it (one sample per workout). */\n bucket?: HealthBucket;\n}"
1447
+ },
1448
+ {
1449
+ "name": "Workout",
1450
+ "kind": "interface",
1451
+ "jsdoc": "",
1452
+ "decl": "export interface Workout {\n activity: WorkoutActivity;\n /** ISO-8601. */\n start: string;\n /** ISO-8601, after `start`. */\n end: string;\n energyKcal?: number;\n distanceM?: number;\n}"
1453
+ },
1454
+ {
1455
+ "name": "HealthError",
1456
+ "kind": "interface",
1457
+ "jsdoc": "/** The typed rejection every verb throws. A plain Error carrying `code` — worker bundles are\n * compiled to a target without class syntax, so this is a factory, not a subclass. */",
1458
+ "decl": "export interface HealthError extends Error {\n name: \"HealthError\";\n code: HealthErrorCode;\n}"
1459
+ },
1460
+ {
1461
+ "name": "healthError",
1462
+ "kind": "function",
1463
+ "jsdoc": "/** Build a [HealthError] — what the binding throws for every native `{ error }` reply. */",
1464
+ "decl": "export function healthError(code: HealthErrorCode, message: string): HealthError;"
1465
+ },
1466
+ {
1467
+ "name": "isHealthError",
1468
+ "kind": "function",
1469
+ "jsdoc": "/** Narrow a caught value: `catch (e) { if (isHealthError(e) && e.code === \"unavailable\") … }`. */",
1470
+ "decl": "export function isHealthError(e: unknown): e is HealthError;"
1471
+ },
1472
+ {
1473
+ "name": "availability",
1474
+ "kind": "function",
1475
+ "jsdoc": "/** Whether this device has a health store the app can reach. Call it first and branch. */",
1476
+ "decl": "export async function availability(): Promise<HealthAvailability>;"
1477
+ },
1478
+ {
1479
+ "name": "requestPermission",
1480
+ "kind": "function",
1481
+ "jsdoc": "/**\n * Prompt for read (and optionally write) access. Resolves with the types the platform\n * reports as granted — on iOS a read type counts as granted once asked (see header).\n * Writing workouts also covers the energy + distance samples a saved workout carries.\n */",
1482
+ "decl": "export async function requestPermission(p: { read: HealthType[]; write?: HealthWriteType[] }): Promise<{ granted: HealthType[] }>;"
1483
+ },
1484
+ {
1485
+ "name": "getPermissionStatus",
1486
+ "kind": "function",
1487
+ "jsdoc": "/** Current READ permission per type, without prompting. iOS reads are always \"notDetermined\". */",
1488
+ "decl": "export async function getPermissionStatus(p: { types: HealthType[] }): Promise<Record<HealthType, HealthPermissionState>>;"
1489
+ },
1490
+ {
1491
+ "name": "query",
1492
+ "kind": "function",
1493
+ "jsdoc": "/** Read samples of one type over [from, to). */",
1494
+ "decl": "export async function query(p: HealthQuery): Promise<{ samples: HealthSample[] }>;"
1495
+ },
1496
+ {
1497
+ "name": "saveWorkout",
1498
+ "kind": "function",
1499
+ "jsdoc": "/** Save a completed workout (with its energy/distance, when given). Resolves with its store id. */",
1500
+ "decl": "export async function saveWorkout(w: Workout): Promise<{ id: string }>;"
1501
+ },
1502
+ {
1503
+ "name": "version",
1504
+ "kind": "const",
1505
+ "jsdoc": "",
1506
+ "decl": "export const version = 1;"
1507
+ },
1508
+ {
1509
+ "name": "health",
1510
+ "kind": "const",
1511
+ "jsdoc": "/** The whole surface as one namespace: `import { health } from \"@himalaya/health\"`. */",
1512
+ "decl": "export const health = { availability, requestPermission, getPermissionStatus, query, saveWorkout, version };"
1513
+ }
1514
+ ]
1515
+ },
1371
1516
  {
1372
1517
  "name": "@himalaya/himi-apps",
1373
1518
  "source": "core/sdk/himi-apps/src/index.ts",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.1230.0",
3
+ "version": "0.1232.0",
4
4
  "type": "module",
5
5
  "description": "himi — the Himalaya authoring CLI. Build a Tier-5 content package on your own machine, push it to the production serve as a draft, preview it on web + Himi Player, and read worker logs back — the remote dev loop. Wraps @himalaya/serve-cli for transport.",
6
6
  "keywords": [
@@ -67,5 +67,5 @@
67
67
  "artifactId": "himalaya-cli"
68
68
  }
69
69
  },
70
- "falconPackageHash": "da9e50d9f90fe398bc1ef02810d638b44074ee0065a9ecf29d89442d"
70
+ "falconPackageHash": "19c1239851ec3856adb71952d1e9dcb18ea9cdd23456e90bb311a12d"
71
71
  }