@kaiord/cli 0.1.1 → 1.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.
package/README.md CHANGED
@@ -87,6 +87,62 @@ Example `tolerance.json`:
87
87
  }
88
88
  ```
89
89
 
90
+ ## Configuration File
91
+
92
+ You can create a `.kaiordrc.json` file to set default options. The CLI will search for this file in:
93
+
94
+ 1. Current working directory
95
+ 2. User home directory (`~/.kaiordrc.json`)
96
+
97
+ CLI options always take precedence over config file defaults.
98
+
99
+ ### Example Configuration
100
+
101
+ Create `.kaiordrc.json` in your project or home directory:
102
+
103
+ ```json
104
+ {
105
+ "defaultInputFormat": "fit",
106
+ "defaultOutputFormat": "krd",
107
+ "defaultOutputDir": "./converted",
108
+ "defaultToleranceConfig": "./tolerance.json",
109
+ "verbose": false,
110
+ "quiet": false,
111
+ "json": false,
112
+ "logFormat": "pretty"
113
+ }
114
+ ```
115
+
116
+ ### Configuration Options
117
+
118
+ - **defaultInputFormat**: Default input format (`fit`, `krd`, `tcx`, `zwo`)
119
+ - **defaultOutputFormat**: Default output format (`fit`, `krd`, `tcx`, `zwo`)
120
+ - **defaultOutputDir**: Default output directory for batch conversions
121
+ - **defaultToleranceConfig**: Path to default tolerance configuration file
122
+ - **verbose**: Enable verbose logging by default
123
+ - **quiet**: Enable quiet mode by default
124
+ - **json**: Enable JSON output by default
125
+ - **logFormat**: Default log format (`pretty` or `structured`)
126
+
127
+ ### Usage with Config File
128
+
129
+ With a config file, you can simplify your commands:
130
+
131
+ ```bash
132
+ # Without config file
133
+ kaiord convert --input workout.fit --output workout.krd --output-format krd
134
+
135
+ # With config file (defaultOutputFormat: "krd")
136
+ kaiord convert --input workout.fit --output workout.krd
137
+ ```
138
+
139
+ CLI options override config defaults:
140
+
141
+ ```bash
142
+ # Config has verbose: true, but --quiet overrides it
143
+ kaiord convert --input workout.fit --output workout.krd --quiet
144
+ ```
145
+
90
146
  ## Global Options
91
147
 
92
148
  ### Verbosity Control
@@ -112,14 +168,6 @@ kaiord convert --input workout.fit --output workout.krd --log-format pretty
112
168
  kaiord convert --input workout.fit --output workout.krd --log-format json
113
169
  ```
114
170
 
115
- ## Environment Variables
116
-
117
- The CLI automatically detects the environment and adjusts its behavior:
118
-
119
- - **CI=true**: Enables structured JSON logging
120
- - **NODE_ENV=production**: Enables structured JSON logging
121
- - **TTY detection**: Automatically disables colors and spinners in non-interactive environments
122
-
123
171
  ## Supported Formats
124
172
 
125
173
  - **FIT** (.fit) - Garmin's binary workout file format
@@ -127,47 +175,63 @@ The CLI automatically detects the environment and adjusts its behavior:
127
175
  - **TCX** (.tcx) - Training Center XML format
128
176
  - **ZWO** (.zwo) - Zwift workout XML format
129
177
 
130
- ## Exit Codes
178
+ ### Plugin System (Future Enhancement)
131
179
 
132
- - **0**: Success
133
- - **1**: Error (invalid arguments, file not found, parsing error, validation error)
180
+ The CLI is designed with a plugin architecture that will allow third-party developers to add support for custom workout file formats without modifying the core library. See [Plugin Architecture Documentation](./docs/plugin-architecture.md) for details on the design.
134
181
 
135
- ## Troubleshooting
182
+ **Planned Features:**
136
183
 
137
- ### File Not Found
184
+ - Dynamic plugin discovery and loading
185
+ - Type-safe plugin interface
186
+ - Automatic format detection for plugin formats
187
+ - Plugin configuration via `.kaiordrc.json`
188
+ - Plugin management commands (`kaiord plugins list`, `install`, etc.)
138
189
 
139
- Ensure the input file path is correct and the file exists:
190
+ **Example Plugin Usage (Future):**
140
191
 
141
192
  ```bash
142
- ls -la workout.fit
143
- kaiord convert --input workout.fit --output workout.krd
193
+ # Install a plugin
194
+ npm install -g @kaiord/plugin-gpx
195
+
196
+ # Use plugin format
197
+ kaiord convert --input workout.gpx --output workout.krd
144
198
  ```
145
199
 
146
- ### Permission Errors
200
+ See [Example GPX Plugin](./docs/example-plugin-gpx.md) for a complete plugin implementation example.
147
201
 
148
- Check file permissions:
202
+ ## Exit Codes
149
203
 
150
- ```bash
151
- chmod 644 workout.fit
152
- kaiord convert --input workout.fit --output workout.krd
153
- ```
204
+ - **0**: Success
205
+ - **1**: Error (invalid arguments, file not found, parsing error, validation error)
154
206
 
155
- ### Format Detection Issues
207
+ ## Documentation
156
208
 
157
- If automatic format detection fails, use explicit format flags:
209
+ ### Main Documentation
158
210
 
159
- ```bash
160
- kaiord convert --input data.bin --input-format fit --output workout.krd --output-format krd
161
- ```
211
+ - **[Getting Started](../../docs/getting-started.md)** - Installation and basic usage
212
+ - **[KRD Format Specification](../../docs/krd-format.md)** - Complete format documentation
213
+ - **[Deployment Guide](../../docs/deployment.md)** - CI/CD and npm publishing
214
+
215
+ ### Package-Specific Documentation
162
216
 
163
- ### Corrupted Files
217
+ - **[npm Publish Verification](./docs/npm-publish-verification.md)** - Publishing checklist and verification
164
218
 
165
- If a file is corrupted, the CLI will display a descriptive error message:
219
+ ## Development
166
220
 
167
221
  ```bash
168
- kaiord convert --input corrupted.fit --output workout.krd
169
- # Error: Failed to parse FIT file
170
- # Details: Corrupted file header
222
+ # Install dependencies
223
+ pnpm install
224
+
225
+ # Build the CLI
226
+ pnpm build
227
+
228
+ # Run in development mode
229
+ pnpm dev -- convert --input workout.fit --output workout.krd
230
+
231
+ # Link for local testing
232
+ npm link
233
+ kaiord --version
234
+ npm unlink -g
171
235
  ```
172
236
 
