@vibecheckai/cli 3.2.0 → 3.2.2
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/bin/runners/lib/agent-firewall/change-packet/builder.js +214 -0
- package/bin/runners/lib/agent-firewall/change-packet/schema.json +228 -0
- package/bin/runners/lib/agent-firewall/change-packet/store.js +200 -0
- package/bin/runners/lib/agent-firewall/claims/claim-types.js +21 -0
- package/bin/runners/lib/agent-firewall/claims/extractor.js +214 -0
- package/bin/runners/lib/agent-firewall/claims/patterns.js +24 -0
- package/bin/runners/lib/agent-firewall/evidence/auth-evidence.js +88 -0
- package/bin/runners/lib/agent-firewall/evidence/contract-evidence.js +75 -0
- package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +118 -0
- package/bin/runners/lib/agent-firewall/evidence/resolver.js +102 -0
- package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +142 -0
- package/bin/runners/lib/agent-firewall/evidence/side-effect-evidence.js +145 -0
- package/bin/runners/lib/agent-firewall/fs-hook/daemon.js +19 -0
- package/bin/runners/lib/agent-firewall/fs-hook/installer.js +87 -0
- package/bin/runners/lib/agent-firewall/fs-hook/watcher.js +184 -0
- package/bin/runners/lib/agent-firewall/git-hook/pre-commit.js +163 -0
- package/bin/runners/lib/agent-firewall/ide-extension/cursor.js +107 -0
- package/bin/runners/lib/agent-firewall/ide-extension/vscode.js +68 -0
- package/bin/runners/lib/agent-firewall/ide-extension/windsurf.js +66 -0
- package/bin/runners/lib/agent-firewall/interceptor/base.js +304 -0
- package/bin/runners/lib/agent-firewall/interceptor/cursor.js +35 -0
- package/bin/runners/lib/agent-firewall/interceptor/vscode.js +35 -0
- package/bin/runners/lib/agent-firewall/interceptor/windsurf.js +34 -0
- package/bin/runners/lib/agent-firewall/policy/default-policy.json +84 -0
- package/bin/runners/lib/agent-firewall/policy/engine.js +72 -0
- package/bin/runners/lib/agent-firewall/policy/loader.js +143 -0
- package/bin/runners/lib/agent-firewall/policy/rules/auth-drift.js +50 -0
- package/bin/runners/lib/agent-firewall/policy/rules/contract-drift.js +50 -0
- package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +61 -0
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-env.js +50 -0
- package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +50 -0
- package/bin/runners/lib/agent-firewall/policy/rules/scope.js +93 -0
- package/bin/runners/lib/agent-firewall/policy/rules/unsafe-side-effect.js +57 -0
- package/bin/runners/lib/agent-firewall/policy/schema.json +183 -0
- package/bin/runners/lib/agent-firewall/policy/verdict.js +54 -0
- package/bin/runners/lib/agent-firewall/truthpack/index.js +67 -0
- package/bin/runners/lib/agent-firewall/truthpack/loader.js +116 -0
- package/bin/runners/lib/agent-firewall/unblock/planner.js +337 -0
- package/bin/runners/lib/analysis-core.js +198 -180
- package/bin/runners/lib/analyzers.js +1119 -536
- package/bin/runners/lib/cli-output.js +236 -210
- package/bin/runners/lib/detectors-v2.js +547 -785
- package/bin/runners/lib/fingerprint.js +377 -0
- package/bin/runners/lib/route-truth.js +1167 -322
- package/bin/runners/lib/scan-output.js +144 -738
- package/bin/runners/lib/ship-output-enterprise.js +239 -0
- package/bin/runners/lib/terminal-ui.js +188 -770
- package/bin/runners/lib/truth.js +1004 -321
- package/bin/runners/lib/unified-output.js +162 -158
- package/bin/runners/runAgent.js +161 -0
- package/bin/runners/runFirewall.js +134 -0
- package/bin/runners/runFirewallHook.js +56 -0
- package/bin/runners/runScan.js +113 -10
- package/bin/runners/runShip.js +7 -8
- package/bin/runners/runTruth.js +89 -0
- package/mcp-server/agent-firewall-interceptor.js +164 -0
- package/mcp-server/index.js +347 -313
- package/mcp-server/truth-context.js +131 -90
- package/mcp-server/truth-firewall-tools.js +1412 -1045
- package/package.json +1 -1
|
@@ -1,189 +1,193 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Unified Output System
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* This is what makes vibecheck feel "enterprise-grade".
|
|
2
|
+
* Unified Output System - World Class
|
|
3
|
+
* * The central nervous system for Vibecheck's CLI output.
|
|
4
|
+
* Delivers consistent, high-fidelity visualization and strict exit code compliance.
|
|
6
5
|
*/
|
|
7
6
|
|
|
8
|
-
|
|
9
|
-
let EXIT_CODES, formatVerdictOutput;
|
|
7
|
+
"use strict";
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
//
|
|
15
|
-
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
const os = require('os');
|
|
11
|
+
|
|
12
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
13
|
+
// CONFIGURATION & CONSTANTS
|
|
14
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
15
|
+
|
|
16
|
+
const EXIT_CODES = {
|
|
16
17
|
SHIP: 0, // Ready to ship, no blockers
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
MISCONFIG: 2, // Configuration error = block
|
|
22
|
-
INTERNAL: 2, // Internal error = block (safe default)
|
|
18
|
+
WARN: 1, // Warnings found (allowed via flag)
|
|
19
|
+
BLOCK: 2, // Blockers found (hard stop)
|
|
20
|
+
INTERNAL: 2, // Internal error (hard stop)
|
|
21
|
+
MISCONFIG: 2, // Configuration error (hard stop)
|
|
23
22
|
};
|
|
24
23
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
verdict.verdict === 'FAIL' ? `${c.red}✗${c.reset}` :
|
|
45
|
-
`${c.yellow}⚠${c.reset}`;
|
|
46
|
-
|
|
47
|
-
let output = `\n${icon} ${c.bold}${verdict.verdict}${c.reset}\n`;
|
|
48
|
-
|
|
49
|
-
if (verdict.summary) {
|
|
50
|
-
output += `\n${c.dim}Summary:${c.reset}\n`;
|
|
51
|
-
output += ` Blockers: ${verdict.summary.blockers || 0}\n`;
|
|
52
|
-
output += ` Warnings: ${verdict.summary.warnings || 0}\n`;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (verdict.findings && verdict.findings.length > 0) {
|
|
56
|
-
output += `\n${c.dim}Findings:${c.reset}\n`;
|
|
57
|
-
for (const finding of verdict.findings.slice(0, 10)) {
|
|
58
|
-
const sevColor = finding.severity === 'critical' ? c.red :
|
|
59
|
-
finding.severity === 'high' ? c.red :
|
|
60
|
-
finding.severity === 'medium' ? c.yellow : c.dim;
|
|
61
|
-
output += ` ${sevColor}[${finding.severity?.toUpperCase() || 'INFO'}]${c.reset} ${finding.title || finding.message || 'Unknown issue'}\n`;
|
|
62
|
-
if (finding.file) {
|
|
63
|
-
output += ` ${c.cyan}${finding.file}${finding.line ? `:${finding.line}` : ''}${c.reset}\n`;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
if (verdict.findings.length > 10) {
|
|
67
|
-
output += ` ${c.dim}... and ${verdict.findings.length - 10} more${c.reset}\n`;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return output;
|
|
72
|
-
};
|
|
24
|
+
const WIDTH = 76;
|
|
25
|
+
|
|
26
|
+
const BOX = {
|
|
27
|
+
tl: '╔', tr: '╗', bl: '╚', br: '╝', h: '═', v: '║',
|
|
28
|
+
trT: '╠', tlT: '╣',
|
|
29
|
+
// Light (Inner)
|
|
30
|
+
ltl: '┌', ltr: '┐', lbl: '└', lbr: '┘', lh: '─', lv: '│',
|
|
31
|
+
lt: '┬', lb: '┴', lx: '┼', ltrT: '├', ltlT: '┤'
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
35
|
+
// VISUAL RENDERING ENGINE
|
|
36
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
37
|
+
|
|
38
|
+
function padCenter(str, width) {
|
|
39
|
+
const visibleLen = str.replace(/\u001b\[\d+m/g, '').length;
|
|
40
|
+
const padding = Math.max(0, width - visibleLen);
|
|
41
|
+
const left = Math.floor(padding / 2);
|
|
42
|
+
return ' '.repeat(left) + str + ' '.repeat(padding - left);
|
|
73
43
|
}
|
|
74
44
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const { verbose = false, json = false } = options;
|
|
80
|
-
|
|
81
|
-
if (json) {
|
|
82
|
-
return JSON.stringify(result, null, 2);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return formatVerdictOutput(result.verdict, { verbose, json });
|
|
45
|
+
function padRight(str, len) {
|
|
46
|
+
const visibleLen = str.length;
|
|
47
|
+
const truncated = visibleLen > len ? str.substring(0, len - 3) + '...' : str;
|
|
48
|
+
return truncated + ' '.repeat(Math.max(0, len - truncated.length));
|
|
86
49
|
}
|
|
87
50
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
*/
|
|
92
|
-
function getExitCode(verdict) {
|
|
93
|
-
if (!verdict) return EXIT_CODES.BLOCK; // Safe default
|
|
94
|
-
|
|
95
|
-
switch (verdict.verdict) {
|
|
96
|
-
case 'SHIP':
|
|
97
|
-
case 'PASS':
|
|
98
|
-
return EXIT_CODES.SHIP; // 0
|
|
99
|
-
case 'WARN':
|
|
100
|
-
return EXIT_CODES.WARN; // 1
|
|
101
|
-
case 'BLOCK':
|
|
102
|
-
case 'FAIL':
|
|
103
|
-
return EXIT_CODES.BLOCK; // 2
|
|
104
|
-
case 'ERROR':
|
|
105
|
-
case 'MISCONFIG':
|
|
106
|
-
return EXIT_CODES.BLOCK; // 2 (safe default)
|
|
107
|
-
default:
|
|
108
|
-
return EXIT_CODES.BLOCK; // 2 (safe default)
|
|
109
|
-
}
|
|
51
|
+
function renderProgressBar(score, width = 20) {
|
|
52
|
+
const filled = Math.round((score / 100) * width);
|
|
53
|
+
return chalk.green('█'.repeat(filled)) + chalk.gray('░'.repeat(width - filled));
|
|
110
54
|
}
|
|
111
55
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
56
|
+
// --- The Core Formatter ---
|
|
57
|
+
|
|
58
|
+
function renderVerdict(verdictData, options = {}) {
|
|
59
|
+
const {
|
|
60
|
+
score = 0,
|
|
61
|
+
findings = [],
|
|
62
|
+
duration = 0,
|
|
63
|
+
scannedFiles = 0
|
|
64
|
+
} = verdictData;
|
|
65
|
+
|
|
66
|
+
const lines = [];
|
|
67
|
+
const hasIssues = findings.length > 0;
|
|
117
68
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
69
|
+
// 1. FRAME HEADER
|
|
70
|
+
lines.push(chalk.gray(BOX.tl + BOX.h.repeat(WIDTH - 2) + BOX.tr));
|
|
71
|
+
lines.push(chalk.gray(BOX.v) + ' '.repeat(WIDTH - 2) + chalk.gray(BOX.v));
|
|
72
|
+
|
|
73
|
+
// 2. CLEAN HEADER (No ASCII Logo)
|
|
74
|
+
const titleColor = hasIssues ? chalk.red.bold : chalk.cyan.bold;
|
|
75
|
+
const subTitle = hasIssues ? 'INTEGRITY SCAN • ISSUES DETECTED' : 'INTEGRITY SCAN • CLEAN';
|
|
121
76
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
77
|
+
// Add some vertical padding for elegance
|
|
78
|
+
lines.push(chalk.gray(BOX.v) + ' '.repeat(WIDTH - 2) + chalk.gray(BOX.v));
|
|
79
|
+
lines.push(chalk.gray(BOX.v) + padCenter(titleColor(subTitle), WIDTH - 2) + chalk.gray(BOX.v));
|
|
80
|
+
lines.push(chalk.gray(BOX.v) + ' '.repeat(WIDTH - 2) + chalk.gray(BOX.v));
|
|
81
|
+
|
|
82
|
+
// 3. TELEMETRY
|
|
83
|
+
lines.push(chalk.gray(BOX.trT + BOX.h.repeat(WIDTH - 2) + BOX.tlT));
|
|
84
|
+
const heapMB = Math.round(process.memoryUsage().heapUsed / 1024 / 1024);
|
|
85
|
+
const stats = `📡 TELEMETRY │ ⏱ ${duration}ms │ 📂 ${scannedFiles || '?'} Files │ 📦 ${heapMB}MB`;
|
|
86
|
+
lines.push(chalk.gray(BOX.v) + padCenter(stats, WIDTH - 2) + chalk.gray(BOX.v));
|
|
87
|
+
lines.push(chalk.gray(BOX.trT + BOX.h.repeat(WIDTH - 2) + BOX.tlT));
|
|
88
|
+
|
|
89
|
+
if (!hasIssues) {
|
|
90
|
+
// CLEAN STATE
|
|
91
|
+
lines.push(chalk.gray(BOX.v) + ' '.repeat(WIDTH - 2) + chalk.gray(BOX.v));
|
|
92
|
+
lines.push(chalk.gray(BOX.v) + padCenter(chalk.green('✅ NO STATIC ISSUES FOUND'), WIDTH - 2) + chalk.gray(BOX.v));
|
|
93
|
+
lines.push(chalk.gray(BOX.v) + ' '.repeat(WIDTH - 2) + chalk.gray(BOX.v));
|
|
94
|
+
} else {
|
|
95
|
+
// ISSUES STATE
|
|
96
|
+
lines.push(chalk.gray(BOX.v) + ' '.repeat(WIDTH - 2) + chalk.gray(BOX.v));
|
|
97
|
+
const scoreBar = renderProgressBar(score, 20);
|
|
98
|
+
lines.push(chalk.gray(BOX.v) + padCenter(`HEALTH SCORE [${scoreBar}] ${score} / 100`, WIDTH + 18) + chalk.gray(BOX.v));
|
|
99
|
+
lines.push(chalk.gray(BOX.v) + ' '.repeat(WIDTH - 2) + chalk.gray(BOX.v));
|
|
100
|
+
|
|
101
|
+
// TABLE HEADER
|
|
102
|
+
lines.push(chalk.gray(BOX.v) + padCenter('DETECTED VULNERABILITIES', WIDTH - 2) + chalk.gray(BOX.v));
|
|
103
|
+
|
|
104
|
+
// Hardcoded column widths for perfect alignment
|
|
105
|
+
const C1=10, C2=13, C3=41;
|
|
106
|
+
|
|
107
|
+
const tTop = ` ${BOX.ltl}${BOX.lh.repeat(C1)}${BOX.lt}${BOX.lh.repeat(C2)}${BOX.lt}${BOX.lh.repeat(C3)}${BOX.ltr} `;
|
|
108
|
+
const tMid = ` ${BOX.ltrT}${BOX.lh.repeat(C1)}${BOX.lx}${BOX.lh.repeat(C2)}${BOX.lx}${BOX.lh.repeat(C3)}${BOX.ltlT} `;
|
|
109
|
+
const tBot = ` ${BOX.lbl}${BOX.lh.repeat(C1)}${BOX.lb}${BOX.lh.repeat(C2)}${BOX.lb}${BOX.lh.repeat(C3)}${BOX.lbr} `;
|
|
110
|
+
|
|
111
|
+
lines.push(chalk.gray(BOX.v) + chalk.gray(tTop) + chalk.gray(BOX.v));
|
|
112
|
+
const header = ` ${BOX.lv}${padRight(' SEVERITY', C1)}${BOX.lv}${padRight(' TYPE', C2)}${BOX.lv}${padRight(' FINDING', C3)}${BOX.lv} `;
|
|
113
|
+
lines.push(chalk.gray(BOX.v) + chalk.bold(header) + chalk.gray(BOX.v));
|
|
114
|
+
lines.push(chalk.gray(BOX.v) + chalk.gray(tMid) + chalk.gray(BOX.v));
|
|
115
|
+
|
|
116
|
+
// ROWS
|
|
117
|
+
findings.slice(0, 5).forEach(f => {
|
|
118
|
+
const sev = (f.severity === 'critical' || f.severity === 'BLOCK') ? chalk.red('🛑 CRIT ') : chalk.yellow('🟡 WARN ');
|
|
119
|
+
const row = ` ${chalk.gray(BOX.lv)}${sev}${chalk.gray(BOX.lv)}${padRight(' '+(f.category||'General'), C2)}${chalk.gray(BOX.lv)}${padRight(' '+(f.message||f.title), C3)}${chalk.gray(BOX.lv)} `;
|
|
120
|
+
lines.push(chalk.gray(BOX.v) + row + chalk.gray(BOX.v));
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
lines.push(chalk.gray(BOX.v) + chalk.gray(tBot) + chalk.gray(BOX.v));
|
|
124
|
+
lines.push(chalk.gray(BOX.v) + ' '.repeat(WIDTH - 2) + chalk.gray(BOX.v));
|
|
125
|
+
|
|
126
|
+
// UPSELL (REDACTED STYLE)
|
|
127
|
+
lines.push(chalk.gray(BOX.trT + BOX.h.repeat(WIDTH - 2) + BOX.tlT));
|
|
128
|
+
const lockTitle = chalk.white.bold('🔒 DEEP SCAN ANALYSIS: ') + chalk.gray('UNAVAILABLE');
|
|
129
|
+
lines.push(chalk.gray(BOX.v) + padCenter(lockTitle, WIDTH + 9) + chalk.gray(BOX.v));
|
|
130
|
+
lines.push(chalk.gray(BOX.v) + padCenter(chalk.gray('─'.repeat(60)), WIDTH + 9) + chalk.gray(BOX.v));
|
|
131
|
+
|
|
132
|
+
// Redacted lines
|
|
133
|
+
['Runtime API Schema Validation', 'Live Auth Boundary Verification', 'Env Config Resolution'].forEach(txt => {
|
|
134
|
+
const line = chalk.dim(`░░ [REDACTED] 🔒 ${txt}`);
|
|
135
|
+
lines.push(chalk.gray(BOX.v) + padCenter(line, WIDTH + 9) + chalk.gray(BOX.v));
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
lines.push(chalk.gray(BOX.v) + ' '.repeat(WIDTH - 2) + chalk.gray(BOX.v));
|
|
139
|
+
lines.push(chalk.gray(BOX.v) + padCenter('Run ' + chalk.cyan('vibecheck upgrade pro') + ' to unlock.', WIDTH + 9) + chalk.gray(BOX.v));
|
|
142
140
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
};
|
|
141
|
+
|
|
142
|
+
// BOTTOM FRAME
|
|
143
|
+
lines.push(chalk.gray(BOX.v) + ' '.repeat(WIDTH - 2) + chalk.gray(BOX.v));
|
|
144
|
+
lines.push(chalk.gray(BOX.bl + BOX.h.repeat(WIDTH - 2) + BOX.br));
|
|
145
|
+
|
|
146
|
+
return lines.join('\n');
|
|
150
147
|
}
|
|
151
148
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
return 'INTERNAL';
|
|
149
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
150
|
+
// PUBLIC API
|
|
151
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
152
|
+
|
|
153
|
+
function formatScanOutput(result, options = {}) {
|
|
154
|
+
const { json = false } = options;
|
|
155
|
+
if (json) return JSON.stringify(result, null, 2);
|
|
156
|
+
return renderVerdict(result, options);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function getExitCode(verdict) {
|
|
160
|
+
if (!verdict) return EXIT_CODES.BLOCK;
|
|
161
|
+
const v = (typeof verdict === 'string' ? verdict : verdict.verdict).toUpperCase();
|
|
162
|
+
if (v === 'SHIP' || v === 'PASS') return EXIT_CODES.SHIP;
|
|
163
|
+
if (v === 'WARN') return EXIT_CODES.WARN;
|
|
164
|
+
return EXIT_CODES.BLOCK;
|
|
169
165
|
}
|
|
170
166
|
|
|
171
|
-
/**
|
|
172
|
-
* Print error with proper formatting
|
|
173
|
-
*/
|
|
174
167
|
function printError(error, context = '') {
|
|
175
|
-
const
|
|
168
|
+
const isConfig = error.message.toLowerCase().includes('config') || error.code === 'ENOENT';
|
|
169
|
+
const exitCode = isConfig ? EXIT_CODES.MISCONFIG : EXIT_CODES.INTERNAL;
|
|
176
170
|
|
|
177
|
-
|
|
178
|
-
|
|
171
|
+
const lines = [];
|
|
172
|
+
lines.push('');
|
|
173
|
+
lines.push(chalk.bgRed.white.bold(` 🛑 SYSTEM ALERT: ${context || 'INTERNAL_ERROR'} `));
|
|
174
|
+
lines.push(chalk.red('─'.repeat(WIDTH)));
|
|
175
|
+
lines.push(`${chalk.bold('Error:')} ${error.message}`);
|
|
176
|
+
if (error.code) lines.push(`${chalk.bold('Code:')} ${error.code}`);
|
|
177
|
+
lines.push('');
|
|
179
178
|
|
|
180
|
-
|
|
179
|
+
const fixCmd = isConfig ? 'vibecheck doctor --fix' : 'vibecheck doctor';
|
|
180
|
+
lines.push(chalk.gray('Recovery Step:'));
|
|
181
|
+
lines.push(`$ ${chalk.cyan(fixCmd)}`);
|
|
182
|
+
lines.push('');
|
|
183
|
+
|
|
184
|
+
console.error(lines.join('\n'));
|
|
185
|
+
return exitCode;
|
|
181
186
|
}
|
|
182
187
|
|
|
183
188
|
module.exports = {
|
|
184
189
|
formatScanOutput,
|
|
185
190
|
getExitCode,
|
|
186
|
-
handleError,
|
|
187
191
|
printError,
|
|
188
192
|
EXIT_CODES,
|
|
189
193
|
};
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Management Command Handler
|
|
3
|
+
*
|
|
4
|
+
* Install/uninstall hooks, status checks.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
"use strict";
|
|
8
|
+
|
|
9
|
+
const fs = require("fs");
|
|
10
|
+
const path = require("path");
|
|
11
|
+
const { loadPolicy } = require("./lib/agent-firewall/policy/loader");
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Run agent command
|
|
15
|
+
* @param {object} options - Command options
|
|
16
|
+
* @param {string} options.action - Action: 'install', 'status', 'lock', 'unlock'
|
|
17
|
+
* @param {string} options.projectRoot - Project root directory
|
|
18
|
+
*/
|
|
19
|
+
async function runAgent(options = {}) {
|
|
20
|
+
const projectRoot = options.projectRoot || process.cwd();
|
|
21
|
+
const action = options.action || "status";
|
|
22
|
+
|
|
23
|
+
switch (action) {
|
|
24
|
+
case "install":
|
|
25
|
+
return installHooks(projectRoot);
|
|
26
|
+
|
|
27
|
+
case "status":
|
|
28
|
+
return showAgentStatus(projectRoot);
|
|
29
|
+
|
|
30
|
+
case "lock":
|
|
31
|
+
return lockRepo(projectRoot);
|
|
32
|
+
|
|
33
|
+
case "unlock":
|
|
34
|
+
return unlockRepo(projectRoot);
|
|
35
|
+
|
|
36
|
+
default:
|
|
37
|
+
throw new Error(`Unknown action: ${action}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Install IDE hooks
|
|
43
|
+
*/
|
|
44
|
+
function installHooks(projectRoot) {
|
|
45
|
+
// Create .vibecheck directory if it doesn't exist
|
|
46
|
+
const vibecheckDir = path.join(projectRoot, ".vibecheck");
|
|
47
|
+
if (!fs.existsSync(vibecheckDir)) {
|
|
48
|
+
fs.mkdirSync(vibecheckDir, { recursive: true });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Create hook marker file
|
|
52
|
+
const hookFile = path.join(vibecheckDir, "agent-firewall-enabled");
|
|
53
|
+
fs.writeFileSync(hookFile, JSON.stringify({
|
|
54
|
+
enabled: true,
|
|
55
|
+
installedAt: new Date().toISOString()
|
|
56
|
+
}, null, 2));
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
success: true,
|
|
60
|
+
message: "Agent Firewall hooks installed. Firewall will intercept AI code changes."
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Show agent status
|
|
66
|
+
*/
|
|
67
|
+
function showAgentStatus(projectRoot) {
|
|
68
|
+
const hookFile = path.join(projectRoot, ".vibecheck", "agent-firewall-enabled");
|
|
69
|
+
const installed = fs.existsSync(hookFile);
|
|
70
|
+
|
|
71
|
+
let policy;
|
|
72
|
+
try {
|
|
73
|
+
policy = loadPolicy(projectRoot);
|
|
74
|
+
} catch {
|
|
75
|
+
policy = { mode: "observe", profile: "default" };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
installed,
|
|
80
|
+
mode: policy.mode,
|
|
81
|
+
profile: policy.profile,
|
|
82
|
+
locked: policy.mode === "enforce" && policy.profile === "repo-lock"
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Lock repo (enable repo-lock mode)
|
|
88
|
+
*/
|
|
89
|
+
function lockRepo(projectRoot) {
|
|
90
|
+
const { loadPolicy, savePolicy, getDefaultPolicy } = require("./lib/agent-firewall/policy/loader");
|
|
91
|
+
|
|
92
|
+
let policy;
|
|
93
|
+
try {
|
|
94
|
+
policy = loadPolicy(projectRoot);
|
|
95
|
+
} catch {
|
|
96
|
+
policy = getDefaultPolicy();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
policy.mode = "enforce";
|
|
100
|
+
policy.profile = "repo-lock";
|
|
101
|
+
|
|
102
|
+
// Enable all hard rules
|
|
103
|
+
if (!policy.rules) {
|
|
104
|
+
policy.rules = {};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const hardRules = [
|
|
108
|
+
"ghost_route",
|
|
109
|
+
"ghost_env",
|
|
110
|
+
"auth_drift",
|
|
111
|
+
"contract_drift",
|
|
112
|
+
"scope_explosion",
|
|
113
|
+
"unsafe_side_effect"
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
for (const rule of hardRules) {
|
|
117
|
+
if (!policy.rules[rule]) {
|
|
118
|
+
policy.rules[rule] = {};
|
|
119
|
+
}
|
|
120
|
+
policy.rules[rule].enabled = true;
|
|
121
|
+
policy.rules[rule].severity = "block";
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
savePolicy(projectRoot, policy);
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
success: true,
|
|
128
|
+
message: "Repo Lock Mode enabled. All hard rules enforced."
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Unlock repo (disable repo-lock mode)
|
|
134
|
+
*/
|
|
135
|
+
function unlockRepo(projectRoot) {
|
|
136
|
+
const { loadPolicy, savePolicy } = require("./lib/agent-firewall/policy/loader");
|
|
137
|
+
|
|
138
|
+
let policy;
|
|
139
|
+
try {
|
|
140
|
+
policy = loadPolicy(projectRoot);
|
|
141
|
+
} catch {
|
|
142
|
+
return {
|
|
143
|
+
success: false,
|
|
144
|
+
message: "No policy found to unlock"
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
policy.mode = "observe";
|
|
149
|
+
policy.profile = "balanced";
|
|
150
|
+
|
|
151
|
+
savePolicy(projectRoot, policy);
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
success: true,
|
|
155
|
+
message: "Repo Lock Mode disabled. Firewall in observe mode."
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
module.exports = {
|
|
160
|
+
runAgent
|
|
161
|
+
};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Firewall Command Handler
|
|
3
|
+
*
|
|
4
|
+
* Main firewall command handler.
|
|
5
|
+
* Enable/disable firewall, set mode, show status and statistics.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
const fs = require("fs");
|
|
11
|
+
const path = require("path");
|
|
12
|
+
const { loadPolicy, savePolicy, getDefaultPolicy } = require("./lib/agent-firewall/policy/loader");
|
|
13
|
+
const { getPacketStats, queryPackets } = require("./lib/agent-firewall/change-packet/store");
|
|
14
|
+
const { isTruthpackFresh } = require("./lib/agent-firewall/truthpack/loader");
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Run firewall command
|
|
18
|
+
* @param {object} options - Command options
|
|
19
|
+
* @param {string} options.mode - Set mode: 'observe' or 'enforce'
|
|
20
|
+
* @param {boolean} options.status - Show firewall status
|
|
21
|
+
* @param {boolean} options.stats - Show statistics
|
|
22
|
+
* @param {string} options.projectRoot - Project root directory
|
|
23
|
+
*/
|
|
24
|
+
async function runFirewall(options = {}) {
|
|
25
|
+
const projectRoot = options.projectRoot || process.cwd();
|
|
26
|
+
|
|
27
|
+
if (options.status) {
|
|
28
|
+
return showStatus(projectRoot);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (options.stats) {
|
|
32
|
+
return showStats(projectRoot);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (options.mode) {
|
|
36
|
+
return setMode(projectRoot, options.mode);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Default: show status
|
|
40
|
+
return showStatus(projectRoot);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Show firewall status
|
|
45
|
+
*/
|
|
46
|
+
function showStatus(projectRoot) {
|
|
47
|
+
try {
|
|
48
|
+
const policy = loadPolicy(projectRoot);
|
|
49
|
+
const truthpackFresh = isTruthpackFresh(projectRoot);
|
|
50
|
+
|
|
51
|
+
const output = {
|
|
52
|
+
mode: policy.mode || "observe",
|
|
53
|
+
profile: policy.profile || "default",
|
|
54
|
+
truthpackFresh,
|
|
55
|
+
rulesEnabled: Object.keys(policy.rules || {}).filter(
|
|
56
|
+
key => policy.rules[key]?.enabled !== false
|
|
57
|
+
).length
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return output;
|
|
61
|
+
} catch (error) {
|
|
62
|
+
return {
|
|
63
|
+
error: error.message,
|
|
64
|
+
mode: "unknown",
|
|
65
|
+
truthpackFresh: false
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Show firewall statistics
|
|
72
|
+
*/
|
|
73
|
+
function showStats(projectRoot) {
|
|
74
|
+
try {
|
|
75
|
+
const stats = getPacketStats(projectRoot);
|
|
76
|
+
const recentPackets = queryPackets(projectRoot, { limit: 10 });
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
total: stats.total,
|
|
80
|
+
byAgent: stats.byAgent,
|
|
81
|
+
byVerdict: stats.byVerdict,
|
|
82
|
+
byDate: stats.byDate,
|
|
83
|
+
recent: recentPackets.map(p => ({
|
|
84
|
+
id: p.id,
|
|
85
|
+
timestamp: p.timestamp,
|
|
86
|
+
agentId: p.agentId,
|
|
87
|
+
verdict: p.verdict?.decision,
|
|
88
|
+
files: p.files.length
|
|
89
|
+
}))
|
|
90
|
+
};
|
|
91
|
+
} catch (error) {
|
|
92
|
+
return {
|
|
93
|
+
error: error.message,
|
|
94
|
+
total: 0
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Set firewall mode
|
|
101
|
+
*/
|
|
102
|
+
function setMode(projectRoot, mode) {
|
|
103
|
+
if (mode !== "observe" && mode !== "enforce") {
|
|
104
|
+
throw new Error(`Invalid mode: ${mode}. Must be 'observe' or 'enforce'`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
let policy;
|
|
109
|
+
try {
|
|
110
|
+
policy = loadPolicy(projectRoot);
|
|
111
|
+
} catch {
|
|
112
|
+
// Policy doesn't exist, use default
|
|
113
|
+
policy = getDefaultPolicy();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
policy.mode = mode;
|
|
117
|
+
savePolicy(projectRoot, policy);
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
success: true,
|
|
121
|
+
mode,
|
|
122
|
+
message: `Firewall mode set to: ${mode}`
|
|
123
|
+
};
|
|
124
|
+
} catch (error) {
|
|
125
|
+
return {
|
|
126
|
+
success: false,
|
|
127
|
+
error: error.message
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = {
|
|
133
|
+
runFirewall
|
|
134
|
+
};
|