kahunas-cli 1.2.0 → 1.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.
Files changed (3) hide show
  1. package/README.md +9 -139
  2. package/dist/cli.js +167 -20
  3. package/package.json +2 -1
package/README.md CHANGED
@@ -1,77 +1,17 @@
1
1
  # Kahunas CLI
2
2
 
3
- A TypeScript CLI for Kahunas (kahunas.io) to fetch check-ins and workouts.
4
-
5
- ![Kahunas CLI screenshot](https://unpkg.com/kahunas-cli@latest/ai-screenshot.png)
3
+ Fetch workouts from Kahunas and preview them locally.
6
4
 
7
5
  ## Quick start
8
6
 
9
- 1) Install dependencies and build:
7
+ 1) Install and build:
10
8
 
11
9
  ```bash
12
10
  pnpm install
13
11
  pnpm build
14
12
  ```
15
13
 
16
- 2) Fetch data (browser login runs automatically on first run, headless by default):
17
-
18
- ```bash
19
- pnpm kahunas checkins list
20
- pnpm kahunas workout list
21
- pnpm kahunas workout pick
22
- ```
23
-
24
- You can also run without installing globally:
25
-
26
- ```bash
27
- npx kahunas-cli checkins list
28
- npx kahunas-cli workout events
29
- ```
30
-
31
- ## Commands
32
-
33
- ### Check-ins
34
-
35
- - `kahunas checkins list`
36
- - Lists recent check-ins.
37
-
38
- ### Workouts
39
-
40
- - `kahunas workout list`
41
- - Lists workout programs.
42
- - `kahunas workout pick`
43
- - Shows a numbered list and lets you choose a program.
44
- - `kahunas workout latest`
45
- - Loads the most recently updated program.
46
- - `kahunas workout events`
47
- - Lists workout log events with dates and a human-friendly workout summary (from the calendar endpoint).
48
- - `kahunas workout serve`
49
- - Starts a local dev server with a workout preview page and a JSON endpoint that matches the CLI output.
50
- - `kahunas workout program <id>`
51
- - Fetches a program by UUID.
52
-
53
- ### Workout sync (browser capture)
54
-
55
- If the API list is missing a program you see in the web UI, run:
56
-
57
- ```bash
58
- pnpm kahunas sync
59
- ```
60
-
61
- Or:
62
-
63
- ```bash
64
- pnpm kahunas workout sync
65
- ```
66
-
67
- This runs a browser session (headless by default). You log in, then navigate to your workouts page. After you press Enter, the CLI captures the workout list from network responses and writes a cache:
68
-
69
- - `~/.config/kahunas/workouts.json`
70
-
71
- `workout list`, `workout pick`, and `workout latest` automatically merge the API list with this cache.
72
- Raw output (`--raw`) prints the API response only.
73
-
74
- If you add `~/.config/kahunas/auth.json`, the browser flow will attempt an automatic login and open your workouts page before capturing. Example:
14
+ 2) Optional: add auto-login credentials at `~/.config/kahunas/auth.json`:
75
15
 
76
16
  ```json
77
17
  {
@@ -80,87 +20,17 @@ If you add `~/.config/kahunas/auth.json`, the browser flow will attempt an autom
80
20
  }
81
21
  ```
82
22
 
83
- Keep this file private; it contains credentials.
84
-
85
- Optional fields:
86
-
87
- - `username` (use instead of `email`)
88
- - `loginPath` (default: `/dashboard`)
89
-
90
- If auto-capture does not find workouts, the CLI falls back to the manual prompt.
91
-
92
- ### Workout events (dates)
93
-
94
- To see when workouts happened, the calendar endpoint returns log events with timestamps. The CLI returns the latest event summarized into a human-friendly structure (total volume sets, exercises, supersets). Use `--full` to return the full program payload (best effort; falls back to cached summary if needed).
95
-
96
- ```bash
97
- pnpm kahunas workout events
98
- ```
99
-
100
- Use `--minimal` to return the raw event objects without program enrichment. Use `--full` for full enriched output. Use `--debug-preview` to log where preview HTML was discovered (stderr only).
101
-
102
- If the user UUID is missing, `workout events` will attempt to discover it from check-ins and save it.
103
-
104
- ### Workout preview server
105
-
106
- Run a local dev server to preview workouts in a browser:
23
+ 3) Sync workouts, then run the preview server:
107
24
 
