@milldr/crono 0.4.0 → 0.6.1
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 +71 -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 +137 -0
- package/dist/automation/runner.js.map +1 -0
- package/dist/automation/types.d.ts +66 -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/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 +10 -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 -127
- package/dist/kernel/add-custom-food.js.map +1 -1
- 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 -175
- 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 -186
- 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
|
@@ -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,132 +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 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);
|
|
184
|
+
${buildFoodDialogCode({
|
|
185
|
+
errorPrefix: "Food created but ",
|
|
186
|
+
requireServingSize: false,
|
|
187
|
+
})}
|
|
309
188
|
}
|
|
310
189
|
|
|
311
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"}
|
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"}
|
package/dist/kernel/client.js
CHANGED
|
@@ -1,21 +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
|
-
import * as p from "@clack/prompts";
|
|
11
4
|
import Kernel from "@onkernel/sdk";
|
|
5
|
+
import { createAutomationClient } from "../automation/runner.js";
|
|
12
6
|
import { getCredential } from "../credentials.js";
|
|
13
|
-
import { buildAutoLoginCode, buildLoginCheckCode, buildNavigateToLoginCode, } from "./login.js";
|
|
14
|
-
import { buildQuickAddCode } from "./quick-add.js";
|
|
15
|
-
import { buildAddCustomFoodCode } from "./add-custom-food.js";
|
|
16
|
-
import { buildLogFoodCode } from "./log-food.js";
|
|
17
|
-
import { buildDiaryCode } from "./diary.js";
|
|
18
|
-
import { buildWeightCode } from "./weight.js";
|
|
19
7
|
/**
|
|
20
8
|
* Create a Kernel client for Cronometer automation.
|
|
21
9
|
*
|
|
@@ -31,266 +19,33 @@ export async function getKernelClient() {
|
|
|
31
19
|
}
|
|
32
20
|
process.env["KERNEL_API_KEY"] = apiKey;
|
|
33
21
|
const kernel = new Kernel();
|
|
34
|
-
return
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
22
|
+
return createAutomationClient(createKernelRuntimeFactory(kernel));
|
|
23
|
+
}
|
|
24
|
+
function createKernelRuntimeFactory(kernel) {
|
|
25
|
+
return async (hasAutoCredentials) => {
|
|
26
|
+
const browser = await kernel.browsers.create({
|
|
27
|
+
headless: hasAutoCredentials,
|
|
28
|
+
stealth: true,
|
|
29
|
+
timeout_seconds: hasAutoCredentials ? 120 : 300,
|
|
30
|
+
});
|
|
31
|
+
return {
|
|
32
|
+
liveViewUrl: browser.browser_live_view_url,
|
|
33
|
+
execute: async (code, timeoutSec = 60) => {
|
|
34
|
+
const response = await kernel.browsers.playwright.execute(browser.session_id, {
|
|
35
|
+
code,
|
|
36
|
+
timeout_sec: timeoutSec,
|
|
37
|
+
});
|
|
38
|
+
return response;
|
|
39
|
+
},
|
|
40
|
+
close: async () => {
|
|
41
|
+
try {
|
|
42
|
+
await kernel.browsers.deleteByID(browser.session_id);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// Browser may already be cleaned up by Kernel.
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
};
|
|
40
49
|
};
|
|
41
50
|
}
|
|
42
|
-
/**
|
|
43
|
-
* Execute the quick-add automation on Cronometer.
|
|
44
|
-
* Creates a browser, logs in, performs the quick-add, then tears down.
|
|
45
|
-
*/
|
|
46
|
-
async function addQuickEntry(kernel, entry, onStatus) {
|
|
47
|
-
const username = getCredential("cronometer-username");
|
|
48
|
-
const password = getCredential("cronometer-password");
|
|
49
|
-
const hasAutoCreds = !!(username && password);
|
|
50
|
-
const browser = await kernel.browsers.create({
|
|
51
|
-
headless: hasAutoCreds,
|
|
52
|
-
stealth: true,
|
|
53
|
-
timeout_seconds: hasAutoCreds ? 120 : 300,
|
|
54
|
-
});
|
|
55
|
-
try {
|
|
56
|
-
// Log in to Cronometer
|
|
57
|
-
if (hasAutoCreds) {
|
|
58
|
-
await autoLogin(kernel, browser.session_id, username, password, onStatus);
|
|
59
|
-
}
|
|
60
|
-
else {
|
|
61
|
-
await manualLogin(kernel, browser);
|
|
62
|
-
}
|
|
63
|
-
// Execute quick-add
|
|
64
|
-
onStatus?.("Adding quick entry...");
|
|
65
|
-
const result = await kernel.browsers.playwright.execute(browser.session_id, { code: buildQuickAddCode(entry), timeout_sec: 60 });
|
|
66
|
-
if (!result.success) {
|
|
67
|
-
throw new Error(`Automation failed: ${result.error ?? "Unknown error"}`);
|
|
68
|
-
}
|
|
69
|
-
const data = result.result;
|
|
70
|
-
if (!data.success) {
|
|
71
|
-
throw new Error(`Quick add failed: ${data.error ?? "Unknown error"}`);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
finally {
|
|
75
|
-
try {
|
|
76
|
-
await kernel.browsers.deleteByID(browser.session_id);
|
|
77
|
-
}
|
|
78
|
-
catch {
|
|
79
|
-
// Browser may already be cleaned up by Kernel
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Execute the weight scraping automation on Cronometer.
|
|
85
|
-
* Creates a browser, logs in, reads weight data, then tears down.
|
|
86
|
-
*/
|
|
87
|
-
async function getWeight(kernel, dates, onStatus) {
|
|
88
|
-
const username = getCredential("cronometer-username");
|
|
89
|
-
const password = getCredential("cronometer-password");
|
|
90
|
-
const hasAutoCreds = !!(username && password);
|
|
91
|
-
const browser = await kernel.browsers.create({
|
|
92
|
-
headless: hasAutoCreds,
|
|
93
|
-
stealth: true,
|
|
94
|
-
timeout_seconds: hasAutoCreds ? 120 : 300,
|
|
95
|
-
});
|
|
96
|
-
try {
|
|
97
|
-
if (hasAutoCreds) {
|
|
98
|
-
await autoLogin(kernel, browser.session_id, username, password, onStatus);
|
|
99
|
-
}
|
|
100
|
-
else {
|
|
101
|
-
await manualLogin(kernel, browser);
|
|
102
|
-
}
|
|
103
|
-
onStatus?.("Reading weight data...");
|
|
104
|
-
const result = await kernel.browsers.playwright.execute(browser.session_id, { code: buildWeightCode(dates), timeout_sec: 60 });
|
|
105
|
-
if (!result.success) {
|
|
106
|
-
throw new Error(`Automation failed: ${result.error ?? "Unknown error"}`);
|
|
107
|
-
}
|
|
108
|
-
const data = result.result;
|
|
109
|
-
if (!data.success) {
|
|
110
|
-
throw new Error(`Weight read failed: ${data.error ?? "Unknown error"}`);
|
|
111
|
-
}
|
|
112
|
-
return data.entries ?? [];
|
|
113
|
-
}
|
|
114
|
-
finally {
|
|
115
|
-
try {
|
|
116
|
-
await kernel.browsers.deleteByID(browser.session_id);
|
|
117
|
-
}
|
|
118
|
-
catch {
|
|
119
|
-
// Browser may already be cleaned up by Kernel
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Execute the diary nutrition scraping automation on Cronometer.
|
|
125
|
-
* Creates a browser, logs in, reads nutrition data, then tears down.
|
|
126
|
-
*/
|
|
127
|
-
async function getDiary(kernel, dates, onStatus, scrapeTargets) {
|
|
128
|
-
const username = getCredential("cronometer-username");
|
|
129
|
-
const password = getCredential("cronometer-password");
|
|
130
|
-
const hasAutoCreds = !!(username && password);
|
|
131
|
-
const browser = await kernel.browsers.create({
|
|
132
|
-
headless: hasAutoCreds,
|
|
133
|
-
stealth: true,
|
|
134
|
-
timeout_seconds: hasAutoCreds ? 120 : 300,
|
|
135
|
-
});
|
|
136
|
-
try {
|
|
137
|
-
if (hasAutoCreds) {
|
|
138
|
-
await autoLogin(kernel, browser.session_id, username, password, onStatus);
|
|
139
|
-
}
|
|
140
|
-
else {
|
|
141
|
-
await manualLogin(kernel, browser);
|
|
142
|
-
}
|
|
143
|
-
onStatus?.("Reading diary data...");
|
|
144
|
-
const result = await kernel.browsers.playwright.execute(browser.session_id, { code: buildDiaryCode(dates, scrapeTargets), timeout_sec: 60 });
|
|
145
|
-
if (!result.success) {
|
|
146
|
-
throw new Error(`Automation failed: ${result.error ?? "Unknown error"}`);
|
|
147
|
-
}
|
|
148
|
-
const data = result.result;
|
|
149
|
-
if (!data.success) {
|
|
150
|
-
throw new Error(`Diary read failed: ${data.error ?? "Unknown error"}`);
|
|
151
|
-
}
|
|
152
|
-
return data.entries ?? [];
|
|
153
|
-
}
|
|
154
|
-
finally {
|
|
155
|
-
try {
|
|
156
|
-
await kernel.browsers.deleteByID(browser.session_id);
|
|
157
|
-
}
|
|
158
|
-
catch {
|
|
159
|
-
// Browser may already be cleaned up by Kernel
|
|
160
|
-
}
|
|
161
|
-
}
|
|
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
|
-
}
|
|
241
|
-
/**
|
|
242
|
-
* Auto-login using stored Cronometer credentials.
|
|
243
|
-
*/
|
|
244
|
-
async function autoLogin(kernel, sessionId, username, password, onStatus) {
|
|
245
|
-
onStatus?.("Logging into Cronometer...");
|
|
246
|
-
const result = await kernel.browsers.playwright.execute(sessionId, {
|
|
247
|
-
code: buildAutoLoginCode(username, password),
|
|
248
|
-
timeout_sec: 60,
|
|
249
|
-
});
|
|
250
|
-
const data = result.result;
|
|
251
|
-
if (!result.success || !data.loggedIn) {
|
|
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` +
|
|
262
|
-
"Your credentials may be incorrect. Run `crono login` to update them.");
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
/**
|
|
266
|
-
* Manual login: show a live browser view for the user to log in interactively.
|
|
267
|
-
*/
|
|
268
|
-
async function manualLogin(kernel, browser) {
|
|
269
|
-
p.log.warn("No stored Cronometer credentials found.");
|
|
270
|
-
p.log.info("Tip: run `crono login` to save credentials for automatic login.");
|
|
271
|
-
if (browser.browser_live_view_url) {
|
|
272
|
-
p.note(browser.browser_live_view_url, "Browser Live View");
|
|
273
|
-
}
|
|
274
|
-
await kernel.browsers.playwright.execute(browser.session_id, {
|
|
275
|
-
code: buildNavigateToLoginCode(),
|
|
276
|
-
timeout_sec: 30,
|
|
277
|
-
});
|
|
278
|
-
p.log.info("Please log into Cronometer in the browser above.");
|
|
279
|
-
const confirmation = await p.text({
|
|
280
|
-
message: "Press Enter once you've logged in...",
|
|
281
|
-
defaultValue: "",
|
|
282
|
-
});
|
|
283
|
-
if (p.isCancel(confirmation)) {
|
|
284
|
-
throw new Error("Login cancelled by user.");
|
|
285
|
-
}
|
|
286
|
-
const result = await kernel.browsers.playwright.execute(browser.session_id, {
|
|
287
|
-
code: buildLoginCheckCode(),
|
|
288
|
-
timeout_sec: 30,
|
|
289
|
-
});
|
|
290
|
-
const data = result.result;
|
|
291
|
-
if (!result.success || !data.loggedIn) {
|
|
292
|
-
throw new Error("Login verification failed. Make sure you're fully logged in and try again.");
|
|
293
|
-
}
|
|
294
|
-
p.log.step("Logged in.");
|
|
295
|
-
}
|
|
296
51
|
//# sourceMappingURL=client.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/kernel/client.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/kernel/client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,MAAM,MAAM,eAAe,CAAC;AACnC,OAAO,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAYjE,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAWlD;;;;;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;IAED,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,GAAG,MAAM,CAAC;IACvC,MAAM,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;IAE5B,OAAO,sBAAsB,CAAC,0BAA0B,CAAC,MAAM,CAAC,CAAC,CAAC;AACpE,CAAC;AAED,SAAS,0BAA0B,CAAC,MAAc;IAChD,OAAO,KAAK,EAAE,kBAA2B,EAAE,EAAE;QAC3C,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;YAC3C,QAAQ,EAAE,kBAAkB;YAC5B,OAAO,EAAE,IAAI;YACb,eAAe,EAAE,kBAAkB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG;SAChD,CAAC,CAAC;QAEH,OAAO;YACL,WAAW,EAAE,OAAO,CAAC,qBAAqB;YAC1C,OAAO,EAAE,KAAK,EACZ,IAAY,EACZ,UAAU,GAAG,EAAE,EAC0B,EAAE;gBAC3C,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CACvD,OAAO,CAAC,UAAU,EAClB;oBACE,IAAI;oBACJ,WAAW,EAAE,UAAU;iBACxB,CACF,CAAC;gBACF,OAAO,QAA0C,CAAC;YACpD,CAAC;YACD,KAAK,EAAE,KAAK,IAAI,EAAE;gBAChB,IAAI,CAAC;oBACH,MAAM,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;gBACvD,CAAC;gBAAC,MAAM,CAAC;oBACP,+CAA+C;gBACjD,CAAC;YACH,CAAC;SAC0B,CAAC;IAChC,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface FoodDialogOptions {
|
|
2
|
+
foodNameVar?: string;
|
|
3
|
+
mealLabelVar?: string;
|
|
4
|
+
errorPrefix?: string;
|
|
5
|
+
itemNameVar?: string;
|
|
6
|
+
requireServingSize?: boolean;
|
|
7
|
+
servingCountVar?: string;
|
|
8
|
+
alwaysUpdateServingSize?: boolean;
|
|
9
|
+
updateServingSize?: boolean;
|
|
10
|
+
verifyDialogDismissed?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function buildFoodDialogCode(options?: FoodDialogOptions): string;
|
|
13
|
+
//# sourceMappingURL=food-dialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"food-dialog.d.ts","sourceRoot":"","sources":["../../src/kernel/food-dialog.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,iBAAiB;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,iBAAsB,GAAG,MAAM,CAqN3E"}
|