babylitics-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 (58) hide show
  1. package/README.md +124 -0
  2. package/dist/auth/device-flow.d.ts +4 -0
  3. package/dist/auth/device-flow.d.ts.map +1 -0
  4. package/dist/auth/device-flow.js +133 -0
  5. package/dist/auth/device-flow.js.map +1 -0
  6. package/dist/auth/token-store.d.ts +11 -0
  7. package/dist/auth/token-store.d.ts.map +1 -0
  8. package/dist/auth/token-store.js +39 -0
  9. package/dist/auth/token-store.js.map +1 -0
  10. package/dist/config.d.ts +12 -0
  11. package/dist/config.d.ts.map +1 -0
  12. package/dist/config.js +20 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/graphql/client.d.ts +9 -0
  15. package/dist/graphql/client.d.ts.map +1 -0
  16. package/dist/graphql/client.js +30 -0
  17. package/dist/graphql/client.js.map +1 -0
  18. package/dist/graphql/queries.d.ts +17 -0
  19. package/dist/graphql/queries.d.ts.map +1 -0
  20. package/dist/graphql/queries.js +309 -0
  21. package/dist/graphql/queries.js.map +1 -0
  22. package/dist/index.d.ts +3 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +47 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/tools/babies.d.ts +3 -0
  27. package/dist/tools/babies.d.ts.map +1 -0
  28. package/dist/tools/babies.js +17 -0
  29. package/dist/tools/babies.js.map +1 -0
  30. package/dist/tools/dashboard.d.ts +3 -0
  31. package/dist/tools/dashboard.d.ts.map +1 -0
  32. package/dist/tools/dashboard.js +24 -0
  33. package/dist/tools/dashboard.js.map +1 -0
  34. package/dist/tools/feedings.d.ts +4 -0
  35. package/dist/tools/feedings.d.ts.map +1 -0
  36. package/dist/tools/feedings.js +90 -0
  37. package/dist/tools/feedings.js.map +1 -0
  38. package/dist/tools/helpers.d.ts +19 -0
  39. package/dist/tools/helpers.d.ts.map +1 -0
  40. package/dist/tools/helpers.js +40 -0
  41. package/dist/tools/helpers.js.map +1 -0
  42. package/dist/tools/lengths.d.ts +4 -0
  43. package/dist/tools/lengths.d.ts.map +1 -0
  44. package/dist/tools/lengths.js +54 -0
  45. package/dist/tools/lengths.js.map +1 -0
  46. package/dist/tools/sleep.d.ts +4 -0
  47. package/dist/tools/sleep.d.ts.map +1 -0
  48. package/dist/tools/sleep.js +71 -0
  49. package/dist/tools/sleep.js.map +1 -0
  50. package/dist/tools/temperatures.d.ts +4 -0
  51. package/dist/tools/temperatures.d.ts.map +1 -0
  52. package/dist/tools/temperatures.js +54 -0
  53. package/dist/tools/temperatures.js.map +1 -0
  54. package/dist/tools/weights.d.ts +4 -0
  55. package/dist/tools/weights.d.ts.map +1 -0
  56. package/dist/tools/weights.js +70 -0
  57. package/dist/tools/weights.js.map +1 -0
  58. package/package.json +51 -0
