@kaiord/cli 1.0.0 → 3.0.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/LICENSE +21 -0
  2. package/dist/bin/kaiord.js +839 -612
  3. package/package.json +21 -21
@@ -79,7 +79,7 @@ var pretty_logger_exports = {};
79
79
  __export(pretty_logger_exports, {
80
80
  createPrettyLogger: () => createPrettyLogger
81
81
  });
82
- import chalk2 from "chalk";
82
+ import chalk6 from "chalk";
83
83
  var createPrettyLogger;
84
84
  var init_pretty_logger = __esm({
85
85
  "src/adapters/logger/pretty-logger.ts"() {
@@ -105,31 +105,31 @@ var init_pretty_logger = __esm({
105
105
  return "";
106
106
  }
107
107
  const contextStr = JSON.stringify(context);
108
- return " " + (useColors ? chalk2.gray(contextStr) : contextStr);
108
+ return " " + (useColors ? chalk6.gray(contextStr) : contextStr);
109
109
  };
110
110
  return {
111
111
  debug: (message, context) => {
112
112
  if (shouldLog("debug")) {
113
113
  const formatted = `\u{1F41B} ${message}${formatContext(context)}`;
114
- console.log(useColors ? chalk2.gray(formatted) : formatted);
114
+ console.log(useColors ? chalk6.gray(formatted) : formatted);
115
115
  }
116
116
  },
117
117
  info: (message, context) => {
118
118
  if (shouldLog("info")) {
119
119
  const formatted = `\u2139 ${message}${formatContext(context)}`;
120
- console.log(useColors ? chalk2.blue(formatted) : formatted);
120
+ console.log(useColors ? chalk6.blue(formatted) : formatted);
121
121
  }
122
122
  },
123
123
  warn: (message, context) => {
124
124
  if (shouldLog("warn")) {
125
125
  const formatted = `\u26A0 ${message}${formatContext(context)}`;
126
- console.warn(useColors ? chalk2.yellow(formatted) : formatted);
126
+ console.warn(useColors ? chalk6.yellow(formatted) : formatted);
127
127
  }
128
128
  },
129
129
  error: (message, context) => {
130
130
  if (shouldLog("error")) {
131
131
  const formatted = `\u2716 ${message}${formatContext(context)}`;
132
- console.error(useColors ? chalk2.red(formatted) : formatted);
132
+ console.error(useColors ? chalk6.red(formatted) : formatted);
133
133
  }
134
134
  }
135
135
  };
@@ -139,7 +139,6 @@ var init_pretty_logger = __esm({
139
139
 
140
140
  // src/bin/kaiord.ts
141
141
  init_esm_shims();
142
- import chalk5 from "chalk";
143
142
  import { readFileSync } from "fs";
144
143
  import { dirname as dirname2, join as join3 } from "path";
145
144
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -148,16 +147,15 @@ import { hideBin } from "yargs/helpers";
148
147
 
149
148
  // src/commands/convert.ts
150
149
  init_esm_shims();
150
+
151
+ // src/commands/convert/index.ts
152
+ init_esm_shims();
151
153
  import {
152
- createDefaultProviders,
153
- FitParsingError as FitParsingError2,
154
- KrdValidationError as KrdValidationError2,
155
- ToleranceExceededError as ToleranceExceededError2
154
+ FitParsingError as FitParsingError3,
155
+ KrdValidationError as KrdValidationError3,
156
+ ToleranceExceededError as ToleranceExceededError3
156
157
  } from "@kaiord/core";
157
- import chalk3 from "chalk";
158
- import ora from "ora";
159
- import { basename, join as join2 } from "path";
160
- import { z as z3 } from "zod";
158
+ import { createAllProviders } from "@kaiord/all";
161
159
 
162
160
  // src/utils/config-loader.ts
163
161
  init_esm_shims();
@@ -197,7 +195,7 @@ var configSchema = z2.object({
197
195
  json: z2.boolean().optional(),
198
196
  logFormat: z2.enum(["pretty", "structured"]).optional()
199
197
  });
200
- var loadConfig = async () => {
198
+ var loadConfigWithMetadata = async () => {
201
199
  const configPaths = [
202
200
  join(process.cwd(), ".kaiordrc.json"),
203
201
  join(homedir(), ".kaiordrc.json")
@@ -207,12 +205,12 @@ var loadConfig = async () => {
207
205
  const configContent = await readFile(configPath, "utf-8");
208
206
  const configJson = JSON.parse(configContent);
209
207
  const config = configSchema.parse(configJson);
210
- return config;
211
- } catch (error) {
208
+ return { config, loadedFrom: configPath, searchedPaths: configPaths };
209
+ } catch {
212
210
  continue;
213
211
  }
214
212
  }
215
- return {};
213
+ return { config: {}, loadedFrom: null, searchedPaths: configPaths };
216
214
  };
217
215
  var mergeWithConfig = (cliOptions, config) => {
218
216
  const merged = { ...config };
@@ -226,33 +224,219 @@ var mergeWithConfig = (cliOptions, config) => {
226
224
 
227
225
  // src/utils/error-formatter.ts
228
226
  init_esm_shims();
229
- init_is_tty();
227
+
228
+ // src/utils/error-formatter-json.ts
229
+ init_esm_shims();
230
230
  import {
231
231
  FitParsingError,
232
232
  KrdValidationError,
233
233
  ToleranceExceededError
234
234
  } from "@kaiord/core";
235
+
236
+ // src/utils/error-suggestions.ts
237
+ init_esm_shims();
238
+ var ERROR_PATTERNS = [
239
+ {
240
+ pattern: "file not found",
241
+ title: "File not found",
242
+ suggestions: [
243
+ "Check that the file path is correct.",
244
+ "Use 'ls' or 'dir' to verify the file exists.",
245
+ "Ensure the path is relative to current directory or absolute."
246
+ ]
247
+ },
248
+ {
249
+ pattern: "permission denied",
250
+ title: "Permission denied",
251
+ suggestions: [
252
+ "Check file/directory permissions with 'ls -la'.",
253
+ "Ensure you have read/write access to the path.",
254
+ "Try running with appropriate permissions."
255
+ ]
256
+ },
257
+ {
258
+ pattern: "failed to create directory",
259
+ title: "Directory creation failed",
260
+ suggestions: [
261
+ "Check that the parent directory exists.",
262
+ "Verify you have write permissions to the parent directory.",
263
+ "Ensure the path does not conflict with an existing file."
264
+ ]
265
+ },
266
+ {
267
+ pattern: "cannot create directory",
268
+ title: "Directory creation failed",
269
+ suggestions: [
270
+ "Check that the parent directory exists.",
271
+ "Verify you have write permissions to the parent directory.",
272
+ "Ensure the path does not conflict with an existing file."
273
+ ]
274
+ },
275
+ {
276
+ pattern: "cannot use both",
277
+ title: "Invalid argument combination",
278
+ suggestions: [
279
+ "Use --output (-o) for single file conversion.",
280
+ "Use --output-dir for batch conversion with glob patterns."
281
+ ]
282
+ },
283
+ {
284
+ pattern: "no files found matching",
285
+ title: "No files matched",
286
+ suggestions: [
287
+ "Verify the glob pattern matches your files.",
288
+ 'Use quotes around patterns with wildcards: "*.fit"',
289
+ "Check that files exist in the specified directory."
290
+ ]
291
+ },
292
+ {
293
+ pattern: "batch mode requires",
294
+ title: "Invalid argument",
295
+ suggestions: [
296
+ "Add --output-dir to specify where converted files should go.",
297
+ "Add --output-format to specify the target format."
298
+ ]
299
+ },
300
+ {
301
+ pattern: "unable to detect format",
302
+ title: "Invalid argument",
303
+ suggestions: [
304
+ "Use --input-format to explicitly specify the format.",
305
+ "Supported formats: fit, krd, tcx, zwo"
306
+ ]
307
+ }
308
+ ];
309
+ var findMatchingPattern = (msg) => {
310
+ return ERROR_PATTERNS.find((p) => msg.includes(p.pattern));
311
+ };
312
+ var getErrorTitle = (error) => {
313
+ const msg = error.message.toLowerCase();
314
+ const match = findMatchingPattern(msg);
315
+ if (match) return match.title;
316
+ if (error.name === "InvalidArgumentError") return "Invalid argument";
317
+ return "An unexpected error occurred";
318
+ };
319
+ var getSuggestionForError = (error) => {
320
+ const msg = error.message.toLowerCase();
321
+ const match = findMatchingPattern(msg);
322
+ return match?.suggestions ?? null;
323
+ };
324
+
325
+ // src/utils/error-formatter-json.ts
326
+ var toJson = (result) => {
327
+ return JSON.stringify(result, null, 2);
328
+ };
329
+ var formatFitError = (error) => toJson({
330
+ success: false,
331
+ error: {
332
+ type: "FitParsingError",
333
+ message: error.message,
334
+ cause: error.cause ? String(error.cause) : void 0,
335
+ suggestion: "Verify the file is a valid FIT workout file."
336
+ }
337
+ });
338
+ var formatKrdError = (error) => toJson({
339
+ success: false,
340
+ error: {
341
+ type: "KrdValidationError",
342
+ message: error.message,
343
+ errors: error.errors,
344
+ suggestion: "Check the KRD file against the schema."
345
+ }
346
+ });
347
+ var formatToleranceError = (error) => toJson({
348
+ success: false,
349
+ error: {
350
+ type: "ToleranceExceededError",
351
+ message: error.message,
352
+ violations: error.violations,
353
+ suggestion: "Consider adjusting tolerance values if acceptable."
354
+ }
355
+ });
356
+ var formatGenericError = (error) => {
357
+ const suggestion = getSuggestionForError(error);
358
+ return toJson({
359
+ success: false,
360
+ error: {
361
+ type: error.name || "Error",
362
+ message: error.message,
363
+ suggestion: suggestion ? suggestion.join(" ") : void 0
364
+ }
365
+ });
366
+ };
367
+ var formatUnknownError = (error) => toJson({
368
+ success: false,
369
+ error: {
370
+ type: "UnknownError",
371
+ message: String(error)
372
+ }
373
+ });
374
+ var formatErrorAsJson = (error) => {
375
+ if (error instanceof FitParsingError) return formatFitError(error);
376
+ if (error instanceof KrdValidationError) return formatKrdError(error);
377
+ if (error instanceof ToleranceExceededError)
378
+ return formatToleranceError(error);
379
+ if (error instanceof Error) return formatGenericError(error);
380
+ return formatUnknownError(error);
381
+ };
382
+
383
+ // src/utils/error-formatter-pretty.ts
384
+ init_esm_shims();
385
+ init_is_tty();
386
+ import {
387
+ FitParsingError as FitParsingError2,
388
+ KrdValidationError as KrdValidationError2,
389
+ ToleranceExceededError as ToleranceExceededError2
390
+ } from "@kaiord/core";
391
+
392
+ // src/utils/pretty-formatters/index.ts
393
+ init_esm_shims();
394
+
395
+ // src/utils/pretty-formatters/fit-error.ts
396
+ init_esm_shims();
235
397
  import chalk from "chalk";
398
+ var formatFitParsingError = (error, useColors) => {
399
+ const lines = [
400
+ useColors ? chalk.red("\u2716 Error: Failed to parse FIT file") : "\u2716 Error: Failed to parse FIT file",
401
+ "",
402
+ useColors ? chalk.gray("Details:") : "Details:",
403
+ ` ${error.message}`
404
+ ];
405
+ if (error.cause) {
406
+ lines.push("", useColors ? chalk.gray("Cause:") : "Cause:");
407
+ lines.push(` ${String(error.cause)}`);
408
+ }
409
+ lines.push(
410
+ "",
411
+ useColors ? chalk.cyan("Suggestion:") : "Suggestion:",
412
+ " Verify the file is a valid FIT workout file.",
413
+ " Try opening it in Garmin Connect to confirm."
414
+ );
415
+ return lines.join("\n");
416
+ };
417
+
418
+ // src/utils/pretty-formatters/krd-error.ts
419
+ init_esm_shims();
420
+ import chalk3 from "chalk";
421
+
422
+ // src/utils/format-violations.ts
423
+ init_esm_shims();
424
+ init_is_tty();
425
+ import chalk2 from "chalk";
236
426
  var shouldUseColors = () => {
237
427
  return isTTY() || process.env.FORCE_COLOR === "1";
238
428
  };
239
- var formatError = (error, options = {}) => {
240
- if (options.json) {
241
- return formatErrorAsJson(error);
242
- }
243
- return formatErrorAsPretty(error);
244
- };
245
429
  var formatValidationErrors = (errors) => {
246
430
  if (errors.length === 0) {
247
431
  return "";
248
432
  }
249
433
  const useColors = shouldUseColors();
250
434
  const lines = [
251
- useColors ? chalk.red("Validation errors:") : "Validation errors:"
435
+ useColors ? chalk2.red("Validation errors:") : "Validation errors:"
252
436
  ];
253
437
  for (const error of errors) {
254
- const fieldPath = useColors ? chalk.yellow(error.field) : error.field;
255
- const bullet = useColors ? chalk.red("\u2022") : "\u2022";
438
+ const fieldPath = useColors ? chalk2.yellow(error.field) : error.field;
439
+ const bullet = useColors ? chalk2.red("\u2022") : "\u2022";
256
440
  lines.push(` ${bullet} ${fieldPath}: ${error.message}`);
257
441
  }
258
442
  return lines.join("\n");
@@ -263,173 +447,209 @@ var formatToleranceViolations = (violations) => {
263
447
  }
264
448
  const useColors = shouldUseColors();
265
449
  const lines = [
266
- useColors ? chalk.red("Tolerance violations:") : "Tolerance violations:"
450
+ useColors ? chalk2.red("Tolerance violations:") : "Tolerance violations:"
267
451
  ];
268
452
  for (const violation of violations) {
269
- const fieldPath = useColors ? chalk.yellow(violation.field) : violation.field;
270
- const bullet = useColors ? chalk.red("\u2022") : "\u2022";
271
- const expected = violation.expected;
272
- const actual = violation.actual;
453
+ const fieldPath = useColors ? chalk2.yellow(violation.field) : violation.field;
454
+ const bullet = useColors ? chalk2.red("\u2022") : "\u2022";
455
+ const { expected, actual, tolerance } = violation;
273
456
  const deviation = Math.abs(violation.deviation);
274
- const tolerance = violation.tolerance;
275
457
  lines.push(
276
458
  ` ${bullet} ${fieldPath}: expected ${expected}, got ${actual} (deviation: ${deviation}, tolerance: \xB1${tolerance})`
277
459
  );
278
460
  }
279
461
  return lines.join("\n");
280
462
  };
281
- var formatErrorAsPretty = (error) => {
282
- const useColors = shouldUseColors();
283
- if (error instanceof FitParsingError) {
284
- const lines = [
285
- useColors ? chalk.red("\u2716 Error: Failed to parse FIT file") : "\u2716 Error: Failed to parse FIT file",
286
- "",
287
- useColors ? chalk.gray("Details:") : "Details:",
288
- ` ${error.message}`
289
- ];
290
- if (error.cause) {
291
- lines.push("", useColors ? chalk.gray("Cause:") : "Cause:");
292
- lines.push(` ${String(error.cause)}`);
293
- }
294
- lines.push(
295
- "",
296
- useColors ? chalk.cyan("Suggestion:") : "Suggestion:",
297
- " Verify the file is a valid FIT workout file.",
298
- " Try opening it in Garmin Connect to confirm."
299
- );
300
- return lines.join("\n");
301
- }
302
- if (error instanceof KrdValidationError) {
303
- const lines = [
304
- useColors ? chalk.red("\u2716 Error: Invalid KRD format") : "\u2716 Error: Invalid KRD format",
305
- "",
306
- formatValidationErrors(error.errors)
307
- ];
308
- lines.push(
309
- "",
310
- useColors ? chalk.cyan("Suggestion:") : "Suggestion:",
311
- " Check the KRD file against the schema.",
312
- " Ensure all required fields are present and have valid values."
313
- );
314
- return lines.join("\n");
315
- }
316
- if (error instanceof ToleranceExceededError) {
317
- const lines = [
318
- useColors ? chalk.red("\u2716 Error: Round-trip conversion failed") : "\u2716 Error: Round-trip conversion failed",
319
- "",
320
- formatToleranceViolations(error.violations)
321
- ];
463
+
464
+ // src/utils/pretty-formatters/krd-error.ts
465
+ var formatKrdValidationError = (error, useColors) => {
466
+ const lines = [
467
+ useColors ? chalk3.red("\u2716 Error: Invalid KRD format") : "\u2716 Error: Invalid KRD format",
468
+ "",
469
+ formatValidationErrors(error.errors),
470
+ "",
471
+ useColors ? chalk3.cyan("Suggestion:") : "Suggestion:",
472
+ " Check the KRD file against the schema.",
473
+ " Ensure all required fields are present and have valid values."
474
+ ];
475
+ return lines.join("\n");
476
+ };
477
+
478
+ // src/utils/pretty-formatters/tolerance-error.ts
479
+ init_esm_shims();
480
+ import chalk4 from "chalk";
481
+ var formatToleranceError2 = (error, useColors) => {
482
+ const lines = [
483
+ useColors ? chalk4.red("\u2716 Error: Round-trip conversion failed") : "\u2716 Error: Round-trip conversion failed",
484
+ "",
485
+ formatToleranceViolations(error.violations),
486
+ "",
487
+ useColors ? chalk4.cyan("Suggestion:") : "Suggestion:",
488
+ " The conversion may have lost precision.",
489
+ " Consider adjusting tolerance values if the deviations are acceptable."
490
+ ];
491
+ return lines.join("\n");
492
+ };
493
+
494
+ // src/utils/pretty-formatters/generic-error.ts
495
+ init_esm_shims();
496
+ import chalk5 from "chalk";
497
+ var formatGenericError2 = (error, useColors) => {
498
+ const suggestion = getSuggestionForError(error);
499
+ const lines = [
500
+ useColors ? chalk5.red(`\u2716 Error: ${getErrorTitle(error)}`) : `\u2716 Error: ${getErrorTitle(error)}`,
501
+ "",
502
+ useColors ? chalk5.gray("Details:") : "Details:",
503
+ ` ${error.message}`
504
+ ];
505
+ if (suggestion) {
322
506
  lines.push(
323
507
  "",
324
- useColors ? chalk.cyan("Suggestion:") : "Suggestion:",
325
- " The conversion may have lost precision.",
326
- " Consider adjusting tolerance values if the deviations are acceptable."
508
+ useColors ? chalk5.cyan("Suggestion:") : "Suggestion:",
509
+ ...suggestion.map((s) => ` ${s}`)
327
510
  );
328
- return lines.join("\n");
329
- }
330
- if (error instanceof Error) {
331
- return [
332
- useColors ? chalk.red("\u2716 Error: An unexpected error occurred") : "\u2716 Error: An unexpected error occurred",
333
- "",
334
- useColors ? chalk.gray("Details:") : "Details:",
335
- ` ${error.message}`,
336
- "",
337
- ...error.stack ? [
338
- useColors ? chalk.gray("Stack trace:") : "Stack trace:",
339
- ` ${error.stack}`
340
- ] : []
341
- ].join("\n");
342
511
  }
512
+ return lines.join("\n");
513
+ };
514
+ var formatUnknownError2 = (error, useColors) => {
343
515
  return [
344
- useColors ? chalk.red("\u2716 Error: An unexpected error occurred") : "\u2716 Error: An unexpected error occurred",
516
+ useColors ? chalk5.red("\u2716 Error: An unexpected error occurred") : "\u2716 Error: An unexpected error occurred",
345
517
  "",
346
- useColors ? chalk.gray("Details:") : "Details:",
518
+ useColors ? chalk5.gray("Details:") : "Details:",
347
519
  ` ${String(error)}`
348
520
  ].join("\n");
349
521
  };
350
- var formatErrorAsJson = (error) => {
351
- if (error instanceof FitParsingError) {
352
- return JSON.stringify(
353
- {
354
- success: false,
355
- error: {
356
- type: "FitParsingError",
357
- message: error.message,
358
- cause: error.cause ? String(error.cause) : void 0,
359
- suggestion: "Verify the file is a valid FIT workout file."
360
- }
361
- },
362
- null,
363
- 2
364
- );
522
+
523
+ // src/utils/error-formatter-pretty.ts
524
+ var shouldUseColors2 = () => {
525
+ return isTTY() || process.env.FORCE_COLOR === "1";
526
+ };
527
+ var formatErrorAsPretty = (error) => {
528
+ const useColors = shouldUseColors2();
529
+ if (error instanceof FitParsingError2) {
530
+ return formatFitParsingError(error, useColors);
365
531
  }
366
- if (error instanceof KrdValidationError) {
367
- return JSON.stringify(
368
- {
369
- success: false,
370
- error: {
371
- type: "KrdValidationError",
372
- message: error.message,
373
- errors: error.errors,
374
- suggestion: "Check the KRD file against the schema."
375
- }
376
- },
377
- null,
378
- 2
379
- );
532
+ if (error instanceof KrdValidationError2) {
533
+ return formatKrdValidationError(error, useColors);
380
534
  }
381
- if (error instanceof ToleranceExceededError) {
382
- return JSON.stringify(
383
- {
384
- success: false,
385
- error: {
386
- type: "ToleranceExceededError",
387
- message: error.message,
388
- violations: error.violations,
389
- suggestion: "Consider adjusting tolerance values if acceptable."
390
- }
391
- },
392
- null,
393
- 2
394
- );
535
+ if (error instanceof ToleranceExceededError2) {
536
+ return formatToleranceError2(error, useColors);
395
537
  }
396
538
  if (error instanceof Error) {
397
- return JSON.stringify(
398
- {
399
- success: false,
400
- error: {
401
- type: error.name || "Error",
402
- message: error.message,
403
- stack: error.stack
404
- }
405
- },
406
- null,
407
- 2
408
- );
539
+ return formatGenericError2(error, useColors);
540
+ }
541
+ return formatUnknownError2(error, useColors);
542
+ };
543
+
544
+ // src/utils/error-formatter.ts
545
+ var formatError = (error, options = {}) => {
546
+ if (options.json) {
547
+ return formatErrorAsJson(error);
548
+ }
549
+ return formatErrorAsPretty(error);
550
+ };
551
+
552
+ // src/utils/exit-codes.ts
553
+ init_esm_shims();
554
+ var ExitCode = {
555
+ /** Successful execution */
556
+ SUCCESS: 0,
557
+ /** Invalid arguments or usage error */
558
+ INVALID_ARGUMENT: 1,
559
+ /** File not found */
560
+ FILE_NOT_FOUND: 2,
561
+ /** Permission denied */
562
+ PERMISSION_DENIED: 3,
563
+ /** Parsing error (corrupted or invalid file format) */
564
+ PARSING_ERROR: 4,
565
+ /** Validation error (schema validation failed) */
566
+ VALIDATION_ERROR: 5,
567
+ /** Tolerance exceeded (round-trip validation failed) */
568
+ TOLERANCE_EXCEEDED: 6,
569
+ /** Differences found (diff command - files are different, not an error) */
570
+ DIFFERENCES_FOUND: 10,
571
+ /** Partial success (batch operations with some failures) */
572
+ PARTIAL_SUCCESS: 11,
573
+ /** Directory creation failed */
574
+ DIRECTORY_CREATE_ERROR: 12,
575
+ /** Unknown or unhandled error */
576
+ UNKNOWN_ERROR: 99
577
+ };
578
+
579
+ // src/utils/logger-factory.ts
580
+ init_esm_shims();
581
+ var isCI = () => {
582
+ return process.env.CI === "true" || process.env.NODE_ENV === "production" || !process.stdout.isTTY;
583
+ };
584
+ var createLogger = async (options = {}) => {
585
+ const loggerType = options.type || (isCI() ? "structured" : "pretty");
586
+ if (loggerType === "structured") {
587
+ const { createStructuredLogger: createStructuredLogger2 } = await Promise.resolve().then(() => (init_structured_logger(), structured_logger_exports));
588
+ return createStructuredLogger2(options);
589
+ } else {
590
+ const { createPrettyLogger: createPrettyLogger2 } = await Promise.resolve().then(() => (init_pretty_logger(), pretty_logger_exports));
591
+ return createPrettyLogger2(options);
409
592
  }
410
- return JSON.stringify(
411
- {
412
- success: false,
413
- error: {
414
- type: "UnknownError",
415
- message: String(error)
416
- }
417
- },
418
- null,
419
- 2
420
- );
421
593
  };
422
594
 
595
+ // src/commands/convert/batch.ts
596
+ init_esm_shims();
597
+ import chalk7 from "chalk";
598
+ import ora2 from "ora";
599
+ import { basename, join as join2 } from "path";
600
+
423
601
  // src/utils/file-handler.ts
424
602
  init_esm_shims();
425
- import {
426
- readFile as fsReadFile,
427
- writeFile as fsWriteFile,
428
- mkdir
429
- } from "fs/promises";
603
+ import { readFile as fsReadFile, writeFile as fsWriteFile } from "fs/promises";
430
604
  import { glob } from "glob";
605
+
606
+ // src/utils/directory-handler.ts
607
+ init_esm_shims();
608
+ import { mkdir } from "fs/promises";
431
609
  import { dirname } from "path";
610
+
611
+ // src/utils/fs-errors.ts
612
+ init_esm_shims();
613
+ var isNodeSystemError = (error) => {
614
+ return error instanceof Error && "code" in error;
615
+ };
616
+
617
+ // src/utils/directory-handler.ts
618
+ var createOutputDirectory = async (path2) => {
619
+ const dir = dirname(path2);
620
+ try {
621
+ await mkdir(dir, { recursive: true });
622
+ } catch (error) {
623
+ if (isNodeSystemError(error)) {
624
+ if (error.code === "EACCES") {
625
+ throw new Error(`Permission denied creating directory: ${dir}`);
626
+ }
627
+ if (error.code === "ENOTDIR" || error.code === "EEXIST") {
628
+ throw new Error(
629
+ `Cannot create directory (path exists as file): ${dir}`
630
+ );
631
+ }
632
+ }
633
+ throw new Error(`Failed to create directory: ${dir}`);
634
+ }
635
+ };
636
+
637
+ // src/utils/path-security.ts
638
+ init_esm_shims();
639
+ var validatePathSecurity = (inputPath) => {
640
+ if (inputPath.includes("\0")) {
641
+ throw new Error(`Invalid path: null byte detected in ${inputPath}`);
642
+ }
643
+ if (inputPath.includes("|") || inputPath.includes(";")) {
644
+ throw new Error(
645
+ `Invalid path: shell metacharacters detected in ${inputPath}`
646
+ );
647
+ }
648
+ };
649
+
650
+ // src/utils/file-handler.ts
432
651
  var readFile2 = async (path2, format) => {
652
+ validatePathSecurity(path2);
433
653
  try {
434
654
  if (format === "fit") {
435
655
  const buffer = await fsReadFile(path2);
@@ -438,7 +658,7 @@ var readFile2 = async (path2, format) => {
438
658
  return await fsReadFile(path2, "utf-8");
439
659
  }
440
660
  } catch (error) {
441
- if (error instanceof Error && "code" in error) {
661
+ if (isNodeSystemError(error)) {
442
662
  if (error.code === "ENOENT") {
443
663
  throw new Error(`File not found: ${path2}`);
444
664
  }
@@ -450,28 +670,28 @@ var readFile2 = async (path2, format) => {
450
670
  }
451
671
  };
452
672
  var writeFile = async (path2, data, format) => {
673
+ validatePathSecurity(path2);
674
+ if (format === "fit" && !(data instanceof Uint8Array)) {
675
+ throw new Error("FIT files require Uint8Array data");
676
+ }
677
+ if (format !== "fit" && typeof data !== "string") {
678
+ throw new Error("Text files require string data");
679
+ }
680
+ await createOutputDirectory(path2);
453
681
  try {
454
- const dir = dirname(path2);
455
- await mkdir(dir, { recursive: true });
456
682
  if (format === "fit") {
457
- if (!(data instanceof Uint8Array)) {
458
- throw new Error("FIT files require Uint8Array data");
459
- }
460
683
  await fsWriteFile(path2, data);
461
684
  } else {
462
- if (typeof data !== "string") {
463
- throw new Error("Text files require string data");
464
- }
465
685
  await fsWriteFile(path2, data, "utf-8");
466
686
  }
467
687
  } catch (error) {
468
- if (error instanceof Error && "code" in error) {
688
+ if (isNodeSystemError(error)) {
469
689
  if (error.code === "EACCES") {
470
- throw new Error(`Permission denied: ${path2}`);
690
+ throw new Error(`Permission denied writing file: ${path2}`);
691
+ }
692
+ if (error.code === "EISDIR") {
693
+ throw new Error(`Cannot write file (path is a directory): ${path2}`);
471
694
  }
472
- }
473
- if (error instanceof Error && error.message.includes("require")) {
474
- throw error;
475
695
  }
476
696
  throw new Error(`Failed to write file: ${path2}`);
477
697
  }
@@ -484,23 +704,258 @@ var findFiles = async (pattern) => {
484
704
  return files.sort();
485
705
  };
486
706
 
487
- // src/utils/logger-factory.ts
707
+ // src/commands/convert/single-file.ts
488
708
  init_esm_shims();
489
- var isCI = () => {
490
- return process.env.CI === "true" || process.env.NODE_ENV === "production" || !process.stdout.isTTY;
709
+ import ora from "ora";
710
+
711
+ // src/utils/krd-file-loader.ts
712
+ init_esm_shims();
713
+ var loadFileAsKrd = async (filePath, format, providers) => {
714
+ const detectedFormat = format || detectFormat(filePath);
715
+ if (!detectedFormat) {
716
+ throw new Error(
717
+ `Unable to detect format for file: ${filePath}. Supported formats: .fit, .krd, .tcx, .zwo`
718
+ );
719
+ }
720
+ const fileData = await readFile2(filePath, detectedFormat);
721
+ return convertToKrd(fileData, detectedFormat, providers);
491
722
  };
492
- var createLogger = async (options = {}) => {
493
- const loggerType = options.type || (isCI() ? "structured" : "pretty");
494
- if (loggerType === "structured") {
495
- const { createStructuredLogger: createStructuredLogger2 } = await Promise.resolve().then(() => (init_structured_logger(), structured_logger_exports));
496
- return createStructuredLogger2(options);
723
+ var convertToKrd = async (data, format, providers) => {
724
+ if (format === "fit") {
725
+ if (!(data instanceof Uint8Array)) {
726
+ throw new Error("FIT input must be Uint8Array");
727
+ }
728
+ return providers.convertFitToKrd({ fitBuffer: data });
729
+ }
730
+ if (format === "tcx") {
731
+ if (typeof data !== "string") {
732
+ throw new Error("TCX input must be string");
733
+ }
734
+ return providers.convertTcxToKrd({ tcxString: data });
735
+ }
736
+ if (format === "zwo") {
737
+ if (typeof data !== "string") {
738
+ throw new Error("ZWO input must be string");
739
+ }
740
+ return providers.convertZwiftToKrd({ zwiftString: data });
741
+ }
742
+ if (format === "krd") {
743
+ if (typeof data !== "string") {
744
+ throw new Error("KRD input must be string");
745
+ }
746
+ return JSON.parse(data);
747
+ }
748
+ throw new Error(`Unsupported format: ${format}`);
749
+ };
750
+ var convertFromKrd = async (krd, format, providers) => {
751
+ if (format === "fit") {
752
+ return providers.convertKrdToFit({ krd });
753
+ }
754
+ if (format === "tcx") {
755
+ return providers.convertKrdToTcx({ krd });
756
+ }
757
+ if (format === "zwo") {
758
+ return providers.convertKrdToZwift({ krd });
759
+ }
760
+ if (format === "krd") {
761
+ return JSON.stringify(krd, null, 2);
762
+ }
763
+ throw new Error(`Unsupported output format: ${format}`);
764
+ };
765
+
766
+ // src/commands/convert/single-file.ts
767
+ var convertSingleFile = async (inputFile, outputFile, inputFormat, outputFormat, providers) => {
768
+ const krd = await loadFileAsKrd(inputFile, inputFormat, providers);
769
+ const outputData = await convertFromKrd(krd, outputFormat, providers);
770
+ await writeFile(outputFile, outputData, outputFormat);
771
+ };
772
+ var executeSingleFileConversion = async (options, providers, logger) => {
773
+ const inputFormat = options.inputFormat || detectFormat(options.input);
774
+ if (!inputFormat) {
775
+ const error = new Error(
776
+ `Unable to detect input format from file: ${options.input}. Supported formats: .fit, .krd, .tcx, .zwo`
777
+ );
778
+ error.name = "InvalidArgumentError";
779
+ throw error;
780
+ }
781
+ if (!options.output) {
782
+ const error = new Error("Output file is required");
783
+ error.name = "InvalidArgumentError";
784
+ throw error;
785
+ }
786
+ const outputFormat = options.outputFormat || detectFormat(options.output);
787
+ if (!outputFormat) {
788
+ const error = new Error(
789
+ `Unable to detect output format from file: ${options.output}. Supported formats: .fit, .krd, .tcx, .zwo`
790
+ );
791
+ error.name = "InvalidArgumentError";
792
+ throw error;
793
+ }
794
+ logger.debug("Convert command initialized", {
795
+ input: options.input,
796
+ output: options.output,
797
+ inputFormat,
798
+ outputFormat
799
+ });
800
+ const isTTY2 = process.stdout.isTTY && !options.quiet && !options.json;
801
+ const spinner = isTTY2 ? ora("Converting...").start() : null;
802
+ try {
803
+ await convertSingleFile(
804
+ options.input,
805
+ options.output,
806
+ inputFormat,
807
+ outputFormat,
808
+ providers
809
+ );
810
+ if (options.json) {
811
+ console.log(
812
+ JSON.stringify(
813
+ {
814
+ success: true,
815
+ inputFile: options.input,
816
+ outputFile: options.output,
817
+ inputFormat,
818
+ outputFormat
819
+ },
820
+ null,
821
+ 2
822
+ )
823
+ );
824
+ } else if (spinner) {
825
+ spinner.succeed(
826
+ `Conversion complete: ${options.input} -> ${options.output}`
827
+ );
828
+ } else {
829
+ logger.info("Conversion complete", {
830
+ input: options.input,
831
+ output: options.output
832
+ });
833
+ }
834
+ } catch (error) {
835
+ if (spinner) {
836
+ spinner.fail("Conversion failed");
837
+ }
838
+ throw error;
839
+ }
840
+ };
841
+
842
+ // src/commands/convert/batch.ts
843
+ var executeBatchConversion = async (options, providers, logger) => {
844
+ if (!options.outputDir) {
845
+ const error = new Error("Batch mode requires --output-dir flag");
846
+ error.name = "InvalidArgumentError";
847
+ throw error;
848
+ }
849
+ const outputFormat = options.outputFormat;
850
+ if (!outputFormat) {
851
+ const error = new Error(
852
+ "Batch mode requires --output-format flag to specify target format"
853
+ );
854
+ error.name = "InvalidArgumentError";
855
+ throw error;
856
+ }
857
+ const startTime = Date.now();
858
+ const files = await findFiles(options.input);
859
+ if (files.length === 0) {
860
+ const error = new Error(
861
+ `No files found matching pattern: ${options.input}`
862
+ );
863
+ error.name = "InvalidArgumentError";
864
+ throw error;
865
+ }
866
+ logger.debug("Batch conversion started", {
867
+ pattern: options.input,
868
+ fileCount: files.length,
869
+ outputDir: options.outputDir,
870
+ outputFormat
871
+ });
872
+ const isTTY2 = process.stdout.isTTY && !options.quiet && !options.json;
873
+ const spinner = isTTY2 ? ora2("Processing batch conversion...").start() : null;
874
+ const results = [];
875
+ for (const [index, file] of files.entries()) {
876
+ const fileNum = index + 1;
877
+ const fileName = basename(file);
878
+ if (spinner) {
879
+ spinner.text = `Converting ${fileNum}/${files.length}: ${fileName}`;
880
+ } else {
881
+ logger.info(`Converting ${fileNum}/${files.length}: ${fileName}`);
882
+ }
883
+ try {
884
+ const inputFormat = options.inputFormat || detectFormat(file);
885
+ if (!inputFormat) {
886
+ throw new Error(`Unable to detect format for file: ${file}`);
887
+ }
888
+ const outputFileName = fileName.replace(
889
+ /\.(fit|krd|tcx|zwo)$/i,
890
+ `.${outputFormat}`
891
+ );
892
+ const outputFile = join2(options.outputDir, outputFileName);
893
+ await convertSingleFile(
894
+ file,
895
+ outputFile,
896
+ inputFormat,
897
+ outputFormat,
898
+ providers
899
+ );
900
+ results.push({ success: true, inputFile: file, outputFile });
901
+ } catch (error) {
902
+ results.push({
903
+ success: false,
904
+ inputFile: file,
905
+ error: error instanceof Error ? error.message : String(error)
906
+ });
907
+ logger.error(`Failed to convert ${file}`, { error });
908
+ }
909
+ }
910
+ const totalTime = Date.now() - startTime;
911
+ const successful = results.filter((r) => r.success);
912
+ const failed = results.filter((r) => !r.success);
913
+ if (spinner) {
914
+ spinner.stop();
915
+ }
916
+ if (!options.json) {
917
+ console.log("\nBatch conversion complete:");
918
+ console.log(
919
+ chalk7.green(` Successful: ${successful.length}/${files.length}`)
920
+ );
921
+ if (failed.length > 0) {
922
+ console.log(chalk7.red(` Failed: ${failed.length}/${files.length}`));
923
+ }
924
+ console.log(` Total time: ${(totalTime / 1e3).toFixed(2)}s`);
925
+ if (failed.length > 0) {
926
+ console.log(chalk7.red("\nFailed conversions:"));
927
+ for (const result of failed) {
928
+ console.log(chalk7.red(` ${result.inputFile}: ${result.error}`));
929
+ }
930
+ }
497
931
  } else {
498
- const { createPrettyLogger: createPrettyLogger2 } = await Promise.resolve().then(() => (init_pretty_logger(), pretty_logger_exports));
499
- return createPrettyLogger2(options);
932
+ console.log(
933
+ JSON.stringify(
934
+ {
935
+ success: failed.length === 0,
936
+ total: files.length,
937
+ successful: successful.length,
938
+ failed: failed.length,
939
+ totalTime,
940
+ results
941
+ },
942
+ null,
943
+ 2
944
+ )
945
+ );
946
+ }
947
+ if (failed.length === 0) {
948
+ return ExitCode.SUCCESS;
949
+ }
950
+ if (successful.length === 0) {
951
+ return ExitCode.INVALID_ARGUMENT;
500
952
  }
953
+ return ExitCode.PARTIAL_SUCCESS;
501
954
  };
502
955
 
503
- // src/commands/convert.ts
956
+ // src/commands/convert/types.ts
957
+ init_esm_shims();
958
+ import { z as z3 } from "zod";
504
959
  var convertOptionsSchema = z3.object({
505
960
  input: z3.string(),
506
961
  output: z3.string().optional(),
@@ -512,51 +967,14 @@ var convertOptionsSchema = z3.object({
512
967
  json: z3.boolean().optional(),
513
968
  logFormat: z3.enum(["pretty", "structured"]).optional()
514
969
  });
970
+
971
+ // src/commands/convert/index.ts
515
972
  var isBatchMode = (input) => {
516
973
  return input.includes("*") || input.includes("?");
517
974
  };
518
- var convertSingleFile = async (inputFile, outputFile, inputFormat, outputFormat, providers) => {
519
- const inputData = await readFile2(inputFile, inputFormat);
520
- let krd;
521
- if (inputFormat === "fit") {
522
- if (!(inputData instanceof Uint8Array)) {
523
- throw new Error("FIT input must be Uint8Array");
524
- }
525
- krd = await providers.convertFitToKrd({ fitBuffer: inputData });
526
- } else if (inputFormat === "tcx") {
527
- if (typeof inputData !== "string") {
528
- throw new Error("TCX input must be string");
529
- }
530
- krd = await providers.convertTcxToKrd({ tcxString: inputData });
531
- } else if (inputFormat === "zwo") {
532
- if (typeof inputData !== "string") {
533
- throw new Error("ZWO input must be string");
534
- }
535
- krd = await providers.convertZwiftToKrd({ zwiftString: inputData });
536
- } else if (inputFormat === "krd") {
537
- if (typeof inputData !== "string") {
538
- throw new Error("KRD input must be string");
539
- }
540
- krd = JSON.parse(inputData);
541
- } else {
542
- throw new Error(`Unsupported input format: ${inputFormat}`);
543
- }
544
- let outputData;
545
- if (outputFormat === "fit") {
546
- outputData = await providers.convertKrdToFit({ krd });
547
- } else if (outputFormat === "tcx") {
548
- outputData = await providers.convertKrdToTcx({ krd });
549
- } else if (outputFormat === "zwo") {
550
- outputData = await providers.convertKrdToZwift({ krd });
551
- } else if (outputFormat === "krd") {
552
- outputData = JSON.stringify(krd, null, 2);
553
- } else {
554
- throw new Error(`Unsupported output format: ${outputFormat}`);
555
- }
556
- await writeFile(outputFile, outputData, outputFormat);
557
- };
558
975
  var convertCommand = async (options) => {
559
- const config = await loadConfig();
976
+ const configResult = await loadConfigWithMetadata();
977
+ const { config } = configResult;
560
978
  const mergedOptions = mergeWithConfig(options, config);
561
979
  const optionsWithDefaults = {
562
980
  ...mergedOptions,
@@ -569,196 +987,32 @@ var convertCommand = async (options) => {
569
987
  logFormat: mergedOptions.logFormat || config.logFormat
570
988
  };
571
989
  const validatedOptions = convertOptionsSchema.parse(optionsWithDefaults);
990
+ if (validatedOptions.output && validatedOptions.outputDir) {
991
+ const error = new Error(
992
+ "Cannot use both --output and --output-dir. Use --output for single file, --output-dir for batch conversion."
993
+ );
994
+ error.name = "InvalidArgumentError";
995
+ throw error;
996
+ }
572
997
  const logger = await createLogger({
573
998
  type: validatedOptions.logFormat,
574
999
  level: validatedOptions.verbose ? "debug" : validatedOptions.quiet ? "error" : "info",
575
1000
  quiet: validatedOptions.quiet
576
1001
  });
1002
+ if (configResult.loadedFrom) {
1003
+ logger.debug("Configuration loaded", { path: configResult.loadedFrom });
1004
+ } else {
1005
+ logger.debug("No configuration file found", {
1006
+ searchedPaths: configResult.searchedPaths
1007
+ });
1008
+ }
577
1009
  try {
578
- const batchMode = isBatchMode(validatedOptions.input);
579
- if (batchMode) {
580
- if (!validatedOptions.outputDir) {
581
- const error = new Error("Batch mode requires --output-dir flag");
582
- error.name = "InvalidArgumentError";
583
- throw error;
584
- }
585
- const outputFormat = validatedOptions.outputFormat;
586
- if (!outputFormat) {
587
- const error = new Error(
588
- "Batch mode requires --output-format flag to specify target format"
589
- );
590
- error.name = "InvalidArgumentError";
591
- throw error;
592
- }
593
- const providers = createDefaultProviders(logger);
594
- const startTime = Date.now();
595
- const files = await findFiles(validatedOptions.input);
596
- if (files.length === 0) {
597
- const error = new Error(
598
- `No files found matching pattern: ${validatedOptions.input}`
599
- );
600
- error.name = "InvalidArgumentError";
601
- throw error;
602
- }
603
- logger.debug("Batch conversion started", {
604
- pattern: validatedOptions.input,
605
- fileCount: files.length,
606
- outputDir: validatedOptions.outputDir,
607
- outputFormat
608
- });
609
- const isTTY2 = process.stdout.isTTY && !validatedOptions.quiet && !validatedOptions.json;
610
- const spinner = isTTY2 ? ora("Processing batch conversion...").start() : null;
611
- const results = [];
612
- for (const [index, file] of files.entries()) {
613
- const fileNum = index + 1;
614
- const fileName = basename(file);
615
- if (spinner) {
616
- spinner.text = `Converting ${fileNum}/${files.length}: ${fileName}`;
617
- } else {
618
- logger.info(`Converting ${fileNum}/${files.length}: ${fileName}`);
619
- }
620
- try {
621
- const inputFormat = validatedOptions.inputFormat || detectFormat(file);
622
- if (!inputFormat) {
623
- throw new Error(`Unable to detect format for file: ${file}`);
624
- }
625
- const outputFileName = fileName.replace(
626
- /\.(fit|krd|tcx|zwo)$/i,
627
- `.${outputFormat}`
628
- );
629
- const outputFile = join2(validatedOptions.outputDir, outputFileName);
630
- await convertSingleFile(
631
- file,
632
- outputFile,
633
- inputFormat,
634
- outputFormat,
635
- providers
636
- );
637
- results.push({
638
- success: true,
639
- inputFile: file,
640
- outputFile
641
- });
642
- } catch (error) {
643
- results.push({
644
- success: false,
645
- inputFile: file,
646
- error: error instanceof Error ? error.message : String(error)
647
- });
648
- logger.error(`Failed to convert ${file}`, { error });
649
- }
650
- }
651
- const totalTime = Date.now() - startTime;
652
- const successful = results.filter((r) => r.success);
653
- const failed = results.filter((r) => !r.success);
654
- if (spinner) {
655
- spinner.stop();
656
- }
657
- if (!validatedOptions.json) {
658
- console.log("\nBatch conversion complete:");
659
- console.log(
660
- chalk3.green(` \u2713 Successful: ${successful.length}/${files.length}`)
661
- );
662
- if (failed.length > 0) {
663
- console.log(
664
- chalk3.red(` \u2717 Failed: ${failed.length}/${files.length}`)
665
- );
666
- }
667
- console.log(` Total time: ${(totalTime / 1e3).toFixed(2)}s`);
668
- if (failed.length > 0) {
669
- console.log(chalk3.red("\nFailed conversions:"));
670
- for (const result of failed) {
671
- console.log(chalk3.red(` ${result.inputFile}: ${result.error}`));
672
- }
673
- }
674
- } else {
675
- console.log(
676
- JSON.stringify(
677
- {
678
- success: failed.length === 0,
679
- total: files.length,
680
- successful: successful.length,
681
- failed: failed.length,
682
- totalTime,
683
- results
684
- },
685
- null,
686
- 2
687
- )
688
- );
689
- }
690
- if (failed.length > 0) {
691
- process.exit(1);
692
- }
1010
+ const providers = createAllProviders(logger);
1011
+ if (isBatchMode(validatedOptions.input)) {
1012
+ return await executeBatchConversion(validatedOptions, providers, logger);
693
1013
  } else {
694
- const inputFormat = validatedOptions.inputFormat || detectFormat(validatedOptions.input);
695
- if (!inputFormat) {
696
- const error = new Error(
697
- `Unable to detect input format from file: ${validatedOptions.input}. Supported formats: .fit, .krd, .tcx, .zwo`
698
- );
699
- error.name = "InvalidArgumentError";
700
- throw error;
701
- }
702
- if (!validatedOptions.output) {
703
- const error = new Error("Output file is required");
704
- error.name = "InvalidArgumentError";
705
- throw error;
706
- }
707
- const outputFormat = validatedOptions.outputFormat || detectFormat(validatedOptions.output);
708
- if (!outputFormat) {
709
- const error = new Error(
710
- `Unable to detect output format from file: ${validatedOptions.output}. Supported formats: .fit, .krd, .tcx, .zwo`
711
- );
712
- error.name = "InvalidArgumentError";
713
- throw error;
714
- }
715
- const providers = createDefaultProviders(logger);
716
- logger.debug("Convert command initialized", {
717
- input: validatedOptions.input,
718
- output: validatedOptions.output,
719
- inputFormat,
720
- outputFormat
721
- });
722
- const isTTY2 = process.stdout.isTTY && !validatedOptions.quiet && !validatedOptions.json;
723
- const spinner = isTTY2 ? ora("Converting...").start() : null;
724
- try {
725
- await convertSingleFile(
726
- validatedOptions.input,
727
- validatedOptions.output,
728
- inputFormat,
729
- outputFormat,
730
- providers
731
- );
732
- if (validatedOptions.json) {
733
- console.log(
734
- JSON.stringify(
735
- {
736
- success: true,
737
- inputFile: validatedOptions.input,
738
- outputFile: validatedOptions.output,
739
- inputFormat,
740
- outputFormat
741
- },
742
- null,
743
- 2
744
- )
745
- );
746
- } else if (spinner) {
747
- spinner.succeed(
748
- `Conversion complete: ${validatedOptions.input} \u2192 ${validatedOptions.output}`
749
- );
750
- } else {
751
- logger.info("Conversion complete", {
752
- input: validatedOptions.input,
753
- output: validatedOptions.output
754
- });
755
- }
756
- } catch (error) {
757
- if (spinner) {
758
- spinner.fail("Conversion failed");
759
- }
760
- throw error;
761
- }
1014
+ await executeSingleFileConversion(validatedOptions, providers, logger);
1015
+ return ExitCode.SUCCESS;
762
1016
  }
763
1017
  } catch (error) {
764
1018
  logger.error("Conversion failed", { error });
@@ -770,108 +1024,38 @@ var convertCommand = async (options) => {
770
1024
  } else {
771
1025
  console.error(formattedError);
772
1026
  }
773
- let exitCode = 99;
1027
+ let exitCode = ExitCode.UNKNOWN_ERROR;
774
1028
  if (error instanceof Error) {
775
1029
  if (error.message.includes("File not found")) {
776
- exitCode = 2;
1030
+ exitCode = ExitCode.FILE_NOT_FOUND;
777
1031
  } else if (error.message.includes("Permission denied")) {
778
- exitCode = 3;
779
- } else if (error instanceof FitParsingError2) {
780
- exitCode = 4;
781
- } else if (error instanceof KrdValidationError2) {
782
- exitCode = 5;
783
- } else if (error instanceof ToleranceExceededError2) {
784
- exitCode = 6;
1032
+ exitCode = ExitCode.PERMISSION_DENIED;
1033
+ } else if (error instanceof FitParsingError3) {
1034
+ exitCode = ExitCode.PARSING_ERROR;
1035
+ } else if (error instanceof KrdValidationError3) {
1036
+ exitCode = ExitCode.VALIDATION_ERROR;
1037
+ } else if (error instanceof ToleranceExceededError3) {
1038
+ exitCode = ExitCode.TOLERANCE_EXCEEDED;
785
1039
  } else if (error.name === "InvalidArgumentError") {
786
- exitCode = 1;
1040
+ exitCode = ExitCode.INVALID_ARGUMENT;
787
1041
  }
788
1042
  }
789
1043
  return exitCode;
790
1044
  }
791
- return 0;
792
1045
  };
793
1046
 
794
1047
  // src/commands/diff.ts
795
1048
  init_esm_shims();
796
- import { createDefaultProviders as createDefaultProviders2 } from "@kaiord/core";
797
- import chalk4 from "chalk";
798
- import { z as z4 } from "zod";
799
- var diffOptionsSchema = z4.object({
800
- file1: z4.string(),
801
- file2: z4.string(),
802
- format1: fileFormatSchema.optional(),
803
- format2: fileFormatSchema.optional(),
804
- verbose: z4.boolean().optional(),
805
- quiet: z4.boolean().optional(),
806
- json: z4.boolean().optional(),
807
- logFormat: z4.enum(["pretty", "structured"]).optional()
808
- });
809
- var loadFileAsKrd = async (filePath, format, providers) => {
810
- const detectedFormat = format || detectFormat(filePath);
811
- if (!detectedFormat) {
812
- throw new Error(
813
- `Unable to detect format for file: ${filePath}. Supported formats: .fit, .krd, .tcx, .zwo`
814
- );
815
- }
816
- const fileData = await readFile2(filePath, detectedFormat);
817
- let krd;
818
- if (detectedFormat === "fit") {
819
- if (!(fileData instanceof Uint8Array)) {
820
- throw new Error("FIT input must be Uint8Array");
821
- }
822
- krd = await providers.convertFitToKrd({ fitBuffer: fileData });
823
- } else if (detectedFormat === "tcx") {
824
- if (typeof fileData !== "string") {
825
- throw new Error("TCX input must be string");
826
- }
827
- krd = await providers.convertTcxToKrd({ tcxString: fileData });
828
- } else if (detectedFormat === "zwo") {
829
- if (typeof fileData !== "string") {
830
- throw new Error("ZWO input must be string");
831
- }
832
- krd = await providers.convertZwiftToKrd({ zwiftString: fileData });
833
- } else if (detectedFormat === "krd") {
834
- if (typeof fileData !== "string") {
835
- throw new Error("KRD input must be string");
836
- }
837
- krd = JSON.parse(fileData);
838
- } else {
839
- throw new Error(`Unsupported format: ${detectedFormat}`);
840
- }
841
- return krd;
842
- };
843
- var isDifferent = (value1, value2) => {
844
- if (value1 === value2) return false;
845
- if (value1 === null || value1 === void 0) return true;
846
- if (value2 === null || value2 === void 0) return true;
847
- if (typeof value1 === "object" && typeof value2 === "object") {
848
- return JSON.stringify(value1) !== JSON.stringify(value2);
849
- }
850
- return value1 !== value2;
851
- };
852
- var compareMetadata = (krd1, krd2) => {
853
- const differences = [];
854
- const metadataFields = [
855
- "created",
856
- "manufacturer",
857
- "product",
858
- "serialNumber",
859
- "sport",
860
- "subSport"
861
- ];
862
- for (const field of metadataFields) {
863
- const value1 = krd1.metadata[field];
864
- const value2 = krd2.metadata[field];
865
- if (isDifferent(value1, value2)) {
866
- differences.push({
867
- field,
868
- file1Value: value1,
869
- file2Value: value2
870
- });
871
- }
872
- }
873
- return differences;
874
- };
1049
+
1050
+ // src/commands/diff/index.ts
1051
+ init_esm_shims();
1052
+ import { createAllProviders as createAllProviders2 } from "@kaiord/all";
1053
+
1054
+ // src/commands/diff/comparators.ts
1055
+ init_esm_shims();
1056
+
1057
+ // src/commands/diff/compare-steps.ts
1058
+ init_esm_shims();
875
1059
  var compareSteps = (krd1, krd2) => {
876
1060
  const workout1 = krd1.extensions?.workout;
877
1061
  const workout2 = krd2.extensions?.workout;
@@ -901,35 +1085,62 @@ var compareSteps = (krd1, krd2) => {
901
1085
  continue;
902
1086
  }
903
1087
  if (!step1 || !step2) continue;
904
- const stepFields = [
905
- "stepIndex",
906
- "name",
907
- "durationType",
908
- "duration",
909
- "targetType",
910
- "target",
911
- "intensity",
912
- "notes",
913
- "equipment"
914
- ];
915
- for (const field of stepFields) {
916
- const value1 = step1[field];
917
- const value2 = step2[field];
918
- if (isDifferent(value1, value2)) {
919
- differences.push({
920
- stepIndex: i,
921
- field,
922
- file1Value: value1,
923
- file2Value: value2
924
- });
925
- }
1088
+ compareStepFields(i, step1, step2, differences);
1089
+ }
1090
+ return { file1Count: steps1.length, file2Count: steps2.length, differences };
1091
+ };
1092
+ var STEP_FIELDS = [
1093
+ "stepIndex",
1094
+ "name",
1095
+ "durationType",
1096
+ "duration",
1097
+ "targetType",
1098
+ "target",
1099
+ "intensity",
1100
+ "notes",
1101
+ "equipment"
1102
+ ];
1103
+ function compareStepFields(stepIndex, step1, step2, differences) {
1104
+ for (const field of STEP_FIELDS) {
1105
+ if (isDifferent(step1[field], step2[field])) {
1106
+ differences.push({
1107
+ stepIndex,
1108
+ field,
1109
+ file1Value: step1[field],
1110
+ file2Value: step2[field]
1111
+ });
926
1112
  }
927
1113
  }
928
- return {
929
- file1Count: steps1.length,
930
- file2Count: steps2.length,
931
- differences
932
- };
1114
+ }
1115
+
1116
+ // src/commands/diff/comparators.ts
1117
+ var isDifferent = (value1, value2) => {
1118
+ if (value1 === value2) return false;
1119
+ if (value1 === null || value1 === void 0) return true;
1120
+ if (value2 === null || value2 === void 0) return true;
1121
+ if (typeof value1 === "object" && typeof value2 === "object") {
1122
+ return JSON.stringify(value1) !== JSON.stringify(value2);
1123
+ }
1124
+ return value1 !== value2;
1125
+ };
1126
+ var compareMetadata = (krd1, krd2) => {
1127
+ const differences = [];
1128
+ const metadataFields = [
1129
+ "created",
1130
+ "manufacturer",
1131
+ "product",
1132
+ "serialNumber",
1133
+ "sport",
1134
+ "subSport"
1135
+ ];
1136
+ for (const field of metadataFields) {
1137
+ const value1 = krd1.metadata[field];
1138
+ const value2 = krd2.metadata[field];
1139
+ if (isDifferent(value1, value2)) {
1140
+ differences.push({ field, file1Value: value1, file2Value: value2 });
1141
+ }
1142
+ }
1143
+ return differences;
933
1144
  };
934
1145
  var compareExtensions = (krd1, krd2) => {
935
1146
  const ext1 = krd1.extensions || {};
@@ -939,72 +1150,82 @@ var compareExtensions = (krd1, krd2) => {
939
1150
  const allKeys = /* @__PURE__ */ new Set([...keys1, ...keys2]);
940
1151
  const differences = [];
941
1152
  for (const key of allKeys) {
942
- const value1 = ext1[key];
943
- const value2 = ext2[key];
944
- if (isDifferent(value1, value2)) {
945
- differences.push({
946
- key,
947
- file1Value: value1,
948
- file2Value: value2
949
- });
1153
+ if (isDifferent(ext1[key], ext2[key])) {
1154
+ differences.push({ key, file1Value: ext1[key], file2Value: ext2[key] });
950
1155
  }
951
1156
  }
952
- return {
953
- file1Keys: keys1,
954
- file2Keys: keys2,
955
- differences
956
- };
1157
+ return { file1Keys: keys1, file2Keys: keys2, differences };
957
1158
  };
1159
+
1160
+ // src/commands/diff/formatter.ts
1161
+ init_esm_shims();
1162
+ import chalk8 from "chalk";
958
1163
  var formatDiffPretty = (result, file1, file2) => {
959
1164
  if (result.identical) {
960
- return chalk4.green("\u2713 Files are identical");
1165
+ return chalk8.green("Files are identical");
961
1166
  }
962
1167
  const lines = [];
963
- lines.push(chalk4.yellow(`
1168
+ lines.push(chalk8.yellow(`
964
1169
  Comparing: ${file1} vs ${file2}
965
1170
  `));
966
1171
  if (result.metadataDiff && result.metadataDiff.length > 0) {
967
- lines.push(chalk4.bold("Metadata Differences:"));
1172
+ lines.push(chalk8.bold("Metadata Differences:"));
968
1173
  for (const diff of result.metadataDiff) {
969
1174
  lines.push(
970
- ` ${chalk4.cyan(diff.field)}:`,
971
- ` ${chalk4.red("-")} ${JSON.stringify(diff.file1Value)}`,
972
- ` ${chalk4.green("+")} ${JSON.stringify(diff.file2Value)}`
1175
+ ` ${chalk8.cyan(diff.field)}:`,
1176
+ ` ${chalk8.red("-")} ${JSON.stringify(diff.file1Value)}`,
1177
+ ` ${chalk8.green("+")} ${JSON.stringify(diff.file2Value)}`
973
1178
  );
974
1179
  }
975
1180
  lines.push("");
976
1181
  }
977
1182
  if (result.stepsDiff && result.stepsDiff.differences.length > 0) {
978
- lines.push(chalk4.bold("Workout Steps Differences:"));
1183
+ lines.push(chalk8.bold("Workout Steps Differences:"));
979
1184
  lines.push(
980
1185
  ` Step count: ${result.stepsDiff.file1Count} vs ${result.stepsDiff.file2Count}`
981
1186
  );
982
1187
  for (const diff of result.stepsDiff.differences) {
983
1188
  lines.push(
984
- ` Step ${diff.stepIndex} - ${chalk4.cyan(diff.field)}:`,
985
- ` ${chalk4.red("-")} ${JSON.stringify(diff.file1Value)}`,
986
- ` ${chalk4.green("+")} ${JSON.stringify(diff.file2Value)}`
1189
+ ` Step ${diff.stepIndex} - ${chalk8.cyan(diff.field)}:`,
1190
+ ` ${chalk8.red("-")} ${JSON.stringify(diff.file1Value)}`,
1191
+ ` ${chalk8.green("+")} ${JSON.stringify(diff.file2Value)}`
987
1192
  );
988
1193
  }
989
1194
  lines.push("");
990
1195
  }
991
1196
  if (result.extensionsDiff && result.extensionsDiff.differences.length > 0) {
992
- lines.push(chalk4.bold("Extensions Differences:"));
1197
+ lines.push(chalk8.bold("Extensions Differences:"));
993
1198
  lines.push(
994
1199
  ` Keys in file1: ${result.extensionsDiff.file1Keys.join(", ")}`,
995
1200
  ` Keys in file2: ${result.extensionsDiff.file2Keys.join(", ")}`
996
1201
  );
997
1202
  for (const diff of result.extensionsDiff.differences) {
998
1203
  lines.push(
999
- ` ${chalk4.cyan(diff.key)}:`,
1000
- ` ${chalk4.red("-")} ${JSON.stringify(diff.file1Value)}`,
1001
- ` ${chalk4.green("+")} ${JSON.stringify(diff.file2Value)}`
1204
+ ` ${chalk8.cyan(diff.key)}:`,
1205
+ ` ${chalk8.red("-")} ${JSON.stringify(diff.file1Value)}`,
1206
+ ` ${chalk8.green("+")} ${JSON.stringify(diff.file2Value)}`
1002
1207
  );
1003
1208
  }
1004
1209
  lines.push("");
1005
1210
  }
1006
1211
  return lines.join("\n");
1007
1212
  };
1213
+
1214
+ // src/commands/diff/types.ts
1215
+ init_esm_shims();
1216
+ import { z as z4 } from "zod";
1217
+ var diffOptionsSchema = z4.object({
1218
+ file1: z4.string(),
1219
+ file2: z4.string(),
1220
+ format1: fileFormatSchema.optional(),
1221
+ format2: fileFormatSchema.optional(),
1222
+ verbose: z4.boolean().optional(),
1223
+ quiet: z4.boolean().optional(),
1224
+ json: z4.boolean().optional(),
1225
+ logFormat: z4.enum(["pretty", "structured"]).optional()
1226
+ });
1227
+
1228
+ // src/commands/diff/index.ts
1008
1229
  var diffCommand = async (options) => {
1009
1230
  const validatedOptions = diffOptionsSchema.parse(options);
1010
1231
  const logger = await createLogger({
@@ -1012,8 +1233,16 @@ var diffCommand = async (options) => {
1012
1233
  level: validatedOptions.verbose ? "debug" : validatedOptions.quiet ? "error" : "info",
1013
1234
  quiet: validatedOptions.quiet
1014
1235
  });
1236
+ const configResult = await loadConfigWithMetadata();
1237
+ if (configResult.loadedFrom) {
1238
+ logger.debug("Configuration loaded", { path: configResult.loadedFrom });
1239
+ } else {
1240
+ logger.debug("No configuration file found", {
1241
+ searchedPaths: configResult.searchedPaths
1242
+ });
1243
+ }
1015
1244
  try {
1016
- const providers = createDefaultProviders2(logger);
1245
+ const providers = createAllProviders2(logger);
1017
1246
  logger.debug("Loading files for comparison", {
1018
1247
  file1: validatedOptions.file1,
1019
1248
  file2: validatedOptions.file2
@@ -1053,14 +1282,11 @@ var diffCommand = async (options) => {
1053
1282
  )
1054
1283
  );
1055
1284
  } else {
1056
- const output = formatDiffPretty(
1057
- result,
1058
- validatedOptions.file1,
1059
- validatedOptions.file2
1285
+ console.log(
1286
+ formatDiffPretty(result, validatedOptions.file1, validatedOptions.file2)
1060
1287
  );
1061
- console.log(output);
1062
1288
  }
1063
- return identical ? 0 : 1;
1289
+ return identical ? ExitCode.SUCCESS : ExitCode.DIFFERENCES_FOUND;
1064
1290
  } catch (error) {
1065
1291
  logger.error("Diff command failed", { error });
1066
1292
  const formattedError = formatError(error, {
@@ -1071,20 +1297,20 @@ var diffCommand = async (options) => {
1071
1297
  } else {
1072
1298
  console.error(formattedError);
1073
1299
  }
1074
- return 99;
1300
+ return ExitCode.UNKNOWN_ERROR;
1075
1301
  }
1076
1302
  };
1077
1303
 
1078
1304
  // src/commands/validate.ts
1079
1305
  init_esm_shims();
1080
1306
  import {
1081
- createDefaultProviders as createDefaultProviders3,
1082
1307
  createToleranceChecker,
1083
1308
  toleranceConfigSchema,
1084
1309
  validateRoundTrip
1085
1310
  } from "@kaiord/core";
1311
+ import { createAllProviders as createAllProviders3 } from "@kaiord/all";
1086
1312
  import { readFile as fsReadFile2 } from "fs/promises";
1087
- import ora2 from "ora";
1313
+ import ora3 from "ora";
1088
1314
  import { z as z5 } from "zod";
1089
1315
  var validateOptionsSchema = z5.object({
1090
1316
  input: z5.string(),
@@ -1098,7 +1324,8 @@ var validateCommand = async (options) => {
1098
1324
  let logger;
1099
1325
  let spinner = null;
1100
1326
  try {
1101
- const config = await loadConfig();
1327
+ const configResult = await loadConfigWithMetadata();
1328
+ const { config } = configResult;
1102
1329
  const mergedOptions = mergeWithConfig(
1103
1330
  options,
1104
1331
  config
@@ -1118,6 +1345,13 @@ var validateCommand = async (options) => {
1118
1345
  level: opts.verbose ? "debug" : opts.quiet ? "error" : "info",
1119
1346
  quiet: opts.quiet
1120
1347
  });
1348
+ if (configResult.loadedFrom) {
1349
+ logger.debug("Configuration loaded", { path: configResult.loadedFrom });
1350
+ } else {
1351
+ logger.debug("No configuration file found", {
1352
+ searchedPaths: configResult.searchedPaths
1353
+ });
1354
+ }
1121
1355
  const format = detectFormat(opts.input);
1122
1356
  if (!format) {
1123
1357
  throw new Error(`Unable to detect format from file: ${opts.input}`);
@@ -1144,7 +1378,7 @@ var validateCommand = async (options) => {
1144
1378
  config: toleranceConfig
1145
1379
  });
1146
1380
  }
1147
- const providers = createDefaultProviders3(logger);
1381
+ const providers = createAllProviders3(logger);
1148
1382
  const toleranceChecker = toleranceConfig ? createToleranceChecker(toleranceConfig) : providers.toleranceChecker;
1149
1383
  const roundTripValidator = validateRoundTrip(
1150
1384
  providers.fitReader,
@@ -1152,7 +1386,7 @@ var validateCommand = async (options) => {
1152
1386
  toleranceChecker,
1153
1387
  logger
1154
1388
  );
1155
- spinner = opts.quiet || opts.json ? null : ora2("Validating round-trip conversion...").start();
1389
+ spinner = opts.quiet || opts.json ? null : ora3("Validating round-trip conversion...").start();
1156
1390
  logger?.info("Starting round-trip validation", { file: opts.input });
1157
1391
  const violations = await roundTripValidator.validateFitToKrdToFit({
1158
1392
  originalFit: inputData
@@ -1182,9 +1416,9 @@ var validateCommand = async (options) => {
1182
1416
  )
1183
1417
  );
1184
1418
  } else if (!opts.quiet) {
1185
- console.log("\u2713 Round-trip validation passed");
1419
+ console.log("Round-trip validation passed");
1186
1420
  }
1187
- process.exit(0);
1421
+ return ExitCode.SUCCESS;
1188
1422
  } else {
1189
1423
  logger?.warn("Round-trip validation failed", {
1190
1424
  violationCount: violations.length
@@ -1203,10 +1437,10 @@ var validateCommand = async (options) => {
1203
1437
  )
1204
1438
  );
1205
1439
  } else {
1206
- console.error("\u2716 Round-trip validation failed\n");
1440
+ console.error("Round-trip validation failed\n");
1207
1441
  console.error(formatToleranceViolations(violations));
1208
1442
  }
1209
- process.exit(1);
1443
+ return ExitCode.TOLERANCE_EXCEEDED;
1210
1444
  }
1211
1445
  } catch (error) {
1212
1446
  logger?.error("Validation failed", { error });
@@ -1232,7 +1466,15 @@ var validateCommand = async (options) => {
1232
1466
  } else {
1233
1467
  console.error(formatError(error, { json: false }));
1234
1468
  }
1235
- process.exit(1);
1469
+ if (error instanceof Error) {
1470
+ if (error.message.includes("File not found")) {
1471
+ return ExitCode.FILE_NOT_FOUND;
1472
+ }
1473
+ if (error.message.includes("only supports") || error.message.includes("Unable to detect")) {
1474
+ return ExitCode.INVALID_ARGUMENT;
1475
+ }
1476
+ }
1477
+ return ExitCode.UNKNOWN_ERROR;
1236
1478
  } finally {
1237
1479
  spinner?.stop();
1238
1480
  }
@@ -1244,33 +1486,8 @@ var __dirname2 = dirname2(__filename2);
1244
1486
  var packageJsonPath = __dirname2.includes("/dist") ? join3(__dirname2, "../../package.json") : join3(__dirname2, "../../package.json");
1245
1487
  var packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
1246
1488
  var version = packageJson.version;
1247
- var showKiroEasterEgg = () => {
1248
- console.log(
1249
- chalk5.cyan(`
1250
- \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
1251
- \u2551 \u2551
1252
- \u2551 \u{1F47B} Built with Kiro AI during Kiroween Hackathon \u{1F47B} \u2551
1253
- \u2551 \u2551
1254
- \u2551 Kiro helped design, architect, and implement this \u2551
1255
- \u2551 entire CLI tool through spec-driven development. \u2551
1256
- \u2551 \u2551
1257
- \u2551 Learn more about Kiroween: \u2551
1258
- \u2551 \u{1F449} http://kiroween.devpost.com/ \u2551
1259
- \u2551 \u2551
1260
- \u2551 Kiro: Your AI pair programmer for building better \u2551
1261
- \u2551 software, faster. \u{1F680} \u2551
1262
- \u2551 \u2551
1263
- \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
1264
- `)
1265
- );
1266
- process.exit(0);
1267
- };
1268
1489
  var main = async () => {
1269
1490
  try {
1270
- const args = process.argv.slice(2);
1271
- if (args.includes("--kiro") || args.includes("--kiroween")) {
1272
- showKiroEasterEgg();
1273
- }
1274
1491
  await yargs(hideBin(process.argv)).scriptName("kaiord").usage("$0 <command> [options]").command(
1275
1492
  "convert",
1276
1493
  "Convert workout files between formats",
@@ -1324,7 +1541,7 @@ var main = async () => {
1324
1541
  }
1325
1542
  ).command(
1326
1543
  "validate",
1327
- "Validate round-trip conversion of workout files",
1544
+ "Validate round-trip conversion of FIT files (FIT only)",
1328
1545
  (yargs2) => {
1329
1546
  return yargs2.option("input", {
1330
1547
  alias: "i",
@@ -1343,7 +1560,7 @@ var main = async () => {
1343
1560
  );
1344
1561
  },
1345
1562
  async (argv) => {
1346
- await validateCommand({
1563
+ const exitCode = await validateCommand({
1347
1564
  input: argv.input,
1348
1565
  toleranceConfig: argv.toleranceConfig,
1349
1566
  verbose: argv.verbose,
@@ -1351,16 +1568,21 @@ var main = async () => {
1351
1568
  json: argv.json,
1352
1569
  logFormat: argv.logFormat
1353
1570
  });
1571
+ if (exitCode !== ExitCode.SUCCESS) {
1572
+ process.exit(exitCode);
1573
+ }
1354
1574
  }
1355
1575
  ).command(
1356
1576
  "diff",
1357
1577
  "Compare two workout files and show differences",
1358
1578
  (yargs2) => {
1359
1579
  return yargs2.option("file1", {
1580
+ alias: "1",
1360
1581
  type: "string",
1361
1582
  description: "First file to compare",
1362
1583
  demandOption: true
1363
1584
  }).option("file2", {
1585
+ alias: "2",
1364
1586
  type: "string",
1365
1587
  description: "Second file to compare",
1366
1588
  demandOption: true
@@ -1394,7 +1616,10 @@ var main = async () => {
1394
1616
  json: argv.json,
1395
1617
  logFormat: argv.logFormat
1396
1618
  });
1397
- if (exitCode !== 0 && exitCode !== 1) {
1619
+ if (exitCode !== ExitCode.SUCCESS && exitCode !== ExitCode.DIFFERENCES_FOUND) {
1620
+ process.exit(exitCode);
1621
+ }
1622
+ if (exitCode === ExitCode.DIFFERENCES_FOUND) {
1398
1623
  process.exit(exitCode);
1399
1624
  }
1400
1625
  }
@@ -1422,25 +1647,27 @@ var main = async () => {
1422
1647
  if (error && typeof error === "object" && "name" in error) {
1423
1648
  const errorName = error.name;
1424
1649
  if (errorName === "FitParsingError") {
1425
- process.exit(4);
1650
+ process.exit(ExitCode.PARSING_ERROR);
1426
1651
  } else if (errorName === "KrdValidationError") {
1427
- process.exit(5);
1652
+ process.exit(ExitCode.VALIDATION_ERROR);
1428
1653
  } else if (errorName === "ToleranceExceededError") {
1429
- process.exit(6);
1654
+ process.exit(ExitCode.TOLERANCE_EXCEEDED);
1655
+ } else if (errorName === "InvalidArgumentError") {
1656
+ process.exit(ExitCode.INVALID_ARGUMENT);
1430
1657
  }
1431
1658
  }
1432
- process.exit(99);
1659
+ process.exit(ExitCode.UNKNOWN_ERROR);
1433
1660
  }
1434
1661
  };
1435
1662
  process.on("unhandledRejection", (reason) => {
1436
1663
  console.error("Unhandled rejection:", reason);
1437
- process.exit(99);
1664
+ process.exit(ExitCode.UNKNOWN_ERROR);
1438
1665
  });
1439
1666
  process.on("uncaughtException", (error) => {
1440
1667
  console.error("Uncaught exception:", error);
1441
- process.exit(99);
1668
+ process.exit(ExitCode.UNKNOWN_ERROR);
1442
1669
  });
1443
1670
  main().catch((error) => {
1444
1671
  console.error("Fatal error:", error);
1445
- process.exit(99);
1672
+ process.exit(ExitCode.UNKNOWN_ERROR);
1446
1673
  });