108
25
  ```bash
26
+ pnpm kahunas sync
109
27
  pnpm kahunas serve
110
28
  ```
111
29
 
112
- Or:
113
-
114
- ```bash
115
- pnpm kahunas workout serve
116
- ```
117
-
118
- The HTML page is available at `http://127.0.0.1:3000` and the JSON endpoint is at `http://127.0.0.1:3000/api/workout`.
119
- The JSON response matches the CLI output for `workout events`, so there is only one data shape to maintain.
120
-
121
- Use `?day=<index>` to switch the selected workout day tab in the browser.
122
-
123
- ## Auto-login
124
-
125
- Most commands auto-login if a token is missing or expired. This runs a browser session and saves session details in `~/.config/kahunas/config.json` (headless by default). If `~/.config/kahunas/auth.json` is present, the login step is automated.
126
-
127
- ## Debug logging
128
-
129
- Set `debug` to `true` in `~/.config/kahunas/config.json` to enable extra logs on stderr (includes workout preview debug output).
130
-
131
- ## Headless mode
132
-
133
- Set `headless` to `false` in `~/.config/kahunas/config.json` to show the Playwright browser. Defaults to `true`.
30
+ Open `http://127.0.0.1:3000`.
134
31
 
135
- ## Flags
136
-
137
- - `--raw` prints raw API responses (no formatting).
138
-
139
- ## Playwright
140
-
141
- If Playwright did not download a browser during install:
142
-
143
- ```bash
144
- pnpm exec playwright install chromium
145
- ```
146
-
147
- ## Testing
148
-
149
- Run the unit tests with:
150
-
151
- ```bash
152
- pnpm test
153
- ```
154
-
155
- ## Publishing
156
-
157
- The npm package is built from `dist/cli.js`. Publishing runs the build automatically:
158
-
159
- ```bash
160
- pnpm publish
161
- ```
32
+ If `auth.json` is missing, `sync` will prompt for credentials and save them.
162
33
 
163
- ## Notes
34
+ ## Advanced usage
164
35
 
