@milldr/crono 0.1.0 → 0.2.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 (50) hide show
  1. package/README.md +128 -0
  2. package/dist/commands/add.d.ts +9 -0
  3. package/dist/commands/add.d.ts.map +1 -0
  4. package/dist/commands/add.js +65 -0
  5. package/dist/commands/add.js.map +1 -0
  6. package/dist/commands/export.d.ts +8 -0
  7. package/dist/commands/export.d.ts.map +1 -0
  8. package/dist/commands/export.js +142 -0
  9. package/dist/commands/export.js.map +1 -0
  10. package/dist/commands/log.d.ts +6 -0
  11. package/dist/commands/log.d.ts.map +1 -0
  12. package/dist/commands/log.js +40 -0
  13. package/dist/commands/log.js.map +1 -0
  14. package/dist/config.d.ts +2 -0
  15. package/dist/config.d.ts.map +1 -1
  16. package/dist/config.js.map +1 -1
  17. package/dist/cronometer/auth.d.ts +31 -0
  18. package/dist/cronometer/auth.d.ts.map +1 -0
  19. package/dist/cronometer/auth.js +151 -0
  20. package/dist/cronometer/auth.js.map +1 -0
  21. package/dist/cronometer/export.d.ts +22 -0
  22. package/dist/cronometer/export.d.ts.map +1 -0
  23. package/dist/cronometer/export.js +83 -0
  24. package/dist/cronometer/export.js.map +1 -0
  25. package/dist/cronometer/parse.d.ts +35 -0
  26. package/dist/cronometer/parse.d.ts.map +1 -0
  27. package/dist/cronometer/parse.js +158 -0
  28. package/dist/cronometer/parse.js.map +1 -0
  29. package/dist/index.js +33 -0
  30. package/dist/index.js.map +1 -1
  31. package/dist/kernel/add-custom-food.d.ts +22 -0
  32. package/dist/kernel/add-custom-food.d.ts.map +1 -0
  33. package/dist/kernel/add-custom-food.js +314 -0
  34. package/dist/kernel/add-custom-food.js.map +1 -0
  35. package/dist/kernel/client.d.ts +15 -0
  36. package/dist/kernel/client.d.ts.map +1 -1
  37. package/dist/kernel/client.js +92 -1
  38. package/dist/kernel/client.js.map +1 -1
  39. package/dist/kernel/log-food.d.ts +17 -0
  40. package/dist/kernel/log-food.d.ts.map +1 -0
  41. package/dist/kernel/log-food.js +230 -0
  42. package/dist/kernel/log-food.js.map +1 -0
  43. package/dist/kernel/login.d.ts.map +1 -1
  44. package/dist/kernel/login.js +24 -1
  45. package/dist/kernel/login.js.map +1 -1
  46. package/package.json +5 -1
  47. package/dist/debug-nav.d.ts +0 -2
  48. package/dist/debug-nav.d.ts.map +0 -1
  49. package/dist/debug-nav.js +0 -99
  50. package/dist/debug-nav.js.map +0 -1
