@vertaaux/cli 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +345 -0
- package/dist/auth/ci-token.d.ts +49 -0
- package/dist/auth/ci-token.d.ts.map +1 -0
- package/dist/auth/ci-token.js +83 -0
- package/dist/auth/device-flow.d.ts +66 -0
- package/dist/auth/device-flow.d.ts.map +1 -0
- package/dist/auth/device-flow.js +156 -0
- package/dist/auth/token-store.d.ts +53 -0
- package/dist/auth/token-store.d.ts.map +1 -0
- package/dist/auth/token-store.js +78 -0
- package/dist/baseline/diff.d.ts +57 -0
- package/dist/baseline/diff.d.ts.map +1 -0
- package/dist/baseline/diff.js +152 -0
- package/dist/baseline/hash.d.ts +54 -0
- package/dist/baseline/hash.d.ts.map +1 -0
- package/dist/baseline/hash.js +66 -0
- package/dist/baseline/manager.d.ts +89 -0
- package/dist/baseline/manager.d.ts.map +1 -0
- package/dist/baseline/manager.js +157 -0
- package/dist/cache/index.d.ts +8 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +7 -0
- package/dist/cache/route-cache.d.ts +119 -0
- package/dist/cache/route-cache.d.ts.map +1 -0
- package/dist/cache/route-cache.js +213 -0
- package/dist/ci/changed-routes.d.ts +95 -0
- package/dist/ci/changed-routes.d.ts.map +1 -0
- package/dist/ci/changed-routes.js +304 -0
- package/dist/ci/github-api.d.ts +68 -0
- package/dist/ci/github-api.d.ts.map +1 -0
- package/dist/ci/github-api.js +138 -0
- package/dist/ci/gitlab-api.d.ts +75 -0
- package/dist/ci/gitlab-api.d.ts.map +1 -0
- package/dist/ci/gitlab-api.js +180 -0
- package/dist/ci/index.d.ts +6 -0
- package/dist/ci/index.d.ts.map +1 -0
- package/dist/ci/index.js +4 -0
- package/dist/commands/audit.d.ts +58 -0
- package/dist/commands/audit.d.ts.map +1 -0
- package/dist/commands/audit.js +862 -0
- package/dist/commands/baseline.d.ts +22 -0
- package/dist/commands/baseline.d.ts.map +1 -0
- package/dist/commands/baseline.js +210 -0
- package/dist/commands/comment.d.ts +14 -0
- package/dist/commands/comment.d.ts.map +1 -0
- package/dist/commands/comment.js +363 -0
- package/dist/commands/diff.d.ts +24 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +196 -0
- package/dist/commands/doctor.d.ts +58 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +338 -0
- package/dist/commands/download.d.ts +12 -0
- package/dist/commands/download.d.ts.map +1 -0
- package/dist/commands/download.js +183 -0
- package/dist/commands/explain.d.ts +62 -0
- package/dist/commands/explain.d.ts.map +1 -0
- package/dist/commands/explain.js +302 -0
- package/dist/commands/init.d.ts +12 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +212 -0
- package/dist/commands/login.d.ts +14 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +222 -0
- package/dist/commands/policy.d.ts +13 -0
- package/dist/commands/policy.d.ts.map +1 -0
- package/dist/commands/policy.js +347 -0
- package/dist/commands/upload.d.ts +12 -0
- package/dist/commands/upload.d.ts.map +1 -0
- package/dist/commands/upload.js +158 -0
- package/dist/config/defaults.d.ts +21 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +49 -0
- package/dist/config/loader.d.ts +66 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +167 -0
- package/dist/config/schema.d.ts +55 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +6 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1090 -0
- package/dist/interactive/fix-wizard.d.ts +44 -0
- package/dist/interactive/fix-wizard.d.ts.map +1 -0
- package/dist/interactive/fix-wizard.js +286 -0
- package/dist/interactive/init-wizard.d.ts +32 -0
- package/dist/interactive/init-wizard.d.ts.map +1 -0
- package/dist/interactive/init-wizard.js +193 -0
- package/dist/interactive/prompts.d.ts +62 -0
- package/dist/interactive/prompts.d.ts.map +1 -0
- package/dist/interactive/prompts.js +78 -0
- package/dist/monorepo/detector.d.ts +70 -0
- package/dist/monorepo/detector.d.ts.map +1 -0
- package/dist/monorepo/detector.js +278 -0
- package/dist/monorepo/index.d.ts +9 -0
- package/dist/monorepo/index.d.ts.map +1 -0
- package/dist/monorepo/index.js +8 -0
- package/dist/monorepo/workspace.d.ts +142 -0
- package/dist/monorepo/workspace.d.ts.map +1 -0
- package/dist/monorepo/workspace.js +171 -0
- package/dist/output/envelope.d.ts +21 -0
- package/dist/output/envelope.d.ts.map +1 -0
- package/dist/output/envelope.js +27 -0
- package/dist/output/factory.d.ts +73 -0
- package/dist/output/factory.d.ts.map +1 -0
- package/dist/output/factory.js +60 -0
- package/dist/output/formats.d.ts +11 -0
- package/dist/output/formats.d.ts.map +1 -0
- package/dist/output/formats.js +41 -0
- package/dist/output/html.d.ts +45 -0
- package/dist/output/html.d.ts.map +1 -0
- package/dist/output/html.js +607 -0
- package/dist/output/human.d.ts +41 -0
- package/dist/output/human.d.ts.map +1 -0
- package/dist/output/human.js +274 -0
- package/dist/output/json.d.ts +42 -0
- package/dist/output/json.d.ts.map +1 -0
- package/dist/output/json.js +37 -0
- package/dist/output/junit.d.ts +56 -0
- package/dist/output/junit.d.ts.map +1 -0
- package/dist/output/junit.js +135 -0
- package/dist/output/markdown.d.ts +77 -0
- package/dist/output/markdown.d.ts.map +1 -0
- package/dist/output/markdown.js +411 -0
- package/dist/output/sarif.d.ts +160 -0
- package/dist/output/sarif.d.ts.map +1 -0
- package/dist/output/sarif.js +207 -0
- package/dist/policy/evaluator.d.ts +111 -0
- package/dist/policy/evaluator.d.ts.map +1 -0
- package/dist/policy/evaluator.js +362 -0
- package/dist/policy/index.d.ts +15 -0
- package/dist/policy/index.d.ts.map +1 -0
- package/dist/policy/index.js +11 -0
- package/dist/policy/loader.d.ts +97 -0
- package/dist/policy/loader.d.ts.map +1 -0
- package/dist/policy/loader.js +281 -0
- package/dist/policy/schema.d.ts +297 -0
- package/dist/policy/schema.d.ts.map +1 -0
- package/dist/policy/schema.js +230 -0
- package/dist/quality-gate/evaluator.d.ts +58 -0
- package/dist/quality-gate/evaluator.d.ts.map +1 -0
- package/dist/quality-gate/evaluator.js +274 -0
- package/dist/quality-gate/index.d.ts +10 -0
- package/dist/quality-gate/index.d.ts.map +1 -0
- package/dist/quality-gate/index.js +7 -0
- package/dist/quality-gate/types.d.ts +103 -0
- package/dist/quality-gate/types.d.ts.map +1 -0
- package/dist/quality-gate/types.js +23 -0
- package/dist/templates/azure-devops.d.ts +25 -0
- package/dist/templates/azure-devops.d.ts.map +1 -0
- package/dist/templates/azure-devops.js +109 -0
- package/dist/templates/circleci.d.ts +28 -0
- package/dist/templates/circleci.d.ts.map +1 -0
- package/dist/templates/circleci.js +86 -0
- package/dist/templates/github-actions.d.ts +81 -0
- package/dist/templates/github-actions.d.ts.map +1 -0
- package/dist/templates/github-actions.js +393 -0
- package/dist/templates/gitlab-ci.d.ts +26 -0
- package/dist/templates/gitlab-ci.d.ts.map +1 -0
- package/dist/templates/gitlab-ci.js +70 -0
- package/dist/templates/index.d.ts +72 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/index.js +112 -0
- package/dist/templates/jenkins.d.ts +26 -0
- package/dist/templates/jenkins.d.ts.map +1 -0
- package/dist/templates/jenkins.js +110 -0
- package/dist/ui/banner.d.ts +31 -0
- package/dist/ui/banner.d.ts.map +1 -0
- package/dist/ui/banner.js +84 -0
- package/dist/ui/diagnostics.d.ts +39 -0
- package/dist/ui/diagnostics.d.ts.map +1 -0
- package/dist/ui/diagnostics.js +153 -0
- package/dist/ui/spinner.d.ts +61 -0
- package/dist/ui/spinner.d.ts.map +1 -0
- package/dist/ui/spinner.js +101 -0
- package/dist/ui/table.d.ts +63 -0
- package/dist/ui/table.d.ts.map +1 -0
- package/dist/ui/table.js +236 -0
- package/dist/utils/client.d.ts +82 -0
- package/dist/utils/client.d.ts.map +1 -0
- package/dist/utils/client.js +128 -0
- package/dist/utils/detect-env.d.ts +59 -0
- package/dist/utils/detect-env.d.ts.map +1 -0
- package/dist/utils/detect-env.js +115 -0
- package/dist/utils/exit-codes.d.ts +47 -0
- package/dist/utils/exit-codes.d.ts.map +1 -0
- package/dist/utils/exit-codes.js +61 -0
- package/dist/utils/logger.d.ts +87 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +185 -0
- package/dist/utils/sanitize.d.ts +36 -0
- package/dist/utils/sanitize.d.ts.map +1 -0
- package/dist/utils/sanitize.js +64 -0
- package/dist/utils/validators.d.ts +41 -0
- package/dist/utils/validators.d.ts.map +1 -0
- package/dist/utils/validators.js +123 -0
- package/package.json +63 -0
- package/schemas/vertaaux.config.schema.json +103 -0
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diff command for VertaaUX CLI.
|
|
3
|
+
*
|
|
4
|
+
* Compares current audit results against baseline or previous audit.
|
|
5
|
+
* Shows three categories: new, fixed, still present.
|
|
6
|
+
*
|
|
7
|
+
* Exit codes:
|
|
8
|
+
* - 0: No new issues
|
|
9
|
+
* - 1: New issues found
|
|
10
|
+
* - 2: Error
|
|
11
|
+
*/
|
|
12
|
+
import { readFile } from "fs/promises";
|
|
13
|
+
import { existsSync } from "fs";
|
|
14
|
+
import { ExitCode } from "../utils/exit-codes.js";
|
|
15
|
+
import { loadBaseline, DEFAULT_BASELINE_PATH } from "../baseline/manager.js";
|
|
16
|
+
import { computeDiff, formatDiffHuman, formatDiffJson } from "../baseline/diff.js";
|
|
17
|
+
import { writeJsonOutput, writeOutput } from "../output/envelope.js";
|
|
18
|
+
import { resolveCommandFormat } from "../output/formats.js";
|
|
19
|
+
const DEFAULT_API_BASE = "https://vertaaux.ai/v1";
|
|
20
|
+
/**
|
|
21
|
+
* Get API base URL from environment.
|
|
22
|
+
*/
|
|
23
|
+
function getApiBase() {
|
|
24
|
+
return (process.env.VERTAAUX_API_BASE || DEFAULT_API_BASE).replace(/\/$/, "");
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Get API key from environment.
|
|
28
|
+
*/
|
|
29
|
+
function getApiKey() {
|
|
30
|
+
const key = process.env.VERTAAUX_API_KEY;
|
|
31
|
+
if (!key) {
|
|
32
|
+
throw new Error("VERTAAUX_API_KEY is required");
|
|
33
|
+
}
|
|
34
|
+
return key;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Fetch audit results from API.
|
|
38
|
+
*/
|
|
39
|
+
async function fetchAudit(jobId) {
|
|
40
|
+
const base = getApiBase();
|
|
41
|
+
const url = `${base}/audit/${jobId}`;
|
|
42
|
+
const res = await fetch(url, {
|
|
43
|
+
method: "GET",
|
|
44
|
+
headers: {
|
|
45
|
+
"Content-Type": "application/json",
|
|
46
|
+
"X-API-Key": getApiKey(),
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
if (!res.ok) {
|
|
50
|
+
let detail = res.statusText;
|
|
51
|
+
try {
|
|
52
|
+
const data = (await res.json());
|
|
53
|
+
detail = data.error || data.message || detail;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// ignore
|
|
57
|
+
}
|
|
58
|
+
throw new Error(`HTTP ${res.status}: ${detail}`);
|
|
59
|
+
}
|
|
60
|
+
return (await res.json());
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Normalize issues from various API response formats.
|
|
64
|
+
*/
|
|
65
|
+
function normalizeIssues(issues) {
|
|
66
|
+
if (Array.isArray(issues))
|
|
67
|
+
return issues;
|
|
68
|
+
if (issues && typeof issues === "object") {
|
|
69
|
+
const values = Object.values(issues);
|
|
70
|
+
return values.flatMap((value) => Array.isArray(value) ? value : []);
|
|
71
|
+
}
|
|
72
|
+
return [];
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Register the diff command with the Commander program.
|
|
76
|
+
*/
|
|
77
|
+
export function registerDiffCommand(program) {
|
|
78
|
+
program
|
|
79
|
+
.command("diff [job-id]")
|
|
80
|
+
.description("Compare audit results against baseline")
|
|
81
|
+
.option("--baseline <path>", "Baseline file path", DEFAULT_BASELINE_PATH)
|
|
82
|
+
.option("--compare <job-id>", "Compare two audit jobs instead of baseline")
|
|
83
|
+
.option("--format <format>", "Output format: json | human (default: auto)")
|
|
84
|
+
.option("--verbose", "Show all present issues")
|
|
85
|
+
.option("--from-file <file>", "Read current audit from JSON file")
|
|
86
|
+
.action(async (jobIdArg, cmdOptions, cmd) => {
|
|
87
|
+
try {
|
|
88
|
+
const baselinePath = cmdOptions.baseline || DEFAULT_BASELINE_PATH;
|
|
89
|
+
const machineMode = cmd.optsWithGlobals?.().machine || false;
|
|
90
|
+
const format = resolveCommandFormat("diff", cmdOptions.format, machineMode);
|
|
91
|
+
const verbose = cmdOptions.verbose || false;
|
|
92
|
+
// Get current audit issues
|
|
93
|
+
let currentIssues;
|
|
94
|
+
let currentUrl;
|
|
95
|
+
if (cmdOptions.fromFile) {
|
|
96
|
+
// Load from file
|
|
97
|
+
const filePath = cmdOptions.fromFile;
|
|
98
|
+
if (!existsSync(filePath)) {
|
|
99
|
+
console.error(`File not found: ${filePath}`);
|
|
100
|
+
process.exit(ExitCode.ERROR);
|
|
101
|
+
}
|
|
102
|
+
const content = await readFile(filePath, "utf-8");
|
|
103
|
+
const data = JSON.parse(content);
|
|
104
|
+
currentIssues = normalizeIssues(data.issues);
|
|
105
|
+
currentUrl = data.url || "unknown";
|
|
106
|
+
}
|
|
107
|
+
else if (jobIdArg) {
|
|
108
|
+
// Fetch from API
|
|
109
|
+
const audit = await fetchAudit(jobIdArg);
|
|
110
|
+
if (audit.status !== "completed") {
|
|
111
|
+
console.error(`Audit ${jobIdArg} is not completed (status: ${audit.status})`);
|
|
112
|
+
process.exit(ExitCode.ERROR);
|
|
113
|
+
}
|
|
114
|
+
currentIssues = normalizeIssues(audit.issues);
|
|
115
|
+
currentUrl = audit.url || "unknown";
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
console.error("Provide job ID or use --from-file");
|
|
119
|
+
console.error("Usage: vertaa diff <job-id>");
|
|
120
|
+
console.error(" vertaa diff --from-file <audit.json>");
|
|
121
|
+
process.exit(ExitCode.ERROR);
|
|
122
|
+
}
|
|
123
|
+
// Handle --compare: compare two audits
|
|
124
|
+
if (cmdOptions.compare) {
|
|
125
|
+
const compareAudit = await fetchAudit(cmdOptions.compare);
|
|
126
|
+
if (compareAudit.status !== "completed") {
|
|
127
|
+
console.error(`Audit ${cmdOptions.compare} is not completed (status: ${compareAudit.status})`);
|
|
128
|
+
process.exit(ExitCode.ERROR);
|
|
129
|
+
}
|
|
130
|
+
// Create temporary baseline from compare job
|
|
131
|
+
const compareIssues = normalizeIssues(compareAudit.issues);
|
|
132
|
+
const tempBaseline = {
|
|
133
|
+
version: 1,
|
|
134
|
+
created: new Date().toISOString(),
|
|
135
|
+
updated: new Date().toISOString(),
|
|
136
|
+
url: compareAudit.url || "unknown",
|
|
137
|
+
issues: compareIssues.map((issue) => ({
|
|
138
|
+
fingerprint: "",
|
|
139
|
+
ruleId: issue.ruleId || issue.rule_id || issue.id || "unknown",
|
|
140
|
+
severity: issue.severity || "warning",
|
|
141
|
+
category: issue.category || "general",
|
|
142
|
+
description: issue.description || "",
|
|
143
|
+
selector: issue.selector,
|
|
144
|
+
baselinedAt: new Date().toISOString(),
|
|
145
|
+
})),
|
|
146
|
+
};
|
|
147
|
+
// Re-generate fingerprints using the proper function
|
|
148
|
+
const { generateFingerprint } = await import("../baseline/hash.js");
|
|
149
|
+
for (let i = 0; i < tempBaseline.issues.length; i++) {
|
|
150
|
+
tempBaseline.issues[i].fingerprint = generateFingerprint(compareIssues[i]);
|
|
151
|
+
}
|
|
152
|
+
const diff = computeDiff(currentIssues, tempBaseline);
|
|
153
|
+
// Output
|
|
154
|
+
if (format === "json") {
|
|
155
|
+
const diffData = JSON.parse(formatDiffJson(diff));
|
|
156
|
+
writeJsonOutput(diffData, "diff");
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
console.error(`Comparing ${currentUrl} (current) vs ${tempBaseline.url} (previous)\n`);
|
|
160
|
+
writeOutput(formatDiffHuman(diff, verbose));
|
|
161
|
+
}
|
|
162
|
+
// Exit code: 1 if new issues
|
|
163
|
+
if (diff.summary.newCount > 0) {
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
// Load baseline
|
|
169
|
+
const baseline = await loadBaseline(baselinePath);
|
|
170
|
+
if (!baseline) {
|
|
171
|
+
console.error(`No baseline found at: ${baselinePath}`);
|
|
172
|
+
console.error("Create one with: vertaa baseline <job-id>");
|
|
173
|
+
process.exit(ExitCode.ERROR);
|
|
174
|
+
}
|
|
175
|
+
// Compute diff
|
|
176
|
+
const diff = computeDiff(currentIssues, baseline);
|
|
177
|
+
// Output
|
|
178
|
+
if (format === "json") {
|
|
179
|
+
const diffData = JSON.parse(formatDiffJson(diff));
|
|
180
|
+
writeJsonOutput(diffData, "diff");
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
console.error(`Comparing ${currentUrl} against baseline\n`);
|
|
184
|
+
writeOutput(formatDiffHuman(diff, verbose));
|
|
185
|
+
}
|
|
186
|
+
// Exit code: 1 if new issues
|
|
187
|
+
if (diff.summary.newCount > 0) {
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
193
|
+
process.exit(ExitCode.ERROR);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doctor command for VertaaUX CLI.
|
|
3
|
+
*
|
|
4
|
+
* Runs health checks on config, auth, network, and output contracts.
|
|
5
|
+
* Reports pass/fail with actionable remediation instructions.
|
|
6
|
+
* Never crashes -- all checks are wrapped in try/catch.
|
|
7
|
+
*/
|
|
8
|
+
import type { Command } from "commander";
|
|
9
|
+
export interface DoctorCheck {
|
|
10
|
+
name: string;
|
|
11
|
+
status: "pass" | "fail" | "warn" | "skip";
|
|
12
|
+
message: string;
|
|
13
|
+
remediation?: string;
|
|
14
|
+
detail?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface DoctorResult {
|
|
17
|
+
checks: DoctorCheck[];
|
|
18
|
+
summary: {
|
|
19
|
+
pass: number;
|
|
20
|
+
fail: number;
|
|
21
|
+
warn: number;
|
|
22
|
+
skip: number;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Check 1: Configuration validity.
|
|
27
|
+
* Uses resolveConfig() from config/loader.ts.
|
|
28
|
+
*/
|
|
29
|
+
export declare function checkConfig(): Promise<DoctorCheck>;
|
|
30
|
+
/**
|
|
31
|
+
* Check 2: Authentication status.
|
|
32
|
+
* Checks CI token env vars first, then stored credentials.
|
|
33
|
+
* --online flag verifies credentials against the API.
|
|
34
|
+
*/
|
|
35
|
+
export declare function checkAuth(online: boolean): Promise<DoctorCheck>;
|
|
36
|
+
/**
|
|
37
|
+
* Check 3: Network connectivity.
|
|
38
|
+
* Uses fetch with 5s timeout to the /auth/validate endpoint.
|
|
39
|
+
* Any response (even 401) counts as reachable.
|
|
40
|
+
*/
|
|
41
|
+
export declare function checkNetwork(): Promise<DoctorCheck>;
|
|
42
|
+
/**
|
|
43
|
+
* Check 4: Output contract health (only with --deep).
|
|
44
|
+
* Verifies COMMAND_FORMATS registry has entries for expected commands
|
|
45
|
+
* and that the config schema file is loadable.
|
|
46
|
+
*/
|
|
47
|
+
export declare function checkOutputContracts(deep: boolean): DoctorCheck;
|
|
48
|
+
export declare function formatDoctorHuman(result: DoctorResult): string;
|
|
49
|
+
export declare function handleDoctor(options: {
|
|
50
|
+
online?: boolean;
|
|
51
|
+
deep?: boolean;
|
|
52
|
+
format?: string;
|
|
53
|
+
}): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Register the doctor command with the Commander program.
|
|
56
|
+
*/
|
|
57
|
+
export declare function registerDoctorCommand(program: Command): void;
|
|
58
|
+
//# sourceMappingURL=doctor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmBzC,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACrE;AAMD;;;GAGG;AACH,wBAAsB,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC,CAkCxD;AAED;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,WAAW,CAAC,CA4FrE;AAED;;;;GAIG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,WAAW,CAAC,CAsBzD;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,OAAO,GAAG,WAAW,CAuD/D;AAMD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,GAAG,MAAM,CAqC9D;AAMD,wBAAsB,YAAY,CAAC,OAAO,EAAE;IAC1C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgChB;AAMD;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAwB5D"}
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doctor command for VertaaUX CLI.
|
|
3
|
+
*
|
|
4
|
+
* Runs health checks on config, auth, network, and output contracts.
|
|
5
|
+
* Reports pass/fail with actionable remediation instructions.
|
|
6
|
+
* Never crashes -- all checks are wrapped in try/catch.
|
|
7
|
+
*/
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
import { createRequire } from "module";
|
|
10
|
+
import { resolveConfig } from "../config/loader.js";
|
|
11
|
+
import { ConfigValidationError, ConfigLoadError } from "../config/loader.js";
|
|
12
|
+
import { loadToken, isTokenExpired, getCredentialsPath } from "../auth/token-store.js";
|
|
13
|
+
import { getCIToken, validateCIToken } from "../auth/ci-token.js";
|
|
14
|
+
import { resolveApiBase } from "../utils/client.js";
|
|
15
|
+
import { ExitCode } from "../utils/exit-codes.js";
|
|
16
|
+
import { writeJsonOutput, writeOutput } from "../output/envelope.js";
|
|
17
|
+
import { COMMAND_FORMATS } from "../output/formats.js";
|
|
18
|
+
import { getVersion } from "../ui/banner.js";
|
|
19
|
+
const require = createRequire(import.meta.url);
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Individual health checks
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
/**
|
|
24
|
+
* Check 1: Configuration validity.
|
|
25
|
+
* Uses resolveConfig() from config/loader.ts.
|
|
26
|
+
*/
|
|
27
|
+
export async function checkConfig() {
|
|
28
|
+
try {
|
|
29
|
+
await resolveConfig();
|
|
30
|
+
return {
|
|
31
|
+
name: "Configuration",
|
|
32
|
+
status: "pass",
|
|
33
|
+
message: "Config loaded and validated successfully",
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (error instanceof ConfigValidationError) {
|
|
38
|
+
return {
|
|
39
|
+
name: "Configuration",
|
|
40
|
+
status: "fail",
|
|
41
|
+
message: `Config validation failed: ${error.message}`,
|
|
42
|
+
remediation: "Run: vertaa init -y --force",
|
|
43
|
+
detail: "Config file exists but contains invalid values",
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
if (error instanceof ConfigLoadError) {
|
|
47
|
+
return {
|
|
48
|
+
name: "Configuration",
|
|
49
|
+
status: "warn",
|
|
50
|
+
message: "No config file found (using defaults)",
|
|
51
|
+
remediation: "Run: vertaa init",
|
|
52
|
+
detail: "CLI will use built-in defaults without a config file",
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
name: "Configuration",
|
|
57
|
+
status: "fail",
|
|
58
|
+
message: `Unexpected error: ${error instanceof Error ? error.message : String(error)}`,
|
|
59
|
+
remediation: "Run: vertaa init -y --force",
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Check 2: Authentication status.
|
|
65
|
+
* Checks CI token env vars first, then stored credentials.
|
|
66
|
+
* --online flag verifies credentials against the API.
|
|
67
|
+
*/
|
|
68
|
+
export async function checkAuth(online) {
|
|
69
|
+
try {
|
|
70
|
+
// Check for CI token first (env var)
|
|
71
|
+
const ciToken = getCIToken();
|
|
72
|
+
if (ciToken) {
|
|
73
|
+
if (online) {
|
|
74
|
+
const apiBase = resolveApiBase();
|
|
75
|
+
try {
|
|
76
|
+
const valid = await validateCIToken(ciToken, apiBase);
|
|
77
|
+
return {
|
|
78
|
+
name: "Authentication",
|
|
79
|
+
status: valid ? "pass" : "fail",
|
|
80
|
+
message: valid
|
|
81
|
+
? "CI token valid (verified online)"
|
|
82
|
+
: "CI token rejected by API",
|
|
83
|
+
remediation: valid ? undefined : "Check your VERTAAUX_API_KEY or VERTAAUX_TOKEN value",
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return {
|
|
88
|
+
name: "Authentication",
|
|
89
|
+
status: "warn",
|
|
90
|
+
message: "CI token found but online verification failed",
|
|
91
|
+
remediation: "Check network connectivity for online token validation",
|
|
92
|
+
detail: "CI token present in environment but could not verify against API",
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
name: "Authentication",
|
|
98
|
+
status: "pass",
|
|
99
|
+
message: "CI token found in environment",
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
// Check stored credentials
|
|
103
|
+
const token = await loadToken();
|
|
104
|
+
if (!token) {
|
|
105
|
+
return {
|
|
106
|
+
name: "Authentication",
|
|
107
|
+
status: "fail",
|
|
108
|
+
message: "No credentials found",
|
|
109
|
+
remediation: "Run: vertaa login",
|
|
110
|
+
detail: `Checked: ${getCredentialsPath()} and env vars VERTAAUX_TOKEN, VERTAAUX_API_KEY`,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
if (isTokenExpired(token)) {
|
|
114
|
+
return {
|
|
115
|
+
name: "Authentication",
|
|
116
|
+
status: "fail",
|
|
117
|
+
message: "Token expired",
|
|
118
|
+
remediation: "Run: vertaa login",
|
|
119
|
+
detail: `Token expired at ${token.expiresAt}`,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
// Optionally verify online
|
|
123
|
+
if (online) {
|
|
124
|
+
const apiBase = resolveApiBase();
|
|
125
|
+
try {
|
|
126
|
+
const valid = await validateCIToken(token.accessToken, apiBase);
|
|
127
|
+
if (!valid) {
|
|
128
|
+
return {
|
|
129
|
+
name: "Authentication",
|
|
130
|
+
status: "fail",
|
|
131
|
+
message: "Token rejected by API (may be revoked)",
|
|
132
|
+
remediation: "Run: vertaa login",
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
return {
|
|
138
|
+
name: "Authentication",
|
|
139
|
+
status: "warn",
|
|
140
|
+
message: "Token found but online verification failed",
|
|
141
|
+
remediation: "Check network connectivity for online token validation",
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
name: "Authentication",
|
|
147
|
+
status: "pass",
|
|
148
|
+
message: `Authenticated (${token.type} token, saved ${token.savedAt})`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
return {
|
|
153
|
+
name: "Authentication",
|
|
154
|
+
status: "fail",
|
|
155
|
+
message: `Auth check error: ${error instanceof Error ? error.message : String(error)}`,
|
|
156
|
+
remediation: "Run: vertaa login",
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Check 3: Network connectivity.
|
|
162
|
+
* Uses fetch with 5s timeout to the /auth/validate endpoint.
|
|
163
|
+
* Any response (even 401) counts as reachable.
|
|
164
|
+
*/
|
|
165
|
+
export async function checkNetwork() {
|
|
166
|
+
const apiBase = resolveApiBase();
|
|
167
|
+
try {
|
|
168
|
+
const res = await fetch(`${apiBase}/auth/validate`, {
|
|
169
|
+
method: "GET",
|
|
170
|
+
signal: AbortSignal.timeout(5000),
|
|
171
|
+
});
|
|
172
|
+
// Any response means API is reachable (even 401/403)
|
|
173
|
+
return {
|
|
174
|
+
name: "Network",
|
|
175
|
+
status: "pass",
|
|
176
|
+
message: `API reachable at ${apiBase} (HTTP ${res.status})`,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
181
|
+
return {
|
|
182
|
+
name: "Network",
|
|
183
|
+
status: "fail",
|
|
184
|
+
message: `Cannot reach API: ${msg}`,
|
|
185
|
+
remediation: `Check network connectivity. API base: ${apiBase}. Set VERTAAUX_API_BASE to override.`,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Check 4: Output contract health (only with --deep).
|
|
191
|
+
* Verifies COMMAND_FORMATS registry has entries for expected commands
|
|
192
|
+
* and that the config schema file is loadable.
|
|
193
|
+
*/
|
|
194
|
+
export function checkOutputContracts(deep) {
|
|
195
|
+
if (!deep) {
|
|
196
|
+
return {
|
|
197
|
+
name: "Output Contracts",
|
|
198
|
+
status: "skip",
|
|
199
|
+
message: "Run with --deep to check output contracts",
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
const expectedCommands = ["audit", "comment", "explain", "policy-show", "diff"];
|
|
204
|
+
const registeredCommands = Object.keys(COMMAND_FORMATS);
|
|
205
|
+
const missing = expectedCommands.filter((cmd) => !registeredCommands.includes(cmd));
|
|
206
|
+
if (missing.length > 0) {
|
|
207
|
+
return {
|
|
208
|
+
name: "Output Contracts",
|
|
209
|
+
status: "warn",
|
|
210
|
+
message: `Format registry missing entries for: ${missing.join(", ")}`,
|
|
211
|
+
detail: `Registered commands: ${registeredCommands.join(", ")}`,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
// Check schema file accessibility via createRequire
|
|
215
|
+
let schemaOk = false;
|
|
216
|
+
try {
|
|
217
|
+
require("../../schemas/vertaaux.config.schema.json");
|
|
218
|
+
schemaOk = true;
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// Schema not found
|
|
222
|
+
}
|
|
223
|
+
if (!schemaOk) {
|
|
224
|
+
return {
|
|
225
|
+
name: "Output Contracts",
|
|
226
|
+
status: "warn",
|
|
227
|
+
message: `Output contracts registered for ${registeredCommands.length} commands, but config schema file not accessible`,
|
|
228
|
+
detail: "Schema file: schemas/vertaaux.config.schema.json",
|
|
229
|
+
remediation: "Reinstall CLI: npm install -g @vertaaux/cli",
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
name: "Output Contracts",
|
|
234
|
+
status: "pass",
|
|
235
|
+
message: `Output contracts registered for ${registeredCommands.length} commands, schema valid`,
|
|
236
|
+
detail: `Commands: ${registeredCommands.join(", ")}`,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
return {
|
|
241
|
+
name: "Output Contracts",
|
|
242
|
+
status: "fail",
|
|
243
|
+
message: `Contract check error: ${error instanceof Error ? error.message : String(error)}`,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
// Output formatting
|
|
249
|
+
// ---------------------------------------------------------------------------
|
|
250
|
+
export function formatDoctorHuman(result) {
|
|
251
|
+
const statusIcons = {
|
|
252
|
+
pass: chalk.green("PASS"),
|
|
253
|
+
fail: chalk.red("FAIL"),
|
|
254
|
+
warn: chalk.yellow("WARN"),
|
|
255
|
+
skip: chalk.dim("SKIP"),
|
|
256
|
+
};
|
|
257
|
+
const lines = [];
|
|
258
|
+
const version = getVersion();
|
|
259
|
+
lines.push(chalk.bold(`VertaaUX Doctor`) + chalk.dim(` v${version}`));
|
|
260
|
+
lines.push("");
|
|
261
|
+
for (const check of result.checks) {
|
|
262
|
+
lines.push(` ${statusIcons[check.status]} ${check.name}: ${check.message}`);
|
|
263
|
+
if (check.remediation) {
|
|
264
|
+
lines.push(` ${chalk.cyan("Fix:")} ${check.remediation}`);
|
|
265
|
+
}
|
|
266
|
+
if (check.detail) {
|
|
267
|
+
lines.push(` ${chalk.dim(check.detail)}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
lines.push("");
|
|
271
|
+
const { pass, fail, warn } = result.summary;
|
|
272
|
+
if (fail > 0) {
|
|
273
|
+
lines.push(chalk.red(`${fail} check(s) failed.`) +
|
|
274
|
+
" Run the suggested fix commands above.");
|
|
275
|
+
}
|
|
276
|
+
else if (warn > 0) {
|
|
277
|
+
lines.push(chalk.yellow(`All checks passed (${warn} warning(s)).`));
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
lines.push(chalk.green(`All ${pass} checks passed.`));
|
|
281
|
+
}
|
|
282
|
+
return lines.join("\n");
|
|
283
|
+
}
|
|
284
|
+
// ---------------------------------------------------------------------------
|
|
285
|
+
// Command handler
|
|
286
|
+
// ---------------------------------------------------------------------------
|
|
287
|
+
export async function handleDoctor(options) {
|
|
288
|
+
const format = options.format || "human";
|
|
289
|
+
const online = options.online || false;
|
|
290
|
+
const deep = options.deep || false;
|
|
291
|
+
const checks = [];
|
|
292
|
+
// Run checks in dependency order
|
|
293
|
+
checks.push(await checkConfig());
|
|
294
|
+
checks.push(await checkAuth(online));
|
|
295
|
+
checks.push(await checkNetwork());
|
|
296
|
+
checks.push(checkOutputContracts(deep));
|
|
297
|
+
// Build summary
|
|
298
|
+
const summary = { pass: 0, fail: 0, warn: 0, skip: 0 };
|
|
299
|
+
for (const check of checks) {
|
|
300
|
+
summary[check.status]++;
|
|
301
|
+
}
|
|
302
|
+
const result = { checks, summary };
|
|
303
|
+
// Output
|
|
304
|
+
if (format === "json") {
|
|
305
|
+
writeJsonOutput(result, "doctor");
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
writeOutput(formatDoctorHuman(result));
|
|
309
|
+
}
|
|
310
|
+
// Exit code: 0 on all-pass/warn, 1 on any fail
|
|
311
|
+
if (summary.fail > 0) {
|
|
312
|
+
process.exitCode = ExitCode.ISSUES_FOUND;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
// ---------------------------------------------------------------------------
|
|
316
|
+
// Command registration
|
|
317
|
+
// ---------------------------------------------------------------------------
|
|
318
|
+
/**
|
|
319
|
+
* Register the doctor command with the Commander program.
|
|
320
|
+
*/
|
|
321
|
+
export function registerDoctorCommand(program) {
|
|
322
|
+
program
|
|
323
|
+
.command("doctor")
|
|
324
|
+
.description("Check CLI health: config, auth, network, output contracts")
|
|
325
|
+
.option("--online", "Verify auth credentials against API (default: offline check)")
|
|
326
|
+
.option("--deep", "Run output contract self-tests")
|
|
327
|
+
.option("--format <format>", "Output format: human, json", "human")
|
|
328
|
+
.action(async (options) => {
|
|
329
|
+
try {
|
|
330
|
+
await handleDoctor(options);
|
|
331
|
+
}
|
|
332
|
+
catch (error) {
|
|
333
|
+
// Doctor must NEVER crash -- fallback error reporting
|
|
334
|
+
process.stderr.write(`Error: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
335
|
+
process.exit(ExitCode.ERROR);
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Download command for VertaaUX CLI.
|
|
3
|
+
*
|
|
4
|
+
* Syncs audit results from VertaaUX cloud to local.
|
|
5
|
+
* Enables pulling results from CI runs or shared audits.
|
|
6
|
+
*/
|
|
7
|
+
import { Command } from "commander";
|
|
8
|
+
/**
|
|
9
|
+
* Register the download command with the Commander program.
|
|
10
|
+
*/
|
|
11
|
+
export declare function registerDownloadCommand(program: Command): void;
|
|
12
|
+
//# sourceMappingURL=download.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"download.d.ts","sourceRoot":"","sources":["../../src/commands/download.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsNpC;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAkB9D"}
|