@milldr/crono 0.1.0 → 0.3.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 +128 -0
- package/dist/commands/add.d.ts +9 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +65 -0
- package/dist/commands/add.js.map +1 -0
- package/dist/commands/export.d.ts +8 -0
- package/dist/commands/export.d.ts.map +1 -0
- package/dist/commands/export.js +142 -0
- package/dist/commands/export.js.map +1 -0
- package/dist/commands/log.d.ts +6 -0
- package/dist/commands/log.d.ts.map +1 -0
- package/dist/commands/log.js +40 -0
- package/dist/commands/log.js.map +1 -0
- package/dist/commands/quick-add.d.ts +2 -0
- package/dist/commands/quick-add.d.ts.map +1 -1
- package/dist/commands/quick-add.js +20 -3
- package/dist/commands/quick-add.js.map +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/cronometer/auth.d.ts +31 -0
- package/dist/cronometer/auth.d.ts.map +1 -0
- package/dist/cronometer/auth.js +151 -0
- package/dist/cronometer/auth.js.map +1 -0
- package/dist/cronometer/export.d.ts +22 -0
- package/dist/cronometer/export.d.ts.map +1 -0
- package/dist/cronometer/export.js +83 -0
- package/dist/cronometer/export.js.map +1 -0
- package/dist/cronometer/parse.d.ts +35 -0
- package/dist/cronometer/parse.d.ts.map +1 -0
- package/dist/cronometer/parse.js +158 -0
- package/dist/cronometer/parse.js.map +1 -0
- package/dist/index.js +35 -0
- package/dist/index.js.map +1 -1
- package/dist/kernel/add-custom-food.d.ts +22 -0
- package/dist/kernel/add-custom-food.d.ts.map +1 -0
- package/dist/kernel/add-custom-food.js +314 -0
- package/dist/kernel/add-custom-food.js.map +1 -0
- package/dist/kernel/client.d.ts +17 -0
- package/dist/kernel/client.d.ts.map +1 -1
- package/dist/kernel/client.js +92 -1
- package/dist/kernel/client.js.map +1 -1
- package/dist/kernel/log-food.d.ts +17 -0
- package/dist/kernel/log-food.d.ts.map +1 -0
- package/dist/kernel/log-food.js +230 -0
- package/dist/kernel/log-food.js.map +1 -0
- package/dist/kernel/login.d.ts.map +1 -1
- package/dist/kernel/login.js +24 -1
- package/dist/kernel/login.js.map +1 -1
- package/dist/kernel/quick-add.d.ts.map +1 -1
- package/dist/kernel/quick-add.js +27 -1
- package/dist/kernel/quick-add.js.map +1 -1
- package/dist/utils/date.d.ts +9 -0
- package/dist/utils/date.d.ts.map +1 -1
- package/dist/utils/date.js +22 -0
- package/dist/utils/date.js.map +1 -1
- package/package.json +5 -1
- package/dist/debug-nav.d.ts +0 -2
- package/dist/debug-nav.d.ts.map +0 -1
- package/dist/debug-nav.js +0 -99
- package/dist/debug-nav.js.map +0 -1
|
@@ -0,0 +1,230 @@
|
|
|
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
|
+
/**
|
|
9
|
+
* Generate Playwright code for logging a food to the Cronometer diary.
|
|
10
|
+
*
|
|
11
|
+
* Flow:
|
|
12
|
+
* navigate to #diary → right-click meal → "Add Food" → search food name →
|
|
13
|
+
* select result → set servings → "Add to Diary"
|
|
14
|
+
*/
|
|
15
|
+
export function buildLogFoodCode(entry) {
|
|
16
|
+
const { name, meal, servings } = entry;
|
|
17
|
+
const mealLabel = meal
|
|
18
|
+
? meal.charAt(0).toUpperCase() + meal.slice(1).toLowerCase()
|
|
19
|
+
: "Uncategorized";
|
|
20
|
+
const foodName = JSON.stringify(name);
|
|
21
|
+
const servingCount = servings ?? 1;
|
|
22
|
+
return `
|
|
23
|
+
const foodName = ${foodName};
|
|
24
|
+
const mealLabel = ${JSON.stringify(mealLabel)};
|
|
25
|
+
const servingCount = ${servingCount};
|
|
26
|
+
|
|
27
|
+
// Navigate to diary
|
|
28
|
+
await page.goto('https://cronometer.com/#diary', { waitUntil: 'domcontentloaded', timeout: 15000 });
|
|
29
|
+
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
30
|
+
|
|
31
|
+
// Verify we're logged in
|
|
32
|
+
const url = page.url();
|
|
33
|
+
if (url.includes('/login') || url.includes('/signin')) {
|
|
34
|
+
return { success: false, error: 'Not logged in. Login may have failed.' };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Helper: find and click an element from a list of selectors
|
|
38
|
+
async function clickFirst(selectors, description) {
|
|
39
|
+
for (const sel of selectors) {
|
|
40
|
+
try {
|
|
41
|
+
const el = page.locator(sel);
|
|
42
|
+
if (await el.count() > 0) {
|
|
43
|
+
await el.first().click();
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
} catch {}
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Helper: right-click an element from a list of selectors
|
|
52
|
+
async function rightClickFirst(selectors, description) {
|
|
53
|
+
for (const sel of selectors) {
|
|
54
|
+
try {
|
|
55
|
+
const el = page.locator(sel);
|
|
56
|
+
if (await el.count() > 0) {
|
|
57
|
+
await el.first().click({ button: 'right' });
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
} catch {}
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Right-click the meal category
|
|
66
|
+
const clicked = await rightClickFirst([
|
|
67
|
+
'text="' + mealLabel + '"',
|
|
68
|
+
':has-text("' + mealLabel + '")',
|
|
69
|
+
], 'meal category');
|
|
70
|
+
if (!clicked) {
|
|
71
|
+
return { success: false, error: 'Could not find meal category "' + mealLabel + '" in diary' };
|
|
72
|
+
}
|
|
73
|
+
await page.waitForSelector('text="Add Food..."', { timeout: 3000 }).catch(() =>
|
|
74
|
+
page.waitForSelector('text="Add Food"', { timeout: 2000 }).catch(() => {})
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
// Click "Add Food..." in context menu
|
|
78
|
+
const addFoodClicked = await clickFirst([
|
|
79
|
+
'text="Add Food..."',
|
|
80
|
+
'text="Add Food\u2026"',
|
|
81
|
+
'text="Add Food"',
|
|
82
|
+
'[role="menuitem"]:has-text("Add Food")',
|
|
83
|
+
], 'Add Food menu item');
|
|
84
|
+
if (!addFoodClicked) {
|
|
85
|
+
return { success: false, error: 'Could not find "Add Food" in context menu' };
|
|
86
|
+
}
|
|
87
|
+
await page.waitForTimeout(200);
|
|
88
|
+
|
|
89
|
+
// Wait for "Add Food to Diary" dialog
|
|
90
|
+
try {
|
|
91
|
+
await page.waitForSelector('text="Add Food to Diary"', { timeout: 5000 });
|
|
92
|
+
} catch {
|
|
93
|
+
return { success: false, error: 'Add Food to Diary dialog did not appear' };
|
|
94
|
+
}
|
|
95
|
+
await page.waitForTimeout(300);
|
|
96
|
+
|
|
97
|
+
// Search for the food
|
|
98
|
+
const searchSelectors = [
|
|
99
|
+
'input[placeholder*="Search all foods" i]',
|
|
100
|
+
'input[placeholder*="Search" i]',
|
|
101
|
+
'input[placeholder*="food" i]',
|
|
102
|
+
'input.gwt-TextBox',
|
|
103
|
+
'input[type="text"]',
|
|
104
|
+
'input[type="search"]',
|
|
105
|
+
];
|
|
106
|
+
let searched = false;
|
|
107
|
+
for (const sel of searchSelectors) {
|
|
108
|
+
try {
|
|
109
|
+
const el = page.locator(sel);
|
|
110
|
+
if (await el.count() > 0) {
|
|
111
|
+
await el.first().click();
|
|
112
|
+
await page.waitForTimeout(200);
|
|
113
|
+
await el.first().fill('');
|
|
114
|
+
await page.keyboard.type(foodName, { delay: 50 });
|
|
115
|
+
searched = true;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
} catch {}
|
|
119
|
+
}
|
|
120
|
+
if (!searched) {
|
|
121
|
+
return { success: false, error: 'Could not find food search bar in Add Food dialog' };
|
|
122
|
+
}
|
|
123
|
+
await page.waitForTimeout(300);
|
|
124
|
+
|
|
125
|
+
// Click SEARCH
|
|
126
|
+
await clickFirst([
|
|
127
|
+
'text="SEARCH"',
|
|
128
|
+
'button:has-text("SEARCH")',
|
|
129
|
+
'button:has-text("Search")',
|
|
130
|
+
], 'SEARCH button');
|
|
131
|
+
|
|
132
|
+
// Wait for results
|
|
133
|
+
try {
|
|
134
|
+
await page.waitForSelector('td:has-text("' + foodName + '")', { timeout: 8000 });
|
|
135
|
+
} catch {
|
|
136
|
+
return { success: false, error: 'No food found matching "' + foodName + '"' };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Select the search result
|
|
140
|
+
const resultSelectors = [
|
|
141
|
+
'td:has-text("' + foodName + '")',
|
|
142
|
+
'tr:has-text("' + foodName + '") td',
|
|
143
|
+
'.gwt-HTML:has-text("' + foodName + '")',
|
|
144
|
+
'div:has-text("' + foodName + '"):not(:has(input))',
|
|
145
|
+
];
|
|
146
|
+
let resultClicked = false;
|
|
147
|
+
for (const sel of resultSelectors) {
|
|
148
|
+
try {
|
|
149
|
+
const el = page.locator(sel);
|
|
150
|
+
if (await el.count() > 0) {
|
|
151
|
+
await el.first().click();
|
|
152
|
+
resultClicked = true;
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
} catch {}
|
|
156
|
+
}
|
|
157
|
+
if (!resultClicked) {
|
|
158
|
+
return { success: false, error: 'No food found matching "' + foodName + '"' };
|
|
159
|
+
}
|
|
160
|
+
await page.waitForTimeout(200);
|
|
161
|
+
|
|
162
|
+
// Wait for the detail panel with serving size
|
|
163
|
+
try {
|
|
164
|
+
await page.waitForSelector('text="Serving Size"', { timeout: 5000 });
|
|
165
|
+
} catch {
|
|
166
|
+
return { success: false, error: 'Serving Size panel did not appear for "' + foodName + '"' };
|
|
167
|
+
}
|
|
168
|
+
await page.waitForTimeout(500);
|
|
169
|
+
|
|
170
|
+
// If servings != 1, update the serving size input
|
|
171
|
+
if (servingCount !== 1) {
|
|
172
|
+
let servingFilled = false;
|
|
173
|
+
try {
|
|
174
|
+
servingFilled = await page.evaluate((count) => {
|
|
175
|
+
const walker = document.createTreeWalker(
|
|
176
|
+
document.body,
|
|
177
|
+
NodeFilter.SHOW_TEXT,
|
|
178
|
+
{ acceptNode: (node) =>
|
|
179
|
+
node.textContent && node.textContent.trim() === 'Serving Size'
|
|
180
|
+
? NodeFilter.FILTER_ACCEPT
|
|
181
|
+
: NodeFilter.FILTER_REJECT
|
|
182
|
+
}
|
|
183
|
+
);
|
|
184
|
+
const textNode = walker.nextNode();
|
|
185
|
+
if (!textNode) return false;
|
|
186
|
+
|
|
187
|
+
let container = textNode.parentElement;
|
|
188
|
+
for (let i = 0; i < 5 && container; i++) {
|
|
189
|
+
const input = container.querySelector('input');
|
|
190
|
+
if (input) {
|
|
191
|
+
input.focus();
|
|
192
|
+
input.select();
|
|
193
|
+
const nativeSetter = Object.getOwnPropertyDescriptor(
|
|
194
|
+
window.HTMLInputElement.prototype, 'value'
|
|
195
|
+
).set;
|
|
196
|
+
nativeSetter.call(input, String(count));
|
|
197
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
198
|
+
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
container = container.parentElement;
|
|
202
|
+
}
|
|
203
|
+
return false;
|
|
204
|
+
}, servingCount);
|
|
205
|
+
} catch {}
|
|
206
|
+
|
|
207
|
+
if (!servingFilled) {
|
|
208
|
+
return { success: false, error: 'Could not update serving size for "' + foodName + '"' };
|
|
209
|
+
}
|
|
210
|
+
await page.waitForTimeout(500);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Click "ADD TO DIARY"
|
|
214
|
+
const addClicked = await clickFirst([
|
|
215
|
+
'button:has-text("ADD TO DIARY")',
|
|
216
|
+
'button:has-text("Add to Diary")',
|
|
217
|
+
'text="ADD TO DIARY"',
|
|
218
|
+
'text="Add to Diary"',
|
|
219
|
+
'button[type="submit"]',
|
|
220
|
+
], 'ADD TO DIARY button');
|
|
221
|
+
if (!addClicked) {
|
|
222
|
+
return { success: false, error: 'Could not find "Add to Diary" button' };
|
|
223
|
+
}
|
|
224
|
+
await page.waitForSelector('text="Add Food to Diary"', { state: 'hidden', timeout: 8000 }).catch(() => {});
|
|
225
|
+
await page.waitForTimeout(300);
|
|
226
|
+
|
|
227
|
+
return { success: true };
|
|
228
|
+
`;
|
|
229
|
+
}
|
|
230
|
+
//# sourceMappingURL=log-food.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log-food.js","sourceRoot":"","sources":["../../src/kernel/log-food.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAmB;IAClD,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,KAAK,CAAC;IAEvC,MAAM,SAAS,GAAG,IAAI;QACpB,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE;QAC5D,CAAC,CAAC,eAAe,CAAC;IAEpB,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,YAAY,GAAG,QAAQ,IAAI,CAAC,CAAC;IAEnC,OAAO;uBACc,QAAQ;wBACP,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC;2BACtB,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2MpC,CAAC;AACJ,CAAC"}
|
|
@@ -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,CAQ5C;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CAKjD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,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,CAQ5C;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,CAKjD;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAgH7E"}
|
package/dist/kernel/login.js
CHANGED
|
@@ -119,7 +119,30 @@ export function buildAutoLoginCode(username, password) {
|
|
|
119
119
|
|
|
120
120
|
const url = page.url();
|
|
121
121
|
const loggedIn = !url.includes('/login') && !url.includes('/signin');
|
|
122
|
-
|
|
122
|
+
|
|
123
|
+
// If still on login page, check for error messages (rate limit, wrong creds, etc.)
|
|
124
|
+
let loginError = null;
|
|
125
|
+
if (!loggedIn) {
|
|
126
|
+
loginError = await page.evaluate(() => {
|
|
127
|
+
const selectors = [
|
|
128
|
+
'.error-message', '.alert', '.notification',
|
|
129
|
+
'[class*="error"]', '[class*="alert"]',
|
|
130
|
+
'.gwt-HTML',
|
|
131
|
+
];
|
|
132
|
+
for (const sel of selectors) {
|
|
133
|
+
const els = document.querySelectorAll(sel);
|
|
134
|
+
for (const el of els) {
|
|
135
|
+
const text = el.textContent?.trim();
|
|
136
|
+
if (text && text.length > 5 && text.length < 300 && el.offsetParent !== null) {
|
|
137
|
+
return text;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return { success: true, loggedIn, url, loginError };
|
|
123
146
|
`;
|
|
124
147
|
}
|
|
125
148
|
//# sourceMappingURL=login.js.map
|
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;;;;;;GAMN,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB;IACtC,OAAO;;;GAGN,CAAC;AACJ,CAAC;AAED;;;;GAIG;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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kCAiCyB,QAAQ;;;;;;;;;;;;;;;;;kCAiBR,QAAQ
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/kernel/login.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;GAGG;AACH,MAAM,UAAU,mBAAmB;IACjC,OAAO;;;;;;GAMN,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,wBAAwB;IACtC,OAAO;;;GAGN,CAAC;AACJ,CAAC;AAED;;;;GAIG;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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kCAiCyB,QAAQ;;;;;;;;;;;;;;;;;kCAiBR,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyDvC,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quick-add.d.ts","sourceRoot":"","sources":["../../src/kernel/quick-add.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"quick-add.d.ts","sourceRoot":"","sources":["../../src/kernel/quick-add.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAKrD,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAwQ3D"}
|
package/dist/kernel/quick-add.js
CHANGED
|
@@ -13,6 +13,7 @@ export const MACRO_SEARCH_NAMES = {
|
|
|
13
13
|
protein: "Quick Add, Protein",
|
|
14
14
|
carbs: "Quick Add, Carbohydrate",
|
|
15
15
|
fat: "Quick Add, Fat",
|
|
16
|
+
alcohol: "Quick Add, Alcohol",
|
|
16
17
|
};
|
|
17
18
|
/**
|
|
18
19
|
* Generate Playwright code for adding a quick entry to Cronometer.
|
|
@@ -22,7 +23,7 @@ export const MACRO_SEARCH_NAMES = {
|
|
|
22
23
|
* select result → enter serving size (grams) → "Add to Diary"
|
|
23
24
|
*/
|
|
24
25
|
export function buildQuickAddCode(entry) {
|
|
25
|
-
const { protein, carbs, fat, meal } = entry;
|
|
26
|
+
const { protein, carbs, fat, alcohol, meal, date } = entry;
|
|
26
27
|
const mealLabel = meal
|
|
27
28
|
? meal.charAt(0).toUpperCase() + meal.slice(1).toLowerCase()
|
|
28
29
|
: "Uncategorized";
|
|
@@ -46,21 +47,46 @@ export function buildQuickAddCode(entry) {
|
|
|
46
47
|
searchName: MACRO_SEARCH_NAMES.fat,
|
|
47
48
|
grams: fat,
|
|
48
49
|
});
|
|
50
|
+
if (alcohol !== undefined)
|
|
51
|
+
macros.push({
|
|
52
|
+
name: "alcohol",
|
|
53
|
+
searchName: MACRO_SEARCH_NAMES.alcohol,
|
|
54
|
+
grams: alcohol,
|
|
55
|
+
});
|
|
49
56
|
const macrosJson = JSON.stringify(macros);
|
|
50
57
|
return `
|
|
51
58
|
const macros = ${macrosJson};
|
|
52
59
|
const mealLabel = ${JSON.stringify(mealLabel)};
|
|
60
|
+
const targetDate = ${JSON.stringify(date ?? null)};
|
|
53
61
|
|
|
54
62
|
// Navigate to diary — we're already logged in from the same session
|
|
55
63
|
await page.goto('https://cronometer.com/#diary', { waitUntil: 'domcontentloaded', timeout: 15000 });
|
|
56
64
|
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
57
65
|
|
|
66
|
+
// Wait for the diary to fully render
|
|
67
|
+
await page.waitForTimeout(2000);
|
|
68
|
+
|
|
58
69
|
// Verify we're logged in
|
|
59
70
|
const url = page.url();
|
|
60
71
|
if (url.includes('/login') || url.includes('/signin')) {
|
|
61
72
|
return { success: false, error: 'Not logged in. Login may have failed.' };
|
|
62
73
|
}
|
|
63
74
|
|
|
75
|
+
// Navigate to the target date using prev-day arrows (same approach as diary/weight)
|
|
76
|
+
if (targetDate) {
|
|
77
|
+
const today = new Date();
|
|
78
|
+
today.setHours(0, 0, 0, 0);
|
|
79
|
+
const target = new Date(targetDate + 'T00:00:00');
|
|
80
|
+
const daysBack = Math.round((today - target) / (1000 * 60 * 60 * 24));
|
|
81
|
+
for (let s = 0; s < daysBack && s < 90; s++) {
|
|
82
|
+
const prev = page.locator('i.diary-date-previous').filter({ visible: true });
|
|
83
|
+
if (await prev.count() > 0) {
|
|
84
|
+
await prev.first().click();
|
|
85
|
+
await page.waitForTimeout(2000);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
64
90
|
// Helper: find and click an element from a list of selectors
|
|
65
91
|
async function clickFirst(selectors, description) {
|
|
66
92
|
for (const sel of selectors) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"quick-add.js","sourceRoot":"","sources":["../../src/kernel/quick-add.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAA2B;IACxD,OAAO,EAAE,oBAAoB;IAC7B,KAAK,EAAE,yBAAyB;IAChC,GAAG,EAAE,gBAAgB;
|
|
1
|
+
{"version":3,"file":"quick-add.js","sourceRoot":"","sources":["../../src/kernel/quick-add.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAA2B;IACxD,OAAO,EAAE,oBAAoB;IAC7B,KAAK,EAAE,yBAAyB;IAChC,GAAG,EAAE,gBAAgB;IACrB,OAAO,EAAE,oBAAoB;CAC9B,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAiB;IACjD,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC;IAE3D,MAAM,SAAS,GAAG,IAAI;QACpB,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE;QAC5D,CAAC,CAAC,eAAe,CAAC;IAEpB,8BAA8B;IAC9B,MAAM,MAAM,GAA0D,EAAE,CAAC;IACzE,IAAI,OAAO,KAAK,SAAS;QACvB,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,SAAS;YACf,UAAU,EAAE,kBAAkB,CAAC,OAAO;YACtC,KAAK,EAAE,OAAO;SACf,CAAC,CAAC;IACL,IAAI,KAAK,KAAK,SAAS;QACrB,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,OAAO;YACb,UAAU,EAAE,kBAAkB,CAAC,KAAK;YACpC,KAAK,EAAE,KAAK;SACb,CAAC,CAAC;IACL,IAAI,GAAG,KAAK,SAAS;QACnB,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,KAAK;YACX,UAAU,EAAE,kBAAkB,CAAC,GAAG;YAClC,KAAK,EAAE,GAAG;SACX,CAAC,CAAC;IACL,IAAI,OAAO,KAAK,SAAS;QACvB,MAAM,CAAC,IAAI,CAAC;YACV,IAAI,EAAE,SAAS;YACf,UAAU,EAAE,kBAAkB,CAAC,OAAO;YACtC,KAAK,EAAE,OAAO;SACf,CAAC,CAAC;IAEL,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAE1C,OAAO;qBACY,UAAU;wBACP,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC;yBACxB,IAAI,CAAC,SAAS,CAAC,IAAI,IAAI,IAAI,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgOlD,CAAC;AACJ,CAAC"}
|
package/dist/utils/date.d.ts
CHANGED
|
@@ -12,6 +12,15 @@ export declare function parseDate(str: string): string;
|
|
|
12
12
|
export declare function formatDate(date: Date): string;
|
|
13
13
|
/** Return today's date as YYYY-MM-DD. */
|
|
14
14
|
export declare function todayStr(): string;
|
|
15
|
+
/**
|
|
16
|
+
* Resolve a date input string to YYYY-MM-DD.
|
|
17
|
+
*
|
|
18
|
+
* Supports:
|
|
19
|
+
* - "yesterday"
|
|
20
|
+
* - Relative: "-1d", "-7d" (N days ago)
|
|
21
|
+
* - Absolute: "YYYY-MM-DD"
|
|
22
|
+
*/
|
|
23
|
+
export declare function resolveDate(input: string): string;
|
|
15
24
|
/**
|
|
16
25
|
* Parse a range spec into start/end date strings.
|
|
17
26
|
*
|
package/dist/utils/date.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"date.d.ts","sourceRoot":"","sources":["../../src/utils/date.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH;;;GAGG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAa7C;AAED,0CAA0C;AAC1C,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAK7C;AAED,yCAAyC;AACzC,wBAAgB,QAAQ,IAAI,MAAM,CAEjC;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CA0BvE;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAW9D"}
|
|
1
|
+
{"version":3,"file":"date.d.ts","sourceRoot":"","sources":["../../src/utils/date.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH;;;GAGG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAa7C;AAED,0CAA0C;AAC1C,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CAK7C;AAED,yCAAyC;AACzC,wBAAgB,QAAQ,IAAI,MAAM,CAEjC;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAajD;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CA0BvE;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAW9D"}
|
package/dist/utils/date.js
CHANGED
|
@@ -35,6 +35,28 @@ export function formatDate(date) {
|
|
|
35
35
|
export function todayStr() {
|
|
36
36
|
return formatDate(new Date());
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Resolve a date input string to YYYY-MM-DD.
|
|
40
|
+
*
|
|
41
|
+
* Supports:
|
|
42
|
+
* - "yesterday"
|
|
43
|
+
* - Relative: "-1d", "-7d" (N days ago)
|
|
44
|
+
* - Absolute: "YYYY-MM-DD"
|
|
45
|
+
*/
|
|
46
|
+
export function resolveDate(input) {
|
|
47
|
+
if (input === "yesterday") {
|
|
48
|
+
const d = new Date();
|
|
49
|
+
d.setDate(d.getDate() - 1);
|
|
50
|
+
return formatDate(d);
|
|
51
|
+
}
|
|
52
|
+
const relMatch = input.match(/^-(\d+)d$/);
|
|
53
|
+
if (relMatch) {
|
|
54
|
+
const d = new Date();
|
|
55
|
+
d.setDate(d.getDate() - parseInt(relMatch[1], 10));
|
|
56
|
+
return formatDate(d);
|
|
57
|
+
}
|
|
58
|
+
return parseDate(input);
|
|
59
|
+
}
|
|
38
60
|
/**
|
|
39
61
|
* Parse a range spec into start/end date strings.
|
|
40
62
|
*
|
package/dist/utils/date.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"date.js","sourceRoot":"","sources":["../../src/utils/date.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"date.js","sourceRoot":"","sources":["../../src/utils/date.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,OAAO,GAAG,qBAAqB,CAAC;AACtC,MAAM,WAAW,GAAG,UAAU,CAAC;AAC/B,MAAM,WAAW,GAAG,2CAA2C,CAAC;AAEhE;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,wBAAwB,GAAG,mBAAmB,CAAC,CAAC;IAClE,CAAC;IACD,MAAM,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,GAAG,WAAW,CAAC,CAAC;IACtC,IAAI,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,iBAAiB,GAAG,GAAG,CAAC,CAAC;IAC3C,CAAC;IACD,4EAA4E;IAC5E,IAAI,UAAU,CAAC,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CAAC,iBAAiB,GAAG,GAAG,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,0CAA0C;AAC1C,MAAM,UAAU,UAAU,CAAC,IAAU;IACnC,MAAM,CAAC,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IAC7B,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACvD,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAClD,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;AAC1B,CAAC;AAED,yCAAyC;AACzC,MAAM,UAAU,QAAQ;IACtB,OAAO,UAAU,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;AAChC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,IAAI,KAAK,KAAK,WAAW,EAAE,CAAC;QAC1B,MAAM,CAAC,GAAG,IAAI,IAAI,EAAE,CAAC;QACrB,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;QAC3B,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC;IACvB,CAAC;IACD,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAC1C,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,CAAC,GAAG,IAAI,IAAI,EAAE,CAAC;QACrB,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,EAAE,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;QACnD,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC;IACvB,CAAC;IACD,OAAO,SAAS,CAAC,KAAK,CAAC,CAAC;AAC1B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IACzC,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACvC,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC;YACd,MAAM,IAAI,KAAK,CAAC,kBAAkB,IAAI,+BAA+B,CAAC,CAAC;QACzE,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,MAAM,KAAK,GAAG,IAAI,IAAI,EAAE,CAAC;QACzB,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC;QAC5C,OAAO,EAAE,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;IAC5D,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IACzC,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QACrC,MAAM,GAAG,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QACnC,IAAI,KAAK,GAAG,GAAG,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,yBAAyB,KAAK,mBAAmB,GAAG,GAAG,CAAC,CAAC;QAC3E,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;IACxB,CAAC;IAED,MAAM,IAAI,KAAK,CACb,yBAAyB,IAAI,gDAAgD,CAC9E,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,KAAa,EAAE,GAAW;IAClD,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,MAAM,GAAG,GAAG,IAAI,IAAI,CAAC,GAAG,GAAG,WAAW,CAAC,CAAC;IACxC,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC,CAAC;IAEhD,OAAO,GAAG,IAAI,SAAS,EAAE,CAAC;QACxB,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;QAC5B,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;IACjC,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@milldr/crono",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "CLI for Cronometer automation via Kernel.sh",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -26,6 +26,10 @@
|
|
|
26
26
|
"automation",
|
|
27
27
|
"kernel"
|
|
28
28
|
],
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "https://github.com/milldr/crono"
|
|
32
|
+
},
|
|
29
33
|
"author": "Daniel Miller",
|
|
30
34
|
"license": "MIT",
|
|
31
35
|
"engines": {
|
package/dist/debug-nav.d.ts
DELETED
package/dist/debug-nav.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"debug-nav.d.ts","sourceRoot":"","sources":["../src/debug-nav.ts"],"names":[],"mappings":""}
|
package/dist/debug-nav.js
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Debug script: dump date navigation elements from Cronometer diary.
|
|
3
|
-
* Run with: npx tsx src/debug-nav.ts
|
|
4
|
-
*/
|
|
5
|
-
import Kernel from "@onkernel/sdk";
|
|
6
|
-
import { getCredential } from "./credentials.js";
|
|
7
|
-
import { buildAutoLoginCode } from "./kernel/login.js";
|
|
8
|
-
async function main() {
|
|
9
|
-
const apiKey = process.env["KERNEL_API_KEY"] ?? getCredential("kernel-api-key");
|
|
10
|
-
process.env["KERNEL_API_KEY"] = apiKey;
|
|
11
|
-
const kernel = new Kernel();
|
|
12
|
-
const browser = await kernel.browsers.create({
|
|
13
|
-
headless: true,
|
|
14
|
-
stealth: true,
|
|
15
|
-
timeout_seconds: 120,
|
|
16
|
-
});
|
|
17
|
-
try {
|
|
18
|
-
const username = getCredential("cronometer-username");
|
|
19
|
-
const password = getCredential("cronometer-password");
|
|
20
|
-
// Login
|
|
21
|
-
await kernel.browsers.playwright.execute(browser.session_id, {
|
|
22
|
-
code: buildAutoLoginCode(username, password),
|
|
23
|
-
timeout_sec: 60,
|
|
24
|
-
});
|
|
25
|
-
// Navigate to diary and dump nav info
|
|
26
|
-
const result = await kernel.browsers.playwright.execute(browser.session_id, {
|
|
27
|
-
code: `
|
|
28
|
-
await page.goto('https://cronometer.com/#diary', { waitUntil: 'domcontentloaded', timeout: 15000 });
|
|
29
|
-
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
30
|
-
await page.waitForTimeout(3000);
|
|
31
|
-
|
|
32
|
-
const debug = await page.evaluate(() => {
|
|
33
|
-
// 1. All images and their src attributes
|
|
34
|
-
const imgs = Array.from(document.querySelectorAll('img')).map(img => ({
|
|
35
|
-
src: img.getAttribute('src'),
|
|
36
|
-
alt: img.getAttribute('alt'),
|
|
37
|
-
visible: img.offsetParent !== null,
|
|
38
|
-
width: img.offsetWidth,
|
|
39
|
-
height: img.offsetHeight,
|
|
40
|
-
classes: img.className,
|
|
41
|
-
}));
|
|
42
|
-
|
|
43
|
-
// 2. All buttons/clickable elements near the top of the page
|
|
44
|
-
const buttons = Array.from(document.querySelectorAll('button, [role="button"], .gwt-PushButton')).map(el => ({
|
|
45
|
-
text: (el.textContent || '').trim().substring(0, 80),
|
|
46
|
-
classes: el.className,
|
|
47
|
-
visible: el.offsetParent !== null,
|
|
48
|
-
tag: el.tagName,
|
|
49
|
-
}));
|
|
50
|
-
|
|
51
|
-
// 3. Elements that might be date-related
|
|
52
|
-
const dateEls = Array.from(document.querySelectorAll('[class*="date" i], [class*="calendar" i], [class*="nav" i], [class*="arrow" i], [class*="picker" i]')).map(el => ({
|
|
53
|
-
tag: el.tagName,
|
|
54
|
-
classes: el.className,
|
|
55
|
-
text: (el.textContent || '').trim().substring(0, 80),
|
|
56
|
-
visible: el.offsetParent !== null,
|
|
57
|
-
html: el.outerHTML.substring(0, 200),
|
|
58
|
-
}));
|
|
59
|
-
|
|
60
|
-
// 4. Look for any element containing a date-like text (month name)
|
|
61
|
-
const dateTexts = [];
|
|
62
|
-
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
|
|
63
|
-
let node;
|
|
64
|
-
while (node = walker.nextNode()) {
|
|
65
|
-
const t = (node.textContent || '').trim();
|
|
66
|
-
if (/(?:January|February|March|April|May|June|July|August|September|October|November|December)/i.test(t)) {
|
|
67
|
-
dateTexts.push({
|
|
68
|
-
text: t.substring(0, 100),
|
|
69
|
-
parentTag: node.parentElement?.tagName,
|
|
70
|
-
parentClasses: node.parentElement?.className,
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// 5. GWT PushButton elements (Cronometer uses GWT)
|
|
76
|
-
const pushButtons = Array.from(document.querySelectorAll('.gwt-PushButton, .gwt-PushButton-up, .gwt-PushButton-down')).map(el => ({
|
|
77
|
-
classes: el.className,
|
|
78
|
-
html: el.outerHTML.substring(0, 300),
|
|
79
|
-
visible: el.offsetParent !== null,
|
|
80
|
-
}));
|
|
81
|
-
|
|
82
|
-
return { imgs, buttons, dateEls, dateTexts, pushButtons };
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
return debug;
|
|
86
|
-
`,
|
|
87
|
-
timeout_sec: 60,
|
|
88
|
-
});
|
|
89
|
-
console.log(JSON.stringify(result.result, null, 2));
|
|
90
|
-
}
|
|
91
|
-
finally {
|
|
92
|
-
try {
|
|
93
|
-
await kernel.browsers.deleteByID(browser.session_id);
|
|
94
|
-
}
|
|
95
|
-
catch { }
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
main().catch(console.error);
|
|
99
|
-
//# sourceMappingURL=debug-nav.js.map
|
package/dist/debug-nav.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"debug-nav.js","sourceRoot":"","sources":["../src/debug-nav.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,MAAM,MAAM,eAAe,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEvD,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GACV,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,aAAa,CAAC,gBAAgB,CAAC,CAAC;IACnE,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,GAAG,MAAO,CAAC;IAExC,MAAM,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;IAC5B,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;QAC3C,QAAQ,EAAE,IAAI;QACd,OAAO,EAAE,IAAI;QACb,eAAe,EAAE,GAAG;KACrB,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,aAAa,CAAC,qBAAqB,CAAE,CAAC;QACvD,MAAM,QAAQ,GAAG,aAAa,CAAC,qBAAqB,CAAE,CAAC;QAEvD,QAAQ;QACR,MAAM,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,EAAE;YAC3D,IAAI,EAAE,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,CAAC;YAC5C,WAAW,EAAE,EAAE;SAChB,CAAC,CAAC;QAEH,sCAAsC;QACtC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CACrD,OAAO,CAAC,UAAU,EAClB;YACE,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;SA2DL;YACD,WAAW,EAAE,EAAE;SAChB,CACF,CAAC;QAEF,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACtD,CAAC;YAAS,CAAC;QACT,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACvD,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACZ,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC"}
|