@@ -0,0 +1,314 @@
1
+ /**
2
+ * Playwright code generator for Cronometer custom food creation.
3
+ *
4
+ * Returns a code string that executes remotely via
5
+ * kernel.browsers.playwright.execute(). The code has access to
6
+ * `page`, `context`, and `browser` from the Playwright environment.
7
+ */
8
+ /**
9
+ * Nutrient labels as they appear in Cronometer's nutrition facts editor.
10
+ */
11
+ export const NUTRIENT_LABELS = {
12
+ calories: "Calories",
13
+ protein: "Protein",
14
+ carbs: "Total Carbohydrate",
15
+ fat: "Total Fat",
16
+ };
17
+ /**
18
+ * Generate Playwright code for creating a custom food in Cronometer.
19
+ *
20
+ * Flow:
21
+ * navigate to #foods → expand sidebar → click Custom Foods →
22
+ * click CREATE FOOD → fill name → click "-" to reveal nutrient inputs →
23
+ * fill macros → Save Changes → optionally log to diary
24
+ */
25
+ export function buildAddCustomFoodCode(entry) {
26
+ const { name, protein, carbs, fat, calories, log } = entry;
27
+ const logMeal = log === true ? "Uncategorized" : typeof log === "string" ? log : null;
28
+ const mealLabel = logMeal
29
+ ? logMeal.charAt(0).toUpperCase() + logMeal.slice(1).toLowerCase()
30
+ : null;
31
+ // Calculate calories: use explicit value or derive from macros (P*4 + C*4 + F*9)
32
+ const totalCalories = calories ?? (protein ?? 0) * 4 + (carbs ?? 0) * 4 + (fat ?? 0) * 9;
33
+ const nutrients = [];
34
+ nutrients.push({ label: NUTRIENT_LABELS.calories, value: totalCalories });
35
+ if (protein !== undefined)
36
+ nutrients.push({ label: NUTRIENT_LABELS.protein, value: protein });
37
+ if (carbs !== undefined)
38
+ nutrients.push({ label: NUTRIENT_LABELS.carbs, value: carbs });
39
+ if (fat !== undefined)
40
+ nutrients.push({ label: NUTRIENT_LABELS.fat, value: fat });
41
+ const nutrientsJson = JSON.stringify(nutrients);
42
+ const foodName = JSON.stringify(name);
43
+ const mealLabelJson = JSON.stringify(mealLabel);
44
+ return `
45
+ const foodName = ${foodName};
46
+ const nutrients = ${nutrientsJson};
47
+ const mealLabel = ${mealLabelJson};
48
+
49
+ // Navigate directly to Custom Foods page
50
+ await page.goto('https://cronometer.com/#custom-foods', { waitUntil: 'domcontentloaded', timeout: 15000 });
51
+ await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
52
+
53
+ // Verify we're logged in
54
+ const url = page.url();
55
+ if (url.includes('/login') || url.includes('/signin')) {
56
+ return { success: false, error: 'Not logged in. Login may have failed.' };
57
+ }
58
+
59
+ // Helper: find and click an element from a list of selectors
60
+ async function clickFirst(selectors, description) {
61
+ for (const sel of selectors) {
62
+ try {
63
+ const el = page.locator(sel);
64
+ if (await el.count() > 0) {
65
+ await el.first().click();
66
+ return true;
67
+ }
68
+ } catch {}
69
+ }
70
+ return false;
71
+ }
72
+
73
+ await page.waitForTimeout(2000);
74
+
75
+ // Click "CREATE FOOD" button
76
+ const createClicked = await clickFirst([
77
+ 'button:has-text("CREATE FOOD")',
78
+ 'text="CREATE FOOD"',
79
+ ], 'CREATE FOOD button');
80
+ if (!createClicked) {
81
+ return { success: false, error: 'Could not find "CREATE FOOD" button on Custom Foods page' };
82
+ }
83
+ await page.waitForTimeout(3000);
84
+
85
+ // Fill in the food name using keyboard.type() for GWT compatibility.
86
+ // Find the visible input.text-box via evaluate (many hidden inputs on page).
87
+ const nameClicked = await page.evaluate(() => {
88
+ const inputs = document.querySelectorAll('input.text-box');
89
+ for (const inp of inputs) {
90
+ if (inp.offsetParent !== null) {
91
+ inp.focus();
92
+ inp.select();
93
+ return true;
94
+ }
95
+ }
96
+ return false;
97
+ });
98
+ if (nameClicked) {
99
+ await page.waitForTimeout(200);
100
+ await page.keyboard.type(foodName, { delay: 50 });
101
+ } else {
102
+ return { success: false, error: 'Could not find food name input field' };
103
+ }
104
+ await page.waitForTimeout(500);
105
+
106
+ // Enter macro values in the nutrition facts label
107
+ // Each nutrient row has a display div showing "-" and a hidden input.
108
+ // Clicking "-" reveals the input, then we fill it.
109
+ for (const nutrient of nutrients) {
110
+ // Step 1: Click the "-" display div to reveal the hidden input
111
+ const revealed = await page.evaluate((label) => {
112
+ const rows = document.querySelectorAll('tr');
113
+ for (const row of rows) {
114
+ const nameDiv = row.querySelector('div');
115
+ if (nameDiv && nameDiv.textContent?.trim() === label && nameDiv.offsetParent !== null) {
116
+ const valueDivs = row.querySelectorAll('div');
117
+ for (const div of valueDivs) {
118
+ if (div.textContent?.trim() === '-' && div.offsetParent !== null) {
119
+ div.click();
120
+ return true;
121
+ }
122
+ }
123
+ }
124
+ }
125
+ return false;
126
+ }, nutrient.label);
127
+
128
+ if (!revealed) {
129
+ return { success: false, error: 'Could not find nutrient row for "' + nutrient.label + '"' };
130
+ }
131
+ await page.waitForTimeout(500);
132
+
133
+ // Step 2: Fill the now-visible input in the same row
134
+ const filled = await page.evaluate(({ label, value }) => {
135
+ const rows = document.querySelectorAll('tr');
136
+ for (const row of rows) {
137
+ const nameDiv = row.querySelector('div');
138
+ if (nameDiv && nameDiv.textContent?.trim() === label && nameDiv.offsetParent !== null) {
139
+ const inputs = row.querySelectorAll('input');
140
+ for (const inp of inputs) {
141
+ if (inp.offsetParent !== null) {
142
+ inp.focus();
143
+ const nativeSetter = Object.getOwnPropertyDescriptor(
144
+ window.HTMLInputElement.prototype, 'value'
145
+ ).set;
146
+ nativeSetter.call(inp, String(value));
147
+ inp.dispatchEvent(new Event('input', { bubbles: true }));
148
+ inp.dispatchEvent(new Event('change', { bubbles: true }));
149
+ inp.dispatchEvent(new Event('blur', { bubbles: true }));
150
+ return true;
151
+ }
152
+ }
153
+ }
154
+ }
155
+ return false;
156
+ }, nutrient);
157
+
158
+ if (!filled) {
159
+ return { success: false, error: 'Could not fill input for "' + nutrient.label + '"' };
160
+ }
161
+ await page.waitForTimeout(300);
162
+ }
163
+
164
+ // Click "Save Changes" button (appears after edits are made)
165
+ await page.waitForTimeout(500);
166
+ const saveClicked = await clickFirst([
167
+ 'button:has-text("Save Changes")',
168
+ 'button:has-text("Save")',
169
+ 'button:has-text("SAVE")',
170
+ ], 'Save Changes button');
171
+ if (!saveClicked) {
172
+ return { success: false, error: 'Could not find "Save Changes" button' };
173
+ }
174
+ await page.waitForTimeout(1000);
175
+
176
+ // If --log is set, continue to log the food to diary
177
+ if (mealLabel) {
178
+ // Navigate to diary
179
+ await page.goto('https://cronometer.com/#diary', { waitUntil: 'domcontentloaded', timeout: 15000 });
180
+ await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
181
+ await page.waitForTimeout(500);
182
+
183
+ // Helper: right-click an element from a list of selectors
184
+ async function rightClickFirst(selectors, description) {
185
+ for (const sel of selectors) {
186
+ try {
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 the meal category
198
+ const mealClicked = await rightClickFirst([
199
+ 'text="' + mealLabel + '"',
200
+ ':has-text("' + mealLabel + '")',
201
+ ], 'meal category');
202
+ if (!mealClicked) {
203
+ return { success: false, error: 'Food created but could not find meal category "' + mealLabel + '" in diary' };
204
+ }
205
+ await page.waitForSelector('text="Add Food..."', { timeout: 3000 }).catch(() =>
206
+ page.waitForSelector('text="Add Food"', { timeout: 2000 }).catch(() => {})
207
+ );
208
+
209
+ // Click "Add Food..." in context menu
210
+ const addFoodClicked = await clickFirst([
211
+ 'text="Add Food..."',
212
+ 'text="Add Food\u2026"',
213
+ 'text="Add Food"',
214
+ '[role="menuitem"]:has-text("Add Food")',
215
+ ], 'Add Food menu item');
216
+ if (!addFoodClicked) {
217
+ return { success: false, error: 'Food created but could not find "Add Food" in context menu' };
218
+ }
219
+ await page.waitForTimeout(200);
220
+
221
+ // Wait for "Add Food to Diary" dialog
222
+ try {
223
+ await page.waitForSelector('text="Add Food to Diary"', { timeout: 5000 });
224
+ } catch {
225
+ return { success: false, error: 'Food created but Add Food to Diary dialog did not appear' };
226
+ }
227
+ await page.waitForTimeout(300);
228
+
229
+ // Search for the just-created food
230
+ const searchSelectors = [
231
+ 'input[placeholder*="Search all foods" i]',
232
+ 'input[placeholder*="Search" i]',
233
+ 'input[placeholder*="food" i]',
234
+ 'input.gwt-TextBox',
235
+ 'input[type="text"]',
236
+ 'input[type="search"]',
237
+ ];
238
+ let searched = false;
239
+ for (const sel of searchSelectors) {
240
+ try {
241
+ const el = page.locator(sel);
242
+ if (await el.count() > 0) {
243
+ await el.first().click();
244
+ await page.waitForTimeout(200);
245
+ await el.first().fill('');
246
+ await page.keyboard.type(foodName, { delay: 50 });
247
+ searched = true;
248
+ break;
249
+ }
250
+ } catch {}
251
+ }
252
+ if (!searched) {
253
+ return { success: false, error: 'Food created but could not find search bar in Add Food dialog' };
254
+ }
255
+ await page.waitForTimeout(300);
256
+
257
+ // Click SEARCH
258
+ await clickFirst([
259
+ 'text="SEARCH"',
260
+ 'button:has-text("SEARCH")',
261
+ 'button:has-text("Search")',
262
+ ], 'SEARCH button');
263
+ await page.waitForSelector('td:has-text("' + foodName + '")', { timeout: 8000 }).catch(() => {});
264
+
265
+ // Select the search result
266
+ const resultSelectors = [
267
+ 'td:has-text("' + foodName + '")',
268
+ 'tr:has-text("' + foodName + '") td',
269
+ '.gwt-HTML:has-text("' + foodName + '")',
270
+ 'div:has-text("' + foodName + '"):not(:has(input))',
271
+ ];
272
+ let resultClicked = false;
273
+ for (const sel of resultSelectors) {
274
+ try {
275
+ const el = page.locator(sel);
276
+ if (await el.count() > 0) {
277
+ await el.first().click();
278
+ resultClicked = true;
279
+ break;
280
+ }
281
+ } catch {}
282
+ }
283
+ if (!resultClicked) {
284
+ return { success: false, error: 'Food created but no search result found for "' + foodName + '"' };
285
+ }
286
+ await page.waitForTimeout(200);
287
+
288
+ // Wait for serving size panel
289
+ try {
290
+ await page.waitForSelector('text="Serving Size"', { timeout: 5000 });
291
+ } catch {
292
+ return { success: false, error: 'Food created but Serving Size panel did not appear' };
293
+ }
294
+ await page.waitForTimeout(500);
295
+
296
+ // Click "ADD TO DIARY"
297
+ const addClicked = await clickFirst([
298
+ 'button:has-text("ADD TO DIARY")',
299
+ 'button:has-text("Add to Diary")',
300
+ 'text="ADD TO DIARY"',
301
+ 'text="Add to Diary"',
302
+ 'button[type="submit"]',
303
+ ], 'ADD TO DIARY button');
304
+ if (!addClicked) {
305
+ return { success: false, error: 'Food created but could not find "Add to Diary" button' };
306
+ }
307
+ await page.waitForSelector('text="Add Food to Diary"', { state: 'hidden', timeout: 8000 }).catch(() => {});
308
+ await page.waitForTimeout(300);
309
+ }
310
+
311
+ return { success: true };
312
+ `;
313
+ }
314
+ //# sourceMappingURL=add-custom-food.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add-custom-food.js","sourceRoot":"","sources":["../../src/kernel/add-custom-food.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH;;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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyQlC,CAAC;AACJ,CAAC"}
@@ -25,10 +25,25 @@ export interface DiaryData {
25
25
  carbs: number;
26
26
  fat: number;
27
27
  }
