@vibecheckai/cli 3.0.2 → 3.0.3
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/package.json +9 -1
- package/bin/cli-hygiene.js +0 -241
- package/bin/guardrail.js +0 -834
- package/bin/runners/cli-utils.js +0 -1070
- package/bin/runners/context/ai-task-decomposer.js +0 -337
- package/bin/runners/context/analyzer.js +0 -462
- package/bin/runners/context/api-contracts.js +0 -427
- package/bin/runners/context/context-diff.js +0 -342
- package/bin/runners/context/context-pruner.js +0 -291
- package/bin/runners/context/dependency-graph.js +0 -414
- package/bin/runners/context/generators/claude.js +0 -107
- package/bin/runners/context/generators/codex.js +0 -108
- package/bin/runners/context/generators/copilot.js +0 -119
- package/bin/runners/context/generators/cursor.js +0 -514
- package/bin/runners/context/generators/mcp.js +0 -151
- package/bin/runners/context/generators/windsurf.js +0 -180
- package/bin/runners/context/git-context.js +0 -302
- package/bin/runners/context/index.js +0 -1042
- package/bin/runners/context/insights.js +0 -173
- package/bin/runners/context/mcp-server/generate-rules.js +0 -337
- package/bin/runners/context/mcp-server/index.js +0 -1176
- package/bin/runners/context/mcp-server/package.json +0 -24
- package/bin/runners/context/memory.js +0 -200
- package/bin/runners/context/monorepo.js +0 -215
- package/bin/runners/context/multi-repo-federation.js +0 -404
- package/bin/runners/context/patterns.js +0 -253
- package/bin/runners/context/proof-context.js +0 -972
- package/bin/runners/context/security-scanner.js +0 -303
- package/bin/runners/context/semantic-search.js +0 -350
- package/bin/runners/context/shared.js +0 -264
- package/bin/runners/context/team-conventions.js +0 -310
- package/bin/runners/lib/ai-bridge.js +0 -416
- package/bin/runners/lib/analysis-core.js +0 -271
- package/bin/runners/lib/analyzers.js +0 -541
- package/bin/runners/lib/audit-bridge.js +0 -391
- package/bin/runners/lib/auth-truth.js +0 -193
- package/bin/runners/lib/auth.js +0 -215
- package/bin/runners/lib/backup.js +0 -62
- package/bin/runners/lib/billing.js +0 -107
- package/bin/runners/lib/claims.js +0 -118
- package/bin/runners/lib/cli-ui.js +0 -540
- package/bin/runners/lib/compliance-bridge-new.js +0 -0
- package/bin/runners/lib/compliance-bridge.js +0 -165
- package/bin/runners/lib/contracts/auth-contract.js +0 -194
- package/bin/runners/lib/contracts/env-contract.js +0 -178
- package/bin/runners/lib/contracts/external-contract.js +0 -198
- package/bin/runners/lib/contracts/guard.js +0 -168
- package/bin/runners/lib/contracts/index.js +0 -89
- package/bin/runners/lib/contracts/plan-validator.js +0 -311
- package/bin/runners/lib/contracts/route-contract.js +0 -192
- package/bin/runners/lib/detect.js +0 -89
- package/bin/runners/lib/doctor/autofix.js +0 -254
- package/bin/runners/lib/doctor/index.js +0 -37
- package/bin/runners/lib/doctor/modules/dependencies.js +0 -325
- package/bin/runners/lib/doctor/modules/index.js +0 -46
- package/bin/runners/lib/doctor/modules/network.js +0 -250
- package/bin/runners/lib/doctor/modules/project.js +0 -312
- package/bin/runners/lib/doctor/modules/runtime.js +0 -224
- package/bin/runners/lib/doctor/modules/security.js +0 -348
- package/bin/runners/lib/doctor/modules/system.js +0 -213
- package/bin/runners/lib/doctor/modules/vibecheck.js +0 -394
- package/bin/runners/lib/doctor/reporter.js +0 -262
- package/bin/runners/lib/doctor/service.js +0 -262
- package/bin/runners/lib/doctor/types.js +0 -113
- package/bin/runners/lib/doctor/ui.js +0 -263
- package/bin/runners/lib/doctor-enhanced.js +0 -233
- package/bin/runners/lib/doctor-v2.js +0 -608
- package/bin/runners/lib/enforcement.js +0 -72
package/bin/runners/cli-utils.js
DELETED
|
@@ -1,1070 +0,0 @@
|
|
|
1
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
2
|
-
// vibecheck CLI UTILS - Professional Terminal Styling
|
|
3
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
4
|
-
|
|
5
|
-
// ANSI color codes
|
|
6
|
-
const c = {
|
|
7
|
-
reset: '\x1b[0m',
|
|
8
|
-
bold: '\x1b[1m',
|
|
9
|
-
dim: '\x1b[2m',
|
|
10
|
-
italic: '\x1b[3m',
|
|
11
|
-
underline: '\x1b[4m',
|
|
12
|
-
// Colors
|
|
13
|
-
red: '\x1b[31m',
|
|
14
|
-
green: '\x1b[32m',
|
|
15
|
-
yellow: '\x1b[33m',
|
|
16
|
-
blue: '\x1b[34m',
|
|
17
|
-
magenta: '\x1b[35m',
|
|
18
|
-
cyan: '\x1b[36m',
|
|
19
|
-
white: '\x1b[37m',
|
|
20
|
-
gray: '\x1b[90m',
|
|
21
|
-
// Bright colors
|
|
22
|
-
brightRed: '\x1b[91m',
|
|
23
|
-
brightGreen: '\x1b[92m',
|
|
24
|
-
brightYellow: '\x1b[93m',
|
|
25
|
-
brightBlue: '\x1b[94m',
|
|
26
|
-
brightMagenta: '\x1b[95m',
|
|
27
|
-
brightCyan: '\x1b[96m',
|
|
28
|
-
brightWhite: '\x1b[97m',
|
|
29
|
-
// Background
|
|
30
|
-
bgRed: '\x1b[41m',
|
|
31
|
-
bgGreen: '\x1b[42m',
|
|
32
|
-
bgYellow: '\x1b[43m',
|
|
33
|
-
bgBlue: '\x1b[44m',
|
|
34
|
-
bgMagenta: '\x1b[45m',
|
|
35
|
-
bgCyan: '\x1b[46m',
|
|
36
|
-
bgGray: '\x1b[100m',
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
// ASCII Art Banner
|
|
40
|
-
const BANNER = `
|
|
41
|
-
${c.brightCyan} ██████╗ ██╗ ██╗ █████╗ ██████╗ ██████╗ ██████╗ █████╗ ██╗██╗
|
|
42
|
-
██╔════╝ ██║ ██║██╔══██╗██╔══██╗██╔══██╗██╔══██╗██╔══██╗██║██║
|
|
43
|
-
██║ ███╗██║ ██║███████║██████╔╝██║ ██║██████╔╝███████║██║██║
|
|
44
|
-
██║ ██║██║ ██║██╔══██║██╔══██╗██║ ██║██╔══██╗██╔══██║██║██║
|
|
45
|
-
╚██████╔╝╚██████╔╝██║ ██║██║ ██║██████╔╝██║ ██║██║ ██║██║███████╗
|
|
46
|
-
╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚══════╝${c.reset}
|
|
47
|
-
${c.dim} AI-Native Code Security Platform${c.reset}
|
|
48
|
-
`;
|
|
49
|
-
|
|
50
|
-
// Compact Banner for subcommands
|
|
51
|
-
const COMPACT_BANNER = `${c.cyan}╔══════════════════════════════════════════════════════════════╗${c.reset}
|
|
52
|
-
${c.cyan}║${c.reset} ${c.bold}vibecheck CLI${c.reset} ${c.dim}— Professional Code Security & Analysis${c.reset} ${c.cyan}║${c.reset}
|
|
53
|
-
${c.cyan}╚══════════════════════════════════════════════════════════════╝${c.reset}
|
|
54
|
-
`;
|
|
55
|
-
|
|
56
|
-
// Box drawing utilities
|
|
57
|
-
const BOX_WIDTH = 61;
|
|
58
|
-
|
|
59
|
-
function box(title, color = c.cyan, width = BOX_WIDTH) {
|
|
60
|
-
const padding = Math.max(0, width - title.length - 2);
|
|
61
|
-
const leftPad = Math.floor(padding / 2);
|
|
62
|
-
const rightPad = padding - leftPad;
|
|
63
|
-
return (
|
|
64
|
-
`${color}┌${'─'.repeat(width)}┐${c.reset}\n` +
|
|
65
|
-
`${color}│${c.reset}${c.bold}${' '.repeat(leftPad)}${title}${' '.repeat(rightPad)}${c.reset}${color}│${c.reset}\n` +
|
|
66
|
-
`${color}└${'─'.repeat(width)}┘${c.reset}`
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function doubleBox(title, color = c.cyan, width = BOX_WIDTH) {
|
|
71
|
-
const padding = Math.max(0, width - title.length - 2);
|
|
72
|
-
const leftPad = Math.floor(padding / 2);
|
|
73
|
-
const rightPad = padding - leftPad;
|
|
74
|
-
return (
|
|
75
|
-
`${color}╔${'═'.repeat(width)}╗${c.reset}\n` +
|
|
76
|
-
`${color}║${c.reset}${c.bold}${' '.repeat(leftPad)}${title}${' '.repeat(rightPad)}${c.reset}${color}║${c.reset}\n` +
|
|
77
|
-
`${color}╚${'═'.repeat(width)}╝${c.reset}`
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Progress indicators
|
|
82
|
-
function spinner() {
|
|
83
|
-
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
84
|
-
let i = 0;
|
|
85
|
-
return () => frames[i++ % frames.length];
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function progressBar(current, total, width = 20, color = c.cyan) {
|
|
89
|
-
const filled = Math.round((current / total) * width);
|
|
90
|
-
const empty = width - filled;
|
|
91
|
-
return `${color}${'█'.repeat(filled)}${c.dim}${'░'.repeat(empty)}${c.reset}`;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Status indicators
|
|
95
|
-
function statusIcon(status) {
|
|
96
|
-
const icons = {
|
|
97
|
-
success: `${c.green}✓${c.reset}`,
|
|
98
|
-
error: `${c.red}✗${c.reset}`,
|
|
99
|
-
warning: `${c.yellow}⚠${c.reset}`,
|
|
100
|
-
info: `${c.blue}ℹ${c.reset}`,
|
|
101
|
-
loading: `${c.dim}○${c.reset}`,
|
|
102
|
-
pending: `${c.yellow}⏳${c.reset}`,
|
|
103
|
-
};
|
|
104
|
-
return icons[status] || '?';
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Severity bars for visual representation
|
|
108
|
-
function severityBar(count, color, max = 10) {
|
|
109
|
-
const filled = Math.min(count, max);
|
|
110
|
-
return `${color}${'█'.repeat(filled)}${c.dim}${'░'.repeat(max - filled)}${c.reset}`;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Text formatting
|
|
114
|
-
function formatNumber(num) {
|
|
115
|
-
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function formatBytes(bytes) {
|
|
119
|
-
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
120
|
-
if (bytes === 0) return '0 B';
|
|
121
|
-
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
122
|
-
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function formatDuration(ms) {
|
|
126
|
-
if (ms < 1000) return `${ms}ms`;
|
|
127
|
-
if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
|
|
128
|
-
return `${(ms / 60000).toFixed(1)}m`;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Header printing functions
|
|
132
|
-
function printBanner() {
|
|
133
|
-
console.log(BANNER);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function printCompactBanner() {
|
|
137
|
-
console.log(COMPACT_BANNER);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function printCommandHeader(command, description) {
|
|
141
|
-
console.log();
|
|
142
|
-
console.log(doubleBox(command.toUpperCase(), c.cyan));
|
|
143
|
-
console.log(`${c.dim} ${description}${c.reset}`);
|
|
144
|
-
console.log();
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function printSectionHeader(title) {
|
|
148
|
-
console.log();
|
|
149
|
-
console.log(`${c.cyan}┌─ ${c.bold}${title}${c.reset}${c.cyan} ──────────────────────────────────────────────┐${c.reset}`);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
function printSectionFooter() {
|
|
153
|
-
console.log(`${c.cyan}└─────────────────────────────────────────────────────────────┘${c.reset}`);
|
|
154
|
-
console.log();
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// List formatting
|
|
158
|
-
function printListItem(icon, text, indent = 0) {
|
|
159
|
-
const spaces = ' '.repeat(indent * 2);
|
|
160
|
-
console.log(`${spaces}${icon} ${text}`);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
function printBulletedList(items, bullet = '•') {
|
|
164
|
-
items.forEach(item => {
|
|
165
|
-
console.log(` ${c.dim}${bullet}${c.reset} ${item}`);
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Table formatting
|
|
170
|
-
function printTable(headers, rows) {
|
|
171
|
-
const colWidths = headers.map(h => h.length);
|
|
172
|
-
|
|
173
|
-
// Calculate column widths
|
|
174
|
-
rows.forEach(row => {
|
|
175
|
-
row.forEach((cell, i) => {
|
|
176
|
-
colWidths[i] = Math.max(colWidths[i], cell.length);
|
|
177
|
-
});
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
// Print header
|
|
181
|
-
const headerRow = headers.map((h, i) =>
|
|
182
|
-
`${c.bold}${h.padEnd(colWidths[i])}${c.reset}`
|
|
183
|
-
).join(' │ ');
|
|
184
|
-
const separator = colWidths.map(w => '─'.repeat(w)).join('─┼─');
|
|
185
|
-
|
|
186
|
-
console.log(`${c.cyan}┌${colWidths.map(w => '─'.repeat(w)).join('─┬─')}┐${c.reset}`);
|
|
187
|
-
console.log(`${c.cyan}│${c.reset} ${headerRow} ${c.cyan}│${c.reset}`);
|
|
188
|
-
console.log(`${c.cyan}├${separator}┤${c.reset}`);
|
|
189
|
-
|
|
190
|
-
// Print rows
|
|
191
|
-
rows.forEach(row => {
|
|
192
|
-
const rowStr = row.map((cell, i) =>
|
|
193
|
-
cell.padEnd(colWidths[i])
|
|
194
|
-
).join(' │ ');
|
|
195
|
-
console.log(`${c.cyan}│${c.reset} ${rowStr} ${c.cyan}│${c.reset}`);
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
console.log(`${c.cyan}└${colWidths.map(w => '─'.repeat(w)).join('─┴─')}┘${c.reset}`);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
// Highlighting
|
|
202
|
-
function highlight(text, color = c.yellow) {
|
|
203
|
-
return `${color}${text}${c.reset}`;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
function highlightCode(code) {
|
|
207
|
-
return `${c.cyan}${code}${c.reset}`;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function highlightPath(path) {
|
|
211
|
-
return `${c.blue}${path}${c.reset}`;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Error formatting
|
|
215
|
-
function printError(message, details = null) {
|
|
216
|
-
console.log();
|
|
217
|
-
console.log(`${c.bgRed}${c.white} ERROR ${c.reset}`);
|
|
218
|
-
console.log(`${c.red} ✗ ${message}${c.reset}`);
|
|
219
|
-
if (details) {
|
|
220
|
-
console.log(`${c.dim} ${details}${c.reset}`);
|
|
221
|
-
}
|
|
222
|
-
console.log();
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function printWarning(message) {
|
|
226
|
-
console.log(`${c.yellow} ⚠ ${message}${c.reset}`);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function printSuccess(message) {
|
|
230
|
-
console.log(`${c.green} ✓ ${message}${c.reset}`);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Animated loading
|
|
234
|
-
function printLoading(message, duration = 1000) {
|
|
235
|
-
const spin = spinner();
|
|
236
|
-
const interval = setInterval(() => {
|
|
237
|
-
process.stdout.write(`\r${c.dim}${spin()} ${message}...${c.reset}`);
|
|
238
|
-
}, 100);
|
|
239
|
-
|
|
240
|
-
setTimeout(() => {
|
|
241
|
-
clearInterval(interval);
|
|
242
|
-
process.stdout.write(`\r${c.green}✓${c.reset} ${message}\n`);
|
|
243
|
-
}, duration);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Summary cards
|
|
247
|
-
function printSummaryCard(title, value, unit = '', color = c.cyan) {
|
|
248
|
-
console.log(`${color}┌─ ${title} ──────────────┐${c.reset}`);
|
|
249
|
-
console.log(`${color}│${c.reset} ${c.bold}${value}${c.reset}${c.dim}${unit}${c.reset} ${color}│${c.reset}`);
|
|
250
|
-
console.log(`${color}└─────────────────────┘${c.reset}`);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// Gradient text effect (simulated with colors)
|
|
254
|
-
function gradientText(text, colors = [c.cyan, c.blue, c.cyan]) {
|
|
255
|
-
const chunkSize = Math.ceil(text.length / colors.length);
|
|
256
|
-
let result = '';
|
|
257
|
-
|
|
258
|
-
colors.forEach((color, i) => {
|
|
259
|
-
const start = i * chunkSize;
|
|
260
|
-
const end = start + chunkSize;
|
|
261
|
-
const chunk = text.slice(start, end);
|
|
262
|
-
result += color + chunk;
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
return result + c.reset;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Multi-bar progress container
|
|
269
|
-
function createMultiBar() {
|
|
270
|
-
// Simplified implementation
|
|
271
|
-
return {
|
|
272
|
-
create: (total, startValue, payload) => ({
|
|
273
|
-
update: (current, payload) => {
|
|
274
|
-
const percentage = Math.round((current / total) * 100);
|
|
275
|
-
const bar = '█'.repeat(Math.round(percentage / 5)) + '░'.repeat(20 - Math.round(percentage / 5));
|
|
276
|
-
process.stdout.write(`\r[${bar}] ${percentage}%`);
|
|
277
|
-
}
|
|
278
|
-
})
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Animated typing effect
|
|
283
|
-
async function typewriter(text, speed = 50) {
|
|
284
|
-
for (let i = 0; i < text.length; i++) {
|
|
285
|
-
process.stdout.write(text[i]);
|
|
286
|
-
await new Promise(resolve => setTimeout(resolve, speed));
|
|
287
|
-
}
|
|
288
|
-
console.log();
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Rainbow gradient text
|
|
292
|
-
function rainbowText(text) {
|
|
293
|
-
const colors = [c.red, c.yellow, c.green, c.cyan, c.blue, c.magenta];
|
|
294
|
-
let result = '';
|
|
295
|
-
for (let i = 0; i < text.length; i++) {
|
|
296
|
-
result += colors[i % colors.length] + text[i];
|
|
297
|
-
}
|
|
298
|
-
return result + c.reset;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// Terminal sparkle effect
|
|
302
|
-
function sparkle(text) {
|
|
303
|
-
const sparkles = ['✨', '⭐', '💫', '🌟'];
|
|
304
|
-
return `${sparkles[Math.floor(Math.random() * sparkles.length)]} ${text} ${sparkles[Math.floor(Math.random() * sparkles.length)]}`;
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// Create a beautiful box with multiple styles
|
|
308
|
-
function createBox(content, style = 'single') {
|
|
309
|
-
const styles = {
|
|
310
|
-
single: { topLeft: '┌', topRight: '┐', bottomLeft: '└', bottomRight: '┘', horizontal: '─', vertical: '│' },
|
|
311
|
-
double: { topLeft: '╔', topRight: '╗', bottomLeft: '╚', bottomRight: '╝', horizontal: '═', vertical: '║' },
|
|
312
|
-
round: { topLeft: '╭', topRight: '╮', bottomLeft: '╰', bottomRight: '╯', horizontal: '─', vertical: '│' },
|
|
313
|
-
bold: { topLeft: '┏', topRight: '┓', bottomLeft: '┗', bottomRight: '┛', horizontal: '━', vertical: '┃' },
|
|
314
|
-
};
|
|
315
|
-
|
|
316
|
-
const s = styles[style] || styles.single;
|
|
317
|
-
const lines = content.split('\n');
|
|
318
|
-
|
|
319
|
-
// Calculate width considering ANSI codes
|
|
320
|
-
let maxWidth = 0;
|
|
321
|
-
for (const line of lines) {
|
|
322
|
-
const stripped = stripAnsi(line);
|
|
323
|
-
maxWidth = Math.max(maxWidth, stripped.length);
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// Ensure minimum width
|
|
327
|
-
maxWidth = Math.max(maxWidth, 30);
|
|
328
|
-
|
|
329
|
-
let box = s.topLeft + s.horizontal.repeat(maxWidth + 4) + s.topRight + '\n';
|
|
330
|
-
|
|
331
|
-
for (const line of lines) {
|
|
332
|
-
const stripped = stripAnsi(line);
|
|
333
|
-
const padding = maxWidth - stripped.length;
|
|
334
|
-
box += s.vertical + ' ' + line + ' '.repeat(padding) + ' ' + s.vertical + '\n';
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
box += s.bottomLeft + s.horizontal.repeat(maxWidth + 4) + s.bottomRight;
|
|
338
|
-
|
|
339
|
-
return box;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// Strip ANSI codes for width calculation
|
|
343
|
-
function stripAnsi(str) {
|
|
344
|
-
return str.replace(/\x1b\[[0-9;]*m/g, '');
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// Interactive prompt
|
|
348
|
-
async function prompt(message) {
|
|
349
|
-
const readline = require('readline');
|
|
350
|
-
const rl = readline.createInterface({
|
|
351
|
-
input: process.stdin,
|
|
352
|
-
output: process.stdout
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
return new Promise((resolve) => {
|
|
356
|
-
rl.question(`${c.cyan}?${c.reset} ${message} `, (answer) => {
|
|
357
|
-
rl.close();
|
|
358
|
-
resolve(answer.trim());
|
|
359
|
-
});
|
|
360
|
-
});
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// List selector
|
|
364
|
-
async function selectList(items, message = 'Select an item:') {
|
|
365
|
-
console.log(`${c.cyan}?${c.reset} ${message}`);
|
|
366
|
-
items.forEach((item, idx) => {
|
|
367
|
-
console.log(` ${idx + 1}. ${item}`);
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
const answer = await prompt('Enter number:');
|
|
371
|
-
const index = parseInt(answer) - 1;
|
|
372
|
-
return items[index] || null;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// Animation frames
|
|
376
|
-
const animations = {
|
|
377
|
-
loading: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'],
|
|
378
|
-
success: ['✓', '✓', '✓'],
|
|
379
|
-
error: ['✗', '✗', '✗'],
|
|
380
|
-
dots: ['⠁', '⠂', '⠄', '⠂'],
|
|
381
|
-
pulse: ['●', '○', '●', '○'],
|
|
382
|
-
};
|
|
383
|
-
|
|
384
|
-
// Animated progress
|
|
385
|
-
function showProgress(current, total, message = 'Processing') {
|
|
386
|
-
const percentage = Math.round((current / total) * 100);
|
|
387
|
-
const barLength = 30;
|
|
388
|
-
const filled = Math.round((barLength * percentage) / 100);
|
|
389
|
-
const empty = barLength - filled;
|
|
390
|
-
|
|
391
|
-
const bar = c.green('█'.repeat(filled)) + c.gray('░'.repeat(empty));
|
|
392
|
-
const animation = animations.loading[current % animations.loading.length];
|
|
393
|
-
|
|
394
|
-
process.stdout.write(`\r${c.cyan}${animation}${c.reset} ${message} [${bar}] ${percentage}%`);
|
|
395
|
-
|
|
396
|
-
if (current === total) {
|
|
397
|
-
console.log();
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
// Heat map visualization
|
|
402
|
-
function drawHeatMap(data, labels) {
|
|
403
|
-
const heatColors = [
|
|
404
|
-
c.bgBlue, c.bgCyan, c.bgGreen, c.bgYellow, c.bgRed
|
|
405
|
-
];
|
|
406
|
-
|
|
407
|
-
const max = Math.max(...data);
|
|
408
|
-
const min = Math.min(...data);
|
|
409
|
-
const range = max - min || 1;
|
|
410
|
-
|
|
411
|
-
let output = '\n';
|
|
412
|
-
for (let i = 0; i < data.length; i++) {
|
|
413
|
-
const value = data[i];
|
|
414
|
-
const normalized = (value - min) / range;
|
|
415
|
-
const colorIndex = Math.floor(normalized * (heatColors.length - 1));
|
|
416
|
-
const color = heatColors[colorIndex];
|
|
417
|
-
|
|
418
|
-
const bar = ' '.repeat(Math.round(normalized * 20));
|
|
419
|
-
output += `${labels[i].padEnd(15)} ${color}${bar}${c.reset} ${value}\n`;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
return output;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// ASCII chart
|
|
426
|
-
function drawChart(data, width = 50, height = 10) {
|
|
427
|
-
const max = Math.max(...data);
|
|
428
|
-
const min = Math.min(...data);
|
|
429
|
-
const range = max - min || 1;
|
|
430
|
-
|
|
431
|
-
const chart = [];
|
|
432
|
-
|
|
433
|
-
for (let y = height; y >= 0; y--) {
|
|
434
|
-
let line = '';
|
|
435
|
-
const threshold = min + (range * y) / height;
|
|
436
|
-
|
|
437
|
-
for (let x = 0; x < data.length; x++) {
|
|
438
|
-
if (data[x] >= threshold) {
|
|
439
|
-
line += '█';
|
|
440
|
-
} else if (x > 0 && data[x - 1] >= threshold && data[x] < threshold) {
|
|
441
|
-
line += '▄';
|
|
442
|
-
} else if (x > 0 && data[x - 1] < threshold && data[x] >= threshold) {
|
|
443
|
-
line += '▀';
|
|
444
|
-
} else {
|
|
445
|
-
line += ' ';
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
const label = threshold.toFixed(0).padStart(8);
|
|
450
|
-
chart.push(`${c.dim}${label}${c.reset} │${c.cyan}${line}${c.reset}`);
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
chart.push(''.padStart(9) + '└' + '─'.repeat(data.length));
|
|
454
|
-
|
|
455
|
-
return '\n' + chart.join('\n') + '\n';
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
// Terminal column layout
|
|
459
|
-
function createColumns(columns, gap = 4) {
|
|
460
|
-
const terminalWidth = process.stdout.columns || 80;
|
|
461
|
-
const columnWidth = Math.floor((terminalWidth - gap * (columns.length - 1)) / columns.length);
|
|
462
|
-
|
|
463
|
-
const rows = Math.max(...columns.map(col => col.length));
|
|
464
|
-
let output = '';
|
|
465
|
-
|
|
466
|
-
for (let row = 0; row < rows; row++) {
|
|
467
|
-
const line = [];
|
|
468
|
-
for (let col = 0; col < columns.length; col++) {
|
|
469
|
-
const cell = columns[col][row] || '';
|
|
470
|
-
line.push(cell.padEnd(columnWidth));
|
|
471
|
-
}
|
|
472
|
-
output += line.join(' '.repeat(gap)) + '\n';
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
return output;
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// Interactive menu
|
|
479
|
-
async function showMenu(title, options) {
|
|
480
|
-
console.log(`\n${c.bold}${c.underline}${title}${c.reset}\n`);
|
|
481
|
-
|
|
482
|
-
for (let i = 0; i < options.length; i++) {
|
|
483
|
-
const option = options[i];
|
|
484
|
-
const icon = option.icon || '•';
|
|
485
|
-
console.log(` ${c.cyan}${i + 1}.${c.reset} ${icon} ${option.title}`);
|
|
486
|
-
if (option.description) {
|
|
487
|
-
console.log(` ${c.dim}${option.description}${c.reset}`);
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
console.log();
|
|
492
|
-
const choice = await prompt('Enter your choice:');
|
|
493
|
-
const index = parseInt(choice) - 1;
|
|
494
|
-
|
|
495
|
-
if (options[index]) {
|
|
496
|
-
return options[index];
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
return null;
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
// Status widget
|
|
503
|
-
function createStatusWidget(title, status, details = {}) {
|
|
504
|
-
const statusColors = {
|
|
505
|
-
online: c.green,
|
|
506
|
-
offline: c.red,
|
|
507
|
-
warning: c.yellow,
|
|
508
|
-
pending: c.blue,
|
|
509
|
-
};
|
|
510
|
-
|
|
511
|
-
const color = statusColors[status] || c.white;
|
|
512
|
-
const icon = status === 'online' ? '●' : status === 'offline' ? '●' : '○';
|
|
513
|
-
|
|
514
|
-
let widget = `${color}${icon} ${title}${c.reset}\n`;
|
|
515
|
-
|
|
516
|
-
Object.entries(details).forEach(([key, value]) => {
|
|
517
|
-
widget += ` ${c.dim}${key}:${c.reset} ${value}\n`;
|
|
518
|
-
});
|
|
519
|
-
|
|
520
|
-
return createBox(widget.trim(), 'round');
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
// Tree view
|
|
524
|
-
function drawTree(data, prefix = '', isLast = true) {
|
|
525
|
-
const connector = isLast ? '└── ' : '├── ';
|
|
526
|
-
const extension = isLast ? ' ' : '│ ';
|
|
527
|
-
|
|
528
|
-
let output = prefix + connector + data.name + '\n';
|
|
529
|
-
|
|
530
|
-
if (data.children) {
|
|
531
|
-
data.children.forEach((child, idx) => {
|
|
532
|
-
const isLastChild = idx === data.children.length - 1;
|
|
533
|
-
output += drawTree(child, prefix + extension, isLastChild);
|
|
534
|
-
});
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
return output;
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
// Advanced spinner styles
|
|
541
|
-
function createSpinner(text, style = 'dots') {
|
|
542
|
-
const spinnerStyles = {
|
|
543
|
-
dots: { interval: 80, frames: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'] },
|
|
544
|
-
line: { interval: 130, frames: ['-', '\\', '|', '/'] },
|
|
545
|
-
pipe: { interval: 100, frames: ['┤', '┘', '┴', '└', '├', '┌', '┬', '┐'] },
|
|
546
|
-
star: { interval: 70, frames: ['✶', '✸', '✹', '✺', '✹', '✷'] },
|
|
547
|
-
arrow: { interval: 80, frames: ['←', '↖', '↑', '↗', '→', '↘', '↓', '↙'] },
|
|
548
|
-
bounce: { interval: 120, frames: ['⠁', '⠂', '⠄', '⠂'] },
|
|
549
|
-
pulse: { interval: 80, frames: ['●', '○', '●', '○'] },
|
|
550
|
-
matrix: { interval: 70, frames: ['╱', '╲', '╳', '╲', '╱'] },
|
|
551
|
-
};
|
|
552
|
-
|
|
553
|
-
const config = spinnerStyles[style] || spinnerStyles.dots;
|
|
554
|
-
let current = 0;
|
|
555
|
-
let interval = null;
|
|
556
|
-
const stop = () => {
|
|
557
|
-
if (interval) {
|
|
558
|
-
clearInterval(interval);
|
|
559
|
-
interval = null;
|
|
560
|
-
}
|
|
561
|
-
};
|
|
562
|
-
|
|
563
|
-
return {
|
|
564
|
-
start: () => {
|
|
565
|
-
interval = setInterval(() => {
|
|
566
|
-
process.stdout.write(`\r${config.frames[current]} ${text}`);
|
|
567
|
-
current = (current + 1) % config.frames.length;
|
|
568
|
-
}, config.interval);
|
|
569
|
-
},
|
|
570
|
-
stop: stop,
|
|
571
|
-
succeed: (message) => {
|
|
572
|
-
stop();
|
|
573
|
-
process.stdout.write(`\r${c.green}✓${c.reset} ${message || text}\n`);
|
|
574
|
-
},
|
|
575
|
-
fail: (message) => {
|
|
576
|
-
stop();
|
|
577
|
-
process.stdout.write(`\r${c.red}✗${c.reset} ${message || text}\n`);
|
|
578
|
-
}
|
|
579
|
-
};
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
// Advanced terminal features
|
|
583
|
-
const readline = require('readline');
|
|
584
|
-
const { Writable } = require('stream');
|
|
585
|
-
|
|
586
|
-
/**
|
|
587
|
-
* Interactive CLI with real-time input
|
|
588
|
-
*/
|
|
589
|
-
class InteractiveCLI {
|
|
590
|
-
constructor() {
|
|
591
|
-
this.interface = readline.createInterface({
|
|
592
|
-
input: process.stdin,
|
|
593
|
-
output: process.stdout,
|
|
594
|
-
terminal: true
|
|
595
|
-
});
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
/**
|
|
599
|
-
* Create an interactive prompt with auto-completion
|
|
600
|
-
*/
|
|
601
|
-
async promptWithAutoComplete(message, suggestions = []) {
|
|
602
|
-
return new Promise((resolve) => {
|
|
603
|
-
let input = '';
|
|
604
|
-
|
|
605
|
-
this.interface.question(`${c.cyan}?${c.reset} ${message} `, (answer) => {
|
|
606
|
-
resolve(answer.trim());
|
|
607
|
-
});
|
|
608
|
-
|
|
609
|
-
// Enable tab completion
|
|
610
|
-
this.interface.on('TAB', () => {
|
|
611
|
-
const matches = suggestions.filter(s => s.startsWith(input));
|
|
612
|
-
if (matches.length === 1) {
|
|
613
|
-
this.interface.write(matches[0].slice(input.length));
|
|
614
|
-
input = matches[0];
|
|
615
|
-
}
|
|
616
|
-
});
|
|
617
|
-
});
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
/**
|
|
621
|
-
* Multi-select with checkboxes
|
|
622
|
-
*/
|
|
623
|
-
async multiSelect(items, message = 'Select items:') {
|
|
624
|
-
const selected = new Set();
|
|
625
|
-
|
|
626
|
-
console.log(`${c.cyan}?${c.reset} ${message}`);
|
|
627
|
-
console.log(`${c.dim}Use space to toggle, enter to confirm${c.reset}\n`);
|
|
628
|
-
|
|
629
|
-
// Display items with checkboxes
|
|
630
|
-
const display = () => {
|
|
631
|
-
console.clear();
|
|
632
|
-
console.log(`${c.cyan}?${c.reset} ${message}\n`);
|
|
633
|
-
|
|
634
|
-
items.forEach((item, idx) => {
|
|
635
|
-
const checked = selected.has(idx) ? '☑' : '☐';
|
|
636
|
-
console.log(` ${checked} ${idx + 1}. ${item}`);
|
|
637
|
-
});
|
|
638
|
-
};
|
|
639
|
-
|
|
640
|
-
display();
|
|
641
|
-
|
|
642
|
-
return new Promise((resolve) => {
|
|
643
|
-
this.interface.input.on('keypress', (str, key) => {
|
|
644
|
-
if (key.name === 'escape') {
|
|
645
|
-
this.interface.close();
|
|
646
|
-
resolve([]);
|
|
647
|
-
} else if (key.name === 'return') {
|
|
648
|
-
this.interface.close();
|
|
649
|
-
resolve(Array.from(selected).map(i => items[i]));
|
|
650
|
-
} else if (key.name >= '1' && key.name <= '9') {
|
|
651
|
-
const idx = parseInt(key.name) - 1;
|
|
652
|
-
if (idx < items.length) {
|
|
653
|
-
if (selected.has(idx)) {
|
|
654
|
-
selected.delete(idx);
|
|
655
|
-
} else {
|
|
656
|
-
selected.add(idx);
|
|
657
|
-
}
|
|
658
|
-
display();
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
});
|
|
662
|
-
});
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
close() {
|
|
666
|
-
this.interface.close();
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
/**
|
|
671
|
-
* Real-time terminal dashboard
|
|
672
|
-
*/
|
|
673
|
-
class TerminalDashboard {
|
|
674
|
-
constructor() {
|
|
675
|
-
this.widgets = new Map();
|
|
676
|
-
this.updateInterval = null;
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
/**
|
|
680
|
-
* Add a widget to the dashboard
|
|
681
|
-
*/
|
|
682
|
-
addWidget(name, widget) {
|
|
683
|
-
this.widgets.set(name, widget);
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
/**
|
|
687
|
-
* Start rendering the dashboard
|
|
688
|
-
*/
|
|
689
|
-
start(interval = 1000) {
|
|
690
|
-
const render = () => {
|
|
691
|
-
// Clear screen more thoroughly
|
|
692
|
-
process.stdout.write('\x1b[2J\x1b[3J\x1b[H');
|
|
693
|
-
|
|
694
|
-
// Print banner only once at the top
|
|
695
|
-
printBanner();
|
|
696
|
-
console.log();
|
|
697
|
-
|
|
698
|
-
// Simple vertical layout instead of columns
|
|
699
|
-
const widgetEntries = Array.from(this.widgets.entries());
|
|
700
|
-
|
|
701
|
-
// Display widgets side by side in a row
|
|
702
|
-
const widget1 = widgetEntries[0] ? widgetEntries[0][1].render().split('\n') : [];
|
|
703
|
-
const widget2 = widgetEntries[1] ? widgetEntries[1][1].render().split('\n') : [];
|
|
704
|
-
const widget3 = widgetEntries[2] ? widgetEntries[2][1].render().split('\n') : [];
|
|
705
|
-
|
|
706
|
-
// Find the maximum height
|
|
707
|
-
const maxHeight = Math.max(widget1.length, widget2.length, widget3.length);
|
|
708
|
-
|
|
709
|
-
// Print widgets side by side
|
|
710
|
-
for (let i = 0; i < maxHeight; i++) {
|
|
711
|
-
const line1 = widget1[i] || ' '.repeat(40);
|
|
712
|
-
const line2 = widget2[i] || ' '.repeat(40);
|
|
713
|
-
const line3 = widget3[i] || ' '.repeat(40);
|
|
714
|
-
console.log(line1 + ' ' + line2 + ' ' + line3);
|
|
715
|
-
}
|
|
716
|
-
};
|
|
717
|
-
|
|
718
|
-
render();
|
|
719
|
-
this.updateInterval = setInterval(render, interval);
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
/**
|
|
723
|
-
* Stop the dashboard
|
|
724
|
-
*/
|
|
725
|
-
stop() {
|
|
726
|
-
if (this.updateInterval) {
|
|
727
|
-
clearInterval(this.updateInterval);
|
|
728
|
-
this.updateInterval = null;
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
/**
|
|
734
|
-
* Widget base class for dashboard
|
|
735
|
-
*/
|
|
736
|
-
class Widget {
|
|
737
|
-
constructor(title, data) {
|
|
738
|
-
this.title = title;
|
|
739
|
-
this.data = data;
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
render() {
|
|
743
|
-
return createBox(`${this.title}\n${this.formatData()}`, 'round');
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
formatData() {
|
|
747
|
-
return JSON.stringify(this.data, null, 2);
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
/**
|
|
752
|
-
* Progress ring widget
|
|
753
|
-
*/
|
|
754
|
-
class ProgressRing extends Widget {
|
|
755
|
-
constructor(title, value, max = 100) {
|
|
756
|
-
super(title, { value, max });
|
|
757
|
-
this.value = value;
|
|
758
|
-
this.max = max;
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
render() {
|
|
762
|
-
const percentage = Math.round((this.value / this.max) * 100);
|
|
763
|
-
const bar = this.drawProgressBar(percentage);
|
|
764
|
-
|
|
765
|
-
return createBox(
|
|
766
|
-
`${c.bold}${this.title}${c.reset}\n\n` +
|
|
767
|
-
`${bar}\n\n` +
|
|
768
|
-
`${c.cyan}${percentage}%${c.reset} (${this.value}/${this.max})`,
|
|
769
|
-
'round'
|
|
770
|
-
);
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
drawProgressBar(percentage) {
|
|
774
|
-
const width = 30;
|
|
775
|
-
const filled = Math.round((percentage / 100) * width);
|
|
776
|
-
const empty = width - filled;
|
|
777
|
-
|
|
778
|
-
let bar = '';
|
|
779
|
-
for (let i = 0; i < width; i++) {
|
|
780
|
-
if (i < filled) {
|
|
781
|
-
if (percentage < 30) bar += c.red + '█';
|
|
782
|
-
else if (percentage < 70) bar += c.yellow + '█';
|
|
783
|
-
else bar += c.green + '█';
|
|
784
|
-
} else {
|
|
785
|
-
bar += c.dim + '░';
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
return bar + c.reset;
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
/**
|
|
794
|
-
* Sparkline chart widget
|
|
795
|
-
*/
|
|
796
|
-
class Sparkline extends Widget {
|
|
797
|
-
constructor(title, data) {
|
|
798
|
-
super(title, data);
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
render() {
|
|
802
|
-
const sparkline = this.drawSparkline(this.data);
|
|
803
|
-
const min = Math.min(...this.data);
|
|
804
|
-
const max = Math.max(...this.data);
|
|
805
|
-
|
|
806
|
-
return createBox(
|
|
807
|
-
`${c.bold}${this.title}${c.reset}\n\n` +
|
|
808
|
-
`${sparkline}\n\n` +
|
|
809
|
-
`${c.dim}Min: ${min} Max: ${max}${c.reset}`,
|
|
810
|
-
'round'
|
|
811
|
-
);
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
drawSparkline(data) {
|
|
815
|
-
const blocks = ['▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'];
|
|
816
|
-
const max = Math.max(...data);
|
|
817
|
-
const min = Math.min(...data);
|
|
818
|
-
const range = max - min || 1;
|
|
819
|
-
|
|
820
|
-
return data.map(value => {
|
|
821
|
-
const normalized = (value - min) / range;
|
|
822
|
-
const index = Math.floor(normalized * (blocks.length - 1));
|
|
823
|
-
return c.cyan + blocks[index];
|
|
824
|
-
}).join('');
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
|
|
828
|
-
/**
|
|
829
|
-
* Terminal pager for long content
|
|
830
|
-
*/
|
|
831
|
-
class TerminalPager {
|
|
832
|
-
constructor(content, linesPerPage = 20) {
|
|
833
|
-
this.content = content.split('\n');
|
|
834
|
-
this.linesPerPage = linesPerPage;
|
|
835
|
-
this.currentPage = 0;
|
|
836
|
-
this.totalPages = Math.ceil(this.content.length / linesPerPage);
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
async show() {
|
|
840
|
-
const rl = readline.createInterface({
|
|
841
|
-
input: process.stdin,
|
|
842
|
-
output: process.stdout
|
|
843
|
-
});
|
|
844
|
-
|
|
845
|
-
const display = () => {
|
|
846
|
-
console.clear();
|
|
847
|
-
const start = this.currentPage * this.linesPerPage;
|
|
848
|
-
const end = start + this.linesPerPage;
|
|
849
|
-
const page = this.content.slice(start, end);
|
|
850
|
-
|
|
851
|
-
console.log(page.join('\n'));
|
|
852
|
-
console.log();
|
|
853
|
-
console.log(`${c.dim}Page ${this.currentPage + 1}/${this.totalPages}${c.reset}`);
|
|
854
|
-
console.log(`${c.dim}Controls: n-next, p-prev, q-quit${c.reset}`);
|
|
855
|
-
};
|
|
856
|
-
|
|
857
|
-
display();
|
|
858
|
-
|
|
859
|
-
for await (const line of rl) {
|
|
860
|
-
switch (line.trim()) {
|
|
861
|
-
case 'n':
|
|
862
|
-
if (this.currentPage < this.totalPages - 1) {
|
|
863
|
-
this.currentPage++;
|
|
864
|
-
display();
|
|
865
|
-
}
|
|
866
|
-
break;
|
|
867
|
-
case 'p':
|
|
868
|
-
if (this.currentPage > 0) {
|
|
869
|
-
this.currentPage--;
|
|
870
|
-
display();
|
|
871
|
-
}
|
|
872
|
-
break;
|
|
873
|
-
case 'q':
|
|
874
|
-
rl.close();
|
|
875
|
-
return;
|
|
876
|
-
}
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
/**
|
|
882
|
-
* Real-time log viewer with filtering
|
|
883
|
-
*/
|
|
884
|
-
class LogViewer {
|
|
885
|
-
constructor() {
|
|
886
|
-
this.logs = [];
|
|
887
|
-
this.filters = [];
|
|
888
|
-
this.maxLogs = 1000;
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
addLog(level, message, timestamp = new Date()) {
|
|
892
|
-
const log = {
|
|
893
|
-
level,
|
|
894
|
-
message,
|
|
895
|
-
timestamp: timestamp.toISOString()
|
|
896
|
-
};
|
|
897
|
-
|
|
898
|
-
this.logs.push(log);
|
|
899
|
-
|
|
900
|
-
// Keep only recent logs
|
|
901
|
-
if (this.logs.length > this.maxLogs) {
|
|
902
|
-
this.logs.shift();
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
addFilter(pattern) {
|
|
907
|
-
this.filters.push(new RegExp(pattern, 'i'));
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
clearFilters() {
|
|
911
|
-
this.filters = [];
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
display() {
|
|
915
|
-
const filtered = this.logs.filter(log => {
|
|
916
|
-
return this.filters.length === 0 ||
|
|
917
|
-
this.filters.some(regex => regex.test(log.message));
|
|
918
|
-
});
|
|
919
|
-
|
|
920
|
-
console.clear();
|
|
921
|
-
printBanner();
|
|
922
|
-
console.log(`${c.bold}Log Viewer${c.reset}`);
|
|
923
|
-
if (this.filters.length > 0) {
|
|
924
|
-
console.log(`${c.dim}Filters: ${this.filters.map(f => f.source).join(', ')}${c.reset}`);
|
|
925
|
-
}
|
|
926
|
-
console.log();
|
|
927
|
-
|
|
928
|
-
filtered.slice(-50).forEach(log => {
|
|
929
|
-
const time = new Date(log.timestamp).toLocaleTimeString();
|
|
930
|
-
const levelColor = {
|
|
931
|
-
ERROR: c.red,
|
|
932
|
-
WARN: c.yellow,
|
|
933
|
-
INFO: c.blue,
|
|
934
|
-
DEBUG: c.dim
|
|
935
|
-
}[log.level] || c.white;
|
|
936
|
-
|
|
937
|
-
console.log(`${c.dim}${time}${c.reset} ${levelColor}${log.level.padEnd(5)}${c.reset} ${log.message}`);
|
|
938
|
-
});
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
/**
|
|
943
|
-
* ASCII art generator for metrics
|
|
944
|
-
*/
|
|
945
|
-
function drawMetricGauge(value, max, label, width = 30) {
|
|
946
|
-
const percentage = (value / max) * 100;
|
|
947
|
-
const filled = Math.round((percentage / 100) * width);
|
|
948
|
-
|
|
949
|
-
let gauge = '';
|
|
950
|
-
for (let i = 0; i < width; i++) {
|
|
951
|
-
if (i < filled) {
|
|
952
|
-
if (percentage < 30) gauge += c.red + '█';
|
|
953
|
-
else if (percentage < 70) gauge += c.yellow + '█';
|
|
954
|
-
else gauge += c.green + '█';
|
|
955
|
-
} else {
|
|
956
|
-
gauge += c.dim + '░';
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
return `${label}\n${gauge}${c.reset} ${percentage.toFixed(1)}%`;
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
/**
|
|
964
|
-
* Terminal animation system
|
|
965
|
-
*/
|
|
966
|
-
class TerminalAnimation {
|
|
967
|
-
constructor(frames, interval = 100) {
|
|
968
|
-
this.frames = frames;
|
|
969
|
-
this.interval = interval;
|
|
970
|
-
this.current = 0;
|
|
971
|
-
this.running = false;
|
|
972
|
-
this.timer = null;
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
start() {
|
|
976
|
-
this.running = true;
|
|
977
|
-
this.timer = setInterval(() => {
|
|
978
|
-
process.stdout.write(`\r${this.frames[this.current]}`);
|
|
979
|
-
this.current = (this.current + 1) % this.frames.length;
|
|
980
|
-
}, this.interval);
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
stop() {
|
|
984
|
-
this.running = false;
|
|
985
|
-
if (this.timer) {
|
|
986
|
-
clearInterval(this.timer);
|
|
987
|
-
this.timer = null;
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
/**
|
|
993
|
-
* Rich text formatting with markup
|
|
994
|
-
*/
|
|
995
|
-
function richText(text) {
|
|
996
|
-
// Simple markup parser
|
|
997
|
-
const markup = {
|
|
998
|
-
'**': (content) => c.bold + content + c.reset,
|
|
999
|
-
'*': (content) => c.italic + content + c.reset,
|
|
1000
|
-
'`': (content) => c.cyan + content + c.reset,
|
|
1001
|
-
'__': (content) => c.underline + content + c.reset,
|
|
1002
|
-
'~~': (content) => c.strikethrough + content + c.reset,
|
|
1003
|
-
};
|
|
1004
|
-
|
|
1005
|
-
let result = text;
|
|
1006
|
-
|
|
1007
|
-
Object.entries(markup).forEach(([tag, formatter]) => {
|
|
1008
|
-
const regex = new RegExp(`\\${tag}([^\\${tag}]+)\\${tag}`, 'g');
|
|
1009
|
-
result = result.replace(regex, formatter);
|
|
1010
|
-
});
|
|
1011
|
-
|
|
1012
|
-
return result;
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
/**
|
|
1016
|
-
* Export all advanced features
|
|
1017
|
-
*/
|
|
1018
|
-
module.exports = {
|
|
1019
|
-
// Original exports
|
|
1020
|
-
colors: c,
|
|
1021
|
-
printBanner,
|
|
1022
|
-
printCompactBanner,
|
|
1023
|
-
printCommandHeader,
|
|
1024
|
-
printSectionHeader,
|
|
1025
|
-
printSectionFooter,
|
|
1026
|
-
printListItem,
|
|
1027
|
-
printBulletedList,
|
|
1028
|
-
printTable,
|
|
1029
|
-
highlight,
|
|
1030
|
-
highlightCode,
|
|
1031
|
-
highlightPath,
|
|
1032
|
-
printError,
|
|
1033
|
-
printWarning,
|
|
1034
|
-
printSuccess,
|
|
1035
|
-
printSummaryCard,
|
|
1036
|
-
gradientText,
|
|
1037
|
-
formatNumber,
|
|
1038
|
-
formatBytes,
|
|
1039
|
-
statusIcon,
|
|
1040
|
-
progressBar,
|
|
1041
|
-
|
|
1042
|
-
// Advanced features
|
|
1043
|
-
InteractiveCLI,
|
|
1044
|
-
TerminalDashboard,
|
|
1045
|
-
Widget,
|
|
1046
|
-
ProgressRing,
|
|
1047
|
-
Sparkline,
|
|
1048
|
-
TerminalPager,
|
|
1049
|
-
LogViewer,
|
|
1050
|
-
drawMetricGauge,
|
|
1051
|
-
TerminalAnimation,
|
|
1052
|
-
richText,
|
|
1053
|
-
createSpinner,
|
|
1054
|
-
createMultiBar,
|
|
1055
|
-
typewriter,
|
|
1056
|
-
rainbowText,
|
|
1057
|
-
sparkle,
|
|
1058
|
-
createBox,
|
|
1059
|
-
prompt,
|
|
1060
|
-
selectList,
|
|
1061
|
-
animations,
|
|
1062
|
-
showProgress,
|
|
1063
|
-
drawHeatMap,
|
|
1064
|
-
drawChart,
|
|
1065
|
-
createColumns,
|
|
1066
|
-
showMenu,
|
|
1067
|
-
createStatusWidget,
|
|
1068
|
-
drawTree,
|
|
1069
|
-
stripAnsi,
|
|
1070
|
-
};
|