@vertaaux/cli 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +58 -2
  3. package/dist/auth/device-flow.js +6 -8
  4. package/dist/commands/audit.d.ts +2 -0
  5. package/dist/commands/audit.d.ts.map +1 -1
  6. package/dist/commands/audit.js +165 -6
  7. package/dist/commands/compare.d.ts +20 -0
  8. package/dist/commands/compare.d.ts.map +1 -0
  9. package/dist/commands/compare.js +335 -0
  10. package/dist/commands/doc.d.ts +18 -0
  11. package/dist/commands/doc.d.ts.map +1 -0
  12. package/dist/commands/doc.js +161 -0
  13. package/dist/commands/download.d.ts.map +1 -1
  14. package/dist/commands/download.js +9 -8
  15. package/dist/commands/explain.d.ts +14 -33
  16. package/dist/commands/explain.d.ts.map +1 -1
  17. package/dist/commands/explain.js +277 -179
  18. package/dist/commands/fix-plan.d.ts +15 -0
  19. package/dist/commands/fix-plan.d.ts.map +1 -0
  20. package/dist/commands/fix-plan.js +182 -0
  21. package/dist/commands/patch-review.d.ts +14 -0
  22. package/dist/commands/patch-review.d.ts.map +1 -0
  23. package/dist/commands/patch-review.js +200 -0
  24. package/dist/commands/release-notes.d.ts +17 -0
  25. package/dist/commands/release-notes.d.ts.map +1 -0
  26. package/dist/commands/release-notes.js +145 -0
  27. package/dist/commands/suggest.d.ts +18 -0
  28. package/dist/commands/suggest.d.ts.map +1 -0
  29. package/dist/commands/suggest.js +152 -0
  30. package/dist/commands/triage.d.ts +17 -0
  31. package/dist/commands/triage.d.ts.map +1 -0
  32. package/dist/commands/triage.js +205 -0
  33. package/dist/commands/upload.d.ts.map +1 -1
  34. package/dist/commands/upload.js +8 -7
  35. package/dist/index.js +62 -25
  36. package/dist/output/formats.d.ts.map +1 -1
  37. package/dist/output/formats.js +14 -0
  38. package/dist/output/human.d.ts +1 -10
  39. package/dist/output/human.d.ts.map +1 -1
  40. package/dist/output/human.js +26 -98
  41. package/dist/prompts/command-catalog.d.ts +46 -0
  42. package/dist/prompts/command-catalog.d.ts.map +1 -0
  43. package/dist/prompts/command-catalog.js +187 -0
  44. package/dist/ui/spinner.d.ts +10 -35
  45. package/dist/ui/spinner.d.ts.map +1 -1
  46. package/dist/ui/spinner.js +11 -58
  47. package/dist/ui/table.d.ts +1 -18
  48. package/dist/ui/table.d.ts.map +1 -1
  49. package/dist/ui/table.js +56 -163
  50. package/dist/utils/ai-error.d.ts +48 -0
  51. package/dist/utils/ai-error.d.ts.map +1 -0
  52. package/dist/utils/ai-error.js +190 -0
  53. package/dist/utils/detect-env.d.ts +6 -8
  54. package/dist/utils/detect-env.d.ts.map +1 -1
  55. package/dist/utils/detect-env.js +6 -25
  56. package/dist/utils/stdin.d.ts +50 -0
  57. package/dist/utils/stdin.d.ts.map +1 -0
  58. package/dist/utils/stdin.js +93 -0
  59. package/package.json +9 -5
