@vertaaux/cli 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +58 -2
  3. package/dist/auth/device-flow.d.ts.map +1 -1
  4. package/dist/auth/device-flow.js +46 -14
  5. package/dist/commands/audit.d.ts +2 -0
  6. package/dist/commands/audit.d.ts.map +1 -1
  7. package/dist/commands/audit.js +167 -8
  8. package/dist/commands/client.d.ts +14 -0
  9. package/dist/commands/client.d.ts.map +1 -0
  10. package/dist/commands/client.js +362 -0
  11. package/dist/commands/compare.d.ts +20 -0
  12. package/dist/commands/compare.d.ts.map +1 -0
  13. package/dist/commands/compare.js +335 -0
  14. package/dist/commands/doc.d.ts +18 -0
  15. package/dist/commands/doc.d.ts.map +1 -0
  16. package/dist/commands/doc.js +161 -0
  17. package/dist/commands/download.d.ts.map +1 -1
  18. package/dist/commands/download.js +9 -8
  19. package/dist/commands/drift.d.ts +15 -0
  20. package/dist/commands/drift.d.ts.map +1 -0
  21. package/dist/commands/drift.js +309 -0
  22. package/dist/commands/explain.d.ts +14 -33
  23. package/dist/commands/explain.d.ts.map +1 -1
  24. package/dist/commands/explain.js +277 -179
  25. package/dist/commands/fix-plan.d.ts +15 -0
  26. package/dist/commands/fix-plan.d.ts.map +1 -0
  27. package/dist/commands/fix-plan.js +182 -0
  28. package/dist/commands/patch-review.d.ts +14 -0
  29. package/dist/commands/patch-review.d.ts.map +1 -0
  30. package/dist/commands/patch-review.js +200 -0
  31. package/dist/commands/protect.d.ts +16 -0
  32. package/dist/commands/protect.d.ts.map +1 -0
  33. package/dist/commands/protect.js +323 -0
  34. package/dist/commands/release-notes.d.ts +17 -0
  35. package/dist/commands/release-notes.d.ts.map +1 -0
  36. package/dist/commands/release-notes.js +145 -0
  37. package/dist/commands/report.d.ts +15 -0
  38. package/dist/commands/report.d.ts.map +1 -0
  39. package/dist/commands/report.js +214 -0
  40. package/dist/commands/suggest.d.ts +18 -0
  41. package/dist/commands/suggest.d.ts.map +1 -0
  42. package/dist/commands/suggest.js +152 -0
  43. package/dist/commands/triage.d.ts +17 -0
  44. package/dist/commands/triage.d.ts.map +1 -0
  45. package/dist/commands/triage.js +205 -0
  46. package/dist/commands/upload.d.ts.map +1 -1
  47. package/dist/commands/upload.js +8 -7
  48. package/dist/index.js +62 -25
  49. package/dist/output/formats.d.ts.map +1 -1
  50. package/dist/output/formats.js +18 -2
  51. package/dist/output/human.d.ts +1 -10
  52. package/dist/output/human.d.ts.map +1 -1
  53. package/dist/output/human.js +26 -98
  54. package/dist/policy/sync.d.ts +67 -0
  55. package/dist/policy/sync.d.ts.map +1 -0
  56. package/dist/policy/sync.js +147 -0
  57. package/dist/prompts/command-catalog.d.ts +46 -0
  58. package/dist/prompts/command-catalog.d.ts.map +1 -0
  59. package/dist/prompts/command-catalog.js +187 -0
  60. package/dist/ui/spinner.d.ts +10 -35
  61. package/dist/ui/spinner.d.ts.map +1 -1
  62. package/dist/ui/spinner.js +11 -58
  63. package/dist/ui/table.d.ts +1 -18
  64. package/dist/ui/table.d.ts.map +1 -1
  65. package/dist/ui/table.js +56 -163
  66. package/dist/utils/ai-error.d.ts +48 -0
  67. package/dist/utils/ai-error.d.ts.map +1 -0
  68. package/dist/utils/ai-error.js +190 -0
  69. package/dist/utils/detect-env.d.ts +6 -8
  70. package/dist/utils/detect-env.d.ts.map +1 -1
  71. package/dist/utils/detect-env.js +6 -25
  72. package/dist/utils/stdin.d.ts +50 -0
  73. package/dist/utils/stdin.d.ts.map +1 -0
  74. package/dist/utils/stdin.js +93 -0
  75. package/package.json +11 -7
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Fix-plan command for VertaaUX CLI.
3
+ *
4
+ * Accepts full audit JSON (via stdin, --file, or --job) and calls the
5
+ * LLM fix-plan endpoint to produce a structured remediation plan with
6
+ * ordered steps, effort estimates, and code hints.
7
+ *
8
+ * Examples:
9
+ * vertaa audit https://example.com --json | vertaa fix-plan
10
+ * vertaa fix-plan --job abc123
11
+ * vertaa fix-plan --file audit.json --json
12
+ */
13
+ import chalk from "chalk";
14
+ import { ExitCode } from "../utils/exit-codes.js";
15
+ import { resolveApiBase, getApiKey, apiRequest } from "../utils/client.js";
16
+ import { resolveConfig } from "../config/loader.js";
17
+ import { writeJsonOutput, writeOutput } from "../output/envelope.js";
18
+ import { resolveCommandFormat } from "../output/formats.js";
19
+ import { createSpinner, succeedSpinner } from "../ui/spinner.js";
20
+ import { readJsonInput } from "../utils/stdin.js";
21
+ import { handleAiCommandError, AI_TIMEOUT_MS } from "../utils/ai-error.js";
22
+ // ---------------------------------------------------------------------------
23
+ // Helpers
24
+ // ---------------------------------------------------------------------------
25
+ function normalizeIssues(issues) {
26
+ let list;
27
+ if (Array.isArray(issues)) {
28
+ list = issues;
29
+ }
30
+ else if (issues && typeof issues === "object") {
31
+ list = Object.values(issues).flatMap((v) => Array.isArray(v) ? v : []);
32
+ }
33
+ else {
34
+ return [];
35
+ }
36
+ return list.map((raw) => {
37
+ const i = raw;
38
+ return {
39
+ id: i.id || i.ruleId || i.rule_id || null,
40
+ title: i.title || i.description || null,
41
+ description: i.description || null,
42
+ severity: i.severity || null,
43
+ category: i.category || null,
44
+ selector: i.selector || null,
45
+ wcag_reference: i.wcag_reference || null,
46
+ recommendation: i.recommendation || i.recommended_fix || null,
47
+ };
48
+ });
49
+ }
50
+ // ---------------------------------------------------------------------------
51
+ // Formatters
52
+ // ---------------------------------------------------------------------------
53
+ const SEVERITY_COLORS = {
54
+ critical: chalk.red.bold,
55
+ high: chalk.red,
56
+ medium: chalk.yellow,
57
+ low: chalk.cyan,
58
+ };
59
+ const EFFORT_LABELS = {
60
+ trivial: chalk.green("trivial"),
61
+ small: chalk.green("small"),
62
+ medium: chalk.yellow("medium"),
63
+ large: chalk.red("large"),
64
+ };
65
+ const FIX_TYPE_LABELS = {
66
+ code: chalk.blue("code"),
67
+ config: chalk.magenta("config"),
68
+ content: chalk.cyan("content"),
69
+ design: chalk.yellow("design"),
70
+ };
71
+ function formatFixPlanHuman(data) {
72
+ const lines = [];
73
+ lines.push(chalk.bold(`Remediation Plan (${data.items.length} items)`));
74
+ lines.push(chalk.dim(`Estimated total effort: ${data.estimated_total_effort}`));
75
+ lines.push("");
76
+ for (let idx = 0; idx < data.items.length; idx++) {
77
+ const item = data.items[idx];
78
+ const severityFn = SEVERITY_COLORS[item.severity] || chalk.dim;
79
+ const effort = EFFORT_LABELS[item.effort] || chalk.dim(item.effort);
80
+ const fixType = FIX_TYPE_LABELS[item.fix_type] || chalk.dim(item.fix_type);
81
+ lines.push(`${chalk.bold(`${idx + 1}.`)} ${severityFn(`[${item.severity}]`)} ${chalk.bold(item.title)}`);
82
+ lines.push(` Effort: ${effort} Type: ${fixType}${item.id ? chalk.dim(` (${item.id})`) : ""}`);
83
+ for (let s = 0; s < item.steps.length; s++) {
84
+ lines.push(` ${chalk.dim(`${s + 1})`)} ${item.steps[s]}`);
85
+ }
86
+ if (item.code_hint) {
87
+ lines.push(` ${chalk.dim("Hint:")} ${chalk.italic(item.code_hint)}`);
88
+ }
89
+ lines.push("");
90
+ }
91
+ return lines.join("\n");
92
+ }
93
+ // ---------------------------------------------------------------------------
94
+ // Command Registration
95
+ // ---------------------------------------------------------------------------
96
+ export function registerFixPlanCommand(program) {
97
+ program
98
+ .command("fix-plan")
99
+ .description("Generate a structured remediation plan from audit findings")
100
+ .option("--job <job-id>", "Fetch audit data from a job ID")
101
+ .option("--file <path>", "Load audit JSON from file")
102
+ .option("-f, --format <format>", "Output format: json | human")
103
+ .addHelpText("after", `
104
+ Examples:
105
+ vertaa audit https://example.com --json | vertaa fix-plan
106
+ vertaa fix-plan --job abc123
107
+ vertaa fix-plan --file audit.json --json
108
+ vertaa audit ... --json | vertaa fix-plan --json | jq .
109
+ `)
110
+ .action(async (options, command) => {
111
+ try {
112
+ const globalOpts = command.optsWithGlobals();
113
+ const config = await resolveConfig(globalOpts.config);
114
+ const machineMode = globalOpts.machine || false;
115
+ const format = resolveCommandFormat("fix-plan", options.format, machineMode);
116
+ // Resolve audit data
117
+ let auditPayload;
118
+ if (options.job) {
119
+ const base = resolveApiBase(globalOpts.base);
120
+ const apiKey = getApiKey(config.apiKey);
121
+ const result = await apiRequest(base, `/audit/${options.job}`, { method: "GET" }, apiKey);
122
+ const issues = normalizeIssues(result.issues);
123
+ auditPayload = {
124
+ job_id: result.job_id || options.job,
125
+ url: result.url || null,
126
+ scores: result.scores || null,
127
+ issues,
128
+ };
129
+ }
130
+ else {
131
+ const input = await readJsonInput(options.file);
132
+ if (!input) {
133
+ console.error("Error: No audit data provided.");
134
+ console.error("Usage:");
135
+ console.error(" vertaa audit https://example.com --json | vertaa fix-plan");
136
+ console.error(" vertaa fix-plan --job <job-id>");
137
+ console.error(" vertaa fix-plan --file audit.json");
138
+ process.exit(ExitCode.ERROR);
139
+ }
140
+ const data = input;
141
+ const innerData = (data.data && typeof data.data === "object" ? data.data : data);
142
+ const issues = normalizeIssues(innerData.issues);
143
+ auditPayload = {
144
+ job_id: innerData.job_id || null,
145
+ url: innerData.url || null,
146
+ scores: innerData.scores || null,
147
+ issues,
148
+ };
149
+ }
150
+ if (!Array.isArray(auditPayload.issues) ||
151
+ auditPayload.issues.length === 0) {
152
+ console.error("Error: No issues found in audit data.");
153
+ process.exit(ExitCode.ERROR);
154
+ }
155
+ // Auth check
156
+ const base = resolveApiBase(globalOpts.base);
157
+ const apiKey = getApiKey(config.apiKey);
158
+ // Call LLM fix-plan API
159
+ const spinner = createSpinner("Generating remediation plan...");
160
+ try {
161
+ const response = await Promise.race([
162
+ apiRequest(base, "/cli/ai/fix-plan", { method: "POST", body: { audit: auditPayload } }, apiKey),
163
+ new Promise((_, reject) => setTimeout(() => reject(new Error("LLM request timed out")), AI_TIMEOUT_MS)),
164
+ ]);
165
+ succeedSpinner(spinner, "Plan ready");
166
+ if (format === "json") {
167
+ writeJsonOutput(response.data, "fix-plan");
168
+ }
169
+ else {
170
+ writeOutput(formatFixPlanHuman(response.data));
171
+ }
172
+ }
173
+ catch (error) {
174
+ handleAiCommandError(error, "fix-plan", spinner);
175
+ }
176
+ }
177
+ catch (error) {
178
+ console.error("Error:", error instanceof Error ? error.message : String(error));
179
+ process.exit(ExitCode.ERROR);
180
+ }
181
+ });
182
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Patch-review command for VertaaUX CLI.
3
+ *
4
+ * Reads a diff from stdin and (optionally) findings from a job,
5
+ * calls the LLM patch-review endpoint to produce a SAFE/UNSAFE/NEEDS_REVIEW verdict.
6
+ *
7
+ * Examples:
8
+ * gh pr diff 123 | vertaa patch-review --job abc123
9
+ * git diff HEAD~1 | vertaa patch-review --job abc123
10
+ * cat fix.patch | vertaa patch-review --findings findings.json
11
+ */
12
+ import { Command } from "commander";
13
+ export declare function registerPatchReviewCommand(program: Command): void;
14
+ //# sourceMappingURL=patch-review.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patch-review.d.ts","sourceRoot":"","sources":["../../src/commands/patch-review.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmIpC,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA2HjE"}
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Patch-review command for VertaaUX CLI.
3
+ *
4
+ * Reads a diff from stdin and (optionally) findings from a job,
5
+ * calls the LLM patch-review endpoint to produce a SAFE/UNSAFE/NEEDS_REVIEW verdict.
6
+ *
7
+ * Examples:
8
+ * gh pr diff 123 | vertaa patch-review --job abc123
9
+ * git diff HEAD~1 | vertaa patch-review --job abc123
10
+ * cat fix.patch | vertaa patch-review --findings findings.json
11
+ */
12
+ import chalk from "chalk";
13
+ import { ExitCode } from "../utils/exit-codes.js";
14
+ import { resolveApiBase, getApiKey, apiRequest } from "../utils/client.js";
15
+ import { resolveConfig } from "../config/loader.js";
16
+ import { writeJsonOutput, writeOutput } from "../output/envelope.js";
17
+ import { resolveCommandFormat } from "../output/formats.js";
18
+ import { createSpinner, succeedSpinner } from "../ui/spinner.js";
19
+ import { readTextInput, readJsonInput } from "../utils/stdin.js";
20
+ import { handleAiCommandError, AI_TIMEOUT_MS } from "../utils/ai-error.js";
21
+ // ---------------------------------------------------------------------------
22
+ // Helpers
23
+ // ---------------------------------------------------------------------------
24
+ function normalizeIssues(issues) {
25
+ let list;
26
+ if (Array.isArray(issues)) {
27
+ list = issues;
28
+ }
29
+ else if (issues && typeof issues === "object") {
30
+ list = Object.values(issues).flatMap((v) => Array.isArray(v) ? v : []);
31
+ }
32
+ else {
33
+ return [];
34
+ }
35
+ return list.map((raw) => {
36
+ const i = raw;
37
+ return {
38
+ id: i.id || i.ruleId || i.rule_id || null,
39
+ title: i.title || i.description || null,
40
+ description: i.description || null,
41
+ severity: i.severity || null,
42
+ category: i.category || null,
43
+ selector: i.selector || null,
44
+ wcag_reference: i.wcag_reference || null,
45
+ recommendation: i.recommendation || i.recommended_fix || null,
46
+ };
47
+ });
48
+ }
49
+ // ---------------------------------------------------------------------------
50
+ // Formatters
51
+ // ---------------------------------------------------------------------------
52
+ const VERDICT_DISPLAY = {
53
+ SAFE: chalk.green.bold("SAFE"),
54
+ UNSAFE: chalk.red.bold("UNSAFE"),
55
+ NEEDS_REVIEW: chalk.yellow.bold("NEEDS REVIEW"),
56
+ };
57
+ const CONCERN_SEVERITY = {
58
+ critical: chalk.red("critical"),
59
+ warning: chalk.yellow("warning"),
60
+ info: chalk.cyan("info"),
61
+ };
62
+ function formatPatchReviewHuman(data) {
63
+ const lines = [];
64
+ const verdictDisplay = VERDICT_DISPLAY[data.verdict] || chalk.bold(data.verdict);
65
+ lines.push(`Verdict: ${verdictDisplay} (confidence: ${data.confidence}%)`);
66
+ lines.push("");
67
+ lines.push(data.summary);
68
+ if (data.concerns.length > 0) {
69
+ lines.push("");
70
+ lines.push(chalk.bold("Concerns:"));
71
+ for (const concern of data.concerns) {
72
+ const sev = CONCERN_SEVERITY[concern.severity] || chalk.dim(concern.severity);
73
+ const loc = concern.location ? chalk.dim(` @ ${concern.location}`) : "";
74
+ lines.push(` ${sev}${loc}: ${concern.description}`);
75
+ }
76
+ }
77
+ if (data.findings_addressed.length > 0) {
78
+ lines.push("");
79
+ lines.push(chalk.green(`Findings addressed (${data.findings_addressed.length}):`));
80
+ for (const id of data.findings_addressed) {
81
+ lines.push(` ${chalk.green("+")} ${id}`);
82
+ }
83
+ }
84
+ if (data.findings_remaining.length > 0) {
85
+ lines.push("");
86
+ lines.push(chalk.yellow(`Findings remaining (${data.findings_remaining.length}):`));
87
+ for (const id of data.findings_remaining) {
88
+ lines.push(` ${chalk.yellow("-")} ${id}`);
89
+ }
90
+ }
91
+ return lines.join("\n");
92
+ }
93
+ // ---------------------------------------------------------------------------
94
+ // Command Registration
95
+ // ---------------------------------------------------------------------------
96
+ export function registerPatchReviewCommand(program) {
97
+ program
98
+ .command("patch-review")
99
+ .description("Review a patch/diff against audit findings for safety")
100
+ .option("--job <job-id>", "Fetch findings from a job ID")
101
+ .option("--findings <path>", "Load findings JSON from file")
102
+ .option("--diff-file <path>", "Load diff from file instead of stdin")
103
+ .option("-f, --format <format>", "Output format: json | human")
104
+ .addHelpText("after", `
105
+ Examples:
106
+ gh pr diff 123 | vertaa patch-review --job abc123
107
+ git diff HEAD~1 | vertaa patch-review --job abc123
108
+ vertaa patch-review --diff-file fix.patch --findings audit.json
109
+ `)
110
+ .action(async (options, command) => {
111
+ try {
112
+ const globalOpts = command.optsWithGlobals();
113
+ const config = await resolveConfig(globalOpts.config);
114
+ const machineMode = globalOpts.machine || false;
115
+ const dryRun = globalOpts.dryRun || false;
116
+ const format = resolveCommandFormat("patch-review", options.format, machineMode);
117
+ // Read diff content
118
+ let diffContent;
119
+ if (options.diffFile) {
120
+ const fs = await import("fs");
121
+ const path = await import("path");
122
+ const resolved = path.resolve(process.cwd(), options.diffFile);
123
+ if (!fs.existsSync(resolved)) {
124
+ console.error(`Error: Diff file not found: ${options.diffFile}`);
125
+ process.exit(ExitCode.ERROR);
126
+ }
127
+ diffContent = fs.readFileSync(resolved, "utf-8");
128
+ }
129
+ else {
130
+ const text = await readTextInput();
131
+ if (!text) {
132
+ console.error("Error: No diff provided.");
133
+ console.error("Pipe a diff via stdin or use --diff-file:");
134
+ console.error(" gh pr diff 123 | vertaa patch-review --job abc123");
135
+ console.error(" vertaa patch-review --diff-file fix.patch --findings audit.json");
136
+ process.exit(ExitCode.ERROR);
137
+ }
138
+ diffContent = text;
139
+ }
140
+ // Resolve findings
141
+ let findingsList = null;
142
+ let jobId = null;
143
+ if (options.job) {
144
+ const base = resolveApiBase(globalOpts.base);
145
+ const apiKey = getApiKey(config.apiKey);
146
+ const result = await apiRequest(base, `/audit/${options.job}`, { method: "GET" }, apiKey);
147
+ findingsList = normalizeIssues(result.issues);
148
+ jobId = result.job_id || options.job;
149
+ }
150
+ else if (options.findings) {
151
+ const input = await readJsonInput(options.findings);
152
+ if (input) {
153
+ const data = input;
154
+ const innerData = (data.data && typeof data.data === "object" ? data.data : data);
155
+ findingsList = normalizeIssues(innerData.issues);
156
+ jobId = innerData.job_id || null;
157
+ }
158
+ }
159
+ if (dryRun) {
160
+ console.log(chalk.yellow("[dry-run]") + " Would analyze diff against findings");
161
+ console.log(` Diff size: ${diffContent.length} characters`);
162
+ console.log(` Findings: ${findingsList ? findingsList.length : "none"}`);
163
+ console.log(` Job ID: ${jobId || "none"}`);
164
+ return;
165
+ }
166
+ // Auth check
167
+ const base = resolveApiBase(globalOpts.base);
168
+ const apiKey = getApiKey(config.apiKey);
169
+ // Call LLM patch-review API
170
+ const spinner = createSpinner("Reviewing patch...");
171
+ try {
172
+ const response = await Promise.race([
173
+ apiRequest(base, "/cli/ai/patch-review", {
174
+ method: "POST",
175
+ body: {
176
+ diff: diffContent,
177
+ findings: findingsList,
178
+ job_id: jobId,
179
+ },
180
+ }, apiKey),
181
+ new Promise((_, reject) => setTimeout(() => reject(new Error("LLM request timed out")), AI_TIMEOUT_MS)),
182
+ ]);
183
+ succeedSpinner(spinner, "Review complete");
184
+ if (format === "json") {
185
+ writeJsonOutput(response.data, "patch-review");
186
+ }
187
+ else {
188
+ writeOutput(formatPatchReviewHuman(response.data));
189
+ }
190
+ }
191
+ catch (error) {
192
+ handleAiCommandError(error, "patch-review", spinner);
193
+ }
194
+ }
195
+ catch (error) {
196
+ console.error("Error:", error instanceof Error ? error.message : String(error));
197
+ process.exit(ExitCode.ERROR);
198
+ }
199
+ });
200
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Protect command for VertaaUX CLI.
3
+ *
4
+ * Implements the 3-step conversion flow from the command line:
5
+ * 1. Audit - Captures scores (from latest audit or --input)
6
+ * 2. Protect - Generates policy YAML with score thresholds
7
+ * 3. Monitor - Outputs CI snippet for continuous quality gating
8
+ *
9
+ * Usage: vertaa protect [url] [options]
10
+ */
11
+ import { Command } from "commander";
12
+ /**
13
+ * Register the protect command with the Commander program.
14
+ */
15
+ export declare function registerProtectCommand(program: Command): void;
16
+ //# sourceMappingURL=protect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"protect.d.ts","sourceRoot":"","sources":["../../src/commands/protect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA6DpC;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAqC7D"}