28
+ export interface CustomFoodEntry {
29
+ name: string;
30
+ protein?: number;
31
+ carbs?: number;
32
+ fat?: number;
33
+ calories?: number;
34
+ log?: string | boolean;
35
+ }
36
+ export interface LogFoodEntry {
37
+ name: string;
38
+ meal?: string;
39
+ servings?: number;
40
+ }
28
41
  export interface KernelClient {
29
42
  addQuickEntry(entry: MacroEntry, onStatus?: (msg: string) => void): Promise<void>;
30
43
  getWeight(dates: string[], onStatus?: (msg: string) => void): Promise<WeightData[]>;
31
44
  getDiary(dates: string[], onStatus?: (msg: string) => void): Promise<DiaryData[]>;
45
+ addCustomFood(entry: CustomFoodEntry, onStatus?: (msg: string) => void): Promise<void>;
46
+ logFood(entry: LogFoodEntry, onStatus?: (msg: string) => void): Promise<void>;
32
47
  }
33
48
  /**
34
49
  * Create a Kernel client for Cronometer automation.
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/kernel/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAcH,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,YAAY;IAC3B,aAAa,CACX,KAAK,EAAE,UAAU,EACjB,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAC/B,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,SAAS,CACP,KAAK,EAAE,MAAM,EAAE,EACf,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAC/B,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IACzB,QAAQ,CACN,KAAK,EAAE,MAAM,EAAE,EACf,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAC/B,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,YAAY,CAAC,CAsB7D"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/kernel/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAgBH,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,aAAa,CACX,KAAK,EAAE,UAAU,EACjB,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAC/B,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,SAAS,CACP,KAAK,EAAE,MAAM,EAAE,EACf,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAC/B,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IACzB,QAAQ,CACN,KAAK,EAAE,MAAM,EAAE,EACf,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAC/B,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;IACxB,aAAa,CACX,KAAK,EAAE,eAAe,EACtB,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAC/B,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,OAAO,CAAC,KAAK,EAAE,YAAY,EAAE,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/E;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,YAAY,CAAC,CA0B7D"}
@@ -12,6 +12,8 @@ import Kernel from "@onkernel/sdk";
12
12
  import { getCredential } from "../credentials.js";
13
13
  import { buildAutoLoginCode, buildLoginCheckCode, buildNavigateToLoginCode, } from "./login.js";
14
14
  import { buildQuickAddCode } from "./quick-add.js";
15
+ import { buildAddCustomFoodCode } from "./add-custom-food.js";
16
+ import { buildLogFoodCode } from "./log-food.js";
15
17
  import { buildDiaryCode } from "./diary.js";
16
18
  import { buildWeightCode } from "./weight.js";
17
19
  /**
@@ -33,6 +35,8 @@ export async function getKernelClient() {
33
35
  addQuickEntry: (entry, onStatus) => addQuickEntry(kernel, entry, onStatus),
34
36
  getWeight: (dates, onStatus) => getWeight(kernel, dates, onStatus),
35
37
  getDiary: (dates, onStatus) => getDiary(kernel, dates, onStatus),
38
+ addCustomFood: (entry, onStatus) => addCustomFood(kernel, entry, onStatus),
39
+ logFood: (entry, onStatus) => logFood(kernel, entry, onStatus),
36
40
  };
37
41
  }
38
42
  /**
@@ -156,6 +160,84 @@ async function getDiary(kernel, dates, onStatus) {
156
160
  }
157
161
  }
158
162
  }
163
+ /**
164
+ * Execute the custom food creation automation on Cronometer.
165
+ * Creates a browser, logs in, creates the food, optionally logs it, then tears down.
166
+ */
167
+ async function addCustomFood(kernel, entry, onStatus) {
168
+ const username = getCredential("cronometer-username");
169
+ const password = getCredential("cronometer-password");
170
+ const hasAutoCreds = !!(username && password);
171
+ const browser = await kernel.browsers.create({
172
+ headless: hasAutoCreds,
173
+ stealth: true,
174
+ timeout_seconds: hasAutoCreds ? 120 : 300,
175
+ });
176
+ try {
177
+ if (hasAutoCreds) {
178
+ await autoLogin(kernel, browser.session_id, username, password, onStatus);
179
+ }
180
+ else {
181
+ await manualLogin(kernel, browser);
182
+ }
183
+ onStatus?.("Creating custom food...");
184
+ const result = await kernel.browsers.playwright.execute(browser.session_id, { code: buildAddCustomFoodCode(entry), timeout_sec: 120 });
185
+ if (!result.success) {
186
+ throw new Error(`Automation failed: ${result.error ?? "Unknown error"}`);
187
+ }
188
+ const data = result.result;
189
+ if (!data.success) {
190
+ throw new Error(`Custom food creation failed: ${data.error ?? "Unknown error"}`);
191
+ }
192
+ }
193
+ finally {
194
+ try {
195
+ await kernel.browsers.deleteByID(browser.session_id);
196
+ }
197
+ catch {
198
+ // Browser may already be cleaned up by Kernel
199
+ }
200
+ }
201
+ }
202
+ /**
203
+ * Execute the food logging automation on Cronometer.
204
+ * Creates a browser, logs in, searches for the food, logs it, then tears down.
205
+ */
206
+ async function logFood(kernel, entry, onStatus) {
207
+ const username = getCredential("cronometer-username");
208
+ const password = getCredential("cronometer-password");
209
+ const hasAutoCreds = !!(username && password);
210
+ const browser = await kernel.browsers.create({
211
+ headless: hasAutoCreds,
212
+ stealth: true,
213
+ timeout_seconds: hasAutoCreds ? 120 : 300,
214
+ });
215
+ try {
216
+ if (hasAutoCreds) {
217
+ await autoLogin(kernel, browser.session_id, username, password, onStatus);
218
+ }
219
+ else {
220
+ await manualLogin(kernel, browser);
221
+ }
222
+ onStatus?.("Logging food to diary...");
223
+ const result = await kernel.browsers.playwright.execute(browser.session_id, { code: buildLogFoodCode(entry), timeout_sec: 60 });
224
+ if (!result.success) {
225
+ throw new Error(`Automation failed: ${result.error ?? "Unknown error"}`);
226
+ }
227
+ const data = result.result;
228
+ if (!data.success) {
229
+ throw new Error(`Food logging failed: ${data.error ?? "Unknown error"}`);
230
+ }
231
+ }
232
+ finally {
233
+ try {
234
+ await kernel.browsers.deleteByID(browser.session_id);
235
+ }
236
+ catch {
237
+ // Browser may already be cleaned up by Kernel
238
+ }
239
+ }
240
+ }
159
241
  /**
160
242
  * Auto-login using stored Cronometer credentials.
161
243
  */
