@maverick006/vibeguard 1.0.3 → 1.0.6
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/dist/formatter.js +68 -32
- package/package.json +1 -1
- package/src/formatter.ts +72 -33
package/dist/formatter.js
CHANGED
|
@@ -10,6 +10,29 @@ const chalk_1 = __importDefault(require("chalk"));
|
|
|
10
10
|
const child_process_1 = require("child_process");
|
|
11
11
|
const types_1 = require("@maverick006/types");
|
|
12
12
|
const path_1 = __importDefault(require("path"));
|
|
13
|
+
function stripAnsi(str) {
|
|
14
|
+
return str.replace(/\u001b\[[0-9;]*m/g, '');
|
|
15
|
+
}
|
|
16
|
+
function visibleWidth(str) {
|
|
17
|
+
const plain = stripAnsi(str);
|
|
18
|
+
let width = 0;
|
|
19
|
+
for (const char of plain) {
|
|
20
|
+
const code = char.codePointAt(0) || 0;
|
|
21
|
+
// Emojis and certain symbols take 2 terminal columns
|
|
22
|
+
if (code > 0x1F000 || (code >= 0x2600 && code <= 0x27BF)) {
|
|
23
|
+
width += 2;
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
width += 1;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return width;
|
|
30
|
+
}
|
|
31
|
+
function padVisible(str, targetWidth) {
|
|
32
|
+
const current = visibleWidth(str);
|
|
33
|
+
const diff = Math.max(0, targetWidth - current);
|
|
34
|
+
return str + ' '.repeat(diff);
|
|
35
|
+
}
|
|
13
36
|
function getGitInfo(cwd = process.cwd()) {
|
|
14
37
|
let name = path_1.default.basename(cwd);
|
|
15
38
|
let branch = 'main';
|
|
@@ -95,31 +118,43 @@ function renderDashboard(options) {
|
|
|
95
118
|
' \\ \\ / / ',
|
|
96
119
|
' `-------\' '
|
|
97
120
|
];
|
|
98
|
-
// Title ASCII Art
|
|
121
|
+
// Title ASCII Art (VIBEGUARD)
|
|
99
122
|
const title = [
|
|
100
|
-
'
|
|
101
|
-
'\\ \\
|
|
102
|
-
' \\
|
|
103
|
-
'
|
|
104
|
-
'
|
|
123
|
+
' __ __ ___ ____ _____ ____ _ _ _ ____ ____ ',
|
|
124
|
+
'\\ \\ / /|_ _| __ )| ____/ ___| | | | / \\ | _ \\| _ \\ ',
|
|
125
|
+
' \\ \\ / / | || _ \\| _|| | _| | | |/ _ \\| |_) | | | |',
|
|
126
|
+
' \\ V / | || |_) | |___| |_| |_| / ___ \\ _ <| |_| |',
|
|
127
|
+
' \\_/ |___|____/|_____|\\____|\\___/_/ \\_\\_| \\_\\____/'
|
|
105
128
|
];
|
|
106
129
|
const scoreColor = stats.score >= 85 ? green : stats.score >= 65 ? yellow : red;
|
|
107
130
|
const riskColor = stats.riskLevel === 'LOW RISK' ? green : stats.riskLevel === 'MEDIUM RISK' ? yellow : red;
|
|
108
131
|
console.log('\n');
|
|
109
132
|
// Print Top Row: Clock aligned right
|
|
110
|
-
console.log(' '.repeat(
|
|
111
|
-
//
|
|
112
|
-
const
|
|
113
|
-
`${cyan(shield[0])} ${cyan.bold(title[0])}
|
|
114
|
-
`${cyan(shield[1])} ${cyan.bold(title[1])}
|
|
115
|
-
`${cyan(shield[2])} ${cyan.bold(title[2])}
|
|
116
|
-
`${cyan(shield[3])} ${cyan.bold(title[3])}
|
|
117
|
-
`${cyan(shield[4])} ${cyan.bold(title[4])}
|
|
118
|
-
`${cyan(shield[5])} ${gray('AI-Powered DevSecOps Orchestrator')}
|
|
119
|
-
` ${cyan('Scanning. Analyzing. Protecting.')}
|
|
133
|
+
console.log(' '.repeat(58) + cyan.bold(timeStr));
|
|
134
|
+
// Top Left Box lines (ASCII)
|
|
135
|
+
const leftHeader = [
|
|
136
|
+
`${cyan(shield[0])} ${cyan.bold(title[0])}`,
|
|
137
|
+
`${cyan(shield[1])} ${cyan.bold(title[1])}`,
|
|
138
|
+
`${cyan(shield[2])} ${cyan.bold(title[2])}`,
|
|
139
|
+
`${cyan(shield[3])} ${cyan.bold(title[3])}`,
|
|
140
|
+
`${cyan(shield[4])} ${cyan.bold(title[4])}`,
|
|
141
|
+
`${cyan(shield[5])} ${gray('AI-Powered DevSecOps Orchestrator')}`,
|
|
142
|
+
` ${cyan('Scanning. Analyzing. Protecting.')}`
|
|
143
|
+
];
|
|
144
|
+
// Top Right Risk Box lines
|
|
145
|
+
const rightBox = [
|
|
146
|
+
`${darkBorder('┌───────────────────────────────────────┐')}`,
|
|
147
|
+
`${darkBorder('│')} ${cyan('OVERALL RISK SCORE')} ${darkBorder('│')}`,
|
|
148
|
+
`${darkBorder('│')} ${red('CRITICAL')} ${String(stats.critical).padStart(2, ' ')} ${darkBorder('│')}`,
|
|
149
|
+
`${darkBorder('│')} ${scoreColor.bold(String(stats.score))} ${gray('/100')} ${orange('HIGH')} ${String(stats.high).padStart(2, ' ')} ${darkBorder('│')}`,
|
|
150
|
+
`${darkBorder('│')} ${yellow('MEDIUM')} ${String(stats.medium).padStart(2, ' ')} ${darkBorder('│')}`,
|
|
151
|
+
`${darkBorder('│')} ${riskColor.bold(stats.riskLevel.padEnd(13, ' '))} ${cyan('LOW')} ${String(stats.low).padStart(2, ' ')} ${darkBorder('│')}`,
|
|
152
|
+
`${darkBorder('└───────────────────────────────────────┘')}`
|
|
120
153
|
];
|
|
121
|
-
for (
|
|
122
|
-
|
|
154
|
+
for (let i = 0; i < leftHeader.length; i++) {
|
|
155
|
+
const l = padVisible(leftHeader[i], 66);
|
|
156
|
+
const r = rightBox[i] || '';
|
|
157
|
+
console.log(`${l}${r}`);
|
|
123
158
|
}
|
|
124
159
|
console.log('');
|
|
125
160
|
// 3-Column / Multi-panel Grid
|
|
@@ -140,7 +175,7 @@ function renderDashboard(options) {
|
|
|
140
175
|
];
|
|
141
176
|
// Table of findings (top 10)
|
|
142
177
|
const topFindings = findings.slice(0, 10);
|
|
143
|
-
const tableHeader = `${dimGray('ID'
|
|
178
|
+
const tableHeader = `${dimGray(padVisible('ID', 12))} ${dimGray(padVisible('SEVERITY', 10))} ${dimGray(padVisible('TITLE', 28))} ${dimGray('FILE:LINE')}`;
|
|
144
179
|
const findingRows = [tableHeader];
|
|
145
180
|
if (topFindings.length === 0) {
|
|
146
181
|
findingRows.push(green(' ✓ No security vulnerabilities detected. Codebase is clean!'));
|
|
@@ -156,18 +191,21 @@ function renderDashboard(options) {
|
|
|
156
191
|
sevFormatted = orange.bold('HIGH ');
|
|
157
192
|
else if (sev === 'MEDIUM')
|
|
158
193
|
sevFormatted = yellow('MEDIUM ');
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
194
|
+
const rawTitle = f.title || 'Security Finding';
|
|
195
|
+
const titleTruncated = rawTitle.length > 25 ? rawTitle.slice(0, 23) + '..' : rawTitle;
|
|
196
|
+
const titleFormatted = padVisible(white(titleTruncated), 28);
|
|
162
197
|
const fileLine = `${f.file || 'unknown'}:${f.line || 1}`;
|
|
163
|
-
|
|
198
|
+
const idFormatted = padVisible(cyan(id), 12);
|
|
199
|
+
findingRows.push(`${idFormatted} ${sevFormatted} ${titleFormatted} ${dimGray(fileLine)}`);
|
|
164
200
|
}
|
|
165
201
|
}
|
|
166
|
-
// Print Section Headers
|
|
167
|
-
|
|
202
|
+
// Print Section Headers with ANSI-safe padding
|
|
203
|
+
const headerCol1 = padVisible(cyan('▶ SCANNER PIPELINE'), 36);
|
|
204
|
+
const headerCol2 = cyan('▶ TOP FINDINGS');
|
|
205
|
+
console.log(`\n${headerCol1} ${headerCol2}`);
|
|
168
206
|
const maxRows = Math.max(pipelineRows.length, findingRows.length);
|
|
169
207
|
for (let i = 0; i < maxRows; i++) {
|
|
170
|
-
const left = (pipelineRows[i] || ''
|
|
208
|
+
const left = padVisible(pipelineRows[i] || '', 36);
|
|
171
209
|
const right = findingRows[i] || '';
|
|
172
210
|
console.log(`${left} ${right}`);
|
|
173
211
|
}
|
|
@@ -191,10 +229,8 @@ function renderDashboard(options) {
|
|
|
191
229
|
styled = dimGray(d);
|
|
192
230
|
else
|
|
193
231
|
styled = white(d);
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
const padLen = Math.max(0, 58 - plain.length);
|
|
197
|
-
console.log(`${darkBorder('│')} ${styled}${' '.repeat(padLen)} ${darkBorder('│')}`);
|
|
232
|
+
const paddedLine = padVisible(styled, 58);
|
|
233
|
+
console.log(`${darkBorder('│')} ${paddedLine} ${darkBorder('│')}`);
|
|
198
234
|
}
|
|
199
235
|
console.log(darkBorder('└────────────────────────────────────────────────────────────┘'));
|
|
200
236
|
console.log(`\n${cyan('Confidence:')} ${green.bold(remediation.confidence + '%')}`);
|
|
@@ -202,9 +238,9 @@ function renderDashboard(options) {
|
|
|
202
238
|
// Summary Bar (bottom capsule)
|
|
203
239
|
console.log(`\n${cyan('▶ SUMMARY')}`);
|
|
204
240
|
const summaryText = `🛡️ Scan completed in ${duration} │ ${stats.total} findings │ 6 passed`;
|
|
205
|
-
const barTop = `┌${'─'.repeat(summaryText
|
|
241
|
+
const barTop = `┌${'─'.repeat(visibleWidth(summaryText) + 4)}┐`;
|
|
206
242
|
const barMid = `│ ${summaryText} │`;
|
|
207
|
-
const barBot = `└${'─'.repeat(summaryText
|
|
243
|
+
const barBot = `└${'─'.repeat(visibleWidth(summaryText) + 4)}┘`;
|
|
208
244
|
console.log(cyan(barTop));
|
|
209
245
|
console.log(cyan(barMid));
|
|
210
246
|
console.log(cyan(barBot));
|
package/package.json
CHANGED
package/src/formatter.ts
CHANGED
|
@@ -13,6 +13,31 @@ export interface ScanStats {
|
|
|
13
13
|
riskLevel: 'LOW RISK' | 'MEDIUM RISK' | 'HIGH RISK' | 'CRITICAL RISK';
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
function stripAnsi(str: string): string {
|
|
17
|
+
return str.replace(/\u001b\[[0-9;]*m/g, '');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function visibleWidth(str: string): number {
|
|
21
|
+
const plain = stripAnsi(str);
|
|
22
|
+
let width = 0;
|
|
23
|
+
for (const char of plain) {
|
|
24
|
+
const code = char.codePointAt(0) || 0;
|
|
25
|
+
// Emojis and certain symbols take 2 terminal columns
|
|
26
|
+
if (code > 0x1F000 || (code >= 0x2600 && code <= 0x27BF)) {
|
|
27
|
+
width += 2;
|
|
28
|
+
} else {
|
|
29
|
+
width += 1;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return width;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function padVisible(str: string, targetWidth: number): string {
|
|
36
|
+
const current = visibleWidth(str);
|
|
37
|
+
const diff = Math.max(0, targetWidth - current);
|
|
38
|
+
return str + ' '.repeat(diff);
|
|
39
|
+
}
|
|
40
|
+
|
|
16
41
|
export function getGitInfo(cwd: string = process.cwd()) {
|
|
17
42
|
let name = path.basename(cwd);
|
|
18
43
|
let branch = 'main';
|
|
@@ -112,13 +137,13 @@ export function renderDashboard(options: {
|
|
|
112
137
|
' `-------\' '
|
|
113
138
|
];
|
|
114
139
|
|
|
115
|
-
// Title ASCII Art
|
|
140
|
+
// Title ASCII Art (VIBEGUARD)
|
|
116
141
|
const title = [
|
|
117
|
-
'
|
|
118
|
-
'\\ \\
|
|
119
|
-
' \\
|
|
120
|
-
'
|
|
121
|
-
'
|
|
142
|
+
' __ __ ___ ____ _____ ____ _ _ _ ____ ____ ',
|
|
143
|
+
'\\ \\ / /|_ _| __ )| ____/ ___| | | | / \\ | _ \\| _ \\ ',
|
|
144
|
+
' \\ \\ / / | || _ \\| _|| | _| | | |/ _ \\| |_) | | | |',
|
|
145
|
+
' \\ V / | || |_) | |___| |_| |_| / ___ \\ _ <| |_| |',
|
|
146
|
+
' \\_/ |___|____/|_____|\\____|\\___/_/ \\_\\_| \\_\\____/'
|
|
122
147
|
];
|
|
123
148
|
|
|
124
149
|
const scoreColor = stats.score >= 85 ? green : stats.score >= 65 ? yellow : red;
|
|
@@ -127,21 +152,34 @@ export function renderDashboard(options: {
|
|
|
127
152
|
console.log('\n');
|
|
128
153
|
|
|
129
154
|
// Print Top Row: Clock aligned right
|
|
130
|
-
console.log(' '.repeat(
|
|
131
|
-
|
|
132
|
-
//
|
|
133
|
-
const
|
|
134
|
-
`${cyan(shield[0])} ${cyan.bold(title[0])}
|
|
135
|
-
`${cyan(shield[1])} ${cyan.bold(title[1])}
|
|
136
|
-
`${cyan(shield[2])} ${cyan.bold(title[2])}
|
|
137
|
-
`${cyan(shield[3])} ${cyan.bold(title[3])}
|
|
138
|
-
`${cyan(shield[4])} ${cyan.bold(title[4])}
|
|
139
|
-
`${cyan(shield[5])} ${gray('AI-Powered DevSecOps Orchestrator')}
|
|
140
|
-
` ${cyan('Scanning. Analyzing. Protecting.')}
|
|
155
|
+
console.log(' '.repeat(58) + cyan.bold(timeStr));
|
|
156
|
+
|
|
157
|
+
// Top Left Box lines (ASCII)
|
|
158
|
+
const leftHeader = [
|
|
159
|
+
`${cyan(shield[0])} ${cyan.bold(title[0])}`,
|
|
160
|
+
`${cyan(shield[1])} ${cyan.bold(title[1])}`,
|
|
161
|
+
`${cyan(shield[2])} ${cyan.bold(title[2])}`,
|
|
162
|
+
`${cyan(shield[3])} ${cyan.bold(title[3])}`,
|
|
163
|
+
`${cyan(shield[4])} ${cyan.bold(title[4])}`,
|
|
164
|
+
`${cyan(shield[5])} ${gray('AI-Powered DevSecOps Orchestrator')}`,
|
|
165
|
+
` ${cyan('Scanning. Analyzing. Protecting.')}`
|
|
166
|
+
];
|
|
167
|
+
|
|
168
|
+
// Top Right Risk Box lines
|
|
169
|
+
const rightBox = [
|
|
170
|
+
`${darkBorder('┌───────────────────────────────────────┐')}`,
|
|
171
|
+
`${darkBorder('│')} ${cyan('OVERALL RISK SCORE')} ${darkBorder('│')}`,
|
|
172
|
+
`${darkBorder('│')} ${red('CRITICAL')} ${String(stats.critical).padStart(2, ' ')} ${darkBorder('│')}`,
|
|
173
|
+
`${darkBorder('│')} ${scoreColor.bold(String(stats.score))} ${gray('/100')} ${orange('HIGH')} ${String(stats.high).padStart(2, ' ')} ${darkBorder('│')}`,
|
|
174
|
+
`${darkBorder('│')} ${yellow('MEDIUM')} ${String(stats.medium).padStart(2, ' ')} ${darkBorder('│')}`,
|
|
175
|
+
`${darkBorder('│')} ${riskColor.bold(stats.riskLevel.padEnd(13, ' '))} ${cyan('LOW')} ${String(stats.low).padStart(2, ' ')} ${darkBorder('│')}`,
|
|
176
|
+
`${darkBorder('└───────────────────────────────────────┘')}`
|
|
141
177
|
];
|
|
142
178
|
|
|
143
|
-
for (
|
|
144
|
-
|
|
179
|
+
for (let i = 0; i < leftHeader.length; i++) {
|
|
180
|
+
const l = padVisible(leftHeader[i], 66);
|
|
181
|
+
const r = rightBox[i] || '';
|
|
182
|
+
console.log(`${l}${r}`);
|
|
145
183
|
}
|
|
146
184
|
console.log('');
|
|
147
185
|
|
|
@@ -164,7 +202,7 @@ export function renderDashboard(options: {
|
|
|
164
202
|
|
|
165
203
|
// Table of findings (top 10)
|
|
166
204
|
const topFindings = findings.slice(0, 10);
|
|
167
|
-
const tableHeader = `${dimGray('ID'
|
|
205
|
+
const tableHeader = `${dimGray(padVisible('ID', 12))} ${dimGray(padVisible('SEVERITY', 10))} ${dimGray(padVisible('TITLE', 28))} ${dimGray('FILE:LINE')}`;
|
|
168
206
|
|
|
169
207
|
const findingRows: string[] = [tableHeader];
|
|
170
208
|
|
|
@@ -179,21 +217,24 @@ export function renderDashboard(options: {
|
|
|
179
217
|
else if (sev === 'HIGH') sevFormatted = orange.bold('HIGH ');
|
|
180
218
|
else if (sev === 'MEDIUM') sevFormatted = yellow('MEDIUM ');
|
|
181
219
|
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
220
|
+
const rawTitle = f.title || 'Security Finding';
|
|
221
|
+
const titleTruncated = rawTitle.length > 25 ? rawTitle.slice(0, 23) + '..' : rawTitle;
|
|
222
|
+
const titleFormatted = padVisible(white(titleTruncated), 28);
|
|
185
223
|
|
|
186
224
|
const fileLine = `${f.file || 'unknown'}:${f.line || 1}`;
|
|
187
|
-
|
|
225
|
+
const idFormatted = padVisible(cyan(id), 12);
|
|
226
|
+
findingRows.push(`${idFormatted} ${sevFormatted} ${titleFormatted} ${dimGray(fileLine)}`);
|
|
188
227
|
}
|
|
189
228
|
}
|
|
190
229
|
|
|
191
|
-
// Print Section Headers
|
|
192
|
-
|
|
230
|
+
// Print Section Headers with ANSI-safe padding
|
|
231
|
+
const headerCol1 = padVisible(cyan('▶ SCANNER PIPELINE'), 36);
|
|
232
|
+
const headerCol2 = cyan('▶ TOP FINDINGS');
|
|
233
|
+
console.log(`\n${headerCol1} ${headerCol2}`);
|
|
193
234
|
|
|
194
235
|
const maxRows = Math.max(pipelineRows.length, findingRows.length);
|
|
195
236
|
for (let i = 0; i < maxRows; i++) {
|
|
196
|
-
const left = (pipelineRows[i] || ''
|
|
237
|
+
const left = padVisible(pipelineRows[i] || '', 36);
|
|
197
238
|
const right = findingRows[i] || '';
|
|
198
239
|
console.log(`${left} ${right}`);
|
|
199
240
|
}
|
|
@@ -216,10 +257,8 @@ export function renderDashboard(options: {
|
|
|
216
257
|
else if (d.trim().startsWith('#')) styled = dimGray(d);
|
|
217
258
|
else styled = white(d);
|
|
218
259
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const padLen = Math.max(0, 58 - plain.length);
|
|
222
|
-
console.log(`${darkBorder('│')} ${styled}${' '.repeat(padLen)} ${darkBorder('│')}`);
|
|
260
|
+
const paddedLine = padVisible(styled, 58);
|
|
261
|
+
console.log(`${darkBorder('│')} ${paddedLine} ${darkBorder('│')}`);
|
|
223
262
|
}
|
|
224
263
|
console.log(darkBorder('└────────────────────────────────────────────────────────────┘'));
|
|
225
264
|
|
|
@@ -229,9 +268,9 @@ export function renderDashboard(options: {
|
|
|
229
268
|
// Summary Bar (bottom capsule)
|
|
230
269
|
console.log(`\n${cyan('▶ SUMMARY')}`);
|
|
231
270
|
const summaryText = `🛡️ Scan completed in ${duration} │ ${stats.total} findings │ 6 passed`;
|
|
232
|
-
const barTop = `┌${'─'.repeat(summaryText
|
|
271
|
+
const barTop = `┌${'─'.repeat(visibleWidth(summaryText) + 4)}┐`;
|
|
233
272
|
const barMid = `│ ${summaryText} │`;
|
|
234
|
-
const barBot = `└${'─'.repeat(summaryText
|
|
273
|
+
const barBot = `└${'─'.repeat(visibleWidth(summaryText) + 4)}┘`;
|
|
235
274
|
console.log(cyan(barTop));
|
|
236
275
|
console.log(cyan(barMid));
|
|
237
276
|
console.log(cyan(barBot));
|