@ateam-ai-mobile/expo 0.1.4 → 0.1.7

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.
Files changed (158) hide show
  1. package/android/build.gradle +16 -0
  2. package/android/src/main/java/expo/modules/adavision/AdaVisionModule.kt +75 -0
  3. package/dist/attachmentCache-DhQTqi2E.d.mts +18 -0
  4. package/dist/attachmentCache-DhQTqi2E.d.ts +18 -0
  5. package/dist/bootstrap.d.mts +95 -0
  6. package/dist/bootstrap.d.ts +95 -0
  7. package/dist/bootstrap.js +2473 -0
  8. package/dist/bootstrap.js.map +1 -0
  9. package/dist/bootstrap.mjs +64 -0
  10. package/dist/bootstrap.mjs.map +1 -0
  11. package/dist/chunk-4BMWVN25.mjs +91 -0
  12. package/dist/chunk-4BMWVN25.mjs.map +1 -0
  13. package/dist/chunk-4XYXJYN5.mjs +1513 -0
  14. package/dist/chunk-4XYXJYN5.mjs.map +1 -0
  15. package/dist/chunk-5HM6UA2K.mjs +571 -0
  16. package/dist/chunk-5HM6UA2K.mjs.map +1 -0
  17. package/dist/chunk-6LXF3JVX.mjs +96 -0
  18. package/dist/chunk-6LXF3JVX.mjs.map +1 -0
  19. package/dist/chunk-FF6ZZZXH.mjs +71 -0
  20. package/dist/chunk-FF6ZZZXH.mjs.map +1 -0
  21. package/dist/chunk-KOXF2FZM.mjs +92 -0
  22. package/dist/chunk-KOXF2FZM.mjs.map +1 -0
  23. package/dist/chunk-L4V7OCJ3.mjs +71 -0
  24. package/dist/chunk-L4V7OCJ3.mjs.map +1 -0
  25. package/dist/chunk-TZXWVYYR.mjs +67 -0
  26. package/dist/chunk-TZXWVYYR.mjs.map +1 -0
  27. package/dist/chunk-VQJPSLHP.mjs +1801 -0
  28. package/dist/chunk-VQJPSLHP.mjs.map +1 -0
  29. package/dist/chunk-WMJKH4XE.mjs +34 -0
  30. package/dist/chunk-WMJKH4XE.mjs.map +1 -0
  31. package/dist/chunk-XQSY6MHP.mjs +213 -0
  32. package/dist/chunk-XQSY6MHP.mjs.map +1 -0
  33. package/dist/createBridge.mjs +2 -0
  34. package/dist/createBridge.mjs.map +1 -1
  35. package/dist/device/createDeviceProvider.d.mts +23 -0
  36. package/dist/device/createDeviceProvider.d.ts +23 -0
  37. package/dist/device/createDeviceProvider.js +967 -0
  38. package/dist/device/createDeviceProvider.js.map +1 -0
  39. package/dist/device/createDeviceProvider.mjs +17 -0
  40. package/dist/device/createDeviceProvider.mjs.map +1 -0
  41. package/dist/device-bridge/collectors/appEngagement.d.mts +10 -0
  42. package/dist/device-bridge/collectors/appEngagement.d.ts +10 -0
  43. package/dist/device-bridge/collectors/appEngagement.js +128 -0
  44. package/dist/device-bridge/collectors/appEngagement.js.map +1 -0
  45. package/dist/device-bridge/collectors/appEngagement.mjs +12 -0
  46. package/dist/device-bridge/collectors/appEngagement.mjs.map +1 -0
  47. package/dist/device-bridge/collectors/health.d.mts +24 -0
  48. package/dist/device-bridge/collectors/health.d.ts +24 -0
  49. package/dist/device-bridge/collectors/health.js +238 -0
  50. package/dist/device-bridge/collectors/health.js.map +1 -0
  51. package/dist/device-bridge/collectors/health.mjs +18 -0
  52. package/dist/device-bridge/collectors/health.mjs.map +1 -0
  53. package/dist/device-bridge/cookies.d.mts +49 -0
  54. package/dist/device-bridge/cookies.d.ts +49 -0
  55. package/dist/device-bridge/cookies.js +90 -0
  56. package/dist/device-bridge/cookies.js.map +1 -0
  57. package/dist/device-bridge/cookies.mjs +14 -0
  58. package/dist/device-bridge/cookies.mjs.map +1 -0
  59. package/dist/device-bridge/index.d.mts +483 -0
  60. package/dist/device-bridge/index.d.ts +483 -0
  61. package/dist/device-bridge/index.js +2084 -0
  62. package/dist/device-bridge/index.js.map +1 -0
  63. package/dist/device-bridge/index.mjs +32 -0
  64. package/dist/device-bridge/index.mjs.map +1 -0
  65. package/dist/device-bridge/nfc.d.mts +30 -0
  66. package/dist/device-bridge/nfc.d.ts +30 -0
  67. package/dist/device-bridge/nfc.js +109 -0
  68. package/dist/device-bridge/nfc.js.map +1 -0
  69. package/dist/device-bridge/nfc.mjs +14 -0
  70. package/dist/device-bridge/nfc.mjs.map +1 -0
  71. package/dist/index.d.mts +493 -18
  72. package/dist/index.d.ts +493 -18
  73. package/dist/index.js +1409 -6
  74. package/dist/index.js.map +1 -1
  75. package/dist/index.mjs +23 -194
  76. package/dist/index.mjs.map +1 -1
  77. package/dist/oauth/expoOAuth.d.mts +18 -0
  78. package/dist/oauth/expoOAuth.d.ts +18 -0
  79. package/dist/oauth/expoOAuth.js +102 -0
  80. package/dist/oauth/expoOAuth.js.map +1 -0
  81. package/dist/oauth/expoOAuth.mjs +8 -0
  82. package/dist/oauth/expoOAuth.mjs.map +1 -0
  83. package/dist/types-BRanJYTn.d.mts +138 -0
  84. package/dist/types-BRanJYTn.d.ts +138 -0
  85. package/dist/vision/index.d.mts +81 -0
  86. package/dist/vision/index.d.ts +81 -0
  87. package/dist/vision/index.js +101 -0
  88. package/dist/vision/index.js.map +1 -0
  89. package/dist/vision/index.mjs +28 -0
  90. package/dist/vision/index.mjs.map +1 -0
  91. package/expo-module.config.json +9 -0
  92. package/ios/AdaVision.podspec +28 -0
  93. package/ios/AdaVisionModule.swift +432 -0
  94. package/ios/Resources/yolov8n-oiv7.mlmodelc/analytics/coremldata.bin +0 -0
  95. package/ios/Resources/yolov8n-oiv7.mlmodelc/coremldata.bin +0 -0
  96. package/ios/Resources/yolov8n-oiv7.mlmodelc/metadata.json +773 -0
  97. package/ios/Resources/yolov8n-oiv7.mlmodelc/model0/analytics/coremldata.bin +0 -0
  98. package/ios/Resources/yolov8n-oiv7.mlmodelc/model0/coremldata.bin +0 -0
  99. package/ios/Resources/yolov8n-oiv7.mlmodelc/model0/model.mil +720 -0
  100. package/ios/Resources/yolov8n-oiv7.mlmodelc/model0/weights/weight.bin +0 -0
  101. package/ios/Resources/yolov8n-oiv7.mlmodelc/model1/coremldata.bin +0 -0
  102. package/package.json +107 -7
  103. package/src/bootstrap.ts +159 -0
  104. package/src/camera/CameraTaskHost.tsx +175 -0
  105. package/src/camera/cameraTasks.ts +129 -0
  106. package/src/device/createDeviceProvider.ts +832 -0
  107. package/src/device-bridge/actions/calendar-actions.ts +217 -0
  108. package/src/device-bridge/actions/dnd.ts +74 -0
  109. package/src/device-bridge/actions/health-actions.ts +58 -0
  110. package/src/device-bridge/actions/index.ts +12 -0
  111. package/src/device-bridge/actions/messaging.ts +74 -0
  112. package/src/device-bridge/actions/navigation.ts +88 -0
  113. package/src/device-bridge/actions/queue-poller.ts +151 -0
  114. package/src/device-bridge/background.ts +137 -0
  115. package/src/device-bridge/biometrics.ts +64 -0
  116. package/src/device-bridge/capabilities.ts +218 -0
  117. package/src/device-bridge/collectors/appEngagement.ts +120 -0
  118. package/src/device-bridge/collectors/battery.ts +83 -0
  119. package/src/device-bridge/collectors/calendar.ts +162 -0
  120. package/src/device-bridge/collectors/camera.ts +27 -0
  121. package/src/device-bridge/collectors/connectivity.ts +60 -0
  122. package/src/device-bridge/collectors/contacts.ts +200 -0
  123. package/src/device-bridge/collectors/health.ts +299 -0
  124. package/src/device-bridge/collectors/index.ts +12 -0
  125. package/src/device-bridge/collectors/location.ts +224 -0
  126. package/src/device-bridge/collectors/mood.ts +141 -0
  127. package/src/device-bridge/collectors/notifications.ts +70 -0
  128. package/src/device-bridge/cookies.ts +89 -0
  129. package/src/device-bridge/dynamic-connector.ts +422 -0
  130. package/src/device-bridge/index.ts +467 -0
  131. package/src/device-bridge/nfc.ts +85 -0
  132. package/src/device-bridge/plugin.ts +71 -0
  133. package/src/device-bridge/relay-client.ts +237 -0
  134. package/src/device-bridge/scanner.ts +42 -0
  135. package/src/device-bridge/sharing.ts +35 -0
  136. package/src/device-bridge/types.ts +326 -0
  137. package/src/device-state/activityClassifier.ts +151 -0
  138. package/src/device-state/activityEngine.ts +252 -0
  139. package/src/device-state/collectors.ts +31 -0
  140. package/src/device-state/domains/activity.ts +93 -0
  141. package/src/device-state/domains/capability.ts +60 -0
  142. package/src/device-state/domains/connectivity.ts +60 -0
  143. package/src/device-state/domains/deviceContext.ts +55 -0
  144. package/src/device-state/domains/interaction.ts +59 -0
  145. package/src/device-state/domains/spatial.ts +283 -0
  146. package/src/device-state/domains/visual.ts +100 -0
  147. package/src/device-state/headingSource.ts +84 -0
  148. package/src/device-state/index.ts +77 -0
  149. package/src/device-state/quality.ts +60 -0
  150. package/src/device-state/replay.ts +220 -0
  151. package/src/device-state/turnDetector.ts +121 -0
  152. package/src/device-state/visualEngine.ts +341 -0
  153. package/src/haptics.ts +4 -0
  154. package/src/index.ts +37 -0
  155. package/src/oauth/expoOAuth.tsx +113 -0
  156. package/src/vision/analysis.js +80 -0
  157. package/src/vision/index.ts +144 -0
  158. package/src/vision/ring.js +35 -0
