@milldr/crono 0.6.1 → 0.8.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 +44 -0
- package/dist/automation/runner.d.ts.map +1 -1
- package/dist/automation/runner.js +18 -1
- package/dist/automation/runner.js.map +1 -1
- package/dist/automation/types.d.ts +9 -0
- package/dist/automation/types.d.ts.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/index.js +13 -0
- package/dist/index.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.map +1 -1
- package/dist/kernel/client.js +19 -2
- package/dist/kernel/client.js.map +1 -1
- package/dist/kernel/login.d.ts +6 -0
- package/dist/kernel/login.d.ts.map +1 -1
- package/dist/kernel/login.js +36 -29
- package/dist/kernel/login.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -283,6 +283,50 @@ crono diary -r 7d --json
|
|
|
283
283
|
# → [{"date":"2026-02-11","calories":1847,"protein":168,"carbs":142,"fat":58}, ...]
|
|
284
284
|
```
|
|
285
285
|
|
|
286
|
+
### `crono biometrics`
|
|
287
|
+
|
|
288
|
+
Log biometrics to Cronometer (weight, body fat percentage, blood pressure). Defaults to today if no date is specified.
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
crono biometrics [options]
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
**Options:**
|
|
295
|
+
|
|
296
|
+
| Flag | Long | Description |
|
|
297
|
+
| ---- | --------------------- | ------------------------------------ |
|
|
298
|
+
| `-w` | `--weight <value>` | Weight value |
|
|
299
|
+
| `-b` | `--body-fat <value>` | Body fat percentage |
|
|
300
|
+
| `-s` | `--systolic <value>` | Systolic blood pressure (mmHg) |
|
|
301
|
+
| `-d` | `--diastolic <value>` | Diastolic blood pressure (mmHg) |
|
|
302
|
+
| | `--date <date>` | Date (YYYY-MM-DD, yesterday, -1d) |
|
|
303
|
+
| `-u` | `--unit <unit>` | Weight unit (kg or lbs), default: kg |
|
|
304
|
+
|
|
305
|
+
At least one biometric value is required. Blood pressure requires both systolic and diastolic values.
|
|
306
|
+
|
|
307
|
+
**Examples:**
|
|
308
|
+
|
|
309
|
+
```bash
|
|
310
|
+
# Log weight in kg (default)
|
|
311
|
+
crono biometrics --weight 90
|
|
312
|
+
|
|
313
|
+
# Log weight in lbs
|
|
314
|
+
crono biometrics --weight 198 --unit lbs
|
|
315
|
+
|
|
316
|
+
# Log body fat percentage
|
|
317
|
+
crono biometrics --body-fat 19
|
|
318
|
+
|
|
319
|
+
# Log blood pressure
|
|
320
|
+
crono biometrics --systolic 130 --diastolic 76
|
|
321
|
+
|
|
322
|
+
# Log weight for yesterday
|
|
323
|
+
crono biometrics --weight 90 --date yesterday
|
|
324
|
+
|
|
325
|
+
# Log multiple biometrics (requires multiple commands)
|
|
326
|
+
crono biometrics --weight 90
|
|
327
|
+
crono biometrics --body-fat 19
|
|
328
|
+
```
|
|
329
|
+
|
|
286
330
|
### `crono recipes`
|
|
287
331
|
|
|
288
332
|
List your custom recipes from Cronometer.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/automation/runner.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../../src/automation/runner.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EACV,gBAAgB,EAEhB,wBAAwB,EASzB,MAAM,YAAY,CAAC;AAEpB,wBAAgB,sBAAsB,CACpC,aAAa,EAAE,wBAAwB,GACtC,gBAAgB,CA4GlB"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as p from "@clack/prompts";
|
|
2
2
|
import { getCredential } from "../credentials.js";
|
|
3
3
|
import { buildAddCustomFoodCode } from "../kernel/add-custom-food.js";
|
|
4
|
+
import { buildBiometricsCode } from "../kernel/biometrics.js";
|
|
4
5
|
import { buildDiaryCode } from "../kernel/diary.js";
|
|
5
6
|
import { buildLogFoodCode } from "../kernel/log-food.js";
|
|
6
7
|
import { buildRecipesCode } from "../kernel/recipes.js";
|
|
@@ -54,6 +55,18 @@ export function createAutomationClient(createRuntime) {
|
|
|
54
55
|
}
|
|
55
56
|
return data.recipes ?? [];
|
|
56
57
|
}),
|
|
58
|
+
logBiometric: (entry, onStatus) => withLoggedInRuntime(createRuntime, onStatus, async (runtime) => {
|
|
59
|
+
const typeLabel = entry.type === "weight"
|
|
60
|
+
? "weight"
|
|
61
|
+
: entry.type === "bodyfat"
|
|
62
|
+
? "body fat"
|
|
63
|
+
: "blood pressure";
|
|
64
|
+
onStatus?.(`Logging ${typeLabel}...`);
|
|
65
|
+
const data = await executeAutomation(runtime, buildBiometricsCode(entry), 60);
|
|
66
|
+
if (!data.success) {
|
|
67
|
+
throw new Error(`Biometric logging failed: ${data.error ?? "Unknown error"}`);
|
|
68
|
+
}
|
|
69
|
+
}),
|
|
57
70
|
};
|
|
58
71
|
}
|
|
59
72
|
async function withLoggedInRuntime(createRuntime, onStatus, run) {
|
|
@@ -96,7 +109,11 @@ async function autoLogin(runtime, username, password, onStatus) {
|
|
|
96
109
|
"Please wait a few minutes and try again.");
|
|
97
110
|
}
|
|
98
111
|
throw new Error(`Auto-login failed: ${data.error ?? data.loginError ?? "Login verification failed"}.\n` +
|
|
99
|
-
"Your credentials may be incorrect
|
|
112
|
+
"Your credentials may be incorrect — run `crono login` to update them.\n" +
|
|
113
|
+
"If they are known good, Cronometer is most likely throttling logins: it\n" +
|
|
114
|
+
"then serves the logged-out marketing page with no error text, which\n" +
|
|
115
|
+
"surfaces as this message. Wait a few minutes before trying again;\n" +
|
|
116
|
+
"retrying in a loop extends the throttle.");
|
|
100
117
|
}
|
|
101
118
|
}
|
|
102
119
|
async function manualLogin(runtime) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../../src/automation/runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,gBAAgB,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"runner.js","sourceRoot":"","sources":["../../src/automation/runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,gBAAgB,CAAC;AACpC,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,sBAAsB,EAAE,MAAM,8BAA8B,CAAC;AACtE,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EACL,kBAAkB,EAClB,mBAAmB,EACnB,wBAAwB,GACzB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAetD,MAAM,UAAU,sBAAsB,CACpC,aAAuC;IAEvC,OAAO;QACL,aAAa,EAAE,CAAC,KAAiB,EAAE,QAAgC,EAAE,EAAE,CACrE,mBAAmB,CAAC,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YAC7D,QAAQ,EAAE,CAAC,uBAAuB,CAAC,CAAC;YACpC,MAAM,IAAI,GAAG,MAAM,iBAAiB,CAGjC,OAAO,EAAE,iBAAiB,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;YAC1C,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;gBAClB,MAAM,IAAI,KAAK,CAAC,qBAAqB,IAAI,CAAC,KAAK,IAAI,eAAe,EAAE,CAAC,CAAC;YACxE,CAAC;QACH,CAAC,CAAC;QACJ,SAAS,EAAE,CAAC,KAAe,EAAE,QAAgC,EAAE,EAAE,CAC/D,mBAAmB,CAAC,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YAC7D,QAAQ,EAAE,CAAC,wBAAwB,CAAC,CAAC;YACrC,MAAM,IAAI,GAAG,MAAM,iBAAiB,CAIjC,OAAO,EAAE,eAAe,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;YACxC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;gBAClB,MAAM,IAAI,KAAK,CACb,uBAAuB,IAAI,CAAC,KAAK,IAAI,eAAe,EAAE,CACvD,CAAC;YACJ,CAAC;YACD,OAAO,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC;QAC5B,CAAC,CAAC;QACJ,QAAQ,EAAE,CACR,KAAe,EACf,QAAgC,EAChC,aAAuB,EACvB,EAAE,CACF,mBAAmB,CAAC,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YAC7D,QAAQ,EAAE,CAAC,uBAAuB,CAAC,CAAC;YACpC,MAAM,IAAI,GAAG,MAAM,iBAAiB,CAIjC,OAAO,EAAE,cAAc,CAAC,KAAK,EAAE,aAAa,CAAC,EAAE,EAAE,CAAC,CAAC;YACtD,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;gBAClB,MAAM,IAAI,KAAK,CACb,sBAAsB,IAAI,CAAC,KAAK,IAAI,eAAe,EAAE,CACtD,CAAC;YACJ,CAAC;YACD,OAAO,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC;QAC5B,CAAC,CAAC;QACJ,aAAa,EAAE,CAAC,KAAsB,EAAE,QAAgC,EAAE,EAAE,CAC1E,mBAAmB,CAAC,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YAC7D,QAAQ,EAAE,CAAC,yBAAyB,CAAC,CAAC;YACtC,MAAM,IAAI,GAAG,MAAM,iBAAiB,CAGjC,OAAO,EAAE,sBAAsB,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC;YAChD,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;gBAClB,MAAM,IAAI,KAAK,CACb,gCAAgC,IAAI,CAAC,KAAK,IAAI,eAAe,EAAE,CAChE,CAAC;YACJ,CAAC;QACH,CAAC,CAAC;QACJ,OAAO,EAAE,CAAC,KAAmB,EAAE,QAAgC,EAAE,EAAE,CACjE,mBAAmB,CAAC,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YAC7D,QAAQ,EAAE,CAAC,0BAA0B,CAAC,CAAC;YACvC,MAAM,IAAI,GAAG,MAAM,iBAAiB,CAGjC,OAAO,EAAE,gBAAgB,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;YACzC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;gBAClB,MAAM,IAAI,KAAK,CACb,wBAAwB,IAAI,CAAC,KAAK,IAAI,eAAe,EAAE,CACxD,CAAC;YACJ,CAAC;QACH,CAAC,CAAC;QACJ,UAAU,EAAE,CAAC,QAAgC,EAAE,EAAE,CAC/C,mBAAmB,CAAC,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YAC7D,QAAQ,EAAE,CAAC,2BAA2B,CAAC,CAAC;YACxC,MAAM,IAAI,GAAG,MAAM,iBAAiB,CAIjC,OAAO,EAAE,gBAAgB,EAAE,EAAE,EAAE,CAAC,CAAC;YACpC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;gBAClB,MAAM,IAAI,KAAK,CACb,wBAAwB,IAAI,CAAC,KAAK,IAAI,eAAe,EAAE,CACxD,CAAC;YACJ,CAAC;YACD,OAAO,IAAI,CAAC,OAAO,IAAI,EAAE,CAAC;QAC5B,CAAC,CAAC;QACJ,YAAY,EAAE,CAAC,KAAqB,EAAE,QAAgC,EAAE,EAAE,CACxE,mBAAmB,CAAC,aAAa,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;YAC7D,MAAM,SAAS,GACb,KAAK,CAAC,IAAI,KAAK,QAAQ;gBACrB,CAAC,CAAC,QAAQ;gBACV,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,SAAS;oBACxB,CAAC,CAAC,UAAU;oBACZ,CAAC,CAAC,gBAAgB,CAAC;YACzB,QAAQ,EAAE,CAAC,WAAW,SAAS,KAAK,CAAC,CAAC;YACtC,MAAM,IAAI,GAAG,MAAM,iBAAiB,CAGjC,OAAO,EAAE,mBAAmB,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;YAC5C,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC;gBAClB,MAAM,IAAI,KAAK,CACb,6BAA6B,IAAI,CAAC,KAAK,IAAI,eAAe,EAAE,CAC7D,CAAC;YACJ,CAAC;QACH,CAAC,CAAC;KACL,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,mBAAmB,CAChC,aAAuC,EACvC,QAA6C,EAC7C,GAA+C;IAE/C,MAAM,QAAQ,GAAG,aAAa,CAAC,qBAAqB,CAAC,CAAC;IACtD,MAAM,QAAQ,GAAG,aAAa,CAAC,qBAAqB,CAAC,CAAC;IACtD,MAAM,kBAAkB,GAAG,CAAC,CAAC,CAAC,QAAQ,IAAI,QAAQ,CAAC,CAAC;IACpD,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC,kBAAkB,CAAC,CAAC;IAExD,IAAI,CAAC;QACH,MAAM,cAAc,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAE5D,OAAO,MAAM,GAAG,CAAC,OAAO,CAAC,CAAC;IAC5B,CAAC;YAAS,CAAC;QACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;IACxB,CAAC;AACH,CAAC;AAED,KAAK,UAAU,cAAc,CAC3B,OAA0B,EAC1B,QAAuB,EACvB,QAAuB,EACvB,QAAgC;IAEhC,QAAQ,EAAE,CAAC,gCAAgC,CAAC,CAAC;IAE7C,MAAM,IAAI,GAAG,MAAM,iBAAiB,CAIjC,OAAO,EAAE,mBAAmB,EAAE,EAAE,EAAE,CAAC,CAAC;IAEvC,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAClB,OAAO;IACT,CAAC;IAED,IAAI,QAAQ,IAAI,QAAQ,EAAE,CAAC;QACzB,MAAM,SAAS,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACzD,CAAC;SAAM,CAAC;QACN,MAAM,WAAW,CAAC,OAAO,CAAC,CAAC;IAC7B,CAAC;AACH,CAAC;AAED,KAAK,UAAU,SAAS,CACtB,OAA0B,EAC1B,QAAgB,EAChB,QAAgB,EAChB,QAAgC;IAEhC,QAAQ,EAAE,CAAC,4BAA4B,CAAC,CAAC;IAEzC,MAAM,IAAI,GAAG,MAAM,iBAAiB,CAMjC,OAAO,EAAE,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC;IAExD,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACnB,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,yEAAyE;YACzE,2EAA2E;YAC3E,uEAAuE;YACvE,qEAAqE;YACrE,0CAA0C,CAC7C,CAAC;IACJ,CAAC;AACH,CAAC;AAED,KAAK,UAAU,WAAW,CAAC,OAA0B;IACnD,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,yCAAyC,CAAC,CAAC;IACtD,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,iEAAiE,CAAC,CAAC;IAE9E,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;QACxB,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,mBAAmB,CAAC,CAAC;IACnD,CAAC;IAED,MAAM,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,EAAE,EAAE,EAAE,CAAC,CAAC;IACjE,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,IAAI,GAAG,MAAM,iBAAiB,CAIjC,OAAO,EAAE,mBAAmB,EAAE,EAAE,EAAE,CAAC,CAAC;IAEvC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CACb,4EAA4E,CAC7E,CAAC;IACJ,CAAC;IAED,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;AAC3B,CAAC;AAED,KAAK,UAAU,iBAAiB,CAC9B,OAA0B,EAC1B,IAAY,EACZ,UAAkB;IAElB,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,OAAO,CAAI,IAAI,EAAE,UAAU,CAAC,CAAC;IAC5D,OAAO,qBAAqB,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;AACvD,CAAC;AAED,SAAS,qBAAqB,CAC5B,QAAwC,EACxC,MAAc;IAEd,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;QACtB,MAAM,OAAO,GAAG,QAAQ,CAAC,KAAK,IAAI,QAAQ,CAAC,MAAM,IAAI,eAAe,CAAC;QACrE,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,YAAY,OAAO,EAAE,CAAC,CAAC;IAClD,CAAC;IAED,IAAI,QAAQ,CAAC,MAAM,KAAK,SAAS,IAAI,QAAQ,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;QAC9D,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,6BAA6B,CAAC,CAAC;IAC1D,CAAC;IAED,OAAO,QAAQ,CAAC,MAAM,CAAC;AACzB,CAAC"}
|
|
@@ -43,6 +43,14 @@ export interface LogFoodEntry {
|
|
|
43
43
|
export interface RecipeData {
|
|
44
44
|
name: string;
|
|
45
45
|
}
|
|
46
|
+
export interface BiometricEntry {
|
|
47
|
+
type: "weight" | "bodyfat" | "bloodpressure";
|
|
48
|
+
value?: number;
|
|
49
|
+
unit?: string;
|
|
50
|
+
systolic?: number;
|
|
51
|
+
diastolic?: number;
|
|
52
|
+
date: string;
|
|
53
|
+
}
|
|
46
54
|
export interface AutomationClient {
|
|
47
55
|
addQuickEntry(entry: MacroEntry, onStatus?: (msg: string) => void): Promise<void>;
|
|
48
56
|
getWeight(dates: string[], onStatus?: (msg: string) => void): Promise<WeightData[]>;
|
|
@@ -50,6 +58,7 @@ export interface AutomationClient {
|
|
|
50
58
|
addCustomFood(entry: CustomFoodEntry, onStatus?: (msg: string) => void): Promise<void>;
|
|
51
59
|
logFood(entry: LogFoodEntry, onStatus?: (msg: string) => void): Promise<void>;
|
|
52
60
|
getRecipes(onStatus?: (msg: string) => void): Promise<RecipeData[]>;
|
|
61
|
+
logBiometric(entry: BiometricEntry, onStatus?: (msg: string) => void): Promise<void>;
|
|
53
62
|
}
|
|
54
63
|
export interface PlaywrightExecutionResponse<T = unknown> {
|
|
55
64
|
success: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/automation/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,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;IACZ,OAAO,CAAC,EAAE;QACR,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;QACrB,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;KACpB,GAAG,IAAI,CAAC;IACT,QAAQ,CAAC,EAAE;QACT,cAAc,EAAE,MAAM,CAAC;KACxB,GAAG,IAAI,CAAC;CACV;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,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,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,EAChC,aAAa,CAAC,EAAE,OAAO,GACtB,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;IAC9E,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/automation/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface BiometricsOptions {
|
|
2
|
+
weight?: number;
|
|
3
|
+
bodyFat?: number;
|
|
4
|
+
systolic?: number;
|
|
5
|
+
diastolic?: number;
|
|
6
|
+
date?: string;
|
|
7
|
+
unit?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function biometrics(options: BiometricsOptions): Promise<void>;
|
|
10
|
+
//# sourceMappingURL=biometrics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"biometrics.d.ts","sourceRoot":"","sources":["../../src/commands/biometrics.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsG1E"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as p from "@clack/prompts";
|
|
2
|
+
import { getAutomationClient } from "../automation/client.js";
|
|
3
|
+
import { formatKernelError } from "../kernel/errors.js";
|
|
4
|
+
import { parseDate, todayStr } from "../utils/date.js";
|
|
5
|
+
export async function biometrics(options) {
|
|
6
|
+
// Validate at least one biometric is provided
|
|
7
|
+
if (options.weight === undefined &&
|
|
8
|
+
options.bodyFat === undefined &&
|
|
9
|
+
(options.systolic === undefined || options.diastolic === undefined)) {
|
|
10
|
+
p.log.error("At least one biometric required: --weight, --body-fat, or both --systolic and --diastolic");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
// Validate blood pressure requires both values
|
|
14
|
+
if ((options.systolic !== undefined && options.diastolic === undefined) ||
|
|
15
|
+
(options.systolic === undefined && options.diastolic !== undefined)) {
|
|
16
|
+
p.log.error("Blood pressure requires both --systolic and --diastolic");
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
// Validate weight unit
|
|
20
|
+
const unit = options.unit?.toLowerCase() || "kg";
|
|
21
|
+
if (unit !== "kg" && unit !== "lbs") {
|
|
22
|
+
p.log.error('Weight unit must be "kg" or "lbs"');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
// Resolve date
|
|
26
|
+
let targetDate;
|
|
27
|
+
try {
|
|
28
|
+
targetDate = options.date ? parseDate(options.date) : todayStr();
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
p.log.error(String(error instanceof Error ? error.message : error));
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
p.intro("🍎 crono biometrics");
|
|
35
|
+
const s = p.spinner();
|
|
36
|
+
s.start("Connecting...");
|
|
37
|
+
try {
|
|
38
|
+
const client = await getAutomationClient();
|
|
39
|
+
// Log weight if provided
|
|
40
|
+
if (options.weight !== undefined) {
|
|
41
|
+
await client.logBiometric({
|
|
42
|
+
type: "weight",
|
|
43
|
+
value: options.weight,
|
|
44
|
+
unit,
|
|
45
|
+
date: targetDate,
|
|
46
|
+
}, (msg) => s.message(msg));
|
|
47
|
+
}
|
|
48
|
+
// Log body fat if provided
|
|
49
|
+
if (options.bodyFat !== undefined) {
|
|
50
|
+
await client.logBiometric({
|
|
51
|
+
type: "bodyfat",
|
|
52
|
+
value: options.bodyFat,
|
|
53
|
+
date: targetDate,
|
|
54
|
+
}, (msg) => s.message(msg));
|
|
55
|
+
}
|
|
56
|
+
// Log blood pressure if provided
|
|
57
|
+
if (options.systolic !== undefined && options.diastolic !== undefined) {
|
|
58
|
+
await client.logBiometric({
|
|
59
|
+
type: "bloodpressure",
|
|
60
|
+
systolic: options.systolic,
|
|
61
|
+
diastolic: options.diastolic,
|
|
62
|
+
date: targetDate,
|
|
63
|
+
}, (msg) => s.message(msg));
|
|
64
|
+
}
|
|
65
|
+
s.stop("Done.");
|
|
66
|
+
const logged = [];
|
|
67
|
+
if (options.weight !== undefined)
|
|
68
|
+
logged.push(`Weight: ${options.weight} ${unit}`);
|
|
69
|
+
if (options.bodyFat !== undefined)
|
|
70
|
+
logged.push(`Body Fat: ${options.bodyFat}%`);
|
|
71
|
+
if (options.systolic !== undefined && options.diastolic !== undefined)
|
|
72
|
+
logged.push(`Blood Pressure: ${options.systolic}/${options.diastolic} mmHg`);
|
|
73
|
+
p.outro(`Logged biometrics for ${targetDate}:\n ${logged.join("\n ")}`);
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
s.stop("Failed.");
|
|
77
|
+
p.log.error(`Failed to log biometrics: ${formatKernelError(error)}`);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=biometrics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"biometrics.js","sourceRoot":"","sources":["../../src/commands/biometrics.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import { diary } from "./commands/diary.js";
|
|
|
9
9
|
import { weight } from "./commands/weight.js";
|
|
10
10
|
import { exportCmd } from "./commands/export.js";
|
|
11
11
|
import { recipes } from "./commands/recipes.js";
|
|
12
|
+
import { biometrics } from "./commands/biometrics.js";
|
|
12
13
|
const require = createRequire(import.meta.url);
|
|
13
14
|
const { version } = require("../package.json");
|
|
14
15
|
const program = new Command();
|
|
@@ -91,5 +92,17 @@ program
|
|
|
91
92
|
.action(async (options) => {
|
|
92
93
|
await recipes(options);
|
|
93
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
|
+
});
|
|
94
107
|
program.parse();
|
|
95
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;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -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"}
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;AAsBhC,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
|
@@ -4,6 +4,23 @@
|
|
|
4
4
|
import Kernel from "@onkernel/sdk";
|
|
5
5
|
import { createAutomationClient } from "../automation/runner.js";
|
|
6
6
|
import { getCredential } from "../credentials.js";
|
|
7
|
+
/**
|
|
8
|
+
* Client-side HTTP timeout for Kernel API requests.
|
|
9
|
+
*
|
|
10
|
+
* The SDK defaults to 60s, but automations are dispatched with `timeout_sec`
|
|
11
|
+
* values up to 120s (see `addCustomFood`). When the client gives up first the
|
|
12
|
+
* browser keeps going, so Cronometer commits the change while crono reports
|
|
13
|
+
* failure — and a retry then duplicates the data. Keep this comfortably above
|
|
14
|
+
* the largest `timeout_sec` used by any automation.
|
|
15
|
+
*/
|
|
16
|
+
const KERNEL_REQUEST_TIMEOUT_MS = 10 * 60 * 1000;
|
|
17
|
+
/**
|
|
18
|
+
* Lifetime of a remote browser session.
|
|
19
|
+
*
|
|
20
|
+
* A single command can spend ~60s logging in before a 120s automation even
|
|
21
|
+
* starts, so the previous 120s budget could expire mid-operation.
|
|
22
|
+
*/
|
|
23
|
+
const BROWSER_SESSION_TIMEOUT_SEC = 300;
|
|
7
24
|
/**
|
|
8
25
|
* Create a Kernel client for Cronometer automation.
|
|
9
26
|
*
|
|
@@ -18,7 +35,7 @@ export async function getKernelClient() {
|
|
|
18
35
|
" export KERNEL_API_KEY=your-key-here");
|
|
19
36
|
}
|
|
20
37
|
process.env["KERNEL_API_KEY"] = apiKey;
|
|
21
|
-
const kernel = new Kernel();
|
|
38
|
+
const kernel = new Kernel({ timeout: KERNEL_REQUEST_TIMEOUT_MS });
|
|
22
39
|
return createAutomationClient(createKernelRuntimeFactory(kernel));
|
|
23
40
|
}
|
|
24
41
|
function createKernelRuntimeFactory(kernel) {
|
|
@@ -26,7 +43,7 @@ function createKernelRuntimeFactory(kernel) {
|
|
|
26
43
|
const browser = await kernel.browsers.create({
|
|
27
44
|
headless: hasAutoCredentials,
|
|
28
45
|
stealth: true,
|
|
29
|
-
timeout_seconds:
|
|
46
|
+
timeout_seconds: BROWSER_SESSION_TIMEOUT_SEC,
|
|
30
47
|
});
|
|
31
48
|
return {
|
|
32
49
|
liveViewUrl: browser.browser_live_view_url,
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;AAElD;;;;;;;;GAQG;AACH,MAAM,yBAAyB,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAEjD;;;;;GAKG;AACH,MAAM,2BAA2B,GAAG,GAAG,CAAC;AAWxC;;;;;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,CAAC,EAAE,OAAO,EAAE,yBAAyB,EAAE,CAAC,CAAC;IAElE,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,2BAA2B;SAC7C,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"}
|
package/dist/kernel/login.d.ts
CHANGED
|
@@ -19,6 +19,12 @@ export declare function buildNavigateToLoginCode(): string;
|
|
|
19
19
|
* Generate Playwright code that automates Cronometer login.
|
|
20
20
|
* Fills email/password, submits, and verifies login succeeded.
|
|
21
21
|
* Credentials are embedded via JSON.stringify for safe escaping.
|
|
22
|
+
*
|
|
23
|
+
* Navigates directly to /login/. An earlier version loaded the marketing
|
|
24
|
+
* homepage and clicked its "Log In" link, but that click frequently does not
|
|
25
|
+
* navigate — and because the loop recorded a successful *click* it also
|
|
26
|
+
* suppressed the direct-navigation fallback, leaving the form unreachable and
|
|
27
|
+
* failing with "Could not find email input on https://cronometer.com/".
|
|
22
28
|
*/
|
|
23
29
|
export declare function buildAutoLoginCode(username: string, password: string): string;
|
|
24
30
|
//# sourceMappingURL=login.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/kernel/login.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,
|
|
1
|
+
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/kernel/login.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,CAW5C;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CAKjD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAyG7E"}
|
package/dist/kernel/login.js
CHANGED
|
@@ -16,10 +16,7 @@ export function buildLoginCheckCode() {
|
|
|
16
16
|
await page.waitForTimeout(1000);
|
|
17
17
|
const url = page.url();
|
|
18
18
|
${buildLoginPresentedCheckCode()}
|
|
19
|
-
|
|
20
|
-
return !!document.querySelector('i.diary-date-previous, i.diary-date-next') ||
|
|
21
|
-
/Energy\\s+\\d+\\.?\\d*\\s*kcal/i.test(document.body.innerText);
|
|
22
|
-
}).catch(() => false);
|
|
19
|
+
${buildDiaryPresentedCheckCode()}
|
|
23
20
|
const isLoggedIn = url.includes('#diary') && !url.includes('/login') && !url.includes('/signin') && !loginPresented && diaryPresented;
|
|
24
21
|
return { success: true, loggedIn: isLoggedIn, url, loginPresented, diaryPresented };
|
|
25
22
|
`;
|
|
@@ -38,35 +35,23 @@ export function buildNavigateToLoginCode() {
|
|
|
38
35
|
* Generate Playwright code that automates Cronometer login.
|
|
39
36
|
* Fills email/password, submits, and verifies login succeeded.
|
|
40
37
|
* Credentials are embedded via JSON.stringify for safe escaping.
|
|
38
|
+
*
|
|
39
|
+
* Navigates directly to /login/. An earlier version loaded the marketing
|
|
40
|
+
* homepage and clicked its "Log In" link, but that click frequently does not
|
|
41
|
+
* navigate — and because the loop recorded a successful *click* it also
|
|
42
|
+
* suppressed the direct-navigation fallback, leaving the form unreachable and
|
|
43
|
+
* failing with "Could not find email input on https://cronometer.com/".
|
|
41
44
|
*/
|
|
42
45
|
export function buildAutoLoginCode(username, password) {
|
|
43
46
|
const safeUser = JSON.stringify(username);
|
|
44
47
|
const safePass = JSON.stringify(password);
|
|
45
48
|
return `
|
|
46
|
-
// Navigate
|
|
47
|
-
await page.goto('https://cronometer.com', { waitUntil: 'domcontentloaded', timeout:
|
|
48
|
-
await page.waitForLoadState('networkidle', { timeout:
|
|
49
|
-
|
|
50
|
-
// Click the "Log In" link in the top navigation
|
|
51
|
-
const loginLinkSelectors = ['a[href="/login/"]', 'a[href="/login"]', 'a:has-text("Log In")', 'a:has-text("Login")'];
|
|
52
|
-
let clickedLogin = false;
|
|
53
|
-
for (const sel of loginLinkSelectors) {
|
|
54
|
-
try {
|
|
55
|
-
const el = page.locator(sel);
|
|
56
|
-
if (await el.count() > 0) {
|
|
57
|
-
await el.first().click();
|
|
58
|
-
clickedLogin = true;
|
|
59
|
-
break;
|
|
60
|
-
}
|
|
61
|
-
} catch {}
|
|
62
|
-
}
|
|
63
|
-
if (!clickedLogin) {
|
|
64
|
-
// Fallback: navigate directly
|
|
65
|
-
await page.goto('https://cronometer.com/login/', { waitUntil: 'domcontentloaded', timeout: 15000 });
|
|
66
|
-
}
|
|
49
|
+
// Navigate straight to the login page.
|
|
50
|
+
await page.goto('https://cronometer.com/login/', { waitUntil: 'domcontentloaded', timeout: 20000 });
|
|
51
|
+
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
67
52
|
|
|
68
53
|
// Wait for login page to load
|
|
69
|
-
await page.waitForSelector('input[type="email"], input[name="username"], input[name="email"], #email, #username', { timeout:
|
|
54
|
+
await page.waitForSelector('input[type="email"], input[name="username"], input[name="email"], #email, #username', { timeout: 15000 }).catch(() => {});
|
|
70
55
|
|
|
71
56
|
// Fill email — try multiple selectors
|
|
72
57
|
const emailSelectors = ['input[type="email"]', 'input[name="username"]', 'input[name="email"]', '#email', '#username'];
|
|
@@ -119,13 +104,23 @@ export function buildAutoLoginCode(username, password) {
|
|
|
119
104
|
return { success: false, loggedIn: false, url: page.url(), error: 'Could not find submit button on ' + page.url() };
|
|
120
105
|
}
|
|
121
106
|
|
|
122
|
-
// Wait for navigation after login
|
|
107
|
+
// Wait for navigation after login. The GWT app then needs a moment to swap
|
|
108
|
+
// the login UI for the app shell — checking too early made a login that had
|
|
109
|
+
// actually succeeded report "Login verification failed".
|
|
123
110
|
await page.waitForURL(u => !u.href.includes('/login') && !u.href.includes('/signin'), { timeout: 15000 }).catch(() => {});
|
|
124
|
-
await page.waitForTimeout(
|
|
111
|
+
await page.waitForTimeout(3000);
|
|
112
|
+
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
113
|
+
|
|
114
|
+
// Confirm with a positive signal — that the diary actually renders — rather
|
|
115
|
+
// than relying only on the absence of login UI, which races the app's boot.
|
|
116
|
+
await page.goto('https://cronometer.com/#diary', { waitUntil: 'domcontentloaded', timeout: 20000 }).catch(() => {});
|
|
117
|
+
await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
|
|
118
|
+
await page.waitForTimeout(2000);
|
|
125
119
|
|
|
126
120
|
const url = page.url();
|
|
127
121
|
${buildLoginPresentedCheckCode()}
|
|
128
|
-
|
|
122
|
+
${buildDiaryPresentedCheckCode()}
|
|
123
|
+
const loggedIn = !url.includes('/login') && !url.includes('/signin') && (diaryPresented || !loginPresented);
|
|
129
124
|
|
|
130
125
|
// If still on login page, check for error messages (rate limit, wrong creds, etc.)
|
|
131
126
|
let loginError = null;
|
|
@@ -152,6 +147,18 @@ export function buildAutoLoginCode(username, password) {
|
|
|
152
147
|
return { success: true, loggedIn, url, loginError };
|
|
153
148
|
`;
|
|
154
149
|
}
|
|
150
|
+
/**
|
|
151
|
+
* Generate code that sets `diaryPresented` — a positive signal that the diary
|
|
152
|
+
* UI actually rendered, rather than the mere absence of login UI.
|
|
153
|
+
*/
|
|
154
|
+
function buildDiaryPresentedCheckCode() {
|
|
155
|
+
return `
|
|
156
|
+
const diaryPresented = await page.evaluate(() => {
|
|
157
|
+
return !!document.querySelector('i.diary-date-previous, i.diary-date-next') ||
|
|
158
|
+
/Energy\\s+\\d+\\.?\\d*\\s*kcal/i.test(document.body.innerText);
|
|
159
|
+
}).catch(() => false);
|
|
160
|
+
`;
|
|
161
|
+
}
|
|
155
162
|
function buildLoginPresentedCheckCode() {
|
|
156
163
|
return `
|
|
157
164
|
const loginPresented = await page.evaluate(() => {
|
package/dist/kernel/login.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/kernel/login.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;GAGG;AACH,MAAM,UAAU,mBAAmB;IACjC,OAAO;;;;;MAKH,4BAA4B,EAAE
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/kernel/login.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;GAGG;AACH,MAAM,UAAU,mBAAmB;IACjC,OAAO;;;;;MAKH,4BAA4B,EAAE;MAC9B,4BAA4B,EAAE;;;GAGjC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB;IACtC,OAAO;;;GAGN,CAAC;AACJ,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAAC,QAAgB,EAAE,QAAgB;IACnE,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IAC1C,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IAE1C,OAAO;;;;;;;;;;;;;;;kCAeyB,QAAQ;;;;;;;;;;;;;;;;;kCAiBR,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAyCpC,4BAA4B,EAAE;MAC9B,4BAA4B,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BjC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAS,4BAA4B;IACnC,OAAO;;;;;GAKN,CAAC;AACJ,CAAC;AAED,SAAS,4BAA4B;IACnC,OAAO;;;;;;;;;;;;;;;;;;;;GAoBN,CAAC;AACJ,CAAC"}
|