systemlens 1.0.0

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.
@@ -0,0 +1,318 @@
1
+ // ─── CLI Renderer ────────────────────────────────────────────────
2
+ // Beautiful terminal output for SystemLens
3
+ import chalk from 'chalk';
4
+ import Table from 'cli-table3';
5
+ import { progressBar } from '../utils/helpers.js';
6
+
7
+ export class CliRenderer {
8
+ constructor() {
9
+ this.width = process.stdout.columns || 80;
10
+ }
11
+
12
+ /**
13
+ * Render the full system report
14
+ * @param {Object} report - { analysis, explanation, suggestions, snapshot }
15
+ */
16
+ render(report) {
17
+ const { analysis, explanation, suggestions, snapshot, aiInsights } = report;
18
+
19
+ console.clear();
20
+ this._renderHeader();
21
+ this._renderHealthBanner(explanation.health);
22
+ this._renderHeadline(explanation.headline);
23
+ this._renderSystemSummary(analysis.cpu, analysis.memory, snapshot);
24
+ this._renderExplanation(explanation.explanation);
25
+ this._renderCauseEffect(explanation.causeAndEffect);
26
+ this._renderTopProcesses(analysis.rawProcesses?.topByCpu || []);
27
+ this._renderDevInsights(explanation.devInsights);
28
+ this._renderPatterns(explanation.patternNarrative);
29
+ this._renderAIInsights(aiInsights);
30
+ this._renderSuggestions(suggestions);
31
+ this._renderFooter(analysis.patterns);
32
+ }
33
+
34
+ // ─── Header ──────────────────────────────────────────────
35
+ _renderHeader() {
36
+ const title = chalk.bold.cyan(`
37
+ ╔═══════════════════════════════════════════════════════════╗
38
+ ║ 🔬 S Y S T E M L E N S ║
39
+ ║ Intelligent System Explainer ║
40
+ ║ CreatedBYNJ5.0 ║
41
+ ╚═══════════════════════════════════════════════════════════╝`);
42
+ console.log(title);
43
+ console.log();
44
+ }
45
+
46
+ // ─── Health Banner ───────────────────────────────────────
47
+ _renderHealthBanner(health) {
48
+ const colors = {
49
+ excellent: chalk.bgGreen.black.bold,
50
+ good: chalk.bgYellow.black.bold,
51
+ stressed: chalk.bgHex('#FF8C00').black.bold,
52
+ critical: chalk.bgRed.white.bold,
53
+ };
54
+
55
+ const colorFn = colors[health.status] || chalk.bgGray.white.bold;
56
+ const banner = colorFn(` ${health.emoji} SYSTEM STATUS: ${health.label.toUpperCase()} `);
57
+ console.log(` ${banner}`);
58
+ console.log(chalk.dim(` ${health.description}`));
59
+ console.log();
60
+ }
61
+
62
+ // ─── Headline ────────────────────────────────────────────
63
+ _renderHeadline(headline) {
64
+ console.log(chalk.bold.white(` ${headline}`));
65
+ console.log();
66
+ }
67
+
68
+ // ─── System Summary ──────────────────────────────────────
69
+ _renderSystemSummary(cpu, memory, snapshot) {
70
+ console.log(chalk.bold.underline.cyan(' 📊 System Summary'));
71
+ console.log();
72
+
73
+ // CPU bar
74
+ const cpuColor = cpu.overall >= 80 ? chalk.red : cpu.overall >= 60 ? chalk.yellow : chalk.green;
75
+ const cpuBar = this._coloredBar(cpu.overall);
76
+ console.log(` CPU ${cpuBar} ${cpuColor.bold(`${cpu.overall}%`)}`);
77
+ console.log(chalk.dim(` User: ${cpu.details.user}% │ System: ${cpu.details.system}% │ Idle: ${cpu.details.idle}%`));
78
+
79
+ // Memory bar
80
+ const memPct = memory.usedPercent;
81
+ const memColor = memPct >= 80 ? chalk.red : memPct >= 60 ? chalk.yellow : chalk.green;
82
+ const memBar = this._coloredBar(memPct);
83
+ console.log(` Memory ${memBar} ${memColor.bold(`${memPct}%`)}`);
84
+ console.log(chalk.dim(` Used: ${memory.details.formatted.used} │ Free: ${memory.details.formatted.free} │ Total: ${memory.details.formatted.total}`));
85
+
86
+ // Disk (if available)
87
+ if (snapshot?.disk?.filesystems?.length > 0) {
88
+ const rootFs = snapshot.disk.filesystems.find(f => f.mount === '/') || snapshot.disk.filesystems[0];
89
+ if (rootFs) {
90
+ const diskBar = this._coloredBar(rootFs.usedPercent);
91
+ const diskColor = rootFs.usedPercent >= 90 ? chalk.red : rootFs.usedPercent >= 75 ? chalk.yellow : chalk.green;
92
+ console.log(` Disk ${diskBar} ${diskColor.bold(`${rootFs.usedPercent}%`)}`);
93
+ console.log(chalk.dim(` Used: ${rootFs.formatted.used} │ Free: ${rootFs.formatted.available} │ Total: ${rootFs.formatted.size} [${rootFs.mount}]`));
94
+ }
95
+ }
96
+
97
+ // Temperature
98
+ if (cpu.details.temperature) {
99
+ const tempColor = cpu.details.temperature > 80 ? chalk.red : cpu.details.temperature > 60 ? chalk.yellow : chalk.green;
100
+ console.log(` Temp ${tempColor.bold(`${cpu.details.temperature}°C`)}`);
101
+ }
102
+
103
+ console.log();
104
+ }
105
+
106
+ // ─── Main Explanation ────────────────────────────────────
107
+ _renderExplanation(explanation) {
108
+ if (!explanation) return;
109
+
110
+ console.log(chalk.bold.underline.cyan(' 💬 What\'s Happening'));
111
+ console.log();
112
+
113
+ const paragraphs = explanation.split('\n\n');
114
+ for (const para of paragraphs) {
115
+ // Word-wrap each paragraph
116
+ const wrapped = this._wordWrap(para, this.width - 6);
117
+ for (const line of wrapped) {
118
+ console.log(chalk.white(` ${line}`));
119
+ }
120
+ console.log();
121
+ }
122
+ }
123
+
124
+ // ─── Cause & Effect ──────────────────────────────────────
125
+ _renderCauseEffect(chains) {
126
+ if (!chains || chains.length === 0) return;
127
+
128
+ console.log(chalk.bold.underline.cyan(' 🔗 Cause & Effect'));
129
+ console.log();
130
+
131
+ for (const chain of chains) {
132
+ console.log(chalk.bold(` ${chain.emoji} Cause: `) + chalk.white(chain.cause));
133
+ console.log(chalk.bold(` Effect: `) + chalk.yellow(chain.effect));
134
+ console.log(chalk.bold(` Consequence: `) + chalk.red(chain.consequence));
135
+ console.log();
136
+ }
137
+ }
138
+
139
+ // ─── Top Processes Table ─────────────────────────────────
140
+ _renderTopProcesses(processes) {
141
+ if (!processes || processes.length === 0) return;
142
+
143
+ // Filter out system & background processes — only show apps the user can actually close
144
+ const userProcesses = processes.filter(p => {
145
+ const cat = p.classification?.category;
146
+ return cat !== 'SYSTEM' && cat !== 'BACKGROUND';
147
+ });
148
+
149
+ if (userProcesses.length === 0) {
150
+ console.log(chalk.bold.underline.cyan(' 🔍 Apps Using Resources'));
151
+ console.log();
152
+ console.log(chalk.green(' All resource usage is from system processes — nothing for you to close.'));
153
+ console.log();
154
+ return;
155
+ }
156
+
157
+ console.log(chalk.bold.underline.cyan(' 🔍 Apps Using Resources'));
158
+ console.log(chalk.dim(' (System processes hidden — only showing apps you can close)'));
159
+ console.log();
160
+
161
+ const table = new Table({
162
+ head: [
163
+ chalk.cyan('PID'),
164
+ chalk.cyan('Name'),
165
+ chalk.cyan('Category'),
166
+ chalk.cyan('CPU %'),
167
+ chalk.cyan('Memory %'),
168
+ chalk.cyan('Memory'),
169
+ ],
170
+ style: { head: [], border: ['dim'] },
171
+ colWidths: [8, 20, 16, 8, 10, 10],
172
+ chars: {
173
+ 'top': '─', 'top-mid': '┬', 'top-left': ' ┌', 'top-right': '┐',
174
+ 'bottom': '─', 'bottom-mid': '┴', 'bottom-left': ' └', 'bottom-right': '┘',
175
+ 'left': ' │', 'left-mid': ' ├', 'mid': '─', 'mid-mid': '┼',
176
+ 'right': '│', 'right-mid': '┤', 'middle': '│'
177
+ },
178
+ });
179
+
180
+ for (const proc of userProcesses.slice(0, 8)) {
181
+ const cpuColor = proc.cpu >= 30 ? chalk.red : proc.cpu >= 15 ? chalk.yellow : chalk.white;
182
+ const memColor = proc.mem >= 20 ? chalk.red : proc.mem >= 10 ? chalk.yellow : chalk.white;
183
+
184
+ table.push([
185
+ chalk.dim(proc.pid.toString()),
186
+ chalk.bold.white(proc.name.substring(0, 18)),
187
+ (proc.classification?.label || '❓ Other').substring(0, 14),
188
+ cpuColor(proc.cpu.toString()),
189
+ memColor(proc.mem.toString()),
190
+ chalk.dim(proc.memRssFormatted || ''),
191
+ ]);
192
+ }
193
+
194
+ console.log(table.toString());
195
+ console.log();
196
+ }
197
+
198
+ // ─── Dev Insights ────────────────────────────────────────
199
+ _renderDevInsights(devInsights) {
200
+ if (!devInsights || devInsights.length === 0) return;
201
+
202
+ console.log(chalk.bold.underline.magenta(' 🛠️ Developer Insights'));
203
+ console.log();
204
+
205
+ for (const insight of devInsights) {
206
+ const prefix = insight.actionable ? chalk.yellow('!') : chalk.green('✓');
207
+ console.log(` ${insight.emoji} ${prefix} ${chalk.white(insight.insight)}`);
208
+ }
209
+ console.log();
210
+ }
211
+
212
+ // ─── Pattern Narratives ──────────────────────────────────
213
+ _renderPatterns(patterns) {
214
+ if (!patterns || patterns.length === 0) return;
215
+
216
+ console.log(chalk.bold.underline.cyan(' 📈 Trends & Patterns'));
217
+ console.log();
218
+
219
+ for (const narrative of patterns) {
220
+ console.log(chalk.white(` ${narrative}`));
221
+ }
222
+ console.log();
223
+ }
224
+
225
+ // ─── AI Insights ─────────────────────────────────────────
226
+ _renderAIInsights(aiInsights) {
227
+ if (!aiInsights) return;
228
+
229
+ console.log(chalk.bold.underline.hex('#8B5CF6')(' 🤖 AI Deep Insights'));
230
+ console.log();
231
+
232
+ if (aiInsights.deepInsight) {
233
+ console.log(chalk.hex('#A78BFA')(` 💡 ${aiInsights.deepInsight}`));
234
+ }
235
+ if (aiInsights.prediction) {
236
+ console.log(chalk.hex('#818CF8')(` 🔮 ${aiInsights.prediction}`));
237
+ }
238
+ if (aiInsights.proTip) {
239
+ console.log(chalk.hex('#6366F1')(` ⚡ ${aiInsights.proTip}`));
240
+ }
241
+ console.log();
242
+ }
243
+
244
+ // ─── Suggestions ─────────────────────────────────────────
245
+ _renderSuggestions(suggestions) {
246
+ if (!suggestions || suggestions.length === 0) {
247
+ console.log(chalk.bold.underline.green(' ✅ Suggested Actions'));
248
+ console.log();
249
+ console.log(chalk.green(' No action needed — system is running well!'));
250
+ console.log();
251
+ return;
252
+ }
253
+
254
+ console.log(chalk.bold.underline.green(' ✅ Suggested Actions'));
255
+ console.log();
256
+
257
+ for (let i = 0; i < suggestions.length; i++) {
258
+ const s = suggestions[i];
259
+ const impactColors = {
260
+ critical: chalk.red.bold,
261
+ high: chalk.yellow.bold,
262
+ medium: chalk.cyan,
263
+ low: chalk.dim,
264
+ };
265
+ const impactFn = impactColors[s.impact] || chalk.white;
266
+
267
+ console.log(` ${s.emoji} ${chalk.bold.white(`${i + 1}. ${s.action}`)}`);
268
+ console.log(chalk.dim(` ${s.reason}`));
269
+ console.log(` Impact: ${impactFn(s.impact.toUpperCase())} │ Category: ${chalk.cyan(s.category)}`);
270
+ console.log();
271
+ }
272
+ }
273
+
274
+ // ─── Footer ──────────────────────────────────────────────
275
+ _renderFooter(patterns) {
276
+ const time = new Date().toLocaleTimeString();
277
+ const dataPoints = patterns?.cpu?.recentAvg !== undefined
278
+ ? chalk.dim(` │ Avg CPU: ${patterns.cpu.recentAvg}% │ Trend: ${patterns.cpu.trend}`)
279
+ : '';
280
+
281
+ console.log(chalk.dim(` ─────────────────────────────────────────────────────────────`));
282
+ console.log(chalk.dim(` 🕐 Last updated: ${time}${dataPoints}`));
283
+ console.log(chalk.dim(` Press Ctrl+C to exit │ Refreshes every 3 seconds │ CreatedBYNJ5.0`));
284
+ console.log();
285
+ }
286
+
287
+ // ─── Helpers ─────────────────────────────────────────────
288
+ _coloredBar(value, width = 25) {
289
+ const filled = Math.round((value / 100) * width);
290
+ const empty = width - filled;
291
+
292
+ let barColor;
293
+ if (value >= 80) barColor = chalk.red;
294
+ else if (value >= 60) barColor = chalk.yellow;
295
+ else if (value >= 40) barColor = chalk.cyan;
296
+ else barColor = chalk.green;
297
+
298
+ return barColor('█'.repeat(filled)) + chalk.dim('░'.repeat(empty));
299
+ }
300
+
301
+ _wordWrap(text, maxWidth) {
302
+ const words = text.split(' ');
303
+ const lines = [];
304
+ let currentLine = '';
305
+
306
+ for (const word of words) {
307
+ if ((currentLine + ' ' + word).trim().length > maxWidth) {
308
+ lines.push(currentLine.trim());
309
+ currentLine = word;
310
+ } else {
311
+ currentLine += ' ' + word;
312
+ }
313
+ }
314
+ if (currentLine.trim()) lines.push(currentLine.trim());
315
+
316
+ return lines;
317
+ }
318
+ }
@@ -0,0 +1,80 @@
1
+ // ─── SystemLens Constants ────────────────────────────────────────
2
+ // Thresholds, categories, and configuration for the analysis engine
3
+
4
+ export const THRESHOLDS = {
5
+ CPU_HIGH: 80,
6
+ CPU_WARNING: 60,
7
+ CPU_MODERATE: 40,
8
+ MEMORY_HIGH: 80,
9
+ MEMORY_WARNING: 60,
10
+ MEMORY_MODERATE: 40,
11
+ DISK_HIGH: 90,
12
+ DISK_WARNING: 75,
13
+ PROCESS_CPU_HIGH: 30,
14
+ PROCESS_CPU_WARNING: 15,
15
+ PROCESS_MEM_HIGH: 20,
16
+ PROCESS_MEM_WARNING: 10,
17
+ SPIKE_THRESHOLD: 20, // % jump considered a spike
18
+ SUSTAINED_DURATION: 60000, // ms — 60s of high usage = sustained
19
+ };
20
+
21
+ export const PROCESS_CATEGORIES = {
22
+ BROWSER: {
23
+ label: '🌐 Browser',
24
+ patterns: ['chrome', 'chromium', 'firefox', 'brave', 'safari', 'edge', 'opera', 'vivaldi'],
25
+ color: '#4285F4',
26
+ },
27
+ DEV_TOOLS: {
28
+ label: '🛠️ Development',
29
+ patterns: ['node', 'npm', 'npx', 'yarn', 'pnpm', 'code', 'electron', 'webpack', 'vite', 'tsc', 'tsx', 'esbuild', 'turbo', 'jest', 'vitest', 'cargo', 'rustc', 'go', 'python', 'python3', 'pip', 'java', 'javac', 'gradle', 'maven', 'docker', 'git'],
30
+ color: '#F7DF1E',
31
+ },
32
+ EDITORS: {
33
+ label: '📝 Editors/IDE',
34
+ patterns: ['code', 'cursor', 'windsurf', 'sublime', 'atom', 'vim', 'nvim', 'emacs', 'idea', 'webstorm', 'pycharm', 'android-studio'],
35
+ color: '#007ACC',
36
+ },
37
+ MEDIA: {
38
+ label: '🎵 Media',
39
+ patterns: ['spotify', 'vlc', 'mpv', 'ffmpeg', 'obs', 'audacity', 'gimp', 'blender', 'kdenlive'],
40
+ color: '#1DB954',
41
+ },
42
+ COMMUNICATION: {
43
+ label: '💬 Communication',
44
+ patterns: ['slack', 'discord', 'teams', 'zoom', 'telegram', 'signal', 'skype', 'thunderbird'],
45
+ color: '#7289DA',
46
+ },
47
+ SYSTEM: {
48
+ label: '⚙️ System',
49
+ patterns: ['systemd', 'init', 'kworker', 'ksoftirqd', 'kswapd', 'irq', 'migration', 'watchdog', 'dbus', 'polkit', 'NetworkManager', 'snapd', 'thermald', 'upowerd', 'udisks', 'accounts-daemon', 'gdm', 'lightdm', 'sddm', 'Xorg', 'Xwayland', 'gnome-shell', 'plasmashell', 'pipewire', 'pulseaudio', 'wireplumber', 'krunner', 'kactivitymanagerd', 'kwin_wayland', 'kwin_x11', 'kwin', 'kglobalaccel', 'kded', 'ksmserver', 'kscreen', 'ksystemstats', 'powerdevil', 'polkit-kde', 'xdg-desktop-portal', 'xdg-document-portal', 'xdg-permission-store', 'gsd-', 'gnome-keyring', 'at-spi', 'ibus', 'fcitx', 'gvfs', 'nautilus', 'dolphin', 'evolution-', 'goa-', 'mutter', 'marco', 'xfwm', 'xfce4-', 'cinnamon', 'budgie', 'exe'],
50
+ color: '#6C757D',
51
+ },
52
+ BACKGROUND: {
53
+ label: '🔄 Background',
54
+ patterns: ['cron', 'atd', 'rsyslog', 'journald', 'cupsd', 'avahi', 'colord', 'packagekitd', 'fwupd', 'tracker', 'baloo', 'zeitgeist', 'gvfsd', 'dconf', 'at-spi-bus', 'xdg-', 'evolution-data', 'gnome-calendar', 'update-notifier', 'unattended-upgrade', 'packagekit'],
55
+ color: '#ADB5BD',
56
+ },
57
+ UNKNOWN: {
58
+ label: '❓ Other',
59
+ patterns: [],
60
+ color: '#CED4DA',
61
+ },
62
+ };
63
+
64
+ export const SEVERITY = {
65
+ CRITICAL: { label: 'CRITICAL', emoji: '🔴', color: 'red' },
66
+ WARNING: { label: 'WARNING', emoji: '🟡', color: 'yellow' },
67
+ INFO: { label: 'INFO', emoji: '🟢', color: 'green' },
68
+ OK: { label: 'OK', emoji: '✅', color: 'green' },
69
+ };
70
+
71
+ export const MONITORING_INTERVAL = 3000; // ms between data collection cycles
72
+ export const HISTORY_MAX_ENTRIES = 200; // max snapshots in history
73
+ export const TOP_PROCESSES_COUNT = 10; // number of top processes to analyze
74
+
75
+ export const HEALTH_LABELS = {
76
+ EXCELLENT: { emoji: '🟢', label: 'Excellent', description: 'System is running smoothly' },
77
+ GOOD: { emoji: '🟡', label: 'Good', description: 'Minor load detected' },
78
+ STRESSED: { emoji: '🟠', label: 'Stressed', description: 'System under notable pressure' },
79
+ CRITICAL: { emoji: '🔴', label: 'Critical', description: 'System is struggling — action needed' },
80
+ };
@@ -0,0 +1,110 @@
1
+ // ─── SystemLens Helpers ──────────────────────────────────────────
2
+
3
+ /**
4
+ * Format bytes into human-readable string
5
+ */
6
+ export function formatBytes(bytes, decimals = 1) {
7
+ if (bytes === 0) return '0 B';
8
+ const k = 1024;
9
+ const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
10
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
11
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(decimals))} ${sizes[i]}`;
12
+ }
13
+
14
+ /**
15
+ * Format uptime seconds into human-readable duration
16
+ */
17
+ export function formatUptime(seconds) {
18
+ const days = Math.floor(seconds / 86400);
19
+ const hours = Math.floor((seconds % 86400) / 3600);
20
+ const minutes = Math.floor((seconds % 3600) / 60);
21
+
22
+ const parts = [];
23
+ if (days > 0) parts.push(`${days}d`);
24
+ if (hours > 0) parts.push(`${hours}h`);
25
+ if (minutes > 0) parts.push(`${minutes}m`);
26
+
27
+ return parts.length > 0 ? parts.join(' ') : '< 1m';
28
+ }
29
+
30
+ /**
31
+ * Format percentage with color coding level
32
+ */
33
+ export function classifyPercentage(value) {
34
+ if (value >= 80) return 'critical';
35
+ if (value >= 60) return 'warning';
36
+ if (value >= 40) return 'moderate';
37
+ return 'normal';
38
+ }
39
+
40
+ /**
41
+ * Calculate percentage
42
+ */
43
+ export function percentage(part, total) {
44
+ if (total === 0) return 0;
45
+ return parseFloat(((part / total) * 100).toFixed(1));
46
+ }
47
+
48
+ /**
49
+ * Truncate string to max length with ellipsis
50
+ */
51
+ export function truncate(str, maxLength = 30) {
52
+ if (!str) return '';
53
+ return str.length > maxLength ? str.substring(0, maxLength - 1) + '…' : str;
54
+ }
55
+
56
+ /**
57
+ * Get current timestamp in ISO format
58
+ */
59
+ export function timestamp() {
60
+ return new Date().toISOString();
61
+ }
62
+
63
+ /**
64
+ * Sleep for ms milliseconds
65
+ */
66
+ export function sleep(ms) {
67
+ return new Promise(resolve => setTimeout(resolve, ms));
68
+ }
69
+
70
+ /**
71
+ * Generate a progress bar string
72
+ */
73
+ export function progressBar(value, maxValue = 100, width = 20) {
74
+ const ratio = Math.min(value / maxValue, 1);
75
+ const filled = Math.round(ratio * width);
76
+ const empty = width - filled;
77
+
78
+ let bar;
79
+ if (ratio >= 0.8) {
80
+ bar = '█'.repeat(filled) + '░'.repeat(empty);
81
+ } else if (ratio >= 0.6) {
82
+ bar = '█'.repeat(filled) + '░'.repeat(empty);
83
+ } else {
84
+ bar = '█'.repeat(filled) + '░'.repeat(empty);
85
+ }
86
+
87
+ return bar;
88
+ }
89
+
90
+ /**
91
+ * Round to n decimal places
92
+ */
93
+ export function round(value, decimals = 1) {
94
+ return Number(Math.round(value + 'e' + decimals) + 'e-' + decimals);
95
+ }
96
+
97
+ /**
98
+ * Deep clone an object (simple, non-circular)
99
+ */
100
+ export function deepClone(obj) {
101
+ return JSON.parse(JSON.stringify(obj));
102
+ }
103
+
104
+ /**
105
+ * Calculate average of an array
106
+ */
107
+ export function average(arr) {
108
+ if (!arr || arr.length === 0) return 0;
109
+ return arr.reduce((sum, val) => sum + val, 0) / arr.length;
110
+ }