@@ -0,0 +1,190 @@
1
+ /**
2
+ * Centralized AI error classification and formatting for CLI commands.
3
+ *
4
+ * Provides consistent error handling across all 8 AI-powered commands.
5
+ * Classifies errors into distinct types (timeout, unreachable, auth, etc.)
6
+ * and formats user-friendly messages with actionable suggestions.
7
+ */
8
+ import { ExitCode } from "./exit-codes.js";
9
+ import { failSpinner } from "../ui/spinner.js";
10
+ // ---------------------------------------------------------------------------
11
+ // Constants
12
+ // ---------------------------------------------------------------------------
13
+ /** Default timeout for LLM API requests (30 seconds). */
14
+ export const AI_TIMEOUT_MS = 30_000;
15
+ // ---------------------------------------------------------------------------
16
+ // Classification
17
+ // ---------------------------------------------------------------------------
18
+ /**
19
+ * Classify an unknown error into a specific AI error kind.
20
+ *
21
+ * Inspects error properties (name, code, message, status) to determine
22
+ * the most specific classification. Falls back to `"unknown"` when no
23
+ * pattern matches.
24
+ *
25
+ * @param error - The caught error (any type)
26
+ * @returns The classified error kind
27
+ */
28
+ export function classifyAiError(error) {
29
+ if (!error)
30
+ return "unknown";
31
+ const err = error;
32
+ const name = typeof err.name === "string" ? err.name : "";
33
+ const code = typeof err.code === "string" ? err.code : "";
34
+ const message = error instanceof Error
35
+ ? error.message.toLowerCase()
36
+ : typeof error === "string"
37
+ ? error.toLowerCase()
38
+ : "";
39
+ const status = typeof err.status === "number"
40
+ ? err.status
41
+ : typeof err.statusCode === "number"
42
+ ? err.statusCode
43
+ : extractHttpStatus(message);
44
+ // Timeout indicators
45
+ if (name === "AbortError" ||
46
+ code === "ETIMEDOUT" ||
47
+ code === "TIMEOUT" ||
48
+ code === "UND_ERR_CONNECT_TIMEOUT" ||
49
+ message.includes("timeout") ||
50
+ message.includes("timed out") ||
51
+ message.includes("aborted")) {
52
+ return "timeout";
53
+ }
54
+ // Unreachable / network errors
55
+ if (code === "ECONNREFUSED" ||
56
+ code === "ENOTFOUND" ||
57
+ code === "ECONNRESET" ||
58
+ code === "FETCH_ERROR" ||
59
+ code === "UND_ERR_SOCKET" ||
60
+ message.includes("fetch failed") ||
61
+ message.includes("network error") ||
62
+ message.includes("dns resolution") ||
63
+ name === "TypeError" && message.includes("fetch")) {
64
+ return "unreachable";
65
+ }
66
+ // HTTP status-based classification
67
+ if (status !== null) {
68
+ if (status === 401 || status === 403)
69
+ return "auth";
70
+ if (status === 429)
71
+ return "rate_limit";
72
+ if (status >= 500)
73
+ return "server_error";
74
+ }
75
+ // Auth keywords in message
76
+ if (message.includes("unauthorized") ||
77
+ message.includes("authentication required") ||
78
+ message.includes("invalid api key") ||
79
+ message.includes("forbidden")) {
80
+ return "auth";
81
+ }
82
+ // Rate limit keywords
83
+ if (message.includes("rate limit") ||
84
+ message.includes("too many requests")) {
85
+ return "rate_limit";
86
+ }
87
+ // Invalid response / schema mismatch
88
+ if (message.includes("unexpected token") ||
89
+ message.includes("invalid json") ||
90
+ message.includes("unexpected response") ||
91
+ message.includes("schema") ||
92
+ message.includes("validation failed")) {
93
+ return "invalid_response";
94
+ }
95
+ return "unknown";
96
+ }
97
+ /**
98
+ * Extract an HTTP status code from an error message like "HTTP 401: Unauthorized".
99
+ */
100
+ function extractHttpStatus(message) {
101
+ const match = message.match(/\bhttp\s+(\d{3})\b/i);
102
+ if (match)
103
+ return parseInt(match[1], 10);
104
+ return null;
105
+ }
106
+ // ---------------------------------------------------------------------------
107
+ // Formatting
108
+ // ---------------------------------------------------------------------------
109
+ /**
110
+ * Format an AI error kind into a user-friendly message with suggestions.
111
+ *
112
+ * @param kind - The classified error kind
113
+ * @param command - The CLI command name (e.g., "explain", "triage")
114
+ * @returns Multi-line string with error description and actionable suggestions
115
+ */
116
+ export function formatAiError(kind, command) {
117
+ switch (kind) {
118
+ case "timeout":
119
+ return [
120
+ "LLM request timed out after 30 seconds.",
121
+ "",
122
+ " Suggestions:",
123
+ " - Try again (transient server load)",
124
+ " - Use a simpler audit (fewer issues = faster response)",
125
+ " - Check service status at https://status.vertaaux.ai",
126
+ ].join("\n");
127
+ case "unreachable":
128
+ return [
129
+ "Could not reach the VertaaUX API.",
130
+ "",
131
+ " Suggestions:",
132
+ " - Check your internet connection",
133
+ " - Verify VERTAAUX_API_BASE is correct",
134
+ " - Try: vertaa doctor",
135
+ ].join("\n");
136
+ case "auth":
137
+ return [
138
+ "Authentication required for AI commands.",
139
+ "",
140
+ " Run: vertaa login",
141
+ " Or set: VERTAAUX_API_KEY=<key>",
142
+ ].join("\n");
143
+ case "invalid_response":
144
+ return [
145
+ "Received unexpected response from AI service.",
146
+ "",
147
+ " This is likely a temporary issue. Try again in a few moments.",
148
+ ].join("\n");
149
+ case "rate_limit":
150
+ return [
151
+ "Rate limit exceeded.",
152
+ "",
153
+ " Wait a few seconds and try again.",
154
+ ].join("\n");
155
+ case "server_error":
156
+ return [
157
+ "VertaaUX API returned a server error.",
158
+ "",
159
+ " This is usually temporary. Try again in a moment.",
160
+ ].join("\n");
161
+ case "unknown":
162
+ return [
163
+ `An unexpected error occurred during ${command}.`,
164
+ "",
165
+ " Try: vertaa doctor",
166
+ " If this persists, file an issue.",
167
+ ].join("\n");
168
+ }
169
+ }
170
+ // ---------------------------------------------------------------------------
171
+ // Handler
172
+ // ---------------------------------------------------------------------------
173
+ /**
174
+ * Handle an AI command error: classify, format, display, and exit.
175
+ *
176
+ * This is the primary entry point for error handling in AI commands.
177
+ * It classifies the error, fails the spinner with a summary, writes
178
+ * the full formatted message to stderr, and exits with ExitCode.ERROR.
179
+ *
180
+ * @param error - The caught error
181
+ * @param command - The CLI command name (e.g., "explain", "triage")
182
+ * @param spinner - Active spinner instance to fail
183
+ */
184
+ export function handleAiCommandError(error, command, spinner) {
185
+ const kind = classifyAiError(error);
186
+ const message = formatAiError(kind, command);
187
+ failSpinner(spinner, `${command} failed`);
188
+ console.error(message);
189
+ process.exit(ExitCode.ERROR);
190
+ }
@@ -29,13 +29,11 @@ export declare function isPiped(): boolean;
29
29
  /**
30
30
  * Determine if colors should be used in output.
31
31
  *
32
- * Priority:
33
- * 1. NO_COLOR env var (disable)
34
- * 2. FORCE_COLOR env var (enable)
35
- * 3. TTY detection
36
- * 4. CI environment (usually disable)
32
+ * Delegates to @vertaaux/tui's isColorEnabled() which is set
33
+ * by the CLI's preAction hook based on --color/--no-color flags
34
+ * and environment detection.
37
35
  */
