@vertaaux/cli 0.4.0 → 0.5.1

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 (248) hide show
  1. package/CHANGELOG.md +116 -0
  2. package/MIGRATION.md +239 -0
  3. package/README.md +62 -17
  4. package/dist/app/interactive-app.d.ts +103 -0
  5. package/dist/app/interactive-app.d.ts.map +1 -0
  6. package/dist/app/interactive-app.js +328 -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 +166 -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 +415 -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 +46 -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 +11 -0
  49. package/dist/commands/a11y.d.ts.map +1 -0
  50. package/dist/commands/a11y.js +149 -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 +589 -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 +129 -0
  69. package/dist/commands/audit/policy.d.ts +27 -0
  70. package/dist/commands/audit/policy.d.ts.map +1 -0
  71. package/dist/commands/audit/policy.js +147 -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 +89 -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 +2 -0
  82. package/dist/commands/baseline.d.ts.map +1 -1
  83. package/dist/commands/baseline.js +221 -123
  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 +127 -62
  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 +288 -181
  90. package/dist/commands/diff.d.ts +7 -0
  91. package/dist/commands/diff.d.ts.map +1 -1
  92. package/dist/commands/diff.js +181 -143
  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 +135 -77
  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 +166 -19
  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 +242 -156
  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 +154 -90
  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 +160 -98
  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 +270 -125
  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 +128 -74
  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 +180 -83
  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 +207 -82
  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/config/schema.d.ts +4 -0
  148. package/dist/config/schema.d.ts.map +1 -1
  149. package/dist/index.d.ts +3 -2
  150. package/dist/index.d.ts.map +1 -1
  151. package/dist/index.js +127 -991
  152. package/dist/interactive/fix-wizard.d.ts +3 -0
  153. package/dist/interactive/fix-wizard.d.ts.map +1 -1
  154. package/dist/interactive/fix-wizard.js +130 -112
  155. package/dist/interactive/init-wizard.d.ts +3 -1
  156. package/dist/interactive/init-wizard.d.ts.map +1 -1
  157. package/dist/interactive/init-wizard.js +207 -138
  158. package/dist/interactive/prompts.d.ts +7 -3
  159. package/dist/interactive/prompts.d.ts.map +1 -1
  160. package/dist/interactive/prompts.js +44 -23
  161. package/dist/output/envelope.d.ts +9 -0
  162. package/dist/output/envelope.d.ts.map +1 -1
  163. package/dist/output/envelope.js +37 -3
  164. package/dist/output/factory.d.ts +2 -1
  165. package/dist/output/factory.d.ts.map +1 -1
  166. package/dist/output/html.d.ts +2 -1
  167. package/dist/output/html.d.ts.map +1 -1
  168. package/dist/output/html.js +3 -2
  169. package/dist/output/human.d.ts +2 -1
  170. package/dist/output/human.d.ts.map +1 -1
  171. package/dist/output/human.js +3 -2
  172. package/dist/output/json.d.ts +2 -1
  173. package/dist/output/json.d.ts.map +1 -1
  174. package/dist/output/junit.d.ts +2 -1
  175. package/dist/output/junit.d.ts.map +1 -1
  176. package/dist/output/sarif.d.ts +2 -1
  177. package/dist/output/sarif.d.ts.map +1 -1
  178. package/dist/policy/schema.d.ts +137 -0
  179. package/dist/policy/schema.d.ts.map +1 -1
  180. package/dist/policy/schema.js +107 -0
  181. package/dist/prompts/command-catalog.js +9 -9
  182. package/dist/types.d.ts +74 -0
  183. package/dist/types.d.ts.map +1 -0
  184. package/dist/types.js +5 -0
  185. package/dist/ui/banner.d.ts +34 -0
  186. package/dist/ui/banner.d.ts.map +1 -1
  187. package/dist/ui/banner.js +97 -5
  188. package/dist/ui/diagnostics.d.ts +9 -4
  189. package/dist/ui/diagnostics.d.ts.map +1 -1
  190. package/dist/ui/diagnostics.js +32 -82
  191. package/dist/ui/strings.d.ts +373 -0
  192. package/dist/ui/strings.d.ts.map +1 -0
  193. package/dist/ui/strings.js +499 -0
  194. package/dist/ui/table.d.ts +0 -2
  195. package/dist/ui/table.d.ts.map +1 -1
  196. package/dist/ui/table.js +3 -4
  197. package/dist/utils/api-client.d.ts +46 -0
  198. package/dist/utils/api-client.d.ts.map +1 -0
  199. package/dist/utils/api-client.js +170 -0
  200. package/dist/utils/client.d.ts +29 -18
  201. package/dist/utils/client.d.ts.map +1 -1
  202. package/dist/utils/client.js +104 -12
  203. package/dist/utils/formatters.d.ts +38 -0
  204. package/dist/utils/formatters.d.ts.map +1 -0
  205. package/dist/utils/formatters.js +277 -0
  206. package/dist/utils/root-args.d.ts +12 -0
  207. package/dist/utils/root-args.d.ts.map +1 -0
  208. package/dist/utils/root-args.js +44 -0
  209. package/dist/utils/stdin.d.ts +7 -0
  210. package/dist/utils/stdin.d.ts.map +1 -1
  211. package/dist/utils/stdin.js +32 -2
  212. package/dist/utils/url-classify.d.ts.map +1 -1
  213. package/dist/utils/url-classify.js +24 -3
  214. package/node_modules/@vertaaux/tui/dist/index.cjs +1216 -27
  215. package/node_modules/@vertaaux/tui/dist/index.cjs.map +1 -1
  216. package/node_modules/@vertaaux/tui/dist/index.d.cts +361 -4
  217. package/node_modules/@vertaaux/tui/dist/index.d.ts +361 -4
  218. package/node_modules/@vertaaux/tui/dist/index.js +1189 -27
  219. package/node_modules/@vertaaux/tui/dist/index.js.map +1 -1
  220. package/node_modules/@vertaaux/tui/package.json +2 -3
  221. package/node_modules/chalk/license +9 -0
  222. package/node_modules/chalk/package.json +83 -0
  223. package/node_modules/chalk/readme.md +297 -0
  224. package/node_modules/chalk/source/index.d.ts +325 -0
  225. package/node_modules/chalk/source/index.js +225 -0
  226. package/node_modules/chalk/source/utilities.js +33 -0
  227. package/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +236 -0
  228. package/node_modules/chalk/source/vendor/ansi-styles/index.js +223 -0
  229. package/node_modules/chalk/source/vendor/supports-color/browser.d.ts +1 -0
  230. package/node_modules/chalk/source/vendor/supports-color/browser.js +34 -0
  231. package/node_modules/chalk/source/vendor/supports-color/index.d.ts +55 -0
  232. package/node_modules/chalk/source/vendor/supports-color/index.js +190 -0
  233. package/package.json +20 -5
  234. package/dist/commands/client.d.ts +0 -14
  235. package/dist/commands/client.d.ts.map +0 -1
  236. package/dist/commands/client.js +0 -362
  237. package/dist/commands/drift.d.ts +0 -15
  238. package/dist/commands/drift.d.ts.map +0 -1
  239. package/dist/commands/drift.js +0 -309
  240. package/dist/commands/protect.d.ts +0 -16
  241. package/dist/commands/protect.d.ts.map +0 -1
  242. package/dist/commands/protect.js +0 -323
  243. package/dist/commands/report.d.ts +0 -15
  244. package/dist/commands/report.d.ts.map +0 -1
  245. package/dist/commands/report.js +0 -214
  246. package/dist/policy/sync.d.ts +0 -67
  247. package/dist/policy/sync.d.ts.map +0 -1
  248. package/dist/policy/sync.js +0 -147
