@daobrew/wellness-mcp 0.1.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.
Files changed (76) hide show
  1. package/README.md +98 -0
  2. package/SKILL.md +190 -0
  3. package/audio/earth_breathing.m4a +0 -0
  4. package/audio/earth_meditation.m4a +0 -0
  5. package/audio/earth_zhanZhuang.m4a +0 -0
  6. package/audio/fire_breathing.m4a +0 -0
  7. package/audio/fire_meditation.m4a +0 -0
  8. package/audio/fire_zhanZhuang.m4a +0 -0
  9. package/audio/metal_breathing.m4a +0 -0
  10. package/audio/metal_meditation.m4a +0 -0
  11. package/audio/metal_zhanZhuang.m4a +0 -0
  12. package/audio/water_breathing.m4a +0 -0
  13. package/audio/water_meditation.m4a +0 -0
  14. package/audio/water_zhanZhuang.m4a +0 -0
  15. package/audio/wood_breathing.m4a +0 -0
  16. package/audio/wood_meditation.m4a +0 -0
  17. package/audio/wood_zhanZhuang.m4a +0 -0
  18. package/dist/src/audio.d.ts +13 -0
  19. package/dist/src/audio.js +88 -0
  20. package/dist/src/cache.d.ts +7 -0
  21. package/dist/src/cache.js +31 -0
  22. package/dist/src/client.d.ts +22 -0
  23. package/dist/src/client.js +65 -0
  24. package/dist/src/cooldown.d.ts +5 -0
  25. package/dist/src/cooldown.js +35 -0
  26. package/dist/src/headphones.d.ts +6 -0
  27. package/dist/src/headphones.js +50 -0
  28. package/dist/src/health/google-fit.d.ts +13 -0
  29. package/dist/src/health/google-fit.js +108 -0
  30. package/dist/src/health/index.d.ts +6 -0
  31. package/dist/src/health/index.js +42 -0
  32. package/dist/src/health/oauth.d.ts +6 -0
  33. package/dist/src/health/oauth.js +69 -0
  34. package/dist/src/health/oura.d.ts +14 -0
  35. package/dist/src/health/oura.js +130 -0
  36. package/dist/src/health/sync.d.ts +7 -0
  37. package/dist/src/health/sync.js +194 -0
  38. package/dist/src/index.d.ts +2 -0
  39. package/dist/src/index.js +107 -0
  40. package/dist/src/mock.d.ts +8 -0
  41. package/dist/src/mock.js +176 -0
  42. package/dist/src/preferences.d.ts +13 -0
  43. package/dist/src/preferences.js +47 -0
  44. package/dist/src/session.d.ts +15 -0
  45. package/dist/src/session.js +40 -0
  46. package/dist/src/setup-cli.js +2 -0
  47. package/dist/src/setup.d.ts +17 -0
  48. package/dist/src/setup.js +323 -0
  49. package/dist/src/tools.d.ts +4 -0
  50. package/dist/src/tools.js +420 -0
  51. package/dist/src/types.d.ts +86 -0
  52. package/dist/src/types.js +52 -0
  53. package/dist/tests/audio.test.d.ts +1 -0
  54. package/dist/tests/audio.test.js +67 -0
  55. package/dist/tests/cache.test.d.ts +1 -0
  56. package/dist/tests/cache.test.js +61 -0
  57. package/dist/tests/client.test.d.ts +1 -0
  58. package/dist/tests/client.test.js +95 -0
  59. package/dist/tests/cooldown.test.d.ts +1 -0
  60. package/dist/tests/cooldown.test.js +66 -0
  61. package/dist/tests/e2e.test.d.ts +1 -0
  62. package/dist/tests/e2e.test.js +144 -0
  63. package/dist/tests/guards.test.d.ts +1 -0
  64. package/dist/tests/guards.test.js +169 -0
  65. package/dist/tests/headphones.test.d.ts +1 -0
  66. package/dist/tests/headphones.test.js +46 -0
  67. package/dist/tests/mock.test.d.ts +1 -0
  68. package/dist/tests/mock.test.js +194 -0
  69. package/dist/tests/preferences.test.d.ts +1 -0
  70. package/dist/tests/preferences.test.js +71 -0
  71. package/dist/tests/session.test.d.ts +1 -0
  72. package/dist/tests/session.test.js +85 -0
  73. package/dist/tests/sync.test.d.ts +1 -0
  74. package/dist/tests/sync.test.js +54 -0
  75. package/package.json +29 -0
  76. package/src/setup-cli.js +2 -0
