@vertaaux/cli 0.4.0 → 0.5.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 (223) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/MIGRATION.md +239 -0
  3. package/README.md +34 -16
  4. package/dist/app/interactive-app.d.ts +101 -0
  5. package/dist/app/interactive-app.d.ts.map +1 -0
  6. package/dist/app/interactive-app.js +309 -0
  7. package/dist/app/layout/canvas.d.ts +23 -0
  8. package/dist/app/layout/canvas.d.ts.map +1 -0
  9. package/dist/app/layout/canvas.js +36 -0
  10. package/dist/app/layout/footer.d.ts +31 -0
  11. package/dist/app/layout/footer.d.ts.map +1 -0
  12. package/dist/app/layout/footer.js +41 -0
  13. package/dist/app/layout/header.d.ts +20 -0
  14. package/dist/app/layout/header.d.ts.map +1 -0
  15. package/dist/app/layout/header.js +27 -0
  16. package/dist/app/menu/categories.d.ts +20 -0
  17. package/dist/app/menu/categories.d.ts.map +1 -0
  18. package/dist/app/menu/categories.js +181 -0
  19. package/dist/app/menu/filter.d.ts +17 -0
  20. package/dist/app/menu/filter.d.ts.map +1 -0
  21. package/dist/app/menu/filter.js +33 -0
  22. package/dist/app/menu/menu-view.d.ts +35 -0
  23. package/dist/app/menu/menu-view.d.ts.map +1 -0
  24. package/dist/app/menu/menu-view.js +230 -0
  25. package/dist/app/menu/recent.d.ts +24 -0
  26. package/dist/app/menu/recent.d.ts.map +1 -0
  27. package/dist/app/menu/recent.js +49 -0
  28. package/dist/app/types.d.ts +43 -0
  29. package/dist/app/types.d.ts.map +1 -0
  30. package/dist/app/types.js +7 -0
  31. package/dist/app/views/command-runner.d.ts +36 -0
  32. package/dist/app/views/command-runner.d.ts.map +1 -0
  33. package/dist/app/views/command-runner.js +372 -0
  34. package/dist/app/views/help-overlay.d.ts +21 -0
  35. package/dist/app/views/help-overlay.d.ts.map +1 -0
  36. package/dist/app/views/help-overlay.js +45 -0
  37. package/dist/auth/ci-token.d.ts +8 -2
  38. package/dist/auth/ci-token.d.ts.map +1 -1
  39. package/dist/auth/ci-token.js +15 -30
  40. package/dist/auth/device-flow.d.ts +2 -1
  41. package/dist/auth/device-flow.d.ts.map +1 -1
  42. package/dist/auth/device-flow.js +13 -10
  43. package/dist/auth/token-store.d.ts.map +1 -1
  44. package/dist/auth/token-store.js +12 -2
  45. package/dist/baseline/diff.d.ts +2 -2
  46. package/dist/baseline/diff.d.ts.map +1 -1
  47. package/dist/baseline/diff.js +15 -34
  48. package/dist/commands/a11y.d.ts +9 -0
  49. package/dist/commands/a11y.d.ts.map +1 -0
  50. package/dist/commands/a11y.js +76 -0
  51. package/dist/commands/audit/artifacts.d.ts +27 -0
  52. package/dist/commands/audit/artifacts.d.ts.map +1 -0
  53. package/dist/commands/audit/artifacts.js +158 -0
  54. package/dist/commands/audit/ci-detection.d.ts +18 -0
  55. package/dist/commands/audit/ci-detection.d.ts.map +1 -0
  56. package/dist/commands/audit/ci-detection.js +71 -0
  57. package/dist/commands/audit/explain.d.ts +11 -0
  58. package/dist/commands/audit/explain.d.ts.map +1 -0
  59. package/dist/commands/audit/explain.js +45 -0
  60. package/dist/commands/audit/filters.d.ts +17 -0
  61. package/dist/commands/audit/filters.d.ts.map +1 -0
  62. package/dist/commands/audit/filters.js +40 -0
  63. package/dist/commands/audit/index.d.ts +18 -0
  64. package/dist/commands/audit/index.d.ts.map +1 -0
  65. package/dist/commands/audit/index.js +564 -0
  66. package/dist/commands/audit/output.d.ts +32 -0
  67. package/dist/commands/audit/output.d.ts.map +1 -0
  68. package/dist/commands/audit/output.js +130 -0
  69. package/dist/commands/audit/policy.d.ts +19 -0
  70. package/dist/commands/audit/policy.d.ts.map +1 -0
  71. package/dist/commands/audit/policy.js +102 -0
  72. package/dist/commands/audit/scoring.d.ts +23 -0
  73. package/dist/commands/audit/scoring.d.ts.map +1 -0
  74. package/dist/commands/audit/scoring.js +70 -0
  75. package/dist/commands/audit/types.d.ts +88 -0
  76. package/dist/commands/audit/types.d.ts.map +1 -0
  77. package/dist/commands/audit/types.js +8 -0
  78. package/dist/commands/audit.d.ts +2 -60
  79. package/dist/commands/audit.d.ts.map +1 -1
  80. package/dist/commands/audit.js +2 -1097
  81. package/dist/commands/baseline.d.ts +1 -0
  82. package/dist/commands/baseline.d.ts.map +1 -1
  83. package/dist/commands/baseline.js +205 -121
  84. package/dist/commands/comment.d.ts +22 -0
  85. package/dist/commands/comment.d.ts.map +1 -1
  86. package/dist/commands/comment.js +122 -58
  87. package/dist/commands/compare.d.ts +17 -0
  88. package/dist/commands/compare.d.ts.map +1 -1
  89. package/dist/commands/compare.js +287 -180
  90. package/dist/commands/diff.d.ts +5 -0
  91. package/dist/commands/diff.d.ts.map +1 -1
  92. package/dist/commands/diff.js +168 -141
  93. package/dist/commands/doc.d.ts +10 -0
  94. package/dist/commands/doc.d.ts.map +1 -1
  95. package/dist/commands/doc.js +134 -76
  96. package/dist/commands/doctor.d.ts +2 -0
  97. package/dist/commands/doctor.d.ts.map +1 -1
  98. package/dist/commands/doctor.js +164 -17
  99. package/dist/commands/download.d.ts +10 -0
  100. package/dist/commands/download.d.ts.map +1 -1
  101. package/dist/commands/download.js +169 -112
  102. package/dist/commands/explain.d.ts +5 -0
  103. package/dist/commands/explain.d.ts.map +1 -1
  104. package/dist/commands/explain.js +241 -155
  105. package/dist/commands/fix-all.d.ts +25 -0
  106. package/dist/commands/fix-all.d.ts.map +1 -0
  107. package/dist/commands/fix-all.js +206 -0
  108. package/dist/commands/fix-plan.d.ts +9 -0
  109. package/dist/commands/fix-plan.d.ts.map +1 -1
  110. package/dist/commands/fix-plan.js +152 -89
  111. package/dist/commands/fix.d.ts +17 -0
  112. package/dist/commands/fix.d.ts.map +1 -0
  113. package/dist/commands/fix.js +111 -0
  114. package/dist/commands/init.d.ts +11 -0
  115. package/dist/commands/init.d.ts.map +1 -1
  116. package/dist/commands/init.js +94 -42
  117. package/dist/commands/login.d.ts +18 -0
  118. package/dist/commands/login.d.ts.map +1 -1
  119. package/dist/commands/login.js +263 -92
  120. package/dist/commands/patch-review.d.ts +11 -0
  121. package/dist/commands/patch-review.d.ts.map +1 -1
  122. package/dist/commands/patch-review.js +159 -97
  123. package/dist/commands/policy.d.ts +31 -0
  124. package/dist/commands/policy.d.ts.map +1 -1
  125. package/dist/commands/policy.js +269 -124
  126. package/dist/commands/release-notes.d.ts +10 -0
  127. package/dist/commands/release-notes.d.ts.map +1 -1
  128. package/dist/commands/release-notes.js +127 -73
  129. package/dist/commands/scan.d.ts +13 -0
  130. package/dist/commands/scan.d.ts.map +1 -0
  131. package/dist/commands/scan.js +133 -0
  132. package/dist/commands/status.d.ts +9 -0
  133. package/dist/commands/status.d.ts.map +1 -0
  134. package/dist/commands/status.js +81 -0
  135. package/dist/commands/suggest.d.ts +10 -0
  136. package/dist/commands/suggest.d.ts.map +1 -1
  137. package/dist/commands/suggest.js +153 -82
  138. package/dist/commands/triage.d.ts +35 -0
  139. package/dist/commands/triage.d.ts.map +1 -1
  140. package/dist/commands/triage.js +206 -81
  141. package/dist/commands/upload.d.ts +9 -0
  142. package/dist/commands/upload.d.ts.map +1 -1
  143. package/dist/commands/upload.js +140 -101
  144. package/dist/commands/verify.d.ts +13 -0
  145. package/dist/commands/verify.d.ts.map +1 -0
  146. package/dist/commands/verify.js +118 -0
  147. package/dist/index.d.ts +3 -2
  148. package/dist/index.d.ts.map +1 -1
  149. package/dist/index.js +125 -990
  150. package/dist/interactive/fix-wizard.d.ts +3 -0
  151. package/dist/interactive/fix-wizard.d.ts.map +1 -1
  152. package/dist/interactive/fix-wizard.js +130 -112
  153. package/dist/interactive/init-wizard.d.ts +3 -1
  154. package/dist/interactive/init-wizard.d.ts.map +1 -1
  155. package/dist/interactive/init-wizard.js +207 -138
  156. package/dist/interactive/prompts.d.ts +7 -3
  157. package/dist/interactive/prompts.d.ts.map +1 -1
  158. package/dist/interactive/prompts.js +44 -23
  159. package/dist/output/envelope.d.ts +2 -0
  160. package/dist/output/envelope.d.ts.map +1 -1
  161. package/dist/output/envelope.js +18 -2
  162. package/dist/output/factory.d.ts +2 -1
  163. package/dist/output/factory.d.ts.map +1 -1
  164. package/dist/output/html.d.ts +2 -1
  165. package/dist/output/html.d.ts.map +1 -1
  166. package/dist/output/html.js +3 -2
  167. package/dist/output/human.d.ts +2 -1
  168. package/dist/output/human.d.ts.map +1 -1
  169. package/dist/output/human.js +3 -2
  170. package/dist/output/json.d.ts +2 -1
  171. package/dist/output/json.d.ts.map +1 -1
  172. package/dist/output/junit.d.ts +2 -1
  173. package/dist/output/junit.d.ts.map +1 -1
  174. package/dist/output/sarif.d.ts +2 -1
  175. package/dist/output/sarif.d.ts.map +1 -1
  176. package/dist/types.d.ts +74 -0
  177. package/dist/types.d.ts.map +1 -0
  178. package/dist/types.js +5 -0
  179. package/dist/ui/banner.d.ts +34 -0
  180. package/dist/ui/banner.d.ts.map +1 -1
  181. package/dist/ui/banner.js +97 -5
  182. package/dist/ui/diagnostics.d.ts +9 -4
  183. package/dist/ui/diagnostics.d.ts.map +1 -1
  184. package/dist/ui/diagnostics.js +32 -82
  185. package/dist/ui/strings.d.ts +373 -0
  186. package/dist/ui/strings.d.ts.map +1 -0
  187. package/dist/ui/strings.js +499 -0
  188. package/dist/ui/table.d.ts +0 -2
  189. package/dist/ui/table.d.ts.map +1 -1
  190. package/dist/ui/table.js +3 -4
  191. package/dist/utils/api-client.d.ts +46 -0
  192. package/dist/utils/api-client.d.ts.map +1 -0
  193. package/dist/utils/api-client.js +170 -0
  194. package/dist/utils/client.d.ts +29 -18
  195. package/dist/utils/client.d.ts.map +1 -1
  196. package/dist/utils/client.js +102 -12
  197. package/dist/utils/formatters.d.ts +38 -0
  198. package/dist/utils/formatters.d.ts.map +1 -0
  199. package/dist/utils/formatters.js +277 -0
  200. package/dist/utils/url-classify.d.ts.map +1 -1
  201. package/dist/utils/url-classify.js +24 -3
  202. package/node_modules/@vertaaux/tui/dist/index.cjs +713 -20
  203. package/node_modules/@vertaaux/tui/dist/index.cjs.map +1 -1
  204. package/node_modules/@vertaaux/tui/dist/index.d.cts +361 -4
  205. package/node_modules/@vertaaux/tui/dist/index.d.ts +361 -4
  206. package/node_modules/@vertaaux/tui/dist/index.js +689 -21
  207. package/node_modules/@vertaaux/tui/dist/index.js.map +1 -1
  208. package/package.json +13 -5
  209. package/dist/commands/client.d.ts +0 -14
  210. package/dist/commands/client.d.ts.map +0 -1
  211. package/dist/commands/client.js +0 -362
  212. package/dist/commands/drift.d.ts +0 -15
  213. package/dist/commands/drift.d.ts.map +0 -1
  214. package/dist/commands/drift.js +0 -309
  215. package/dist/commands/protect.d.ts +0 -16
  216. package/dist/commands/protect.d.ts.map +0 -1
  217. package/dist/commands/protect.js +0 -323
  218. package/dist/commands/report.d.ts +0 -15
  219. package/dist/commands/report.d.ts.map +0 -1
  220. package/dist/commands/report.js +0 -214
  221. package/dist/policy/sync.d.ts +0 -67
  222. package/dist/policy/sync.d.ts.map +0 -1
  223. package/dist/policy/sync.js +0 -147
