@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
@@ -5,7 +5,7 @@
5
5
  * Reports pass/fail with actionable remediation instructions.
6
6
  * Never crashes -- all checks are wrapped in try/catch.
7
7
  */
8
- import chalk from "chalk";
8
+ import { bold, dim, colorize, brand, severity as severityPalette, runSteps, createRenderer, renderWarning, isCI, isTTY } from "@vertaaux/tui";
9
9
  import { createRequire } from "module";
10
10
  import { resolveConfig } from "../config/loader.js";
11
11
  import { ConfigValidationError, ConfigLoadError } from "../config/loader.js";
@@ -13,9 +13,10 @@ import { loadToken, isTokenExpired, getCredentialsPath } from "../auth/token-sto
13
13
  import { getCIToken, validateCIToken } from "../auth/ci-token.js";
14
14
  import { resolveApiBase } from "../utils/client.js";
15
15
  import { ExitCode } from "../utils/exit-codes.js";
16
- import { writeJsonOutput, writeOutput } from "../output/envelope.js";
16
+ import { writeDataOutput, writeJsonOutput, writeOutput } from "../output/envelope.js";
17
17
  import { COMMAND_FORMATS } from "../output/formats.js";
18
18
  import { getVersion } from "../ui/banner.js";
19
+ import { strings } from "../ui/strings.js";
19
20
  const require = createRequire(import.meta.url);
20
21
  // ---------------------------------------------------------------------------
21
22
  // Individual health checks