38
- export declare function shouldUseColor(): boolean;
36
+ export { isColorEnabled as shouldUseColor } from "@vertaaux/tui";
39
37
  /**
40
38
  * Detect the appropriate output format based on environment and explicit override.
41
39
  *
@@ -53,7 +51,7 @@ export declare function shouldUseColor(): boolean;
53
51
  export declare function detectOutputFormat(explicit?: string): OutputFormat;
54
52
  /**
55
53
  * Get terminal width for formatting.
56
- * Falls back to 80 columns if not detectable.
54
+ * Delegates to @vertaaux/tui (reads from stderr where UI renders).
57
55
  */
58
- export declare function getTerminalWidth(): number;
56
+ export { getTerminalWidth } from "@vertaaux/tui";
59
57
  //# sourceMappingURL=detect-env.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"detect-env.d.ts","sourceRoot":"","sources":["../../src/utils/detect-env.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;AAEzE;;;GAGG;AACH,wBAAgB,IAAI,IAAI,OAAO,CAY9B;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAED;;GAEG;AACH,wBAAgB,UAAU,IAAI,OAAO,CAEpC;AAED;;GAEG;AACH,wBAAgB,KAAK,IAAI,OAAO,CAE/B;AAED;;GAEG;AACH,wBAAgB,OAAO,IAAI,OAAO,CAEjC;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAkBxC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,YAAY,CA0BlE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC"}