@@ -167,7 +249,16 @@ async function autoLogin(kernel, sessionId, username, password, onStatus) {
167
249
  });
168
250
  const data = result.result;
169
251
  if (!result.success || !data.loggedIn) {
170
- throw new Error(`Auto-login failed: ${data.error ?? "Login verification failed"}.\n` +
252
+ const pageError = data.loginError?.toLowerCase() ?? "";
253
+ const isRateLimited = pageError.includes("too many") ||
254
+ pageError.includes("rate limit") ||
255
+ pageError.includes("try again later") ||
256
+ pageError.includes("temporarily");
257
+ if (isRateLimited) {
258
+ throw new Error(`Cronometer is rate-limiting login attempts. ${data.loginError}\n` +
259
+ "Please wait a few minutes and try again.");
260
+ }
261
+ throw new Error(`Auto-login failed: ${data.error ?? data.loginError ?? "Login verification failed"}.\n` +
171
262
  "Your credentials may be incorrect. Run `crono login` to update them.");
172
263
  }
173
264
  }
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/kernel/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/kernel/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,CAAC,MAAM,gBAAgB,CAAC;AACpC,OAAO,MAAM,MAAM,eAAe,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,GACzB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AA0D9C;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,MAAM,MAAM,GACV,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,aAAa,CAAC,gBAAgB,CAAC,CAAC;IACnE,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,6BAA6B;YAC3B,uDAAuD;YACvD,uCAAuC,CAC1C,CAAC;IACJ,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,GAAG,MAAM,CAAC;IAEvC,MAAM,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;IAE5B,OAAO;QACL,aAAa,EAAE,CAAC,KAAiB,EAAE,QAAgC,EAAE,EAAE,CACrE,aAAa,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC;QACxC,SAAS,EAAE,CAAC,KAAe,EAAE,QAAgC,EAAE,EAAE,CAC/D,SAAS,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC;QACpC,QAAQ,EAAE,CAAC,KAAe,EAAE,QAAgC,EAAE,EAAE,CAC9D,QAAQ,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC;QACnC,aAAa,EAAE,CAAC,KAAsB,EAAE,QAAgC,EAAE,EAAE,CAC1E,aAAa,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC;QACxC,OAAO,EAAE,CAAC,KAAmB,EAAE,QAAgC,EAAE,EAAE,CACjE,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ,CAAC;KACnC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,aAAa,CAC1B,MAAc,EACd,KAAiB,EACjB,QAAgC;IAEhC,MAAM,QAAQ,GAAG,aAAa,CAAC,qBAAqB,CAAC,CAAC;IACtD,MAAM,QAAQ,GAAG,aAAa,CAAC,qBAAqB,CAAC,CAAC;IACtD,MAAM,YAAY,GAAG,CAAC,CAAC,CAAC,QAAQ,IAAI,QAAQ,CAAC,CAAC;IAE9C,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;QAC3C,QAAQ,EAAE,YAAY;QACtB,OAAO,EAAE,IAAI;QACb,eAAe,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;KAC1C,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,uBAAuB;QACvB,IAAI,YAAY,EAAE,CAAC;YACjB,MAAM,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAC5E,CAAC;aAAM,CAAC;YACN,MAAM,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACrC,CAAC;QAED,oBAAoB;QACpB,QAAQ,EAAE,CAAC,uBAAuB,CAAC,CAAC;QACpC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CACrD,OAAO,CAAC,UAAU,EAClB,EAAE,IAAI,EAAE,iBAAiB,CAAC,KAAK,CAAC,EAAE,WAAW,EAAE,EAAE,EAAE,CACpD,CAAC;QAEF,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,sBAAsB,MAAM,CAAC,KAAK,IAAI,eAAe,EAAE,CAAC,CAAC;QAC3E,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,CAAC,MAA8C,CAAC;QACnE,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,qBAAqB,IAAI,CAAC,KAAK,IAAI,eAAe,EAAE,CAAC,CAAC;QACxE,CAAC;IACH,CAAC;YAAS,CAAC;QACT,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACvD,CAAC;QAAC,MAAM,CAAC;YACP,8CAA8C;QAChD,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,SAAS,CACtB,MAAc,EACd,KAAe,EACf,QAAgC;IAEhC,MAAM,QAAQ,GAAG,aAAa,CAAC,qBAAqB,CAAC,CAAC;IACtD,MAAM,QAAQ,GAAG,aAAa,CAAC,qBAAqB,CAAC,CAAC;IACtD,MAAM,YAAY,GAAG,CAAC,CAAC,CAAC,QAAQ,IAAI,QAAQ,CAAC,CAAC;IAE9C,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;QAC3C,QAAQ,EAAE,YAAY;QACtB,OAAO,EAAE,IAAI;QACb,eAAe,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;KAC1C,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,IAAI,YAAY,EAAE,CAAC;YACjB,MAAM,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAC5E,CAAC;aAAM,CAAC;YACN,MAAM,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACrC,CAAC;QAED,QAAQ,EAAE,CAAC,wBAAwB,CAAC,CAAC;QACrC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CACrD,OAAO,CAAC,UAAU,EAClB,EAAE,IAAI,EAAE,eAAe,CAAC,KAAK,CAAC,EAAE,WAAW,EAAE,EAAE,EAAE,CAClD,CAAC;QAEF,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,sBAAsB,MAAM,CAAC,KAAK,IAAI,eAAe,EAAE,CAAC,CAAC;QAC3E,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,CAAC,MAInB,CAAC;QACF,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,uBAAuB,IAAI,CAAC,KAAK,IAAI,eAAe,EAAE,CAAC,CAAC;QAC1E,CAAC;QAED,OAAO,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC;IAC5B,CAAC;YAAS,CAAC;QACT,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACvD,CAAC;QAAC,MAAM,CAAC;YACP,8CAA8C;QAChD,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,QAAQ,CACrB,MAAc,EACd,KAAe,EACf,QAAgC;IAEhC,MAAM,QAAQ,GAAG,aAAa,CAAC,qBAAqB,CAAC,CAAC;IACtD,MAAM,QAAQ,GAAG,aAAa,CAAC,qBAAqB,CAAC,CAAC;IACtD,MAAM,YAAY,GAAG,CAAC,CAAC,CAAC,QAAQ,IAAI,QAAQ,CAAC,CAAC;IAE9C,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;QAC3C,QAAQ,EAAE,YAAY;QACtB,OAAO,EAAE,IAAI;QACb,eAAe,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;KAC1C,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,IAAI,YAAY,EAAE,CAAC;YACjB,MAAM,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAC5E,CAAC;aAAM,CAAC;YACN,MAAM,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACrC,CAAC;QAED,QAAQ,EAAE,CAAC,uBAAuB,CAAC,CAAC;QACpC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CACrD,OAAO,CAAC,UAAU,EAClB,EAAE,IAAI,EAAE,cAAc,CAAC,KAAK,CAAC,EAAE,WAAW,EAAE,EAAE,EAAE,CACjD,CAAC;QAEF,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,sBAAsB,MAAM,CAAC,KAAK,IAAI,eAAe,EAAE,CAAC,CAAC;QAC3E,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,CAAC,MAInB,CAAC;QACF,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,sBAAsB,IAAI,CAAC,KAAK,IAAI,eAAe,EAAE,CAAC,CAAC;QACzE,CAAC;QAED,OAAO,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC;IAC5B,CAAC;YAAS,CAAC;QACT,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACvD,CAAC;QAAC,MAAM,CAAC;YACP,8CAA8C;QAChD,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,aAAa,CAC1B,MAAc,EACd,KAAsB,EACtB,QAAgC;IAEhC,MAAM,QAAQ,GAAG,aAAa,CAAC,qBAAqB,CAAC,CAAC;IACtD,MAAM,QAAQ,GAAG,aAAa,CAAC,qBAAqB,CAAC,CAAC;IACtD,MAAM,YAAY,GAAG,CAAC,CAAC,CAAC,QAAQ,IAAI,QAAQ,CAAC,CAAC;IAE9C,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;QAC3C,QAAQ,EAAE,YAAY;QACtB,OAAO,EAAE,IAAI;QACb,eAAe,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;KAC1C,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,IAAI,YAAY,EAAE,CAAC;YACjB,MAAM,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAC5E,CAAC;aAAM,CAAC;YACN,MAAM,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACrC,CAAC;QAED,QAAQ,EAAE,CAAC,yBAAyB,CAAC,CAAC;QACtC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CACrD,OAAO,CAAC,UAAU,EAClB,EAAE,IAAI,EAAE,sBAAsB,CAAC,KAAK,CAAC,EAAE,WAAW,EAAE,GAAG,EAAE,CAC1D,CAAC;QAEF,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,sBAAsB,MAAM,CAAC,KAAK,IAAI,eAAe,EAAE,CAAC,CAAC;QAC3E,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,CAAC,MAA8C,CAAC;QACnE,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CACb,gCAAgC,IAAI,CAAC,KAAK,IAAI,eAAe,EAAE,CAChE,CAAC;QACJ,CAAC;IACH,CAAC;YAAS,CAAC;QACT,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACvD,CAAC;QAAC,MAAM,CAAC;YACP,8CAA8C;QAChD,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,OAAO,CACpB,MAAc,EACd,KAAmB,EACnB,QAAgC;IAEhC,MAAM,QAAQ,GAAG,aAAa,CAAC,qBAAqB,CAAC,CAAC;IACtD,MAAM,QAAQ,GAAG,aAAa,CAAC,qBAAqB,CAAC,CAAC;IACtD,MAAM,YAAY,GAAG,CAAC,CAAC,CAAC,QAAQ,IAAI,QAAQ,CAAC,CAAC;IAE9C,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;QAC3C,QAAQ,EAAE,YAAY;QACtB,OAAO,EAAE,IAAI;QACb,eAAe,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;KAC1C,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,IAAI,YAAY,EAAE,CAAC;YACjB,MAAM,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAC5E,CAAC;aAAM,CAAC;YACN,MAAM,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACrC,CAAC;QAED,QAAQ,EAAE,CAAC,0BAA0B,CAAC,CAAC;QACvC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CACrD,OAAO,CAAC,UAAU,EAClB,EAAE,IAAI,EAAE,gBAAgB,CAAC,KAAK,CAAC,EAAE,WAAW,EAAE,EAAE,EAAE,CACnD,CAAC;QAEF,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CAAC,sBAAsB,MAAM,CAAC,KAAK,IAAI,eAAe,EAAE,CAAC,CAAC;QAC3E,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,CAAC,MAA8C,CAAC;QACnE,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CAAC,wBAAwB,IAAI,CAAC,KAAK,IAAI,eAAe,EAAE,CAAC,CAAC;QAC3E,CAAC;IACH,CAAC;YAAS,CAAC;QACT,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACvD,CAAC;QAAC,MAAM,CAAC;YACP,8CAA8C;QAChD,CAAC;IACH,CAAC;AACH,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,SAAS,CACtB,MAAc,EACd,SAAiB,EACjB,QAAgB,EAChB,QAAgB,EAChB,QAAgC;IAEhC,QAAQ,EAAE,CAAC,4BAA4B,CAAC,CAAC;IAEzC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,SAAS,EAAE;QACjE,IAAI,EAAE,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,CAAC;QAC5C,WAAW,EAAE,EAAE;KAChB,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,MAAM,CAAC,MAMnB,CAAC;IAEF,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACtC,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;QACvD,MAAM,aAAa,GACjB,SAAS,CAAC,QAAQ,CAAC,UAAU,CAAC;YAC9B,SAAS,CAAC,QAAQ,CAAC,YAAY,CAAC;YAChC,SAAS,CAAC,QAAQ,CAAC,iBAAiB,CAAC;YACrC,SAAS,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;QAEpC,IAAI,aAAa,EAAE,CAAC;YAClB,MAAM,IAAI,KAAK,CACb,+CAA+C,IAAI,CAAC,UAAU,IAAI;gBAChE,0CAA0C,CAC7C,CAAC;QACJ,CAAC;QAED,MAAM,IAAI,KAAK,CACb,sBAAsB,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,UAAU,IAAI,2BAA2B,KAAK;YACrF,sEAAsE,CACzE,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,WAAW,CACxB,MAAc,EACd,OAAsE;IAEtE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,yCAAyC,CAAC,CAAC;IACtD,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,iEAAiE,CAAC,CAAC;IAE9E,IAAI,OAAO,CAAC,qBAAqB,EAAE,CAAC;QAClC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,qBAAqB,EAAE,mBAAmB,CAAC,CAAC;IAC7D,CAAC;IAED,MAAM,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,EAAE;QAC3D,IAAI,EAAE,wBAAwB,EAAE;QAChC,WAAW,EAAE,EAAE;KAChB,CAAC,CAAC;IAEH,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,kDAAkD,CAAC,CAAC;IAE/D,MAAM,YAAY,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC;QAChC,OAAO,EAAE,sCAAsC;QAC/C,YAAY,EAAE,EAAE;KACjB,CAAC,CAAC;IAEH,IAAI,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,0BAA0B,CAAC,CAAC;IAC9C,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,EAAE;QAC1E,IAAI,EAAE,mBAAmB,EAAE;QAC3B,WAAW,EAAE,EAAE;KAChB,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,MAAM,CAAC,MAInB,CAAC;IAEF,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CACb,4EAA4E,CAC7E,CAAC;IACJ,CAAC;IAED,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;AAC3B,CAAC"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Playwright code generator for Cronometer food logging.
3
+ *
4
+ * Returns a code string that executes remotely via
5
+ * kernel.browsers.playwright.execute(). The code has access to
6
+ * `page`, `context`, and `browser` from the Playwright environment.
7
+ */
8
+ import type { LogFoodEntry } from "./client.js";
9
+ /**
10
+ * Generate Playwright code for logging a food to the Cronometer diary.
11
+ *
12
+ * Flow:
13
+ * navigate to #diary → right-click meal → "Add Food" → search food name →
14
+ * select result → set servings → "Add to Diary"
15
+ */
16
+ export declare function buildLogFoodCode(entry: LogFoodEntry): string;
17
+ //# sourceMappingURL=log-food.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log-food.d.ts","sourceRoot":"","sources":["../../src/kernel/log-food.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAyN5D"}