173
237
  ## Testing
@@ -191,37 +255,6 @@ pnpm test:smoke
191
255
  pnpm test:watch
192
256
  ```
193
257
 
194
- ## Development
195
-
196
- ```bash
197
- # Install dependencies
198
- pnpm install
199
-
200
- # Build the CLI
201
- pnpm build
202
-
203
- # Run in development mode
204
- pnpm dev -- convert --input workout.fit --output workout.krd
205
-
206
- # Link for local testing
207
- npm link
208
- kaiord --version
209
- npm unlink -g
210
- ```
211
-
212
- ## Documentation
213
-
214
- For more information about the Kaiord project and the KRD format, see:
215
-
216
- - [Kaiord Core Library](https://github.com/your-org/kaiord/tree/main/packages/core)
217
- - [KRD Format Specification](https://github.com/your-org/kaiord/blob/main/docs/KRD_FORMAT.md)
218
- - [Contributing Guide](https://github.com/your-org/kaiord/blob/main/CONTRIBUTING.md)
219
-
220
258
  ## License
221
259
 
222
260
  MIT - See [LICENSE](../../LICENSE) file for details.
223
-
224
- ## Support
225
-
226
- - Report issues: [GitHub Issues](https://github.com/your-org/kaiord/issues)
227
- - Ask questions: [GitHub Discussions](https://github.com/your-org/kaiord/discussions)
@@ -1,29 +1,232 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- isTTY
4
- } from "../chunk-TI3WVGXE.js";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // ../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_tsx@4.20.6_typescript@5.9.3/node_modules/tsup/assets/esm_shims.js
13
+ import path from "path";
14
+ import { fileURLToPath } from "url";
15
+ var init_esm_shims = __esm({
16
+ "../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_tsx@4.20.6_typescript@5.9.3/node_modules/tsup/assets/esm_shims.js"() {
17
+ "use strict";
18
+ }
19
+ });
20
+
21
+ // src/utils/is-tty.ts
22
+ var isTTY;
23
+ var init_is_tty = __esm({
24
+ "src/utils/is-tty.ts"() {
25
+ "use strict";
26
+ init_esm_shims();
27
+ isTTY = () => {
28
+ return process.stdout.isTTY === true;
29
+ };
30
+ }
31
+ });
32
+
33
+ // src/adapters/logger/structured-logger.ts
34
+ var structured_logger_exports = {};
35
+ __export(structured_logger_exports, {
36
+ createStructuredLogger: () => createStructuredLogger
37
+ });
38
+ import winston from "winston";
39
+ var createStructuredLogger;
40
+ var init_structured_logger = __esm({
41
+ "src/adapters/logger/structured-logger.ts"() {
42
+ "use strict";
43
+ init_esm_shims();
44
+ createStructuredLogger = (options = {}) => {
45
+ const level = options.level || "info";
46
+ const quiet = options.quiet || false;
47
+ const winstonLogger = winston.createLogger({
48
+ level: quiet ? "error" : level,
49
+ format: winston.format.combine(
50
+ winston.format.timestamp(),
51
+ winston.format.json()
52
+ ),
53
+ transports: [
54
+ new winston.transports.Console({
55
+ stderrLevels: ["error", "warn", "info", "debug"]
56
+ })
57
+ ]
58
+ });
59
+ return {
60
+ debug: (message, context) => {
61
+ winstonLogger.debug(message, context);
62
+ },
63
+ info: (message, context) => {
64
+ winstonLogger.info(message, context);
65
+ },
66
+ warn: (message, context) => {
67
+ winstonLogger.warn(message, context);
68
+ },
69
+ error: (message, context) => {
70
+ winstonLogger.error(message, context);
71
+ }
72
+ };
73
+ };
74
+ }
75
+ });
76
+
77
+ // src/adapters/logger/pretty-logger.ts
78
+ var pretty_logger_exports = {};
79
+ __export(pretty_logger_exports, {
80
+ createPrettyLogger: () => createPrettyLogger
81
+ });
82
+ import chalk2 from "chalk";
83
+ var createPrettyLogger;
84
+ var init_pretty_logger = __esm({
85
+ "src/adapters/logger/pretty-logger.ts"() {
86
+ "use strict";
87
+ init_esm_shims();
88
+ init_is_tty();
89
+ createPrettyLogger = (options = {}) => {
90
+ const level = options.level || "info";
91
+ const quiet = options.quiet || false;
92
+ const forceColor = process.env.FORCE_COLOR === "1";
93
+ const useColors = isTTY() || forceColor;
94
+ const levels = ["debug", "info", "warn", "error"];
95
+ const minLevelIndex = levels.indexOf(level);
96
+ const shouldLog = (messageLevel) => {
97
+ if (quiet && messageLevel !== "error") {
98
+ return false;
99
+ }
100
+ const messageLevelIndex = levels.indexOf(messageLevel);
101
+ return messageLevelIndex >= minLevelIndex;
102
+ };
103
+ const formatContext = (context) => {
104
+ if (!context || Object.keys(context).length === 0) {
105
+ return "";
106
+ }
107
+ const contextStr = JSON.stringify(context);
108
+ return " " + (useColors ? chalk2.gray(contextStr) : contextStr);
109
+ };
110
+ return {
111
+ debug: (message, context) => {
112
+ if (shouldLog("debug")) {
113
+ const formatted = `\u{1F41B} ${message}${formatContext(context)}`;
114
+ console.log(useColors ? chalk2.gray(formatted) : formatted);
115
+ }
116
+ },
117
+ info: (message, context) => {
118
+ if (shouldLog("info")) {
119
+ const formatted = `\u2139 ${message}${formatContext(context)}`;
120
+ console.log(useColors ? chalk2.blue(formatted) : formatted);
121
+ }
122
+ },
123
+ warn: (message, context) => {
124
+ if (shouldLog("warn")) {
125
+ const formatted = `\u26A0 ${message}${formatContext(context)}`;
126
+ console.warn(useColors ? chalk2.yellow(formatted) : formatted);
127
+ }
128
+ },
129
+ error: (message, context) => {
130
+ if (shouldLog("error")) {
131
+ const formatted = `\u2716 ${message}${formatContext(context)}`;
132
+ console.error(useColors ? chalk2.red(formatted) : formatted);
133
+ }
134
+ }
135
+ };
136
+ };
137
+ }
138
+ });
5
139
 
6
140
  // src/bin/kaiord.ts
7
- import chalk3 from "chalk";
141
+ init_esm_shims();
142
+ import chalk5 from "chalk";
8
143
  import { readFileSync } from "fs";
9
- import { dirname as dirname2, join as join2 } from "path";
10
- import { fileURLToPath } from "url";
144
+ import { dirname as dirname2, join as join3 } from "path";
145
+ import { fileURLToPath as fileURLToPath2 } from "url";
11
146
  import yargs from "yargs";
12
147
  import { hideBin } from "yargs/helpers";
13
148
 
14
149
  // src/commands/convert.ts
150
+ init_esm_shims();
15
151
  import {
16
152
  createDefaultProviders,
17
153
  FitParsingError as FitParsingError2,
18
154
  KrdValidationError as KrdValidationError2,
19
155
  ToleranceExceededError as ToleranceExceededError2
20
156
  } from "@kaiord/core";
21
- import chalk2 from "chalk";
157
+ import chalk3 from "chalk";
22
158
  import ora from "ora";
23
- import { basename, join } from "path";
159
+ import { basename, join as join2 } from "path";
160
+ import { z as z3 } from "zod";
161
+
162
+ // src/utils/config-loader.ts
163
+ init_esm_shims();
164
+ import { readFile } from "fs/promises";
165
+ import { homedir } from "os";
166
+ import { join } from "path";
24
167
  import { z as z2 } from "zod";
25
168
 
169
+ // src/utils/format-detector.ts
170
+ init_esm_shims();
171
+ import { extname } from "path";
172
+ import { z } from "zod";
173
+ var fileFormatSchema = z.enum(["fit", "krd", "tcx", "zwo"]);
174
+ var EXTENSION_TO_FORMAT = {
175
+ ".fit": "fit",
176
+ ".krd": "krd",
177
+ ".tcx": "tcx",
178
+ ".zwo": "zwo"
179
+ };
180
+ var detectFormat = (filePath) => {
181
+ const ext = extname(filePath).toLowerCase();
182
+ return EXTENSION_TO_FORMAT[ext] || null;
183
+ };
184
+
185
+ // src/utils/config-loader.ts
186
+ var configSchema = z2.object({
187
+ // Default formats
188
+ defaultInputFormat: fileFormatSchema.optional(),
189
+ defaultOutputFormat: fileFormatSchema.optional(),
190
+ // Default directories
191
+ defaultOutputDir: z2.string().optional(),
192
+ // Default tolerance config path
193
+ defaultToleranceConfig: z2.string().optional(),
194
+ // Default logging options
195
+ verbose: z2.boolean().optional(),
196
+ quiet: z2.boolean().optional(),
197
+ json: z2.boolean().optional(),
198
+ logFormat: z2.enum(["pretty", "structured"]).optional()
199
+ });
200
+ var loadConfig = async () => {
201
+ const configPaths = [
202
+ join(process.cwd(), ".kaiordrc.json"),
203
+ join(homedir(), ".kaiordrc.json")
204
+ ];
205
+ for (const configPath of configPaths) {
206
+ try {
207
+ const configContent = await readFile(configPath, "utf-8");
208
+ const configJson = JSON.parse(configContent);
209
+ const config = configSchema.parse(configJson);
210
+ return config;
211
+ } catch (error) {
212
+ continue;
213
+ }
214
+ }
215
+ return {};
216
+ };
217
+ var mergeWithConfig = (cliOptions, config) => {
218
+ const merged = { ...config };
219
+ for (const key in cliOptions) {
220
+ if (cliOptions[key] !== void 0) {
221
+ merged[key] = cliOptions[key];
222
+ }
223
+ }
224
+ return merged;
225
+ };
226
+
26
227
  // src/utils/error-formatter.ts
228
+ init_esm_shims();
229
+ init_is_tty();
27
230
  import {
28
231
  FitParsingError,
29
232
  KrdValidationError,
@@ -218,6 +421,7 @@ var formatErrorAsJson = (error) => {
218
421
  };
219
422
 
220
423
  // src/utils/file-handler.ts
424
+ init_esm_shims();
221
425
  import {
222
426
  readFile as fsReadFile,
223
427
  writeFile as fsWriteFile,
@@ -225,51 +429,51 @@ import {
225
429
  } from "fs/promises";
226
430
  import { glob } from "glob";
227
431
  import { dirname } from "path";
228
- var readFile = async (path, format) => {
432
+ var readFile2 = async (path2, format) => {
229
433
  try {
230
434
  if (format === "fit") {
231
- const buffer = await fsReadFile(path);
435
+ const buffer = await fsReadFile(path2);
232
436
  return new Uint8Array(buffer);
233
437
  } else {
234
- return await fsReadFile(path, "utf-8");
438
+ return await fsReadFile(path2, "utf-8");
235
439
  }
236
440
  } catch (error) {
237
441
  if (error instanceof Error && "code" in error) {
238
442
  if (error.code === "ENOENT") {
239
- throw new Error(`File not found: ${path}`);
443
+ throw new Error(`File not found: ${path2}`);
240
444
  }
241
445
  if (error.code === "EACCES") {
242
- throw new Error(`Permission denied: ${path}`);
446
+ throw new Error(`Permission denied: ${path2}`);
243
447
  }
244
448
  }
245
- throw new Error(`Failed to read file: ${path}`);
449
+ throw new Error(`Failed to read file: ${path2}`);
246
450
  }
247
451
  };
248
- var writeFile = async (path, data, format) => {
452
+ var writeFile = async (path2, data, format) => {
249
453
  try {
250
- const dir = dirname(path);
454
+ const dir = dirname(path2);
251
455
  await mkdir(dir, { recursive: true });
252
456
  if (format === "fit") {
253
457
  if (!(data instanceof Uint8Array)) {
254
458
  throw new Error("FIT files require Uint8Array data");
255
459
  }
256
- await fsWriteFile(path, data);
460
+ await fsWriteFile(path2, data);
257
461
  } else {
258
462
  if (typeof data !== "string") {
259
463
  throw new Error("Text files require string data");
260
464
  }
261
- await fsWriteFile(path, data, "utf-8");
465
+ await fsWriteFile(path2, data, "utf-8");
262
466
  }
263
467
  } catch (error) {
264
468
  if (error instanceof Error && "code" in error) {
265
469
  if (error.code === "EACCES") {
266
- throw new Error(`Permission denied: ${path}`);
470
+ throw new Error(`Permission denied: ${path2}`);
267
471
  }
268
472
  }
269
473
  if (error instanceof Error && error.message.includes("require")) {
270
474
  throw error;
271
475
  }
272
- throw new Error(`Failed to write file: ${path}`);
476
+ throw new Error(`Failed to write file: ${path2}`);
273
477
  }
274
478
  };
275
479
  var findFiles = async (pattern) => {
@@ -280,53 +484,39 @@ var findFiles = async (pattern) => {
280
484
  return files.sort();
281
485
  };
282
486
 
283
- // src/utils/format-detector.ts
284
- import { extname } from "path";
285
- import { z } from "zod";
286
- var fileFormatSchema = z.enum(["fit", "krd", "tcx", "zwo"]);
287
- var EXTENSION_TO_FORMAT = {
288
- ".fit": "fit",
289
- ".krd": "krd",
290
- ".tcx": "tcx",
291
- ".zwo": "zwo"
292
- };
293
- var detectFormat = (filePath) => {
294
- const ext = extname(filePath).toLowerCase();
295
- return EXTENSION_TO_FORMAT[ext] || null;
296
- };
297
-
298
487
  // src/utils/logger-factory.ts
488
+ init_esm_shims();
299
489
  var isCI = () => {
300
490
  return process.env.CI === "true" || process.env.NODE_ENV === "production" || !process.stdout.isTTY;
301
491
  };
302
492
  var createLogger = async (options = {}) => {
303
493
  const loggerType = options.type || (isCI() ? "structured" : "pretty");
304
494
  if (loggerType === "structured") {
305
- const { createStructuredLogger } = await import("../structured-logger-HMEQGEES.js");
306
- return createStructuredLogger(options);
495
+ const { createStructuredLogger: createStructuredLogger2 } = await Promise.resolve().then(() => (init_structured_logger(), structured_logger_exports));
496
+ return createStructuredLogger2(options);
307
497
  } else {
308
- const { createPrettyLogger } = await import("../pretty-logger-P2OWMOGW.js");
309
- return createPrettyLogger(options);
498
+ const { createPrettyLogger: createPrettyLogger2 } = await Promise.resolve().then(() => (init_pretty_logger(), pretty_logger_exports));
499
+ return createPrettyLogger2(options);
310
500
  }
311
501
  };
312
502
 
313
503
  // src/commands/convert.ts
314
- var convertOptionsSchema = z2.object({
315
- input: z2.string(),
316
- output: z2.string().optional(),
317
- outputDir: z2.string().optional(),
504
+ var convertOptionsSchema = z3.object({
505
+ input: z3.string(),
506
+ output: z3.string().optional(),
507
+ outputDir: z3.string().optional(),
318
508
  inputFormat: fileFormatSchema.optional(),
319
509
  outputFormat: fileFormatSchema.optional(),
320
- verbose: z2.boolean().optional(),
321
- quiet: z2.boolean().optional(),
322
- json: z2.boolean().optional(),
323
- logFormat: z2.enum(["pretty", "structured"]).optional()
510
+ verbose: z3.boolean().optional(),
511
+ quiet: z3.boolean().optional(),
512
+ json: z3.boolean().optional(),
513
+ logFormat: z3.enum(["pretty", "structured"]).optional()
324
514
  });
325
515
  var isBatchMode = (input) => {
326
516
  return input.includes("*") || input.includes("?");
327
517
  };
328
518
  var convertSingleFile = async (inputFile, outputFile, inputFormat, outputFormat, providers) => {
329
- const inputData = await readFile(inputFile, inputFormat);
519
+ const inputData = await readFile2(inputFile, inputFormat);
330
520
  let krd;
331
521
  if (inputFormat === "fit") {
332
522
  if (!(inputData instanceof Uint8Array)) {
@@ -366,7 +556,19 @@ var convertSingleFile = async (inputFile, outputFile, inputFormat, outputFormat,
366
556
  await writeFile(outputFile, outputData, outputFormat);
367
557
  };
368
558
  var convertCommand = async (options) => {
369
- const validatedOptions = convertOptionsSchema.parse(options);
559
+ const config = await loadConfig();
560
+ const mergedOptions = mergeWithConfig(options, config);
561
+ const optionsWithDefaults = {
562
+ ...mergedOptions,
563
+ inputFormat: mergedOptions.inputFormat || config.defaultInputFormat,
564
+ outputFormat: mergedOptions.outputFormat || config.defaultOutputFormat,
565
+ outputDir: mergedOptions.outputDir || config.defaultOutputDir,
566
+ verbose: mergedOptions.verbose ?? config.verbose,
567
+ quiet: mergedOptions.quiet ?? config.quiet,
568
+ json: mergedOptions.json ?? config.json,
569
+ logFormat: mergedOptions.logFormat || config.logFormat
570
+ };
571
+ const validatedOptions = convertOptionsSchema.parse(optionsWithDefaults);
370
572
  const logger = await createLogger({
371
573
  type: validatedOptions.logFormat,
372
574
  level: validatedOptions.verbose ? "debug" : validatedOptions.quiet ? "error" : "info",
@@ -424,7 +626,7 @@ var convertCommand = async (options) => {
424
626
  /\.(fit|krd|tcx|zwo)$/i,
425
627
  `.${outputFormat}`
426
628
  );
427
- const outputFile = join(validatedOptions.outputDir, outputFileName);
629
+ const outputFile = join2(validatedOptions.outputDir, outputFileName);
428
630
  await convertSingleFile(
429
631
  file,
430
632
  outputFile,
@@ -455,18 +657,18 @@ var convertCommand = async (options) => {
455
657
  if (!validatedOptions.json) {
456
658
  console.log("\nBatch conversion complete:");
457
659
  console.log(
458
- chalk2.green(` \u2713 Successful: ${successful.length}/${files.length}`)
660
+ chalk3.green(` \u2713 Successful: ${successful.length}/${files.length}`)
459
661
  );
460
662
  if (failed.length > 0) {
461
663
  console.log(
462
- chalk2.red(` \u2717 Failed: ${failed.length}/${files.length}`)
664
+ chalk3.red(` \u2717 Failed: ${failed.length}/${files.length}`)
463
665
  );
464
666
  }
465
667
  console.log(` Total time: ${(totalTime / 1e3).toFixed(2)}s`);
466
668
  if (failed.length > 0) {
467
- console.log(chalk2.red("\nFailed conversions:"));
669
+ console.log(chalk3.red("\nFailed conversions:"));
468
670
  for (const result of failed) {
469
- console.log(chalk2.red(` ${result.inputFile}: ${result.error}`));
671
+ console.log(chalk3.red(` ${result.inputFile}: ${result.error}`));
470
672
  }
471
673
  }
472
674
  } else {
@@ -568,53 +770,354 @@ var convertCommand = async (options) => {
568
770
  } else {
569
771
  console.error(formattedError);
570
772
  }
571
- let exitCode = 1;
773
+ let exitCode = 99;
572
774
  if (error instanceof Error) {
573
775
  if (error.message.includes("File not found")) {
574
- exitCode = 1;
776
+ exitCode = 2;
575
777
  } else if (error.message.includes("Permission denied")) {
576
- exitCode = 1;
778
+ exitCode = 3;
577
779
  } else if (error instanceof FitParsingError2) {
578
- exitCode = 1;
780
+ exitCode = 4;
579
781
  } else if (error instanceof KrdValidationError2) {
580
- exitCode = 1;
782
+ exitCode = 5;
581
783
  } else if (error instanceof ToleranceExceededError2) {
582
- exitCode = 1;
784
+ exitCode = 6;
583
785
  } else if (error.name === "InvalidArgumentError") {
584
786
  exitCode = 1;
585
787
  }
586
788
  }
587
- process.exit(exitCode);
789
+ return exitCode;
790
+ }
791
+ return 0;
792
+ };
793
+
794
+ // src/commands/diff.ts
795
+ 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
+ };
875
+ var compareSteps = (krd1, krd2) => {
876
+ const workout1 = krd1.extensions?.workout;
877
+ const workout2 = krd2.extensions?.workout;
878
+ const steps1 = workout1?.steps || [];
879
+ const steps2 = workout2?.steps || [];
880
+ const differences = [];
881
+ const maxSteps = Math.max(steps1.length, steps2.length);
882
+ for (let i = 0; i < maxSteps; i++) {
883
+ const step1 = steps1[i];
884
+ const step2 = steps2[i];
885
+ if (!step1 && step2) {
886
+ differences.push({
887
+ stepIndex: i,
888
+ field: "step",
889
+ file1Value: void 0,
890
+ file2Value: step2
891
+ });
892
+ continue;
893
+ }
894
+ if (step1 && !step2) {
895
+ differences.push({
896
+ stepIndex: i,
897
+ field: "step",
898
+ file1Value: step1,
899
+ file2Value: void 0
900
+ });
901
+ continue;
902
+ }
903
+ 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
+ }
926
+ }
927
+ }
928
+ return {
929
+ file1Count: steps1.length,
930
+ file2Count: steps2.length,
931
+ differences
932
+ };
933
+ };
934
+ var compareExtensions = (krd1, krd2) => {
935
+ const ext1 = krd1.extensions || {};
936
+ const ext2 = krd2.extensions || {};
937
+ const keys1 = Object.keys(ext1);
938
+ const keys2 = Object.keys(ext2);
939
+ const allKeys = /* @__PURE__ */ new Set([...keys1, ...keys2]);
940
+ const differences = [];
941
+ 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
+ });
950
+ }
951
+ }
952
+ return {
953
+ file1Keys: keys1,
954
+ file2Keys: keys2,
955
+ differences
956
+ };
957
+ };
958
+ var formatDiffPretty = (result, file1, file2) => {
959
+ if (result.identical) {
960
+ return chalk4.green("\u2713 Files are identical");
961
+ }
962
+ const lines = [];
963
+ lines.push(chalk4.yellow(`
964
+ Comparing: ${file1} vs ${file2}
965
+ `));
966
+ if (result.metadataDiff && result.metadataDiff.length > 0) {
967
+ lines.push(chalk4.bold("Metadata Differences:"));
968
+ for (const diff of result.metadataDiff) {
969
+ lines.push(
970
+ ` ${chalk4.cyan(diff.field)}:`,
971
+ ` ${chalk4.red("-")} ${JSON.stringify(diff.file1Value)}`,
972
+ ` ${chalk4.green("+")} ${JSON.stringify(diff.file2Value)}`
973
+ );
974
+ }
975
+ lines.push("");
976
+ }
977
+ if (result.stepsDiff && result.stepsDiff.differences.length > 0) {
978
+ lines.push(chalk4.bold("Workout Steps Differences:"));
979
+ lines.push(
980
+ ` Step count: ${result.stepsDiff.file1Count} vs ${result.stepsDiff.file2Count}`
981
+ );
982
+ for (const diff of result.stepsDiff.differences) {
983
+ lines.push(
984
+ ` Step ${diff.stepIndex} - ${chalk4.cyan(diff.field)}:`,
985
+ ` ${chalk4.red("-")} ${JSON.stringify(diff.file1Value)}`,
986
+ ` ${chalk4.green("+")} ${JSON.stringify(diff.file2Value)}`
987
+ );
988
+ }
989
+ lines.push("");
990
+ }
991
+ if (result.extensionsDiff && result.extensionsDiff.differences.length > 0) {
992
+ lines.push(chalk4.bold("Extensions Differences:"));
993
+ lines.push(
994
+ ` Keys in file1: ${result.extensionsDiff.file1Keys.join(", ")}`,
995
+ ` Keys in file2: ${result.extensionsDiff.file2Keys.join(", ")}`
996
+ );
997
+ for (const diff of result.extensionsDiff.differences) {
998
+ lines.push(
999
+ ` ${chalk4.cyan(diff.key)}:`,
1000
+ ` ${chalk4.red("-")} ${JSON.stringify(diff.file1Value)}`,
1001
+ ` ${chalk4.green("+")} ${JSON.stringify(diff.file2Value)}`
1002
+ );
1003
+ }
1004
+ lines.push("");
1005
+ }
1006
+ return lines.join("\n");
1007
+ };
1008
+ var diffCommand = async (options) => {
1009
+ const validatedOptions = diffOptionsSchema.parse(options);
1010
+ const logger = await createLogger({
1011
+ type: validatedOptions.logFormat,
1012
+ level: validatedOptions.verbose ? "debug" : validatedOptions.quiet ? "error" : "info",
1013
+ quiet: validatedOptions.quiet
1014
+ });
1015
+ try {
1016
+ const providers = createDefaultProviders2(logger);
1017
+ logger.debug("Loading files for comparison", {
1018
+ file1: validatedOptions.file1,
1019
+ file2: validatedOptions.file2
1020
+ });
1021
+ const krd1 = await loadFileAsKrd(
1022
+ validatedOptions.file1,
1023
+ validatedOptions.format1,
1024
+ providers
1025
+ );
1026
+ const krd2 = await loadFileAsKrd(
1027
+ validatedOptions.file2,
1028
+ validatedOptions.format2,
1029
+ providers
1030
+ );
1031
+ logger.debug("Files loaded successfully");
1032
+ const metadataDiff = compareMetadata(krd1, krd2);
1033
+ const stepsDiff = compareSteps(krd1, krd2);
1034
+ const extensionsDiff = compareExtensions(krd1, krd2);
1035
+ const identical = metadataDiff.length === 0 && stepsDiff.differences.length === 0 && extensionsDiff.differences.length === 0;
1036
+ const result = {
1037
+ identical,
1038
+ metadataDiff: metadataDiff.length > 0 ? metadataDiff : void 0,
1039
+ stepsDiff: stepsDiff.differences.length > 0 ? stepsDiff : void 0,
1040
+ extensionsDiff: extensionsDiff.differences.length > 0 ? extensionsDiff : void 0
1041
+ };
1042
+ if (validatedOptions.json) {
1043
+ console.log(
1044
+ JSON.stringify(
1045
+ {
1046
+ success: true,
1047
+ file1: validatedOptions.file1,
1048
+ file2: validatedOptions.file2,
1049
+ ...result
1050
+ },
1051
+ null,
1052
+ 2
1053
+ )
1054
+ );
1055
+ } else {
1056
+ const output = formatDiffPretty(
1057
+ result,
1058
+ validatedOptions.file1,
1059
+ validatedOptions.file2
1060
+ );
1061
+ console.log(output);
1062
+ }
1063
+ return identical ? 0 : 1;
1064
+ } catch (error) {
1065
+ logger.error("Diff command failed", { error });
1066
+ const formattedError = formatError(error, {
1067
+ json: validatedOptions.json
1068
+ });
1069
+ if (validatedOptions.json) {
1070
+ console.log(formattedError);
1071
+ } else {
1072
+ console.error(formattedError);
1073
+ }
1074
+ return 99;
588
1075
  }
589
1076
  };
590
1077
 
591
1078
  // src/commands/validate.ts
1079
+ init_esm_shims();
592
1080
  import {
593
- createDefaultProviders as createDefaultProviders2,
1081
+ createDefaultProviders as createDefaultProviders3,
594
1082
  createToleranceChecker,
595
1083
  toleranceConfigSchema,
596
1084
  validateRoundTrip
597
1085
  } from "@kaiord/core";
598
1086
  import { readFile as fsReadFile2 } from "fs/promises";
599
1087
  import ora2 from "ora";
600
- import { z as z3 } from "zod";
601
- var validateOptionsSchema = z3.object({
602
- input: z3.string(),
603
- toleranceConfig: z3.string().optional(),
604
- verbose: z3.boolean().optional(),
605
- quiet: z3.boolean().optional(),
606
- json: z3.boolean().optional(),
607
- logFormat: z3.enum(["pretty", "json"]).optional()
1088
+ import { z as z5 } from "zod";
1089
+ var validateOptionsSchema = z5.object({
1090
+ input: z5.string(),
1091
+ toleranceConfig: z5.string().optional(),
1092
+ verbose: z5.boolean().optional(),
1093
+ quiet: z5.boolean().optional(),
1094
+ json: z5.boolean().optional(),
1095
+ logFormat: z5.enum(["pretty", "json"]).optional()
608
1096
  });
609
1097
  var validateCommand = async (options) => {
610
- const opts = validateOptionsSchema.parse(options);
611
- const loggerType = opts.logFormat === "json" ? "structured" : opts.logFormat;
612
- const logger = await createLogger({
613
- type: loggerType,
614
- level: opts.verbose ? "debug" : opts.quiet ? "error" : "info",
615
- quiet: opts.quiet
616
- });
1098
+ let logger;
1099
+ let spinner = null;
617
1100
  try {
1101
+ const config = await loadConfig();
1102
+ const mergedOptions = mergeWithConfig(
1103
+ options,
1104
+ config
1105
+ );
1106
+ const optionsWithDefaults = {
1107
+ ...mergedOptions,
1108
+ toleranceConfig: mergedOptions.toleranceConfig || config.defaultToleranceConfig,
1109
+ verbose: mergedOptions.verbose ?? config.verbose,
1110
+ quiet: mergedOptions.quiet ?? config.quiet,
1111
+ json: mergedOptions.json ?? config.json,
1112
+ logFormat: mergedOptions.logFormat || config.logFormat
1113
+ };
1114
+ const opts = validateOptionsSchema.parse(optionsWithDefaults);
1115
+ const loggerType = opts.logFormat === "json" ? "structured" : opts.logFormat;
1116
+ logger = await createLogger({
1117
+ type: loggerType,
1118
+ level: opts.verbose ? "debug" : opts.quiet ? "error" : "info",
1119
+ quiet: opts.quiet
1120
+ });
618
1121
  const format = detectFormat(opts.input);
619
1122
  if (!format) {
620
1123
  throw new Error(`Unable to detect format from file: ${opts.input}`);
@@ -624,24 +1127,24 @@ var validateCommand = async (options) => {
624
1127
  `Validation currently only supports FIT files. Got: ${format}`
625
1128
  );
626
1129
  }
627
- logger.debug("Reading input file", { path: opts.input, format });
628
- const inputData = await readFile(opts.input, format);
1130
+ logger?.debug("Reading input file", { path: opts.input, format });
1131
+ const inputData = await readFile2(opts.input, format);
629
1132
  if (typeof inputData === "string") {
630
1133
  throw new Error("Expected binary data for FIT file");
631
1134
  }
632
1135
  let toleranceConfig;
633
1136
  if (opts.toleranceConfig) {
634
- logger.debug("Loading custom tolerance config", {
1137
+ logger?.debug("Loading custom tolerance config", {
635
1138
  path: opts.toleranceConfig
636
1139
  });
637
1140
  const configContent = await fsReadFile2(opts.toleranceConfig, "utf-8");
638
1141
  const configJson = JSON.parse(configContent);
639
1142
  toleranceConfig = toleranceConfigSchema.parse(configJson);
640
- logger.debug("Custom tolerance config loaded", {
1143
+ logger?.debug("Custom tolerance config loaded", {
641
1144
  config: toleranceConfig
642
1145
  });
643
1146
  }
644
- const providers = createDefaultProviders2(logger);
1147
+ const providers = createDefaultProviders3(logger);
645
1148
  const toleranceChecker = toleranceConfig ? createToleranceChecker(toleranceConfig) : providers.toleranceChecker;
646
1149
  const roundTripValidator = validateRoundTrip(
647
1150
  providers.fitReader,
@@ -649,8 +1152,8 @@ var validateCommand = async (options) => {
649
1152
  toleranceChecker,
650
1153
  logger
651
1154
  );
652
- const spinner = opts.quiet || opts.json ? null : ora2("Validating round-trip conversion...").start();
653
- logger.info("Starting round-trip validation", { file: opts.input });
1155
+ spinner = opts.quiet || opts.json ? null : ora2("Validating round-trip conversion...").start();
1156
+ logger?.info("Starting round-trip validation", { file: opts.input });
654
1157
  const violations = await roundTripValidator.validateFitToKrdToFit({
655
1158
  originalFit: inputData
656
1159
  });
@@ -664,7 +1167,7 @@ var validateCommand = async (options) => {
664
1167
  }
665
1168
  }
666
1169
  if (violations.length === 0) {
667
- logger.info("Round-trip validation passed");
1170
+ logger?.info("Round-trip validation passed");
668
1171
  if (opts.json) {
669
1172
  console.log(
670
1173
  JSON.stringify(
@@ -683,7 +1186,7 @@ var validateCommand = async (options) => {
683
1186
  }
684
1187
  process.exit(0);
685
1188
  } else {
686
- logger.warn("Round-trip validation failed", {
1189
+ logger?.warn("Round-trip validation failed", {
687
1190
  violationCount: violations.length
688
1191
  });
689
1192
  if (opts.json) {
@@ -706,13 +1209,21 @@ var validateCommand = async (options) => {
706
1209
  process.exit(1);
707
1210
  }
708
1211
  } catch (error) {
709
- logger.error("Validation failed", { error });
710
- if (opts.json) {
1212
+ logger?.error("Validation failed", { error });
1213
+ let jsonOutput = false;
1214
+ try {
1215
+ const opts = validateOptionsSchema.parse(options);
1216
+ jsonOutput = opts.json || false;
1217
+ } catch {
1218
+ }
1219
+ if (jsonOutput) {
1220
+ const errorObj = formatError(error, { json: true });
1221
+ const errorData = typeof errorObj === "string" ? JSON.parse(errorObj) : errorObj;
711
1222
  console.log(
712
1223
  JSON.stringify(
713
1224
  {
714
1225
  success: false,
715
- error: formatError(error, { json: true })
1226
+ error: errorData
716
1227
  },
717
1228
  null,
718
1229
  2
@@ -722,29 +1233,31 @@ var validateCommand = async (options) => {
722
1233
  console.error(formatError(error, { json: false }));
723
1234
  }
724
1235
  process.exit(1);
1236
+ } finally {
1237
+ spinner?.stop();
725
1238
  }
726
1239
  };
727
1240
 
728
1241
  // src/bin/kaiord.ts
729
- var __filename2 = fileURLToPath(import.meta.url);
1242
+ var __filename2 = fileURLToPath2(import.meta.url);
730
1243
  var __dirname2 = dirname2(__filename2);
731
- var packageJsonPath = __dirname2.includes("/dist") ? join2(__dirname2, "../../package.json") : join2(__dirname2, "../../package.json");
1244
+ var packageJsonPath = __dirname2.includes("/dist") ? join3(__dirname2, "../../package.json") : join3(__dirname2, "../../package.json");
732
1245
  var packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
733
1246
  var version = packageJson.version;
734
1247
  var showKiroEasterEgg = () => {
735
1248
  console.log(
736
- chalk3.cyan(`
1249
+ chalk5.cyan(`
737
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
738
1251
  \u2551 \u2551
739
- \u2551 \u{1F47B} Built with Kiro AI during Kiroween Hackathon \u{1F47B} \u2551
1252
+ \u2551 \u{1F47B} Built with Kiro AI during Kiroween Hackathon \u{1F47B} \u2551
740
1253
  \u2551 \u2551
741
- \u2551 Kiro helped design, architect, and implement this \u2551
742
- \u2551 entire CLI tool through spec-driven development. \u2551
1254
+ \u2551 Kiro helped design, architect, and implement this \u2551
1255
+ \u2551 entire CLI tool through spec-driven development. \u2551
743
1256
  \u2551 \u2551
744
- \u2551 Learn more about Kiroween: \u2551
745
- \u2551 \u{1F449} http://kiroween.devpost.com/ \u2551
1257
+ \u2551 Learn more about Kiroween: \u2551
1258
+ \u2551 \u{1F449} http://kiroween.devpost.com/ \u2551
746
1259
  \u2551 \u2551
747
- \u2551 Kiro: Your AI pair programmer for building better \u2551
1260
+ \u2551 Kiro: Your AI pair programmer for building better \u2551
748
1261
  \u2551 software, faster. \u{1F680} \u2551
749
1262
  \u2551 \u2551
750
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
@@ -794,7 +1307,7 @@ var main = async () => {
794
1307
  );
795
1308
  },
796
1309
  async (argv) => {
797
- await convertCommand({
1310
+ const exitCode = await convertCommand({
798
1311
  input: argv.input,
799
1312
  output: argv.output,
800
1313
  outputDir: argv.outputDir,
@@ -805,6 +1318,9 @@ var main = async () => {
805
1318
  json: argv.json,
806
1319
  logFormat: argv.logFormat
807
1320
  });
1321
+ if (exitCode !== 0) {
1322
+ process.exit(exitCode);
1323
+ }
808
1324
  }
809
1325
  ).command(
810
1326
  "validate",
@@ -836,6 +1352,52 @@ var main = async () => {
836
1352
  logFormat: argv.logFormat
837
1353
  });
838
1354
  }
1355
+ ).command(
1356
+ "diff",
1357
+ "Compare two workout files and show differences",
1358
+ (yargs2) => {
1359
+ return yargs2.option("file1", {
1360
+ type: "string",
1361
+ description: "First file to compare",
1362
+ demandOption: true
1363
+ }).option("file2", {
1364
+ type: "string",
1365
+ description: "Second file to compare",
1366
+ demandOption: true
1367
+ }).option("format1", {
1368
+ type: "string",
1369
+ choices: ["fit", "krd", "tcx", "zwo"],
1370
+ description: "Override format detection for first file"
1371
+ }).option("format2", {
1372
+ type: "string",
1373
+ choices: ["fit", "krd", "tcx", "zwo"],
1374
+ description: "Override format detection for second file"
1375
+ }).example(
1376
+ "$0 diff --file1 workout1.fit --file2 workout2.fit",
1377
+ "Compare two FIT files"
1378
+ ).example(
1379
+ "$0 diff --file1 workout.fit --file2 workout.krd",
1380
+ "Compare FIT and KRD files"
1381
+ ).example(
1382
+ "$0 diff --file1 workout1.krd --file2 workout2.krd --json",
1383
+ "Compare with JSON output"
1384
+ );
1385
+ },
1386
+ async (argv) => {
1387
+ const exitCode = await diffCommand({
1388
+ file1: argv.file1,
1389
+ file2: argv.file2,
1390
+ format1: argv.format1,
1391
+ format2: argv.format2,
1392
+ verbose: argv.verbose,
1393
+ quiet: argv.quiet,
1394
+ json: argv.json,
1395
+ logFormat: argv.logFormat
1396
+ });
1397
+ if (exitCode !== 0 && exitCode !== 1) {
1398
+ process.exit(exitCode);
1399
+ }
1400
+ }
839
1401
  ).option("verbose", {
840
1402
  type: "boolean",
841
1403
  description: "Enable verbose logging",
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaiord/cli",
3
- "version": "0.1.1",
3
+ "version": "1.0.0",
4
4
  "description": "Command-line interface for Kaiord workout file conversion",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,12 +13,13 @@
13
13
  ],
14
14
  "scripts": {
15
15
  "build": "tsup",
16
- "test": "vitest run",
16
+ "test": "pnpm build && vitest run",
17
17
  "test:unit": "vitest run --exclude '**/*-{integration,smoke,snapshot}.test.ts'",
18
- "test:integration": "vitest run src/commands/convert-integration.test.ts src/commands/validate-integration.test.ts",
19
- "test:smoke": "vitest run src/tests/cli-smoke.test.ts",
18
+ "test:integration": "pnpm build && vitest run src/commands/convert-integration.test.ts src/commands/validate-integration.test.ts",
19
+ "test:smoke": "pnpm build && vitest run src/tests/cli-smoke.test.ts",
20
20
  "test:watch": "vitest",
21
21
  "dev": "tsx src/bin/kaiord.ts",
22
+ "prebuild": "echo 'Building CLI...'",
22
23
  "prepublishOnly": "pnpm build && pnpm test",
23
24
  "check-licenses": "license-checker --onlyAllow 'MIT;Apache-2.0;BSD;BSD-2-Clause;BSD-3-Clause;ISC' --production"
24
25
  },
@@ -37,18 +38,18 @@
37
38
  "license": "MIT",
38
39
  "repository": {
39
40
  "type": "git",
40
- "url": "https://github.com/your-org/kaiord.git",
41
+ "url": "https://github.com/pablo-albaladejo/kaiord.git",
41
42
  "directory": "packages/cli"
42
43
  },
43
44
  "homepage": "https://www.npmjs.com/package/@kaiord/cli",
44
45
  "bugs": {
45
- "url": "https://github.com/your-org/kaiord/issues"
46
+ "url": "https://github.com/pablo-albaladejo/kaiord/issues"
46
47
  },
47
48
  "publishConfig": {
48
49
  "access": "public"
49
50
  },
50
51
  "dependencies": {
51
- "@kaiord/core": "workspace:*",
52
+ "@kaiord/core": "^1.0.0",
52
53
  "chalk": "^5.3.0",
53
54
  "glob": "^10.3.10",
54
55
  "ora": "^8.0.1",
@@ -62,7 +63,7 @@
62
63
  "license-checker": "^25.0.1",
63
64
  "strip-ansi": "^7.1.0",
64
65
  "tmp-promise": "^3.0.3",
65
- "tsup": "^8.0.1",
66
+ "tsup": "^8.5.1",
66
67
  "tsx": "^4.7.0",
67
68
  "typescript": "^5.3.3",
68
69
  "vitest": "^1.2.0"
@@ -1,10 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/utils/is-tty.ts
4
- var isTTY = () => {
5
- return process.stdout.isTTY === true;
6
- };
7
-
8
- export {
9
- isTTY
10
- };
@@ -1,58 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- isTTY
4
- } from "./chunk-TI3WVGXE.js";
5
-
6
- // src/adapters/logger/pretty-logger.ts
7
- import chalk from "chalk";
8
- var createPrettyLogger = (options = {}) => {
9
- const level = options.level || "info";
10
- const quiet = options.quiet || false;
11
- const forceColor = process.env.FORCE_COLOR === "1";
12
- const useColors = isTTY() || forceColor;
13
- const levels = ["debug", "info", "warn", "error"];
14
- const minLevelIndex = levels.indexOf(level);
15
- const shouldLog = (messageLevel) => {
16
- if (quiet && messageLevel !== "error") {
17
- return false;
18
- }
19
- const messageLevelIndex = levels.indexOf(messageLevel);
20
- return messageLevelIndex >= minLevelIndex;
21
- };
22
- const formatContext = (context) => {
23
- if (!context || Object.keys(context).length === 0) {
24
- return "";
25
- }
26
- const contextStr = JSON.stringify(context);
27
- return " " + (useColors ? chalk.gray(contextStr) : contextStr);
28
- };
29
- return {
30
- debug: (message, context) => {
31
- if (shouldLog("debug")) {
32
- const formatted = `\u{1F41B} ${message}${formatContext(context)}`;
33
- console.log(useColors ? chalk.gray(formatted) : formatted);
34
- }
35
- },
36
- info: (message, context) => {
37
- if (shouldLog("info")) {
38
- const formatted = `\u2139 ${message}${formatContext(context)}`;
39
- console.log(useColors ? chalk.blue(formatted) : formatted);
40
- }
41
- },
42
- warn: (message, context) => {
43
- if (shouldLog("warn")) {
44
- const formatted = `\u26A0 ${message}${formatContext(context)}`;
45
- console.warn(useColors ? chalk.yellow(formatted) : formatted);
46
- }
47
- },
48
- error: (message, context) => {
49
- if (shouldLog("error")) {
50
- const formatted = `\u2716 ${message}${formatContext(context)}`;
51
- console.error(useColors ? chalk.red(formatted) : formatted);
52
- }
53
- }
54
- };
55
- };
56
- export {
57
- createPrettyLogger
58
- };
@@ -1,37 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/adapters/logger/structured-logger.ts
4
- import winston from "winston";
5
- var createStructuredLogger = (options = {}) => {
6
- const level = options.level || "info";
7
- const quiet = options.quiet || false;
8
- const winstonLogger = winston.createLogger({
9
- level: quiet ? "error" : level,
10
- format: winston.format.combine(
11
- winston.format.timestamp(),
12
- winston.format.json()
13
- ),
14
- transports: [
15
- new winston.transports.Console({
16
- stderrLevels: ["error", "warn", "info", "debug"]
17
- })
18
- ]
19
- });
20
- return {
21
- debug: (message, context) => {
22
- winstonLogger.debug(message, context);
23
- },
24
- info: (message, context) => {
25
- winstonLogger.info(message, context);
26
- },
27
- warn: (message, context) => {
28
- winstonLogger.warn(message, context);
29
- },
30
- error: (message, context) => {
31
- winstonLogger.error(message, context);
32
- }
33
- };
34
- };
35
- export {
36
- createStructuredLogger
37
- };