@milldr/crono 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +219 -0
  3. package/dist/commands/diary.d.ts +7 -0
  4. package/dist/commands/diary.d.ts.map +1 -0
  5. package/dist/commands/diary.js +79 -0
  6. package/dist/commands/diary.js.map +1 -0
  7. package/dist/commands/login.d.ts +19 -0
  8. package/dist/commands/login.d.ts.map +1 -0
  9. package/dist/commands/login.js +160 -0
  10. package/dist/commands/login.js.map +1 -0
  11. package/dist/commands/quick-add.d.ts +8 -0
  12. package/dist/commands/quick-add.d.ts.map +1 -0
  13. package/dist/commands/quick-add.js +49 -0
  14. package/dist/commands/quick-add.js.map +1 -0
  15. package/dist/commands/weight.d.ts +7 -0
  16. package/dist/commands/weight.d.ts.map +1 -0
  17. package/dist/commands/weight.js +78 -0
  18. package/dist/commands/weight.js.map +1 -0
  19. package/dist/config.d.ts +17 -0
  20. package/dist/config.d.ts.map +1 -0
  21. package/dist/config.js +43 -0
  22. package/dist/config.js.map +1 -0
  23. package/dist/credentials.d.ts +27 -0
  24. package/dist/credentials.d.ts.map +1 -0
  25. package/dist/credentials.js +178 -0
  26. package/dist/credentials.js.map +1 -0
  27. package/dist/debug-nav.d.ts +2 -0
  28. package/dist/debug-nav.d.ts.map +1 -0
  29. package/dist/debug-nav.js +99 -0
  30. package/dist/debug-nav.js.map +1 -0
  31. package/dist/index.d.ts +3 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +47 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/kernel/client.d.ts +40 -0
  36. package/dist/kernel/client.d.ts.map +1 -0
  37. package/dist/kernel/client.js +205 -0
  38. package/dist/kernel/client.js.map +1 -0
  39. package/dist/kernel/diary.d.ts +22 -0
  40. package/dist/kernel/diary.d.ts.map +1 -0
  41. package/dist/kernel/diary.js +156 -0
  42. package/dist/kernel/diary.js.map +1 -0
  43. package/dist/kernel/login.d.ts +24 -0
  44. package/dist/kernel/login.d.ts.map +1 -0
  45. package/dist/kernel/login.js +125 -0
  46. package/dist/kernel/login.js.map +1 -0
  47. package/dist/kernel/quick-add.d.ts +22 -0
  48. package/dist/kernel/quick-add.d.ts.map +1 -0
  49. package/dist/kernel/quick-add.js +260 -0
  50. package/dist/kernel/quick-add.js.map +1 -0
  51. package/dist/kernel/weight.d.ts +20 -0
  52. package/dist/kernel/weight.d.ts.map +1 -0
  53. package/dist/kernel/weight.js +160 -0
  54. package/dist/kernel/weight.js.map +1 -0
  55. package/dist/utils/date.d.ts +31 -0
  56. package/dist/utils/date.d.ts.map +1 -0
  57. package/dist/utils/date.js +82 -0
  58. package/dist/utils/date.js.map +1 -0
  59. package/package.json +54 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Daniel Miller
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,219 @@
1
+ # 🍎 crono
2
+
3
+ CLI for Cronometer automation via [Kernel.sh](https://kernel.sh).
4
+
5
+ Cronometer has no public API, so crono automates the web UI through Kernel.sh browser automation. Log macros from your terminal in seconds.
6
+
7
+ ![crono quick-add demo](demo.gif)
8
+
9
+ ## Quickstart
10
+
11
+ ### 1. Install
12
+
13
+ ```bash
14
+ npm install -g @milldr/crono
15
+ ```
16
+
17
+ ### 2. Log in
18
+
19
+ ```bash
20
+ crono login
21
+ ```
22
+
23
+ You'll be prompted for three things:
24
+
25
+ - **Kernel API key** — get one at [kernel.sh](https://kernel.sh)
26
+ - **Cronometer email** — your Cronometer account email
27
+ - **Cronometer password** — stored securely in your system keychain
28
+
29
+ ```
30
+ ┌ crono login
31
+
32
+ ◇ Kernel API key
33
+ │ sk-abc...
34
+
35
+ ◒ Validating API key...
36
+ ◇ API key valid.
37
+
38
+ ◇ Cronometer email
39
+ │ you@example.com
40
+
41
+ ◇ Cronometer password
42
+ │ ****
43
+
44
+ └ Credentials saved.
45
+ ```
46
+
47
+ ### 3. Log a meal
48
+
49
+ ```bash
50
+ crono quick-add -p 30 -c 100 -f 20 -m Dinner
51
+ ```
52
+
53
+ ```
54
+ ┌ crono quick-add
55
+
56
+ ◒ Logging into Cronometer...
57
+ ◇ Done.
58
+
59
+ └ Added: 30g protein, 100g carbs, 20g fat → Dinner
60
+ ```
61
+
62
+ ## Commands
63
+
64
+ ### `crono login`
65
+
66
+ Set up or update your Kernel API key and Cronometer credentials. If credentials already exist, pressing Enter keeps the current value.
67
+
68
+ ```bash
69
+ crono login
70
+ ```
71
+
72
+ ### `crono quick-add`
73
+
74
+ Add a quick macro entry to your Cronometer diary.
75
+
76
+ ```bash
77
+ crono quick-add [options]
78
+ ```
79
+
80
+ **Options:**
81
+
82
+ | Flag | Long | Description |
83
+ | ---- | --------------- | ------------------------------------------------ |
84
+ | `-p` | `--protein <g>` | Grams of protein |
85
+ | `-c` | `--carbs <g>` | Grams of carbohydrates |
86
+ | `-f` | `--fat <g>` | Grams of fat |
87
+ | `-m` | `--meal <name>` | Meal category (Breakfast, Lunch, Dinner, Snacks) |
88
+
89
+ At least one macro flag (`-p`, `-c`, or `-f`) is required.
90
+
91
+ **Examples:**
92
+
93
+ ```bash
94
+ # Log 30g protein
95
+ crono quick-add -p 30
96
+
97
+ # Log full meal macros
98
+ crono quick-add -p 30 -c 100 -f 20
99
+
100
+ # Log to Dinner category
101
+ crono quick-add -p 30 -c 50 -f 15 --meal Dinner
102
+ ```
103
+
104
+ ### `crono weight`
105
+
106
+ Check your weight from Cronometer. Defaults to today if no date or range is specified.
107
+
108
+ ```bash
109
+ crono weight [options]
110
+ ```
111
+
112
+ **Options:**
113
+
114
+ | Flag | Long | Description |
115
+ | ---- | ----------------- | ----------------------------------------- |
116
+ | `-d` | `--date <date>` | Date (YYYY-MM-DD) |
117
+ | `-r` | `--range <range>` | Range (7d, 30d, or YYYY-MM-DD:YYYY-MM-DD) |
118
+ | | `--json` | Output as JSON |
119
+
120
+ `-d` and `-r` are mutually exclusive.
121
+
122
+ **Examples:**
123
+
124
+ ```bash
125
+ # Today's weight
126
+ crono weight
127
+ # → 212.5 lbs
128
+
129
+ # Specific date
130
+ crono weight -d 2026-02-05
131
+
132
+ # Last 7 days
133
+ crono weight -r 7d
134
+ # → 2026-02-11: 212.5
135
+ # → 2026-02-10: 212.7
136
+ # → 2026-02-09: 215.5
137
+ # → ...
138
+
139
+ # JSON output for scripting
140
+ crono weight --json
141
+ # → {"date":"2026-02-11","weight":212.5,"unit":"lbs"}
142
+
143
+ # Range as JSON
144
+ crono weight -r 7d --json
145
+ # → [{"date":"2026-02-11","weight":212.5,"unit":"lbs"}, ...]
146
+ ```
147
+
148
+ ### `crono diary`
149
+
150
+ View daily nutrition totals (calories, protein, carbs, fat) from Cronometer. Defaults to today if no date or range is specified.
151
+
152
+ ```bash
153
+ crono diary [options]
154
+ ```
155
+
156
+ **Options:**
157
+
158
+ | Flag | Long | Description |
159
+ | ---- | ----------------- | ----------------------------------------- |
160
+ | `-d` | `--date <date>` | Date (YYYY-MM-DD) |
161
+ | `-r` | `--range <range>` | Range (7d, 30d, or YYYY-MM-DD:YYYY-MM-DD) |
162
+ | | `--json` | Output as JSON |
163
+
164
+ `-d` and `-r` are mutually exclusive.
165
+
166
+ **Examples:**
167
+
168
+ ```bash
169
+ # Today's nutrition
170
+ crono diary
171
+ # → 1847 kcal | P: 168g | C: 142g | F: 58g
172
+
173
+ # Specific date
174
+ crono diary -d 2026-02-05
175
+
176
+ # Last 7 days
177
+ crono diary -r 7d
178
+ # → 2026-02-11: 1847 kcal | P: 168g | C: 142g | F: 58g
179
+ # → 2026-02-10: 2103 kcal | P: 155g | C: 200g | F: 72g
180
+ # → ...
181
+
182
+ # JSON output for scripting
183
+ crono diary --json
184
+ # → {"date":"2026-02-11","calories":1847,"protein":168,"carbs":142,"fat":58}
185
+
186
+ # Range as JSON
187
+ crono diary -r 7d --json
188
+ # → [{"date":"2026-02-11","calories":1847,"protein":168,"carbs":142,"fat":58}, ...]
189
+ ```
190
+
191
+ ## Requirements
192
+
193
+ - Node.js 18+
194
+ - [Kernel.sh](https://kernel.sh) account (for browser automation)
195
+ - [Cronometer](https://cronometer.com) account
196
+
197
+ ## Development
198
+
199
+ ```bash
200
+ git clone https://github.com/milldr/crono.git
201
+ cd crono
202
+ npm install
203
+
204
+ # Run in dev mode
205
+ npm run dev -- login
206
+ npm run dev -- quick-add -p 30
207
+ npm run dev -- weight -r 7d
208
+ npm run dev -- diary
209
+
210
+ # Run tests
211
+ npm test
212
+
213
+ # Build
214
+ npm run build
215
+ ```
216
+
217
+ ## License
218
+
219
+ MIT
@@ -0,0 +1,7 @@
1
+ export interface DiaryOptions {
2
+ date?: string;
3
+ range?: string;
4
+ json?: boolean;
5
+ }
6
+ export declare function diary(options: DiaryOptions): Promise<void>;
7
+ //# sourceMappingURL=diary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diary.d.ts","sourceRoot":"","sources":["../../src/commands/diary.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAiEhE"}
@@ -0,0 +1,79 @@
1
+ import * as p from "@clack/prompts";
2
+ import { getKernelClient } from "../kernel/client.js";
3
+ import { parseDate, parseRange, dateRange, todayStr } from "../utils/date.js";
4
+ export async function diary(options) {
5
+ // Validate mutual exclusivity
6
+ if (options.date && options.range) {
7
+ p.log.error("-d and -r are mutually exclusive");
8
+ process.exit(1);
9
+ }
10
+ // Resolve dates to fetch
11
+ let dates;
12
+ let isRange = false;
13
+ try {
14
+ if (options.range) {
15
+ isRange = true;
16
+ const { start, end } = parseRange(options.range);
17
+ dates = dateRange(start, end);
18
+ }
19
+ else if (options.date) {
20
+ dates = [parseDate(options.date)];
21
+ }
22
+ else {
23
+ dates = [todayStr()];
24
+ }
25
+ }
26
+ catch (error) {
27
+ p.log.error(String(error instanceof Error ? error.message : error));
28
+ process.exit(1);
29
+ }
30
+ if (!options.json) {
31
+ p.intro("🍎 crono diary");
32
+ }
33
+ const s = options.json ? null : p.spinner();
34
+ s?.start("Connecting...");
35
+ try {
36
+ const kernel = await getKernelClient();
37
+ const entries = await kernel.getDiary(dates, (msg) => s?.message(msg));
38
+ s?.stop("Done.");
39
+ if (options.json) {
40
+ if (isRange) {
41
+ console.log(JSON.stringify(entries, null, 2));
42
+ }
43
+ else {
44
+ console.log(JSON.stringify(entries[0] ?? {
45
+ date: dates[0],
46
+ calories: 0,
47
+ protein: 0,
48
+ carbs: 0,
49
+ fat: 0,
50
+ }, null, 2));
51
+ }
52
+ }
53
+ else {
54
+ formatPlainText(entries, isRange);
55
+ }
56
+ }
57
+ catch (error) {
58
+ s?.stop("Failed.");
59
+ p.log.error(`Failed to read diary: ${error}`);
60
+ process.exit(1);
61
+ }
62
+ }
63
+ function formatPlainText(entries, isRange) {
64
+ if (!isRange) {
65
+ const entry = entries[0];
66
+ if (!entry) {
67
+ p.outro("No diary data found");
68
+ }
69
+ else {
70
+ p.outro(`${entry.calories} kcal | P: ${entry.protein}g | C: ${entry.carbs}g | F: ${entry.fat}g`);
71
+ }
72
+ return;
73
+ }
74
+ // Range output
75
+ for (const entry of entries) {
76
+ p.log.info(`${entry.date}: ${entry.calories} kcal | P: ${entry.protein}g | C: ${entry.carbs}g | F: ${entry.fat}g`);
77
+ }
78
+ }
79
+ //# sourceMappingURL=diary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diary.js","sourceRoot":"","sources":["../../src/commands/diary.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,gBAAgB,CAAC;AACpC,OAAO,EAAE,eAAe,EAAkB,MAAM,qBAAqB,CAAC;AACtE,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAQ9E,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,OAAqB;IAC/C,8BAA8B;IAC9B,IAAI,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QAClC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;QAChD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,yBAAyB;IACzB,IAAI,KAAe,CAAC;IACpB,IAAI,OAAO,GAAG,KAAK,CAAC;IAEpB,IAAI,CAAC;QACH,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;YAClB,OAAO,GAAG,IAAI,CAAC;YACf,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YACjD,KAAK,GAAG,SAAS,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAChC,CAAC;aAAM,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;YACxB,KAAK,GAAG,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QACpC,CAAC;aAAM,CAAC;YACN,KAAK,GAAG,CAAC,QAAQ,EAAE,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;QACpE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;QAClB,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;IAC5B,CAAC;IAED,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC;IAC5C,CAAC,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;IAE1B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,eAAe,EAAE,CAAC;QACvC,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;QAEvE,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;QAEjB,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;YACjB,IAAI,OAAO,EAAE,CAAC;gBACZ,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;YAChD,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,GAAG,CACT,IAAI,CAAC,SAAS,CACZ,OAAO,CAAC,CAAC,CAAC,IAAI;oBACZ,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;oBACd,QAAQ,EAAE,CAAC;oBACX,OAAO,EAAE,CAAC;oBACV,KAAK,EAAE,CAAC;oBACR,GAAG,EAAE,CAAC;iBACP,EACD,IAAI,EACJ,CAAC,CACF,CACF,CAAC;YACJ,CAAC;QACH,CAAC;aAAM,CAAC;YACN,eAAe,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;QACnB,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,yBAAyB,KAAK,EAAE,CAAC,CAAC;QAC9C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,SAAS,eAAe,CAAC,OAAoB,EAAE,OAAgB;IAC7D,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,KAAK,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;QACzB,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,CAAC,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;QACjC,CAAC;aAAM,CAAC;YACN,CAAC,CAAC,KAAK,CACL,GAAG,KAAK,CAAC,QAAQ,cAAc,KAAK,CAAC,OAAO,UAAU,KAAK,CAAC,KAAK,UAAU,KAAK,CAAC,GAAG,GAAG,CACxF,CAAC;QACJ,CAAC;QACD,OAAO;IACT,CAAC;IAED,eAAe;IACf,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,CAAC,CAAC,GAAG,CAAC,IAAI,CACR,GAAG,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,QAAQ,cAAc,KAAK,CAAC,OAAO,UAAU,KAAK,CAAC,KAAK,UAAU,KAAK,CAAC,GAAG,GAAG,CACvG,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Login command — prompts for Kernel API key and Cronometer credentials,
3
+ * validates them, and stores them securely.
4
+ *
5
+ * Uses @clack/prompts for styled terminal UI with cancellation handling.
6
+ */
7
+ /**
8
+ * Validate a Kernel API key by attempting to list profiles.
9
+ * Throws if the key is invalid.
10
+ */
11
+ export declare function validateKernelApiKey(apiKey: string): Promise<void>;
12
+ /**
13
+ * Interactive login flow.
14
+ *
15
+ * Each field shows the existing value (masked) if present.
16
+ * Pressing Enter without typing keeps the existing value.
17
+ */
18
+ export declare function login(): Promise<void>;
19
+ //# sourceMappingURL=login.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAaxE;AAWD;;;;;GAKG;AACH,wBAAsB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAmI3C"}
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Login command — prompts for Kernel API key and Cronometer credentials,
3
+ * validates them, and stores them securely.
4
+ *
5
+ * Uses @clack/prompts for styled terminal UI with cancellation handling.
6
+ */
7
+ import * as p from "@clack/prompts";
8
+ import Kernel from "@onkernel/sdk";
9
+ import { getCredential, setCredential } from "../credentials.js";
10
+ /**
11
+ * Validate a Kernel API key by attempting to list profiles.
12
+ * Throws if the key is invalid.
13
+ */
14
+ export async function validateKernelApiKey(apiKey) {
15
+ const prevKey = process.env["KERNEL_API_KEY"];
16
+ try {
17
+ process.env["KERNEL_API_KEY"] = apiKey;
18
+ const kernel = new Kernel();
19
+ await kernel.profiles.list();
20
+ }
21
+ finally {
22
+ if (prevKey === undefined) {
23
+ delete process.env["KERNEL_API_KEY"];
24
+ }
25
+ else {
26
+ process.env["KERNEL_API_KEY"] = prevKey;
27
+ }
28
+ }
29
+ }
30
+ /**
31
+ * Mask a string for display: show first 4 chars then stars.
32
+ * For short strings (<=4), show all stars.
33
+ */
34
+ function mask(value) {
35
+ if (value.length <= 4)
36
+ return "*".repeat(value.length);
37
+ return value.slice(0, 4) + "*".repeat(value.length - 4);
38
+ }
39
+ /**
40
+ * Interactive login flow.
41
+ *
42
+ * Each field shows the existing value (masked) if present.
43
+ * Pressing Enter without typing keeps the existing value.
44
+ */
45
+ export async function login() {
46
+ const existingKey = getCredential("kernel-api-key");
47
+ const existingEmail = getCredential("cronometer-username");
48
+ const existingPassword = getCredential("cronometer-password");
49
+ p.intro("🍎 crono login");
50
+ if (existingKey || existingEmail || existingPassword) {
51
+ p.log.info(`Credentials already configured${existingEmail ? ` for ${existingEmail}` : ""}. Press Enter to keep existing values.`);
52
+ }
53
+ // 1. Kernel API key
54
+ const apiKeyInput = await p.text({
55
+ message: "Kernel API key",
56
+ placeholder: existingKey ? mask(existingKey) : "sk-...",
57
+ defaultValue: existingKey ?? undefined,
58
+ });
59
+ if (p.isCancel(apiKeyInput)) {
60
+ p.cancel("Login cancelled.");
61
+ process.exit(0);
62
+ }
63
+ const apiKey = apiKeyInput || existingKey;
64
+ if (!apiKey) {
65
+ p.cancel("API key cannot be empty.");
66
+ process.exit(1);
67
+ }
68
+ // Only validate if the key changed
69
+ if (apiKeyInput && apiKeyInput !== existingKey) {
70
+ const s = p.spinner();
71
+ s.start("Validating API key...");
72
+ try {
73
+ await validateKernelApiKey(apiKey);
74
+ s.stop("API key valid.");
75
+ }
76
+ catch {
77
+ s.stop("API key invalid.");
78
+ p.cancel("Invalid API key. Check your key at https://kernel.sh");
79
+ process.exit(1);
80
+ }
81
+ }
82
+ // 2. Cronometer email
83
+ const emailInput = await p.text({
84
+ message: "Cronometer email",
85
+ defaultValue: existingEmail ?? undefined,
86
+ placeholder: existingEmail ?? "you@example.com",
87
+ validate: (v) => {
88
+ if (v && !v.includes("@"))
89
+ return "Must be a valid email";
90
+ },
91
+ });
92
+ if (p.isCancel(emailInput)) {
93
+ p.cancel("Login cancelled.");
94
+ process.exit(0);
95
+ }
96
+ const email = emailInput || existingEmail;
97
+ if (!email || !email.includes("@")) {
98
+ p.cancel("Please enter a valid email address.");
99
+ process.exit(1);
100
+ }
101
+ // 3. Cronometer password
102
+ let password;
103
+ if (existingPassword) {
104
+ const changePassword = await p.confirm({
105
+ message: "Change Cronometer password?",
106
+ initialValue: false,
107
+ });
108
+ if (p.isCancel(changePassword)) {
109
+ p.cancel("Login cancelled.");
110
+ process.exit(0);
111
+ }
112
+ if (changePassword) {
113
+ const passwordInput = await p.password({
114
+ message: "Cronometer password",
115
+ mask: "*",
116
+ });
117
+ if (p.isCancel(passwordInput)) {
118
+ p.cancel("Login cancelled.");
119
+ process.exit(0);
120
+ }
121
+ password = passwordInput || existingPassword;
122
+ }
123
+ else {
124
+ password = existingPassword;
125
+ }
126
+ }
127
+ else {
128
+ const passwordInput = await p.password({
129
+ message: "Cronometer password",
130
+ mask: "*",
131
+ });
132
+ if (p.isCancel(passwordInput)) {
133
+ p.cancel("Login cancelled.");
134
+ process.exit(0);
135
+ }
136
+ password = passwordInput;
137
+ }
138
+ if (!password) {
139
+ p.cancel("Password cannot be empty.");
140
+ process.exit(1);
141
+ }
142
+ // Store credentials
143
+ setCredential("kernel-api-key", apiKey);
144
+ setCredential("cronometer-username", email);
145
+ setCredential("cronometer-password", password);
146
+ const changed = [];
147
+ if (apiKeyInput && apiKeyInput !== existingKey)
148
+ changed.push("API key");
149
+ if (emailInput && emailInput !== existingEmail)
150
+ changed.push("email");
151
+ if (password !== existingPassword)
152
+ changed.push("password");
153
+ if (changed.length > 0) {
154
+ p.outro(`Updated ${changed.join(", ")}.`);
155
+ }
156
+ else {
157
+ p.outro("No changes made.");
158
+ }
159
+ }
160
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export interface QuickAddOptions {
2
+ protein?: number;
3
+ carbs?: number;
4
+ fat?: number;
5
+ meal?: string;
6
+ }
7
+ export declare function quickAdd(options: QuickAddOptions): Promise<void>;
8
+ //# sourceMappingURL=quick-add.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quick-add.d.ts","sourceRoot":"","sources":["../../src/commands/quick-add.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAID,wBAAsB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAoDtE"}
@@ -0,0 +1,49 @@
1
+ import * as p from "@clack/prompts";
2
+ import { getKernelClient } from "../kernel/client.js";
3
+ const VALID_MEALS = ["breakfast", "lunch", "dinner", "snacks"];
4
+ export async function quickAdd(options) {
5
+ // Validate at least one macro is provided
6
+ if (!options.protein && !options.carbs && !options.fat) {
7
+ p.log.error("At least one macro (-p, -c, or -f) is required");
8
+ process.exit(1);
9
+ }
10
+ // Validate meal if provided
11
+ if (options.meal) {
12
+ const normalizedMeal = options.meal.toLowerCase();
13
+ if (!VALID_MEALS.includes(normalizedMeal)) {
14
+ p.log.error(`Invalid meal "${options.meal}". Use: Breakfast, Lunch, Dinner, or Snacks`);
15
+ process.exit(1);
16
+ }
17
+ }
18
+ // Build entry description
19
+ const parts = [];
20
+ if (options.protein)
21
+ parts.push(`${options.protein}g protein`);
22
+ if (options.carbs)
23
+ parts.push(`${options.carbs}g carbs`);
24
+ if (options.fat)
25
+ parts.push(`${options.fat}g fat`);
26
+ const mealLabel = options.meal
27
+ ? options.meal.charAt(0).toUpperCase() + options.meal.slice(1).toLowerCase()
28
+ : "Uncategorized";
29
+ p.intro("🍎 crono quick-add");
30
+ const s = p.spinner();
31
+ s.start("Connecting...");
32
+ try {
33
+ const kernel = await getKernelClient();
34
+ await kernel.addQuickEntry({
35
+ protein: options.protein,
36
+ carbs: options.carbs,
37
+ fat: options.fat,
38
+ meal: options.meal,
39
+ }, (msg) => s.message(msg));
40
+ s.stop("Done.");
41
+ p.outro(`Added: ${parts.join(", ")} → ${mealLabel}`);
42
+ }
43
+ catch (error) {
44
+ s.stop("Failed.");
45
+ p.log.error(`Failed to add entry: ${error}`);
46
+ process.exit(1);
47
+ }
48
+ }
49
+ //# sourceMappingURL=quick-add.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"quick-add.js","sourceRoot":"","sources":["../../src/commands/quick-add.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,CAAC,MAAM,gBAAgB,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAStD,MAAM,WAAW,GAAG,CAAC,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAE/D,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,OAAwB;IACrD,0CAA0C;IAC1C,IAAI,CAAC,OAAO,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;QACvD,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,gDAAgD,CAAC,CAAC;QAC9D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,4BAA4B;IAC5B,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;QACjB,MAAM,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;QAClD,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC;YAC1C,CAAC,CAAC,GAAG,CAAC,KAAK,CACT,iBAAiB,OAAO,CAAC,IAAI,6CAA6C,CAC3E,CAAC;YACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IAED,0BAA0B;IAC1B,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,OAAO,CAAC,OAAO;QAAE,KAAK,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,OAAO,WAAW,CAAC,CAAC;IAC/D,IAAI,OAAO,CAAC,KAAK;QAAE,KAAK,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,KAAK,SAAS,CAAC,CAAC;IACzD,IAAI,OAAO,CAAC,GAAG;QAAE,KAAK,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC;IAEnD,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI;QAC5B,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE;QAC5E,CAAC,CAAC,eAAe,CAAC;IAEpB,CAAC,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC;IAE9B,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC;IACtB,CAAC,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;IAEzB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,eAAe,EAAE,CAAC;QACvC,MAAM,MAAM,CAAC,aAAa,CACxB;YACE,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,IAAI,EAAE,OAAO,CAAC,IAAI;SACnB,EACD,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CACxB,CAAC;QAEF,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAChB,CAAC,CAAC,KAAK,CAAC,UAAU,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,SAAS,EAAE,CAAC,CAAC;IACvD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAClB,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,wBAAwB,KAAK,EAAE,CAAC,CAAC;QAC7C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
@@ -0,0 +1,7 @@
1
+ export interface WeightOptions {
2
+ date?: string;
3
+ range?: string;
4
+ json?: boolean;
5
+ }
6
+ export declare function weight(options: WeightOptions): Promise<void>;
7
+ //# sourceMappingURL=weight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"weight.d.ts","sourceRoot":"","sources":["../../src/commands/weight.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CA2DlE"}