@@ -0,0 +1,13 @@
1
+ export interface GoogleFitToken {
2
+ access_token: string;
3
+ refresh_token: string;
4
+ expires_at: number;
5
+ token_type: string;
6
+ }
7
+ export declare function isConnected(): boolean;
8
+ export declare function loadToken(): GoogleFitToken | null;
9
+ export declare function saveToken(token: GoogleFitToken): void;
10
+ export declare function exchangeCode(code: string, clientId: string, clientSecret: string, redirectUri: string): Promise<GoogleFitToken>;
11
+ export declare function refreshAccessToken(token: GoogleFitToken, clientId: string, clientSecret: string): Promise<GoogleFitToken>;
12
+ export declare function fetchDataSources(token: GoogleFitToken): Promise<any>;
13
+ export declare function fetchHeartRate(token: GoogleFitToken, startTimeMs: number, endTimeMs: number): Promise<any>;
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isConnected = isConnected;
4
+ exports.loadToken = loadToken;
5
+ exports.saveToken = saveToken;
6
+ exports.exchangeCode = exchangeCode;
7
+ exports.refreshAccessToken = refreshAccessToken;
8
+ exports.fetchDataSources = fetchDataSources;
9
+ exports.fetchHeartRate = fetchHeartRate;
10
+ const fs_1 = require("fs");
11
+ const path_1 = require("path");
12
+ const os_1 = require("os");
13
+ const PREFS_DIR = (0, path_1.join)((0, os_1.homedir)(), ".daobrew");
14
+ const TOKEN_FILE = (0, path_1.join)(PREFS_DIR, "google-fit-token.json");
15
+ const FITNESS_API_BASE = "https://www.googleapis.com/fitness/v1/users/me";
16
+ function isConnected() {
17
+ return (0, fs_1.existsSync)(TOKEN_FILE);
18
+ }
19
+ function loadToken() {
20
+ if (!(0, fs_1.existsSync)(TOKEN_FILE))
21
+ return null;
22
+ try {
23
+ return JSON.parse((0, fs_1.readFileSync)(TOKEN_FILE, "utf-8"));
24
+ }
25
+ catch {
26
+ return null;
27
+ }
28
+ }
29
+ function saveToken(token) {
30
+ if (!(0, fs_1.existsSync)(PREFS_DIR))
31
+ (0, fs_1.mkdirSync)(PREFS_DIR, { recursive: true });
32
+ (0, fs_1.writeFileSync)(TOKEN_FILE, JSON.stringify(token, null, 2));
33
+ }
34
+ async function exchangeCode(code, clientId, clientSecret, redirectUri) {
35
+ const response = await fetch("https://oauth2.googleapis.com/token", {
36
+ method: "POST",
37
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
38
+ body: new URLSearchParams({
39
+ grant_type: "authorization_code",
40
+ code,
41
+ client_id: clientId,
42
+ client_secret: clientSecret,
43
+ redirect_uri: redirectUri,
44
+ }),
45
+ });
46
+ if (!response.ok) {
47
+ throw new Error(`Google token exchange failed: ${response.status}`);
48
+ }
49
+ const data = await response.json();
50
+ const token = {
51
+ access_token: data.access_token,
52
+ refresh_token: data.refresh_token,
53
+ expires_at: Date.now() + (data.expires_in ?? 3600) * 1000,
54
+ token_type: data.token_type ?? "Bearer",
55
+ };
56
+ saveToken(token);
57
+ return token;
58
+ }
59
+ async function refreshAccessToken(token, clientId, clientSecret) {
60
+ const response = await fetch("https://oauth2.googleapis.com/token", {
61
+ method: "POST",
62
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
63
+ body: new URLSearchParams({
64
+ grant_type: "refresh_token",
65
+ refresh_token: token.refresh_token,
66
+ client_id: clientId,
67
+ client_secret: clientSecret,
68
+ }),
69
+ });
70
+ if (!response.ok) {
71
+ throw new Error(`Google token refresh failed: ${response.status}`);
72
+ }
73
+ const data = await response.json();
74
+ const newToken = {
75
+ access_token: data.access_token,
76
+ refresh_token: token.refresh_token,
77
+ expires_at: Date.now() + (data.expires_in ?? 3600) * 1000,
78
+ token_type: data.token_type ?? "Bearer",
79
+ };
80
+ saveToken(newToken);
81
+ return newToken;
82
+ }
83
+ async function fetchDataSources(token) {
84
+ const response = await fetch(`${FITNESS_API_BASE}/dataSources`, {
85
+ headers: { Authorization: `Bearer ${token.access_token}` },
86
+ });
87
+ if (!response.ok)
88
+ throw new Error(`Google Fit API error: ${response.status}`);
89
+ return response.json();
90
+ }
91
+ async function fetchHeartRate(token, startTimeMs, endTimeMs) {
92
+ const response = await fetch(`${FITNESS_API_BASE}/dataset:aggregate`, {
93
+ method: "POST",
94
+ headers: {
95
+ Authorization: `Bearer ${token.access_token}`,
96
+ "Content-Type": "application/json",
97
+ },
98
+ body: JSON.stringify({
99
+ aggregateBy: [{ dataTypeName: "com.google.heart_rate.bpm" }],
100
+ bucketByTime: { durationMillis: 3600000 },
101
+ startTimeMillis: startTimeMs,
102
+ endTimeMillis: endTimeMs,
103
+ }),
104
+ });
105
+ if (!response.ok)
106
+ throw new Error(`Google Fit API error: ${response.status}`);
107
+ return response.json();
108
+ }
@@ -0,0 +1,6 @@
1
+ export * as oura from "./oura.js";
2
+ export * as googleFit from "./google-fit.js";
3
+ export { startOAuthFlow } from "./oauth.js";
4
+ export type { OAuthResult } from "./oauth.js";
5
+ export { syncAllConnectedSources } from "./sync.js";
6
+ export type { SyncResult } from "./sync.js";
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.syncAllConnectedSources = exports.startOAuthFlow = exports.googleFit = exports.oura = void 0;
37
+ exports.oura = __importStar(require("./oura.js"));
38
+ exports.googleFit = __importStar(require("./google-fit.js"));
39
+ var oauth_js_1 = require("./oauth.js");
40
+ Object.defineProperty(exports, "startOAuthFlow", { enumerable: true, get: function () { return oauth_js_1.startOAuthFlow; } });
41
+ var sync_js_1 = require("./sync.js");
42
+ Object.defineProperty(exports, "syncAllConnectedSources", { enumerable: true, get: function () { return sync_js_1.syncAllConnectedSources; } });
@@ -0,0 +1,6 @@
1
+ export interface OAuthResult {
2
+ status: "connected" | "error" | "cancelled";
3
+ source: string;
4
+ error?: string;
5
+ }
6
+ export declare function startOAuthFlow(source: "oura" | "google_fit", clientId: string, authUrl: string, tokenUrl: string, scopes: string[]): Promise<OAuthResult>;
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.startOAuthFlow = startOAuthFlow;
4
+ const http_1 = require("http");
5
+ const child_process_1 = require("child_process");
6
+ const fs_1 = require("fs");
7
+ const path_1 = require("path");
8
+ const os_1 = require("os");
9
+ const PREFS_DIR = (0, path_1.join)((0, os_1.homedir)(), ".daobrew");
10
+ async function exchangeCodeForToken(source, code, clientId, clientSecret, tokenUrl, redirectUri) {
11
+ if (source === "oura") {
12
+ const { exchangeCode } = await import("./oura.js");
13
+ await exchangeCode(code, clientId, clientSecret, redirectUri);
14
+ }
15
+ else {
16
+ const { exchangeCode } = await import("./google-fit.js");
17
+ await exchangeCode(code, clientId, clientSecret, redirectUri);
18
+ }
19
+ }
20
+ async function startOAuthFlow(source, clientId, authUrl, tokenUrl, scopes) {
21
+ const clientSecret = source === "oura"
22
+ ? process.env.DAOBREW_OURA_CLIENT_SECRET ?? ""
23
+ : process.env.DAOBREW_GOOGLE_CLIENT_SECRET ?? "";
24
+ return new Promise((resolve) => {
25
+ const server = (0, http_1.createServer)(async (req, res) => {
26
+ const url = new URL(req.url ?? "/", `http://localhost`);
27
+ if (url.pathname === "/callback") {
28
+ const code = url.searchParams.get("code");
29
+ if (!code) {
30
+ res.writeHead(400);
31
+ res.end("Missing authorization code");
32
+ server.close();
33
+ resolve({ status: "error", source, error: "No authorization code received" });
34
+ return;
35
+ }
36
+ try {
37
+ if (!(0, fs_1.existsSync)(PREFS_DIR))
38
+ (0, fs_1.mkdirSync)(PREFS_DIR, { recursive: true });
39
+ const port = server.address().port;
40
+ const redirectUri = `http://localhost:${port}/callback`;
41
+ await exchangeCodeForToken(source, code, clientId, clientSecret, tokenUrl, redirectUri);
42
+ res.writeHead(200, { "Content-Type": "text/html" });
43
+ res.end("<html><body><h2>DaoBrew connected! You can close this tab.</h2></body></html>");
44
+ server.close();
45
+ resolve({ status: "connected", source });
46
+ }
47
+ catch (err) {
48
+ res.writeHead(500);
49
+ res.end("Token exchange failed");
50
+ server.close();
51
+ resolve({ status: "error", source, error: err.message });
52
+ }
53
+ }
54
+ });
55
+ server.listen(0, () => {
56
+ const port = server.address().port;
57
+ const redirectUri = `http://localhost:${port}/callback`;
58
+ const fullAuthUrl = `${authUrl}?client_id=${encodeURIComponent(clientId)}&redirect_uri=${encodeURIComponent(redirectUri)}&scope=${encodeURIComponent(scopes.join(" "))}&response_type=code`;
59
+ // Use execFile (no shell) to avoid command injection
60
+ const openCmd = process.platform === "darwin" ? "open" : "xdg-open";
61
+ (0, child_process_1.execFile)(openCmd, [fullAuthUrl], () => { });
62
+ });
63
+ // Timeout after 2 minutes
64
+ setTimeout(() => {
65
+ server.close();
66
+ resolve({ status: "cancelled", source, error: "OAuth flow timed out (2 min)" });
67
+ }, 120000);
68
+ });
69
+ }
@@ -0,0 +1,14 @@
1
+ export interface OuraToken {
2
+ access_token: string;
3
+ refresh_token: string;
4
+ expires_at: number;
5
+ token_type: string;
6
+ }
7
+ export declare function isConnected(): boolean;
8
+ export declare function loadToken(): OuraToken | null;
9
+ export declare function saveToken(token: OuraToken): void;
10
+ export declare function exchangeCode(code: string, clientId: string, clientSecret: string, redirectUri: string): Promise<OuraToken>;
11
+ export declare function refreshAccessToken(token: OuraToken): Promise<OuraToken>;
12
+ export declare function fetchDailyReadiness(token: OuraToken, startDate?: string, endDate?: string): Promise<any>;
13
+ export declare function fetchHeartRate(token: OuraToken, startDate?: string, endDate?: string): Promise<any>;
14
+ export declare function fetchSleep(token: OuraToken, startDate?: string, endDate?: string): Promise<any>;
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isConnected = isConnected;
4
+ exports.loadToken = loadToken;
5
+ exports.saveToken = saveToken;
6
+ exports.exchangeCode = exchangeCode;
7
+ exports.refreshAccessToken = refreshAccessToken;
8
+ exports.fetchDailyReadiness = fetchDailyReadiness;
9
+ exports.fetchHeartRate = fetchHeartRate;
10
+ exports.fetchSleep = fetchSleep;
11
+ const fs_1 = require("fs");
12
+ const path_1 = require("path");
13
+ const os_1 = require("os");
14
+ const PREFS_DIR = (0, path_1.join)((0, os_1.homedir)(), ".daobrew");
15
+ const TOKEN_FILE = (0, path_1.join)(PREFS_DIR, "oura-token.json");
16
+ const OURA_API_BASE = "https://api.ouraring.com/v2/usercollection";
17
+ function isConnected() {
18
+ return (0, fs_1.existsSync)(TOKEN_FILE);
19
+ }
20
+ function loadToken() {
21
+ if (!(0, fs_1.existsSync)(TOKEN_FILE))
22
+ return null;
23
+ try {
24
+ return JSON.parse((0, fs_1.readFileSync)(TOKEN_FILE, "utf-8"));
25
+ }
26
+ catch {
27
+ return null;
28
+ }
29
+ }
30
+ function saveToken(token) {
31
+ if (!(0, fs_1.existsSync)(PREFS_DIR))
32
+ (0, fs_1.mkdirSync)(PREFS_DIR, { recursive: true });
33
+ (0, fs_1.writeFileSync)(TOKEN_FILE, JSON.stringify(token, null, 2));
34
+ }
35
+ async function exchangeCode(code, clientId, clientSecret, redirectUri) {
36
+ const response = await fetch("https://api.ouraring.com/oauth/token", {
37
+ method: "POST",
38
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
39
+ body: new URLSearchParams({
40
+ grant_type: "authorization_code",
41
+ code,
42
+ client_id: clientId,
43
+ client_secret: clientSecret,
44
+ redirect_uri: redirectUri,
45
+ }),
46
+ });
47
+ if (!response.ok) {
48
+ throw new Error(`Oura token exchange failed: ${response.status}`);
49
+ }
50
+ const data = await response.json();
51
+ const token = {
52
+ access_token: data.access_token,
53
+ refresh_token: data.refresh_token,
54
+ expires_at: Date.now() + (data.expires_in ?? 86400) * 1000,
55
+ token_type: data.token_type ?? "Bearer",
56
+ };
57
+ saveToken(token);
58
+ return token;
59
+ }
60
+ async function refreshAccessToken(token) {
61
+ const clientId = process.env.DAOBREW_OURA_CLIENT_ID ?? "";
62
+ const clientSecret = process.env.DAOBREW_OURA_CLIENT_SECRET ?? "";
63
+ if (!clientId || !clientSecret) {
64
+ throw new Error("Cannot refresh Oura token: DAOBREW_OURA_CLIENT_ID/SECRET not set");
65
+ }
66
+ const response = await fetch("https://api.ouraring.com/oauth/token", {
67
+ method: "POST",
68
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
69
+ body: new URLSearchParams({
70
+ grant_type: "refresh_token",
71
+ refresh_token: token.refresh_token,
72
+ client_id: clientId,
73
+ client_secret: clientSecret,
74
+ }),
75
+ });
76
+ if (!response.ok) {
77
+ throw new Error(`Oura token refresh failed: ${response.status}`);
78
+ }
79
+ const data = await response.json();
80
+ const newToken = {
81
+ access_token: data.access_token,
82
+ refresh_token: data.refresh_token ?? token.refresh_token,
83
+ expires_at: Date.now() + (data.expires_in ?? 86400) * 1000,
84
+ token_type: data.token_type ?? "Bearer",
85
+ };
86
+ saveToken(newToken);
87
+ return newToken;
88
+ }
89
+ async function fetchDailyReadiness(token, startDate, endDate) {
90
+ const params = new URLSearchParams();
91
+ if (startDate)
92
+ params.set("start_date", startDate);
93
+ if (endDate)
94
+ params.set("end_date", endDate);
95
+ const qs = params.toString() ? `?${params.toString()}` : "";
96
+ const response = await fetch(`${OURA_API_BASE}/daily_readiness${qs}`, {
97
+ headers: { Authorization: `Bearer ${token.access_token}` },
98
+ });
99
+ if (!response.ok)
100
+ throw new Error(`Oura API error: ${response.status}`);
101
+ return response.json();
102
+ }
103
+ async function fetchHeartRate(token, startDate, endDate) {
104
+ const params = new URLSearchParams();
105
+ if (startDate)
106
+ params.set("start_datetime", startDate);
107
+ if (endDate)
108
+ params.set("end_datetime", endDate);
109
+ const qs = params.toString() ? `?${params.toString()}` : "";
110
+ const response = await fetch(`${OURA_API_BASE}/heartrate${qs}`, {
111
+ headers: { Authorization: `Bearer ${token.access_token}` },
112
+ });
113
+ if (!response.ok)
114
+ throw new Error(`Oura API error: ${response.status}`);
115
+ return response.json();
116
+ }
117
+ async function fetchSleep(token, startDate, endDate) {
118
+ const params = new URLSearchParams();
119
+ if (startDate)
120
+ params.set("start_date", startDate);
121
+ if (endDate)
122
+ params.set("end_date", endDate);
123
+ const qs = params.toString() ? `?${params.toString()}` : "";
124
+ const response = await fetch(`${OURA_API_BASE}/sleep${qs}`, {
125
+ headers: { Authorization: `Bearer ${token.access_token}` },
126
+ });
127
+ if (!response.ok)
128
+ throw new Error(`Oura API error: ${response.status}`);
129
+ return response.json();
130
+ }
@@ -0,0 +1,7 @@
1
+ import { DaoBrewClient } from "../client.js";
2
+ export interface SyncResult {
3
+ source: string;
4
+ samples_pushed: number;
5
+ error?: string;
6
+ }
7
+ export declare function syncAllConnectedSources(client: DaoBrewClient): Promise<SyncResult[]>;
@@ -0,0 +1,194 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.syncAllConnectedSources = syncAllConnectedSources;
37
+ const oura = __importStar(require("./oura.js"));
38
+ const googleFit = __importStar(require("./google-fit.js"));
39
+ async function syncAllConnectedSources(client) {
40
+ const results = [];
41
+ if (oura.isConnected()) {
42
+ results.push(await syncOura(client));
43
+ }
44
+ if (googleFit.isConnected()) {
45
+ results.push(await syncGoogleFit(client));
46
+ }
47
+ return results;
48
+ }
49
+ async function syncOura(client) {
50
+ let token = oura.loadToken();
51
+ if (!token)
52
+ return { source: "oura", samples_pushed: 0, error: "No token found" };
53
+ try {
54
+ // Refresh token if expired
55
+ if (Date.now() >= token.expires_at) {
56
+ token = await oura.refreshAccessToken(token);
57
+ }
58
+ const samples = [];
59
+ // Use last 4 hours for heart rate, last 2 days for sleep/readiness
60
+ const now = new Date();
61
+ const fourHoursAgo = new Date(now.getTime() - 4 * 60 * 60 * 1000);
62
+ const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000);
63
+ const hrStart = fourHoursAgo.toISOString();
64
+ const hrEnd = now.toISOString();
65
+ const sleepStart = twoDaysAgo.toISOString().slice(0, 10);
66
+ const sleepEnd = now.toISOString().slice(0, 10);
67
+ // Fetch heart rate
68
+ const hrData = await oura.fetchHeartRate(token, hrStart, hrEnd);
69
+ if (hrData?.data) {
70
+ for (const d of hrData.data) {
71
+ if (d.bpm != null && d.timestamp) {
72
+ samples.push({
73
+ metric_type: "heart_rate",
74
+ value: d.bpm,
75
+ unit: "bpm",
76
+ start_time: d.timestamp,
77
+ end_time: d.timestamp,
78
+ source: "oura",
79
+ });
80
+ }
81
+ }
82
+ }
83
+ // Fetch sleep — extract HRV, resting HR, sleep duration
84
+ const sleepData = await oura.fetchSleep(token, sleepStart, sleepEnd);
85
+ if (sleepData?.data) {
86
+ for (const s of sleepData.data) {
87
+ if (s.average_hrv != null && s.bedtime_start && s.bedtime_end) {
88
+ samples.push({
89
+ metric_type: "heart_rate_variability",
90
+ value: s.average_hrv,
91
+ unit: "ms",
92
+ start_time: s.bedtime_start,
93
+ end_time: s.bedtime_end,
94
+ source: "oura",
95
+ });
96
+ }
97
+ if (s.lowest_heart_rate != null && s.bedtime_start && s.bedtime_end) {
98
+ samples.push({
99
+ metric_type: "resting_heart_rate",
100
+ value: s.lowest_heart_rate,
101
+ unit: "bpm",
102
+ start_time: s.bedtime_start,
103
+ end_time: s.bedtime_end,
104
+ source: "oura",
105
+ });
106
+ }
107
+ if (s.total_sleep_duration != null && s.bedtime_start && s.bedtime_end) {
108
+ samples.push({
109
+ metric_type: "sleep_analysis",
110
+ value: s.total_sleep_duration,
111
+ unit: "seconds",
112
+ start_time: s.bedtime_start,
113
+ end_time: s.bedtime_end,
114
+ source: "oura",
115
+ });
116
+ }
117
+ }
118
+ }
119
+ // Fetch readiness — extract body temp deviation
120
+ const readiness = await oura.fetchDailyReadiness(token, sleepStart, sleepEnd);
121
+ if (readiness?.data) {
122
+ for (const r of readiness.data) {
123
+ if (r.temperature_deviation != null && r.day) {
124
+ samples.push({
125
+ metric_type: "body_temperature",
126
+ value: 36.8 + r.temperature_deviation,
127
+ unit: "celsius",
128
+ start_time: r.day + "T00:00:00Z",
129
+ end_time: r.day + "T23:59:59Z",
130
+ source: "oura",
131
+ });
132
+ }
133
+ }
134
+ }
135
+ if (samples.length === 0) {
136
+ return { source: "oura", samples_pushed: 0 };
137
+ }
138
+ const result = await client.pushHealthSamples(samples);
139
+ return { source: "oura", samples_pushed: result.samples_received };
140
+ }
141
+ catch (err) {
142
+ return { source: "oura", samples_pushed: 0, error: err.message };
143
+ }
144
+ }
145
+ async function syncGoogleFit(client) {
146
+ let token = googleFit.loadToken();
147
+ if (!token)
148
+ return { source: "google_fit", samples_pushed: 0, error: "No token found" };
149
+ try {
150
+ // Refresh token if expired
151
+ if (Date.now() >= token.expires_at) {
152
+ const clientId = process.env.DAOBREW_GOOGLE_CLIENT_ID ?? "";
153
+ const clientSecret = process.env.DAOBREW_GOOGLE_CLIENT_SECRET ?? "";
154
+ if (clientId && clientSecret) {
155
+ token = await googleFit.refreshAccessToken(token, clientId, clientSecret);
156
+ }
157
+ }
158
+ const samples = [];
159
+ const now = Date.now();
160
+ const fourHoursAgo = now - 4 * 60 * 60 * 1000;
161
+ // Fetch heart rate via aggregate API
162
+ const hrData = await googleFit.fetchHeartRate(token, fourHoursAgo, now);
163
+ if (hrData?.bucket) {
164
+ for (const bucket of hrData.bucket) {
165
+ for (const dataset of bucket.dataset ?? []) {
166
+ for (const point of dataset.point ?? []) {
167
+ for (const val of point.value ?? []) {
168
+ if (val.fpVal != null) {
169
+ const startMs = parseInt(point.startTimeNanos) / 1_000_000;
170
+ const endMs = parseInt(point.endTimeNanos) / 1_000_000;
171
+ samples.push({
172
+ metric_type: "heart_rate",
173
+ value: val.fpVal,
174
+ unit: "bpm",
175
+ start_time: new Date(startMs).toISOString(),
176
+ end_time: new Date(endMs).toISOString(),
177
+ source: "google_fit",
178
+ });
179
+ }
180
+ }
181
+ }
182
+ }
183
+ }
184
+ }
185
+ if (samples.length === 0) {
186
+ return { source: "google_fit", samples_pushed: 0 };
187
+ }
188
+ const result = await client.pushHealthSamples(samples);
189
+ return { source: "google_fit", samples_pushed: result.samples_received };
190
+ }
191
+ catch (err) {
192
+ return { source: "google_fit", samples_pushed: 0, error: err.message };
193
+ }
194
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};