@vertaaux/cli 0.2.3 → 0.3.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.
- package/LICENSE +21 -0
- package/README.md +58 -2
- package/dist/auth/device-flow.js +6 -8
- package/dist/commands/audit.d.ts +2 -0
- package/dist/commands/audit.d.ts.map +1 -1
- package/dist/commands/audit.js +165 -6
- package/dist/commands/compare.d.ts +20 -0
- package/dist/commands/compare.d.ts.map +1 -0
- package/dist/commands/compare.js +335 -0
- package/dist/commands/doc.d.ts +18 -0
- package/dist/commands/doc.d.ts.map +1 -0
- package/dist/commands/doc.js +161 -0
- package/dist/commands/download.d.ts.map +1 -1
- package/dist/commands/download.js +9 -8
- package/dist/commands/explain.d.ts +14 -33
- package/dist/commands/explain.d.ts.map +1 -1
- package/dist/commands/explain.js +277 -179
- package/dist/commands/fix-plan.d.ts +15 -0
- package/dist/commands/fix-plan.d.ts.map +1 -0
- package/dist/commands/fix-plan.js +182 -0
- package/dist/commands/patch-review.d.ts +14 -0
- package/dist/commands/patch-review.d.ts.map +1 -0
- package/dist/commands/patch-review.js +200 -0
- package/dist/commands/release-notes.d.ts +17 -0
- package/dist/commands/release-notes.d.ts.map +1 -0
- package/dist/commands/release-notes.js +145 -0
- package/dist/commands/suggest.d.ts +18 -0
- package/dist/commands/suggest.d.ts.map +1 -0
- package/dist/commands/suggest.js +152 -0
- package/dist/commands/triage.d.ts +17 -0
- package/dist/commands/triage.d.ts.map +1 -0
- package/dist/commands/triage.js +205 -0
- package/dist/commands/upload.d.ts.map +1 -1
- package/dist/commands/upload.js +8 -7
- package/dist/index.js +62 -25
- package/dist/output/formats.d.ts.map +1 -1
- package/dist/output/formats.js +14 -0
- package/dist/output/human.d.ts +1 -10
- package/dist/output/human.d.ts.map +1 -1
- package/dist/output/human.js +26 -98
- package/dist/prompts/command-catalog.d.ts +46 -0
- package/dist/prompts/command-catalog.d.ts.map +1 -0
- package/dist/prompts/command-catalog.js +187 -0
- package/dist/ui/spinner.d.ts +10 -35
- package/dist/ui/spinner.d.ts.map +1 -1
- package/dist/ui/spinner.js +11 -58
- package/dist/ui/table.d.ts +1 -18
- package/dist/ui/table.d.ts.map +1 -1
- package/dist/ui/table.js +56 -163
- package/dist/utils/ai-error.d.ts +48 -0
- package/dist/utils/ai-error.d.ts.map +1 -0
- package/dist/utils/ai-error.js +190 -0
- package/dist/utils/detect-env.d.ts +6 -8
- package/dist/utils/detect-env.d.ts.map +1 -1
- package/dist/utils/detect-env.js +6 -25
- package/dist/utils/stdin.d.ts +50 -0
- package/dist/utils/stdin.d.ts.map +1 -0
- package/dist/utils/stdin.js +93 -0
- package/node_modules/@vertaaux/tui/dist/index.cjs +1157 -0
- package/node_modules/@vertaaux/tui/dist/index.cjs.map +1 -0
- package/node_modules/@vertaaux/tui/dist/index.d.cts +609 -0
- package/node_modules/@vertaaux/tui/dist/index.d.ts +609 -0
- package/node_modules/@vertaaux/tui/dist/index.js +1100 -0
- package/node_modules/@vertaaux/tui/dist/index.js.map +1 -0
- package/node_modules/@vertaaux/tui/package.json +64 -0
- package/package.json +12 -5
|
@@ -0,0 +1,152 @@
|
|
|
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 chalk from "chalk";
|
|
14
|
+
import { ExitCode } from "../utils/exit-codes.js";
|
|
15
|
+
import { resolveApiBase, getApiKey, hasApiKey, 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, failSpinner } from "../ui/spinner.js";
|
|
20
|
+
import { findMatches } from "../prompts/command-catalog.js";
|
|
21
|
+
import { AI_TIMEOUT_MS } from "../utils/ai-error.js";
|
|
22
|
+
/**
|
|
23
|
+
* Format suggestion for human-readable output.
|
|
24
|
+
*/
|
|
25
|
+
function formatSuggestHuman(results) {
|
|
26
|
+
const lines = [];
|
|
27
|
+
for (const result of results) {
|
|
28
|
+
lines.push(` ${chalk.cyan.bold("$")} ${chalk.bold(result.command)}`);
|
|
29
|
+
lines.push(` ${chalk.dim(result.explanation)}`);
|
|
30
|
+
lines.push("");
|
|
31
|
+
}
|
|
32
|
+
if (results.length === 0) {
|
|
33
|
+
lines.push(chalk.yellow("No matching commands found."));
|
|
34
|
+
lines.push(chalk.dim("Try: vertaa --help"));
|
|
35
|
+
}
|
|
36
|
+
return lines.join("\n");
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Format suggestion for JSON output.
|
|
40
|
+
*/
|
|
41
|
+
function formatSuggestJson(results) {
|
|
42
|
+
return {
|
|
43
|
+
suggestions: results.map((r) => ({
|
|
44
|
+
command: r.command,
|
|
45
|
+
explanation: r.explanation,
|
|
46
|
+
source: r.source,
|
|
47
|
+
confidence: r.confidence,
|
|
48
|
+
})),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Register the suggest command with the Commander program.
|
|
53
|
+
*/
|
|
54
|
+
export function registerSuggestCommand(program) {
|
|
55
|
+
program
|
|
56
|
+
.command("suggest <intent...>")
|
|
57
|
+
.description("Convert natural language to exact CLI command(s)")
|
|
58
|
+
.option("-f, --format <format>", "Output format: json | human")
|
|
59
|
+
.addHelpText("after", `
|
|
60
|
+
Examples:
|
|
61
|
+
vertaa suggest "check accessibility"
|
|
62
|
+
vertaa suggest "audit my site for CI"
|
|
63
|
+
vertaa suggest "compare two pages"
|
|
64
|
+
vertaa suggest "what failed in my audit"
|
|
65
|
+
`)
|
|
66
|
+
.action(async (intentParts, options, command) => {
|
|
67
|
+
try {
|
|
68
|
+
const globalOpts = command.optsWithGlobals();
|
|
69
|
+
const config = await resolveConfig(globalOpts.config);
|
|
70
|
+
const machineMode = globalOpts.machine || false;
|
|
71
|
+
const format = resolveCommandFormat("suggest", options.format, machineMode);
|
|
72
|
+
const intent = intentParts.join(" ");
|
|
73
|
+
// Step 1: Local fuzzy match against command catalog
|
|
74
|
+
const localMatches = findMatches(intent);
|
|
75
|
+
let results;
|
|
76
|
+
if (localMatches.length > 0 && localMatches[0].score >= 0.2) {
|
|
77
|
+
// Good local matches — use them
|
|
78
|
+
results = localMatches.map((m) => ({
|
|
79
|
+
command: m.entry.command,
|
|
80
|
+
explanation: m.entry.description,
|
|
81
|
+
source: "local",
|
|
82
|
+
confidence: Math.round(m.score * 100),
|
|
83
|
+
}));
|
|
84
|
+
}
|
|
85
|
+
else if (hasApiKey(config)) {
|
|
86
|
+
// No strong local match — try API
|
|
87
|
+
const spinner = createSpinner("Thinking...");
|
|
88
|
+
try {
|
|
89
|
+
const base = resolveApiBase(globalOpts.base);
|
|
90
|
+
const apiKey = getApiKey(config.apiKey);
|
|
91
|
+
const response = await Promise.race([
|
|
92
|
+
apiRequest(base, "/cli/ai/suggest", {
|
|
93
|
+
method: "POST",
|
|
94
|
+
body: { intent },
|
|
95
|
+
}, apiKey),
|
|
96
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("LLM request timed out")), AI_TIMEOUT_MS)),
|
|
97
|
+
]);
|
|
98
|
+
succeedSpinner(spinner, "Done");
|
|
99
|
+
if (response.data?.suggestions?.length) {
|
|
100
|
+
results = response.data.suggestions.map((s) => ({
|
|
101
|
+
command: s.command,
|
|
102
|
+
explanation: s.explanation,
|
|
103
|
+
source: "api",
|
|
104
|
+
confidence: 80,
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
// API returned nothing — fall back to partial local matches
|
|
109
|
+
results = localMatches.map((m) => ({
|
|
110
|
+
command: m.entry.command,
|
|
111
|
+
explanation: m.entry.description,
|
|
112
|
+
source: "local",
|
|
113
|
+
confidence: Math.round(m.score * 100),
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
// Suggest degrades gracefully — fall back to local catalog
|
|
119
|
+
// instead of hard exit via handleAiCommandError
|
|
120
|
+
failSpinner(spinner, "API unavailable — using local catalog");
|
|
121
|
+
// API failed — use whatever local matches we have
|
|
122
|
+
results = localMatches.map((m) => ({
|
|
123
|
+
command: m.entry.command,
|
|
124
|
+
explanation: m.entry.description,
|
|
125
|
+
source: "local",
|
|
126
|
+
confidence: Math.round(m.score * 100),
|
|
127
|
+
}));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
// No API key — use partial local matches
|
|
132
|
+
results = localMatches.map((m) => ({
|
|
133
|
+
command: m.entry.command,
|
|
134
|
+
explanation: m.entry.description,
|
|
135
|
+
source: "local",
|
|
136
|
+
confidence: Math.round(m.score * 100),
|
|
137
|
+
}));
|
|
138
|
+
}
|
|
139
|
+
// Output
|
|
140
|
+
if (format === "json") {
|
|
141
|
+
writeJsonOutput(formatSuggestJson(results), "suggest");
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
writeOutput(formatSuggestHuman(results));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
149
|
+
process.exit(ExitCode.ERROR);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Triage command for VertaaUX CLI.
|
|
3
|
+
*
|
|
4
|
+
* Accepts full audit JSON (via stdin, --file, or --job) and calls the
|
|
5
|
+
* LLM triage endpoint to produce P0/P1/P2 priority buckets with effort
|
|
6
|
+
* estimates and a quick-wins list.
|
|
7
|
+
*
|
|
8
|
+
* Default output shows bucket counts; --verbose expands each bucket.
|
|
9
|
+
*
|
|
10
|
+
* Examples:
|
|
11
|
+
* vertaa audit https://example.com --json | vertaa triage
|
|
12
|
+
* vertaa triage --job abc123
|
|
13
|
+
* vertaa triage --file audit.json --verbose
|
|
14
|
+
*/
|
|
15
|
+
import { Command } from "commander";
|
|
16
|
+
export declare function registerTriageCommand(program: Command): void;
|
|
17
|
+
//# sourceMappingURL=triage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"triage.d.ts","sourceRoot":"","sources":["../../src/commands/triage.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAuJpC,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA+G5D"}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Triage command for VertaaUX CLI.
|
|
3
|
+
*
|
|
4
|
+
* Accepts full audit JSON (via stdin, --file, or --job) and calls the
|
|
5
|
+
* LLM triage endpoint to produce P0/P1/P2 priority buckets with effort
|
|
6
|
+
* estimates and a quick-wins list.
|
|
7
|
+
*
|
|
8
|
+
* Default output shows bucket counts; --verbose expands each bucket.
|
|
9
|
+
*
|
|
10
|
+
* Examples:
|
|
11
|
+
* vertaa audit https://example.com --json | vertaa triage
|
|
12
|
+
* vertaa triage --job abc123
|
|
13
|
+
* vertaa triage --file audit.json --verbose
|
|
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
|
+
// Helpers
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
function normalizeIssues(issues) {
|
|
28
|
+
let list;
|
|
29
|
+
if (Array.isArray(issues)) {
|
|
30
|
+
list = issues;
|
|
31
|
+
}
|
|
32
|
+
else if (issues && typeof issues === "object") {
|
|
33
|
+
list = Object.values(issues).flatMap((v) => Array.isArray(v) ? v : []);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
return list.map((raw) => {
|
|
39
|
+
const i = raw;
|
|
40
|
+
return {
|
|
41
|
+
id: i.id || i.ruleId || i.rule_id || null,
|
|
42
|
+
title: i.title || i.description || null,
|
|
43
|
+
description: i.description || null,
|
|
44
|
+
severity: i.severity || null,
|
|
45
|
+
category: i.category || null,
|
|
46
|
+
selector: i.selector || null,
|
|
47
|
+
wcag_reference: i.wcag_reference || null,
|
|
48
|
+
recommendation: i.recommendation || i.recommended_fix || null,
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Formatters
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
const EFFORT_LABELS = {
|
|
56
|
+
trivial: chalk.green("trivial"),
|
|
57
|
+
small: chalk.green("small"),
|
|
58
|
+
medium: chalk.yellow("medium"),
|
|
59
|
+
large: chalk.red("large"),
|
|
60
|
+
};
|
|
61
|
+
function formatEffort(effort) {
|
|
62
|
+
return EFFORT_LABELS[effort] || chalk.dim(effort);
|
|
63
|
+
}
|
|
64
|
+
function formatTriageHuman(data, verbose) {
|
|
65
|
+
const lines = [];
|
|
66
|
+
// P0
|
|
67
|
+
lines.push(chalk.red.bold(`P0 Critical (${data.p0_critical.length})`));
|
|
68
|
+
if (verbose) {
|
|
69
|
+
for (const item of data.p0_critical) {
|
|
70
|
+
lines.push(` ${chalk.red(">")} ${chalk.bold(item.title)}${item.id ? chalk.dim(` (${item.id})`) : ""}`);
|
|
71
|
+
lines.push(` ${item.reason}`);
|
|
72
|
+
lines.push(` Effort: ${formatEffort(item.effort)}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else if (data.p0_critical.length > 0) {
|
|
76
|
+
lines.push(` ${data.p0_critical.map((i) => i.title).join(", ")}`);
|
|
77
|
+
}
|
|
78
|
+
lines.push("");
|
|
79
|
+
// P1
|
|
80
|
+
lines.push(chalk.yellow.bold(`P1 Important (${data.p1_important.length})`));
|
|
81
|
+
if (verbose) {
|
|
82
|
+
for (const item of data.p1_important) {
|
|
83
|
+
lines.push(` ${chalk.yellow(">")} ${chalk.bold(item.title)}${item.id ? chalk.dim(` (${item.id})`) : ""}`);
|
|
84
|
+
lines.push(` ${item.reason}`);
|
|
85
|
+
lines.push(` Effort: ${formatEffort(item.effort)}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else if (data.p1_important.length > 0) {
|
|
89
|
+
lines.push(` ${data.p1_important.map((i) => i.title).join(", ")}`);
|
|
90
|
+
}
|
|
91
|
+
lines.push("");
|
|
92
|
+
// P2
|
|
93
|
+
lines.push(chalk.cyan.bold(`P2 Nice to Have (${data.p2_nice_to_have.length})`));
|
|
94
|
+
if (verbose) {
|
|
95
|
+
for (const item of data.p2_nice_to_have) {
|
|
96
|
+
lines.push(` ${chalk.cyan(">")} ${chalk.bold(item.title)}${item.id ? chalk.dim(` (${item.id})`) : ""}`);
|
|
97
|
+
lines.push(` ${item.reason}`);
|
|
98
|
+
lines.push(` Effort: ${formatEffort(item.effort)}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else if (data.p2_nice_to_have.length > 0) {
|
|
102
|
+
lines.push(` ${data.p2_nice_to_have.map((i) => i.title).join(", ")}`);
|
|
103
|
+
}
|
|
104
|
+
lines.push("");
|
|
105
|
+
// Quick wins
|
|
106
|
+
if (data.quick_wins.length > 0) {
|
|
107
|
+
lines.push(chalk.green.bold("Quick Wins (< 5 min each)"));
|
|
108
|
+
for (const win of data.quick_wins) {
|
|
109
|
+
lines.push(` ${chalk.green("*")} ${win}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return lines.join("\n");
|
|
113
|
+
}
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
115
|
+
// Command Registration
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
export function registerTriageCommand(program) {
|
|
118
|
+
program
|
|
119
|
+
.command("triage")
|
|
120
|
+
.description("Prioritize audit findings into P0/P1/P2 buckets with effort estimates")
|
|
121
|
+
.option("--job <job-id>", "Fetch audit data from a job ID")
|
|
122
|
+
.option("--file <path>", "Load audit JSON from file")
|
|
123
|
+
.option("-f, --format <format>", "Output format: json | human")
|
|
124
|
+
.addHelpText("after", `
|
|
125
|
+
Examples:
|
|
126
|
+
vertaa audit https://example.com --json | vertaa triage
|
|
127
|
+
vertaa triage --job abc123
|
|
128
|
+
vertaa triage --file audit.json --verbose
|
|
129
|
+
`)
|
|
130
|
+
.action(async (options, command) => {
|
|
131
|
+
try {
|
|
132
|
+
const globalOpts = command.optsWithGlobals();
|
|
133
|
+
const config = await resolveConfig(globalOpts.config);
|
|
134
|
+
const machineMode = globalOpts.machine || false;
|
|
135
|
+
const verbose = globalOpts.verbose || false;
|
|
136
|
+
const format = resolveCommandFormat("triage", options.format, machineMode);
|
|
137
|
+
// Resolve audit data
|
|
138
|
+
let auditPayload;
|
|
139
|
+
if (options.job) {
|
|
140
|
+
// Fetch from API
|
|
141
|
+
const base = resolveApiBase(globalOpts.base);
|
|
142
|
+
const apiKey = getApiKey(config.apiKey);
|
|
143
|
+
const result = await apiRequest(base, `/audit/${options.job}`, { method: "GET" }, apiKey);
|
|
144
|
+
const issues = normalizeIssues(result.issues);
|
|
145
|
+
auditPayload = {
|
|
146
|
+
job_id: result.job_id || options.job,
|
|
147
|
+
url: result.url || null,
|
|
148
|
+
scores: result.scores || null,
|
|
149
|
+
issues,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
// Read from stdin or --file
|
|
154
|
+
const input = await readJsonInput(options.file);
|
|
155
|
+
if (!input) {
|
|
156
|
+
console.error("Error: No audit data provided.");
|
|
157
|
+
console.error("Usage:");
|
|
158
|
+
console.error(" vertaa audit https://example.com --json | vertaa triage");
|
|
159
|
+
console.error(" vertaa triage --job <job-id>");
|
|
160
|
+
console.error(" vertaa triage --file audit.json");
|
|
161
|
+
process.exit(ExitCode.ERROR);
|
|
162
|
+
}
|
|
163
|
+
const data = input;
|
|
164
|
+
const innerData = (data.data && typeof data.data === "object" ? data.data : data);
|
|
165
|
+
const issues = normalizeIssues(innerData.issues);
|
|
166
|
+
auditPayload = {
|
|
167
|
+
job_id: innerData.job_id || null,
|
|
168
|
+
url: innerData.url || null,
|
|
169
|
+
scores: innerData.scores || null,
|
|
170
|
+
issues,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
if (!Array.isArray(auditPayload.issues) ||
|
|
174
|
+
auditPayload.issues.length === 0) {
|
|
175
|
+
console.error("Error: No issues found in audit data.");
|
|
176
|
+
process.exit(ExitCode.ERROR);
|
|
177
|
+
}
|
|
178
|
+
// Auth check
|
|
179
|
+
const base = resolveApiBase(globalOpts.base);
|
|
180
|
+
const apiKey = getApiKey(config.apiKey);
|
|
181
|
+
// Call LLM triage API
|
|
182
|
+
const spinner = createSpinner("Triaging findings...");
|
|
183
|
+
try {
|
|
184
|
+
const response = await Promise.race([
|
|
185
|
+
apiRequest(base, "/cli/ai/triage", { method: "POST", body: { audit: auditPayload } }, apiKey),
|
|
186
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("LLM request timed out")), AI_TIMEOUT_MS)),
|
|
187
|
+
]);
|
|
188
|
+
succeedSpinner(spinner, "Triage complete");
|
|
189
|
+
if (format === "json") {
|
|
190
|
+
writeJsonOutput(response.data, "triage");
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
writeOutput(formatTriageHuman(response.data, verbose));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
handleAiCommandError(error, "triage", spinner);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
202
|
+
process.exit(ExitCode.ERROR);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../src/commands/upload.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../src/commands/upload.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0LpC;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAiB5D"}
|
package/dist/commands/upload.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import fs from "fs";
|
|
8
8
|
import path from "path";
|
|
9
9
|
import chalk from "chalk";
|
|
10
|
-
import
|
|
10
|
+
import { createSpinner, succeedSpinner, failSpinner } from "../ui/spinner.js";
|
|
11
11
|
import { loadToken } from "../auth/token-store.js";
|
|
12
12
|
import { getCIToken } from "../auth/ci-token.js";
|
|
13
13
|
import { resolveApiBase } from "../utils/client.js";
|
|
@@ -45,7 +45,8 @@ async function handleUpload(jobId, options) {
|
|
|
45
45
|
const config = await resolveConfig(options.configPath);
|
|
46
46
|
const apiBase = resolveApiBase(options.base);
|
|
47
47
|
// Determine what to upload
|
|
48
|
-
const spinner =
|
|
48
|
+
const spinner = createSpinner("Preparing upload...");
|
|
49
|
+
spinner.start();
|
|
49
50
|
try {
|
|
50
51
|
// If no job ID, find the most recent local result
|
|
51
52
|
let targetJobId = jobId;
|
|
@@ -71,12 +72,12 @@ async function handleUpload(jobId, options) {
|
|
|
71
72
|
}
|
|
72
73
|
}
|
|
73
74
|
if (!targetJobId) {
|
|
74
|
-
spinner
|
|
75
|
+
failSpinner(spinner, "No job ID provided and no local results found.");
|
|
75
76
|
console.error("Run `vertaa audit --save-trace` to save local results first.");
|
|
76
77
|
process.exit(ExitCode.ERROR);
|
|
77
78
|
}
|
|
78
79
|
}
|
|
79
|
-
spinner.
|
|
80
|
+
spinner.setText(`Uploading audit ${targetJobId}...`);
|
|
80
81
|
// Prepare upload payload
|
|
81
82
|
const payload = {
|
|
82
83
|
job_id: targetJobId,
|
|
@@ -108,7 +109,7 @@ async function handleUpload(jobId, options) {
|
|
|
108
109
|
const baseline = await loadBaseline(DEFAULT_BASELINE_PATH);
|
|
109
110
|
if (baseline) {
|
|
110
111
|
payload.baseline = baseline;
|
|
111
|
-
spinner.
|
|
112
|
+
spinner.setText(`Uploading audit ${targetJobId} with baseline...`);
|
|
112
113
|
}
|
|
113
114
|
}
|
|
114
115
|
// Make API request
|
|
@@ -128,7 +129,7 @@ async function handleUpload(jobId, options) {
|
|
|
128
129
|
if (!result.success) {
|
|
129
130
|
throw new Error(result.error?.message || "Upload failed");
|
|
130
131
|
}
|
|
131
|
-
spinner
|
|
132
|
+
succeedSpinner(spinner, "Upload complete!");
|
|
132
133
|
console.error("");
|
|
133
134
|
console.error(` Job ID: ${result.job_id}`);
|
|
134
135
|
console.error(` URL: ${chalk.cyan(result.url)}`);
|
|
@@ -136,7 +137,7 @@ async function handleUpload(jobId, options) {
|
|
|
136
137
|
console.error("Share this URL with your team to view the results.");
|
|
137
138
|
}
|
|
138
139
|
catch (error) {
|
|
139
|
-
spinner
|
|
140
|
+
failSpinner(spinner, "Upload failed");
|
|
140
141
|
console.error(chalk.red("Error:"), error instanceof Error ? error.message : String(error));
|
|
141
142
|
process.exit(ExitCode.ERROR);
|
|
142
143
|
}
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import fs from "fs";
|
|
|
10
10
|
import path from "path";
|
|
11
11
|
import { fileURLToPath } from "url";
|
|
12
12
|
import { Command } from "commander";
|
|
13
|
+
import { setColorEnabled, shouldUseColor } from "@vertaaux/tui";
|
|
13
14
|
import { showBanner, getVersion } from "./ui/banner.js";
|
|
14
15
|
import { registerAuditCommand } from "./commands/audit.js";
|
|
15
16
|
import { registerBaselineCommand } from "./commands/baseline.js";
|
|
@@ -22,6 +23,13 @@ import { registerUploadCommand } from "./commands/upload.js";
|
|
|
22
23
|
import { registerDownloadCommand } from "./commands/download.js";
|
|
23
24
|
import { registerPolicyCommand } from "./commands/policy.js";
|
|
24
25
|
import { registerDoctorCommand } from "./commands/doctor.js";
|
|
26
|
+
import { registerSuggestCommand } from "./commands/suggest.js";
|
|
27
|
+
import { registerTriageCommand } from "./commands/triage.js";
|
|
28
|
+
import { registerFixPlanCommand } from "./commands/fix-plan.js";
|
|
29
|
+
import { registerPatchReviewCommand } from "./commands/patch-review.js";
|
|
30
|
+
import { registerCompareCommand } from "./commands/compare.js";
|
|
31
|
+
import { registerReleaseNotesCommand } from "./commands/release-notes.js";
|
|
32
|
+
import { registerDocCommand } from "./commands/doc.js";
|
|
25
33
|
import { ExitCode } from "./utils/exit-codes.js";
|
|
26
34
|
import { formatCommanderError } from "./ui/diagnostics.js";
|
|
27
35
|
import { parseMode, parseTimeout, parseInterval, parseScore } from "./utils/validators.js";
|
|
@@ -625,7 +633,7 @@ async function runCompareCommand(base, urls, flags) {
|
|
|
625
633
|
}
|
|
626
634
|
printOutput(format, compare, formatCompareMarkdown(compare));
|
|
627
635
|
}
|
|
628
|
-
async function runFixCommand(base, jobId, issueId, flags) {
|
|
636
|
+
async function runFixCommand(base, jobId, issueId, flags, globalFlags = {}) {
|
|
629
637
|
const fileContent = getString(flags, "file-content");
|
|
630
638
|
const format = resolveFormat(flags);
|
|
631
639
|
if (!fileContent) {
|
|
@@ -649,8 +657,12 @@ async function runFixCommand(base, jobId, issueId, flags) {
|
|
|
649
657
|
if (!result.success) {
|
|
650
658
|
process.exitCode = 1;
|
|
651
659
|
}
|
|
660
|
+
if (globalFlags.dryRun && isTTYOutput) {
|
|
661
|
+
process.stderr.write("[dry-run] Showing patch preview — no changes applied.\n\n");
|
|
662
|
+
}
|
|
652
663
|
if (format === "json") {
|
|
653
|
-
|
|
664
|
+
const output = globalFlags.dryRun ? { ...result, dry_run: true } : result;
|
|
665
|
+
console.log(JSON.stringify(output, null, 2));
|
|
654
666
|
}
|
|
655
667
|
else {
|
|
656
668
|
if (result.success && result.patch) {
|
|
@@ -708,7 +720,7 @@ async function runVerifyCommand(base, flags) {
|
|
|
708
720
|
}
|
|
709
721
|
}
|
|
710
722
|
const BATCH_LIMIT = 10;
|
|
711
|
-
async function runFixAllCommand(base, jobId, flags) {
|
|
723
|
+
async function runFixAllCommand(base, jobId, flags, globalFlags = {}) {
|
|
712
724
|
const fileContent = getString(flags, "file-content");
|
|
713
725
|
const autoFixOnly = getBool(flags, "auto-fix-only");
|
|
714
726
|
const format = resolveFormat(flags);
|
|
@@ -741,6 +753,21 @@ async function runFixAllCommand(base, jobId, flags) {
|
|
|
741
753
|
if (isTTYOutput) {
|
|
742
754
|
process.stderr.write(" \r");
|
|
743
755
|
}
|
|
756
|
+
// --dry-run: show what would be processed and exit
|
|
757
|
+
if (globalFlags.dryRun) {
|
|
758
|
+
process.stderr.write(`[dry-run] Would generate patches for ${issuesToProcess.length} issues:\n`);
|
|
759
|
+
for (const issue of issuesToProcess) {
|
|
760
|
+
process.stderr.write(` - ${issue.id || "unknown"}: ${issue.title || issue.description || "(no title)"}\n`);
|
|
761
|
+
}
|
|
762
|
+
if (totalSkipped > 0) {
|
|
763
|
+
process.stderr.write(` (${totalSkipped} additional issues skipped — batch limit ${BATCH_LIMIT})\n`);
|
|
764
|
+
}
|
|
765
|
+
process.stderr.write("\nNo patches generated. Remove --dry-run to execute.\n");
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
// --yes is available for future interactive confirmation steps
|
|
769
|
+
// Currently fix-all runs non-interactively, but --yes suppresses any
|
|
770
|
+
// confirmation prompts that may be added (e.g., "Apply all N patches?")
|
|
744
771
|
const results = {
|
|
745
772
|
successes: [],
|
|
746
773
|
failures: [],
|
|
@@ -845,6 +872,13 @@ program
|
|
|
845
872
|
.option("-q, --quiet", "Suppress banner and non-essential output")
|
|
846
873
|
.option("--no-banner", "Hide the V-mark banner")
|
|
847
874
|
.option("--machine", "Strict machine-readable output (JSON stdout, diagnostics stderr)")
|
|
875
|
+
.option("--color", "Force color output")
|
|
876
|
+
.option("--no-color", "Disable color output")
|
|
877
|
+
.option("--dashboard", "Force live dashboard during audit --wait")
|
|
878
|
+
.option("--no-dashboard", "Disable live dashboard (use spinner instead)")
|
|
879
|
+
.option("--dry-run", "Show what would happen without executing")
|
|
880
|
+
.option("-y, --yes", "Auto-confirm all interactive prompts")
|
|
881
|
+
.option("--verbose", "Expand output with additional details")
|
|
848
882
|
.configureOutput({
|
|
849
883
|
outputError: (str, _write) => {
|
|
850
884
|
const formatted = formatCommanderError(str);
|
|
@@ -864,9 +898,20 @@ program
|
|
|
864
898
|
.hook("preAction", (thisCommand) => {
|
|
865
899
|
const opts = thisCommand.optsWithGlobals();
|
|
866
900
|
const machineMode = opts.machine || false;
|
|
901
|
+
// Apply color settings from flags or environment
|
|
902
|
+
if (opts.color === false || process.env.NO_COLOR !== undefined) {
|
|
903
|
+
setColorEnabled(false);
|
|
904
|
+
}
|
|
905
|
+
else if (opts.color === true || process.env.FORCE_COLOR !== undefined) {
|
|
906
|
+
setColorEnabled(true);
|
|
907
|
+
}
|
|
908
|
+
else {
|
|
909
|
+
setColorEnabled(shouldUseColor());
|
|
910
|
+
}
|
|
867
911
|
if (machineMode) {
|
|
868
912
|
opts.quiet = true;
|
|
869
913
|
opts.banner = false;
|
|
914
|
+
setColorEnabled(false);
|
|
870
915
|
}
|
|
871
916
|
showBanner({
|
|
872
917
|
version,
|
|
@@ -886,6 +931,13 @@ registerUploadCommand(program);
|
|
|
886
931
|
registerDownloadCommand(program);
|
|
887
932
|
registerPolicyCommand(program);
|
|
888
933
|
registerDoctorCommand(program);
|
|
934
|
+
registerSuggestCommand(program);
|
|
935
|
+
registerTriageCommand(program);
|
|
936
|
+
registerFixPlanCommand(program);
|
|
937
|
+
registerPatchReviewCommand(program);
|
|
938
|
+
registerCompareCommand(program);
|
|
939
|
+
registerReleaseNotesCommand(program);
|
|
940
|
+
registerDocCommand(program);
|
|
889
941
|
// Legacy commands using old argument parsing
|
|
890
942
|
// These will be migrated in subsequent plans
|
|
891
943
|
program
|
|
@@ -924,24 +976,7 @@ program
|
|
|
924
976
|
process.exit(ExitCode.ERROR);
|
|
925
977
|
}
|
|
926
978
|
});
|
|
927
|
-
|
|
928
|
-
.command("compare <urlA> <urlB>")
|
|
929
|
-
.description("Compare audits of two URLs")
|
|
930
|
-
.option("--mode <mode>", "Audit depth: basic|standard|deep", parseMode, "basic")
|
|
931
|
-
.option("--wait", "Wait for audits to complete")
|
|
932
|
-
.option("--timeout <ms>", "Wait timeout in milliseconds (1-300000)", parseTimeout, 60000)
|
|
933
|
-
.option("--interval <ms>", "Poll interval in milliseconds (1-300000)", parseInterval, 5000)
|
|
934
|
-
.option("--fail-on-score <n>", "Exit non-zero if score below n (0-100)", parseScore)
|
|
935
|
-
.action(async (urlA, urlB, cmdOptions) => {
|
|
936
|
-
try {
|
|
937
|
-
const base = resolveApiBase(cmdOptions);
|
|
938
|
-
await runCompareCommand(base, [urlA, urlB], cmdOptions);
|
|
939
|
-
}
|
|
940
|
-
catch (error) {
|
|
941
|
-
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
942
|
-
process.exit(ExitCode.ERROR);
|
|
943
|
-
}
|
|
944
|
-
});
|
|
979
|
+
// compare is now registered via registerCompareCommand (Phase 60)
|
|
945
980
|
program
|
|
946
981
|
.command("status <jobId>")
|
|
947
982
|
.description("Get status of an audit job")
|
|
@@ -973,13 +1008,14 @@ program
|
|
|
973
1008
|
.description("Generate a fix patch for an issue")
|
|
974
1009
|
.requiredOption("--issue <id>", "Issue ID to fix")
|
|
975
1010
|
.requiredOption("--file-content <code>", "Source code content")
|
|
976
|
-
.action(async (jobId, cmdOptions) => {
|
|
1011
|
+
.action(async (jobId, cmdOptions, command) => {
|
|
977
1012
|
try {
|
|
1013
|
+
const globalOpts = command.optsWithGlobals();
|
|
978
1014
|
const base = resolveApiBase(cmdOptions);
|
|
979
1015
|
const issueId = getString(cmdOptions, "issue");
|
|
980
1016
|
if (!issueId)
|
|
981
1017
|
throw new Error("--issue is required");
|
|
982
|
-
await runFixCommand(base, jobId, issueId, cmdOptions);
|
|
1018
|
+
await runFixCommand(base, jobId, issueId, cmdOptions, { dryRun: !!globalOpts.dryRun });
|
|
983
1019
|
}
|
|
984
1020
|
catch (error) {
|
|
985
1021
|
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
@@ -991,10 +1027,11 @@ program
|
|
|
991
1027
|
.description("Generate fix patches for all issues in an audit")
|
|
992
1028
|
.requiredOption("--file-content <code>", "Source code content")
|
|
993
1029
|
.option("--auto-fix-only", "Only process auto-fixable issues")
|
|
994
|
-
.action(async (jobId, cmdOptions) => {
|
|
1030
|
+
.action(async (jobId, cmdOptions, command) => {
|
|
995
1031
|
try {
|
|
1032
|
+
const globalOpts = command.optsWithGlobals();
|
|
996
1033
|
const base = resolveApiBase(cmdOptions);
|
|
997
|
-
await runFixAllCommand(base, jobId, cmdOptions);
|
|
1034
|
+
await runFixAllCommand(base, jobId, cmdOptions, { dryRun: !!globalOpts.dryRun, yes: !!globalOpts.yes });
|
|
998
1035
|
}
|
|
999
1036
|
catch (error) {
|
|
1000
1037
|
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"formats.d.ts","sourceRoot":"","sources":["../../src/output/formats.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"formats.d.ts","sourceRoot":"","sources":["../../src/output/formats.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAa7D,CAAC;AAEF,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAazD,CAAC;AAEF,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAclF;AAED,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,MAAM,GAAG,SAAS,EAClC,WAAW,EAAE,OAAO,GACnB,MAAM,CASR"}
|
package/dist/output/formats.js
CHANGED
|
@@ -10,6 +10,13 @@ export const COMMAND_FORMATS = {
|
|
|
10
10
|
explain: ["json", "human"],
|
|
11
11
|
"policy-show": ["json", "yaml"],
|
|
12
12
|
diff: ["json", "human"],
|
|
13
|
+
suggest: ["json", "human"],
|
|
14
|
+
triage: ["json", "human"],
|
|
15
|
+
"fix-plan": ["json", "human"],
|
|
16
|
+
"patch-review": ["json", "human"],
|
|
17
|
+
"release-notes": ["json", "human", "markdown"],
|
|
18
|
+
compare: ["json", "human"],
|
|
19
|
+
doc: ["json", "markdown"],
|
|
13
20
|
};
|
|
14
21
|
export const COMMAND_DEFAULT_FORMAT = {
|
|
15
22
|
audit: "human",
|
|
@@ -17,6 +24,13 @@ export const COMMAND_DEFAULT_FORMAT = {
|
|
|
17
24
|
explain: "human",
|
|
18
25
|
"policy-show": "yaml",
|
|
19
26
|
diff: "human",
|
|
27
|
+
suggest: "human",
|
|
28
|
+
triage: "human",
|
|
29
|
+
"fix-plan": "human",
|
|
30
|
+
"patch-review": "human",
|
|
31
|
+
"release-notes": "markdown",
|
|
32
|
+
compare: "human",
|
|
33
|
+
doc: "markdown",
|
|
20
34
|
};
|
|
21
35
|
export function validateFormat(command, format) {
|
|
22
36
|
const allowed = COMMAND_FORMATS[command];
|