@@ -15,15 +15,15 @@
15
15
  * vertaa compare --before baseline.json --after current.json
16
16
  * vertaa compare --before baseline.json --after current.json --verbose
17
17
  */
18
- import chalk from "chalk";
18
+ import { bold, dim, colorize, boldColor, scoreColor, brand, severity as severityPalette, renderError, createRenderer, runSteps } from "@vertaaux/tui";
19
19
  import { ExitCode } from "../utils/exit-codes.js";
20
- import { resolveApiBase, getApiKey, apiRequest, waitForAudit, } from "../utils/client.js";
20
+ import { resolveApiBase, getApiKey, apiRequest, waitForAudit, createClient, } from "../utils/client.js";
21
21
  import { resolveConfig } from "../config/loader.js";
22
- import { writeJsonOutput, writeOutput } from "../output/envelope.js";
22
+ import { writeDataOutput, writeJsonOutput } from "../output/envelope.js";
23
23
  import { resolveCommandFormat } from "../output/formats.js";
24
- import { createSpinner, succeedSpinner, failSpinner } from "../ui/spinner.js";
25
24
  import { readJsonInput } from "../utils/stdin.js";
26
- import { handleAiCommandError, AI_TIMEOUT_MS } from "../utils/ai-error.js";
25
+ import { AI_TIMEOUT_MS } from "../utils/ai-error.js";
26
+ import { strings } from "../ui/strings.js";
27
27
  // ---------------------------------------------------------------------------