package/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # babylitics-mcp
2
+
3
+ MCP (Model Context Protocol) server for [Babylitics](https://babylitics.io) — connect AI agents to your baby tracking data.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npx babylitics-mcp
9
+ ```
10
+
11
+ ## Setup
12
+
13
+ ### Claude Desktop
14
+
15
+ Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json`):
16
+
17
+ ```json
18
+ {
19
+ "mcpServers": {
20
+ "babylitics": {
21
+ "command": "npx",
22
+ "args": ["babylitics-mcp"]
23
+ }
24
+ }
25
+ }
26
+ ```
27
+
28
+ ### Claude Code
29
+
30
+ ```bash
31
+ claude mcp add babylitics -- npx babylitics-mcp
32
+ ```
33
+
34
+ ### ChatGPT Desktop
35
+
36
+ Add to your ChatGPT Desktop config (`%APPDATA%\ChatGPT\config.json` on Windows or `~/Library/Application Support/ChatGPT/config.json` on macOS):
37
+
38
+ ```json
39
+ {
40
+ "mcpServers": {
41
+ "babylitics": {
42
+ "command": "npx",
43
+ "args": ["babylitics-mcp"]
44
+ }
45
+ }
46
+ }
47
+ ```
48
+
49
+ ### Cursor
50
+
51
+ Add to your Cursor MCP settings:
52
+
53
+ ```json
54
+ {
55
+ "mcpServers": {
56
+ "babylitics": {
57
+ "command": "npx",
58
+ "args": ["babylitics-mcp"]
59
+ }
60
+ }
61
+ }
62
+ ```
63
+
64
+ ## Authorization
65
+
66
+ On first launch, the server will prompt you to authorize via your browser:
67
+
68
+ ```
69
+ [babylitics-mcp] Authorization required.
70
+
71
+ Visit: https://babylitics.io/device
72
+ Enter code: ABCD-1234
73
+
74
+ [babylitics-mcp] Waiting for authorization...
75
+ [babylitics-mcp] Authorized successfully!
76
+ ```
77
+
78
+ Tokens are cached locally at `~/.babylitics/tokens.json` and refresh automatically.
79
+
80
+ ## Available Tools
81
+
82
+ ### Read Tools (always available)
83
+
84
+ | Tool | Description |
85
+ |------|-------------|
86
+ | `list_babies` | List all tracked babies |
87
+ | `get_dashboard` | Today's summary (last feeding, sleep, etc.) |
88
+ | `get_feedings` | Feeding records with optional date filter |
89
+ | `get_feeding_stats` | Feeding statistics and averages |
90
+ | `get_sleeps` | Sleep records with optional date filter |
91
+ | `get_sleep_stats` | Sleep statistics and averages |
92
+ | `get_weights` | Weight records with optional date filter |
93
+ | `get_weight_vs_guideline` | Weight compared to WHO growth standards |
94
+ | `get_lengths` | Length/height records with optional date filter |
95
+ | `get_temperatures` | Temperature records with optional date filter |
96
+
97
+ ### Write Tools (requires `read:write` scope)
98
+
99
+ | Tool | Description |
100
+ |------|-------------|
101
+ | `log_feeding` | Log a formula, breast, or solid feeding |
102
+ | `log_sleep` | Log a nap or night sleep session |
103
+ | `log_weight` | Log a weight measurement |
104
+ | `log_length` | Log a length/height measurement |
105
+ | `log_temperature` | Log a temperature measurement |
106
+
107
+ ## Configuration
108
+
109
+ | Environment Variable | Default | Description |
110
+ |---------------------|---------|-------------|
111
+ | `BABYLITICS_API_URL` | `https://api.babylitics.io/graphql` | API base URL |
112
+
113
+ ## Development
114
+
115
+ ```bash
116
+ npm install
117
+ npm run build
118
+ npm run dev # watch mode
119
+ npm test
120
+ ```
121
+
122
+ ## License
123
+
124
+ MIT
@@ -0,0 +1,4 @@
1
+ export declare function authenticate(): Promise<string>;
2
+ export declare function getAccessToken(): Promise<string>;
3
+ export declare function getScope(): string;
4
+ //# sourceMappingURL=device-flow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-flow.d.ts","sourceRoot":"","sources":["../../src/auth/device-flow.ts"],"names":[],"mappings":"AAsHA,wBAAsB,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,CAwDpD;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,CAYtD;AAED,wBAAgB,QAAQ,IAAI,MAAM,CAGjC"}
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.authenticate = authenticate;
4
+ exports.getAccessToken = getAccessToken;
5
+ exports.getScope = getScope;
6
+ const config_js_1 = require("../config.js");
7
+ const token_store_js_1 = require("./token-store.js");
8
+ function log(message) {
9
+ // MCP servers communicate via stdout, so logs go to stderr
10
+ process.stderr.write(`[babylitics-mcp] ${message}\n`);
11
+ }
12
+ async function requestDeviceCode() {
13
+ const url = `${config_js_1.CONFIG.apiUrl}${config_js_1.CONFIG.deviceFlow.authorizePath}`;
14
+ const response = await fetch(url, {
15
+ method: "POST",
16
+ headers: { "Content-Type": "application/json" },
17
+ });
18
+ if (!response.ok) {
19
+ throw new Error(`Failed to request device code: ${response.status} ${response.statusText}`);
20
+ }
21
+ return (await response.json());
22
+ }
23
+ async function pollForToken(deviceCode, interval) {
24
+ const url = `${config_js_1.CONFIG.apiUrl}${config_js_1.CONFIG.deviceFlow.tokenPath}`;
25
+ while (true) {
26
+ await new Promise((resolve) => setTimeout(resolve, interval * 1000));
27
+ const response = await fetch(url, {
28
+ method: "POST",
29
+ headers: { "Content-Type": "application/json" },
30
+ body: JSON.stringify({
31
+ device_code: deviceCode,
32
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
33
+ }),
34
+ });
35
+ if (response.ok) {
36
+ return (await response.json());
37
+ }
38
+ const error = (await response.json());
39
+ if (error.error === "authorization_pending") {
40
+ continue;
41
+ }
42
+ if (error.error === "slow_down") {
43
+ interval += 5;
44
+ continue;
45
+ }
46
+ if (error.error === "expired_token") {
47
+ throw new Error("Device code expired. Please try again.");
48
+ }
49
+ if (error.error === "access_denied") {
50
+ throw new Error("Authorization denied by user.");
51
+ }
52
+ throw new Error(`Token request failed: ${error.error} — ${error.error_description || ""}`);
53
+ }
54
+ }
55
+ async function refreshAccessToken(refreshToken) {
56
+ const url = `${config_js_1.CONFIG.apiUrl}${config_js_1.CONFIG.deviceFlow.tokenPath}`;
57
+ try {
58
+ const response = await fetch(url, {
59
+ method: "POST",
60
+ headers: { "Content-Type": "application/json" },
61
+ body: JSON.stringify({
62
+ grant_type: "refresh_token",
63
+ refresh_token: refreshToken,
64
+ }),
65
+ });
66
+ if (!response.ok) {
67
+ return null;
68
+ }
69
+ return (await response.json());
70
+ }
71
+ catch {
72
+ return null;
73
+ }
74
+ }
75
+ async function authenticate() {
76
+ // Try cached tokens first
77
+ const cached = (0, token_store_js_1.loadTokens)();
78
+ if (cached) {
79
+ if (!(0, token_store_js_1.isTokenExpired)(cached)) {
80
+ return cached.access_token;
81
+ }
82
+ // Try to refresh
83
+ log("Access token expired, refreshing...");
84
+ const refreshed = await refreshAccessToken(cached.refresh_token);
85
+ if (refreshed) {
86
+ const tokens = {
87
+ access_token: refreshed.access_token,
88
+ refresh_token: refreshed.refresh_token,
89
+ expires_at: Math.floor(Date.now() / 1000) + refreshed.expires_in,
90
+ scope: refreshed.scope,
91
+ };
92
+ (0, token_store_js_1.saveTokens)(tokens);
93
+ log("Token refreshed successfully.");
94
+ return tokens.access_token;
95
+ }
96
+ // Refresh failed — clear and re-auth
97
+ log("Refresh token expired or revoked. Re-authorizing...");
98
+ (0, token_store_js_1.clearTokens)();
99
+ }
100
+ // Start device flow
101
+ log("Authorization required.");
102
+ const device = await requestDeviceCode();
103
+ log("");
104
+ log(` Visit: ${device.verification_uri}`);
105
+ log(` Enter code: ${device.user_code}`);
106
+ log("");
107
+ log("Waiting for authorization...");
108
+ const tokenResponse = await pollForToken(device.device_code, device.interval || config_js_1.CONFIG.deviceFlow.pollIntervalMs / 1000);
109
+ const tokens = {
110
+ access_token: tokenResponse.access_token,
111
+ refresh_token: tokenResponse.refresh_token,
112
+ expires_at: Math.floor(Date.now() / 1000) + tokenResponse.expires_in,
113
+ scope: tokenResponse.scope,
114
+ };
115
+ (0, token_store_js_1.saveTokens)(tokens);
116
+ log("Authorized successfully!");
117
+ return tokens.access_token;
118
+ }
119
+ async function getAccessToken() {
120
+ const cached = (0, token_store_js_1.loadTokens)();
121
+ if (!cached) {
122
+ return authenticate();
123
+ }
124
+ if ((0, token_store_js_1.isTokenExpired)(cached)) {
125
+ return authenticate();
126
+ }
127
+ return cached.access_token;
128
+ }
129
+ function getScope() {
130
+ const cached = (0, token_store_js_1.loadTokens)();
131
+ return cached?.scope || "read";
132
+ }
133
+ //# sourceMappingURL=device-flow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-flow.js","sourceRoot":"","sources":["../../src/auth/device-flow.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export interface StoredTokens {
2
+ access_token: string;
3
+ refresh_token: string;
4
+ expires_at: number;
5
+ scope: string;
6
+ }
7
+ export declare function loadTokens(): StoredTokens | null;
8
+ export declare function saveTokens(tokens: StoredTokens): void;
9
+ export declare function clearTokens(): void;
10
+ export declare function isTokenExpired(tokens: StoredTokens): boolean;
11
+ //# sourceMappingURL=token-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-store.d.ts","sourceRoot":"","sources":["../../src/auth/token-store.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,UAAU,IAAI,YAAY,GAAG,IAAI,CAOhD;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAKrD;AAED,wBAAgB,WAAW,IAAI,IAAI,CAMlC;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAG5D"}
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.loadTokens = loadTokens;
7
+ exports.saveTokens = saveTokens;
8
+ exports.clearTokens = clearTokens;
9
+ exports.isTokenExpired = isTokenExpired;
10
+ const node_fs_1 = __importDefault(require("node:fs"));
11
+ const config_js_1 = require("../config.js");
12
+ function loadTokens() {
13
+ try {
14
+ const data = node_fs_1.default.readFileSync(config_js_1.CONFIG.tokenFile, "utf-8");
15
+ return JSON.parse(data);
16
+ }
17
+ catch {
18
+ return null;
19
+ }
20
+ }
21
+ function saveTokens(tokens) {
22
+ node_fs_1.default.mkdirSync(config_js_1.CONFIG.tokenDir, { recursive: true, mode: 0o700 });
23
+ node_fs_1.default.writeFileSync(config_js_1.CONFIG.tokenFile, JSON.stringify(tokens, null, 2), {
24
+ mode: 0o600,
25
+ });
26
+ }
27
+ function clearTokens() {
28
+ try {
29
+ node_fs_1.default.unlinkSync(config_js_1.CONFIG.tokenFile);
30
+ }
31
+ catch {
32
+ // File may not exist
33
+ }
34
+ }
35
+ function isTokenExpired(tokens) {
36
+ // Consider expired if within 60 seconds of expiry
37
+ return Date.now() / 1000 >= tokens.expires_at - 60;
38
+ }
39
+ //# sourceMappingURL=token-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-store.js","sourceRoot":"","sources":["../../src/auth/token-store.ts"],"names":[],"mappings":";;;;;AAWA,gCAOC;AAED,gCAKC;AAED,kCAMC;AAED,wCAGC;AAtCD,sDAAyB;AAEzB,4CAAsC;AAStC,SAAgB,UAAU;IACxB,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,iBAAE,CAAC,YAAY,CAAC,kBAAM,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACxD,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAiB,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAgB,UAAU,CAAC,MAAoB;IAC7C,iBAAE,CAAC,SAAS,CAAC,kBAAM,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAChE,iBAAE,CAAC,aAAa,CAAC,kBAAM,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE;QAClE,IAAI,EAAE,KAAK;KACZ,CAAC,CAAC;AACL,CAAC;AAED,SAAgB,WAAW;IACzB,IAAI,CAAC;QACH,iBAAE,CAAC,UAAU,CAAC,kBAAM,CAAC,SAAS,CAAC,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,qBAAqB;IACvB,CAAC;AACH,CAAC;AAED,SAAgB,cAAc,CAAC,MAAoB;IACjD,kDAAkD;IAClD,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,IAAI,MAAM,CAAC,UAAU,GAAG,EAAE,CAAC;AACrD,CAAC"}
@@ -0,0 +1,12 @@
1
+ export declare const CONFIG: {
2
+ readonly apiUrl: string;
3
+ readonly tokenDir: string;
4
+ readonly tokenFile: string;
5
+ readonly deviceFlow: {
6
+ readonly pollIntervalMs: 5000;
7
+ readonly authorizePath: "/oauth/device/authorize";
8
+ readonly tokenPath: "/oauth/device/token";
9
+ };
10
+ readonly graphqlPath: "/graphql";
11
+ };
12
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,MAAM;;;;;;;;;;CAUT,CAAC"}
package/dist/config.js ADDED
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.CONFIG = void 0;
7
+ const node_path_1 = __importDefault(require("node:path"));
8
+ const node_os_1 = __importDefault(require("node:os"));
9
+ exports.CONFIG = {
10
+ apiUrl: process.env.BABYLITICS_API_URL || "https://api.babylitics.io/graphql",
11
+ tokenDir: node_path_1.default.join(node_os_1.default.homedir(), ".babylitics"),
12
+ tokenFile: node_path_1.default.join(node_os_1.default.homedir(), ".babylitics", "tokens.json"),
13
+ deviceFlow: {
14
+ pollIntervalMs: 5000,
15
+ authorizePath: "/oauth/device/authorize",
16
+ tokenPath: "/oauth/device/token",
17
+ },
18
+ graphqlPath: "/graphql",
19
+ };
20
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":";;;;;;AAAA,0DAA6B;AAC7B,sDAAyB;AAEZ,QAAA,MAAM,GAAG;IACpB,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,mCAAmC;IAC7E,QAAQ,EAAE,mBAAI,CAAC,IAAI,CAAC,iBAAE,CAAC,OAAO,EAAE,EAAE,aAAa,CAAC;IAChD,SAAS,EAAE,mBAAI,CAAC,IAAI,CAAC,iBAAE,CAAC,OAAO,EAAE,EAAE,aAAa,EAAE,aAAa,CAAC;IAChE,UAAU,EAAE;QACV,cAAc,EAAE,IAAI;QACpB,aAAa,EAAE,yBAAyB;QACxC,SAAS,EAAE,qBAAqB;KACjC;IACD,WAAW,EAAE,UAAU;CACf,CAAC"}
@@ -0,0 +1,9 @@
1
+ export interface GraphQLResponse<T = unknown> {
2
+ data?: T;
3
+ errors?: Array<{
4
+ message: string;
5
+ path?: string[];
6
+ }>;
7
+ }
8
+ export declare function graphqlRequest<T = unknown>(query: string, variables?: Record<string, unknown>): Promise<T>;
9
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/graphql/client.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,eAAe,CAAC,CAAC,GAAG,OAAO;IAC1C,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;CACtD;AAED,wBAAsB,cAAc,CAAC,CAAC,GAAG,OAAO,EAC9C,KAAK,EAAE,MAAM,EACb,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAClC,OAAO,CAAC,CAAC,CAAC,CA+BZ"}
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.graphqlRequest = graphqlRequest;
4
+ const config_js_1 = require("../config.js");
5
+ const device_flow_js_1 = require("../auth/device-flow.js");
6
+ async function graphqlRequest(query, variables) {
7
+ const token = await (0, device_flow_js_1.getAccessToken)();
8
+ const url = `${config_js_1.CONFIG.apiUrl}${config_js_1.CONFIG.graphqlPath}`;
9
+ const response = await fetch(url, {
10
+ method: "POST",
11
+ headers: {
12
+ "Content-Type": "application/json",
13
+ Authorization: `Bearer ${token}`,
14
+ },
15
+ body: JSON.stringify({ query, variables }),
16
+ });
17
+ if (!response.ok) {
18
+ throw new Error(`GraphQL request failed: ${response.status} ${response.statusText}`);
19
+ }
20
+ const result = (await response.json());
21
+ if (result.errors && result.errors.length > 0) {
22
+ const messages = result.errors.map((e) => e.message).join("; ");
23
+ throw new Error(`GraphQL errors: ${messages}`);
24
+ }
25
+ if (!result.data) {
26
+ throw new Error("GraphQL response contained no data");
27
+ }
28
+ return result.data;
29
+ }
30
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/graphql/client.ts"],"names":[],"mappings":";;AAQA,wCAkCC;AA1CD,4CAAsC;AACtC,2DAAwD;AAOjD,KAAK,UAAU,cAAc,CAClC,KAAa,EACb,SAAmC;IAEnC,MAAM,KAAK,GAAG,MAAM,IAAA,+BAAc,GAAE,CAAC;IACrC,MAAM,GAAG,GAAG,GAAG,kBAAM,CAAC,MAAM,GAAG,kBAAM,CAAC,WAAW,EAAE,CAAC;IAEpD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;QAChC,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACP,cAAc,EAAE,kBAAkB;YAClC,aAAa,EAAE,UAAU,KAAK,EAAE;SACjC;QACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;KAC3C,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CACb,2BAA2B,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CACpE,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAuB,CAAC;IAE7D,IAAI,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9C,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChE,MAAM,IAAI,KAAK,CAAC,mBAAmB,QAAQ,EAAE,CAAC,CAAC;IACjD,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;IACxD,CAAC;IAED,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC"}
@@ -0,0 +1,17 @@
1
+ export declare const LIST_BABIES = "\n query ListBabies {\n babies {\n edges {\n node {\n id\n name\n date_of_birth\n gender\n birth_weight\n birth_weight_unit\n birth_height\n birth_height_unit\n age_in_days\n age_in_weeks\n age_display\n }\n }\n }\n }\n";
2
+ export declare const DASHBOARD_STATS = "\n query DashboardStats($baby_id: ID!) {\n dashboard_stats(baby_id: $baby_id) {\n today {\n total_feedings\n total_amount\n }\n last_feeding {\n feeding_type\n fed_at\n amount\n food_name\n }\n last_sleep {\n sleep_type\n started_at\n ended_at\n duration_minutes\n }\n in_progress_sleep {\n sleep_type\n started_at\n }\n weekly_average {\n average_daily_feedings\n average_daily_amount\n }\n today_breakdown {\n formula\n breast\n solid\n }\n }\n }\n";
3
+ export declare const GET_FEEDINGS = "\n query GetFeedings($baby_id: ID!, $start_date: ISO8601Date, $end_date: ISO8601Date) {\n feedings(baby_id: $baby_id, start_date: $start_date, end_date: $end_date) {\n id\n feeding_type\n amount\n duration_minutes\n side\n food_name\n reaction\n notes\n fed_at\n }\n }\n";
4
+ export declare const FEEDING_STATISTICS = "\n query FeedingStatistics($baby_id: ID!, $start_date: ISO8601Date, $end_date: ISO8601Date) {\n feeding_statistics(baby_id: $baby_id, start_date: $start_date, end_date: $end_date) {\n total_feedings\n average_daily_feedings\n total_by_type {\n feeding_type\n count\n total_amount\n average_amount\n }\n }\n }\n";
5
+ export declare const GET_SLEEPS = "\n query GetSleeps($baby_id: ID!, $start_date: ISO8601Date, $end_date: ISO8601Date) {\n sleeps(baby_id: $baby_id, start_date: $start_date, end_date: $end_date) {\n id\n sleep_type\n started_at\n ended_at\n duration_minutes\n notes\n }\n }\n";
6
+ export declare const SLEEP_STATISTICS = "\n query SleepStatistics($baby_id: ID!) {\n sleep_statistics(baby_id: $baby_id) {\n total_sleep_minutes\n average_daily_sleep_minutes\n total_naps\n total_night_sleeps\n average_nap_duration\n average_night_duration\n }\n }\n";
7
+ export declare const GET_WEIGHTS = "\n query GetWeights($baby_id: ID!, $start_date: ISO8601Date, $end_date: ISO8601Date) {\n weights(baby_id: $baby_id, start_date: $start_date, end_date: $end_date) {\n id\n weight_kg\n measured_at\n notes\n }\n }\n";
8
+ export declare const WEIGHT_VS_GUIDELINE = "\n query WeightVsGuideline($baby_id: ID!) {\n weight_vs_guideline(baby_id: $baby_id) {\n latest_weight\n guideline_median\n guideline_sd1_neg\n guideline_sd1_pos\n guideline_sd2_neg\n guideline_sd2_pos\n percentile\n }\n }\n";
9
+ export declare const GET_LENGTHS = "\n query GetLengths($baby_id: ID!, $start_date: ISO8601Date, $end_date: ISO8601Date) {\n lengths(baby_id: $baby_id, start_date: $start_date, end_date: $end_date) {\n id\n length_cm\n measured_at\n notes\n }\n }\n";
10
+ export declare const GET_TEMPERATURES = "\n query GetTemperatures($baby_id: ID!, $start_date: ISO8601Date, $end_date: ISO8601Date) {\n temperatures(baby_id: $baby_id, start_date: $start_date, end_date: $end_date) {\n id\n temperature_c\n measured_at\n notes\n }\n }\n";
11
+ export declare const CREATE_FEEDING = "\n mutation CreateFeeding(\n $baby_id: ID!,\n $feeding_type: FeedingTypeEnum!,\n $amount: Float,\n $unit: UnitEnum,\n $duration_minutes: Int,\n $side: SideEnum,\n $food_name: String,\n $reaction: ReactionEnum,\n $notes: String,\n $fed_at: ISO8601DateTime\n ) {\n create_feeding(\n baby_id: $baby_id,\n feeding_type: $feeding_type,\n amount: $amount,\n unit: $unit,\n duration_minutes: $duration_minutes,\n side: $side,\n food_name: $food_name,\n reaction: $reaction,\n notes: $notes,\n fed_at: $fed_at\n ) {\n feeding {\n id\n feeding_type\n amount\n fed_at\n }\n errors\n }\n }\n";
12
+ export declare const CREATE_SLEEP = "\n mutation CreateSleep(\n $baby_id: ID!,\n $sleep_type: SleepTypeEnum!,\n $started_at: ISO8601DateTime!,\n $ended_at: ISO8601DateTime,\n $notes: String\n ) {\n create_sleep(\n baby_id: $baby_id,\n sleep_type: $sleep_type,\n started_at: $started_at,\n ended_at: $ended_at,\n notes: $notes\n ) {\n sleep {\n id\n sleep_type\n started_at\n ended_at\n }\n errors\n }\n }\n";
13
+ export declare const CREATE_WEIGHT = "\n mutation CreateWeight(\n $baby_id: ID!,\n $weight: Float!,\n $unit: WeightUnitEnum!,\n $measured_at: ISO8601Date!,\n $notes: String\n ) {\n create_weight(\n baby_id: $baby_id,\n weight: $weight,\n unit: $unit,\n measured_at: $measured_at,\n notes: $notes\n ) {\n weight {\n id\n weight_kg\n measured_at\n }\n errors\n }\n }\n";
14
+ export declare const CREATE_LENGTH = "\n mutation CreateLength(\n $baby_id: ID!,\n $length: Float!,\n $unit: LengthUnitEnum!,\n $measured_at: ISO8601Date!,\n $notes: String\n ) {\n create_length(\n baby_id: $baby_id,\n length: $length,\n unit: $unit,\n measured_at: $measured_at,\n notes: $notes\n ) {\n length {\n id\n length_cm\n measured_at\n }\n errors\n }\n }\n";
15
+ export declare const CREATE_TEMPERATURE = "\n mutation CreateTemperature(\n $baby_id: ID!,\n $temperature: Float!,\n $unit: TemperatureUnitEnum!,\n $measured_at: ISO8601DateTime!,\n $notes: String\n ) {\n create_temperature(\n baby_id: $baby_id,\n temperature: $temperature,\n unit: $unit,\n measured_at: $measured_at,\n notes: $notes\n ) {\n temperature {\n id\n temperature_c\n measured_at\n }\n errors\n }\n }\n";
16
+ export declare const GET_USER_SETTINGS = "\n query GetUserSettings {\n me {\n user_setting {\n preferred_unit\n preferred_weight_unit\n preferred_length_unit\n preferred_temperature_unit\n preferred_language\n }\n }\n }\n";
17
+ //# sourceMappingURL=queries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queries.d.ts","sourceRoot":"","sources":["../../src/graphql/queries.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,WAAW,mWAoBvB,CAAC;AAEF,eAAO,MAAM,eAAe,koBAkC3B,CAAC;AAGF,eAAO,MAAM,YAAY,wUAcxB,CAAC;AAEF,eAAO,MAAM,kBAAkB,mXAa9B,CAAC;AAGF,eAAO,MAAM,UAAU,2RAWtB,CAAC;AAEF,eAAO,MAAM,gBAAgB,4QAW5B,CAAC;AAGF,eAAO,MAAM,WAAW,qPASvB,CAAC;AAEF,eAAO,MAAM,mBAAmB,gRAY/B,CAAC;AAGF,eAAO,MAAM,WAAW,qPASvB,CAAC;AAGF,eAAO,MAAM,gBAAgB,mQAS5B,CAAC;AAGF,eAAO,MAAM,cAAc,gtBAkC1B,CAAC;AAEF,eAAO,MAAM,YAAY,qdAwBxB,CAAC;AAEF,eAAO,MAAM,aAAa,qaAuBzB,CAAC;AAEF,eAAO,MAAM,aAAa,qaAuBzB,CAAC;AAEF,eAAO,MAAM,kBAAkB,gdAuB9B,CAAC;AAGF,eAAO,MAAM,iBAAiB,6OAY7B,CAAC"}