@vertaaux/cli 0.2.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 (198) hide show
  1. package/README.md +345 -0
  2. package/dist/auth/ci-token.d.ts +49 -0
  3. package/dist/auth/ci-token.d.ts.map +1 -0
  4. package/dist/auth/ci-token.js +83 -0
  5. package/dist/auth/device-flow.d.ts +66 -0
  6. package/dist/auth/device-flow.d.ts.map +1 -0
  7. package/dist/auth/device-flow.js +156 -0
  8. package/dist/auth/token-store.d.ts +53 -0
  9. package/dist/auth/token-store.d.ts.map +1 -0
  10. package/dist/auth/token-store.js +78 -0
  11. package/dist/baseline/diff.d.ts +57 -0
  12. package/dist/baseline/diff.d.ts.map +1 -0
  13. package/dist/baseline/diff.js +152 -0
  14. package/dist/baseline/hash.d.ts +54 -0
  15. package/dist/baseline/hash.d.ts.map +1 -0
  16. package/dist/baseline/hash.js +66 -0
  17. package/dist/baseline/manager.d.ts +89 -0
  18. package/dist/baseline/manager.d.ts.map +1 -0
  19. package/dist/baseline/manager.js +157 -0
  20. package/dist/cache/index.d.ts +8 -0
  21. package/dist/cache/index.d.ts.map +1 -0
  22. package/dist/cache/index.js +7 -0
  23. package/dist/cache/route-cache.d.ts +119 -0
  24. package/dist/cache/route-cache.d.ts.map +1 -0
  25. package/dist/cache/route-cache.js +213 -0
  26. package/dist/ci/changed-routes.d.ts +95 -0
  27. package/dist/ci/changed-routes.d.ts.map +1 -0
  28. package/dist/ci/changed-routes.js +304 -0
  29. package/dist/ci/github-api.d.ts +68 -0
  30. package/dist/ci/github-api.d.ts.map +1 -0
  31. package/dist/ci/github-api.js +138 -0
  32. package/dist/ci/gitlab-api.d.ts +75 -0
  33. package/dist/ci/gitlab-api.d.ts.map +1 -0
  34. package/dist/ci/gitlab-api.js +180 -0
  35. package/dist/ci/index.d.ts +6 -0
  36. package/dist/ci/index.d.ts.map +1 -0
  37. package/dist/ci/index.js +4 -0
  38. package/dist/commands/audit.d.ts +58 -0
  39. package/dist/commands/audit.d.ts.map +1 -0
  40. package/dist/commands/audit.js +862 -0
  41. package/dist/commands/baseline.d.ts +22 -0
  42. package/dist/commands/baseline.d.ts.map +1 -0
  43. package/dist/commands/baseline.js +210 -0
  44. package/dist/commands/comment.d.ts +14 -0
  45. package/dist/commands/comment.d.ts.map +1 -0
  46. package/dist/commands/comment.js +363 -0
  47. package/dist/commands/diff.d.ts +24 -0
  48. package/dist/commands/diff.d.ts.map +1 -0
  49. package/dist/commands/diff.js +196 -0
  50. package/dist/commands/doctor.d.ts +58 -0
  51. package/dist/commands/doctor.d.ts.map +1 -0
  52. package/dist/commands/doctor.js +338 -0
  53. package/dist/commands/download.d.ts +12 -0
  54. package/dist/commands/download.d.ts.map +1 -0
  55. package/dist/commands/download.js +183 -0
  56. package/dist/commands/explain.d.ts +62 -0
  57. package/dist/commands/explain.d.ts.map +1 -0
  58. package/dist/commands/explain.js +302 -0
  59. package/dist/commands/init.d.ts +12 -0
  60. package/dist/commands/init.d.ts.map +1 -0
  61. package/dist/commands/init.js +212 -0
  62. package/dist/commands/login.d.ts +14 -0
  63. package/dist/commands/login.d.ts.map +1 -0
  64. package/dist/commands/login.js +222 -0
  65. package/dist/commands/policy.d.ts +13 -0
  66. package/dist/commands/policy.d.ts.map +1 -0
  67. package/dist/commands/policy.js +347 -0
  68. package/dist/commands/upload.d.ts +12 -0
  69. package/dist/commands/upload.d.ts.map +1 -0
  70. package/dist/commands/upload.js +158 -0
  71. package/dist/config/defaults.d.ts +21 -0
  72. package/dist/config/defaults.d.ts.map +1 -0
  73. package/dist/config/defaults.js +49 -0
  74. package/dist/config/loader.d.ts +66 -0
  75. package/dist/config/loader.d.ts.map +1 -0
  76. package/dist/config/loader.js +167 -0
  77. package/dist/config/schema.d.ts +55 -0
  78. package/dist/config/schema.d.ts.map +1 -0
  79. package/dist/config/schema.js +6 -0
  80. package/dist/index.d.ts +9 -0
  81. package/dist/index.d.ts.map +1 -0
  82. package/dist/index.js +1090 -0
  83. package/dist/interactive/fix-wizard.d.ts +44 -0
  84. package/dist/interactive/fix-wizard.d.ts.map +1 -0
  85. package/dist/interactive/fix-wizard.js +286 -0
  86. package/dist/interactive/init-wizard.d.ts +32 -0
  87. package/dist/interactive/init-wizard.d.ts.map +1 -0
  88. package/dist/interactive/init-wizard.js +193 -0
  89. package/dist/interactive/prompts.d.ts +62 -0
  90. package/dist/interactive/prompts.d.ts.map +1 -0
  91. package/dist/interactive/prompts.js +78 -0
  92. package/dist/monorepo/detector.d.ts +70 -0
  93. package/dist/monorepo/detector.d.ts.map +1 -0
  94. package/dist/monorepo/detector.js +278 -0
  95. package/dist/monorepo/index.d.ts +9 -0
  96. package/dist/monorepo/index.d.ts.map +1 -0
  97. package/dist/monorepo/index.js +8 -0
  98. package/dist/monorepo/workspace.d.ts +142 -0
  99. package/dist/monorepo/workspace.d.ts.map +1 -0
  100. package/dist/monorepo/workspace.js +171 -0
  101. package/dist/output/envelope.d.ts +21 -0
  102. package/dist/output/envelope.d.ts.map +1 -0
  103. package/dist/output/envelope.js +27 -0
  104. package/dist/output/factory.d.ts +73 -0
  105. package/dist/output/factory.d.ts.map +1 -0
  106. package/dist/output/factory.js +60 -0
  107. package/dist/output/formats.d.ts +11 -0
  108. package/dist/output/formats.d.ts.map +1 -0
  109. package/dist/output/formats.js +41 -0
  110. package/dist/output/html.d.ts +45 -0
  111. package/dist/output/html.d.ts.map +1 -0
  112. package/dist/output/html.js +607 -0
  113. package/dist/output/human.d.ts +41 -0
  114. package/dist/output/human.d.ts.map +1 -0
  115. package/dist/output/human.js +274 -0
  116. package/dist/output/json.d.ts +42 -0
  117. package/dist/output/json.d.ts.map +1 -0
  118. package/dist/output/json.js +37 -0
  119. package/dist/output/junit.d.ts +56 -0
  120. package/dist/output/junit.d.ts.map +1 -0
  121. package/dist/output/junit.js +135 -0
  122. package/dist/output/markdown.d.ts +77 -0
  123. package/dist/output/markdown.d.ts.map +1 -0
  124. package/dist/output/markdown.js +411 -0
  125. package/dist/output/sarif.d.ts +160 -0
  126. package/dist/output/sarif.d.ts.map +1 -0
  127. package/dist/output/sarif.js +207 -0
  128. package/dist/policy/evaluator.d.ts +111 -0
  129. package/dist/policy/evaluator.d.ts.map +1 -0
  130. package/dist/policy/evaluator.js +362 -0
  131. package/dist/policy/index.d.ts +15 -0
  132. package/dist/policy/index.d.ts.map +1 -0
  133. package/dist/policy/index.js +11 -0
  134. package/dist/policy/loader.d.ts +97 -0
  135. package/dist/policy/loader.d.ts.map +1 -0
  136. package/dist/policy/loader.js +281 -0
  137. package/dist/policy/schema.d.ts +297 -0
  138. package/dist/policy/schema.d.ts.map +1 -0
  139. package/dist/policy/schema.js +230 -0
  140. package/dist/quality-gate/evaluator.d.ts +58 -0
  141. package/dist/quality-gate/evaluator.d.ts.map +1 -0
  142. package/dist/quality-gate/evaluator.js +274 -0
  143. package/dist/quality-gate/index.d.ts +10 -0
  144. package/dist/quality-gate/index.d.ts.map +1 -0
  145. package/dist/quality-gate/index.js +7 -0
  146. package/dist/quality-gate/types.d.ts +103 -0
  147. package/dist/quality-gate/types.d.ts.map +1 -0
  148. package/dist/quality-gate/types.js +23 -0
  149. package/dist/templates/azure-devops.d.ts +25 -0
  150. package/dist/templates/azure-devops.d.ts.map +1 -0
  151. package/dist/templates/azure-devops.js +109 -0
  152. package/dist/templates/circleci.d.ts +28 -0
  153. package/dist/templates/circleci.d.ts.map +1 -0
  154. package/dist/templates/circleci.js +86 -0
  155. package/dist/templates/github-actions.d.ts +81 -0
  156. package/dist/templates/github-actions.d.ts.map +1 -0
  157. package/dist/templates/github-actions.js +393 -0
  158. package/dist/templates/gitlab-ci.d.ts +26 -0
  159. package/dist/templates/gitlab-ci.d.ts.map +1 -0
  160. package/dist/templates/gitlab-ci.js +70 -0
  161. package/dist/templates/index.d.ts +72 -0
  162. package/dist/templates/index.d.ts.map +1 -0
  163. package/dist/templates/index.js +112 -0
  164. package/dist/templates/jenkins.d.ts +26 -0
  165. package/dist/templates/jenkins.d.ts.map +1 -0
  166. package/dist/templates/jenkins.js +110 -0
  167. package/dist/ui/banner.d.ts +31 -0
  168. package/dist/ui/banner.d.ts.map +1 -0
  169. package/dist/ui/banner.js +84 -0
  170. package/dist/ui/diagnostics.d.ts +39 -0
  171. package/dist/ui/diagnostics.d.ts.map +1 -0
  172. package/dist/ui/diagnostics.js +153 -0
  173. package/dist/ui/spinner.d.ts +61 -0
  174. package/dist/ui/spinner.d.ts.map +1 -0
  175. package/dist/ui/spinner.js +101 -0
  176. package/dist/ui/table.d.ts +63 -0
  177. package/dist/ui/table.d.ts.map +1 -0
  178. package/dist/ui/table.js +236 -0
  179. package/dist/utils/client.d.ts +82 -0
  180. package/dist/utils/client.d.ts.map +1 -0
  181. package/dist/utils/client.js +128 -0
  182. package/dist/utils/detect-env.d.ts +59 -0
  183. package/dist/utils/detect-env.d.ts.map +1 -0
  184. package/dist/utils/detect-env.js +115 -0
  185. package/dist/utils/exit-codes.d.ts +47 -0
  186. package/dist/utils/exit-codes.d.ts.map +1 -0
  187. package/dist/utils/exit-codes.js +61 -0
  188. package/dist/utils/logger.d.ts +87 -0
  189. package/dist/utils/logger.d.ts.map +1 -0
  190. package/dist/utils/logger.js +185 -0
  191. package/dist/utils/sanitize.d.ts +36 -0
  192. package/dist/utils/sanitize.d.ts.map +1 -0
  193. package/dist/utils/sanitize.js +64 -0
  194. package/dist/utils/validators.d.ts +41 -0
  195. package/dist/utils/validators.d.ts.map +1 -0
  196. package/dist/utils/validators.js +123 -0
  197. package/package.json +63 -0
  198. package/schemas/vertaaux.config.schema.json +103 -0
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Download command for VertaaUX CLI.
3
+ *
4
+ * Syncs audit results from VertaaUX cloud to local.
5
+ * Enables pulling results from CI runs or shared audits.
6
+ */
7
+ import fs from "fs";
8
+ import path from "path";
9
+ import chalk from "chalk";
10
+ import ora from "ora";
11
+ import { loadToken } from "../auth/token-store.js";
12
+ import { getCIToken } from "../auth/ci-token.js";
13
+ import { resolveApiBase } from "../utils/client.js";
14
+ import { resolveConfig } from "../config/loader.js";
15
+ import { saveBaseline } from "../baseline/manager.js";
16
+ import { isInteractive, confirmAction } from "../interactive/prompts.js";
17
+ import { ExitCode } from "../utils/exit-codes.js";
18
+ import { assertPathContainment } from "../utils/sanitize.js";
19
+ /**
20
+ * Default output directory.
21
+ */
22
+ const DEFAULT_OUTPUT_DIR = ".vertaaux";
23
+ /**
24
+ * Get authentication token from stored credentials or environment.
25
+ */
26
+ async function getAuthToken() {
27
+ // Check stored token first
28
+ const storedToken = await loadToken();
29
+ if (storedToken?.accessToken) {
30
+ return storedToken.accessToken;
31
+ }
32
+ // Check environment
33
+ return getCIToken();
34
+ }
35
+ /**
36
+ * Handle the download command.
37
+ */
38
+ async function handleDownload(jobId, options) {
39
+ // Get auth token
40
+ const token = await getAuthToken();
41
+ if (!token) {
42
+ console.error(chalk.red("Error: Not authenticated."));
43
+ console.error("Run `vertaa login` to authenticate or set VERTAAUX_TOKEN environment variable.");
44
+ process.exit(ExitCode.ERROR);
45
+ }
46
+ // Load config for API base (supports --config global option)
47
+ const config = await resolveConfig(options.configPath);
48
+ const apiBase = resolveApiBase(options.base);
49
+ const outputDir = options.output || DEFAULT_OUTPUT_DIR;
50
+ const spinner = ora(`Downloading audit ${jobId}...`).start();
51
+ try {
52
+ // Build request URL
53
+ let url = `${apiBase}/sync/download/${jobId}`;
54
+ if (options.baseline) {
55
+ url += "?include_baseline=true";
56
+ }
57
+ // Make API request
58
+ const response = await fetch(url, {
59
+ method: "GET",
60
+ headers: {
61
+ "Content-Type": "application/json",
62
+ "X-API-Key": token,
63
+ },
64
+ });
65
+ if (!response.ok) {
66
+ const error = await response.json().catch(() => ({ error: { message: response.statusText } }));
67
+ throw new Error(error.error?.message || `HTTP ${response.status}`);
68
+ }
69
+ const result = (await response.json());
70
+ if (!result.success) {
71
+ throw new Error(result.error?.message || "Download failed");
72
+ }
73
+ // Create output directory
74
+ const resolvedOutputDir = path.resolve(process.cwd(), outputDir);
75
+ if (!fs.existsSync(resolvedOutputDir)) {
76
+ fs.mkdirSync(resolvedOutputDir, { recursive: true });
77
+ }
78
+ // Save audit results
79
+ if (result.audit) {
80
+ const auditPath = path.join(resolvedOutputDir, `audit-${jobId}.json`);
81
+ // Check for existing file
82
+ if (fs.existsSync(auditPath) && !options.force) {
83
+ if (isInteractive()) {
84
+ const overwrite = await confirmAction(`File ${auditPath} already exists. Overwrite?`, false);
85
+ if (!overwrite) {
86
+ spinner.info("Skipping audit results (file exists).");
87
+ }
88
+ else {
89
+ fs.writeFileSync(auditPath, JSON.stringify(result.audit, null, 2), "utf-8");
90
+ }
91
+ }
92
+ else {
93
+ spinner.warn(`Skipping ${auditPath} (already exists, use --force to overwrite)`);
94
+ }
95
+ }
96
+ else {
97
+ fs.writeFileSync(auditPath, JSON.stringify(result.audit, null, 2), "utf-8");
98
+ }
99
+ }
100
+ // Save baseline if included
101
+ if (options.baseline && result.baseline) {
102
+ const baselinePath = path.join(resolvedOutputDir, "baseline.json");
103
+ // Check for existing baseline
104
+ if (fs.existsSync(baselinePath) && !options.force) {
105
+ if (isInteractive()) {
106
+ const overwrite = await confirmAction(`Baseline file already exists. Overwrite?`, false);
107
+ if (!overwrite) {
108
+ spinner.info("Skipping baseline (file exists).");
109
+ }
110
+ else {
111
+ await saveBaseline(result.baseline, baselinePath);
112
+ }
113
+ }
114
+ else {
115
+ spinner.warn(`Skipping baseline (already exists, use --force to overwrite)`);
116
+ }
117
+ }
118
+ else {
119
+ await saveBaseline(result.baseline, baselinePath);
120
+ }
121
+ }
122
+ // Save artifacts if included
123
+ if (result.artifacts) {
124
+ const artifactsDir = path.join(resolvedOutputDir, "artifacts", jobId);
125
+ if (!fs.existsSync(artifactsDir)) {
126
+ fs.mkdirSync(artifactsDir, { recursive: true });
127
+ }
128
+ for (const [filename, content] of Object.entries(result.artifacts)) {
129
+ let filePath;
130
+ try {
131
+ filePath = assertPathContainment(filename, artifactsDir);
132
+ }
133
+ catch {
134
+ console.error(`Security: Rejected artifact "${filename}" -- path traversal outside output directory.`);
135
+ continue;
136
+ }
137
+ const ext = path.extname(filename).toLowerCase();
138
+ const isBinary = [".png", ".jpg", ".jpeg", ".gif", ".zip"].includes(ext);
139
+ if (isBinary) {
140
+ fs.writeFileSync(filePath, Buffer.from(content, "base64"));
141
+ }
142
+ else {
143
+ fs.writeFileSync(filePath, content, "utf-8");
144
+ }
145
+ }
146
+ }
147
+ spinner.succeed("Download complete!");
148
+ console.error("");
149
+ console.error(` Job ID: ${result.job_id}`);
150
+ console.error(` Output: ${resolvedOutputDir}`);
151
+ if (result.audit) {
152
+ console.error(` Status: ${result.audit.status}`);
153
+ if (result.audit.url) {
154
+ console.error(` URL: ${result.audit.url}`);
155
+ }
156
+ }
157
+ if (options.baseline && result.baseline) {
158
+ console.error(` Baseline: ${result.baseline.issues.length} issues`);
159
+ }
160
+ console.error("");
161
+ }
162
+ catch (error) {
163
+ spinner.fail("Download failed");
164
+ console.error(chalk.red("Error:"), error instanceof Error ? error.message : String(error));
165
+ process.exit(ExitCode.ERROR);
166
+ }
167
+ }
168
+ /**
169
+ * Register the download command with the Commander program.
170
+ */
171
+ export function registerDownloadCommand(program) {
172
+ program
173
+ .command("download <job-id>")
174
+ .description("Download audit results from VertaaUX cloud")
175
+ .option("--baseline", "Also download baseline file")
176
+ .option("-o, --output <path>", "Output directory (default: .vertaaux)")
177
+ .option("-b, --base <url>", "API base URL")
178
+ .option("-f, --force", "Overwrite existing files without prompting")
179
+ .action(async (jobId, options, command) => {
180
+ const globalOpts = command.optsWithGlobals();
181
+ await handleDownload(jobId, { ...options, configPath: globalOpts.config });
182
+ });
183
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Explain command for VertaaUX CLI.
3
+ *
4
+ * Shows full evidence bundle for a specific finding:
5
+ * - Description
6
+ * - Selector
7
+ * - WCAG reference
8
+ * - Recommendation
9
+ * - Related artifacts (screenshots, DOM)
10
+ *
11
+ * Supports loading issues from:
12
+ * - Active job via --job flag
13
+ * - Local JSON file via --file flag
14
+ * - Recent audits from .vertaaux/recent.json
15
+ */
16
+ import { Command } from "commander";
17
+ import type { Issue } from "../baseline/hash.js";
18
+ /**
19
+ * Extended issue type with additional evidence fields.
20
+ */
21
+ export interface EvidenceIssue extends Issue {
22
+ /** DOM snippet showing the issue */
23
+ html?: string;
24
+ /** Element HTML snippet */
25
+ element?: string;
26
+ /** Screenshot path or URL */
27
+ screenshot?: string;
28
+ /** Help URL for the rule */
29
+ helpUrl?: string;
30
+ }
31
+ /**
32
+ * Format a full evidence bundle for display.
33
+ *
34
+ * @param issue - Issue with evidence
35
+ * @returns Formatted string for terminal display
36
+ */
37
+ export declare function formatEvidenceBundle(issue: EvidenceIssue): string;
38
+ /**
39
+ * Format evidence as JSON.
40
+ */
41
+ export declare function formatEvidenceJson(issue: EvidenceIssue): string;
42
+ /**
43
+ * Export for reuse in fix-wizard.
44
+ *
45
+ * @param issue - Issue to explain
46
+ * @returns Formatted evidence string
47
+ */
48
+ export declare function explainIssue(issue: EvidenceIssue): string;
49
+ /**
50
+ * Command options for explain.
51
+ */
52
+ export interface ExplainCommandOptions {
53
+ job?: string;
54
+ file?: string;
55
+ format?: "json" | "human";
56
+ base?: string;
57
+ }
58
+ /**
59
+ * Register the explain command with the Commander program.
60
+ */
61
+ export declare function registerExplainCommand(program: Command): void;
62
+ //# sourceMappingURL=explain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"explain.d.ts","sourceRoot":"","sources":["../../src/commands/explain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASpC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAEjD;;GAEG;AACH,MAAM,WAAW,aAAc,SAAQ,KAAK;IAC1C,oCAAoC;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,6BAA6B;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4BAA4B;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAyED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAyDjE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAgB/D;AAUD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAEzD;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAgK7D"}
@@ -0,0 +1,302 @@
1
+ /**
2
+ * Explain command for VertaaUX CLI.
3
+ *
4
+ * Shows full evidence bundle for a specific finding:
5
+ * - Description
6
+ * - Selector
7
+ * - WCAG reference
8
+ * - Recommendation
9
+ * - Related artifacts (screenshots, DOM)
10
+ *
11
+ * Supports loading issues from:
12
+ * - Active job via --job flag
13
+ * - Local JSON file via --file flag
14
+ * - Recent audits from .vertaaux/recent.json
15
+ */
16
+ import fs from "fs";
17
+ import path from "path";
18
+ import chalk from "chalk";
19
+ import { ExitCode } from "../utils/exit-codes.js";
20
+ import { resolveApiBase, getApiKey, apiRequest } from "../utils/client.js";
21
+ import { resolveConfig } from "../config/loader.js";
22
+ import { writeJsonOutput, writeOutput } from "../output/envelope.js";
23
+ import { resolveCommandFormat } from "../output/formats.js";
24
+ /**
25
+ * Path to recent audits cache.
26
+ */
27
+ const RECENT_AUDITS_PATH = ".vertaaux/recent.json";
28
+ /**
29
+ * Load recent audits from cache file.
30
+ */
31
+ function loadRecentAudits() {
32
+ const filePath = path.resolve(process.cwd(), RECENT_AUDITS_PATH);
33
+ if (!fs.existsSync(filePath)) {
34
+ return [];
35
+ }
36
+ try {
37
+ const content = fs.readFileSync(filePath, "utf-8");
38
+ return JSON.parse(content);
39
+ }
40
+ catch {
41
+ return [];
42
+ }
43
+ }
44
+ /**
45
+ * Normalize issues from various API response formats.
46
+ */
47
+ function normalizeIssues(issues) {
48
+ if (Array.isArray(issues))
49
+ return issues;
50
+ if (issues && typeof issues === "object") {
51
+ const values = Object.values(issues);
52
+ return values.flatMap((value) => Array.isArray(value) ? value : []);
53
+ }
54
+ return [];
55
+ }
56
+ /**
57
+ * Get display-friendly rule ID from issue.
58
+ */
59
+ function getRuleId(issue) {
60
+ return issue.ruleId || issue.rule_id || issue.id || "unknown";
61
+ }
62
+ /**
63
+ * Get severity label with appropriate color.
64
+ */
65
+ function coloredSeverity(severity) {
66
+ const sev = (severity || "info").toLowerCase();
67
+ switch (sev) {
68
+ case "critical":
69
+ case "error":
70
+ return chalk.red.bold(sev.toUpperCase());
71
+ case "serious":
72
+ case "warning":
73
+ return chalk.yellow.bold(sev.toUpperCase());
74
+ case "moderate":
75
+ case "minor":
76
+ return chalk.cyan(sev.toUpperCase());
77
+ default:
78
+ return chalk.dim(sev.toUpperCase());
79
+ }
80
+ }
81
+ /**
82
+ * Format a full evidence bundle for display.
83
+ *
84
+ * @param issue - Issue with evidence
85
+ * @returns Formatted string for terminal display
86
+ */
87
+ export function formatEvidenceBundle(issue) {
88
+ const lines = [];
89
+ // Header
90
+ const ruleId = getRuleId(issue);
91
+ const severity = coloredSeverity(issue.severity);
92
+ lines.push(chalk.bold(`ISSUE: ${ruleId} (${severity})`));
93
+ lines.push("");
94
+ // Description
95
+ lines.push(chalk.cyan.bold("DESCRIPTION"));
96
+ lines.push(issue.description || issue.title || "No description available");
97
+ lines.push("");
98
+ // Selector
99
+ if (issue.selector) {
100
+ lines.push(chalk.cyan.bold("SELECTOR"));
101
+ lines.push(chalk.gray(issue.selector));
102
+ lines.push("");
103
+ }
104
+ // WCAG Reference
105
+ if (issue.wcag_reference) {
106
+ lines.push(chalk.cyan.bold("WCAG REFERENCE"));
107
+ lines.push(issue.wcag_reference);
108
+ lines.push("");
109
+ }
110
+ // Recommendation
111
+ const recommendation = issue.recommendation || issue.recommended_fix;
112
+ if (recommendation) {
113
+ lines.push(chalk.cyan.bold("RECOMMENDATION"));
114
+ lines.push(recommendation);
115
+ lines.push("");
116
+ }
117
+ // Evidence section
118
+ const hasEvidence = issue.screenshot || issue.html || issue.element || issue.helpUrl;
119
+ if (hasEvidence) {
120
+ lines.push(chalk.cyan.bold("EVIDENCE"));
121
+ if (issue.screenshot) {
122
+ lines.push(`- Screenshot: ${chalk.underline(issue.screenshot)}`);
123
+ }
124
+ if (issue.html || issue.element) {
125
+ const snippet = issue.html || issue.element || "";
126
+ lines.push(`- DOM Snapshot: ${chalk.dim(truncate(snippet, 100))}`);
127
+ }
128
+ if (issue.helpUrl) {
129
+ lines.push(`- Help: ${chalk.underline(issue.helpUrl)}`);
130
+ }
131
+ }
132
+ return lines.join("\n");
133
+ }
134
+ /**
135
+ * Format evidence as JSON.
136
+ */
137
+ export function formatEvidenceJson(issue) {
138
+ const output = {
139
+ ruleId: getRuleId(issue),
140
+ severity: issue.severity,
141
+ category: issue.category,
142
+ description: issue.description || issue.title,
143
+ selector: issue.selector,
144
+ wcagReference: issue.wcag_reference,
145
+ recommendation: issue.recommendation || issue.recommended_fix,
146
+ evidence: {
147
+ screenshot: issue.screenshot,
148
+ html: issue.html || issue.element,
149
+ helpUrl: issue.helpUrl,
150
+ },
151
+ };
152
+ return JSON.stringify(output, null, 2);
153
+ }
154
+ /**
155
+ * Truncate string with ellipsis.
156
+ */
157
+ function truncate(str, maxLength) {
158
+ if (str.length <= maxLength)
159
+ return str;
160
+ return str.slice(0, maxLength - 3) + "...";
161
+ }
162
+ /**
163
+ * Export for reuse in fix-wizard.
164
+ *
165
+ * @param issue - Issue to explain
166
+ * @returns Formatted evidence string
167
+ */
168
+ export function explainIssue(issue) {
169
+ return formatEvidenceBundle(issue);
170
+ }
171
+ /**
172
+ * Register the explain command with the Commander program.
173
+ */
174
+ export function registerExplainCommand(program) {
175
+ program
176
+ .command("explain <finding-id>")
177
+ .description("Show full evidence bundle for a finding")
178
+ .option("--job <job-id>", "Job ID containing the finding")
179
+ .option("--file <path>", "Load findings from local JSON file")
180
+ .option("-f, --format <format>", "Output format: json | human", "human")
181
+ .action(async (findingId, options, command) => {
182
+ try {
183
+ // Load config (supports --config global option)
184
+ const globalOpts = command.optsWithGlobals();
185
+ const config = await resolveConfig(globalOpts.config);
186
+ // Validate format using per-command registry
187
+ const machineMode = globalOpts.machine || false;
188
+ const format = resolveCommandFormat("explain", options.format, machineMode);
189
+ let issue = null;
190
+ if (options.file) {
191
+ // Load from local file
192
+ const filePath = path.resolve(process.cwd(), options.file);
193
+ if (!fs.existsSync(filePath)) {
194
+ console.error(`Error: File not found: ${filePath}`);
195
+ process.exit(ExitCode.ERROR);
196
+ }
197
+ const content = fs.readFileSync(filePath, "utf-8");
198
+ const data = JSON.parse(content);
199
+ // Handle different file structures
200
+ let issues;
201
+ if (Array.isArray(data)) {
202
+ issues = data;
203
+ }
204
+ else if (data.issues) {
205
+ issues = normalizeIssues(data.issues);
206
+ }
207
+ else {
208
+ // Single issue object
209
+ if (data.id === findingId || data.ruleId === findingId) {
210
+ issue = data;
211
+ }
212
+ issues = [];
213
+ }
214
+ if (!issue) {
215
+ issue =
216
+ issues.find((i) => i.id === findingId ||
217
+ getRuleId(i) === findingId ||
218
+ i.id?.startsWith(findingId) ||
219
+ getRuleId(i).startsWith(findingId)) || null;
220
+ }
221
+ }
222
+ else if (options.job) {
223
+ // Fetch from API
224
+ const base = resolveApiBase(options.base);
225
+ const apiKey = getApiKey(config.apiKey);
226
+ const result = await apiRequest(base, `/audit/${options.job}`, { method: "GET" }, apiKey);
227
+ const issues = normalizeIssues(result.issues);
228
+ issue =
229
+ issues.find((i) => i.id === findingId ||
230
+ getRuleId(i) === findingId ||
231
+ i.id?.startsWith(findingId) ||
232
+ getRuleId(i).startsWith(findingId)) || null;
233
+ }
234
+ else {
235
+ // Try recent audits
236
+ const recent = loadRecentAudits();
237
+ if (recent.length === 0) {
238
+ console.error("Error: No recent audits found. Provide --job or --file option.");
239
+ process.exit(ExitCode.ERROR);
240
+ }
241
+ // Try each recent audit
242
+ const base = resolveApiBase(options.base);
243
+ const apiKey = getApiKey(config.apiKey);
244
+ for (const audit of recent) {
245
+ try {
246
+ const result = await apiRequest(base, `/audit/${audit.jobId}`, { method: "GET" }, apiKey);
247
+ const issues = normalizeIssues(result.issues);
248
+ issue =
249
+ issues.find((i) => i.id === findingId ||
250
+ getRuleId(i) === findingId ||
251
+ i.id?.startsWith(findingId) ||
252
+ getRuleId(i).startsWith(findingId)) || null;
253
+ if (issue)
254
+ break;
255
+ }
256
+ catch {
257
+ // Try next audit
258
+ continue;
259
+ }
260
+ }
261
+ }
262
+ if (!issue) {
263
+ console.error(`Error: Finding "${findingId}" not found.`);
264
+ if (options.job) {
265
+ console.error(`Looked in job: ${options.job}`);
266
+ }
267
+ else if (options.file) {
268
+ console.error(`Looked in file: ${options.file}`);
269
+ }
270
+ else {
271
+ console.error("Try specifying --job or --file to narrow the search.");
272
+ }
273
+ process.exit(ExitCode.ERROR);
274
+ }
275
+ // Output
276
+ if (format === "json") {
277
+ const evidenceData = {
278
+ ruleId: getRuleId(issue),
279
+ severity: issue.severity,
280
+ category: issue.category,
281
+ description: issue.description || issue.title,
282
+ selector: issue.selector,
283
+ wcagReference: issue.wcag_reference,
284
+ recommendation: issue.recommendation || issue.recommended_fix,
285
+ evidence: {
286
+ screenshot: issue.screenshot,
287
+ html: issue.html || issue.element,
288
+ helpUrl: issue.helpUrl,
289
+ },
290
+ };
291
+ writeJsonOutput(evidenceData, "explain");
292
+ }
293
+ else {
294
+ writeOutput(formatEvidenceBundle(issue));
295
+ }
296
+ }
297
+ catch (error) {
298
+ console.error("Error:", error instanceof Error ? error.message : String(error));
299
+ process.exit(ExitCode.ERROR);
300
+ }
301
+ });
302
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Init command for VertaaUX CLI.
3
+ *
4
+ * Creates project configuration with guided wizard or defaults.
5
+ * Generates CI templates for popular platforms.
6
+ */
7
+ import type { Command } from "commander";
8
+ /**
9
+ * Register the init command with the Commander program.
10
+ */
11
+ export declare function registerInitCommand(program: Command): void;
12
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA6NzC;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAgC1D"}