@maverick006/vibeguard 1.0.4 → 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 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
- ' \\ V / | || _ \\| _|| | _| | | |/ _ \\| |_) | | | |',
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(65) + cyan.bold(timeStr));
111
- // Header Row with Logo and Overall Risk Score Box
112
- const headerLines = [
113
- `${cyan(shield[0])} ${cyan.bold(title[0])} ${darkBorder('┌───────────────────────────────────────┐')}`,
114
- `${cyan(shield[1])} ${cyan.bold(title[1])} ${darkBorder('│')} ${cyan('OVERALL RISK SCORE')} ${darkBorder('│')}`,
115
- `${cyan(shield[2])} ${cyan.bold(title[2])} ${darkBorder('│')} ${red('CRITICAL')} ${String(stats.critical).padStart(2, ' ')} ${darkBorder('│')}`,
116
- `${cyan(shield[3])} ${cyan.bold(title[3])} ${darkBorder('│')} ${scoreColor.bold(String(stats.score))} ${gray('/100')} ${orange('HIGH')} ${String(stats.high).padStart(2, ' ')} ${darkBorder('│')}`,
117
- `${cyan(shield[4])} ${cyan.bold(title[4])} ${darkBorder('│')} ${yellow('MEDIUM')} ${String(stats.medium).padStart(2, ' ')} ${darkBorder('│')}`,
118
- `${cyan(shield[5])} ${gray('AI-Powered DevSecOps Orchestrator')} ${darkBorder('│')} ${riskColor.bold(stats.riskLevel.padEnd(13, ' '))} ${cyan('LOW')} ${String(stats.low).padStart(2, ' ')} ${darkBorder('│')}`,
119
- ` ${cyan('Scanning. Analyzing. Protecting.')} ${darkBorder('└───────────────────────────────────────┘')}`
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 (const line of headerLines) {
122
- console.log(line);
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'.padEnd(12))} ${dimGray('SEVERITY'.padEnd(10))} ${dimGray('TITLE'.padEnd(28))} ${dimGray('FILE:LINE')}`;
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 title = (f.title || 'Security Finding').length > 26
160
- ? (f.title || '').slice(0, 24) + '..'
161
- : (f.title || '').padEnd(27, ' ');
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
- findingRows.push(`${cyan(id.padEnd(12))} ${sevFormatted} ${white(title)} ${dimGray(fileLine)}`);
198
+ const idFormatted = padVisible(cyan(id), 12);
199
+ findingRows.push(`${idFormatted} ${sevFormatted} ${titleFormatted} ${dimGray(fileLine)}`);
164
200
  }
165
201
  }
166
- // Print Section Headers
167
- console.log(`\n${cyan('▶ SCANNER PIPELINE')} ${cyan('▶ TOP FINDINGS')}`);
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] || '').padEnd(38, ' ');
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
- // Calculate visible length without ANSI codes for proper border padding
195
- const plain = d.replace(/\u001b\[[0-9;]*m/g, '');
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.length - 2)}┐`;
241
+ const barTop = `┌${'─'.repeat(visibleWidth(summaryText) + 4)}┐`;
206
242
  const barMid = `│ ${summaryText} │`;
207
- const barBot = `└${'─'.repeat(summaryText.length - 2)}┘`;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@maverick006/vibeguard",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "VibeGuard CLI",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
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
- ' \\ V / | || _ \\| _|| | _| | | |/ _ \\| |_) | | | |',
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(65) + cyan.bold(timeStr));
131
-
132
- // Header Row with Logo and Overall Risk Score Box
133
- const headerLines = [
134
- `${cyan(shield[0])} ${cyan.bold(title[0])} ${darkBorder('┌───────────────────────────────────────┐')}`,
135
- `${cyan(shield[1])} ${cyan.bold(title[1])} ${darkBorder('│')} ${cyan('OVERALL RISK SCORE')} ${darkBorder('│')}`,
136
- `${cyan(shield[2])} ${cyan.bold(title[2])} ${darkBorder('│')} ${red('CRITICAL')} ${String(stats.critical).padStart(2, ' ')} ${darkBorder('│')}`,
137
- `${cyan(shield[3])} ${cyan.bold(title[3])} ${darkBorder('│')} ${scoreColor.bold(String(stats.score))} ${gray('/100')} ${orange('HIGH')} ${String(stats.high).padStart(2, ' ')} ${darkBorder('│')}`,
138
- `${cyan(shield[4])} ${cyan.bold(title[4])} ${darkBorder('│')} ${yellow('MEDIUM')} ${String(stats.medium).padStart(2, ' ')} ${darkBorder('│')}`,
139
- `${cyan(shield[5])} ${gray('AI-Powered DevSecOps Orchestrator')} ${darkBorder('│')} ${riskColor.bold(stats.riskLevel.padEnd(13, ' '))} ${cyan('LOW')} ${String(stats.low).padStart(2, ' ')} ${darkBorder('│')}`,
140
- ` ${cyan('Scanning. Analyzing. Protecting.')} ${darkBorder('└───────────────────────────────────────┘')}`
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 (const line of headerLines) {
144
- console.log(line);
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'.padEnd(12))} ${dimGray('SEVERITY'.padEnd(10))} ${dimGray('TITLE'.padEnd(28))} ${dimGray('FILE:LINE')}`;
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 title = (f.title || 'Security Finding').length > 26
183
- ? (f.title || '').slice(0, 24) + '..'
184
- : (f.title || '').padEnd(27, ' ');
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
- findingRows.push(`${cyan(id.padEnd(12))} ${sevFormatted} ${white(title)} ${dimGray(fileLine)}`);
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
- console.log(`\n${cyan('▶ SCANNER PIPELINE')} ${cyan('▶ TOP FINDINGS')}`);
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] || '').padEnd(38, ' ');
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
- // Calculate visible length without ANSI codes for proper border padding
220
- const plain = d.replace(/\u001b\[[0-9;]*m/g, '');
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.length - 2)}┐`;
271
+ const barTop = `┌${'─'.repeat(visibleWidth(summaryText) + 4)}┐`;
233
272
  const barMid = `│ ${summaryText} │`;
234
- const barBot = `└${'─'.repeat(summaryText.length - 2)}┘`;
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));