@@ -0,0 +1,237 @@
1
+ /**
2
+ * Relay Client — REST client for the cloud relay service.
3
+ * Handles data push, action polling, and offline buffering.
4
+ *
5
+ * Endpoints:
6
+ * POST /api/device/{deviceId}/sync ← push data bundle
7
+ * GET /api/device/{deviceId}/data/{domain} ← read latest (used by connector, not phone)
8
+ * POST /api/device/{deviceId}/actions ← queue action (used by connector)
9
+ * GET /api/device/{deviceId}/actions ← poll pending actions
10
+ * DELETE /api/device/{deviceId}/actions/{id} ← confirm action executed
11
+ */
12
+
13
+ import type {
14
+ DeviceDataBundle,
15
+ PendingAction,
16
+ ActionResult,
17
+ RelayResponse,
18
+ } from './types';
19
+
20
+ export interface RelayClientConfig {
21
+ relayUrl: string;
22
+ deviceId: string;
23
+ tenant: string;
24
+ actorId?: string;
25
+ apiKey?: string;
26
+ timeout?: number; // ms, default 10000
27
+ }
28
+
29
+ export class RelayClient {
30
+ private baseUrl: string;
31
+ private deviceId: string;
32
+ private tenant: string;
33
+ private actorId: string;
34
+ private apiKey: string;
35
+ private timeout: number;
36
+ private offlineQueue: DeviceDataBundle[] = [];
37
+
38
+ constructor(config: RelayClientConfig) {
39
+ // Strip trailing slash
40
+ this.baseUrl = config.relayUrl.replace(/\/$/, '');
41
+ this.deviceId = config.deviceId;
42
+ this.tenant = config.tenant;
43
+ this.actorId = config.actorId ?? 'default';
44
+ this.apiKey = config.apiKey ?? '';
45
+ this.timeout = config.timeout ?? 10_000;
46
+ }
47
+
48
+ // ─── Data Sync ───
49
+
50
+ /**
51
+ * Push a full device data bundle to the relay.
52
+ * If offline, buffers the bundle and retries on next sync.
53
+ */
54
+ async pushData(bundle: DeviceDataBundle): Promise<RelayResponse<void>> {
55
+ try {
56
+ const response = await this.fetch(
57
+ `/api/device/${this.deviceId}/sync`,
58
+ {
59
+ method: 'POST',
60
+ body: JSON.stringify(bundle),
61
+ }
62
+ );
63
+
64
+ if (response.ok) {
65
+ // Flush any offline queue on successful connection
66
+ await this.flushOfflineQueue();
67
+ return { ok: true };
68
+ }
69
+
70
+ return {
71
+ ok: false,
72
+ error: `Relay returned ${response.status}: ${await response.text()}`,
73
+ };
74
+ } catch (error) {
75
+ // Network error — buffer for later
76
+ this.offlineQueue.push(bundle);
77
+ return {
78
+ ok: false,
79
+ error: `Offline — buffered (${this.offlineQueue.length} pending)`,
80
+ };
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Push a single domain update (partial sync).
86
+ */
87
+ async pushDomain(
88
+ domain: string,
89
+ data: unknown
90
+ ): Promise<RelayResponse<void>> {
91
+ try {
92
+ const response = await this.fetch(
93
+ `/api/device/${this.deviceId}/sync/${domain}`,
94
+ {
95
+ method: 'POST',
96
+ body: JSON.stringify({ domain, data, timestamp: new Date().toISOString(), actorId: this.actorId }),
97
+ }
98
+ );
99
+
100
+ if (response.ok) return { ok: true };
101
+ return {
102
+ ok: false,
103
+ error: `Relay returned ${response.status}`,
104
+ };
105
+ } catch (error) {
106
+ return { ok: false, error: 'Network error — domain sync failed' };
107
+ }
108
+ }
109
+
110
+ // ─── Action Queue ───
111
+
112
+ /**
113
+ * Poll for pending actions the phone needs to execute.
114
+ */
115
+ async pollActions(): Promise<RelayResponse<PendingAction[]>> {
116
+ try {
117
+ const response = await this.fetch(
118
+ `/api/device/${this.deviceId}/actions`,
119
+ { method: 'GET' }
120
+ );
121
+
122
+ if (response.ok) {
123
+ const data = await response.json();
124
+ return { ok: true, data: data.actions ?? [] };
125
+ }
126
+ return { ok: false, error: `Relay returned ${response.status}` };
127
+ } catch (error) {
128
+ return { ok: false, error: 'Network error — action poll failed' };
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Report action execution result and remove from queue.
134
+ */
135
+ async completeAction(result: ActionResult): Promise<RelayResponse<void>> {
136
+ try {
137
+ const response = await this.fetch(
138
+ `/api/device/${this.deviceId}/actions/${result.id}`,
139
+ {
140
+ method: 'DELETE',
141
+ body: JSON.stringify(result),
142
+ }
143
+ );
144
+
145
+ if (response.ok) return { ok: true };
146
+ return { ok: false, error: `Relay returned ${response.status}` };
147
+ } catch (error) {
148
+ return { ok: false, error: 'Network error — action complete failed' };
149
+ }
150
+ }
151
+
152
+ // ─── Health ───
153
+
154
+ /**
155
+ * Ping the relay to check connectivity.
156
+ */
157
+ async ping(): Promise<boolean> {
158
+ try {
159
+ const response = await this.fetch(
160
+ `/api/health`,
161
+ { method: 'GET' },
162
+ 3_000 // short timeout for health check
163
+ );
164
+ return response.ok;
165
+ } catch {
166
+ return false;
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Get count of buffered offline bundles.
172
+ */
173
+ getOfflineQueueSize(): number {
174
+ return this.offlineQueue.length;
175
+ }
176
+
177
+ // ─── Internal ───
178
+
179
+ private async fetch(
180
+ path: string,
181
+ init: RequestInit,
182
+ timeout?: number
183
+ ): Promise<Response> {
184
+ const controller = new AbortController();
185
+ const timer = setTimeout(
186
+ () => controller.abort(),
187
+ timeout ?? this.timeout
188
+ );
189
+
190
+ try {
191
+ return await fetch(`${this.baseUrl}${path}`, {
192
+ ...init,
193
+ signal: controller.signal,
194
+ headers: {
195
+ 'Content-Type': 'application/json',
196
+ 'X-ADAS-TENANT': this.tenant,
197
+ 'X-ADAS-ACTOR': this.actorId,
198
+ 'X-Device-Id': this.deviceId,
199
+ // The relay (post-2026-04-25) gates every /api/device/* call on
200
+ // a shared secret. Send it as `Authorization: Bearer` so the
201
+ // server's relayAuth middleware accepts the request. Empty/missing
202
+ // secret = 401 from the relay (correct fail-closed behavior).
203
+ ...(this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}),
204
+ ...(init.headers || {}),
205
+ },
206
+ });
207
+ } finally {
208
+ clearTimeout(timer);
209
+ }
210
+ }
211
+
212
+ private async flushOfflineQueue(): Promise<void> {
213
+ if (this.offlineQueue.length === 0) return;
214
+
215
+ const queue = [...this.offlineQueue];
216
+ this.offlineQueue = [];
217
+
218
+ for (const bundle of queue) {
219
+ try {
220
+ const response = await this.fetch(
221
+ `/api/device/${this.deviceId}/sync`,
222
+ {
223
+ method: 'POST',
224
+ body: JSON.stringify(bundle),
225
+ }
226
+ );
227
+ if (!response.ok) {
228
+ // Put it back if still failing
229
+ this.offlineQueue.push(bundle);
230
+ }
231
+ } catch {
232
+ this.offlineQueue.push(bundle);
233
+ break; // Stop trying if still offline
234
+ }
235
+ }
236
+ }
237
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Device-bridge: Scanner (QR / barcode)
3
+ *
4
+ * Wraps expo-camera (which replaced the deprecated expo-barcode-scanner
5
+ * in Expo SDK 50+). The CameraView component has built-in barcode scanning.
6
+ *
7
+ * Use cases:
8
+ * - Wi-Fi QR handoff
9
+ * - OAuth / pairing codes
10
+ * - Payment QR codes
11
+ * - Product lookup
12
+ *
13
+ * Plugins mount <CameraView onBarcodeScanned={...} /> directly. This module
14
+ * exposes the permission helper + a result normalizer so plugins don't need
15
+ * to import expo-camera directly.
16
+ */
17
+
18
+ import { CameraView, useCameraPermissions, type BarcodeScanningResult } from 'expo-camera';
19
+
20
+ export interface ScanResult {
21
+ type: string; // e.g. 'qr', 'ean13', 'code128'
22
+ data: string; // decoded content
23
+ }
24
+
25
+ /** Request camera permission if not already granted. */
26
+ export async function requestPermissions(): Promise<{ granted: boolean; status: string }> {
27
+ // useCameraPermissions is a hook; for the non-hook flow plugins should use
28
+ // Camera.requestCameraPermissionsAsync. But it's easier to expose the hook
29
+ // helper — plugins typically use the hook form inside their Component.
30
+ // For service-style callers, fall back to the imperative API:
31
+ const { Camera } = await import('expo-camera');
32
+ const { status } = await Camera.requestCameraPermissionsAsync();
33
+ return { granted: status === 'granted', status };
34
+ }
35
+
36
+ /** Normalize a CameraView barcode result to a stable shape. */
37
+ export function normalizeResult(r: BarcodeScanningResult): ScanResult {
38
+ return { type: r.type, data: r.data };
39
+ }
40
+
41
+ /** Re-export the component + hook so plugins consume them via device-bridge. */
42
+ export { CameraView, useCameraPermissions };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Device-bridge: Sharing (native share sheet)
3
+ *
4
+ * Wraps expo-sharing. Lets solutions hand content off to the OS share sheet
5
+ * — any installed app can receive it (Messages, WhatsApp, Mail, Files, etc.).
6
+ *
7
+ * Typical use: "Share this article / calendar invite / photo with a contact."
8
+ */
9
+
10
+ import * as Sharing from 'expo-sharing';
11
+
12
+ export interface ShareOptions {
13
+ /** MIME type, e.g. 'application/pdf'. Optional. */
14
+ mimeType?: string;
15
+ /** iOS UTI, e.g. 'public.image'. Optional. */
16
+ UTI?: string;
17
+ /** Dialog title (Android only). */
18
+ dialogTitle?: string;
19
+ }
20
+
21
+ /** Share a local file URI. */
22
+ export async function shareFile(fileUri: string, opts: ShareOptions = {}): Promise<{ ok: boolean; error?: string }> {
23
+ try {
24
+ const available = await Sharing.isAvailableAsync();
25
+ if (!available) return { ok: false, error: 'sharing_unavailable' };
26
+ await Sharing.shareAsync(fileUri, opts);
27
+ return { ok: true };
28
+ } catch (e: any) {
29
+ return { ok: false, error: String(e?.message || e) };
30
+ }
31
+ }
32
+
33
+ export async function isAvailable(): Promise<boolean> {
34
+ return Sharing.isAvailableAsync();
35
+ }
@@ -0,0 +1,326 @@
1
+ // ─── Device Bridge Types ───
2
+ // All types mirror the exact output schemas from device-mock-mcp
3
+ // so that device-mcp connector can serve identical responses.
4
+
5
+ // ─── Configuration ───
6
+
7
+ export interface DeviceBridgeConfig {
8
+ /** Cloud relay base URL (e.g., https://relay.example.com) */
9
+ relayUrl: string;
10
+ /** Unique device identifier */
11
+ deviceId: string;
12
+ /** Tenant ID — required by relay for data isolation */
13
+ tenant: string;
14
+ /** Actor/user ID — scopes device data per user in relay */
15
+ actorId?: string;
16
+ /** API key for relay authentication (reserved for future use) */
17
+ apiKey?: string;
18
+ /** Sync intervals per domain in milliseconds */
19
+ syncIntervals?: Partial<Record<string, number>>;
20
+ /** Enable background sync (requires expo-task-manager) */
21
+ backgroundSync?: boolean;
22
+ /** Callback for sync status changes */
23
+ onStatusChange?: (status: BridgeStatus) => void;
24
+ /** Callback for errors */
25
+ onError?: (error: BridgeError) => void;
26
+ }
27
+
28
+ // ─── Capability System ───
29
+
30
+ export type PermissionState = 'not_requested' | 'granted' | 'denied' | 'not_needed';
31
+
32
+ export interface Capability {
33
+ /** Unique ID (e.g., 'calendar', 'contacts', 'whatsapp') */
34
+ id: string;
35
+ /** Human-readable name for UI */
36
+ name: string;
37
+ /** One-line description of what this capability does */
38
+ description: string;
39
+ /** Why the app needs this — shown to user BEFORE the OS dialog */
40
+ reason: string;
41
+ /** Does this capability require OS permission? */
42
+ requiresPermission: boolean;
43
+ /** Current permission state */
44
+ permissionState: PermissionState;
45
+ /** Request permission from the OS. Returns true if granted. */
46
+ requestPermission: () => Promise<boolean>;
47
+ /** Check current permission without prompting. */
48
+ checkPermission: () => Promise<boolean>;
49
+ /** Collect data for this capability. Returns domain data. */
50
+ collect: () => Promise<unknown>;
51
+ /** Default sync interval in ms */
52
+ defaultInterval: number;
53
+ /** Optional icon name (for host app UI) */
54
+ icon?: string;
55
+ /**
56
+ * Optional OS-event subscription. When provided, the bridge wires this up
57
+ * during startSync and the callback triggers an immediate sync of this
58
+ * capability — useful for sources that change unpredictably (battery
59
+ * level/state) where polling on a fixed interval misses changes while the
60
+ * app is backgrounded. Return an unsubscribe function.
61
+ */
62
+ subscribe?: (notify: () => void) => () => void;
63
+ }
64
+
65
+ // ─── Bridge Status ───
66
+
67
+ export type SyncState = 'idle' | 'syncing' | 'error' | 'stopped';
68
+
69
+ export interface DomainSyncStatus {
70
+ domain: string;
71
+ lastSync: string | null; // ISO timestamp
72
+ state: SyncState;
73
+ error?: string;
74
+ itemCount?: number;
75
+ }
76
+
77
+ export interface BridgeStatus {
78
+ running: boolean;
79
+ capabilities: CapabilityStatus[];
80
+ lastFullSync: string | null;
81
+ pendingActions: number;
82
+ }
83
+
84
+ export interface CapabilityStatus {
85
+ id: string;
86
+ name: string;
87
+ permission: PermissionState;
88
+ sync: DomainSyncStatus;
89
+ }
90
+
91
+ export interface BridgeError {
92
+ domain: string;
93
+ message: string;
94
+ code: string;
95
+ timestamp: string;
96
+ }
97
+
98
+ // ─── Calendar ───
99
+
100
+ export interface CalendarEvent {
101
+ id: string;
102
+ title: string;
103
+ date: string; // YYYY-MM-DD
104
+ start: string; // HH:MM
105
+ end: string; // HH:MM
106
+ location: string;
107
+ attendees: string[];
108
+ notes: string;
109
+ }
110
+
111
+ export interface CalendarTodayData {
112
+ ok: true;
113
+ date: string;
114
+ day_of_week: string;
115
+ event_count: number;
116
+ events: CalendarEvent[];
117
+ }
118
+
119
+ export interface CalendarUpcomingData {
120
+ ok: true;
121
+ from: string;
122
+ days_ahead: number;
123
+ event_count: number;
124
+ events: CalendarEvent[];
125
+ }
126
+
127
+ // ─── Contacts ───
128
+
129
+ export interface Contact {
130
+ id: string;
131
+ name: string;
132
+ relationship: string;
133
+ phone: string | null;
134
+ email: string | null;
135
+ birthday: string | null; // YYYY-MM-DD
136
+ notes: string;
137
+ }
138
+
139
+ export interface ContactSearchData {
140
+ ok: true;
141
+ query: string;
142
+ count: number;
143
+ contacts: Contact[];
144
+ }
145
+
146
+ export interface ContactLastContactData {
147
+ ok: true;
148
+ contact: string;
149
+ relationship: string;
150
+ days_since_last_contact: number;
151
+ last_contact_date: string;
152
+ last_contact_type: string;
153
+ suggestion: string | null;
154
+ }
155
+
156
+ // ─── Location ───
157
+
158
+ export interface LocationData {
159
+ ok: true;
160
+ timestamp: string;
161
+ location: {
162
+ place: string;
163
+ address: string;
164
+ lat: number;
165
+ lng: number;
166
+ type: string;
167
+ };
168
+ }
169
+
170
+ // ─── Battery ───
171
+
172
+ export interface BatteryData {
173
+ ok: true;
174
+ timestamp: string;
175
+ battery: {
176
+ level: number; // 0-100
177
+ state: 'charging' | 'unplugged' | 'full';
178
+ low_power_mode: boolean;
179
+ };
180
+ }
181
+
182
+ // ─── Health (HealthKit on iOS, Health Connect on Android — iOS only for now) ───
183
+
184
+ export interface HealthDayMetrics {
185
+ /** ISO date in local time, e.g. "2026-05-03" */
186
+ date: string;
187
+ /**
188
+ * False when HealthKit is unavailable or the user denied access.
189
+ *
190
+ * The metrics below are then null, NOT zero. A zero here used to be
191
+ * indistinguishable from a real sedentary day, so a denied permission was
192
+ * reported to the user as "you have taken 0 steps today" — and a coaching
193
+ * skill would act on it.
194
+ */
195
+ available: boolean;
196
+ /** Total step count for the day. null when unavailable. */
197
+ steps: number | null;
198
+ /** Flights of stairs climbed. null when unavailable. */
199
+ floors: number | null;
200
+ /** Active energy burned, kcal. null when unavailable. */
201
+ active_energy_kcal: number | null;
202
+ /** Walking + running distance, kilometers. null when unavailable. */
203
+ distance_km: number | null;
204
+ }
205
+
206
+ export interface HealthTodayData {
207
+ ok: true;
208
+ timestamp: string;
209
+ /** Same shape as a single history entry — date is today in local time. */
210
+ today: HealthDayMetrics;
211
+ }
212
+
213
+ export interface HealthHistoryData {
214
+ ok: true;
215
+ timestamp: string;
216
+ /** Newest-first: index 0 is today, index N is N days ago. */
217
+ days: HealthDayMetrics[];
218
+ }
219
+
220
+ // ─── Connectivity ───
221
+
222
+ export interface ConnectivityData {
223
+ ok: true;
224
+ timestamp: string;
225
+ connectivity: {
226
+ type: 'wifi' | 'cellular' | 'none';
227
+ is_connected: boolean;
228
+ wifi_name?: string;
229
+ cellular_type?: string; // '5G', 'LTE', etc.
230
+ };
231
+ }
232
+
233
+ // ─── Notifications ───
234
+
235
+ export interface Notification {
236
+ id: string;
237
+ app: string;
238
+ title: string;
239
+ body: string;
240
+ timestamp: string;
241
+ sender: string | null;
242
+ priority: 'high' | 'medium' | 'low';
243
+ read: boolean;
244
+ }
245
+
246
+ export interface NotificationsData {
247
+ ok: true;
248
+ timestamp: string;
249
+ total: number;
250
+ unread: number;
251
+ notifications: Notification[];
252
+ }
253
+
254
+ // ─── DND ───
255
+
256
+ export interface DndState {
257
+ enabled: boolean;
258
+ schedule: { start: string; end: string };
259
+ allowlist: string[];
260
+ }
261
+
262
+ export interface DndData {
263
+ ok: true;
264
+ dnd: DndState;
265
+ }
266
+
267
+ // ─── App Usage ───
268
+
269
+ export interface AppUsageEntry {
270
+ app: string;
271
+ last_used: string;
272
+ usage_minutes_today: number;
273
+ category: string;
274
+ }
275
+
276
+ export interface AppUsageData {
277
+ ok: true;
278
+ timestamp: string;
279
+ total_screen_time_minutes: number;
280
+ app_count: number;
281
+ apps: AppUsageEntry[];
282
+ }
283
+
284
+ // ─── Data Bundle (full sync payload) ───
285
+
286
+ export interface DeviceDataBundle {
287
+ deviceId: string;
288
+ timestamp: string;
289
+ domains: Record<string, unknown>;
290
+ }
291
+
292
+ // ─── Action Queue ───
293
+
294
+ export type ActionType =
295
+ | 'messaging.send'
296
+ | 'calendar.create'
297
+ | 'calendar.reschedule'
298
+ | 'calendar.cancel'
299
+ | 'dnd.set'
300
+ | 'navigation.start'
301
+ | 'health.today'
302
+ | 'health.history';
303
+
304
+ export interface PendingAction {
305
+ id: string;
306
+ type: ActionType;
307
+ params: Record<string, unknown>;
308
+ created_at: string;
309
+ status: 'pending' | 'executing' | 'completed' | 'failed';
310
+ }
311
+
312
+ export interface ActionResult {
313
+ id: string;
314
+ ok: boolean;
315
+ result?: Record<string, unknown>;
316
+ error?: string;
317
+ executed_at: string;
318
+ }
319
+
320
+ // ─── Relay Client Types ───
321
+
322
+ export interface RelayResponse<T> {
323
+ ok: boolean;
324
+ data?: T;
325
+ error?: string;
326
+ }