28
28
  // Helpers
29
29
  // ---------------------------------------------------------------------------
@@ -60,10 +60,11 @@ function toNumber(value) {
60
60
  function getOverallScore(scores) {
61
61
  if (!scores)
62
62
  return null;
63
- const direct = toNumber(scores.overall ?? scores.ux ?? scores.total);
63
+ const s = scores;
64
+ const direct = toNumber(s.overall ?? s.ux ?? s.total);
64
65
  if (direct !== null)
65
66
  return direct;
66
- const numeric = Object.values(scores)
67
+ const numeric = Object.values(s)
67
68
  .map((v) => toNumber(v))
68
69
  .filter((v) => v !== null);
69
70
  if (numeric.length === 0)
@@ -71,45 +72,38 @@ function getOverallScore(scores) {
71
72
  const avg = numeric.reduce((sum, v) => sum + v, 0) / numeric.length;
72
73
  return Math.round(avg);
73
74
  }
74
- function buildAuditPayload(result, fallbackJobId) {
75
- const issues = normalizeIssues(result.issues);
76
- return {
77
- job_id: result.job_id || fallbackJobId || null,
78
- url: result.url || null,
79
- scores: result.scores || null,
80
- issues,
81
- };
82
- }
83
75
  // ---------------------------------------------------------------------------
84
76
  // Formatters — Legacy URL comparison
85
77
  // ---------------------------------------------------------------------------
86
78
  function formatLegacyCompareHuman(data) {
87
79
  const lines = [];
88
- lines.push(chalk.bold("Audit Comparison"));
89
- lines.push(` URL A: ${chalk.cyan(data.urlA)}`);
90
- lines.push(` URL B: ${chalk.cyan(data.urlB)}`);
80
+ lines.push(bold("Audit Comparison"));
81
+ lines.push(` URL A: ${colorize(data.urlA, brand.cyan)}`);
82
+ lines.push(` URL B: ${colorize(data.urlB, brand.cyan)}`);
91
83
  lines.push("");
92
84
  // Overall scores
93
85
  const deltaStr = data.delta !== null
94
- ? (data.delta >= 0 ? chalk.green(`+${data.delta}`) : chalk.red(`${data.delta}`))
95
- : chalk.dim("n/a");
96
- lines.push(chalk.bold("Overall Scores"));
97
- lines.push(` A: ${data.overallA ?? "n/a"} B: ${data.overallB ?? "n/a"} Delta: ${deltaStr}`);
86
+ ? (data.delta >= 0 ? colorize(`+${data.delta}`, brand.lime) : colorize(`${data.delta}`, severityPalette.error))
87
+ : dim("n/a");
88
+ lines.push(bold("Overall Scores"));
89
+ const scoreA = data.overallA !== null ? boldColor(String(data.overallA), scoreColor(data.overallA)) : dim("n/a");
90
+ const scoreB = data.overallB !== null ? boldColor(String(data.overallB), scoreColor(data.overallB)) : dim("n/a");
91
+ lines.push(` A: ${scoreA} B: ${scoreB} Delta: ${deltaStr}`);
98
92
  lines.push("");
99
93
  // Category deltas
100
94
  const entries = Object.entries(data.categoryDeltas);
101
95
  if (entries.length > 0) {
102
- lines.push(chalk.bold("Category Scores"));
96
+ lines.push(bold("Category Scores"));
103
97
  for (const [key, vals] of entries) {
104
98
  const d = vals.delta !== null
105
- ? (vals.delta >= 0 ? chalk.green(`+${vals.delta}`) : chalk.red(`${vals.delta}`))
106
- : chalk.dim("n/a");
99
+ ? (vals.delta >= 0 ? colorize(`+${vals.delta}`, brand.lime) : colorize(`${vals.delta}`, severityPalette.error))
100
+ : dim("n/a");
107
101
  lines.push(` ${key}: ${vals.a ?? "n/a"} → ${vals.b ?? "n/a"} (${d})`);
108
102
  }
109
103
  lines.push("");
110
104
  }
111
105
  // Issue counts
112
- lines.push(chalk.bold("Issue Counts"));
106
+ lines.push(bold("Issue Counts"));
113
107
  lines.push(` A: ${data.issuesA} B: ${data.issuesB}`);
114
108
  return lines.join("\n");
115
109
  }
@@ -118,43 +112,280 @@ function formatLegacyCompareHuman(data) {
118
112
  // ---------------------------------------------------------------------------
119
113
  function formatLlmCompareHuman(data, verbose) {
120
114
  const lines = [];
121
- lines.push(chalk.bold(data.headline));
115
+ lines.push(bold(data.headline));
122
116
  lines.push("");
123
117
  // Score delta
124
118
  const d = data.score_delta.overall;
125
- const deltaStr = d >= 0 ? chalk.green(`+${d}`) : chalk.red(`${d}`);
119
+ const deltaStr = d >= 0 ? colorize(`+${d}`, brand.lime) : colorize(`${d}`, severityPalette.error);
126
120
  lines.push(`Overall delta: ${deltaStr}`);
127
121
  if (data.score_delta.categories && verbose) {
128
122
  for (const [cat, val] of Object.entries(data.score_delta.categories)) {
129
- const catDelta = val >= 0 ? chalk.green(`+${val}`) : chalk.red(`${val}`);
123
+ const catDelta = val >= 0 ? colorize(`+${val}`, brand.lime) : colorize(`${val}`, severityPalette.error);
130
124
  lines.push(` ${cat}: ${catDelta}`);
131
125
  }
132
126
  }
133
127
  lines.push("");
134
128
  // Improvements
135
129
  if (data.improvements.length > 0) {
136
- lines.push(chalk.green.bold(`Improvements (${data.improvements.length})`));
130
+ lines.push(boldColor(`Improvements (${data.improvements.length})`, brand.lime));
137
131
  for (const item of data.improvements) {
138
- lines.push(` ${chalk.green("+")} ${item}`);
132
+ lines.push(` ${colorize("+", brand.lime)} ${item}`);
139
133
  }
140
134
  lines.push("");
141
135
  }
142
136
  // Regressions
143
137
  if (data.regressions.length > 0) {
144
- lines.push(chalk.red.bold(`Regressions (${data.regressions.length})`));
138
+ lines.push(boldColor(`Regressions (${data.regressions.length})`, severityPalette.error));
145
139
  for (const item of data.regressions) {
146
- lines.push(` ${chalk.red("-")} ${item}`);
140
+ lines.push(` ${colorize("-", severityPalette.error)} ${item}`);
147
141
  }
148
142
  lines.push("");
149
143
  }
150
- lines.push(chalk.dim(`Unchanged: ${data.unchanged}`));
144
+ lines.push(dim(`Unchanged: ${data.unchanged}`));
151
145
  if (verbose) {
152
146
  lines.push("");
153
- lines.push(chalk.bold("Analysis"));
147
+ lines.push(bold("Analysis"));
154
148
  lines.push(data.narrative);
155
149
  }
156
150
  return lines.join("\n");
157
151
  }
152
+ export async function handleCompare(opts) {
153
+ const config = { apiKey: opts.apiKey };
154
+ const format = resolveCommandFormat("compare", opts.format, opts.machine || false);
155
+ const verbose = opts.verbose || false;
156
+ const isFileMode = opts.before || opts.after;
157
+ const renderer = createRenderer("auto");
158
+ const baseState = {
159
+ phase: "compare",
160
+ phaseIndex: 1,
161
+ phaseTotal: isFileMode ? 1 : 3,
162
+ url: opts.urlA || "",
163
+ mode: "compare",
164
+ progress: {},
165
+ totals: {},
166
+ issueCount: 0,
167
+ scorePreview: null,
168
+ verbose: false,
169
+ elapsed: 0,
170
+ };
171
+ const startTime = Date.now();
172
+ if (isFileMode) {
173
+ // File-based LLM comparison
174
+ let responseData = null;
175
+ const steps = [
176
+ {
177
+ id: "fetch-a",
178
+ actionText: "Loading audit files...",
179
+ summaryText: "Audit files loaded",
180
+ run: async () => {
181
+ if (!opts.before || !opts.after) {
182
+ throw new Error(strings.compare.errors.beforeAfterRequired);
183
+ }
184
+ const beforeInput = await readJsonInput(opts.before);
185
+ const afterInput = await readJsonInput(opts.after);
186
+ if (!beforeInput || !afterInput) {
187
+ throw new Error(strings.compare.errors.readFailed);
188
+ }
189
+ function extractPayload(input) {
190
+ const data = input;
191
+ const inner = (data.data && typeof data.data === "object" ? data.data : data);
192
+ const issues = normalizeIssues(inner.issues);
193
+ return {
194
+ job_id: inner.job_id || null,
195
+ url: inner.url || null,
196
+ scores: inner.scores || null,
197
+ issues,
198
+ };
199
+ }
200
+ const beforePayload = extractPayload(beforeInput);
201
+ const afterPayload = extractPayload(afterInput);
202
+ const base = resolveApiBase(opts.base);
203
+ const apiKey = getApiKey(config.apiKey);
204
+ const response = await Promise.race([
205
+ apiRequest(base, "/cli/ai/compare", { method: "POST", body: { before: beforePayload, after: afterPayload } }, apiKey),
206
+ new Promise((_, reject) => setTimeout(() => reject(new Error("LLM request timed out")), AI_TIMEOUT_MS)),
207
+ ]);
208
+ responseData = response.data;
209
+ },
210
+ },
211
+ {
212
+ id: "compare",
213
+ actionText: strings.compare.analyze.action,
214
+ summaryText: strings.compare.analyze.done(),
215
+ run: async () => {
216
+ // Analysis was performed in fetch-a step; this step finalizes output
217
+ },
218
+ },
219
+ ];
220
+ const { success, states } = await runSteps(steps, {
221
+ onStateChange: (stepStates) => {
222
+ renderer.update({ ...baseState, stepStates, elapsed: Date.now() - startTime });
223
+ },
224
+ });
225
+ renderer.finish({
226
+ url: opts.urlA || "",
227
+ mode: "compare",
228
+ overallScore: success ? 100 : 0,
229
+ scores: {},
230
+ issueCount: 0,
231
+ passed: success,
232
+ elapsed: Date.now() - startTime,
233
+ });
234
+ if (!success) {
235
+ const failed = states.find(s => s.status === "failed");
236
+ process.stderr.write(renderError({
237
+ message: failed?.failReason || "Command failed",
238
+ suggestion: "vertaa doctor",
239
+ }) + "\n");
240
+ process.exitCode = ExitCode.ERROR;
241
+ }
242
+ if (success && responseData) {
243
+ if (format === "json") {
244
+ writeJsonOutput(responseData, "compare");
245
+ }
246
+ else {
247
+ writeDataOutput(formatLlmCompareHuman(responseData, verbose));
248
+ }
249
+ }
250
+ }
251
+ else if (opts.urlA && opts.urlB) {
252
+ const urlA = opts.urlA;
253
+ const urlB = opts.urlB;
254
+ let compareOutput = null;
255
+ let compareJsonPayload = null;
256
+ let jobARef = null;
257
+ let jobBRef = null;
258
+ const steps = [
259
+ {
260
+ id: "fetch-a",
261
+ actionText: strings.compare.auditA.action,
262
+ summaryText: strings.compare.auditA.done(),
263
+ run: async () => {
264
+ const sdkClient = createClient({ base: opts.base, apiKey: config.apiKey });
265
+ const mode = opts.mode || "basic";
266
+ jobARef = await sdkClient.audits.create({ url: urlA, mode: mode });
267
+ },
268
+ },
269
+ {
270
+ id: "fetch-b",
271
+ actionText: strings.compare.auditB.action,
272
+ summaryText: strings.compare.auditB.done(),
273
+ run: async () => {
274
+ const sdkClient = createClient({ base: opts.base, apiKey: config.apiKey });
275
+ const mode = opts.mode || "basic";
276
+ jobBRef = await sdkClient.audits.create({ url: urlB, mode: mode });
277
+ },
278
+ },
279
+ {
280
+ id: "compare",
281
+ actionText: strings.compare.analyze.action,
282
+ summaryText: strings.compare.analyze.done(),
283
+ run: async () => {
284
+ if (!jobARef || !jobBRef) {
285
+ throw new Error(strings.compare.errors.missingJobIds);
286
+ }
287
+ const jobA = jobARef;
288
+ const jobB = jobBRef;
289
+ if (!opts.wait) {
290
+ const payload = { job_a: jobA, job_b: jobB };
291
+ if (format === "json") {
292
+ compareJsonPayload = payload;
293
+ }
294
+ else {
295
+ compareOutput = `Audit comparison queued:\n Job A: ${jobA.job_id}\n Job B: ${jobB.job_id}`;
296
+ }
297
+ return;
298
+ }
299
+ if (!jobA.job_id || !jobB.job_id) {
300
+ throw new Error(strings.compare.errors.missingJobIds);
301
+ }
302
+ const timeout = parseInt(opts.timeout || "60000", 10);
303
+ const interval = parseInt(opts.interval || "5000", 10);
304
+ const sdkClientWait = createClient({ base: opts.base, apiKey: config.apiKey });
305
+ const [resultA, resultB] = await Promise.all([
306
+ waitForAudit(sdkClientWait, jobA.job_id, timeout, interval),
307
+ waitForAudit(sdkClientWait, jobB.job_id, timeout, interval),
308
+ ]);
309
+ const overallA = getOverallScore(resultA.scores);
310
+ const overallB = getOverallScore(resultB.scores);
311
+ const delta = overallA !== null && overallB !== null ? overallB - overallA : null;
312
+ const scoresA = (resultA.scores || {});
313
+ const scoresB = (resultB.scores || {});
314
+ const keys = new Set([...Object.keys(scoresA), ...Object.keys(scoresB)]);
315
+ const categoryDeltas = {};
316
+ for (const key of keys) {
317
+ const a = toNumber(scoresA[key]);
318
+ const b = toNumber(scoresB[key]);
319
+ categoryDeltas[key] = {
320
+ a,
321
+ b,
322
+ delta: a !== null && b !== null ? b - a : null,
323
+ };
324
+ }
325
+ const issuesA = normalizeIssues(resultA.issues).length;
326
+ const issuesB = normalizeIssues(resultB.issues).length;
327
+ const data = {
328
+ urlA,
329
+ urlB,
330
+ jobA: resultA.job_id || "",
331
+ jobB: resultB.job_id || "",
332
+ overallA,
333
+ overallB,
334
+ delta,
335
+ categoryDeltas,
336
+ issuesA,
337
+ issuesB,
338
+ };
339
+ const failOnScore = opts.failOnScore ? parseInt(opts.failOnScore, 10) : undefined;
340
+ if (failOnScore !== undefined &&
341
+ ((overallA !== null && overallA < failOnScore) ||
342
+ (overallB !== null && overallB < failOnScore))) {
343
+ process.exitCode = 1;
344
+ }
345
+ if (format === "json") {
346
+ compareJsonPayload = data;
347
+ }
348
+ else {
349
+ compareOutput = formatLegacyCompareHuman(data);
350
+ }
351
+ },
352
+ },
353
+ ];
354
+ const { success: urlSuccess, states: urlStates } = await runSteps(steps, {
355
+ onStateChange: (stepStates) => {
356
+ renderer.update({ ...baseState, stepStates, elapsed: Date.now() - startTime });
357
+ },
358
+ });
359
+ renderer.finish({
360
+ url: urlA,
361
+ mode: "compare",
362
+ overallScore: urlSuccess ? 100 : 0,
363
+ scores: {},
364
+ issueCount: 0,
365
+ passed: urlSuccess,
366
+ elapsed: Date.now() - startTime,
367
+ });
368
+ if (!urlSuccess) {
369
+ const failed = urlStates.find(s => s.status === "failed");
370
+ process.stderr.write(renderError({
371
+ message: failed?.failReason || "Command failed",
372
+ suggestion: "vertaa doctor",
373
+ }) + "\n");
374
+ process.exitCode = ExitCode.ERROR;
375
+ }
376
+ if (urlSuccess) {
377
+ if (format === "json" && compareJsonPayload !== null) {
378
+ writeJsonOutput(compareJsonPayload, "compare");
379
+ }
380
+ else if (compareOutput !== null) {
381
+ writeDataOutput(compareOutput);
382
+ }
383
+ }
384
+ }
385
+ else {
386
+ throw new Error(strings.compare.errors.urlsOrFilesRequired);
387
+ }
388
+ }
158
389
  // ---------------------------------------------------------------------------
159
390
  // Command Registration
160
391
  // ---------------------------------------------------------------------------
@@ -182,154 +413,30 @@ Examples:
182
413
  const config = await resolveConfig(globalOpts.config);
183
414
  const machineMode = globalOpts.machine || false;
184
415
  const verbose = globalOpts.verbose || false;
185
- const format = resolveCommandFormat("compare", options.format, machineMode);
186
- // Decide mode: file-based LLM or URL-based legacy
187
- const isFileMode = options.before || options.after;
188
- if (isFileMode) {
189
- await runLlmCompare(options, globalOpts, config, format, verbose);
190
- }
191
- else if (urlA && urlB) {
192
- await runUrlCompare(urlA, urlB, options, globalOpts, config, format);
193
- }
194
- else {
195
- console.error("Error: Provide either two URLs or --before/--after files.");
196
- console.error("Usage:");
197
- console.error(" vertaa compare https://a.com https://b.com --wait");
198
- console.error(" vertaa compare --before baseline.json --after current.json");
199
- process.exit(ExitCode.ERROR);
200
- }
416
+ await handleCompare({
417
+ urlA,
418
+ urlB,
419
+ before: options.before,
420
+ after: options.after,
421
+ mode: options.mode,
422
+ wait: options.wait,
423
+ timeout: options.timeout,
424
+ interval: options.interval,
425
+ failOnScore: options.failOnScore,
426
+ format: options.format,
427
+ base: globalOpts.base,
428
+ verbose,
429
+ machine: machineMode,
430
+ apiKey: config.apiKey,
431
+ });
201
432
  }
202
433
  catch (error) {
203
- console.error("Error:", error instanceof Error ? error.message : String(error));
434
+ process.stderr.write(renderError({
435
+ message: error instanceof Error ? error.message : String(error),
436
+ suggestion: "vertaa audit <url>",
437
+ exitCode: ExitCode.ERROR,
438
+ }) + "\n");
204
439
  process.exit(ExitCode.ERROR);
205
440
  }
206
441
  });