@@ -17,15 +17,15 @@
17
17
  */
18
18
  import fs from "fs";
19
19
  import path from "path";
20
- import chalk from "chalk";
20
+ import { bold, dim, colorize, boldColor, brand, severity as severityPalette, renderError, createRenderer, runSteps } from "@vertaaux/tui";
21
21
  import { ExitCode } from "../utils/exit-codes.js";
22
- import { resolveApiBase, getApiKey, apiRequest } from "../utils/client.js";
22
+ import { resolveApiBase, getApiKey, apiRequest, createClient } from "../utils/client.js";
23
23
  import { resolveConfig } from "../config/loader.js";
24
24
  import { writeJsonOutput, writeOutput } from "../output/envelope.js";
25
25
  import { resolveCommandFormat } from "../output/formats.js";
26
- import { createSpinner, succeedSpinner } from "../ui/spinner.js";
27
26
  import { readJsonInput } from "../utils/stdin.js";
28
- import { handleAiCommandError, AI_TIMEOUT_MS } from "../utils/ai-error.js";
27
+ import { AI_TIMEOUT_MS } from "../utils/ai-error.js";
28
+ import { strings } from "../ui/strings.js";
29
29
  // ---------------------------------------------------------------------------
30
30
  // Constants
31
31
  // ---------------------------------------------------------------------------
