@vertaaux/cli 0.3.3 → 0.5.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.
- package/CHANGELOG.md +97 -0
- package/MIGRATION.md +239 -0
- package/README.md +34 -16
- package/dist/app/interactive-app.d.ts +101 -0
- package/dist/app/interactive-app.d.ts.map +1 -0
- package/dist/app/interactive-app.js +309 -0
- package/dist/app/layout/canvas.d.ts +23 -0
- package/dist/app/layout/canvas.d.ts.map +1 -0
- package/dist/app/layout/canvas.js +36 -0
- package/dist/app/layout/footer.d.ts +31 -0
- package/dist/app/layout/footer.d.ts.map +1 -0
- package/dist/app/layout/footer.js +41 -0
- package/dist/app/layout/header.d.ts +20 -0
- package/dist/app/layout/header.d.ts.map +1 -0
- package/dist/app/layout/header.js +27 -0
- package/dist/app/menu/categories.d.ts +20 -0
- package/dist/app/menu/categories.d.ts.map +1 -0
- package/dist/app/menu/categories.js +181 -0
- package/dist/app/menu/filter.d.ts +17 -0
- package/dist/app/menu/filter.d.ts.map +1 -0
- package/dist/app/menu/filter.js +33 -0
- package/dist/app/menu/menu-view.d.ts +35 -0
- package/dist/app/menu/menu-view.d.ts.map +1 -0
- package/dist/app/menu/menu-view.js +230 -0
- package/dist/app/menu/recent.d.ts +24 -0
- package/dist/app/menu/recent.d.ts.map +1 -0
- package/dist/app/menu/recent.js +49 -0
- package/dist/app/types.d.ts +43 -0
- package/dist/app/types.d.ts.map +1 -0
- package/dist/app/types.js +7 -0
- package/dist/app/views/command-runner.d.ts +36 -0
- package/dist/app/views/command-runner.d.ts.map +1 -0
- package/dist/app/views/command-runner.js +372 -0
- package/dist/app/views/help-overlay.d.ts +21 -0
- package/dist/app/views/help-overlay.d.ts.map +1 -0
- package/dist/app/views/help-overlay.js +45 -0
- package/dist/auth/ci-token.d.ts +14 -2
- package/dist/auth/ci-token.d.ts.map +1 -1
- package/dist/auth/ci-token.js +15 -30
- package/dist/auth/device-flow.d.ts +2 -1
- package/dist/auth/device-flow.d.ts.map +1 -1
- package/dist/auth/device-flow.js +13 -10
- package/dist/auth/token-store.d.ts.map +1 -1
- package/dist/auth/token-store.js +12 -2
- package/dist/baseline/diff.d.ts +2 -2
- package/dist/baseline/diff.d.ts.map +1 -1
- package/dist/baseline/diff.js +15 -34
- package/dist/commands/a11y.d.ts +9 -0
- package/dist/commands/a11y.d.ts.map +1 -0
- package/dist/commands/a11y.js +76 -0
- package/dist/commands/audit/artifacts.d.ts +27 -0
- package/dist/commands/audit/artifacts.d.ts.map +1 -0
- package/dist/commands/audit/artifacts.js +158 -0
- package/dist/commands/audit/ci-detection.d.ts +18 -0
- package/dist/commands/audit/ci-detection.d.ts.map +1 -0
- package/dist/commands/audit/ci-detection.js +71 -0
- package/dist/commands/audit/explain.d.ts +11 -0
- package/dist/commands/audit/explain.d.ts.map +1 -0
- package/dist/commands/audit/explain.js +45 -0
- package/dist/commands/audit/filters.d.ts +17 -0
- package/dist/commands/audit/filters.d.ts.map +1 -0
- package/dist/commands/audit/filters.js +40 -0
- package/dist/commands/audit/index.d.ts +18 -0
- package/dist/commands/audit/index.d.ts.map +1 -0
- package/dist/commands/audit/index.js +564 -0
- package/dist/commands/audit/output.d.ts +32 -0
- package/dist/commands/audit/output.d.ts.map +1 -0
- package/dist/commands/audit/output.js +130 -0
- package/dist/commands/audit/policy.d.ts +19 -0
- package/dist/commands/audit/policy.d.ts.map +1 -0
- package/dist/commands/audit/policy.js +102 -0
- package/dist/commands/audit/scoring.d.ts +23 -0
- package/dist/commands/audit/scoring.d.ts.map +1 -0
- package/dist/commands/audit/scoring.js +70 -0
- package/dist/commands/audit/types.d.ts +88 -0
- package/dist/commands/audit/types.d.ts.map +1 -0
- package/dist/commands/audit/types.js +8 -0
- package/dist/commands/audit.d.ts +2 -60
- package/dist/commands/audit.d.ts.map +1 -1
- package/dist/commands/audit.js +2 -1038
- package/dist/commands/baseline.d.ts +1 -0
- package/dist/commands/baseline.d.ts.map +1 -1
- package/dist/commands/baseline.js +205 -121
- package/dist/commands/comment.d.ts +22 -0
- package/dist/commands/comment.d.ts.map +1 -1
- package/dist/commands/comment.js +122 -58
- package/dist/commands/compare.d.ts +17 -0
- package/dist/commands/compare.d.ts.map +1 -1
- package/dist/commands/compare.js +287 -180
- package/dist/commands/diff.d.ts +5 -0
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +168 -141
- package/dist/commands/doc.d.ts +10 -0
- package/dist/commands/doc.d.ts.map +1 -1
- package/dist/commands/doc.js +134 -76
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +164 -17
- package/dist/commands/download.d.ts +10 -0
- package/dist/commands/download.d.ts.map +1 -1
- package/dist/commands/download.js +169 -112
- package/dist/commands/explain.d.ts +5 -0
- package/dist/commands/explain.d.ts.map +1 -1
- package/dist/commands/explain.js +241 -155
- package/dist/commands/fix-all.d.ts +25 -0
- package/dist/commands/fix-all.d.ts.map +1 -0
- package/dist/commands/fix-all.js +206 -0
- package/dist/commands/fix-plan.d.ts +9 -0
- package/dist/commands/fix-plan.d.ts.map +1 -1
- package/dist/commands/fix-plan.js +152 -89
- package/dist/commands/fix.d.ts +17 -0
- package/dist/commands/fix.d.ts.map +1 -0
- package/dist/commands/fix.js +111 -0
- package/dist/commands/init.d.ts +11 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +94 -42
- package/dist/commands/login.d.ts +18 -0
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +268 -95
- package/dist/commands/patch-review.d.ts +11 -0
- package/dist/commands/patch-review.d.ts.map +1 -1
- package/dist/commands/patch-review.js +159 -97
- package/dist/commands/policy.d.ts +31 -0
- package/dist/commands/policy.d.ts.map +1 -1
- package/dist/commands/policy.js +269 -124
- package/dist/commands/release-notes.d.ts +10 -0
- package/dist/commands/release-notes.d.ts.map +1 -1
- package/dist/commands/release-notes.js +127 -73
- package/dist/commands/scan.d.ts +13 -0
- package/dist/commands/scan.d.ts.map +1 -0
- package/dist/commands/scan.js +133 -0
- package/dist/commands/status.d.ts +9 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +81 -0
- package/dist/commands/suggest.d.ts +10 -0
- package/dist/commands/suggest.d.ts.map +1 -1
- package/dist/commands/suggest.js +153 -82
- package/dist/commands/triage.d.ts +35 -0
- package/dist/commands/triage.d.ts.map +1 -1
- package/dist/commands/triage.js +206 -81
- package/dist/commands/upload.d.ts +9 -0
- package/dist/commands/upload.d.ts.map +1 -1
- package/dist/commands/upload.js +140 -101
- package/dist/commands/verify.d.ts +13 -0
- package/dist/commands/verify.d.ts.map +1 -0
- package/dist/commands/verify.js +118 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +125 -990
- package/dist/interactive/fix-wizard.d.ts +3 -0
- package/dist/interactive/fix-wizard.d.ts.map +1 -1
- package/dist/interactive/fix-wizard.js +130 -112
- package/dist/interactive/init-wizard.d.ts +3 -1
- package/dist/interactive/init-wizard.d.ts.map +1 -1
- package/dist/interactive/init-wizard.js +207 -138
- package/dist/interactive/prompts.d.ts +7 -3
- package/dist/interactive/prompts.d.ts.map +1 -1
- package/dist/interactive/prompts.js +44 -23
- package/dist/output/envelope.d.ts +2 -0
- package/dist/output/envelope.d.ts.map +1 -1
- package/dist/output/envelope.js +18 -2
- package/dist/output/factory.d.ts +9 -1
- package/dist/output/factory.d.ts.map +1 -1
- package/dist/output/html.d.ts +2 -1
- package/dist/output/html.d.ts.map +1 -1
- package/dist/output/html.js +3 -2
- package/dist/output/human.d.ts +9 -1
- package/dist/output/human.d.ts.map +1 -1
- package/dist/output/human.js +17 -2
- package/dist/output/json.d.ts +2 -1
- package/dist/output/json.d.ts.map +1 -1
- package/dist/output/junit.d.ts +2 -1
- package/dist/output/junit.d.ts.map +1 -1
- package/dist/output/sarif.d.ts +2 -1
- package/dist/output/sarif.d.ts.map +1 -1
- package/dist/types.d.ts +74 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/ui/banner.d.ts +34 -0
- package/dist/ui/banner.d.ts.map +1 -1
- package/dist/ui/banner.js +97 -5
- package/dist/ui/diagnostics.d.ts +9 -4
- package/dist/ui/diagnostics.d.ts.map +1 -1
- package/dist/ui/diagnostics.js +32 -82
- package/dist/ui/strings.d.ts +373 -0
- package/dist/ui/strings.d.ts.map +1 -0
- package/dist/ui/strings.js +499 -0
- package/dist/ui/table.d.ts +0 -2
- package/dist/ui/table.d.ts.map +1 -1
- package/dist/ui/table.js +3 -4
- package/dist/utils/api-client.d.ts +46 -0
- package/dist/utils/api-client.d.ts.map +1 -0
- package/dist/utils/api-client.js +170 -0
- package/dist/utils/client.d.ts +29 -18
- package/dist/utils/client.d.ts.map +1 -1
- package/dist/utils/client.js +102 -12
- package/dist/utils/formatters.d.ts +38 -0
- package/dist/utils/formatters.d.ts.map +1 -0
- package/dist/utils/formatters.js +277 -0
- package/dist/utils/local-capture.d.ts +25 -0
- package/dist/utils/local-capture.d.ts.map +1 -0
- package/dist/utils/local-capture.js +57 -0
- package/dist/utils/url-classify.d.ts +18 -0
- package/dist/utils/url-classify.d.ts.map +1 -0
- package/dist/utils/url-classify.js +106 -0
- package/node_modules/@vertaaux/tui/dist/index.cjs +713 -20
- package/node_modules/@vertaaux/tui/dist/index.cjs.map +1 -1
- package/node_modules/@vertaaux/tui/dist/index.d.cts +361 -4
- package/node_modules/@vertaaux/tui/dist/index.d.ts +361 -4
- package/node_modules/@vertaaux/tui/dist/index.js +689 -21
- package/node_modules/@vertaaux/tui/dist/index.js.map +1 -1
- package/package.json +13 -5
- package/dist/commands/client.d.ts +0 -14
- package/dist/commands/client.d.ts.map +0 -1
- package/dist/commands/client.js +0 -362
- package/dist/commands/drift.d.ts +0 -15
- package/dist/commands/drift.d.ts.map +0 -1
- package/dist/commands/drift.js +0 -309
- package/dist/commands/protect.d.ts +0 -16
- package/dist/commands/protect.d.ts.map +0 -1
- package/dist/commands/protect.js +0 -323
- package/dist/commands/report.d.ts +0 -15
- package/dist/commands/report.d.ts.map +0 -1
- package/dist/commands/report.js +0 -214
- package/dist/policy/sync.d.ts +0 -67
- package/dist/policy/sync.d.ts.map +0 -1
- package/dist/policy/sync.js +0 -147
package/dist/commands/triage.js
CHANGED
|
@@ -12,15 +12,15 @@
|
|
|
12
12
|
* vertaa triage --job abc123
|
|
13
13
|
* vertaa triage --file audit.json --verbose
|
|
14
14
|
*/
|
|
15
|
-
import
|
|
15
|
+
import { bold, dim, colorize, boldColor, brand, severity as severityPalette, runSteps, createRenderer, renderWarning, renderError, isCI, isTTY, isStdinTTY, ScrollingViewport } from "@vertaaux/tui";
|
|
16
16
|
import { ExitCode } from "../utils/exit-codes.js";
|
|
17
|
-
import { resolveApiBase, getApiKey, apiRequest } from "../utils/client.js";
|
|
17
|
+
import { resolveApiBase, getApiKey, apiRequest, createClient } from "../utils/client.js";
|
|
18
18
|
import { resolveConfig } from "../config/loader.js";
|
|
19
19
|
import { writeJsonOutput, writeOutput } from "../output/envelope.js";
|
|
20
20
|
import { resolveCommandFormat } from "../output/formats.js";
|
|
21
|
-
import { createSpinner, succeedSpinner } from "../ui/spinner.js";
|
|
22
21
|
import { readJsonInput } from "../utils/stdin.js";
|
|
23
|
-
import {
|
|
22
|
+
import { AI_TIMEOUT_MS } from "../utils/ai-error.js";
|
|
23
|
+
import { strings } from "../ui/strings.js";
|
|
24
24
|
// ---------------------------------------------------------------------------
|
|
25
25
|
// Helpers
|
|
26
26
|
// ---------------------------------------------------------------------------
|
|
@@ -53,21 +53,21 @@ function normalizeIssues(issues) {
|
|
|
53
53
|
// Formatters
|
|
54
54
|
// ---------------------------------------------------------------------------
|
|
55
55
|
const EFFORT_LABELS = {
|
|
56
|
-
trivial:
|
|
57
|
-
small:
|
|
58
|
-
medium:
|
|
59
|
-
large:
|
|
56
|
+
trivial: colorize("trivial", brand.lime),
|
|
57
|
+
small: colorize("small", brand.lime),
|
|
58
|
+
medium: colorize("medium", severityPalette.warning),
|
|
59
|
+
large: colorize("large", severityPalette.error),
|
|
60
60
|
};
|
|
61
61
|
function formatEffort(effort) {
|
|
62
|
-
return EFFORT_LABELS[effort] ||
|
|
62
|
+
return EFFORT_LABELS[effort] || dim(effort);
|
|
63
63
|
}
|
|
64
64
|
function formatTriageHuman(data, verbose) {
|
|
65
65
|
const lines = [];
|
|
66
66
|
// P0
|
|
67
|
-
lines.push(
|
|
67
|
+
lines.push(boldColor(`P0 Critical (${data.p0_critical.length})`, severityPalette.error));
|
|
68
68
|
if (verbose) {
|
|
69
69
|
for (const item of data.p0_critical) {
|
|
70
|
-
lines.push(` ${
|
|
70
|
+
lines.push(` ${colorize(">", severityPalette.error)} ${bold(item.title)}${item.id ? dim(` (${item.id})`) : ""}`);
|
|
71
71
|
lines.push(` ${item.reason}`);
|
|
72
72
|
lines.push(` Effort: ${formatEffort(item.effort)}`);
|
|
73
73
|
}
|
|
@@ -77,10 +77,10 @@ function formatTriageHuman(data, verbose) {
|
|
|
77
77
|
}
|
|
78
78
|
lines.push("");
|
|
79
79
|
// P1
|
|
80
|
-
lines.push(
|
|
80
|
+
lines.push(boldColor(`P1 Important (${data.p1_important.length})`, severityPalette.warning));
|
|
81
81
|
if (verbose) {
|
|
82
82
|
for (const item of data.p1_important) {
|
|
83
|
-
lines.push(` ${
|
|
83
|
+
lines.push(` ${colorize(">", severityPalette.warning)} ${bold(item.title)}${item.id ? dim(` (${item.id})`) : ""}`);
|
|
84
84
|
lines.push(` ${item.reason}`);
|
|
85
85
|
lines.push(` Effort: ${formatEffort(item.effort)}`);
|
|
86
86
|
}
|
|
@@ -90,10 +90,10 @@ function formatTriageHuman(data, verbose) {
|
|
|
90
90
|
}
|
|
91
91
|
lines.push("");
|
|
92
92
|
// P2
|
|
93
|
-
lines.push(
|
|
93
|
+
lines.push(boldColor(`P2 Nice to Have (${data.p2_nice_to_have.length})`, brand.cyan));
|
|
94
94
|
if (verbose) {
|
|
95
95
|
for (const item of data.p2_nice_to_have) {
|
|
96
|
-
lines.push(` ${
|
|
96
|
+
lines.push(` ${colorize(">", brand.cyan)} ${bold(item.title)}${item.id ? dim(` (${item.id})`) : ""}`);
|
|
97
97
|
lines.push(` ${item.reason}`);
|
|
98
98
|
lines.push(` Effort: ${formatEffort(item.effort)}`);
|
|
99
99
|
}
|
|
@@ -104,14 +104,185 @@ function formatTriageHuman(data, verbose) {
|
|
|
104
104
|
lines.push("");
|
|
105
105
|
// Quick wins
|
|
106
106
|
if (data.quick_wins.length > 0) {
|
|
107
|
-
lines.push(
|
|
107
|
+
lines.push(boldColor("Quick Wins (< 5 min each)", brand.lime));
|
|
108
108
|
for (const win of data.quick_wins) {
|
|
109
|
-
lines.push(` ${
|
|
109
|
+
lines.push(` ${colorize("*", brand.lime)} ${win}`);
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
return lines.join("\n");
|
|
113
113
|
}
|
|
114
114
|
// ---------------------------------------------------------------------------
|
|
115
|
+
// Viewport helpers
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
/**
|
|
118
|
+
* Flatten triage p0/p1/p2 buckets into a ViewportItem array.
|
|
119
|
+
*
|
|
120
|
+
* Order: p0_critical → p1_important → p2_nice_to_have (preserves API order within buckets).
|
|
121
|
+
* Severity mapping: p0 → "critical", p1 → "warning", p2 → "moderate".
|
|
122
|
+
*/
|
|
123
|
+
export function buildViewportItems(data) {
|
|
124
|
+
const items = [];
|
|
125
|
+
for (const item of data.p0_critical) {
|
|
126
|
+
items.push({
|
|
127
|
+
id: item.id ?? item.title,
|
|
128
|
+
severity: "critical",
|
|
129
|
+
title: item.title,
|
|
130
|
+
effort: item.effort,
|
|
131
|
+
detail: item.reason,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
for (const item of data.p1_important) {
|
|
135
|
+
items.push({
|
|
136
|
+
id: item.id ?? item.title,
|
|
137
|
+
severity: "warning",
|
|
138
|
+
title: item.title,
|
|
139
|
+
effort: item.effort,
|
|
140
|
+
detail: item.reason,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
for (const item of data.p2_nice_to_have) {
|
|
144
|
+
items.push({
|
|
145
|
+
id: item.id ?? item.title,
|
|
146
|
+
severity: "moderate",
|
|
147
|
+
title: item.title,
|
|
148
|
+
effort: item.effort,
|
|
149
|
+
detail: item.reason,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
return items;
|
|
153
|
+
}
|
|
154
|
+
export async function handleTriage(opts) {
|
|
155
|
+
const format = resolveCommandFormat("triage", opts.format, opts.machine || false);
|
|
156
|
+
const verbose = opts.verbose || false;
|
|
157
|
+
const config = { apiKey: opts.apiKey };
|
|
158
|
+
// Resolve fail-fast mode: --strict > --continue-on-error > auto-detect
|
|
159
|
+
let failFast;
|
|
160
|
+
if (opts.strict && opts.continueOnError) {
|
|
161
|
+
writeOutput(renderWarning({ message: "--strict and --continue-on-error both set — --strict takes precedence" }));
|
|
162
|
+
failFast = true;
|
|
163
|
+
}
|
|
164
|
+
else if (opts.strict) {
|
|
165
|
+
failFast = true;
|
|
166
|
+
}
|
|
167
|
+
else if (opts.continueOnError) {
|
|
168
|
+
failFast = false;
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
failFast = isCI() || !isTTY();
|
|
172
|
+
}
|
|
173
|
+
// Resolve audit data
|
|
174
|
+
let auditPayload;
|
|
175
|
+
if (opts.job) {
|
|
176
|
+
// Fetch from API using SDK typed client
|
|
177
|
+
const sdkClient = createClient({ base: opts.base, apiKey: config.apiKey });
|
|
178
|
+
const result = await sdkClient.audits.retrieve(opts.job);
|
|
179
|
+
const issues = normalizeIssues(result.issues);
|
|
180
|
+
auditPayload = {
|
|
181
|
+
job_id: result.job_id || opts.job,
|
|
182
|
+
url: result.url || null,
|
|
183
|
+
scores: result.scores || null,
|
|
184
|
+
issues,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
// Read from stdin or --file
|
|
189
|
+
const input = await readJsonInput(opts.file);
|
|
190
|
+
if (!input) {
|
|
191
|
+
throw new Error("No audit data provided.\nUsage:\n vertaa audit https://example.com --json | vertaa triage\n vertaa triage --job <job-id>\n vertaa triage --file audit.json");
|
|
192
|
+
}
|
|
193
|
+
const data = input;
|
|
194
|
+
const innerData = (data.data && typeof data.data === "object" ? data.data : data);
|
|
195
|
+
const issues = normalizeIssues(innerData.issues);
|
|
196
|
+
auditPayload = {
|
|
197
|
+
job_id: innerData.job_id || null,
|
|
198
|
+
url: innerData.url || null,
|
|
199
|
+
scores: innerData.scores || null,
|
|
200
|
+
issues,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
if (!Array.isArray(auditPayload.issues) ||
|
|
204
|
+
auditPayload.issues.length === 0) {
|
|
205
|
+
throw new Error("No issues found in audit data.");
|
|
206
|
+
}
|
|
207
|
+
// Auth check
|
|
208
|
+
const base = resolveApiBase(opts.base);
|
|
209
|
+
const apiKey = getApiKey(config.apiKey);
|
|
210
|
+
const ctx = { triageData: null };
|
|
211
|
+
const renderer = createRenderer("auto");
|
|
212
|
+
const baseState = {
|
|
213
|
+
phase: "triage",
|
|
214
|
+
phaseIndex: 1,
|
|
215
|
+
phaseTotal: 1,
|
|
216
|
+
url: auditPayload.url || "",
|
|
217
|
+
mode: "triage",
|
|
218
|
+
progress: {},
|
|
219
|
+
totals: {},
|
|
220
|
+
issueCount: 0,
|
|
221
|
+
scorePreview: null,
|
|
222
|
+
verbose: false,
|
|
223
|
+
elapsed: 0,
|
|
224
|
+
};
|
|
225
|
+
const steps = [
|
|
226
|
+
{
|
|
227
|
+
id: "triage",
|
|
228
|
+
actionText: strings.triage.run.action,
|
|
229
|
+
summaryText: strings.triage.run.done(0, 0, 0),
|
|
230
|
+
run: async () => {
|
|
231
|
+
const response = await Promise.race([
|
|
232
|
+
apiRequest(base, "/cli/ai/triage", { method: "POST", body: { audit: auditPayload } }, apiKey),
|
|
233
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("LLM request timed out")), AI_TIMEOUT_MS)),
|
|
234
|
+
]);
|
|
235
|
+
ctx.triageData = response.data;
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
];
|
|
239
|
+
const { success, states } = await runSteps(steps, {
|
|
240
|
+
failFast,
|
|
241
|
+
onStateChange: (stepStates) => {
|
|
242
|
+
renderer.update({ ...baseState, stepStates });
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
renderer.finish({
|
|
246
|
+
url: auditPayload.url || "",
|
|
247
|
+
mode: "triage",
|
|
248
|
+
overallScore: success ? 100 : 0,
|
|
249
|
+
scores: {},
|
|
250
|
+
issueCount: 0,
|
|
251
|
+
passed: success,
|
|
252
|
+
elapsed: 0,
|
|
253
|
+
});
|
|
254
|
+
if (!success) {
|
|
255
|
+
const failedStep = states.find((s) => s.status === "failed");
|
|
256
|
+
process.stderr.write(renderError({
|
|
257
|
+
message: strings.triage.errors.triageFailed(failedStep?.failReason ?? "unknown error"),
|
|
258
|
+
suggestion: "vertaa doctor",
|
|
259
|
+
}) + "\n");
|
|
260
|
+
process.exit(ExitCode.ERROR);
|
|
261
|
+
}
|
|
262
|
+
const triageData = ctx.triageData;
|
|
263
|
+
if (!triageData) {
|
|
264
|
+
process.exit(ExitCode.ERROR);
|
|
265
|
+
}
|
|
266
|
+
if (format === "json") {
|
|
267
|
+
writeJsonOutput(triageData, "triage");
|
|
268
|
+
}
|
|
269
|
+
else if (isTTY() && isStdinTTY() && !isCI()) {
|
|
270
|
+
// Interactive viewport navigation — arrow-key navigable list
|
|
271
|
+
const viewportItems = buildViewportItems(triageData);
|
|
272
|
+
if (viewportItems.length > 0) {
|
|
273
|
+
const viewport = new ScrollingViewport({ items: viewportItems });
|
|
274
|
+
await viewport.run();
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
writeOutput(formatTriageHuman(triageData, verbose));
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
// Non-TTY / CI / piped-stdin fallback: current text output
|
|
282
|
+
writeOutput(formatTriageHuman(triageData, verbose));
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// ---------------------------------------------------------------------------
|
|
115
286
|
// Command Registration
|
|
116
287
|
// ---------------------------------------------------------------------------
|
|
117
288
|
export function registerTriageCommand(program) {
|
|
@@ -121,6 +292,8 @@ export function registerTriageCommand(program) {
|
|
|
121
292
|
.option("--job <job-id>", "Fetch audit data from a job ID")
|
|
122
293
|
.option("--file <path>", "Load audit JSON from file")
|
|
123
294
|
.option("-f, --format <format>", "Output format: json | human")
|
|
295
|
+
.option("--strict", "Fail immediately on first step error")
|
|
296
|
+
.option("--continue-on-error", "Continue on step errors even in CI")
|
|
124
297
|
.addHelpText("after", `
|
|
125
298
|
Examples:
|
|
126
299
|
vertaa audit https://example.com --json | vertaa triage
|
|
@@ -133,72 +306,24 @@ Examples:
|
|
|
133
306
|
const config = await resolveConfig(globalOpts.config);
|
|
134
307
|
const machineMode = globalOpts.machine || false;
|
|
135
308
|
const verbose = globalOpts.verbose || false;
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
}
|
|
309
|
+
await handleTriage({
|
|
310
|
+
job: options.job,
|
|
311
|
+
file: options.file,
|
|
312
|
+
format: options.format,
|
|
313
|
+
strict: options.strict,
|
|
314
|
+
continueOnError: options.continueOnError,
|
|
315
|
+
machine: machineMode,
|
|
316
|
+
verbose,
|
|
317
|
+
base: globalOpts.base,
|
|
318
|
+
apiKey: config.apiKey,
|
|
319
|
+
});
|
|
199
320
|
}
|
|
200
321
|
catch (error) {
|
|
201
|
-
|
|
322
|
+
process.stderr.write(renderError({
|
|
323
|
+
message: error instanceof Error ? error.message : String(error),
|
|
324
|
+
suggestion: "vertaa doctor",
|
|
325
|
+
exitCode: ExitCode.ERROR,
|
|
326
|
+
}) + "\n");
|
|
202
327
|
process.exit(ExitCode.ERROR);
|
|
203
328
|
}
|
|
204
329
|
});
|
|
@@ -5,6 +5,15 @@
|
|
|
5
5
|
* Enables sharing results across team members and CI runs.
|
|
6
6
|
*/
|
|
7
7
|
import { Command } from "commander";
|
|
8
|
+
/**
|
|
9
|
+
* Handle the upload command.
|
|
10
|
+
*/
|
|
11
|
+
export declare function handleUpload(jobId: string | undefined, options: {
|
|
12
|
+
baseline?: boolean;
|
|
13
|
+
project?: string;
|
|
14
|
+
base?: string;
|
|
15
|
+
configPath?: string;
|
|
16
|
+
}): Promise<void>;
|
|
8
17
|
/**
|
|
9
18
|
* Register the upload command with the Commander program.
|
|
10
19
|
*/
|
|
@@ -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;AA2CpC;;GAEG;AACH,wBAAsB,YAAY,CAChC,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,OAAO,EAAE;IACP,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GACA,OAAO,CAAC,IAAI,CAAC,CAwKf;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA4B5D"}
|
package/dist/commands/upload.js
CHANGED
|
@@ -6,14 +6,15 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import fs from "fs";
|
|
8
8
|
import path from "path";
|
|
9
|
-
import
|
|
10
|
-
import { createSpinner, succeedSpinner, failSpinner } from "../ui/spinner.js";
|
|
9
|
+
import { colorize, brand, renderError, runSteps, createRenderer } from "@vertaaux/tui";
|
|
11
10
|
import { loadToken } from "../auth/token-store.js";
|
|
12
11
|
import { getCIToken } from "../auth/ci-token.js";
|
|
13
|
-
import { resolveApiBase } from "../utils/client.js";
|
|
12
|
+
import { apiRequest, resolveApiBase } from "../utils/client.js";
|
|
14
13
|
import { resolveConfig } from "../config/loader.js";
|
|
15
14
|
import { loadBaseline, DEFAULT_BASELINE_PATH } from "../baseline/manager.js";
|
|
16
15
|
import { ExitCode } from "../utils/exit-codes.js";
|
|
16
|
+
import { writeOutput } from "../output/envelope.js";
|
|
17
|
+
import { strings } from "../ui/strings.js";
|
|
17
18
|
/**
|
|
18
19
|
* Artifacts directory.
|
|
19
20
|
*/
|
|
@@ -33,113 +34,141 @@ async function getAuthToken() {
|
|
|
33
34
|
/**
|
|
34
35
|
* Handle the upload command.
|
|
35
36
|
*/
|
|
36
|
-
async function handleUpload(jobId, options) {
|
|
37
|
+
export async function handleUpload(jobId, options) {
|
|
37
38
|
// Get auth token
|
|
38
39
|
const token = await getAuthToken();
|
|
39
40
|
if (!token) {
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
process.stderr.write(renderError({
|
|
42
|
+
message: strings.upload.errors.notAuthenticated,
|
|
43
|
+
suggestion: "vertaa login",
|
|
44
|
+
exitCode: ExitCode.ERROR,
|
|
45
|
+
}) + "\n");
|
|
42
46
|
process.exit(ExitCode.ERROR);
|
|
43
47
|
}
|
|
44
48
|
// Load config for API base (supports --config global option)
|
|
45
|
-
|
|
49
|
+
await resolveConfig(options.configPath);
|
|
46
50
|
const apiBase = resolveApiBase(options.base);
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
51
|
+
const renderer = createRenderer("auto");
|
|
52
|
+
const baseState = {
|
|
53
|
+
phase: "upload",
|
|
54
|
+
phaseIndex: 1,
|
|
55
|
+
phaseTotal: 1,
|
|
56
|
+
url: "",
|
|
57
|
+
mode: "upload",
|
|
58
|
+
progress: {},
|
|
59
|
+
totals: {},
|
|
60
|
+
issueCount: 0,
|
|
61
|
+
scorePreview: null,
|
|
62
|
+
verbose: false,
|
|
63
|
+
elapsed: 0,
|
|
64
|
+
};
|
|
65
|
+
const startTime = Date.now();
|
|
66
|
+
const ctx = {
|
|
67
|
+
uploadResult: null,
|
|
68
|
+
artifactCount: 1,
|
|
69
|
+
};
|
|
70
|
+
const steps = [
|
|
71
|
+
{
|
|
72
|
+
id: "read",
|
|
73
|
+
actionText: strings.upload.run.action,
|
|
74
|
+
summaryText: "File loaded",
|
|
75
|
+
run: async () => {
|
|
76
|
+
// If no job ID, find the most recent local result
|
|
77
|
+
let targetJobId = jobId;
|
|
78
|
+
let localResultPath;
|
|
79
|
+
if (!targetJobId) {
|
|
80
|
+
// Look for recent results in artifacts directory
|
|
81
|
+
const artifactsPath = path.resolve(process.cwd(), ARTIFACTS_DIR);
|
|
82
|
+
if (fs.existsSync(artifactsPath)) {
|
|
83
|
+
const dirs = fs.readdirSync(artifactsPath).filter((d) => {
|
|
84
|
+
const stat = fs.statSync(path.join(artifactsPath, d));
|
|
85
|
+
return stat.isDirectory();
|
|
86
|
+
});
|
|
87
|
+
if (dirs.length > 0) {
|
|
88
|
+
// Get most recent by mtime
|
|
89
|
+
const sorted = dirs
|
|
90
|
+
.map((d) => ({
|
|
91
|
+
name: d,
|
|
92
|
+
mtime: fs.statSync(path.join(artifactsPath, d)).mtime,
|
|
93
|
+
}))
|
|
94
|
+
.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
95
|
+
targetJobId = sorted[0].name;
|
|
96
|
+
localResultPath = path.join(artifactsPath, targetJobId);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (!targetJobId) {
|
|
100
|
+
throw new Error(strings.upload.noJobId);
|
|
101
|
+
}
|
|
72
102
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
103
|
+
// Prepare upload payload
|
|
104
|
+
const payload = {
|
|
105
|
+
job_id: targetJobId,
|
|
106
|
+
project: options.project || path.basename(process.cwd()),
|
|
107
|
+
};
|
|
108
|
+
// Add local results if available
|
|
109
|
+
if (localResultPath && fs.existsSync(localResultPath)) {
|
|
110
|
+
const files = fs.readdirSync(localResultPath);
|
|
111
|
+
const artifacts = {};
|
|
112
|
+
for (const file of files) {
|
|
113
|
+
const filePath = path.join(localResultPath, file);
|
|
114
|
+
const stat = fs.statSync(filePath);
|
|
115
|
+
// Skip large files (> 10MB)
|
|
116
|
+
if (stat.size > 10 * 1024 * 1024) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
// Read file content (base64 for binary)
|
|
120
|
+
const content = fs.readFileSync(filePath);
|
|
121
|
+
const ext = path.extname(file).toLowerCase();
|
|
122
|
+
const isBinary = [".png", ".jpg", ".jpeg", ".gif", ".zip"].includes(ext);
|
|
123
|
+
artifacts[file] = isBinary
|
|
124
|
+
? content.toString("base64")
|
|
125
|
+
: content.toString("utf-8");
|
|
126
|
+
}
|
|
127
|
+
payload.artifacts = artifacts;
|
|
128
|
+
ctx.artifactCount = Object.keys(artifacts).length;
|
|
96
129
|
}
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (baseline) {
|
|
111
|
-
payload.baseline = baseline;
|
|
112
|
-
spinner.setText(`Uploading audit ${targetJobId} with baseline...`);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
// Make API request
|
|
116
|
-
const response = await fetch(`${apiBase}/sync/upload`, {
|
|
117
|
-
method: "POST",
|
|
118
|
-
headers: {
|
|
119
|
-
"Content-Type": "application/json",
|
|
120
|
-
"X-API-Key": token,
|
|
130
|
+
// Upload baseline if requested
|
|
131
|
+
if (options.baseline) {
|
|
132
|
+
const baseline = await loadBaseline(DEFAULT_BASELINE_PATH);
|
|
133
|
+
if (baseline) {
|
|
134
|
+
payload.baseline = baseline;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// Make API request via apiRequest() — auth header constructed in client.ts only
|
|
138
|
+
const result = await apiRequest(apiBase, "/sync/upload", { method: "POST", body: payload }, token);
|
|
139
|
+
if (!result.success) {
|
|
140
|
+
throw new Error(result.error?.message || "Upload failed");
|
|
141
|
+
}
|
|
142
|
+
ctx.uploadResult = result;
|
|
121
143
|
},
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
144
|
+
},
|
|
145
|
+
];
|
|
146
|
+
const { success, states } = await runSteps(steps, {
|
|
147
|
+
failFast: true,
|
|
148
|
+
onStateChange: (stepStates) => {
|
|
149
|
+
renderer.update({ ...baseState, stepStates, elapsed: Date.now() - startTime });
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
renderer.finish({ url: "", mode: "upload", overallScore: 0, scores: {}, issueCount: 0, passed: success, elapsed: Date.now() - startTime });
|
|
153
|
+
if (!success) {
|
|
154
|
+
const failed = states.find(s => s.status === "failed");
|
|
155
|
+
process.stderr.write(renderError({
|
|
156
|
+
message: failed?.failReason || "Command failed",
|
|
157
|
+
suggestion: "vertaa doctor",
|
|
158
|
+
}) + "\n");
|
|
159
|
+
process.exitCode = ExitCode.ERROR;
|
|
138
160
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
161
|
+
const { uploadResult, artifactCount } = ctx;
|
|
162
|
+
if (uploadResult) {
|
|
163
|
+
const lines = [
|
|
164
|
+
strings.upload.run.done(artifactCount),
|
|
165
|
+
"",
|
|
166
|
+
` Job ID: ${uploadResult.job_id}`,
|
|
167
|
+
` URL: ${colorize(uploadResult.url, brand.cyan)}`,
|
|
168
|
+
"",
|
|
169
|
+
strings.upload.shareUrl,
|
|
170
|
+
];
|
|
171
|
+
writeOutput(lines.join("\n"));
|
|
143
172
|
}
|
|
144
173
|
}
|
|
145
174
|
/**
|
|
@@ -153,7 +182,17 @@ export function registerUploadCommand(program) {
|
|
|
153
182
|
.option("--project <name>", "Cloud project name (default: current directory name)")
|
|
154
183
|
.option("-b, --base <url>", "API base URL")
|
|
155
184
|
.action(async (jobId, options, command) => {
|
|
156
|
-
|
|
157
|
-
|
|
185
|
+
try {
|
|
186
|
+
const globalOpts = command.optsWithGlobals();
|
|
187
|
+
await handleUpload(jobId, { ...options, configPath: globalOpts.config });
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
process.stderr.write(renderError({
|
|
191
|
+
message: error instanceof Error ? error.message : String(error),
|
|
192
|
+
suggestion: "vertaa doctor",
|
|
193
|
+
exitCode: ExitCode.ERROR,
|
|
194
|
+
}) + "\n");
|
|
195
|
+
process.exit(ExitCode.ERROR);
|
|
196
|
+
}
|
|
158
197
|
});
|
|
159
198
|
}
|