@vertaaux/cli 0.2.3 → 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 (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +58 -2
  3. package/dist/auth/device-flow.js +6 -8
  4. package/dist/commands/audit.d.ts +2 -0
  5. package/dist/commands/audit.d.ts.map +1 -1
  6. package/dist/commands/audit.js +165 -6
  7. package/dist/commands/compare.d.ts +20 -0
  8. package/dist/commands/compare.d.ts.map +1 -0
  9. package/dist/commands/compare.js +335 -0
  10. package/dist/commands/doc.d.ts +18 -0
  11. package/dist/commands/doc.d.ts.map +1 -0
  12. package/dist/commands/doc.js +161 -0
  13. package/dist/commands/download.d.ts.map +1 -1
  14. package/dist/commands/download.js +9 -8
  15. package/dist/commands/explain.d.ts +14 -33
  16. package/dist/commands/explain.d.ts.map +1 -1
  17. package/dist/commands/explain.js +277 -179
  18. package/dist/commands/fix-plan.d.ts +15 -0
  19. package/dist/commands/fix-plan.d.ts.map +1 -0
  20. package/dist/commands/fix-plan.js +182 -0
  21. package/dist/commands/patch-review.d.ts +14 -0
  22. package/dist/commands/patch-review.d.ts.map +1 -0
  23. package/dist/commands/patch-review.js +200 -0
  24. package/dist/commands/release-notes.d.ts +17 -0
  25. package/dist/commands/release-notes.d.ts.map +1 -0
  26. package/dist/commands/release-notes.js +145 -0
  27. package/dist/commands/suggest.d.ts +18 -0
  28. package/dist/commands/suggest.d.ts.map +1 -0
  29. package/dist/commands/suggest.js +152 -0
  30. package/dist/commands/triage.d.ts +17 -0
  31. package/dist/commands/triage.d.ts.map +1 -0
  32. package/dist/commands/triage.js +205 -0
  33. package/dist/commands/upload.d.ts.map +1 -1
  34. package/dist/commands/upload.js +8 -7
  35. package/dist/index.js +62 -25
  36. package/dist/output/formats.d.ts.map +1 -1
  37. package/dist/output/formats.js +14 -0
  38. package/dist/output/human.d.ts +1 -10
  39. package/dist/output/human.d.ts.map +1 -1
  40. package/dist/output/human.js +26 -98
  41. package/dist/prompts/command-catalog.d.ts +46 -0
  42. package/dist/prompts/command-catalog.d.ts.map +1 -0
  43. package/dist/prompts/command-catalog.js +187 -0
  44. package/dist/ui/spinner.d.ts +10 -35
  45. package/dist/ui/spinner.d.ts.map +1 -1
  46. package/dist/ui/spinner.js +11 -58
  47. package/dist/ui/table.d.ts +1 -18
  48. package/dist/ui/table.d.ts.map +1 -1
  49. package/dist/ui/table.js +56 -163
  50. package/dist/utils/ai-error.d.ts +48 -0
  51. package/dist/utils/ai-error.d.ts.map +1 -0
  52. package/dist/utils/ai-error.js +190 -0
  53. package/dist/utils/detect-env.d.ts +6 -8
  54. package/dist/utils/detect-env.d.ts.map +1 -1
  55. package/dist/utils/detect-env.js +6 -25
  56. package/dist/utils/stdin.d.ts +50 -0
  57. package/dist/utils/stdin.d.ts.map +1 -0
  58. package/dist/utils/stdin.js +93 -0
  59. package/package.json +9 -5
@@ -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,17 @@
1
+ /**
2
+ * Release-notes command for VertaaUX CLI.
3
+ *
4
+ * Reads diff data (via stdin, --file, or from two --job-a/--job-b jobs)
5
+ * and calls the LLM release-notes endpoint to produce developer-facing
6
+ * and PM-facing release notes.
7
+ *
8
+ * Default output is markdown; use --format json for structured output.
9
+ *
10
+ * Examples:
11
+ * vertaa diff --job-a abc --job-b def --json | vertaa release-notes
12
+ * vertaa release-notes --file diff.json
13
+ * vertaa release-notes --job-a abc --job-b def
14
+ */
15
+ import { Command } from "commander";
16
+ export declare function registerReleaseNotesCommand(program: Command): void;
17
+ //# sourceMappingURL=release-notes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"release-notes.d.ts","sourceRoot":"","sources":["../../src/commands/release-notes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0EpC,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAgHlE"}
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Release-notes command for VertaaUX CLI.
3
+ *
4
+ * Reads diff data (via stdin, --file, or from two --job-a/--job-b jobs)
5
+ * and calls the LLM release-notes endpoint to produce developer-facing
6
+ * and PM-facing release notes.
7
+ *
8
+ * Default output is markdown; use --format json for structured output.
9
+ *
10
+ * Examples:
11
+ * vertaa diff --job-a abc --job-b def --json | vertaa release-notes
12
+ * vertaa release-notes --file diff.json
13
+ * vertaa release-notes --job-a abc --job-b def
14
+ */
15
+ import chalk from "chalk";
16
+ import { ExitCode } from "../utils/exit-codes.js";
17
+ import { resolveApiBase, getApiKey, apiRequest } from "../utils/client.js";
18
+ import { resolveConfig } from "../config/loader.js";
19
+ import { writeJsonOutput, writeOutput } from "../output/envelope.js";
20
+ import { resolveCommandFormat } from "../output/formats.js";
21
+ import { createSpinner, succeedSpinner } from "../ui/spinner.js";
22
+ import { readJsonInput } from "../utils/stdin.js";
23
+ import { handleAiCommandError, AI_TIMEOUT_MS } from "../utils/ai-error.js";
24
+ // ---------------------------------------------------------------------------
25
+ // Formatters
26
+ // ---------------------------------------------------------------------------
27
+ function formatReleaseNotesHuman(data) {
28
+ const lines = [];
29
+ lines.push(chalk.bold(data.headline));
30
+ lines.push("");
31
+ lines.push(chalk.bold.blue("Developer Notes"));
32
+ lines.push(chalk.dim("─".repeat(40)));
33
+ lines.push(data.developer_notes);
34
+ lines.push("");
35
+ lines.push(chalk.bold.green("PM / User-Facing Notes"));
36
+ lines.push(chalk.dim("─".repeat(40)));
37
+ lines.push(data.pm_notes);
38
+ return lines.join("\n");
39
+ }
40
+ function formatReleaseNotesMarkdown(data) {
41
+ const lines = [];
42
+ lines.push(`# ${data.headline}`);
43
+ lines.push("");
44
+ lines.push("## Developer Notes");
45
+ lines.push("");
46
+ lines.push(data.developer_notes);
47
+ lines.push("");
48
+ lines.push("## User-Facing Notes");
49
+ lines.push("");
50
+ lines.push(data.pm_notes);
51
+ return lines.join("\n");
52
+ }
53
+ // ---------------------------------------------------------------------------
54
+ // Command Registration
55
+ // ---------------------------------------------------------------------------
56
+ export function registerReleaseNotesCommand(program) {
57
+ program
58
+ .command("release-notes")
59
+ .description("Generate developer + PM release notes from audit diff data")
60
+ .option("--file <path>", "Load diff JSON from file")
61
+ .option("--job-a <id>", "First audit job ID (baseline)")
62
+ .option("--job-b <id>", "Second audit job ID (current)")
63
+ .option("-f, --format <format>", "Output format: json | human | markdown")
64
+ .addHelpText("after", `
65
+ Examples:
66
+ vertaa diff --job-a abc --job-b def --json | vertaa release-notes
67
+ vertaa release-notes --file diff.json
68
+ vertaa release-notes --job-a abc --job-b def
69
+ `)
70
+ .action(async (options, command) => {
71
+ try {
72
+ const globalOpts = command.optsWithGlobals();
73
+ const config = await resolveConfig(globalOpts.config);
74
+ const machineMode = globalOpts.machine || false;
75
+ const format = resolveCommandFormat("release-notes", options.format, machineMode);
76
+ // Resolve diff data
77
+ let diffPayload;
78
+ if (options.jobA && options.jobB) {
79
+ // Fetch diff from two jobs via the diff API
80
+ const base = resolveApiBase(globalOpts.base);
81
+ const apiKey = getApiKey(config.apiKey);
82
+ const diffResult = await apiRequest(base, `/diff?job_a=${encodeURIComponent(options.jobA)}&job_b=${encodeURIComponent(options.jobB)}`, { method: "GET" }, apiKey);
83
+ diffPayload = diffResult.data;
84
+ }
85
+ else {
86
+ // Read from stdin or --file
87
+ const input = await readJsonInput(options.file);
88
+ if (!input) {
89
+ console.error("Error: No diff data provided.");
90
+ console.error("Usage:");
91
+ console.error(" vertaa diff --job-a abc --job-b def --json | vertaa release-notes");
92
+ console.error(" vertaa release-notes --file diff.json");
93
+ console.error(" vertaa release-notes --job-a abc --job-b def");
94
+ process.exit(ExitCode.ERROR);
95
+ }
96
+ const data = input;
97
+ const innerData = (data.data && typeof data.data === "object" ? data.data : data);
98
+ // Validate shape
99
+ if (!Array.isArray(innerData.new) || !Array.isArray(innerData.fixed)) {
100
+ console.error("Error: Invalid diff data. Expected { new: [...], fixed: [...], summary: {...} }");
101
+ console.error("Pipe from: vertaa diff --job-a abc --job-b def --json | vertaa release-notes");
102
+ process.exit(ExitCode.ERROR);
103
+ }
104
+ diffPayload = {
105
+ new: innerData.new,
106
+ fixed: innerData.fixed,
107
+ present: innerData.present || null,
108
+ summary: innerData.summary || {
109
+ newCount: innerData.new.length,
110
+ fixedCount: innerData.fixed.length,
111
+ presentCount: Array.isArray(innerData.present) ? innerData.present.length : null,
112
+ },
113
+ };
114
+ }
115
+ // Auth check
116
+ const base = resolveApiBase(globalOpts.base);
117
+ const apiKey = getApiKey(config.apiKey);
118
+ // Call LLM release-notes API
119
+ const spinner = createSpinner("Generating release notes...");
120
+ try {
121
+ const response = await Promise.race([
122
+ apiRequest(base, "/cli/ai/release-notes", { method: "POST", body: { diff: diffPayload } }, apiKey),
123
+ new Promise((_, reject) => setTimeout(() => reject(new Error("LLM request timed out")), AI_TIMEOUT_MS)),
124
+ ]);
125
+ succeedSpinner(spinner, "Notes ready");
126
+ if (format === "json") {
127
+ writeJsonOutput(response.data, "release-notes");
128
+ }
129
+ else if (format === "markdown") {
130
+ writeOutput(formatReleaseNotesMarkdown(response.data));
131
+ }
132
+ else {
133
+ writeOutput(formatReleaseNotesHuman(response.data));
134
+ }
135
+ }
136
+ catch (error) {
137
+ handleAiCommandError(error, "release-notes", spinner);
138
+ }
139
+ }
140
+ catch (error) {
141
+ console.error("Error:", error instanceof Error ? error.message : String(error));
142
+ process.exit(ExitCode.ERROR);
143
+ }
144
+ });
145
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Suggest command for VertaaUX CLI.
3
+ *
4
+ * Converts natural language intent into exact CLI command(s).
5
+ * Uses local command catalog matching first, with API fallback
6
+ * for complex intents.
7
+ *
8
+ * Examples:
9
+ * vertaa suggest "check contrast issues"
10
+ * vertaa suggest "compare two pages"
11
+ * vertaa suggest "set up CI quality gate"
12
+ */
13
+ import { Command } from "commander";
14
+ /**
15
+ * Register the suggest command with the Commander program.
16
+ */
17
+ export declare function registerSuggestCommand(program: Command): void;
18
+ //# sourceMappingURL=suggest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suggest.d.ts","sourceRoot":"","sources":["../../src/commands/suggest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoDpC;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAqH7D"}