@milldr/crono 0.4.1 → 0.7.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.
- package/README.md +115 -13
- package/dist/automation/client.d.ts +5 -0
- package/dist/automation/client.d.ts.map +1 -0
- package/dist/automation/client.js +13 -0
- package/dist/automation/client.js.map +1 -0
- package/dist/automation/runner.d.ts +3 -0
- package/dist/automation/runner.d.ts.map +1 -0
- package/dist/automation/runner.js +150 -0
- package/dist/automation/runner.js.map +1 -0
- package/dist/automation/types.d.ts +75 -0
- package/dist/automation/types.d.ts.map +1 -0
- package/dist/automation/types.js +2 -0
- package/dist/automation/types.js.map +1 -0
- package/dist/commands/add.js +3 -3
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/biometrics.d.ts +10 -0
- package/dist/commands/biometrics.d.ts.map +1 -0
- package/dist/commands/biometrics.js +81 -0
- package/dist/commands/biometrics.js.map +1 -0
- package/dist/commands/diary.d.ts +1 -1
- package/dist/commands/diary.d.ts.map +1 -1
- package/dist/commands/diary.js +3 -3
- package/dist/commands/diary.js.map +1 -1
- package/dist/commands/export.d.ts +1 -0
- package/dist/commands/export.d.ts.map +1 -1
- package/dist/commands/export.js +45 -3
- package/dist/commands/export.js.map +1 -1
- package/dist/commands/log.js +3 -3
- package/dist/commands/log.js.map +1 -1
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +53 -26
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/quick-add.js +3 -3
- package/dist/commands/quick-add.js.map +1 -1
- package/dist/commands/recipes.d.ts +5 -0
- package/dist/commands/recipes.d.ts.map +1 -0
- package/dist/commands/recipes.js +35 -0
- package/dist/commands/recipes.js.map +1 -0
- package/dist/commands/weight.d.ts.map +1 -1
- package/dist/commands/weight.js +3 -3
- package/dist/commands/weight.js.map +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +6 -0
- package/dist/config.js.map +1 -1
- package/dist/cronometer/export.d.ts +1 -1
- package/dist/cronometer/export.d.ts.map +1 -1
- package/dist/cronometer/export.js +1 -0
- package/dist/cronometer/export.js.map +1 -1
- package/dist/cronometer/parse.d.ts +14 -0
- package/dist/cronometer/parse.d.ts.map +1 -1
- package/dist/cronometer/parse.js +62 -0
- package/dist/cronometer/parse.js.map +1 -1
- package/dist/index.js +23 -1
- package/dist/index.js.map +1 -1
- package/dist/kernel/add-custom-food.d.ts.map +1 -1
- package/dist/kernel/add-custom-food.js +6 -138
- package/dist/kernel/add-custom-food.js.map +1 -1
- package/dist/kernel/biometrics.d.ts +20 -0
- package/dist/kernel/biometrics.d.ts.map +1 -0
- package/dist/kernel/biometrics.js +171 -0
- package/dist/kernel/biometrics.js.map +1 -0
- package/dist/kernel/client.d.ts +4 -56
- package/dist/kernel/client.d.ts.map +1 -1
- package/dist/kernel/client.js +29 -274
- package/dist/kernel/client.js.map +1 -1
- package/dist/kernel/food-dialog.d.ts +13 -0
- package/dist/kernel/food-dialog.d.ts.map +1 -0
- package/dist/kernel/food-dialog.js +294 -0
- package/dist/kernel/food-dialog.js.map +1 -0
- package/dist/kernel/log-food.d.ts.map +1 -1
- package/dist/kernel/log-food.js +2 -186
- package/dist/kernel/log-food.js.map +1 -1
- package/dist/kernel/login.d.ts +1 -1
- package/dist/kernel/login.d.ts.map +1 -1
- package/dist/kernel/login.js +34 -4
- package/dist/kernel/login.js.map +1 -1
- package/dist/kernel/quick-add.d.ts.map +1 -1
- package/dist/kernel/quick-add.js +9 -195
- package/dist/kernel/quick-add.js.map +1 -1
- package/dist/kernel/recipes.d.ts +10 -0
- package/dist/kernel/recipes.d.ts.map +1 -0
- package/dist/kernel/recipes.js +38 -0
- package/dist/kernel/recipes.js.map +1 -0
- package/dist/playwright/client.d.ts +6 -0
- package/dist/playwright/client.d.ts.map +1 -0
- package/dist/playwright/client.js +41 -0
- package/dist/playwright/client.js.map +1 -0
- package/package.json +3 -2
package/dist/cronometer/parse.js
CHANGED
|
@@ -123,6 +123,68 @@ export function parseExercises(csv) {
|
|
|
123
123
|
}
|
|
124
124
|
return entries;
|
|
125
125
|
}
|
|
126
|
+
export function parseServings(csv) {
|
|
127
|
+
const rows = parseCSV(csv);
|
|
128
|
+
if (rows.length < 2)
|
|
129
|
+
return [];
|
|
130
|
+
const headers = rows[0];
|
|
131
|
+
const dayIdx = colIndex(headers, "Day");
|
|
132
|
+
const timeIdx = colIndex(headers, "Time");
|
|
133
|
+
const groupIdx = colIndex(headers, "Group");
|
|
134
|
+
const foodIdx = colIndex(headers, "Food Name");
|
|
135
|
+
const amountIdx = colIndex(headers, "Amount");
|
|
136
|
+
const energyIdx = colIndex(headers, "Energy (kcal)");
|
|
137
|
+
const proteinIdx = colIndex(headers, "Protein (g)");
|
|
138
|
+
const carbsIdx = colIndex(headers, "Carbs (g)");
|
|
139
|
+
const fatIdx = colIndex(headers, "Fat (g)");
|
|
140
|
+
const categoryIdx = colIndex(headers, "Category");
|
|
141
|
+
if (dayIdx === -1)
|
|
142
|
+
return [];
|
|
143
|
+
const entries = [];
|
|
144
|
+
for (let i = 1; i < rows.length; i++) {
|
|
145
|
+
const row = rows[i];
|
|
146
|
+
const date = row[dayIdx]?.trim();
|
|
147
|
+
if (!date)
|
|
148
|
+
continue;
|
|
149
|
+
const entry = {
|
|
150
|
+
date,
|
|
151
|
+
time: row[timeIdx]?.trim() ?? "",
|
|
152
|
+
meal: row[groupIdx]?.trim() ?? "",
|
|
153
|
+
food: row[foodIdx]?.trim() ?? "",
|
|
154
|
+
amount: row[amountIdx]?.trim() ?? "",
|
|
155
|
+
calories: num(row[energyIdx]),
|
|
156
|
+
protein: num(row[proteinIdx]),
|
|
157
|
+
carbs: num(row[carbsIdx]),
|
|
158
|
+
fat: num(row[fatIdx]),
|
|
159
|
+
category: row[categoryIdx]?.trim() ?? "",
|
|
160
|
+
};
|
|
161
|
+
// Include all other nutrient columns for full JSON output
|
|
162
|
+
const surfacedIdxs = new Set([
|
|
163
|
+
dayIdx,
|
|
164
|
+
timeIdx,
|
|
165
|
+
groupIdx,
|
|
166
|
+
foodIdx,
|
|
167
|
+
amountIdx,
|
|
168
|
+
energyIdx,
|
|
169
|
+
proteinIdx,
|
|
170
|
+
carbsIdx,
|
|
171
|
+
fatIdx,
|
|
172
|
+
categoryIdx,
|
|
173
|
+
]);
|
|
174
|
+
for (let j = 0; j < headers.length; j++) {
|
|
175
|
+
if (surfacedIdxs.has(j))
|
|
176
|
+
continue;
|
|
177
|
+
const key = headers[j].trim();
|
|
178
|
+
const val = row[j]?.trim() ?? "";
|
|
179
|
+
if (key && val !== "") {
|
|
180
|
+
const parsed = parseFloat(val);
|
|
181
|
+
entry[key] = isNaN(parsed) ? val : parsed;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
entries.push(entry);
|
|
185
|
+
}
|
|
186
|
+
return entries;
|
|
187
|
+
}
|
|
126
188
|
export function parseBiometrics(csv) {
|
|
127
189
|
const rows = parseCSV(csv);
|
|
128
190
|
if (rows.length < 2)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parse.js","sourceRoot":"","sources":["../../src/cronometer/parse.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"parse.js","sourceRoot":"","sources":["../../src/cronometer/parse.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,8 @@ import { log } from "./commands/log.js";
|
|
|
8
8
|
import { diary } from "./commands/diary.js";
|
|
9
9
|
import { weight } from "./commands/weight.js";
|
|
10
10
|
import { exportCmd } from "./commands/export.js";
|
|
11
|
+
import { recipes } from "./commands/recipes.js";
|
|
12
|
+
import { biometrics } from "./commands/biometrics.js";
|
|
11
13
|
const require = createRequire(import.meta.url);
|
|
12
14
|
const { version } = require("../package.json");
|
|
13
15
|
const program = new Command();
|
|
@@ -74,13 +76,33 @@ program
|
|
|
74
76
|
});
|
|
75
77
|
program
|
|
76
78
|
.command("export <type>")
|
|
77
|
-
.description("Export data from Cronometer (nutrition, exercises, biometrics)")
|
|
79
|
+
.description("Export data from Cronometer (nutrition, exercises, biometrics, servings)")
|
|
78
80
|
.option("-d, --date <date>", "Date (YYYY-MM-DD)")
|
|
79
81
|
.option("-r, --range <range>", "Range (7d, 30d, or YYYY-MM-DD:YYYY-MM-DD)")
|
|
82
|
+
.option("-m, --meal <name>", "Filter servings by meal (Breakfast, Lunch, Dinner, Snacks)")
|
|
80
83
|
.option("--csv", "Output as CSV")
|
|
81
84
|
.option("--json", "Output as JSON")
|
|
82
85
|
.action(async (type, options) => {
|
|
83
86
|
await exportCmd(type, options);
|
|
84
87
|
});
|
|
88
|
+
program
|
|
89
|
+
.command("recipes")
|
|
90
|
+
.description("List your custom recipes from Cronometer")
|
|
91
|
+
.option("--json", "Output as JSON")
|
|
92
|
+
.action(async (options) => {
|
|
93
|
+
await recipes(options);
|
|
94
|
+
});
|
|
95
|
+
program
|
|
96
|
+
.command("biometrics")
|
|
97
|
+
.description("Log biometrics to Cronometer (weight, body fat, blood pressure)")
|
|
98
|
+
.option("-w, --weight <value>", "Weight value", parseFloat)
|
|
99
|
+
.option("-b, --body-fat <value>", "Body fat percentage", parseFloat)
|
|
100
|
+
.option("-s, --systolic <value>", "Systolic blood pressure (mmHg)", parseFloat)
|
|
101
|
+
.option("-d, --diastolic <value>", "Diastolic blood pressure (mmHg)", parseFloat)
|
|
102
|
+
.option("--date <date>", "Date (YYYY-MM-DD, yesterday, -1d)")
|
|
103
|
+
.option("-u, --unit <unit>", "Weight unit (kg or lbs)", "kg")
|
|
104
|
+
.action(async (options) => {
|
|
105
|
+
await biometrics(options);
|
|
106
|
+
});
|
|
85
107
|
program.parse();
|
|
86
108
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AACxC,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"add-custom-food.d.ts","sourceRoot":"","sources":["../../src/kernel/add-custom-food.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"add-custom-food.d.ts","sourceRoot":"","sources":["../../src/kernel/add-custom-food.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGnD;;GAEG;AACH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAKlD,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CA6KrE"}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* kernel.browsers.playwright.execute(). The code has access to
|
|
6
6
|
* `page`, `context`, and `browser` from the Playwright environment.
|
|
7
7
|
*/
|
|
8
|
+
import { buildFoodDialogCode } from "./food-dialog.js";
|
|
8
9
|
/**
|
|
9
10
|
* Nutrient labels as they appear in Cronometer's nutrition facts editor.
|
|
10
11
|
*/
|
|
@@ -171,7 +172,7 @@ export function buildAddCustomFoodCode(entry) {
|
|
|
171
172
|
if (!saveClicked) {
|
|
172
173
|
return { success: false, error: 'Could not find "Save Changes" button' };
|
|
173
174
|
}
|
|
174
|
-
await page.waitForTimeout(
|
|
175
|
+
await page.waitForTimeout(15000);
|
|
175
176
|
|
|
176
177
|
// If --log is set, continue to log the food to diary
|
|
177
178
|
if (mealLabel) {
|
|
@@ -180,143 +181,10 @@ export function buildAddCustomFoodCode(entry) {
|
|
|
180
181
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
181
182
|
await page.waitForTimeout(500);
|
|
182
183
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const el = page.locator(sel);
|
|
188
|
-
if (await el.count() > 0) {
|
|
189
|
-
await el.first().click({ button: 'right' });
|
|
190
|
-
return true;
|
|
191
|
-
}
|
|
192
|
-
} catch {}
|
|
193
|
-
}
|
|
194
|
-
return false;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Right-click meal category with retry (GWT context menus can be flaky)
|
|
198
|
-
let menuVisible = false;
|
|
199
|
-
for (let attempt = 0; attempt < 3 && !menuVisible; attempt++) {
|
|
200
|
-
if (attempt > 0) {
|
|
201
|
-
await page.keyboard.press('Escape');
|
|
202
|
-
await page.mouse.click(1, 1);
|
|
203
|
-
await page.waitForTimeout(1000);
|
|
204
|
-
}
|
|
205
|
-
const mealClicked = await rightClickFirst([
|
|
206
|
-
'text="' + mealLabel + '"',
|
|
207
|
-
':has-text("' + mealLabel + '")',
|
|
208
|
-
], 'meal category');
|
|
209
|
-
if (!mealClicked) {
|
|
210
|
-
return { success: false, error: 'Food created but could not find meal category "' + mealLabel + '" in diary' };
|
|
211
|
-
}
|
|
212
|
-
menuVisible = await page.waitForSelector('text="Add Food..."', { timeout: 3000 })
|
|
213
|
-
.then(() => true)
|
|
214
|
-
.catch(() => page.waitForSelector('text="Add Food"', { timeout: 2000 }).then(() => true).catch(() => false));
|
|
215
|
-
}
|
|
216
|
-
if (!menuVisible) {
|
|
217
|
-
return { success: false, error: 'Food created but context menu did not appear after right-clicking "' + mealLabel + '"' };
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Click "Add Food..." in context menu
|
|
221
|
-
const addFoodClicked = await clickFirst([
|
|
222
|
-
'text="Add Food..."',
|
|
223
|
-
'text="Add Food\u2026"',
|
|
224
|
-
'text="Add Food"',
|
|
225
|
-
'[role="menuitem"]:has-text("Add Food")',
|
|
226
|
-
], 'Add Food menu item');
|
|
227
|
-
if (!addFoodClicked) {
|
|
228
|
-
return { success: false, error: 'Food created but could not find "Add Food" in context menu' };
|
|
229
|
-
}
|
|
230
|
-
await page.waitForTimeout(200);
|
|
231
|
-
|
|
232
|
-
// Wait for "Add Food to Diary" dialog
|
|
233
|
-
try {
|
|
234
|
-
await page.waitForSelector('text="Add Food to Diary"', { timeout: 5000 });
|
|
235
|
-
} catch {
|
|
236
|
-
return { success: false, error: 'Food created but Add Food to Diary dialog did not appear' };
|
|
237
|
-
}
|
|
238
|
-
await page.waitForTimeout(300);
|
|
239
|
-
|
|
240
|
-
// Search for the just-created food
|
|
241
|
-
const searchSelectors = [
|
|
242
|
-
'input[placeholder*="Search all foods" i]',
|
|
243
|
-
'input[placeholder*="Search" i]',
|
|
244
|
-
'input[placeholder*="food" i]',
|
|
245
|
-
'input.gwt-TextBox',
|
|
246
|
-
'input[type="text"]',
|
|
247
|
-
'input[type="search"]',
|
|
248
|
-
];
|
|
249
|
-
let searched = false;
|
|
250
|
-
for (const sel of searchSelectors) {
|
|
251
|
-
try {
|
|
252
|
-
const el = page.locator(sel);
|
|
253
|
-
if (await el.count() > 0) {
|
|
254
|
-
await el.first().click();
|
|
255
|
-
await page.waitForTimeout(200);
|
|
256
|
-
await el.first().fill('');
|
|
257
|
-
await page.keyboard.type(foodName, { delay: 50 });
|
|
258
|
-
searched = true;
|
|
259
|
-
break;
|
|
260
|
-
}
|
|
261
|
-
} catch {}
|
|
262
|
-
}
|
|
263
|
-
if (!searched) {
|
|
264
|
-
return { success: false, error: 'Food created but could not find search bar in Add Food dialog' };
|
|
265
|
-
}
|
|
266
|
-
await page.waitForTimeout(300);
|
|
267
|
-
|
|
268
|
-
// Click SEARCH
|
|
269
|
-
await clickFirst([
|
|
270
|
-
'text="SEARCH"',
|
|
271
|
-
'button:has-text("SEARCH")',
|
|
272
|
-
'button:has-text("Search")',
|
|
273
|
-
], 'SEARCH button');
|
|
274
|
-
await page.waitForSelector('td:has-text("' + foodName + '")', { timeout: 8000 }).catch(() => {});
|
|
275
|
-
|
|
276
|
-
// Select the search result
|
|
277
|
-
const resultSelectors = [
|
|
278
|
-
'td:has-text("' + foodName + '")',
|
|
279
|
-
'tr:has-text("' + foodName + '") td',
|
|
280
|
-
'.gwt-HTML:has-text("' + foodName + '")',
|
|
281
|
-
'div:has-text("' + foodName + '"):not(:has(input))',
|
|
282
|
-
];
|
|
283
|
-
let resultClicked = false;
|
|
284
|
-
for (const sel of resultSelectors) {
|
|
285
|
-
try {
|
|
286
|
-
const el = page.locator(sel);
|
|
287
|
-
if (await el.count() > 0) {
|
|
288
|
-
await el.first().click();
|
|
289
|
-
resultClicked = true;
|
|
290
|
-
break;
|
|
291
|
-
}
|
|
292
|
-
} catch {}
|
|
293
|
-
}
|
|
294
|
-
if (!resultClicked) {
|
|
295
|
-
return { success: false, error: 'Food created but no search result found for "' + foodName + '"' };
|
|
296
|
-
}
|
|
297
|
-
await page.waitForTimeout(200);
|
|
298
|
-
|
|
299
|
-
// Wait for serving size panel
|
|
300
|
-
try {
|
|
301
|
-
await page.waitForSelector('text="Serving Size"', { timeout: 5000 });
|
|
302
|
-
} catch {
|
|
303
|
-
return { success: false, error: 'Food created but Serving Size panel did not appear' };
|
|
304
|
-
}
|
|
305
|
-
await page.waitForTimeout(500);
|
|
306
|
-
|
|
307
|
-
// Click "ADD TO DIARY"
|
|
308
|
-
const addClicked = await clickFirst([
|
|
309
|
-
'button:has-text("ADD TO DIARY")',
|
|
310
|
-
'button:has-text("Add to Diary")',
|
|
311
|
-
'text="ADD TO DIARY"',
|
|
312
|
-
'text="Add to Diary"',
|
|
313
|
-
'button[type="submit"]',
|
|
314
|
-
], 'ADD TO DIARY button');
|
|
315
|
-
if (!addClicked) {
|
|
316
|
-
return { success: false, error: 'Food created but could not find "Add to Diary" button' };
|
|
317
|
-
}
|
|
318
|
-
await page.waitForSelector('text="Add Food to Diary"', { state: 'hidden', timeout: 8000 }).catch(() => {});
|
|
319
|
-
await page.waitForTimeout(300);
|
|
184
|
+
${buildFoodDialogCode({
|
|
185
|
+
errorPrefix: "Food created but ",
|
|
186
|
+
requireServingSize: false,
|
|
187
|
+
})}
|
|
320
188
|
}
|
|
321
189
|
|
|
322
190
|
return { success: true };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"add-custom-food.js","sourceRoot":"","sources":["../../src/kernel/add-custom-food.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;
|
|
1
|
+
{"version":3,"file":"add-custom-food.js","sourceRoot":"","sources":["../../src/kernel/add-custom-food.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAEvD;;GAEG;AACH,MAAM,CAAC,MAAM,eAAe,GAA2B;IACrD,QAAQ,EAAE,UAAU;IACpB,OAAO,EAAE,SAAS;IAClB,KAAK,EAAE,oBAAoB;IAC3B,GAAG,EAAE,WAAW;CACjB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,UAAU,sBAAsB,CAAC,KAAsB;IAC3D,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,KAAK,CAAC;IAE3D,MAAM,OAAO,GACX,GAAG,KAAK,IAAI,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;IACxE,MAAM,SAAS,GAAG,OAAO;QACvB,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE;QAClE,CAAC,CAAC,IAAI,CAAC;IAET,iFAAiF;IACjF,MAAM,aAAa,GACjB,QAAQ,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;IAErE,MAAM,SAAS,GAAuC,EAAE,CAAC;IACzD,SAAS,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,eAAe,CAAC,QAAQ,EAAE,KAAK,EAAE,aAAa,EAAE,CAAC,CAAC;IAC1E,IAAI,OAAO,KAAK,SAAS;QACvB,SAAS,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,eAAe,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;IACrE,IAAI,KAAK,KAAK,SAAS;QACrB,SAAS,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,eAAe,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IACjE,IAAI,GAAG,KAAK,SAAS;QACnB,SAAS,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,eAAe,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;IAE7D,MAAM,aAAa,GAAG,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IAChD,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,aAAa,GAAG,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IAEhD,OAAO;uBACc,QAAQ;wBACP,aAAa;wBACb,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAwInC,mBAAmB,CAAC;QACpB,WAAW,EAAE,mBAAmB;QAChC,kBAAkB,EAAE,KAAK;KAC1B,CAAC;;;;GAIC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Playwright code generator for Cronometer biometrics logging.
|
|
3
|
+
*
|
|
4
|
+
* Supports logging weight, body fat percentage, and blood pressure.
|
|
5
|
+
*/
|
|
6
|
+
export interface BiometricEntry {
|
|
7
|
+
type: "weight" | "bodyfat" | "bloodpressure";
|
|
8
|
+
value?: number;
|
|
9
|
+
unit?: string;
|
|
10
|
+
systolic?: number;
|
|
11
|
+
diastolic?: number;
|
|
12
|
+
date: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Generate Playwright code for logging a biometric to Cronometer.
|
|
16
|
+
* Navigates to the diary, clicks BIOMETRIC button, selects the biometric type,
|
|
17
|
+
* fills in values, and adds to diary.
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildBiometricsCode(entry: BiometricEntry): string;
|
|
20
|
+
//# sourceMappingURL=biometrics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"biometrics.d.ts","sourceRoot":"","sources":["../../src/kernel/biometrics.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,eAAe,CAAC;IAC7C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAsFjE"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Playwright code generator for Cronometer biometrics logging.
|
|
3
|
+
*
|
|
4
|
+
* Supports logging weight, body fat percentage, and blood pressure.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Generate Playwright code for logging a biometric to Cronometer.
|
|
8
|
+
* Navigates to the diary, clicks BIOMETRIC button, selects the biometric type,
|
|
9
|
+
* fills in values, and adds to diary.
|
|
10
|
+
*/
|
|
11
|
+
export function buildBiometricsCode(entry) {
|
|
12
|
+
const { type, value, unit, systolic, diastolic } = entry;
|
|
13
|
+
const biometricName = type === "weight"
|
|
14
|
+
? "Weight"
|
|
15
|
+
: type === "bodyfat"
|
|
16
|
+
? "Body Fat"
|
|
17
|
+
: "Blood Pressure";
|
|
18
|
+
return `
|
|
19
|
+
// Navigate to dashboard first (where the BIOMETRIC button is)
|
|
20
|
+
await page.goto('https://cronometer.com/#dashboard', { waitUntil: 'domcontentloaded', timeout: 15000 });
|
|
21
|
+
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
22
|
+
await page.waitForTimeout(2000);
|
|
23
|
+
|
|
24
|
+
// Check if logged in
|
|
25
|
+
const url = page.url();
|
|
26
|
+
if (url.includes('/login') || url.includes('/signin')) {
|
|
27
|
+
return { success: false, error: 'Not logged in. Login may have failed.' };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Wait for the quick add panel to appear
|
|
31
|
+
try {
|
|
32
|
+
await page.waitForSelector('text="Quick Add to Diary"', { timeout: 10000 });
|
|
33
|
+
} catch {
|
|
34
|
+
return { success: false, error: 'Dashboard quick add panel not found' };
|
|
35
|
+
}
|
|
36
|
+
await page.waitForTimeout(500);
|
|
37
|
+
|
|
38
|
+
// Wait for and click BIOMETRIC button
|
|
39
|
+
try {
|
|
40
|
+
await page.waitForSelector('button:has-text("BIOMETRIC"), button:has-text("Biometric")', { timeout: 10000 });
|
|
41
|
+
} catch {
|
|
42
|
+
return { success: false, error: 'Could not find BIOMETRIC button' };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const biometricButton = page.getByRole('button', { name: /BIOMETRIC/i });
|
|
46
|
+
if (await biometricButton.count() === 0) {
|
|
47
|
+
return { success: false, error: 'BIOMETRIC button not found' };
|
|
48
|
+
}
|
|
49
|
+
await biometricButton.first().click();
|
|
50
|
+
await page.waitForTimeout(1000);
|
|
51
|
+
|
|
52
|
+
// Wait for biometric dialog
|
|
53
|
+
try {
|
|
54
|
+
await page.waitForSelector('text="Add Biometric"', { timeout: 5000 });
|
|
55
|
+
} catch {
|
|
56
|
+
return { success: false, error: 'Add Biometric dialog did not appear' };
|
|
57
|
+
}
|
|
58
|
+
await page.waitForTimeout(300);
|
|
59
|
+
|
|
60
|
+
// Click the biometric type (Weight, Body Fat, or Blood Pressure)
|
|
61
|
+
// Look for the biometric item by finding text with the name and clicking its container
|
|
62
|
+
const biometricItem = page.locator('.biometric-content-panel.pretty-row').filter({ hasText: '${biometricName}' }).first();
|
|
63
|
+
const itemCount = await biometricItem.count();
|
|
64
|
+
|
|
65
|
+
if (itemCount === 0) {
|
|
66
|
+
return { success: false, error: 'Could not find ${biometricName} in biometric list' };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
await biometricItem.click();
|
|
70
|
+
await page.waitForTimeout(1000);
|
|
71
|
+
|
|
72
|
+
${type === "weight" ? buildWeightInputCode(value, unit) : ""}
|
|
73
|
+
${type === "bodyfat" ? buildBodyFatInputCode(value) : ""}
|
|
74
|
+
${type === "bloodpressure" ? buildBloodPressureInputCode(systolic, diastolic) : ""}
|
|
75
|
+
|
|
76
|
+
// Wait for ADD TO DIARY button to become enabled, then click it
|
|
77
|
+
const addButton = page.getByRole('button', { name: /add.*diary/i });
|
|
78
|
+
|
|
79
|
+
// Wait for button to be enabled
|
|
80
|
+
await page.waitForTimeout(500);
|
|
81
|
+
const isEnabled = await addButton.isEnabled().catch(() => false);
|
|
82
|
+
if (!isEnabled) {
|
|
83
|
+
return { success: false, error: 'Add to Diary button is disabled. Value may not have been filled correctly.' };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
await addButton.click();
|
|
87
|
+
|
|
88
|
+
// Wait for dialog to close
|
|
89
|
+
await page.waitForSelector('text="Add Biometric"', { state: 'hidden', timeout: 5000 }).catch(() => {});
|
|
90
|
+
await page.waitForTimeout(500);
|
|
91
|
+
|
|
92
|
+
return { success: true };
|
|
93
|
+
`;
|
|
94
|
+
}
|
|
95
|
+
function buildWeightInputCode(value, unit) {
|
|
96
|
+
return `
|
|
97
|
+
// Fill weight value using getByRole
|
|
98
|
+
const weightInput = page.getByRole('textbox', { name: 'Weight' });
|
|
99
|
+
if (await weightInput.count() === 0) {
|
|
100
|
+
return { success: false, error: 'Could not find weight input field' };
|
|
101
|
+
}
|
|
102
|
+
await weightInput.fill(String(${value}));
|
|
103
|
+
// Press Tab to trigger validation and move focus
|
|
104
|
+
await weightInput.press('Tab');
|
|
105
|
+
await page.waitForTimeout(500);
|
|
106
|
+
|
|
107
|
+
// Select unit if not kg
|
|
108
|
+
if ('${unit}' === 'lbs') {
|
|
109
|
+
const unitButton = page.getByRole('button', { name: /Unit.*kg/i });
|
|
110
|
+
if (await unitButton.count() > 0) {
|
|
111
|
+
await unitButton.click();
|
|
112
|
+
await page.waitForTimeout(300);
|
|
113
|
+
// Select lbs from dropdown
|
|
114
|
+
const lbsOption = page.getByText('lbs', { exact: true });
|
|
115
|
+
if (await lbsOption.count() > 0) {
|
|
116
|
+
await lbsOption.click();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
await page.waitForTimeout(500);
|
|
121
|
+
`;
|
|
122
|
+
}
|
|
123
|
+
function buildBodyFatInputCode(value) {
|
|
124
|
+
return `
|
|
125
|
+
// Fill body fat percentage using getByRole
|
|
126
|
+
const bodyFatInput = page.getByRole('textbox', { name: 'Body Fat' });
|
|
127
|
+
if (await bodyFatInput.count() === 0) {
|
|
128
|
+
return { success: false, error: 'Could not find body fat input field' };
|
|
129
|
+
}
|
|
130
|
+
await bodyFatInput.fill(String(${value}));
|
|
131
|
+
// Press Tab to trigger validation and move focus
|
|
132
|
+
await bodyFatInput.press('Tab');
|
|
133
|
+
await page.waitForTimeout(500);
|
|
134
|
+
`;
|
|
135
|
+
}
|
|
136
|
+
function buildBloodPressureInputCode(systolic, diastolic) {
|
|
137
|
+
return `
|
|
138
|
+
// Fill blood pressure values
|
|
139
|
+
// After clicking "Blood Pressure", there should be exactly 2 visible textbox inputs
|
|
140
|
+
const allTextboxes = page.getByRole('textbox');
|
|
141
|
+
const visibleInputs = [];
|
|
142
|
+
|
|
143
|
+
for (let i = 0; i < await allTextboxes.count(); i++) {
|
|
144
|
+
const input = allTextboxes.nth(i);
|
|
145
|
+
if (await input.isVisible()) {
|
|
146
|
+
const placeholder = await input.getAttribute('placeholder');
|
|
147
|
+
// Skip the search box
|
|
148
|
+
if (!placeholder || !placeholder.toLowerCase().includes('search')) {
|
|
149
|
+
visibleInputs.push(i);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (visibleInputs.length < 2) {
|
|
155
|
+
return { success: false, error: 'Could not find systolic and diastolic input fields. Found ' + visibleInputs.length + ' inputs.' };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Fill systolic (first BP input)
|
|
159
|
+
const systolicInput = allTextboxes.nth(visibleInputs[0]);
|
|
160
|
+
await systolicInput.fill(String(${systolic}));
|
|
161
|
+
await systolicInput.press('Tab');
|
|
162
|
+
await page.waitForTimeout(300);
|
|
163
|
+
|
|
164
|
+
// Fill diastolic (second BP input)
|
|
165
|
+
const diastolicInput = allTextboxes.nth(visibleInputs[1]);
|
|
166
|
+
await diastolicInput.fill(String(${diastolic}));
|
|
167
|
+
await diastolicInput.press('Tab');
|
|
168
|
+
await page.waitForTimeout(500);
|
|
169
|
+
`;
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=biometrics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"biometrics.js","sourceRoot":"","sources":["../../src/kernel/biometrics.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAWH;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAAqB;IACvD,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,KAAK,CAAC;IAEzD,MAAM,aAAa,GACjB,IAAI,KAAK,QAAQ;QACf,CAAC,CAAC,QAAQ;QACV,CAAC,CAAC,IAAI,KAAK,SAAS;YAClB,CAAC,CAAC,UAAU;YACZ,CAAC,CAAC,gBAAgB,CAAC;IAEzB,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;mGA4C0F,aAAa;;;;wDAIxD,aAAa;;;;;;MAM/D,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,oBAAoB,CAAC,KAAM,EAAE,IAAK,CAAC,CAAC,CAAC,CAAC,EAAE;MAC5D,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,qBAAqB,CAAC,KAAM,CAAC,CAAC,CAAC,CAAC,EAAE;MACvD,IAAI,KAAK,eAAe,CAAC,CAAC,CAAC,2BAA2B,CAAC,QAAS,EAAE,SAAU,CAAC,CAAC,CAAC,CAAC,EAAE;;;;;;;;;;;;;;;;;;;GAmBrF,CAAC;AACJ,CAAC;AAED,SAAS,oBAAoB,CAAC,KAAa,EAAE,IAAY;IACvD,OAAO;;;;;;oCAM2B,KAAK;;;;;;WAM9B,IAAI;;;;;;;;;;;;;GAaZ,CAAC;AACJ,CAAC;AAED,SAAS,qBAAqB,CAAC,KAAa;IAC1C,OAAO;;;;;;qCAM4B,KAAK;;;;GAIvC,CAAC;AACJ,CAAC;AAED,SAAS,2BAA2B,CAClC,QAAgB,EAChB,SAAiB;IAEjB,OAAO;;;;;;;;;;;;;;;;;;;;;;;sCAuB6B,QAAQ;;;;;;uCAMP,SAAS;;;GAG7C,CAAC;AACJ,CAAC"}
|
package/dist/kernel/client.d.ts
CHANGED
|
@@ -1,61 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Kernel.sh browser automation
|
|
3
|
-
*
|
|
4
|
-
* Orchestrates the SDK to manage browser sessions, handle login,
|
|
5
|
-
* and execute Playwright automation for diary entries.
|
|
6
|
-
*
|
|
7
|
-
* Each operation creates a fresh browser, logs in, performs the
|
|
8
|
-
* action, and tears down.
|
|
2
|
+
* Kernel.sh browser automation runtime.
|
|
9
3
|
*/
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
fat?: number;
|
|
14
|
-
alcohol?: number;
|
|
15
|
-
meal?: string;
|
|
16
|
-
date?: string;
|
|
17
|
-
}
|
|
18
|
-
export interface WeightData {
|
|
19
|
-
date: string;
|
|
20
|
-
weight: number | null;
|
|
21
|
-
unit: string;
|
|
22
|
-
}
|
|
23
|
-
export interface DiaryData {
|
|
24
|
-
date: string;
|
|
25
|
-
calories: number;
|
|
26
|
-
protein: number;
|
|
27
|
-
carbs: number;
|
|
28
|
-
fat: number;
|
|
29
|
-
targets?: {
|
|
30
|
-
calories: number | null;
|
|
31
|
-
protein: number | null;
|
|
32
|
-
carbs: number | null;
|
|
33
|
-
fat: number | null;
|
|
34
|
-
} | null;
|
|
35
|
-
exercise?: {
|
|
36
|
-
caloriesBurned: number;
|
|
37
|
-
} | null;
|
|
38
|
-
}
|
|
39
|
-
export interface CustomFoodEntry {
|
|
40
|
-
name: string;
|
|
41
|
-
protein?: number;
|
|
42
|
-
carbs?: number;
|
|
43
|
-
fat?: number;
|
|
44
|
-
calories?: number;
|
|
45
|
-
log?: string | boolean;
|
|
46
|
-
}
|
|
47
|
-
export interface LogFoodEntry {
|
|
48
|
-
name: string;
|
|
49
|
-
meal?: string;
|
|
50
|
-
servings?: number;
|
|
51
|
-
}
|
|
52
|
-
export interface KernelClient {
|
|
53
|
-
addQuickEntry(entry: MacroEntry, onStatus?: (msg: string) => void): Promise<void>;
|
|
54
|
-
getWeight(dates: string[], onStatus?: (msg: string) => void): Promise<WeightData[]>;
|
|
55
|
-
getDiary(dates: string[], onStatus?: (msg: string) => void, scrapeTargets?: boolean): Promise<DiaryData[]>;
|
|
56
|
-
addCustomFood(entry: CustomFoodEntry, onStatus?: (msg: string) => void): Promise<void>;
|
|
57
|
-
logFood(entry: LogFoodEntry, onStatus?: (msg: string) => void): Promise<void>;
|
|
58
|
-
}
|
|
4
|
+
import type { AutomationClient, CustomFoodEntry, DiaryData, LogFoodEntry, MacroEntry, WeightData } from "../automation/types.js";
|
|
5
|
+
export type KernelClient = AutomationClient;
|
|
6
|
+
export type { CustomFoodEntry, DiaryData, LogFoodEntry, MacroEntry, WeightData, };
|
|
59
7
|
/**
|
|
60
8
|
* Create a Kernel client for Cronometer automation.
|
|
61
9
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/kernel/client.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/kernel/client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EACV,gBAAgB,EAGhB,eAAe,EACf,SAAS,EACT,YAAY,EACZ,UAAU,EAEV,UAAU,EACX,MAAM,wBAAwB,CAAC;AAGhC,MAAM,MAAM,YAAY,GAAG,gBAAgB,CAAC;AAC5C,YAAY,EACV,eAAe,EACf,SAAS,EACT,YAAY,EACZ,UAAU,EACV,UAAU,GACX,CAAC;AAEF;;;;;GAKG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,YAAY,CAAC,CAe7D"}
|