@@ -61,15 +61,15 @@ function coloredSeverity(severity) {
61
61
  switch (sev) {
62
62
  case "critical":
63
63
  case "error":
64
- return chalk.red.bold(sev.toUpperCase());
64
+ return boldColor(sev.toUpperCase(), severityPalette.error);
65
65
  case "serious":
66
66
  case "warning":
67
- return chalk.yellow.bold(sev.toUpperCase());
67
+ return boldColor(sev.toUpperCase(), severityPalette.warning);
68
68
  case "moderate":
69
69
  case "minor":
70
- return chalk.cyan(sev.toUpperCase());
70
+ return colorize(sev.toUpperCase(), brand.cyan);
71
71
  default:
72
- return chalk.dim(sev.toUpperCase());
72
+ return dim(sev.toUpperCase());
73
73
  }
74
74
  }
75
75
  function truncate(str, maxLength) {
@@ -87,37 +87,37 @@ export function formatEvidenceBundle(issue) {
87
87
  const lines = [];
88
88
  const ruleId = getRuleId(issue);
89
89
  const severity = coloredSeverity(issue.severity);
90
- lines.push(chalk.bold(`ISSUE: ${ruleId} (${severity})`));
90
+ lines.push(bold(`ISSUE: ${ruleId} (${severity})`));
91
91
  lines.push("");
92
- lines.push(chalk.cyan.bold("DESCRIPTION"));
92
+ lines.push(boldColor("DESCRIPTION", brand.cyan));
93
93
  lines.push(issue.description || issue.title || "No description available");
94
94
  lines.push("");
95
95
  if (issue.selector) {
96
- lines.push(chalk.cyan.bold("SELECTOR"));
97
- lines.push(chalk.gray(issue.selector));
96
+ lines.push(boldColor("SELECTOR", brand.cyan));
97
+ lines.push(dim(issue.selector));
98
98
  lines.push("");
99
99
  }
100
100
  if (issue.wcag_reference) {
101
- lines.push(chalk.cyan.bold("WCAG REFERENCE"));
101
+ lines.push(boldColor("WCAG REFERENCE", brand.cyan));
102
102
  lines.push(issue.wcag_reference);
103
103
  lines.push("");
104
104
  }
105
105
  const recommendation = issue.recommendation || issue.recommended_fix;
106
106
  if (recommendation) {
107
- lines.push(chalk.cyan.bold("RECOMMENDATION"));
107
+ lines.push(boldColor("RECOMMENDATION", brand.cyan));
108
108
  lines.push(recommendation);
109
109
  lines.push("");
110
110
  }
111
111
  const hasEvidence = issue.screenshot || issue.html || issue.element || issue.helpUrl;
112
112
  if (hasEvidence) {
113
- lines.push(chalk.cyan.bold("EVIDENCE"));
113
+ lines.push(boldColor("EVIDENCE", brand.cyan));
114
114
  if (issue.screenshot)
115
- lines.push(`- Screenshot: ${chalk.underline(issue.screenshot)}`);
115
+ lines.push(`- Screenshot: ${issue.screenshot}`);
116
116
  if (issue.html || issue.element) {
117
- lines.push(`- DOM Snapshot: ${chalk.dim(truncate(issue.html || issue.element || "", 100))}`);
117
+ lines.push(`- DOM Snapshot: ${dim(truncate(issue.html || issue.element || "", 100))}`);
118
118
  }
119
119
  if (issue.helpUrl)
120
- lines.push(`- Help: ${chalk.underline(issue.helpUrl)}`);
120
+ lines.push(`- Help: ${issue.helpUrl}`);
121
121
  }
122
122
  return lines.join("\n");
123
123
  }
@@ -147,27 +147,27 @@ export function explainIssue(issue) {
147
147
  function formatAiExplainHuman(response, verbose, issues) {
148
148
  const lines = [];
149
149
  // 3-bullet summary
150
- lines.push(chalk.bold("Summary"));
150
+ lines.push(bold("Summary"));
151
151
  for (const bullet of response.summary) {
152
- lines.push(` ${chalk.cyan(">")} ${bullet}`);
152
+ lines.push(` ${colorize(">", brand.cyan)} ${bullet}`);
153
153
  }
154
154
  lines.push("");
155
155
  // Issues grouped by severity
156
- lines.push(chalk.bold("Issues"));
156
+ lines.push(bold("Issues"));
157
157
  lines.push("");
158
158
  for (const issue of response.issues) {
159
159
  const severity = coloredSeverity(issue.severity);
160
- lines.push(` ${severity} ${chalk.bold(issue.title)}${issue.id ? chalk.dim(` (${issue.id})`) : ""}`);
160
+ lines.push(` ${severity} ${bold(issue.title)}${issue.id ? dim(` (${issue.id})`) : ""}`);
161
161
  lines.push(` ${issue.explanation}`);
162
- lines.push(` ${chalk.green("Fix:")} ${issue.fix}`);
162
+ lines.push(` ${colorize("Fix:", brand.lime)} ${issue.fix}`);
163
163
  if (verbose && issues) {
164
164
  // Find the matching original issue for full evidence
165
165
  const original = issues.find((i) => getRuleId(i) === issue.id || i.id === issue.id);
166
166
  if (original) {
167
167
  if (original.selector)
168
- lines.push(` ${chalk.dim("Selector:")} ${original.selector}`);
168
+ lines.push(` ${dim("Selector:")} ${original.selector}`);
169
169
  if (original.wcag_reference)
170
- lines.push(` ${chalk.dim("WCAG:")} ${original.wcag_reference}`);
170
+ lines.push(` ${dim("WCAG:")} ${original.wcag_reference}`);
171
171
  }
172
172
  }
173
173
  lines.push("");
@@ -177,13 +177,12 @@ function formatAiExplainHuman(response, verbose, issues) {
177
177
  // ---------------------------------------------------------------------------
178
178
  // Evidence mode handler (backward compat)
179
179
  // ---------------------------------------------------------------------------
180
- async function handleEvidenceMode(findingId, options, config, format) {
180
+ async function resolveEvidenceIssue(findingId, options, config) {
181
181
  let issue = null;
182
182
  if (options.file) {
183
183
  const filePath = path.resolve(process.cwd(), options.file);
184
184
  if (!fs.existsSync(filePath)) {
185
- console.error(`Error: File not found: ${filePath}`);
186
- process.exit(ExitCode.ERROR);
185
+ throw new Error(strings.explain.errors.fileNotFound(filePath));
187
186
  }
188
187
  const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
189
188
  let issues;
@@ -206,9 +205,8 @@ async function handleEvidenceMode(findingId, options, config, format) {
206
205
  }
207
206
  }
208
207
  else if (options.job) {
209
- const base = resolveApiBase(options.base);
210
- const apiKey = getApiKey(config.apiKey);
211
- const result = await apiRequest(base, `/audit/${options.job}`, { method: "GET" }, apiKey);
208
+ const sdkClient = createClient({ base: options.base, apiKey: config.apiKey });
209
+ const result = await sdkClient.audits.retrieve(options.job);
212
210
  const issues = normalizeIssues(result.issues);
213
211
  issue = issues.find((i) => i.id === findingId ||
214
212
  getRuleId(i) === findingId ||
@@ -218,14 +216,12 @@ async function handleEvidenceMode(findingId, options, config, format) {
218
216
  else {
219
217
  const recent = loadRecentAudits();
220
218
  if (recent.length === 0) {
221
- console.error("Error: No recent audits found. Provide --job or --file option.");
222
- process.exit(ExitCode.ERROR);
219
+ throw new Error(strings.explain.errors.noRecentAudits);
223
220
  }
224
- const base = resolveApiBase(options.base);
225
- const apiKey = getApiKey(config.apiKey);
221
+ const sdkClient = createClient({ base: options.base, apiKey: config.apiKey });
226
222
  for (const audit of recent) {
227
223
  try {
228
- const result = await apiRequest(base, `/audit/${audit.jobId}`, { method: "GET" }, apiKey);
224
+ const result = await sdkClient.audits.retrieve(audit.jobId);
229
225
  const issues = normalizeIssues(result.issues);
230
226
  issue = issues.find((i) => i.id === findingId ||
231
227
  getRuleId(i) === findingId ||
@@ -240,123 +236,208 @@ async function handleEvidenceMode(findingId, options, config, format) {
240
236
  }
241
237
  }
242
238
  if (!issue) {
243
- console.error(`Error: Finding "${findingId}" not found.`);
244
- if (options.job)
245
- console.error(`Looked in job: ${options.job}`);
246
- else if (options.file)
247
- console.error(`Looked in file: ${options.file}`);
248
- else
249
- console.error("Try specifying --job or --file to narrow the search.");
250
- process.exit(ExitCode.ERROR);
251
- }
252
- if (format === "json") {
253
- writeJsonOutput({
254
- ruleId: getRuleId(issue),
255
- severity: issue.severity,
256
- category: issue.category,
257
- description: issue.description || issue.title,
258
- selector: issue.selector,
259
- wcagReference: issue.wcag_reference,
260
- recommendation: issue.recommendation || issue.recommended_fix,
261
- evidence: {
262
- screenshot: issue.screenshot,
263
- html: issue.html || issue.element,
264
- helpUrl: issue.helpUrl,
265
- },
266
- }, "explain");
267
- }
268
- else {
269
- writeOutput(formatEvidenceBundle(issue));
239
+ throw new Error(strings.explain.errors.findingNotFound(findingId, options.job || options.file));
270
240
  }
241
+ return issue;
271
242
  }
272
- // ---------------------------------------------------------------------------
273
- // AI mode handler
274
- // ---------------------------------------------------------------------------
275
- async function handleAiMode(options, config, format, verbose) {
276
- // Read audit JSON from stdin, --file, or --job
277
- let auditData = null;
278
- let rawIssues = [];
279
- if (options.job) {
280
- // Fetch full audit from API
281
- const base = resolveApiBase(options.base);
282
- const apiKey = getApiKey(config.apiKey);
283
- const result = await apiRequest(base, `/audit/${options.job}`, { method: "GET" }, apiKey);
284
- rawIssues = normalizeIssues(result.issues);
285
- auditData = {
286
- job_id: result.job_id || options.job,
287
- url: result.url || null,
288
- scores: result.scores || null,
289
- issues: rawIssues.map((i) => ({
290
- id: i.id || getRuleId(i),
291
- title: i.title || i.description || null,
292
- description: i.description || null,
293
- severity: i.severity || null,
294
- category: i.category || null,
295
- selector: i.selector || null,
296
- wcag_reference: i.wcag_reference || null,
297
- recommendation: i.recommendation || i.recommended_fix || null,
298
- })),
299
- };
300
- }
301
- else {
302
- // Read from stdin or --file
303
- const input = await readJsonInput(options.file);
304
- if (!input) {
305
- console.error("Error: No audit data provided.");
306
- console.error("Usage:");
307
- console.error(" vertaa audit https://example.com --json | vertaa explain");
308
- console.error(" vertaa explain --file audit.json");
309
- console.error(" vertaa explain --job <job-id>");
310
- console.error(" vertaa explain <finding-id> --job <job-id>");
311
- process.exit(ExitCode.ERROR);
243
+ export async function handleExplain(opts) {
244
+ const config = { apiKey: opts.apiKey };
245
+ const format = resolveCommandFormat("explain", opts.format, opts.machine || false);
246
+ const verbose = opts.verbose || false;
247
+ const renderer = createRenderer("auto");
248
+ const baseState = {
249
+ phase: "explain",
250
+ phaseIndex: 1,
251
+ phaseTotal: 2,
252
+ url: "",
253
+ mode: "explain",
254
+ progress: {},
255
+ totals: {},
256
+ issueCount: 0,
257
+ scorePreview: null,
258
+ verbose: false,
259
+ elapsed: 0,
260
+ };
261
+ const startTime = Date.now();
262
+ if (opts.findingId) {
263
+ // Evidence mode — fetch and display a specific finding
264
+ let foundIssue = null;
265
+ const steps = [
266
+ {
267
+ id: "fetch",
268
+ actionText: "Fetching audit finding...",
269
+ summaryText: "Finding loaded",
270
+ run: async () => {
271
+ foundIssue = await resolveEvidenceIssue(opts.findingId, { job: opts.job, file: opts.file, base: opts.base }, config);
272
+ },
273
+ },
274
+ {
275
+ id: "explain",
276
+ actionText: "Generating explanation...",
277
+ summaryText: "Explanation generated",
278
+ run: async () => {
279
+ // Explanation is the formatted evidence bundle; output after steps
280
+ },
281
+ },
282
+ ];
283
+ const { success, states } = await runSteps(steps, {
284
+ onStateChange: (stepStates) => {
285
+ renderer.update({ ...baseState, stepStates, elapsed: Date.now() - startTime });
286
+ },
287
+ });
288
+ renderer.finish({
289
+ url: "",
290
+ mode: "explain",
291
+ overallScore: success ? 100 : 0,
292
+ scores: {},
293
+ issueCount: 0,
294
+ passed: success,
295
+ elapsed: Date.now() - startTime,
296
+ });
297
+ if (!success) {
298
+ const failed = states.find(s => s.status === "failed");
299
+ process.stderr.write(renderError({
300
+ message: failed?.failReason || "Command failed",
301
+ suggestion: "vertaa audit <url>",
302
+ }) + "\n");
303
+ process.exitCode = ExitCode.ERROR;
304
+ }
305
+ if (success && foundIssue) {
306
+ const issue = foundIssue;
307
+ if (format === "json") {
308
+ writeJsonOutput({
309
+ ruleId: getRuleId(issue),
310
+ severity: issue.severity,
311
+ category: issue.category,
312
+ description: issue.description || issue.title,
313
+ selector: issue.selector,
314
+ wcagReference: issue.wcag_reference,
315
+ recommendation: issue.recommendation || issue.recommended_fix,
316
+ evidence: {
317
+ screenshot: issue.screenshot,
318
+ html: issue.html || issue.element,
319
+ helpUrl: issue.helpUrl,
320
+ },
321
+ }, "explain");
322
+ }
323
+ else {
324
+ writeOutput(formatEvidenceBundle(issue));
325
+ }
312
326
  }
313
- const data = input;
314
- // Handle the envelope format { meta: {...}, data: {...} }
315
- const innerData = (data.data && typeof data.data === "object" ? data.data : data);
316
- rawIssues = normalizeIssues(innerData.issues);
317
- auditData = {
318
- job_id: innerData.job_id || null,
319
- url: innerData.url || null,
320
- scores: innerData.scores || null,
321
- issues: rawIssues.map((i) => ({
322
- id: i.id || getRuleId(i),
323
- title: i.title || i.description || null,
324
- description: i.description || null,
325
- severity: i.severity || null,
326
- category: i.category || null,
327
- selector: i.selector || null,
328
- wcag_reference: i.wcag_reference || null,
329
- recommendation: i.recommendation || i.recommended_fix || null,
330
- })),
331
- };
332
- }
333
- if (!auditData ||
334
- !Array.isArray(auditData.issues) ||
335
- auditData.issues.length === 0) {
336
- console.error("Error: No issues found in audit data.");
337
- process.exit(ExitCode.ERROR);
338
327
  }
339
- // Call the LLM explain API
340
- const base = resolveApiBase(options.base);
341
- const apiKey = getApiKey(config.apiKey);
342
- const spinner = createSpinner("Analyzing findings...");
343
- try {
344
- const response = await Promise.race([
345
- apiRequest(base, "/cli/ai/explain", { method: "POST", body: { audit: auditData } }, apiKey),
346
- new Promise((_, reject) => setTimeout(() => reject(new Error("LLM request timed out")), AI_TIMEOUT_MS)),
347
- ]);
348
- succeedSpinner(spinner, "Analysis complete");
349
- if (format === "json") {
350
- writeJsonOutput(response.data, "explain");
328
+ else {
329
+ // AI mode — explain all issues in an audit
330
+ let responseData = null;
331
+ let rawIssues = [];
332
+ const steps = [
333
+ {
334
+ id: "fetch",
335
+ actionText: strings.explain.run.action,
336
+ summaryText: "Finding loaded",
337
+ run: async () => {
338
+ let auditData = null;
339
+ if (opts.job) {
340
+ const sdkClient = createClient({ base: opts.base, apiKey: config.apiKey });
341
+ const result = await sdkClient.audits.retrieve(opts.job);
342
+ rawIssues = normalizeIssues(result.issues);
343
+ auditData = {
344
+ job_id: result.job_id || opts.job,
345
+ url: result.url || null,
346
+ scores: result.scores || null,
347
+ issues: rawIssues.map((i) => ({
348
+ id: i.id || getRuleId(i),
349
+ title: i.title || i.description || null,
350
+ description: i.description || null,
351
+ severity: i.severity || null,
352
+ category: i.category || null,
353
+ selector: i.selector || null,
354
+ wcag_reference: i.wcag_reference || null,
355
+ recommendation: i.recommendation || i.recommended_fix || null,
356
+ })),
357
+ };
358
+ }
359
+ else {
360
+ const input = await readJsonInput(opts.file);
361
+ if (!input) {
362
+ throw new Error(strings.explain.errors.noAuditData);
363
+ }
364
+ const data = input;
365
+ const innerData = (data.data && typeof data.data === "object" ? data.data : data);
366
+ rawIssues = normalizeIssues(innerData.issues);
367
+ auditData = {
368
+ job_id: innerData.job_id || null,
369
+ url: innerData.url || null,
370
+ scores: innerData.scores || null,
371
+ issues: rawIssues.map((i) => ({
372
+ id: i.id || getRuleId(i),
373
+ title: i.title || i.description || null,
374
+ description: i.description || null,
375
+ severity: i.severity || null,
376
+ category: i.category || null,
377
+ selector: i.selector || null,
378
+ wcag_reference: i.wcag_reference || null,
379
+ recommendation: i.recommendation || i.recommended_fix || null,
380
+ })),
381
+ };
382
+ }
383
+ if (!auditData ||
384
+ !Array.isArray(auditData.issues) ||
385
+ auditData.issues.length === 0) {
386
+ throw new Error(strings.explain.errors.noIssuesFound);
387
+ }
388
+ const base = resolveApiBase(opts.base);
389
+ const apiKey = getApiKey(config.apiKey);
390
+ const response = await Promise.race([
391
+ apiRequest(base, "/cli/ai/explain", { method: "POST", body: { audit: auditData } }, apiKey),
392
+ new Promise((_, reject) => setTimeout(() => reject(new Error("LLM request timed out")), AI_TIMEOUT_MS)),
393
+ ]);
394
+ responseData = response.data;
395
+ },
396
+ },
397
+ {
398
+ id: "explain",
399
+ actionText: "Generating explanation...",
400
+ summaryText: responseData ? strings.explain.run.done(responseData.issues.length) : "Explanation generated",
401
+ run: async () => {
402
+ // Explanation data gathered in fetch step; output after steps
403
+ },
404
+ },
405
+ ];
406
+ const { success, states } = await runSteps(steps, {
407
+ onStateChange: (stepStates) => {
408
+ renderer.update({ ...baseState, stepStates, elapsed: Date.now() - startTime });
409
+ },
410
+ });
411
+ renderer.finish({
412
+ url: "",
413
+ mode: "explain",
414
+ overallScore: success ? 100 : 0,
415
+ scores: {},
416
+ issueCount: 0,
417
+ passed: success,
418
+ elapsed: Date.now() - startTime,
419
+ });
420
+ if (!success) {
421
+ const failed = states.find(s => s.status === "failed");
422
+ process.stderr.write(renderError({
423
+ message: failed?.failReason || "Command failed",
424
+ suggestion: "vertaa audit <url>",
425
+ }) + "\n");
426
+ process.exitCode = ExitCode.ERROR;
351
427
  }
352
- else {
353
- writeOutput(formatAiExplainHuman(response.data, verbose, rawIssues));
428
+ if (success && responseData) {
429
+ if (format === "json") {
430
+ writeJsonOutput(responseData, "explain");
431
+ }
432
+ else {
433
+ writeOutput(formatAiExplainHuman(responseData, verbose, rawIssues));
434
+ }
354
435
  }
355
436
  }
356
- catch (error) {
357
- handleAiCommandError(error, "explain", spinner);
358
- }
359
437
  }
438
+ // ---------------------------------------------------------------------------
439
+ // Command Registration
440
+ // ---------------------------------------------------------------------------
360
441
  export function registerExplainCommand(program) {
361
442
  program
362
443
  .command("explain [finding-id]")
@@ -382,18 +463,23 @@ Modes:
382
463
  const config = await resolveConfig(globalOpts.config);
383
464
  const machineMode = globalOpts.machine || false;
384
465
  const verbose = globalOpts.verbose || false;
385
- const format = resolveCommandFormat("explain", options.format, machineMode);
386
- if (findingId) {
387
- // Backward-compatible evidence mode
388
- await handleEvidenceMode(findingId, options, config, format);
389
- }
390
- else {
391
- // AI-powered explain mode
392
- await handleAiMode(options, config, format, verbose);
393
- }
466
+ await handleExplain({
467
+ findingId,
468
+ job: options.job,
469
+ file: options.file,
470
+ format: options.format,
471
+ base: globalOpts.base,
472
+ verbose,
473
+ machine: machineMode,
474
+ apiKey: config.apiKey,
475
+ });
394
476
  }
395
477
  catch (error) {
396
- console.error("Error:", error instanceof Error ? error.message : String(error));
478
+ process.stderr.write(renderError({
479
+ message: error instanceof Error ? error.message : String(error),
480
+ suggestion: "vertaa audit <url>",
481
+ exitCode: ExitCode.ERROR,
482
+ }) + "\n");
397
483
  process.exit(ExitCode.ERROR);
398
484
  }
399
485
  });
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Legacy fix-all command handler.
3
+ * Generates fix patches for all issues in an audit.
4
+ */
5
+ import type { Command } from "commander";
6
+ import type { Flags } from "../types.js";
7
+ export interface FixAllCommandOptions {
8
+ base?: string;
9
+ jobId: string;
10
+ fileContent: string;
11
+ autoFixOnly?: boolean;
12
+ format?: string;
13
+ dryRun?: boolean;
14
+ yes?: boolean;
15
+ }
16
+ export declare function handleFixAll(opts: FixAllCommandOptions): Promise<void>;
17
+ /**
18
+ * Shared fix-all runner used by the fix-all command (legacy wrapper for backward compat).
19
+ */
20
+ export declare function runFixAllCommand(base: string, jobId: string, flags: Flags, globalFlags?: {
21
+ dryRun?: boolean;
22
+ yes?: boolean;
23
+ }): Promise<void>;
24
+ export declare function registerFixAllCommand(program: Command): void;
25
+ //# sourceMappingURL=fix-all.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fix-all.d.ts","sourceRoot":"","sources":["../../src/commands/fix-all.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAezC,OAAO,KAAK,EAAE,KAAK,EAAkD,MAAM,aAAa,CAAC;AAUzF,MAAM,WAAW,oBAAoB;IACnC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CA4K5E;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,KAAK,EACZ,WAAW,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,OAAO,CAAA;CAAO,iBAmBtD;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAsB5D"}