@@ -249,51 +250,194 @@ export function checkOutputContracts(deep) {
249
250
  // ---------------------------------------------------------------------------
250
251
  export function formatDoctorHuman(result) {
251
252
  const statusIcons = {
252
- pass: chalk.green("PASS"),
253
- fail: chalk.red("FAIL"),
254
- warn: chalk.yellow("WARN"),
255
- skip: chalk.dim("SKIP"),
253
+ pass: colorize("PASS", brand.lime),
254
+ fail: colorize("FAIL", severityPalette.error),
255
+ warn: colorize("WARN", severityPalette.warning),
256
+ skip: dim("SKIP"),
256
257
  };
257
258
  const lines = [];
258
259
  const version = getVersion();
259
- lines.push(chalk.bold(`VertaaUX Doctor`) + chalk.dim(` v${version}`));
260
+ lines.push(bold(strings.doctor.title(version)));
260
261
  lines.push("");
261
262
  for (const check of result.checks) {
262
263
  lines.push(` ${statusIcons[check.status]} ${check.name}: ${check.message}`);
263
264
  if (check.remediation) {
264
- lines.push(` ${chalk.cyan("Fix:")} ${check.remediation}`);
265
+ lines.push(` ${colorize(strings.doctor.checks.fix, brand.cyan)} ${check.remediation}`);
265
266
  }
266
267
  if (check.detail) {
267
- lines.push(` ${chalk.dim(check.detail)}`);
268
+ lines.push(` ${dim(check.detail)}`);
268
269
  }
269
270
  }
270
271
  lines.push("");
271
272
  const { pass, fail, warn } = result.summary;
272
273
  if (fail > 0) {
273
- lines.push(chalk.red(`${fail} check(s) failed.`) +
274
+ lines.push(colorize(strings.doctor.results.withFailures(fail, pass + fail + warn), severityPalette.error) +
274
275
  " Run the suggested fix commands above.");
275
276
  }
276
277
  else if (warn > 0) {
277
- lines.push(chalk.yellow(`All checks passed (${warn} warning(s)).`));
278
+ lines.push(colorize(strings.doctor.results.withWarnings(warn), severityPalette.warning));
278
279
  }
279
280
  else {
280
- lines.push(chalk.green(`All ${pass} checks passed.`));
281
+ lines.push(colorize(strings.doctor.results.allPassed(pass), brand.lime));
281
282
  }
282
283
  return lines.join("\n");
283
284
  }
284
285
  // ---------------------------------------------------------------------------
286
+ // Step failure mode diagnostic
287
+ // ---------------------------------------------------------------------------
288
+ /**
289
+ * Build a DoctorCheck describing the current step failure mode and how to override it.
290
+ * This is always informational (status: "pass").
291
+ */
292
+ function buildStepFailureModeDiagnostic(strict, continueOnError) {
293
+ let detectedMode;
294
+ let overrideHint;
295
+ if (strict) {
296
+ detectedMode = "fail-fast (--strict flag)";
297
+ overrideHint = "Use --continue-on-error to continue on step errors";
298
+ }
299
+ else if (continueOnError) {
300
+ detectedMode = "continue (--continue-on-error flag)";
301
+ overrideHint = "Use --strict to fail immediately on first step error";
302
+ }
303
+ else if (isCI()) {
304
+ detectedMode = "fail-fast (CI detected)";
305
+ overrideHint = "Use --continue-on-error to get partial results in CI";
306
+ }
307
+ else if (isTTY()) {
308
+ detectedMode = "continue (interactive TTY)";
309
+ overrideHint = "Use --strict to fail-fast in interactive mode";
310
+ }
311
+ else {
312
+ detectedMode = "fail-fast (non-TTY)";
313
+ overrideHint = "Use --continue-on-error to get partial results";
314
+ }
315
+ return {
316
+ name: "Step failure mode",
317
+ status: "pass",
318
+ message: detectedMode,
319
+ detail: `Override: ${overrideHint}. Use --strict to fail-fast in interactive mode, --continue-on-error for partial results in CI`,
320
+ };
321
+ }
322
+ // ---------------------------------------------------------------------------
285
323
  // Command handler
286
324
  // ---------------------------------------------------------------------------
287
325
  export async function handleDoctor(options) {
288
326
  const format = options.format || "human";
289
327
  const online = options.online || false;
290
328
  const deep = options.deep || false;
329
+ // Resolve fail-fast mode: --strict > --continue-on-error > auto-detect
330
+ let failFast;
331
+ if (options.strict && options.continueOnError) {
332
+ writeOutput(renderWarning({ message: "--strict and --continue-on-error both set — --strict takes precedence" }));
333
+ failFast = true;
334
+ }
335
+ else if (options.strict) {
336
+ failFast = true;
337
+ }
338
+ else if (options.continueOnError) {
339
+ failFast = false;
340
+ }
341
+ else {
342
+ failFast = isCI() || !isTTY();
343
+ }
291
344
  const checks = [];
292
- // Run checks in dependency order
293
- checks.push(await checkConfig());
294
- checks.push(await checkAuth(online));
295
- checks.push(await checkNetwork());
296
- checks.push(checkOutputContracts(deep));
345
+ const checkResults = [];
346
+ const renderer = createRenderer("auto");
347
+ const baseState = {
348
+ phase: "doctor",
349
+ phaseIndex: 1,
350
+ phaseTotal: 4,
351
+ url: "",
352
+ mode: "doctor",
353
+ progress: {},
354
+ totals: {},
355
+ issueCount: 0,
356
+ scorePreview: null,
357
+ verbose: false,
358
+ elapsed: 0,
359
+ };
360
+ // Define doctor check steps with 1:1 mapping to check functions
361
+ const steps = [
362
+ {
363
+ id: "config",
364
+ actionText: "Checking configuration...",
365
+ summaryText: "Configuration checked",
366
+ run: async () => {
367
+ const result = await checkConfig();
368
+ checkResults.push(result);
369
+ if (result.status === "fail") {
370
+ throw new Error(result.message);
371
+ }
372
+ },
373
+ },
374
+ {
375
+ id: "auth",
376
+ actionText: "Checking authentication...",
377
+ summaryText: "Authentication checked",
378
+ run: async () => {
379
+ const result = await checkAuth(online);
380
+ checkResults.push(result);
381
+ if (result.status === "fail") {
382
+ throw new Error(result.message);
383
+ }
384
+ },
385
+ },
386
+ {
387
+ id: "network",
388
+ actionText: "Checking network connectivity...",
389
+ summaryText: "Network checked",
390
+ run: async () => {
391
+ const result = await checkNetwork();
392
+ checkResults.push(result);
393
+ if (result.status === "fail") {
394
+ throw new Error(result.message);
395
+ }
396
+ },
397
+ },
398
+ {
399
+ id: "contracts",
400
+ actionText: "Checking output contracts...",
401
+ summaryText: "Output contracts checked",
402
+ run: async () => {
403
+ const result = checkOutputContracts(deep);
404
+ checkResults.push(result);
405
+ if (result.status === "fail") {
406
+ throw new Error(result.message);
407
+ }
408
+ },
409
+ },
410
+ ];
411
+ const startTime = Date.now();
412
+ await runSteps(steps, {
413
+ failFast,
414
+ onStateChange: (stepStates) => {
415
+ renderer.update({ ...baseState, stepStates, elapsed: Date.now() - startTime });
416
+ },
417
+ });
418
+ renderer.finish({
419
+ url: "",
420
+ mode: "doctor",
421
+ overallScore: 0,
422
+ scores: {},
423
+ issueCount: 0,
424
+ passed: true,
425
+ elapsed: Date.now() - startTime,
426
+ });
427
+ // Collect checks: use results captured during runSteps for ran steps.
428
+ // For steps skipped by fail-fast, create a skip placeholder.
429
+ const ranNames = new Set(checkResults.map((c) => c.name));
430
+ const checkNames = ["Configuration", "Authentication", "Network", "Output Contracts"];
431
+ for (const name of checkNames) {
432
+ if (ranNames.has(name)) {
433
+ checks.push(checkResults.find((c) => c.name === name));
434
+ }
435
+ else {
436
+ checks.push({ name, status: "skip", message: "Skipped due to earlier failure" });
437
+ }
438
+ }
439
+ // Add step failure mode diagnostic (always informational, always passes)
440
+ checks.push(buildStepFailureModeDiagnostic(options.strict, options.continueOnError));
297
441
  // Build summary
298
442
  const summary = { pass: 0, fail: 0, warn: 0, skip: 0 };
299
443
  for (const check of checks) {
@@ -305,7 +449,7 @@ export async function handleDoctor(options) {
305
449
  writeJsonOutput(result, "doctor");
306
450
  }
307
451
  else {
308
- writeOutput(formatDoctorHuman(result));
452
+ writeDataOutput(formatDoctorHuman(result));
309
453
  }
310
454
  // Exit code: 0 on all-pass/warn, 1 on any fail
311
455
  if (summary.fail > 0) {
@@ -325,12 +469,15 @@ export function registerDoctorCommand(program) {
325
469
  .option("--online", "Verify auth credentials against API (default: offline check)")
326
470
  .option("--deep", "Run output contract self-tests")
327
471
  .option("--format <format>", "Output format: human, json", "human")
472
+ .option("--strict", "Fail immediately on first check failure")
473
+ .option("--continue-on-error", "Continue running checks even after failures")
328
474
  .action(async (options) => {
329
475
  try {
330
476
  await handleDoctor(options);
331
477
  }
332
478
  catch (error) {
333
- // Doctor must NEVER crash -- fallback error reporting
479
+ // Doctor must NEVER crash raw fallback to avoid renderError() recursion
480
+ // eslint-disable-next-line no-restricted-syntax
334
481
  process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}\n`);
335
482
  process.exit(ExitCode.ERROR);
336
483
  }
@@ -5,6 +5,16 @@
5
5
  * Enables pulling results from CI runs or shared audits.
6
6
  */
7
7
  import { Command } from "commander";
8
+ /**
9
+ * Handle the download command.
10
+ */
11
+ export declare function handleDownload(jobId: string, options: {
12
+ baseline?: boolean;
13
+ output?: string;
14
+ base?: string;
15
+ force?: boolean;
16
+ configPath?: string;
17
+ }): Promise<void>;
8
18
  /**
9
19
  * Register the download command with the Commander program.
10
20
  */
@@ -1 +1 @@
1
- {"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../../src/commands/download.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAuNpC;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAkB9D"}
1
+ {"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../../src/commands/download.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqDpC;;GAEG;AACH,wBAAsB,cAAc,CAClC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE;IACP,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GACA,OAAO,CAAC,IAAI,CAAC,CAgNf;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA6B9D"}
@@ -6,16 +6,17 @@
6
6
  */
7
7
  import fs from "fs";
8
8
  import path from "path";
9
- import chalk from "chalk";
10
- import { createSpinner, succeedSpinner, failSpinner, warnSpinner, infoSpinner } from "../ui/spinner.js";
9
+ import { renderError, runSteps, createRenderer } from "@vertaaux/tui";
11
10
  import { loadToken } from "../auth/token-store.js";
12
11
  import { getCIToken } from "../auth/ci-token.js";
13
- import { resolveApiBase } from "../utils/client.js";
12
+ import { apiRequest, resolveApiBase } from "../utils/client.js";
14
13
  import { resolveConfig } from "../config/loader.js";
15
14
  import { saveBaseline } from "../baseline/manager.js";
16
15
  import { isInteractive, confirmAction } from "../interactive/prompts.js";
17
16
  import { ExitCode } from "../utils/exit-codes.js";
18
17
  import { assertPathContainment } from "../utils/sanitize.js";
18
+ import { writeOutput } from "../output/envelope.js";
19
+ import { strings } from "../ui/strings.js";
19
20
  /**
20
21
  * Default output directory.
21
22
  */
@@ -35,135 +36,181 @@ async function getAuthToken() {
35
36
  /**
36
37
  * Handle the download command.
37
38
  */
38
- async function handleDownload(jobId, options) {
39
+ export async function handleDownload(jobId, options) {
39
40
  // Get auth token
40
41
  const token = await getAuthToken();
41
42
  if (!token) {
42
- console.error(chalk.red("Error: Not authenticated."));
43
- console.error("Run `vertaa login` to authenticate or set VERTAAUX_TOKEN environment variable.");
43
+ process.stderr.write(renderError({
44
+ message: strings.download.errors.notAuthenticated,
45
+ suggestion: "vertaa login",
46
+ exitCode: ExitCode.ERROR,
47
+ }) + "\n");
44
48
  process.exit(ExitCode.ERROR);
45
49
  }
46
50
  // Load config for API base (supports --config global option)
47
- const config = await resolveConfig(options.configPath);
51
+ await resolveConfig(options.configPath);
48
52
  const apiBase = resolveApiBase(options.base);
49
53
  const outputDir = options.output || DEFAULT_OUTPUT_DIR;
50
- const spinner = createSpinner(`Downloading audit ${jobId}...`);
51
- spinner.start();
52
- try {
53
- // Build request URL
54
- let url = `${apiBase}/sync/download/${jobId}`;
55
- if (options.baseline) {
56
- url += "?include_baseline=true";
57
- }
58
- // Make API request
59
- const response = await fetch(url, {
60
- method: "GET",
61
- headers: {
62
- "Content-Type": "application/json",
63
- "X-API-Key": token,
54
+ const renderer = createRenderer("auto");
55
+ const baseState = {
56
+ phase: "download",
57
+ phaseIndex: 1,
58
+ phaseTotal: 1,
59
+ url: "",
60
+ mode: "download",
61
+ progress: {},
62
+ totals: {},
63
+ issueCount: 0,
64
+ scorePreview: null,
65
+ verbose: false,
66
+ elapsed: 0,
67
+ };
68
+ const startTime = Date.now();
69
+ // Use a container so TypeScript can track the value across step closures
70
+ const ctx = {
71
+ downloadResult: null,
72
+ resolvedOutputDir: "",
73
+ skippedFiles: [],
74
+ };
75
+ const steps = [
76
+ {
77
+ id: "fetch",
78
+ actionText: strings.download.run.action,
79
+ summaryText: "Audit data retrieved",
80
+ run: async () => {
81
+ // Make API request via apiRequest() — auth header constructed in client.ts only
82
+ const downloadPath = `/sync/download/${jobId}${options.baseline ? "?include_baseline=true" : ""}`;
83
+ const result = await apiRequest(apiBase, downloadPath, { method: "GET" }, token);
84
+ if (!result.success) {
85
+ throw new Error(result.error?.message || "Download failed");
86
+ }
87
+ ctx.downloadResult = result;
64
88
  },
65
- });
66
- if (!response.ok) {
67
- const error = await response.json().catch(() => ({ error: { message: response.statusText } }));
68
- throw new Error(error.error?.message || `HTTP ${response.status}`);
69
- }
70
- const result = (await response.json());
71
- if (!result.success) {
72
- throw new Error(result.error?.message || "Download failed");
73
- }
74
- // Create output directory
75
- const resolvedOutputDir = path.resolve(process.cwd(), outputDir);
76
- if (!fs.existsSync(resolvedOutputDir)) {
77
- fs.mkdirSync(resolvedOutputDir, { recursive: true });
78
- }
79
- // Save audit results
80
- if (result.audit) {
81
- const auditPath = path.join(resolvedOutputDir, `audit-${jobId}.json`);
82
- // Check for existing file
83
- if (fs.existsSync(auditPath) && !options.force) {
84
- if (isInteractive()) {
85
- const overwrite = await confirmAction(`File ${auditPath} already exists. Overwrite?`, false);
86
- if (!overwrite) {
87
- infoSpinner(spinner, "Skipping audit results (file exists).");
89
+ },
90
+ {
91
+ id: "save",
92
+ actionText: "Writing to file...",
93
+ summaryText: "File saved",
94
+ run: async () => {
95
+ const dlResult = ctx.downloadResult;
96
+ if (!dlResult)
97
+ throw new Error("No download result");
98
+ // Create output directory
99
+ ctx.resolvedOutputDir = path.resolve(process.cwd(), outputDir);
100
+ if (!fs.existsSync(ctx.resolvedOutputDir)) {
101
+ fs.mkdirSync(ctx.resolvedOutputDir, { recursive: true });
102
+ }
103
+ // Save audit results
104
+ if (dlResult.audit) {
105
+ const auditPath = path.join(ctx.resolvedOutputDir, `audit-${jobId}.json`);
106
+ // Check for existing file
107
+ if (fs.existsSync(auditPath) && !options.force) {
108
+ if (isInteractive()) {
109
+ const overwrite = await confirmAction(`File ${auditPath} already exists. Overwrite?`, false);
110
+ if (!overwrite) {
111
+ ctx.skippedFiles.push("audit results");
112
+ }
113
+ else {
114
+ fs.writeFileSync(auditPath, JSON.stringify(dlResult.audit, null, 2), "utf-8");
115
+ }
116
+ }
117
+ else {
118
+ ctx.skippedFiles.push("audit results (non-interactive)");
119
+ }
88
120
  }
89
121
  else {
90
- fs.writeFileSync(auditPath, JSON.stringify(result.audit, null, 2), "utf-8");
122
+ fs.writeFileSync(auditPath, JSON.stringify(dlResult.audit, null, 2), "utf-8");
91
123
  }
92
124
  }
93
- else {
94
- warnSpinner(spinner, `Skipping ${auditPath} (already exists, use --force to overwrite)`);
95
- }
96
- }
97
- else {
98
- fs.writeFileSync(auditPath, JSON.stringify(result.audit, null, 2), "utf-8");
99
- }
100
- }
101
- // Save baseline if included
102
- if (options.baseline && result.baseline) {
103
- const baselinePath = path.join(resolvedOutputDir, "baseline.json");
104
- // Check for existing baseline
105
- if (fs.existsSync(baselinePath) && !options.force) {
106
- if (isInteractive()) {
107
- const overwrite = await confirmAction(`Baseline file already exists. Overwrite?`, false);
108
- if (!overwrite) {
109
- infoSpinner(spinner, "Skipping baseline (file exists).");
125
+ // Save baseline if included
126
+ if (options.baseline && dlResult.baseline) {
127
+ const baselinePath = path.join(ctx.resolvedOutputDir, "baseline.json");
128
+ // Check for existing baseline
129
+ if (fs.existsSync(baselinePath) && !options.force) {
130
+ if (isInteractive()) {
131
+ const overwrite = await confirmAction(`Baseline file already exists. Overwrite?`, false);
132
+ if (!overwrite) {
133
+ ctx.skippedFiles.push("baseline");
134
+ }
135
+ else {
136
+ await saveBaseline(dlResult.baseline, baselinePath);
137
+ }
138
+ }
139
+ else {
140
+ ctx.skippedFiles.push("baseline (non-interactive)");
141
+ }
110
142
  }
111
143
  else {
112
- await saveBaseline(result.baseline, baselinePath);
144
+ await saveBaseline(dlResult.baseline, baselinePath);
113
145
  }
114
146
  }
115
- else {
116
- warnSpinner(spinner, `Skipping baseline (already exists, use --force to overwrite)`);
117
- }
118
- }
119
- else {
120
- await saveBaseline(result.baseline, baselinePath);
121
- }
122
- }
123
- // Save artifacts if included
124
- if (result.artifacts) {
125
- const artifactsDir = path.join(resolvedOutputDir, "artifacts", jobId);
126
- if (!fs.existsSync(artifactsDir)) {
127
- fs.mkdirSync(artifactsDir, { recursive: true });
128
- }
129
- for (const [filename, content] of Object.entries(result.artifacts)) {
130
- let filePath;
131
- try {
132
- filePath = assertPathContainment(filename, artifactsDir);
133
- }
134
- catch {
135
- console.error(`Security: Rejected artifact "${filename}" -- path traversal outside output directory.`);
136
- continue;
137
- }
138
- const ext = path.extname(filename).toLowerCase();
139
- const isBinary = [".png", ".jpg", ".jpeg", ".gif", ".zip"].includes(ext);
140
- if (isBinary) {
141
- fs.writeFileSync(filePath, Buffer.from(content, "base64"));
142
- }
143
- else {
144
- fs.writeFileSync(filePath, content, "utf-8");
147
+ // Save artifacts if included
148
+ if (dlResult.artifacts) {
149
+ const artifactsDir = path.join(ctx.resolvedOutputDir, "artifacts", jobId);
150
+ if (!fs.existsSync(artifactsDir)) {
151
+ fs.mkdirSync(artifactsDir, { recursive: true });
152
+ }
153
+ for (const [filename, content] of Object.entries(dlResult.artifacts)) {
154
+ let filePath;
155
+ try {
156
+ filePath = assertPathContainment(filename, artifactsDir);
157
+ }
158
+ catch {
159
+ ctx.skippedFiles.push(`${filename} (path traversal)`);
160
+ continue;
161
+ }
162
+ const ext = path.extname(filename).toLowerCase();
163
+ const isBinary = [".png", ".jpg", ".jpeg", ".gif", ".zip"].includes(ext);
164
+ if (isBinary) {
165
+ fs.writeFileSync(filePath, Buffer.from(content, "base64"));
166
+ }
167
+ else {
168
+ fs.writeFileSync(filePath, content, "utf-8");
169
+ }
170
+ }
145
171
  }
172
+ },
173
+ },
174
+ ];
175
+ const { success, states } = await runSteps(steps, {
176
+ failFast: true,
177
+ onStateChange: (stepStates) => {
178
+ renderer.update({ ...baseState, stepStates, elapsed: Date.now() - startTime });
179
+ },
180
+ });
181
+ renderer.finish({ url: "", mode: "download", overallScore: 0, scores: {}, issueCount: 0, passed: success, elapsed: Date.now() - startTime });
182
+ if (!success) {
183
+ const failed = states.find(s => s.status === "failed");
184
+ process.stderr.write(renderError({
185
+ message: failed?.failReason || "Command failed",
186
+ suggestion: "vertaa doctor",
187
+ }) + "\n");
188
+ process.exitCode = ExitCode.ERROR;
189
+ }
190
+ const { downloadResult, resolvedOutputDir, skippedFiles } = ctx;
191
+ if (downloadResult) {
192
+ const artifactCount = downloadResult.artifacts ? Object.keys(downloadResult.artifacts).length : (downloadResult.audit ? 1 : 0);
193
+ const lines = [
194
+ strings.download.run.done(artifactCount),
195
+ "",
196
+ ` Job ID: ${downloadResult.job_id}`,
197
+ ` Output: ${resolvedOutputDir}`,
198
+ ];
199
+ if (downloadResult.audit) {
200
+ lines.push(` Status: ${downloadResult.audit.status}`);
201
+ if (downloadResult.audit.url) {
202
+ lines.push(` URL: ${downloadResult.audit.url}`);
146
203
  }
147
204
  }
148
- succeedSpinner(spinner, "Download complete!");
149
- console.error("");
150
- console.error(` Job ID: ${result.job_id}`);
151
- console.error(` Output: ${resolvedOutputDir}`);
152
- if (result.audit) {
153
- console.error(` Status: ${result.audit.status}`);
154
- if (result.audit.url) {
155
- console.error(` URL: ${result.audit.url}`);
156
- }
205
+ if (options.baseline && downloadResult.baseline) {
206
+ lines.push(` Baseline: ${downloadResult.baseline.issues.length} issues`);
157
207
  }
158
- if (options.baseline && result.baseline) {
159
- console.error(` Baseline: ${result.baseline.issues.length} issues`);
208
+ if (skippedFiles.length > 0) {
209
+ lines.push("");
210
+ lines.push(` Skipped: ${skippedFiles.join(", ")}`);
160
211
  }
161
- console.error("");
162
- }
163
- catch (error) {
164
- failSpinner(spinner, "Download failed");
165
- console.error(chalk.red("Error:"), error instanceof Error ? error.message : String(error));
166
- process.exit(ExitCode.ERROR);
212
+ lines.push("");
213
+ writeOutput(lines.join("\n"));
167
214
  }
168
215
  }
169
216
  /**
@@ -178,7 +225,17 @@ export function registerDownloadCommand(program) {
178
225
  .option("-b, --base <url>", "API base URL")
179
226
  .option("-f, --force", "Overwrite existing files without prompting")
180
227
  .action(async (jobId, options, command) => {
181
- const globalOpts = command.optsWithGlobals();
182
- await handleDownload(jobId, { ...options, configPath: globalOpts.config });
228
+ try {
229
+ const globalOpts = command.optsWithGlobals();
230
+ await handleDownload(jobId, { ...options, configPath: globalOpts.config });
231
+ }
232
+ catch (error) {
233
+ process.stderr.write(renderError({
234
+ message: error instanceof Error ? error.message : String(error),
235
+ suggestion: "vertaa doctor",
236
+ exitCode: ExitCode.ERROR,
237
+ }) + "\n");
238
+ process.exit(ExitCode.ERROR);
239
+ }
183
240
  });
184
241
  }
@@ -34,10 +34,15 @@ export declare function formatEvidenceJson(issue: EvidenceIssue): string;
34
34
  /** Exported for reuse in fix-wizard. */
35
35
  export declare function explainIssue(issue: EvidenceIssue): string;
36
36
  export interface ExplainCommandOptions {
37
+ findingId?: string;
37
38
  job?: string;
38
39
  file?: string;
39
40
  format?: string;
40
41
  base?: string;
42
+ verbose?: boolean;
43
+ machine?: boolean;
44
+ apiKey?: string;
41
45
  }
46
+ export declare function handleExplain(opts: ExplainCommandOptions): Promise<void>;
42
47
  export declare function registerExplainCommand(program: Command): void;
43
48
  //# sourceMappingURL=explain.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"explain.d.ts","sourceRoot":"","sources":["../../src/commands/explain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAYpC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAMjD;;GAEG;AACH,MAAM,WAAW,aAAc,SAAQ,KAAK;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAiFD;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAyCjE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAmB/D;AAED,wCAAwC;AACxC,wBAAgB,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAEzD;AAqRD,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA+C7D"}
1
+ {"version":3,"file":"explain.d.ts","sourceRoot":"","sources":["../../src/commands/explain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAWpC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAOjD;;GAEG;AACH,MAAM,WAAW,aAAc,SAAQ,KAAK;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAiFD;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAyCjE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAmB/D;AAED,wCAAwC;AACxC,wBAAgB,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAEzD;AAmID,MAAM,WAAW,qBAAqB;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsO9E;AAMD,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAoD7D"}