1
+ {"version":3,"file":"detect-env.d.ts","sourceRoot":"","sources":["../../src/utils/detect-env.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;AAEzE;;;GAGG;AACH,wBAAgB,IAAI,IAAI,OAAO,CAY9B;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAEzC;AAED;;GAEG;AACH,wBAAgB,UAAU,IAAI,OAAO,CAEpC;AAED;;GAEG;AACH,wBAAgB,KAAK,IAAI,OAAO,CAE/B;AAED;;GAEG;AACH,wBAAgB,OAAO,IAAI,OAAO,CAEjC;AAED;;;;;;GAMG;AACH,OAAO,EAAE,cAAc,IAAI,cAAc,EAAE,MAAM,eAAe,CAAC;AAEjE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,YAAY,CA0BlE;AAED;;;GAGG;AACH,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC"}
@@ -46,28 +46,11 @@ export function isPiped() {
46
46
  /**
47
47
  * Determine if colors should be used in output.
48
48
  *
49
- * Priority:
50
- * 1. NO_COLOR env var (disable)
51
- * 2. FORCE_COLOR env var (enable)
52
- * 3. TTY detection
53
- * 4. CI environment (usually disable)
49
+ * Delegates to @vertaaux/tui's isColorEnabled() which is set
50
+ * by the CLI's preAction hook based on --color/--no-color flags
51
+ * and environment detection.
54
52
  */
55
- export function shouldUseColor() {
56
- // NO_COLOR takes highest priority (https://no-color.org/)
57
- if (process.env.NO_COLOR !== undefined) {
58
- return false;
59
- }
60
- // FORCE_COLOR overrides TTY detection
61
- if (process.env.FORCE_COLOR !== undefined) {
62
- return true;
63
- }
64
- // No color in CI by default
65
- if (isCI()) {
66
- return false;
67
- }
68
- // Use color if interactive TTY
69
- return isTTY();
70
- }
53
+ export { isColorEnabled as shouldUseColor } from "@vertaaux/tui";
71
54
  /**
72
55
  * Detect the appropriate output format based on environment and explicit override.
73
56
  *
@@ -108,8 +91,6 @@ export function detectOutputFormat(explicit) {
108
91
  }
109
92
  /**
110
93
  * Get terminal width for formatting.
111
- * Falls back to 80 columns if not detectable.
94
+ * Delegates to @vertaaux/tui (reads from stderr where UI renders).
112
95
  */
113
- export function getTerminalWidth() {
114
- return process.stdout.columns || 80;
115
- }
96
+ export { getTerminalWidth } from "@vertaaux/tui";
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Universal stdin reader for CLI commands.
3
+ *
4
+ * Detects piped input vs TTY, reads JSON or plaintext from stdin,
5
+ * and provides a consistent interface for all commands that accept
6
+ * piped data (explain, triage, fix-plan, patch-review, etc.).
7
+ */
8
+ /**
9
+ * Result of reading from stdin.
10
+ */
11
+ export interface StdinResult {
12
+ /** Raw string content from stdin */
13
+ raw: string;
14
+ /** Parsed JSON if input was valid JSON, otherwise null */
15
+ json: unknown | null;
16
+ /** Whether the input was valid JSON */
17
+ isJson: boolean;
18
+ }
19
+ /**
20
+ * Check if stdin has piped data available.
21
+ */
22
+ export declare function hasPipedInput(): boolean;
23
+ /**
24
+ * Read all available data from stdin.
25
+ *
26
+ * Only reads when stdin is piped (not a TTY). Returns null if
27
+ * stdin is a TTY (interactive terminal).
28
+ *
29
+ * @returns Parsed stdin result, or null if no piped input
30
+ */
31
+ export declare function readStdin(): Promise<StdinResult | null>;
32
+ /**
33
+ * Read JSON from stdin, a file path, or return null.
34
+ *
35
+ * Provides the common pattern used by AI commands:
36
+ * 1. Check --file flag → read file
37
+ * 2. Check stdin pipe → read piped JSON
38
+ * 3. Return null (caller should check --job or show error)
39
+ *
40
+ * @param filePath - Optional file path from --file flag
41
+ * @returns Parsed JSON object, or null if no input found
42
+ */
43
+ export declare function readJsonInput(filePath?: string): Promise<unknown | null>;
44
+ /**
45
+ * Read plaintext from stdin (for diffs, code, etc.).
46
+ *
47
+ * @returns Raw text content, or null if no piped input
48
+ */
49
+ export declare function readTextInput(): Promise<string | null>;
50
+ //# sourceMappingURL=stdin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stdin.d.ts","sourceRoot":"","sources":["../../src/utils/stdin.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,oCAAoC;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,0DAA0D;IAC1D,IAAI,EAAE,OAAO,GAAG,IAAI,CAAC;IACrB,uCAAuC;IACvC,MAAM,EAAE,OAAO,CAAC;CACjB;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAED;;;;;;;GAOG;AACH,wBAAsB,SAAS,IAAI,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CA2B7D;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,aAAa,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CA+B9E;AAED;;;;GAIG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAG5D"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Universal stdin reader for CLI commands.
3
+ *
4
+ * Detects piped input vs TTY, reads JSON or plaintext from stdin,
5
+ * and provides a consistent interface for all commands that accept
6
+ * piped data (explain, triage, fix-plan, patch-review, etc.).
7
+ */
8
+ /**
9
+ * Check if stdin has piped data available.
10
+ */
11
+ export function hasPipedInput() {
12
+ return !process.stdin.isTTY;
13
+ }
14
+ /**
15
+ * Read all available data from stdin.
16
+ *
17
+ * Only reads when stdin is piped (not a TTY). Returns null if
18
+ * stdin is a TTY (interactive terminal).
19
+ *
20
+ * @returns Parsed stdin result, or null if no piped input
21
+ */
22
+ export async function readStdin() {
23
+ if (process.stdin.isTTY) {
24
+ return null;
25
+ }
26
+ const chunks = [];
27
+ for await (const chunk of process.stdin) {
28
+ chunks.push(chunk);
29
+ }
30
+ const raw = Buffer.concat(chunks).toString("utf-8").trim();
31
+ if (!raw) {
32
+ return null;
33
+ }
34
+ let json = null;
35
+ let isJson = false;
36
+ try {
37
+ json = JSON.parse(raw);
38
+ isJson = true;
39
+ }
40
+ catch {
41
+ // Not JSON — that's fine, could be plaintext (e.g., a diff)
42
+ }
43
+ return { raw, json, isJson };
44
+ }
45
+ /**
46
+ * Read JSON from stdin, a file path, or return null.
47
+ *
48
+ * Provides the common pattern used by AI commands:
49
+ * 1. Check --file flag → read file
50
+ * 2. Check stdin pipe → read piped JSON
51
+ * 3. Return null (caller should check --job or show error)
52
+ *
53
+ * @param filePath - Optional file path from --file flag
54
+ * @returns Parsed JSON object, or null if no input found
55
+ */
56
+ export async function readJsonInput(filePath) {
57
+ // Priority 1: explicit file path
58
+ if (filePath) {
59
+ const fs = await import("fs");
60
+ const path = await import("path");
61
+ const resolved = path.resolve(process.cwd(), filePath);
62
+ if (!fs.existsSync(resolved)) {
63
+ throw new Error(`Input file not found: ${filePath}`);
64
+ }
65
+ const content = fs.readFileSync(resolved, "utf-8");
66
+ try {
67
+ return JSON.parse(content);
68
+ }
69
+ catch {
70
+ throw new Error(`Invalid JSON in file: ${filePath}`);
71
+ }
72
+ }
73
+ // Priority 2: piped stdin
74
+ const stdin = await readStdin();
75
+ if (stdin) {
76
+ if (!stdin.isJson) {
77
+ throw new Error("Could not parse input as JSON. If piping from vertaa, use --json flag:\n" +
78
+ " vertaa audit https://example.com --json | vertaa explain");
79
+ }
80
+ return stdin.json;
81
+ }
82
+ // No input found
83
+ return null;
84
+ }
85
+ /**
86
+ * Read plaintext from stdin (for diffs, code, etc.).
87
+ *
88
+ * @returns Raw text content, or null if no piped input
89
+ */
90
+ export async function readTextInput() {
91
+ const stdin = await readStdin();
92
+ return stdin?.raw ?? null;
93
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertaaux/cli",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "description": "Run automated UX audits, accessibility checks, and performance analysis from the terminal or CI pipelines. Supports policy gating, SARIF output, and multi-page crawling. See https://github.com/PetriLahdelma/vertaa/tree/main/cli#readme for full docs.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -40,19 +40,21 @@
40
40
  "dev": "tsc -w",
41
41
  "start": "node dist/index.js",
42
42
  "prepublishOnly": "npm run build && node scripts/verify-package.mjs",
43
- "test": "vitest run --config vitest.config.ts"
43
+ "test": "vitest run --config vitest.config.ts --exclude tests/e2e/**/*.test.ts",
44
+ "test:e2e": "vitest run --config vitest.config.ts tests/e2e/cli-contract.test.ts",
45
+ "test:e2e:package": "vitest run --config vitest.config.ts tests/e2e/package-install.test.ts",
46
+ "test:e2e:ui": "vitest --ui --config vitest.config.ts tests/e2e/cli-contract.test.ts"
44
47
  },
45
48
  "dependencies": {
46
49
  "@inquirer/prompts": "^8.2.0",
50
+ "@vertaaux/tui": "file:../packages/tui",
47
51
  "ajv": "^8.17.1",
48
52
  "ajv-formats": "^3.0.1",
49
53
  "chalk": "^5.6.2",
50
- "cli-table3": "^0.6.5",
51
54
  "commander": "^14.0.2",
52
55
  "cosmiconfig": "^9.0.0",
53
56
  "dotenv": "^17.2.3",
54
57
  "minimatch": "^10.1.1",
55
- "ora": "^9.1.0",
56
58
  "semver": "^7.7.3",
57
59
  "yaml": "^2.8.2"
58
60
  },
@@ -60,6 +62,8 @@
60
62
  "@types/minimatch": "^5.1.2",
61
63
  "@types/node": "^20.11.25",
62
64
  "@types/semver": "^7.7.1",
63
- "typescript": "^5.6.3"
65
+ "@vitest/ui": "^4.0.18",
66
+ "typescript": "^5.6.3",
67
+ "vitest": "^4.0.15"
64
68
  }
65
69
  }