207
442
  }
208
- // ---------------------------------------------------------------------------
209
- // LLM-based comparison (new)
210
- // ---------------------------------------------------------------------------
211
- async function runLlmCompare(options, globalOpts, config, format, verbose) {
212
- if (!options.before || !options.after) {
213
- console.error("Error: Both --before and --after are required for file-based comparison.");
214
- process.exit(ExitCode.ERROR);
215
- }
216
- const beforeInput = await readJsonInput(options.before);
217
- const afterInput = await readJsonInput(options.after);
218
- if (!beforeInput || !afterInput) {
219
- console.error("Error: Could not read audit files.");
220
- process.exit(ExitCode.ERROR);
221
- }
222
- function extractPayload(input) {
223
- const data = input;
224
- const inner = (data.data && typeof data.data === "object" ? data.data : data);
225
- const issues = normalizeIssues(inner.issues);
226
- return {
227
- job_id: inner.job_id || null,
228
- url: inner.url || null,
229
- scores: inner.scores || null,
230
- issues,
231
- };
232
- }
233
- const beforePayload = extractPayload(beforeInput);
234
- const afterPayload = extractPayload(afterInput);
235
- const base = resolveApiBase(globalOpts.base);
236
- const apiKey = getApiKey(config.apiKey);
237
- const spinner = createSpinner("Comparing audits...");
238
- try {
239
- const response = await Promise.race([
240
- apiRequest(base, "/cli/ai/compare", { method: "POST", body: { before: beforePayload, after: afterPayload } }, apiKey),
241
- new Promise((_, reject) => setTimeout(() => reject(new Error("LLM request timed out")), AI_TIMEOUT_MS)),
242
- ]);
243
- succeedSpinner(spinner, "Comparison complete");
244
- if (format === "json") {
245
- writeJsonOutput(response.data, "compare");
246
- }
247
- else {
248
- writeOutput(formatLlmCompareHuman(response.data, verbose));
249
- }
250
- }
251
- catch (error) {
252
- handleAiCommandError(error, "compare", spinner);
253
- }
254
- }
255
- // ---------------------------------------------------------------------------
256
- // URL-based comparison (legacy backward compat)
257
- // ---------------------------------------------------------------------------
258
- async function runUrlCompare(urlA, urlB, options, globalOpts, config, format) {
259
- const base = resolveApiBase(globalOpts.base);
260
- const apiKey = getApiKey(config.apiKey);
261
- const mode = options.mode || "basic";
262
- const spinner = createSpinner("Starting audits...");
263
- const jobA = await apiRequest(base, "/audit", {
264
- method: "POST",
265
- body: { url: urlA, mode },
266
- }, apiKey);
267
- const jobB = await apiRequest(base, "/audit", {
268
- method: "POST",
269
- body: { url: urlB, mode },
270
- }, apiKey);
271
- if (!options.wait) {
272
- succeedSpinner(spinner, "Audits queued");
273
- const payload = { job_a: jobA, job_b: jobB };
274
- if (format === "json") {
275
- writeJsonOutput(payload, "compare");
276
- }
277
- else {
278
- writeOutput(`Audit comparison queued:\n Job A: ${jobA.job_id}\n Job B: ${jobB.job_id}`);
279
- }
280
- return;
281
- }
282
- if (!jobA.job_id || !jobB.job_id) {
283
- failSpinner(spinner, "Missing job IDs");
284
- throw new Error("Compare response missing job_id");
285
- }
286
- const timeout = parseInt(options.timeout || "60000", 10);
287
- const interval = parseInt(options.interval || "5000", 10);
288
- const [resultA, resultB] = await Promise.all([
289
- waitForAudit(base, jobA.job_id, timeout, interval, apiKey),
290
- waitForAudit(base, jobB.job_id, timeout, interval, apiKey),
291
- ]);
292
- succeedSpinner(spinner, "Audits complete");
293
- const overallA = getOverallScore(resultA.scores);
294
- const overallB = getOverallScore(resultB.scores);
295
- const delta = overallA !== null && overallB !== null ? overallB - overallA : null;
296
- const scoresA = (resultA.scores || {});
297
- const scoresB = (resultB.scores || {});
298
- const keys = new Set([...Object.keys(scoresA), ...Object.keys(scoresB)]);
299
- const categoryDeltas = {};
300
- for (const key of keys) {
301
- const a = toNumber(scoresA[key]);
302
- const b = toNumber(scoresB[key]);
303
- categoryDeltas[key] = {
304
- a,
305
- b,
306
- delta: a !== null && b !== null ? b - a : null,
307
- };
308
- }
309
- const issuesA = normalizeIssues(resultA.issues).length;
310
- const issuesB = normalizeIssues(resultB.issues).length;
311
- const compareData = {
312
- urlA,
313
- urlB,
314
- jobA: resultA.job_id || "",
315
- jobB: resultB.job_id || "",
316
- overallA,
317
- overallB,
318
- delta,
319
- categoryDeltas,
320
- issuesA,
321
- issuesB,
322
- };
323
- const failOnScore = options.failOnScore ? parseInt(options.failOnScore, 10) : undefined;
324
- if (failOnScore !== undefined &&
325
- ((overallA !== null && overallA < failOnScore) ||
326
- (overallB !== null && overallB < failOnScore))) {
327
- process.exitCode = 1;
328
- }
329
- if (format === "json") {
330
- writeJsonOutput(compareData, "compare");
331
- }
332
- else {
333
- writeOutput(formatLegacyCompareHuman(compareData));
334
- }
335
- }
@@ -16,7 +16,14 @@ export interface DiffCommandOptions {
16
16
  format?: string;
17
17
  verbose?: boolean;
18
18
  fromFile?: string;
19
+ strict?: boolean;
20
+ continueOnError?: boolean;
21
+ machine?: boolean;
22
+ jobIdArg?: string;
23
+ base?: string;
24
+ apiKey?: string;
19
25
  }
26
+ export declare function handleDiff(opts: DiffCommandOptions): Promise<void>;
20
27
  /**
21
28
  * Register the diff command with the Commander program.
22
29
  */
@@ -1 +1 @@
1
- {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/commands/diff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAiFzC,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA2I1D"}
1
+ {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/commands/diff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAuCzC,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAMD,wBAAsB,UAAU,CAAC,IAAI,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAwKxE;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAkC1D"}