@maverick006/vibeguard 1.0.11 → 1.0.12
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/credentials.js +63 -0
- package/dist/formatter.js +363 -120
- package/dist/index.js +316 -106
- package/jest.config.js +5 -0
- package/package.json +7 -3
- package/src/credentials.ts +74 -0
- package/src/formatter.ts +444 -142
- package/src/index.ts +455 -211
- package/tests/auth.test.ts +96 -0
- package/tests/formatter.test.ts +666 -0
package/dist/index.js
CHANGED
|
@@ -9,78 +9,101 @@ const commander_1 = require("commander");
|
|
|
9
9
|
const chalk_1 = __importDefault(require("chalk"));
|
|
10
10
|
const ora_1 = __importDefault(require("ora"));
|
|
11
11
|
const ai_engine_1 = require("@maverick006/ai-engine");
|
|
12
|
-
const
|
|
12
|
+
const security_engine_1 = require("@maverick006/security-engine");
|
|
13
13
|
const fs_1 = require("fs");
|
|
14
14
|
const path_1 = require("path");
|
|
15
15
|
const formatter_1 = require("./formatter");
|
|
16
|
+
const credentials_1 = require("./credentials");
|
|
17
|
+
const readline_1 = __importDefault(require("readline"));
|
|
16
18
|
const program = new commander_1.Command();
|
|
17
19
|
program
|
|
18
20
|
.name('vibeguard')
|
|
19
|
-
.description('
|
|
20
|
-
.version('1.0.
|
|
21
|
+
.description('VIBEGUARD: Cloud + Security Posture CLI')
|
|
22
|
+
.version('1.0.12');
|
|
21
23
|
program
|
|
22
24
|
.command('scan [path]')
|
|
23
25
|
.description('Run a security scan on the current directory or target path')
|
|
24
26
|
.option('-d, --dir <path>', 'Directory to scan', process.cwd())
|
|
25
|
-
.option('--fix', '
|
|
27
|
+
.option('--fix', 'Generate optional advisory AI remediation suggestion')
|
|
26
28
|
.option('--ci', 'Run in non-interactive CI mode and exit with policy status code')
|
|
29
|
+
.option('--json', 'Output machine-readable JSON summary')
|
|
30
|
+
.option('-v, --verbose', 'Show detailed scanner telemetry, install hints, and diagnostics')
|
|
31
|
+
.option('--fail-on <severity>', 'Severity threshold to trigger non-zero exit in CI (critical, high, medium, low)', 'high')
|
|
32
|
+
.option('--sync', 'Sync scan telemetry and findings with authenticated VibeGuard Cloud account')
|
|
27
33
|
.action(async (targetPath, options) => {
|
|
28
34
|
const scanDir = (0, path_1.resolve)(targetPath || options.dir || process.cwd());
|
|
29
35
|
const startTime = Date.now();
|
|
30
36
|
let hasSystemError = false;
|
|
31
|
-
|
|
32
|
-
|
|
37
|
+
const isJson = Boolean(options.json);
|
|
38
|
+
const isSilent = options.ci || isJson;
|
|
33
39
|
const spinner = (0, ora_1.default)({
|
|
34
|
-
text: chalk_1.default.hex('#00E5FF')('
|
|
40
|
+
text: chalk_1.default.hex('#00E5FF')('Orchestrating security scanners across repository...'),
|
|
35
41
|
spinner: 'dots',
|
|
36
|
-
isSilent
|
|
42
|
+
isSilent
|
|
37
43
|
}).start();
|
|
38
44
|
let findings = [];
|
|
45
|
+
let scannerResults = [];
|
|
46
|
+
let coverageData = {
|
|
47
|
+
code: false,
|
|
48
|
+
dependencies: false,
|
|
49
|
+
secrets: false,
|
|
50
|
+
containers: false,
|
|
51
|
+
iac: false,
|
|
52
|
+
web: false,
|
|
53
|
+
cloud: false
|
|
54
|
+
};
|
|
39
55
|
try {
|
|
40
|
-
// Initialize the security orchestrator and scanners
|
|
41
56
|
const { Orchestrator } = require('@maverick006/security-engine');
|
|
42
57
|
const scanners = [];
|
|
43
|
-
try {
|
|
44
|
-
const { NpmAuditScanner } = require('@maverick006/scanner-npm-audit');
|
|
45
|
-
scanners.push(new NpmAuditScanner());
|
|
46
|
-
}
|
|
47
|
-
catch (e) { }
|
|
48
|
-
try {
|
|
49
|
-
const { TrivyScanner } = require('@maverick006/scanner-trivy');
|
|
50
|
-
scanners.push(new TrivyScanner());
|
|
51
|
-
}
|
|
52
|
-
catch (e) { }
|
|
53
58
|
try {
|
|
54
59
|
const { SemgrepScanner } = require('@maverick006/scanner-semgrep');
|
|
55
60
|
scanners.push(new SemgrepScanner());
|
|
56
61
|
}
|
|
57
|
-
catch
|
|
62
|
+
catch { }
|
|
58
63
|
try {
|
|
59
64
|
const { GitleaksScanner } = require('@maverick006/scanner-gitleaks');
|
|
60
65
|
scanners.push(new GitleaksScanner());
|
|
61
66
|
}
|
|
62
|
-
catch
|
|
67
|
+
catch { }
|
|
68
|
+
try {
|
|
69
|
+
const { NpmAuditScanner } = require('@maverick006/scanner-npm-audit');
|
|
70
|
+
scanners.push(new NpmAuditScanner());
|
|
71
|
+
}
|
|
72
|
+
catch { }
|
|
73
|
+
try {
|
|
74
|
+
const { TrivyScanner } = require('@maverick006/scanner-trivy');
|
|
75
|
+
scanners.push(new TrivyScanner());
|
|
76
|
+
}
|
|
77
|
+
catch { }
|
|
63
78
|
try {
|
|
64
79
|
const { CheckovScanner } = require('@maverick006/scanner-checkov');
|
|
65
80
|
scanners.push(new CheckovScanner());
|
|
66
81
|
}
|
|
67
|
-
catch
|
|
82
|
+
catch { }
|
|
68
83
|
try {
|
|
69
84
|
const { ZapScanner } = require('@maverick006/scanner-zap');
|
|
70
85
|
scanners.push(new ZapScanner());
|
|
71
86
|
}
|
|
72
|
-
catch
|
|
73
|
-
|
|
87
|
+
catch { }
|
|
88
|
+
try {
|
|
89
|
+
const { AwsCspmScanner } = require('@maverick006/scanner-aws-cspm');
|
|
90
|
+
scanners.push(new AwsCspmScanner());
|
|
91
|
+
}
|
|
92
|
+
catch { }
|
|
93
|
+
const orchestrator = new Orchestrator(scanners, { concurrencyLimit: 3 });
|
|
74
94
|
const scanResult = await orchestrator.runScan({
|
|
75
95
|
scanId: `scan-${Date.now()}`,
|
|
76
|
-
repositoryUrl: 'local',
|
|
77
96
|
repositoryPath: scanDir,
|
|
78
97
|
branch: 'main'
|
|
79
98
|
});
|
|
80
99
|
findings = scanResult.findings || [];
|
|
100
|
+
scannerResults = scanResult.scannerResults || [];
|
|
101
|
+
coverageData = scanResult.coverage || coverageData;
|
|
81
102
|
}
|
|
82
103
|
catch (err) {
|
|
83
|
-
|
|
104
|
+
if (!isJson) {
|
|
105
|
+
console.error(chalk_1.default.red('Orchestrator encountered an execution error:'), err.message || err);
|
|
106
|
+
}
|
|
84
107
|
hasSystemError = true;
|
|
85
108
|
}
|
|
86
109
|
spinner.stop();
|
|
@@ -88,108 +111,295 @@ program
|
|
|
88
111
|
const duration = elapsedMs > 60000
|
|
89
112
|
? `${Math.floor(elapsedMs / 60000)}m ${Math.floor((elapsedMs % 60000) / 1000)}s`
|
|
90
113
|
: `${(elapsedMs / 1000).toFixed(1)}s`;
|
|
114
|
+
// Ensure all findings follow the consistent VG-FIND-001 format
|
|
115
|
+
findings.forEach((f, idx) => {
|
|
116
|
+
if (!f.id || !f.id.startsWith('VG-FIND-')) {
|
|
117
|
+
f.id = `VG-FIND-${String(idx + 1).padStart(3, '0')}`;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
const deterministicScore = (0, security_engine_1.calculateScore)(findings, coverageData);
|
|
91
121
|
const stats = (0, formatter_1.calculateScore)(findings);
|
|
92
122
|
const gitInfo = (0, formatter_1.getGitInfo)(scanDir);
|
|
123
|
+
// Structure AI remediation (advisory only)
|
|
93
124
|
let remediationData = undefined;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const primaryFinding = findings[0];
|
|
99
|
-
let snippet = primaryFinding.codeSnippet || '';
|
|
100
|
-
if (primaryFinding.file && (0, fs_1.existsSync)((0, path_1.join)(scanDir, primaryFinding.file))) {
|
|
101
|
-
const content = (0, fs_1.readFileSync)((0, path_1.join)(scanDir, primaryFinding.file), 'utf-8');
|
|
102
|
-
const lines = content.split('\n');
|
|
103
|
-
const targetLine = primaryFinding.line || 1;
|
|
104
|
-
snippet = lines.slice(Math.max(0, targetLine - 3), targetLine + 3).join('\n');
|
|
105
|
-
}
|
|
106
|
-
const explanation = await explainer.explainFinding(primaryFinding, { codeContext: snippet });
|
|
125
|
+
if (findings.length > 0 && !options.ci && !isJson && (options.fix || true)) {
|
|
126
|
+
const primaryFinding = findings[0];
|
|
127
|
+
const hasAiKey = Boolean(process.env.NVIDIA_API_KEY);
|
|
128
|
+
if (!hasAiKey) {
|
|
107
129
|
remediationData = {
|
|
108
|
-
findingId: primaryFinding.id,
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
130
|
+
findingId: primaryFinding.id || 'VG-FIND-001',
|
|
131
|
+
severity: primaryFinding.severity,
|
|
132
|
+
issue: primaryFinding.title || 'Security finding identified',
|
|
133
|
+
hasConcretePatch: false,
|
|
134
|
+
status: 'UNAVAILABLE',
|
|
135
|
+
unavailableReason: 'NVIDIA_API_KEY not configured.'
|
|
114
136
|
};
|
|
115
137
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
138
|
+
else {
|
|
139
|
+
try {
|
|
140
|
+
const explainer = new ai_engine_1.ContextualExplainer();
|
|
141
|
+
let snippet = primaryFinding.codeSnippet || '';
|
|
142
|
+
if (primaryFinding.file && (0, fs_1.existsSync)((0, path_1.join)(scanDir, primaryFinding.file))) {
|
|
143
|
+
const content = (0, fs_1.readFileSync)((0, path_1.join)(scanDir, primaryFinding.file), 'utf-8');
|
|
144
|
+
const lines = content.split('\n');
|
|
145
|
+
const targetLine = primaryFinding.line || 1;
|
|
146
|
+
snippet = lines.slice(Math.max(0, targetLine - 3), targetLine + 3).join('\n');
|
|
147
|
+
}
|
|
148
|
+
const explanation = await explainer.explainFinding(primaryFinding, { codeContext: snippet });
|
|
149
|
+
const hasConcretePatch = Boolean(explanation.codeFix &&
|
|
150
|
+
explanation.codeFix.trim() &&
|
|
151
|
+
!explanation.codeFix.toLowerCase().includes('no patch'));
|
|
152
|
+
remediationData = {
|
|
153
|
+
findingId: primaryFinding.id || 'VG-FIND-001',
|
|
154
|
+
severity: primaryFinding.severity,
|
|
155
|
+
issue: explanation.summary,
|
|
156
|
+
impact: explanation.details,
|
|
157
|
+
recommendation: explanation.remediation,
|
|
158
|
+
suggestedFix: hasConcretePatch ? explanation.codeFix : undefined,
|
|
159
|
+
hasConcretePatch,
|
|
160
|
+
confidence: hasConcretePatch ? 90 : undefined,
|
|
161
|
+
status: hasConcretePatch ? 'AWAITING REVIEW' : 'GUIDANCE ONLY'
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
remediationData = {
|
|
166
|
+
findingId: primaryFinding.id || 'VG-FIND-001',
|
|
167
|
+
severity: primaryFinding.severity,
|
|
168
|
+
issue: primaryFinding.title,
|
|
169
|
+
hasConcretePatch: false,
|
|
170
|
+
status: 'UNAVAILABLE',
|
|
171
|
+
unavailableReason: 'AI service temporarily unavailable.'
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// Policy verification
|
|
177
|
+
const failThreshold = (options.failOn || 'high').toLowerCase();
|
|
178
|
+
const policyEvaluation = (0, formatter_1.evaluatePolicy)(failThreshold, deterministicScore.breakdown, findings.length);
|
|
179
|
+
const policyPassed = policyEvaluation.passed;
|
|
180
|
+
const thresholdBreached = !policyPassed;
|
|
181
|
+
// Cloud synchronization tracking
|
|
182
|
+
let syncStatus = 'SKIPPED';
|
|
183
|
+
if (options.sync) {
|
|
184
|
+
const creds = (0, credentials_1.loadCredentials)();
|
|
185
|
+
if (!creds || !creds.token) {
|
|
186
|
+
syncStatus = 'FAILED';
|
|
187
|
+
if (!isJson && !options.ci) {
|
|
188
|
+
console.log(chalk_1.default.red('\n✖ Authentication required for cloud sync.'));
|
|
189
|
+
console.log(chalk_1.default.yellow(" Run 'vibeguard login' to authenticate with VibeGuard Cloud, or omit --sync for 100% offline local scanning.\n"));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
try {
|
|
194
|
+
const API_URL = creds.apiUrl || process.env.VIBEGUARD_API_URL || 'https://vibeguard-eep3.onrender.com';
|
|
195
|
+
const repoName = gitInfo.name || 'Local Project';
|
|
196
|
+
const repoUrl = gitInfo.remoteUrl || gitInfo.name || 'local';
|
|
197
|
+
const response = await fetch(`${API_URL}/api/scans/upload`, {
|
|
198
|
+
method: 'POST',
|
|
199
|
+
headers: {
|
|
200
|
+
'Content-Type': 'application/json',
|
|
201
|
+
'Authorization': `Bearer ${creds.token}`
|
|
202
|
+
},
|
|
203
|
+
body: JSON.stringify({
|
|
204
|
+
repositoryName: repoName,
|
|
205
|
+
repositoryUrl: repoUrl,
|
|
206
|
+
numericScore: deterministicScore.score,
|
|
207
|
+
score: deterministicScore.grade,
|
|
208
|
+
findings
|
|
209
|
+
})
|
|
210
|
+
});
|
|
211
|
+
syncStatus = response.ok ? 'SYNCED' : 'FAILED';
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
syncStatus = 'FAILED';
|
|
119
215
|
}
|
|
120
216
|
}
|
|
121
217
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
218
|
+
// Transform scanner telemetry
|
|
219
|
+
const scannerTelemetryList = scannerResults.map(s => ({
|
|
220
|
+
scanner: s.scanner,
|
|
221
|
+
state: s.state,
|
|
222
|
+
durationMs: s.durationMs,
|
|
223
|
+
findingsCount: s.findings ? s.findings.length : 0,
|
|
224
|
+
reason: s.reason
|
|
225
|
+
}));
|
|
226
|
+
const activeDomainsCount = Object.values(coverageData).filter(Boolean).length;
|
|
227
|
+
const postureStatus = activeDomainsCount === 7 ? 'COMPLETE' : 'PARTIAL';
|
|
228
|
+
// 1. JSON Mode (Directive 17: Pure, machine-readable JSON)
|
|
229
|
+
if (isJson) {
|
|
230
|
+
const jsonOutput = (0, formatter_1.generateJsonOutput)({
|
|
231
|
+
deterministicScore,
|
|
125
232
|
findings,
|
|
126
|
-
|
|
233
|
+
coverageData,
|
|
234
|
+
activeDomainsCount,
|
|
235
|
+
postureStatus,
|
|
236
|
+
scanners: scannerTelemetryList,
|
|
127
237
|
gitInfo,
|
|
128
|
-
|
|
129
|
-
|
|
238
|
+
policyPassed,
|
|
239
|
+
failThreshold,
|
|
240
|
+
durationMs: elapsedMs
|
|
130
241
|
});
|
|
242
|
+
console.log(JSON.stringify(jsonOutput, null, 2));
|
|
243
|
+
process.exit(thresholdBreached ? 1 : (hasSystemError ? 2 : 0));
|
|
244
|
+
return;
|
|
131
245
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
// Sync with Web Dashboard
|
|
142
|
-
const syncSpinner = (0, ora_1.default)({
|
|
143
|
-
text: chalk_1.default.dim('Syncing results to VibeGuard Dashboard...'),
|
|
144
|
-
spinner: 'dots',
|
|
145
|
-
isSilent: options.ci
|
|
146
|
-
}).start();
|
|
147
|
-
try {
|
|
148
|
-
const API_URL = process.env.VIBEGUARD_API_URL || 'https://vibeguard-eep3.onrender.com';
|
|
149
|
-
const API_KEY = process.env.VIBEGUARD_API_KEY || 'dev-api-key-123';
|
|
150
|
-
const repoName = gitInfo.name || 'Local Project';
|
|
151
|
-
const repoUrl = gitInfo.remoteUrl || gitInfo.name || 'local';
|
|
152
|
-
const response = await fetch(`${API_URL}/api/scans/upload`, {
|
|
153
|
-
method: 'POST',
|
|
154
|
-
headers: {
|
|
155
|
-
'Content-Type': 'application/json',
|
|
156
|
-
'Authorization': `Bearer ${API_KEY}`
|
|
157
|
-
},
|
|
158
|
-
body: JSON.stringify({
|
|
159
|
-
repositoryName: repoName,
|
|
160
|
-
repositoryUrl: repoUrl,
|
|
161
|
-
numericScore: stats.score,
|
|
162
|
-
score: stats.riskLevel,
|
|
163
|
-
findings: findings
|
|
164
|
-
})
|
|
246
|
+
// 2. CI Mode (Directive 18: Minimal, clean, automation-friendly)
|
|
247
|
+
if (options.ci) {
|
|
248
|
+
const ciLines = (0, formatter_1.renderCIOutput)({
|
|
249
|
+
deterministicScore,
|
|
250
|
+
findings,
|
|
251
|
+
policyPassed,
|
|
252
|
+
failThreshold,
|
|
253
|
+
scanners: scannerTelemetryList,
|
|
254
|
+
verbose: Boolean(options.verbose)
|
|
165
255
|
});
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
syncSpinner.succeed(chalk_1.default.green(`Results synced to dashboard: ${chalk_1.default.cyan.underline(dashboardUrl)}`));
|
|
169
|
-
}
|
|
170
|
-
else {
|
|
171
|
-
syncSpinner.fail(chalk_1.default.red(`Failed to sync results to dashboard (${response.status} ${response.statusText}).`));
|
|
256
|
+
for (const line of ciLines) {
|
|
257
|
+
console.log(line);
|
|
172
258
|
}
|
|
259
|
+
process.exit(thresholdBreached ? 1 : (hasSystemError ? 2 : 0));
|
|
260
|
+
return;
|
|
173
261
|
}
|
|
174
|
-
|
|
175
|
-
|
|
262
|
+
// 3. Verbose Mode Diagnostics (Directive 19)
|
|
263
|
+
if (options.verbose) {
|
|
264
|
+
console.log(chalk_1.default.cyan('▶ VERBOSE DIAGNOSTICS'));
|
|
265
|
+
for (const sr of scannerTelemetryList) {
|
|
266
|
+
let hint = '';
|
|
267
|
+
if (sr.state === 'NOT_INSTALLED') {
|
|
268
|
+
if (sr.scanner === 'Semgrep')
|
|
269
|
+
hint = ' (Install: pip install semgrep or brew install semgrep)';
|
|
270
|
+
else if (sr.scanner === 'Gitleaks')
|
|
271
|
+
hint = ' (Install: brew install gitleaks or download from GitHub)';
|
|
272
|
+
else if (sr.scanner === 'Trivy')
|
|
273
|
+
hint = ' (Install: brew install trivy or see aquasecurity.github.io)';
|
|
274
|
+
else if (sr.scanner === 'Checkov')
|
|
275
|
+
hint = ' (Install: pip install checkov)';
|
|
276
|
+
}
|
|
277
|
+
console.log(` • ${sr.scanner.padEnd(12)} -> ${sr.state} in ${sr.durationMs || 0}ms (${sr.findingsCount} findings)${hint}`);
|
|
278
|
+
}
|
|
279
|
+
console.log('');
|
|
176
280
|
}
|
|
177
|
-
//
|
|
281
|
+
// 4. Interactive Terminal Dashboard
|
|
282
|
+
(0, formatter_1.renderDashboard)({
|
|
283
|
+
findings,
|
|
284
|
+
stats,
|
|
285
|
+
deterministicScore,
|
|
286
|
+
coverage: coverageData,
|
|
287
|
+
gitInfo,
|
|
288
|
+
duration,
|
|
289
|
+
scanners: scannerTelemetryList,
|
|
290
|
+
remediation: remediationData,
|
|
291
|
+
syncStatus,
|
|
292
|
+
policyThreshold: failThreshold,
|
|
293
|
+
verbose: Boolean(options.verbose)
|
|
294
|
+
});
|
|
178
295
|
if (hasSystemError) {
|
|
179
296
|
process.exit(2);
|
|
180
297
|
}
|
|
181
|
-
|
|
182
|
-
const criticalOrHighCount = findings.filter(f => {
|
|
183
|
-
const s = (f.severity || '').toUpperCase();
|
|
184
|
-
return s === types_1.Severity.CRITICAL || s === types_1.Severity.HIGH;
|
|
185
|
-
}).length;
|
|
186
|
-
if (criticalOrHighCount > 0) {
|
|
187
|
-
if (options.ci)
|
|
188
|
-
console.error(chalk_1.default.red('Security Policy FAILED.'));
|
|
298
|
+
if (thresholdBreached) {
|
|
189
299
|
process.exit(1);
|
|
190
300
|
}
|
|
191
|
-
if (options.ci)
|
|
192
|
-
console.log(chalk_1.default.green('Security Policy PASSED.'));
|
|
193
301
|
process.exit(0);
|
|
194
302
|
});
|
|
303
|
+
program
|
|
304
|
+
.command('login')
|
|
305
|
+
.description('Authenticate CLI with VibeGuard Cloud')
|
|
306
|
+
.option('-e, --email <email>', 'Account email')
|
|
307
|
+
.option('-p, --password <password>', 'Account password')
|
|
308
|
+
.option('--api-url <url>', 'VibeGuard API URL', process.env.VIBEGUARD_API_URL || 'https://vibeguard-eep3.onrender.com')
|
|
309
|
+
.action(async (options) => {
|
|
310
|
+
let email = options.email;
|
|
311
|
+
let password = options.password;
|
|
312
|
+
const apiUrl = options.apiUrl || process.env.VIBEGUARD_API_URL || 'https://vibeguard-eep3.onrender.com';
|
|
313
|
+
// Interactive prompt if flags not passed and TTY is active
|
|
314
|
+
if ((!email || !password) && process.stdin.isTTY) {
|
|
315
|
+
const rl = readline_1.default.createInterface({
|
|
316
|
+
input: process.stdin,
|
|
317
|
+
output: process.stdout
|
|
318
|
+
});
|
|
319
|
+
const question = (query) => new Promise((res) => rl.question(query, res));
|
|
320
|
+
if (!email) {
|
|
321
|
+
email = await question(chalk_1.default.cyan('Enter VibeGuard Email: '));
|
|
322
|
+
}
|
|
323
|
+
if (!password) {
|
|
324
|
+
password = await question(chalk_1.default.cyan('Enter VibeGuard Password: '));
|
|
325
|
+
}
|
|
326
|
+
rl.close();
|
|
327
|
+
}
|
|
328
|
+
if (!email || !password) {
|
|
329
|
+
console.error(chalk_1.default.red('Error: Email and password are required. Use --email and --password flags.'));
|
|
330
|
+
process.exit(1);
|
|
331
|
+
}
|
|
332
|
+
const spinner = (0, ora_1.default)(chalk_1.default.cyan('Authenticating with VibeGuard Cloud...')).start();
|
|
333
|
+
try {
|
|
334
|
+
const response = await fetch(`${apiUrl}/api/auth/login`, {
|
|
335
|
+
method: 'POST',
|
|
336
|
+
headers: { 'Content-Type': 'application/json' },
|
|
337
|
+
body: JSON.stringify({ email: email.trim(), password })
|
|
338
|
+
});
|
|
339
|
+
const data = await response.json();
|
|
340
|
+
if (!response.ok) {
|
|
341
|
+
spinner.fail(chalk_1.default.red(`Authentication failed: ${data.error || 'Invalid credentials'}`));
|
|
342
|
+
process.exit(1);
|
|
343
|
+
}
|
|
344
|
+
(0, credentials_1.saveCredentials)({
|
|
345
|
+
token: data.token,
|
|
346
|
+
user: data.user,
|
|
347
|
+
apiUrl
|
|
348
|
+
});
|
|
349
|
+
spinner.succeed(chalk_1.default.green('Successfully authenticated with VibeGuard Cloud!'));
|
|
350
|
+
console.log('');
|
|
351
|
+
console.log(chalk_1.default.gray(' Account: ') + chalk_1.default.white.bold(data.user.email) + (data.user.name ? chalk_1.default.gray(` (${data.user.name})`) : ''));
|
|
352
|
+
console.log(chalk_1.default.gray(' API Host: ') + chalk_1.default.cyan(apiUrl));
|
|
353
|
+
console.log(chalk_1.default.gray(' Stored: ') + chalk_1.default.dim((0, credentials_1.getCredentialsPath)()));
|
|
354
|
+
console.log('');
|
|
355
|
+
console.log(chalk_1.default.white('Cloud sync is now enabled. Run scans with ') + chalk_1.default.cyan('vibeguard scan --sync') + chalk_1.default.white(' to stream telemetry.'));
|
|
356
|
+
console.log('');
|
|
357
|
+
}
|
|
358
|
+
catch (err) {
|
|
359
|
+
spinner.fail(chalk_1.default.red(`Connection error: Could not reach VibeGuard API at ${apiUrl}`));
|
|
360
|
+
console.error(chalk_1.default.dim(err.message || err));
|
|
361
|
+
process.exit(1);
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
program
|
|
365
|
+
.command('logout')
|
|
366
|
+
.description('Log out and remove local VibeGuard Cloud credentials')
|
|
367
|
+
.action(() => {
|
|
368
|
+
(0, credentials_1.clearCredentials)();
|
|
369
|
+
console.log(chalk_1.default.green('\n✓ Successfully logged out from VibeGuard Cloud.'));
|
|
370
|
+
console.log(chalk_1.default.gray(' Stored session cleared. Local scanning remains 100% operational.\n'));
|
|
371
|
+
});
|
|
372
|
+
program
|
|
373
|
+
.command('whoami')
|
|
374
|
+
.description('Display currently authenticated user and VibeGuard Cloud status')
|
|
375
|
+
.action(() => {
|
|
376
|
+
const creds = (0, credentials_1.loadCredentials)();
|
|
377
|
+
if (!creds || !creds.token) {
|
|
378
|
+
console.log(chalk_1.default.yellow('\n○ VibeGuard Cloud Status: NOT AUTHENTICATED'));
|
|
379
|
+
console.log(chalk_1.default.gray(" Run 'vibeguard login' to connect your CLI with VibeGuard Cloud.\n"));
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
console.log(chalk_1.default.green('\n● VibeGuard Cloud Status: AUTHENTICATED'));
|
|
383
|
+
console.log(chalk_1.default.gray(' User: ') + chalk_1.default.white.bold(creds.user.email) + (creds.user.name ? chalk_1.default.gray(` (${creds.user.name})`) : ''));
|
|
384
|
+
console.log(chalk_1.default.gray(' API URL: ') + chalk_1.default.cyan(creds.apiUrl));
|
|
385
|
+
console.log(chalk_1.default.gray(' Saved: ') + chalk_1.default.dim(creds.savedAt || 'Unknown'));
|
|
386
|
+
console.log('');
|
|
387
|
+
});
|
|
388
|
+
const authCmd = program.command('auth').description('Manage VibeGuard Cloud authentication');
|
|
389
|
+
authCmd
|
|
390
|
+
.command('status')
|
|
391
|
+
.description('Display current VibeGuard Cloud authentication status')
|
|
392
|
+
.action(async () => {
|
|
393
|
+
const creds = (0, credentials_1.loadCredentials)();
|
|
394
|
+
if (!creds || !creds.token) {
|
|
395
|
+
console.log(chalk_1.default.yellow('\n○ VibeGuard Cloud Status: NOT AUTHENTICATED'));
|
|
396
|
+
console.log(chalk_1.default.gray(" Run 'vibeguard login' to connect your CLI with VibeGuard Cloud.\n"));
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
console.log(chalk_1.default.green('\n● VibeGuard Cloud Status: AUTHENTICATED'));
|
|
400
|
+
console.log(chalk_1.default.gray(' User: ') + chalk_1.default.white.bold(creds.user.email) + (creds.user.name ? chalk_1.default.gray(` (${creds.user.name})`) : ''));
|
|
401
|
+
console.log(chalk_1.default.gray(' API URL: ') + chalk_1.default.cyan(creds.apiUrl));
|
|
402
|
+
console.log(chalk_1.default.gray(' Saved: ') + chalk_1.default.dim(creds.savedAt || 'Unknown'));
|
|
403
|
+
console.log('');
|
|
404
|
+
});
|
|
195
405
|
program.parse(process.argv);
|
package/jest.config.js
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@maverick006/vibeguard",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.12",
|
|
4
4
|
"description": "VibeGuard CLI",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"build": "tsc",
|
|
11
11
|
"dev": "ts-node src/index.ts",
|
|
12
|
-
"start": "node dist/index.js"
|
|
12
|
+
"start": "node dist/index.js",
|
|
13
|
+
"test": "jest"
|
|
13
14
|
},
|
|
14
15
|
"dependencies": {
|
|
15
16
|
"@maverick006/ai-engine": "*",
|
|
@@ -23,6 +24,9 @@
|
|
|
23
24
|
},
|
|
24
25
|
"devDependencies": {
|
|
25
26
|
"@types/node": "^20.0.0",
|
|
26
|
-
"typescript": "^5.0.0"
|
|
27
|
+
"typescript": "^5.0.0",
|
|
28
|
+
"jest": "^29.5.0",
|
|
29
|
+
"@types/jest": "^29.5.0",
|
|
30
|
+
"ts-jest": "^29.1.0"
|
|
27
31
|
}
|
|
28
32
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
export interface StoredCredentials {
|
|
6
|
+
token: string;
|
|
7
|
+
user: {
|
|
8
|
+
id: string;
|
|
9
|
+
email: string;
|
|
10
|
+
name?: string | null;
|
|
11
|
+
};
|
|
12
|
+
apiUrl: string;
|
|
13
|
+
savedAt: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function getCredentialsDir(): string {
|
|
17
|
+
return path.join(os.homedir(), '.vibeguard');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function getCredentialsPath(): string {
|
|
21
|
+
return path.join(getCredentialsDir(), 'credentials.json');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function saveCredentials(creds: {
|
|
25
|
+
token: string;
|
|
26
|
+
user: { id: string; email: string; name?: string | null };
|
|
27
|
+
apiUrl?: string;
|
|
28
|
+
}): void {
|
|
29
|
+
const dir = getCredentialsDir();
|
|
30
|
+
if (!fs.existsSync(dir)) {
|
|
31
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const credsPath = getCredentialsPath();
|
|
35
|
+
const payload: StoredCredentials = {
|
|
36
|
+
token: creds.token,
|
|
37
|
+
user: creds.user,
|
|
38
|
+
apiUrl: creds.apiUrl || process.env.VIBEGUARD_API_URL || 'https://vibeguard-eep3.onrender.com',
|
|
39
|
+
savedAt: new Date().toISOString()
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
fs.writeFileSync(credsPath, JSON.stringify(payload, null, 2), { encoding: 'utf-8', mode: 0o600 });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function loadCredentials(): StoredCredentials | null {
|
|
46
|
+
const credsPath = getCredentialsPath();
|
|
47
|
+
if (!fs.existsSync(credsPath)) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const raw = fs.readFileSync(credsPath, 'utf-8');
|
|
53
|
+
const parsed = JSON.parse(raw);
|
|
54
|
+
if (!parsed.token || !parsed.user?.email) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
return parsed;
|
|
58
|
+
} catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function clearCredentials(): boolean {
|
|
64
|
+
const credsPath = getCredentialsPath();
|
|
65
|
+
if (fs.existsSync(credsPath)) {
|
|
66
|
+
try {
|
|
67
|
+
fs.unlinkSync(credsPath);
|
|
68
|
+
return true;
|
|
69
|
+
} catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return true;
|
|
74
|
+
}
|