165
- - This CLI uses the same APIs the web app uses; tokens can expire quickly.
166
- - Re-run any command (or `workout sync`) to refresh login when needed.
36
+ See `docs/advanced.md` for all commands, flags, and configuration options.
package/dist/cli.js CHANGED
@@ -6,6 +6,7 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
9
10
  var __copyProps = (to, from, except, desc) => {
10
11
  if (from && typeof from === "object" || typeof from === "function") {
11
12
  for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
@@ -94,6 +95,11 @@ function readAuthConfig() {
94
95
  throw new Error(`Invalid JSON in ${AUTH_PATH}.`);
95
96
  }
96
97
  }
98
+ function writeAuthConfig(auth) {
99
+ const dir = node_path.dirname(AUTH_PATH);
100
+ node_fs.mkdirSync(dir, { recursive: true });
101
+ node_fs.writeFileSync(AUTH_PATH, `${JSON.stringify(auth, null, 2)}\n`, "utf-8");
102
+ }
97
103
  function writeConfig(config) {
98
104
  const dir = node_path.dirname(CONFIG_PATH);
99
105
  node_fs.mkdirSync(dir, { recursive: true });
@@ -190,6 +196,14 @@ function extractJwtExpiry(token) {
190
196
  return;
191
197
  }
192
198
  }
199
+ const DEFAULT_TOKEN_TTL_MS = 7200 * 1e3;
200
+ function resolveTokenExpiry(token, tokenUpdatedAt) {
201
+ const jwtExpiry = extractJwtExpiry(token);
202
+ if (jwtExpiry) return jwtExpiry;
203
+ const updatedAt = Date.parse(tokenUpdatedAt);
204
+ if (!Number.isFinite(updatedAt)) return;
205
+ return new Date(updatedAt + DEFAULT_TOKEN_TTL_MS).toISOString();
206
+ }
193
207
  function decodeBase64Url(value) {
194
208
  const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
195
209
  const padding = normalized.length % 4;
@@ -313,13 +327,108 @@ function extractUserUuidFromCheckins(payload) {
313
327
  return typeof candidate === "string" ? candidate : void 0;
314
328
  }
315
329
 
330
+ //#endregion
331
+ //#region node_modules/.pnpm/picocolors@1.1.1/node_modules/picocolors/picocolors.js
332
+ var require_picocolors = /* @__PURE__ */ __commonJSMin(((exports, module) => {
333
+ let p = process || {}, argv = p.argv || [], env = p.env || {};
334
+ let isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p.platform === "win32" || (p.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
335
+ let formatter = (open, close, replace = open) => (input) => {
336
+ let string = "" + input, index = string.indexOf(close, open.length);
337
+ return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
338
+ };
339
+ let replaceClose = (string, close, replace, index) => {
340
+ let result = "", cursor = 0;
341
+ do {
342
+ result += string.substring(cursor, index) + replace;
343
+ cursor = index + close.length;
344
+ index = string.indexOf(close, cursor);
345
+ } while (~index);
346
+ return result + string.substring(cursor);
347
+ };
348
+ let createColors = (enabled = isColorSupported) => {
349
+ let f = enabled ? formatter : () => String;
350
+ return {
351
+ isColorSupported: enabled,
352
+ reset: f("\x1B[0m", "\x1B[0m"),
353
+ bold: f("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
354
+ dim: f("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
355
+ italic: f("\x1B[3m", "\x1B[23m"),
356
+ underline: f("\x1B[4m", "\x1B[24m"),
357
+ inverse: f("\x1B[7m", "\x1B[27m"),
358
+ hidden: f("\x1B[8m", "\x1B[28m"),
359
+ strikethrough: f("\x1B[9m", "\x1B[29m"),
360
+ black: f("\x1B[30m", "\x1B[39m"),
361
+ red: f("\x1B[31m", "\x1B[39m"),
362
+ green: f("\x1B[32m", "\x1B[39m"),
363
+ yellow: f("\x1B[33m", "\x1B[39m"),
364
+ blue: f("\x1B[34m", "\x1B[39m"),
365
+ magenta: f("\x1B[35m", "\x1B[39m"),
366
+ cyan: f("\x1B[36m", "\x1B[39m"),
367
+ white: f("\x1B[37m", "\x1B[39m"),
368
+ gray: f("\x1B[90m", "\x1B[39m"),
369
+ bgBlack: f("\x1B[40m", "\x1B[49m"),
370
+ bgRed: f("\x1B[41m", "\x1B[49m"),
371
+ bgGreen: f("\x1B[42m", "\x1B[49m"),
372
+ bgYellow: f("\x1B[43m", "\x1B[49m"),
373
+ bgBlue: f("\x1B[44m", "\x1B[49m"),
374
+ bgMagenta: f("\x1B[45m", "\x1B[49m"),
375
+ bgCyan: f("\x1B[46m", "\x1B[49m"),
376
+ bgWhite: f("\x1B[47m", "\x1B[49m"),
377
+ blackBright: f("\x1B[90m", "\x1B[39m"),
378
+ redBright: f("\x1B[91m", "\x1B[39m"),
379
+ greenBright: f("\x1B[92m", "\x1B[39m"),
380
+ yellowBright: f("\x1B[93m", "\x1B[39m"),
381
+ blueBright: f("\x1B[94m", "\x1B[39m"),
382
+ magentaBright: f("\x1B[95m", "\x1B[39m"),
383
+ cyanBright: f("\x1B[96m", "\x1B[39m"),
384
+ whiteBright: f("\x1B[97m", "\x1B[39m"),
385
+ bgBlackBright: f("\x1B[100m", "\x1B[49m"),
386
+ bgRedBright: f("\x1B[101m", "\x1B[49m"),
387
+ bgGreenBright: f("\x1B[102m", "\x1B[49m"),
388
+ bgYellowBright: f("\x1B[103m", "\x1B[49m"),
389
+ bgBlueBright: f("\x1B[104m", "\x1B[49m"),
390
+ bgMagentaBright: f("\x1B[105m", "\x1B[49m"),
391
+ bgCyanBright: f("\x1B[106m", "\x1B[49m"),
392
+ bgWhiteBright: f("\x1B[107m", "\x1B[49m")
393
+ };
394
+ };
395
+ module.exports = createColors();
396
+ module.exports.createColors = createColors;
397
+ }));
398
+
399
+ //#endregion
400
+ //#region src/logger.ts
401
+ var import_picocolors = /* @__PURE__ */ __toESM(require_picocolors());
402
+ function formatLabel(label, color) {
403
+ return import_picocolors.default.bold(color(label));
404
+ }
405
+ function logInfo(message) {
406
+ console.log(`${formatLabel("info", import_picocolors.default.cyan)} ${message}`);
407
+ }
408
+ function logError(message) {
409
+ console.error(`${formatLabel("error", import_picocolors.default.red)} ${message}`);
410
+ }
411
+ function logDebug(enabled, message) {
412
+ if (!enabled) return;
413
+ console.error(`${formatLabel("debug", import_picocolors.default.gray)} ${message}`);
414
+ }
415
+ function logPlain(message) {
416
+ console.log(message);
417
+ }
418
+ function formatHeading(message) {
419
+ return import_picocolors.default.bold(message);
420
+ }
421
+ function formatDim(message) {
422
+ return import_picocolors.default.dim(message);
423
+ }
424
+
316
425
  //#endregion
317
426
  //#region src/utils.ts
318
- function askQuestion(prompt) {
427
+ function askQuestion(prompt, output = process.stdout) {
319
428
  return new Promise((resolve) => {
320
429
  const rl = node_readline.createInterface({
321
430
  input: process.stdin,
322
- output: process.stdout
431
+ output
323
432
  });
324
433
  rl.question(prompt, (answer) => {
325
434
  rl.close();
@@ -327,12 +436,11 @@ function askQuestion(prompt) {
327
436
  });
328
437
  });
329
438
  }
330
- function waitForEnter(prompt) {
331
- return askQuestion(prompt).then(() => void 0);
439
+ function waitForEnter(prompt, output = process.stdout) {
440
+ return askQuestion(prompt, output).then(() => void 0);
332
441
  }
333
442
  function debugLog(enabled, message) {
334
- if (!enabled) return;
335
- console.error(`[debug] ${message}`);
443
+ logDebug(enabled, message);
336
444
  }
337
445
 
338
446
  //#endregion
@@ -486,6 +594,9 @@ function normalizePath(pathname) {
486
594
  if (pathname.startsWith("/")) return pathname;
487
595
  return `/${pathname}`;
488
596
  }
597
+ function normalizeToken(token) {
598
+ return token.trim().replace(/^"(.*)"$/, "$1").replace(/^'(.*)'$/, "$1").replace(/^bearer\s+/i, "");
599
+ }
489
600
  function resolveStoredAuth() {
490
601
  const auth = readAuthConfig();
491
602
  if (!auth) return;
@@ -620,7 +731,8 @@ async function captureWorkoutsFromBrowser(options, config) {
620
731
  let observedToken;
621
732
  const recordToken = (candidate) => {
622
733
  if (!candidate || observedToken) return;
623
- if (isLikelyAuthToken(candidate)) observedToken = candidate;
734
+ const normalized = normalizeToken(candidate);
735
+ if (isLikelyAuthToken(normalized)) observedToken = normalized;
624
736
  };
625
737
  const recordPlans = (incoming) => {
626
738
  for (const plan of incoming) {
@@ -659,6 +771,13 @@ async function captureWorkoutsFromBrowser(options, config) {
659
771
  cookieHeader = cookies.map((cookie) => `${cookie.name}=${cookie.value}`).join("; ");
660
772
  csrfCookie = cookies.find((cookie) => cookie.name === "csrf_kahunas_cookie_token")?.value;
661
773
  csrfToken = csrfCookie ?? resolveCsrfToken(options, config);
774
+ if (!observedToken && csrfToken && cookieHeader) try {
775
+ const { token: fetchedToken } = await fetchAuthToken(csrfToken, cookieHeader, webBaseUrl);
776
+ recordToken(fetchedToken);
777
+ debugLog(debug, "Fetched auth token via /get-token.");
778
+ } catch (error) {
779
+ debugLog(debug, `Failed to fetch auth token via /get-token: ${error instanceof Error ? error.message : "unknown error"}`);
780
+ }
662
781
  if (plans.length === 0) await page.waitForTimeout(1500);
663
782
  } finally {
664
783
  await browser.close();
@@ -682,7 +801,8 @@ async function loginWithBrowser(options, config) {
682
801
  let observedToken;
683
802
  const recordToken = (candidate) => {
684
803
  if (!candidate || observedToken) return;
685
- if (isLikelyAuthToken(candidate)) observedToken = candidate;
804
+ const normalized = normalizeToken(candidate);
805
+ if (isLikelyAuthToken(normalized)) observedToken = normalized;
686
806
  };
687
807
  context.on("request", (request) => {
688
808
  recordToken(request.headers()["auth-user-token"]);
@@ -741,7 +861,7 @@ async function loginWithBrowser(options, config) {
741
861
  async function loginAndPersist(options, config, outputMode) {
742
862
  const result = await loginWithBrowser(options, config);
743
863
  const tokenUpdatedAt = (/* @__PURE__ */ new Date()).toISOString();
744
- const tokenExpiresAt = extractJwtExpiry(result.token) ?? null;
864
+ const tokenExpiresAt = resolveTokenExpiry(result.token, tokenUpdatedAt) ?? null;
745
865
  const nextConfig = {
746
866
  ...config,
747
867
  token: result.token,
@@ -761,7 +881,7 @@ async function loginAndPersist(options, config, outputMode) {
761
881
  //#endregion
762
882
  //#region src/usage.ts
763
883
  function printUsage() {
764
- console.log(`kahunas - CLI for Kahunas API\n\nUsage:\n kahunas checkins list [--raw]\n kahunas workout list [--raw]\n kahunas workout pick [--raw]\n kahunas workout latest [--raw]\n kahunas workout events [--minimal] [--full] [--debug-preview] [--raw]\n kahunas workout serve\n kahunas serve\n kahunas workout sync\n kahunas sync\n kahunas workout program <id> [--raw]\n\nConfig:\n ${CONFIG_PATH}`);
884
+ logPlain(`${formatHeading("kahunas - CLI for Kahunas API")}\n\nUsage:\n kahunas checkins list [--raw]\n kahunas workout list [--raw]\n kahunas workout pick [--raw]\n kahunas workout latest [--raw]\n kahunas workout events [--minimal] [--full] [--debug-preview] [--raw]\n kahunas workout serve\n kahunas serve\n kahunas workout sync\n kahunas sync\n kahunas workout program <id> [--raw]\n\n${formatDim("Config:")}\n ${CONFIG_PATH}`);
765
885
  }
766
886
 
767
887
  //#endregion
@@ -2167,9 +2287,9 @@ async function handleWorkout(positionals, options) {
2167
2287
  if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.text}`);
2168
2288
  if (plans.length === 0) throw new Error("No workout programs found.");
2169
2289
  if (!rawOutput) {
2170
- console.log("Pick a workout program:");
2290
+ logPlain(formatHeading("Pick a workout program:"));
2171
2291
  plans.forEach((plan, index) => {
2172
- console.log(`${index + 1}) ${formatWorkoutSummary(plan)}`);
2292
+ logPlain(`${index + 1}) ${formatWorkoutSummary(plan)}`);
2173
2293
  });
2174
2294
  }
2175
2295
  const answer = await askQuestion(`Enter number (1-${plans.length}): `);
@@ -2336,18 +2456,43 @@ async function handleWorkout(positionals, options) {
2336
2456
  const freshConfig = readConfig();
2337
2457
  const lastSync = cache?.updatedAt ?? "none";
2338
2458
  const tokenExpiry = freshConfig.tokenExpiresAt ?? "unknown";
2339
- console.log(`Local workout server running at http://${host}:${port}`);
2340
- console.log(`JSON endpoint at http://${host}:${port}/api/workout`);
2341
- console.log(`Config: ${CONFIG_PATH}`);
2342
- console.log(`Last workout sync: ${lastSync}`);
2343
- console.log(`Token expiry: ${tokenExpiry}`);
2459
+ const tokenUpdatedAt = freshConfig.tokenUpdatedAt ?? "unknown";
2460
+ logInfo(`Local workout server running at http://${host}:${port}`);
2461
+ logInfo(`JSON endpoint at http://${host}:${port}/api/workout`);
2462
+ logInfo(`Config: ${CONFIG_PATH}`);
2463
+ logInfo(`Last workout sync: ${lastSync}`);
2464
+ logInfo(`Token expiry: ${tokenExpiry}`);
2465
+ if (tokenExpiry === "unknown" && tokenUpdatedAt !== "unknown") logInfo(`Token updated at: ${tokenUpdatedAt}`);
2344
2466
  });
2345
2467
  return;
2346
2468
  }
2347
2469
  if (action === "sync") {
2470
+ const authConfig = readAuthConfig();
2471
+ const hasAuthConfig = !!authConfig && !!authConfig.password && (!!authConfig.email || !!authConfig.username);
2472
+ const tokenUpdatedAt = config.tokenUpdatedAt ?? void 0;
2473
+ const tokenExpiry = token && tokenUpdatedAt ? resolveTokenExpiry(token, tokenUpdatedAt) : null;
2474
+ if (!(!!tokenExpiry && Date.now() < Date.parse(tokenExpiry)) && !hasAuthConfig) {
2475
+ if (!process.stdin.isTTY) throw new Error("Missing auth credentials. Create ~/.config/kahunas/auth.json or run 'kahunas sync' in a terminal.");
2476
+ const login = await askQuestion("Email or username: ", process.stderr);
2477
+ if (!login) throw new Error("Missing email/username for login.");
2478
+ const password = await askQuestion("Password: ", process.stderr);
2479
+ if (!password) throw new Error("Missing password for login.");
2480
+ const isEmail = login.includes("@");
2481
+ writeAuthConfig({
2482
+ email: isEmail ? login : void 0,
2483
+ username: isEmail ? void 0 : login,
2484
+ password
2485
+ });
2486
+ console.error(`Saved credentials to ${AUTH_PATH}`);
2487
+ }
2348
2488
  const captured = await captureWorkoutsFromBrowser(options, config);
2349
2489
  const nextConfig = { ...config };
2350
- if (captured.token) nextConfig.token = captured.token;
2490
+ if (captured.token) {
2491
+ nextConfig.token = captured.token;
2492
+ const tokenUpdatedAt$1 = (/* @__PURE__ */ new Date()).toISOString();
2493
+ nextConfig.tokenUpdatedAt = tokenUpdatedAt$1;
2494
+ nextConfig.tokenExpiresAt = resolveTokenExpiry(captured.token, tokenUpdatedAt$1) ?? null;
2495
+ }
2351
2496
  if (captured.csrfToken) nextConfig.csrfToken = captured.csrfToken;
2352
2497
  if (captured.webBaseUrl) nextConfig.webBaseUrl = captured.webBaseUrl;
2353
2498
  if (captured.cookieHeader) nextConfig.authCookie = captured.cookieHeader;
@@ -2362,6 +2507,9 @@ async function handleWorkout(positionals, options) {
2362
2507
  path: WORKOUT_CACHE_PATH
2363
2508
  }
2364
2509
  }, null, 2));
2510
+ if (process.stdin.isTTY) {
2511
+ if ((await askQuestion("Start the preview server now? (y/N): ", process.stderr)).toLowerCase().startsWith("y")) await handleWorkout(["serve"], options);
2512
+ }
2365
2513
  return;
2366
2514
  }
2367
2515
  if (action !== "program") throw new Error(`Unknown workout action: ${action}`);
@@ -2408,8 +2556,7 @@ async function main() {
2408
2556
  }
2409
2557
  }
2410
2558
  main().catch((error) => {
2411
- const message = error instanceof Error ? error.message : String(error);
2412
- console.error(message);
2559
+ logError(error instanceof Error ? error.message : String(error));
2413
2560
  process.exit(1);
2414
2561
  });
2415
2562
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kahunas-cli",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "",
5
5
  "main": "dist/cli.js",
6
6
  "bin": {
@@ -19,6 +19,7 @@
19
19
  "ai-screenshot.png"
20
20
  ],
21
21
  "dependencies": {
22
+ "picocolors": "^1.1.1",
22
23
  "playwright": "^1.49.1"
23
24
  },
24
25
  "devDependencies": {