stella-coder 3.9.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.
- package/(/320/270/320/274/321/217 +0 -0
- package/.env.example +4 -0
- package/README.md +72 -0
- package/STELLA.md +6 -0
- package/antimalware/database.mjs +871 -0
- package/antimalware/index.mjs +8 -0
- package/antimalware/scanner.mjs +591 -0
- package/antimalware/ui.mjs +570 -0
- package/components.json +21 -0
- package/create_cats_pptx.py +121 -0
- package/eslint.config.mjs +16 -0
- package/hello.py +2 -0
- package/install.bat +35 -0
- package/next-env.d.ts +6 -0
- package/next.config.mjs +11 -0
- package/package.json +58 -0
- package/pnpm-workspace.yaml +7 -0
- package/postcss.config.mjs +8 -0
- package/publish.mjs +52 -0
- package/stella-cli/banner.mjs +46 -0
- package/stella-cli/build.mjs +151 -0
- package/stella-cli/index.mjs +3073 -0
- package/stella-cli/markdown.mjs +100 -0
- package/stella-cli/sea-config.json +5 -0
- package/stella-cli/security.mjs +237 -0
- package/stella-cli/theme.mjs +89 -0
- package/stella-cli/tools.mjs +3145 -0
- package/tsconfig.json +41 -0
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
import readline from "node:readline"
|
|
2
|
+
import { fullSystemScan, quickScan, scanPath, scanFile, scanRunningProcesses, scanBootSector, startRealTimeMonitor, stopRealTimeMonitor, getMonitorStatus, quarantineFile, listQuarantine, restoreFromQuarantine, loadExclusions, addExclusion, removeExclusion, getExclusions, generateReport, saveReport } from "./scanner.mjs"
|
|
3
|
+
import { QUARANTINE_DIR } from "./database.mjs"
|
|
4
|
+
import fs from "node:fs"
|
|
5
|
+
import path from "node:path"
|
|
6
|
+
|
|
7
|
+
// ═══════════════════════════════════════════════════════════════
|
|
8
|
+
// STELLAR ANTIVIRUS — Kaspersky-style UI
|
|
9
|
+
// ═══════════════════════════════════════════════════════════════
|
|
10
|
+
|
|
11
|
+
const COLORS = {
|
|
12
|
+
reset: "\x1b[0m",
|
|
13
|
+
bold: "\x1b[1m",
|
|
14
|
+
dim: "\x1b[2m",
|
|
15
|
+
italic: "\x1b[3m",
|
|
16
|
+
underline: "\x1b[4m",
|
|
17
|
+
|
|
18
|
+
red: "\x1b[31m",
|
|
19
|
+
green: "\x1b[32m",
|
|
20
|
+
yellow: "\x1b[33m",
|
|
21
|
+
blue: "\x1b[34m",
|
|
22
|
+
magenta: "\x1b[35m",
|
|
23
|
+
cyan: "\x1b[36m",
|
|
24
|
+
white: "\x1b[37m",
|
|
25
|
+
gray: "\x1b[90m",
|
|
26
|
+
|
|
27
|
+
bgRed: "\x1b[41m",
|
|
28
|
+
bgGreen: "\x1b[42m",
|
|
29
|
+
bgYellow: "\x1b[43m",
|
|
30
|
+
bgBlue: "\x1b[44m",
|
|
31
|
+
bgMagenta: "\x1b[45m",
|
|
32
|
+
bgCyan: "\x1b[46m",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function c(color, text) {
|
|
36
|
+
return `${COLORS[color]}${text}${COLORS.reset}`
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function bold(text) { return c("bold", text) }
|
|
40
|
+
function dim(text) { return c("dim", text) }
|
|
41
|
+
function red(text) { return c("red", text) }
|
|
42
|
+
function green(text) { return c("green", text) }
|
|
43
|
+
function yellow(text) { return c("yellow", text) }
|
|
44
|
+
function cyan(text) { return c("cyan", text) }
|
|
45
|
+
function magenta(text) { return c("magenta", text) }
|
|
46
|
+
|
|
47
|
+
function box(title, lines, color = [0, 200, 255]) {
|
|
48
|
+
const [r, g, b] = color
|
|
49
|
+
const ansiColor = `\x1b[38;2;${r};${g};${b}m`
|
|
50
|
+
const reset = "\x1b[0m"
|
|
51
|
+
const maxLen = Math.max(title.length + 4, ...lines.map(l => l.length))
|
|
52
|
+
const width = maxLen + 4
|
|
53
|
+
|
|
54
|
+
const top = `${ansiColor}╭${"─".repeat(width)}╮${reset}`
|
|
55
|
+
const bottom = `${ansiColor}╰${"─".repeat(width)}╯${reset}`
|
|
56
|
+
const titleLine = `${ansiColor}│${reset} ${ansiColor}${COLORS.bold}${title}${reset}${" ".repeat(width - title.length - 2)}${ansiColor}│${reset}`
|
|
57
|
+
|
|
58
|
+
const contentLines = lines.map(l =>
|
|
59
|
+
`${ansiColor}│${reset} ${l}${" ".repeat(Math.max(0, width - l.length - 2))}${ansiColor}│${reset}`
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
return [top, titleLine, `${ansiColor}│${reset}${" ".repeat(width)}${ansiColor}│${reset}`, ...contentLines, bottom].join("\n")
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function progressLine(current, total, width = 40) {
|
|
66
|
+
const pct = Math.floor((current / total) * 100)
|
|
67
|
+
const filled = Math.floor((current / total) * width)
|
|
68
|
+
const empty = width - filled
|
|
69
|
+
const bar = "█".repeat(filled) + "░".repeat(empty)
|
|
70
|
+
return ` ${cyan(bar)} ${pct}% (${current}/${total})`
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function statusIcon(severity) {
|
|
74
|
+
switch (severity) {
|
|
75
|
+
case "critical": return red("●")
|
|
76
|
+
case "high": return yellow("●")
|
|
77
|
+
case "medium": return cyan("●")
|
|
78
|
+
case "low": return dim("●")
|
|
79
|
+
default: return green("●")
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function severityColor(severity) {
|
|
84
|
+
switch (severity) {
|
|
85
|
+
case "critical": return red
|
|
86
|
+
case "high": return yellow
|
|
87
|
+
case "medium": return cyan
|
|
88
|
+
default: return dim
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ═══════════════════════════════════════════════════════════════
|
|
93
|
+
// BANNER
|
|
94
|
+
// ═══════════════════════════════════════════════════════════════
|
|
95
|
+
|
|
96
|
+
function printBanner() {
|
|
97
|
+
const gradient = [
|
|
98
|
+
[167, 139, 250], [165, 136, 250], [163, 133, 250], [161, 130, 249],
|
|
99
|
+
[159, 127, 249], [157, 124, 249], [155, 121, 248], [153, 118, 248],
|
|
100
|
+
[151, 115, 248], [149, 112, 248], [147, 109, 247], [145, 106, 247],
|
|
101
|
+
[143, 103, 247], [141, 100, 247], [139, 97, 246], [137, 94, 246],
|
|
102
|
+
[135, 97, 246], [133, 100, 246], [131, 103, 247], [129, 106, 247],
|
|
103
|
+
[127, 109, 247], [125, 112, 247], [123, 115, 246], [121, 118, 246],
|
|
104
|
+
[119, 121, 246], [117, 124, 245], [115, 127, 245], [113, 130, 245],
|
|
105
|
+
[111, 133, 245], [109, 136, 244], [107, 139, 244], [105, 142, 243],
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
const logo = [
|
|
109
|
+
" ███████╗████████╗██████╗ ███████╗██████╗ ███╗ ███╗",
|
|
110
|
+
" ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██╔══██╗████╗ ████║",
|
|
111
|
+
" ███████╗ ██║ ██████╔╝█████╗ ██████╔╝██╔████╔██║",
|
|
112
|
+
" ╚════██║ ██║ ██╔══██╗██╔══╝ ██╔══██╗██║╚██╔╝██║",
|
|
113
|
+
" ███████║ ██║ ██║ ██║███████╗██║ ██║██║ ╚═╝ ██║",
|
|
114
|
+
" ╚══════╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝",
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
console.log()
|
|
118
|
+
for (const line of logo) {
|
|
119
|
+
let colored = ""
|
|
120
|
+
for (let i = 0; i < line.length; i++) {
|
|
121
|
+
const colorIdx = Math.floor((i / line.length) * gradient.length)
|
|
122
|
+
const [r, g, b] = gradient[Math.min(colorIdx, gradient.length - 1)]
|
|
123
|
+
colored += `\x1b[38;2;${r};${g};${b}m${line[i]}`
|
|
124
|
+
}
|
|
125
|
+
console.log(colored + "\x1b[0m")
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
console.log()
|
|
129
|
+
console.log(` ${magenta("✦")} ${bold("Stellar Antivirus")}${dim(" · powered by ")}${cyan("Stella")} ${dim("Security Engine")}`)
|
|
130
|
+
console.log()
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function printStatusBanner(status, detail = "") {
|
|
134
|
+
const [r, g, b] = status === "safe" ? [0, 200, 100] : status === "warning" ? [255, 200, 0] : [255, 80, 80]
|
|
135
|
+
const ansi = `\x1b[38;2;${r};${g};${b}m`
|
|
136
|
+
const icon = status === "safe" ? "✓" : status === "warning" ? "⚠" : "✗"
|
|
137
|
+
|
|
138
|
+
console.log()
|
|
139
|
+
console.log(`${ansi} ╔══════════════════════════════════════════════════════════╗${COLORS.reset}`)
|
|
140
|
+
console.log(`${ansi} ║ ${icon} ${bold(detail || (status === "safe" ? "СИСТЕМА ЗАЩИЩЕНА" : "ОБНАРУЖЕНЫ УГРОЗЫ"))}${" ".repeat(Math.max(0, 52 - (detail || "").length))}║${COLORS.reset}`)
|
|
141
|
+
console.log(`${ansi} ╚══════════════════════════════════════════════════════════╝${COLORS.reset}`)
|
|
142
|
+
console.log()
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ═══════════════════════════════════════════════════════════════
|
|
146
|
+
// SCAN RESULTS DISPLAY
|
|
147
|
+
// ═══════════════════════════════════════════════════════════════
|
|
148
|
+
|
|
149
|
+
function displayScanResults(results, scanType = "full") {
|
|
150
|
+
const filtered = results.filter(r => !r.excluded)
|
|
151
|
+
const excludedCount = results.filter(r => r.excluded).length
|
|
152
|
+
const threats = filtered.filter(r => !r.clean && !r.error)
|
|
153
|
+
const warnings = filtered.filter(r => r.clean && r.warnings && r.warnings.length > 0)
|
|
154
|
+
const clean = filtered.filter(r => r.clean && (!r.warnings || r.warnings.length === 0))
|
|
155
|
+
const errors = filtered.filter(r => r.error)
|
|
156
|
+
|
|
157
|
+
const scanTypeNames = {
|
|
158
|
+
full: "ПОЛНОЕ СКАНИРОВАНИЕ",
|
|
159
|
+
quick: "БЫСТРОЕ СКАНИРОВАНИЕ",
|
|
160
|
+
custom: "СКАНИРОВАНИЕ КАТАЛОГА",
|
|
161
|
+
"boot-sector": "СКАНИРОВАНИЕ MBR",
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
console.log()
|
|
165
|
+
console.log(box(scanTypeNames[scanType] || "СКАНИРОВАНИЕ", [
|
|
166
|
+
`Файлов: ${results.length}`,
|
|
167
|
+
`Угрозы: ${threats.length > 0 ? red(String(threats.length)) : green(String(threats.length))}`,
|
|
168
|
+
`Предупрежд.: ${warnings.length > 0 ? yellow(String(warnings.length)) : green(String(warnings.length))}`,
|
|
169
|
+
`Чисто: ${green(String(clean.length))}`,
|
|
170
|
+
`Исключено: ${dim(String(excludedCount))}`,
|
|
171
|
+
`Ошибки: ${errors.length > 0 ? red(String(errors.length)) : green(String(errors.length))}`,
|
|
172
|
+
], threats.length > 0 ? [255, 80, 80] : [0, 200, 100]))
|
|
173
|
+
|
|
174
|
+
if (threats.length > 0) {
|
|
175
|
+
console.log(`\n ${red(bold("══════ УГРОЗЫ ══════"))}`)
|
|
176
|
+
console.log()
|
|
177
|
+
for (const t of threats) {
|
|
178
|
+
console.log(` ${red("●")} ${bold(path.basename(t.filepath))}`)
|
|
179
|
+
console.log(` ${dim(t.filepath)}`)
|
|
180
|
+
if (t.threats && t.threats.length > 0) {
|
|
181
|
+
for (const d of t.threats) {
|
|
182
|
+
const color = severityColor(d.severity)
|
|
183
|
+
console.log(` ${color("▸")} ${d.message}`)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
console.log()
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (warnings.length > 0) {
|
|
191
|
+
console.log(`\n ${yellow(bold("══════ ПРЕДУПРЕЖДЕНИЯ ══════"))}`)
|
|
192
|
+
console.log()
|
|
193
|
+
for (const w of warnings.slice(0, 20)) {
|
|
194
|
+
console.log(` ${yellow("●")} ${bold(path.basename(w.filepath))}`)
|
|
195
|
+
for (const d of w.warnings) {
|
|
196
|
+
const color = severityColor(d.severity)
|
|
197
|
+
console.log(` ${color("▸")} ${d.message}`)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (warnings.length > 20) {
|
|
201
|
+
console.log(` ${dim(`... и ещё ${warnings.length - 20} предупреждений`)}`)
|
|
202
|
+
}
|
|
203
|
+
console.log()
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (threats.length === 0 && warnings.length === 0) {
|
|
207
|
+
printStatusBanner("safe")
|
|
208
|
+
} else {
|
|
209
|
+
printStatusBanner("danger")
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ═══════════════════════════════════════════════════════════════
|
|
214
|
+
// MAIN MENU
|
|
215
|
+
// ═══════════════════════════════════════════════════════════════
|
|
216
|
+
|
|
217
|
+
function printMenu() {
|
|
218
|
+
console.log(box("✦ Stellar AV — Команды", [
|
|
219
|
+
"",
|
|
220
|
+
` ${cyan("1")} │ ${bold("Полное сканирование")} Сканировать весь компьютер (C:\\, D:\\, ...)`,
|
|
221
|
+
` ${cyan("2")} │ ${bold("Быстрое сканирование")} Temp, Downloads, Desktop, Startup`,
|
|
222
|
+
` ${cyan("3")} │ ${bold("Сканировать файл")} Проверить один файл`,
|
|
223
|
+
` ${cyan("4")} │ ${bold("Сканировать папку")} Проверить директорию`,
|
|
224
|
+
` ${cyan("5")} │ ${bold("Сканировать MBR")} Проверить загрузочный сектор`,
|
|
225
|
+
` ${cyan("6")} │ ${bold("Процессы")} Проверить запущенные процессы`,
|
|
226
|
+
` ${cyan("7")} │ ${bold("Мониторинг 24/7")} Реальное время — следить за файлами`,
|
|
227
|
+
` ${cyan("8")} │ ${bold("Карантин")} Удалённые угрозы`,
|
|
228
|
+
` ${cyan("9")} │ ${bold("Исключения")} Список исключений`,
|
|
229
|
+
` ${cyan("10")}│ ${bold("Отчёт")} Сохранить отчёт в JSON`,
|
|
230
|
+
"",
|
|
231
|
+
` ${cyan("/exit")} │ ${dim("Выход")}`,
|
|
232
|
+
], [0, 200, 255]))
|
|
233
|
+
console.log()
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ═══════════════════════════════════════════════════════════════
|
|
237
|
+
// INTERACTIVE MODE
|
|
238
|
+
// ═══════════════════════════════════════════════════════════════
|
|
239
|
+
|
|
240
|
+
export function startInteractive() {
|
|
241
|
+
const rl = readline.createInterface({
|
|
242
|
+
input: process.stdin,
|
|
243
|
+
output: process.stdout,
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
printBanner()
|
|
247
|
+
printStatusBanner("safe", "Stellar Antivirus запущен — готов к сканированию")
|
|
248
|
+
printMenu()
|
|
249
|
+
|
|
250
|
+
const ask = () => {
|
|
251
|
+
rl.question(`${magenta("❯")} `, async (input) => {
|
|
252
|
+
const cmd = input.trim().toLowerCase()
|
|
253
|
+
|
|
254
|
+
switch (cmd) {
|
|
255
|
+
case "1":
|
|
256
|
+
case "full":
|
|
257
|
+
case "полное": {
|
|
258
|
+
console.log()
|
|
259
|
+
console.log(` ${cyan("▶")} ${bold("Полное сканирование системы...")}`)
|
|
260
|
+
console.log(` ${dim("Сканирование всех дисков C:\\, D:\\, ...")}`)
|
|
261
|
+
console.log()
|
|
262
|
+
|
|
263
|
+
let lastUpdate = Date.now()
|
|
264
|
+
const { results, totalFiles } = fullSystemScan({
|
|
265
|
+
onProgress: (count, file) => {
|
|
266
|
+
if (Date.now() - lastUpdate > 100) {
|
|
267
|
+
process.stdout.write(`\r ${progressLine(count, Math.max(count, 500))} ${dim(path.basename(file).substring(0, 30))}`)
|
|
268
|
+
lastUpdate = Date.now()
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
process.stdout.write("\r" + " ".repeat(80) + "\r")
|
|
274
|
+
displayScanResults(results, "full")
|
|
275
|
+
break
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
case "2":
|
|
279
|
+
case "quick":
|
|
280
|
+
case "быстрое": {
|
|
281
|
+
console.log()
|
|
282
|
+
console.log(` ${cyan("▶")} ${bold("Быстрое сканирование...")}`)
|
|
283
|
+
console.log(` ${dim("Temp, Downloads, Desktop, Startup, AppData")}`)
|
|
284
|
+
console.log()
|
|
285
|
+
|
|
286
|
+
let lastUpdate = Date.now()
|
|
287
|
+
const { results } = quickScan({
|
|
288
|
+
onProgress: (count, file) => {
|
|
289
|
+
if (Date.now() - lastUpdate > 100) {
|
|
290
|
+
process.stdout.write(`\r ${progressLine(count, Math.max(count, 200))} ${dim(path.basename(file).substring(0, 30))}`)
|
|
291
|
+
lastUpdate = Date.now()
|
|
292
|
+
}
|
|
293
|
+
},
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
process.stdout.write("\r" + " ".repeat(80) + "\r")
|
|
297
|
+
displayScanResults(results, "quick")
|
|
298
|
+
break
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
case "3":
|
|
302
|
+
case "file":
|
|
303
|
+
case "файл": {
|
|
304
|
+
rl.question(` Введите путь к файлу: `, (filepath) => {
|
|
305
|
+
if (!filepath.trim()) { ask(); return }
|
|
306
|
+
console.log()
|
|
307
|
+
const result = scanFile(filepath.trim())
|
|
308
|
+
if (result.error) {
|
|
309
|
+
console.log(` ${red("✗")} ${result.error}`)
|
|
310
|
+
} else {
|
|
311
|
+
displayScanResults([result], "custom")
|
|
312
|
+
}
|
|
313
|
+
ask()
|
|
314
|
+
})
|
|
315
|
+
return
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
case "4":
|
|
319
|
+
case "folder":
|
|
320
|
+
case "папка": {
|
|
321
|
+
rl.question(` Введите путь к папке: `, (dirpath) => {
|
|
322
|
+
if (!dirpath.trim()) { ask(); return }
|
|
323
|
+
console.log()
|
|
324
|
+
console.log(` ${cyan("▶")} ${bold("Сканирование:")} ${dirpath}`)
|
|
325
|
+
console.log()
|
|
326
|
+
|
|
327
|
+
let lastUpdate = Date.now()
|
|
328
|
+
const { results } = scanPath(dirpath.trim(), {
|
|
329
|
+
onProgress: (count, file) => {
|
|
330
|
+
if (Date.now() - lastUpdate > 100) {
|
|
331
|
+
process.stdout.write(`\r ${progressLine(count, Math.max(count, 100))} ${dim(path.basename(file).substring(0, 30))}`)
|
|
332
|
+
lastUpdate = Date.now()
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
process.stdout.write("\r" + " ".repeat(80) + "\r")
|
|
338
|
+
displayScanResults(results, "custom")
|
|
339
|
+
ask()
|
|
340
|
+
})
|
|
341
|
+
return
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
case "5":
|
|
345
|
+
case "mbr":
|
|
346
|
+
case "загрузчик": {
|
|
347
|
+
console.log()
|
|
348
|
+
console.log(` ${cyan("▶")} ${bold("Сканирование MBR (Master Boot Record)...")}`)
|
|
349
|
+
console.log()
|
|
350
|
+
|
|
351
|
+
const drives = ["C:", "D:", "E:"]
|
|
352
|
+
for (const drive of drives) {
|
|
353
|
+
if (fs.existsSync(drive + "\\")) {
|
|
354
|
+
console.log(` Проверка ${drive}\\ ...`)
|
|
355
|
+
const result = scanBootSector(drive)
|
|
356
|
+
if (result.error) {
|
|
357
|
+
console.log(` ${yellow("⚠")} ${result.error}`)
|
|
358
|
+
} else {
|
|
359
|
+
if (!result.clean) {
|
|
360
|
+
console.log(` ${red("✗")} ${drive} MBR: ОБНАРУЖЕНА УГРОЗА!`)
|
|
361
|
+
for (const t of result.threats) console.log(` ${red("▸")} ${t.message}`)
|
|
362
|
+
} else {
|
|
363
|
+
console.log(` ${green("✓")} ${drive} MBR: чисто`)
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
console.log()
|
|
369
|
+
break
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
case "6":
|
|
373
|
+
case "processes":
|
|
374
|
+
case "процессы": {
|
|
375
|
+
console.log()
|
|
376
|
+
console.log(` ${cyan("▶")} ${bold("Проверка запущенных процессов...")}`)
|
|
377
|
+
console.log()
|
|
378
|
+
|
|
379
|
+
const procs = scanRunningProcesses()
|
|
380
|
+
if (procs.length === 0) {
|
|
381
|
+
console.log(` ${yellow("⚠")} Не удалось получить список процессов`)
|
|
382
|
+
} else {
|
|
383
|
+
const susp = procs.filter(p => p.suspicious)
|
|
384
|
+
const normal = procs.filter(p => !p.suspicious)
|
|
385
|
+
|
|
386
|
+
console.log(` Всего процессов: ${procs.length}`)
|
|
387
|
+
console.log(` ${green("Чистых:")} ${normal.length}`)
|
|
388
|
+
if (susp.length > 0) {
|
|
389
|
+
console.log(` ${red("Подозрительных:")} ${susp.length}`)
|
|
390
|
+
console.log()
|
|
391
|
+
console.log(` ${red(bold("══════ ПОДОЗРИТЕЛЬНЫЕ ══════"))}`)
|
|
392
|
+
for (const p of susp) {
|
|
393
|
+
console.log(` ${red("●")} ${bold(p.name)} (PID: ${p.pid}, Память: ${p.memory})`)
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
console.log()
|
|
398
|
+
break
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
case "7":
|
|
402
|
+
case "monitor":
|
|
403
|
+
case "мониторинг": {
|
|
404
|
+
const status = getMonitorStatus()
|
|
405
|
+
if (status.active) {
|
|
406
|
+
console.log()
|
|
407
|
+
console.log(` ${green("✓")} Мониторинг активен`)
|
|
408
|
+
console.log(` ${dim("Отслеживаемых файлов:")} ${status.trackedFiles}`)
|
|
409
|
+
console.log()
|
|
410
|
+
rl.question(` Остановить мониторинг? (y/n): `, (ans) => {
|
|
411
|
+
if (ans.toLowerCase() === "y" || ans.toLowerCase() === "да") {
|
|
412
|
+
stopRealTimeMonitor()
|
|
413
|
+
console.log(` ${green("✓")} Мониторинг остановлен`)
|
|
414
|
+
}
|
|
415
|
+
ask()
|
|
416
|
+
})
|
|
417
|
+
return
|
|
418
|
+
} else {
|
|
419
|
+
rl.question(` Путь для мониторинга (Enter = текущая папка): `, (dirpath) => {
|
|
420
|
+
const dir = dirpath.trim() || "."
|
|
421
|
+
console.log()
|
|
422
|
+
console.log(` ${cyan("▶")} ${bold("Запуск мониторинга:")} ${dir}`)
|
|
423
|
+
console.log(` ${dim("Проверка каждые 5 секунд...")}`)
|
|
424
|
+
console.log(` ${dim("Нажмите Ctrl+C для остановки")}`)
|
|
425
|
+
console.log()
|
|
426
|
+
|
|
427
|
+
startRealTimeMonitor(dir, {
|
|
428
|
+
interval: 5000,
|
|
429
|
+
onThreat: (result) => {
|
|
430
|
+
console.log()
|
|
431
|
+
console.log(` ${red("⚠ ОБНАРУЖЕНА УГРОЗА!")}`)
|
|
432
|
+
console.log(` ${bold(path.basename(result.filepath))}`)
|
|
433
|
+
for (const t of result.threats) {
|
|
434
|
+
console.log(` ${red("▸")} ${t.message}`)
|
|
435
|
+
}
|
|
436
|
+
console.log()
|
|
437
|
+
},
|
|
438
|
+
onFileChange: (file, type) => {
|
|
439
|
+
const icon = type === "new" ? green("+") : type === "modified" ? yellow("~") : red("-")
|
|
440
|
+
process.stdout.write(`\r ${icon} ${dim(path.basename(file).substring(0, 50))}`)
|
|
441
|
+
},
|
|
442
|
+
})
|
|
443
|
+
console.log()
|
|
444
|
+
ask()
|
|
445
|
+
})
|
|
446
|
+
return
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
case "8":
|
|
451
|
+
case "quarantine":
|
|
452
|
+
case "карантин": {
|
|
453
|
+
const items = listQuarantine()
|
|
454
|
+
console.log()
|
|
455
|
+
if (items.length === 0) {
|
|
456
|
+
console.log(` ${green("✓")} Карантин пуст`)
|
|
457
|
+
} else {
|
|
458
|
+
console.log(box("Карантин", [`Файлов: ${items.length}`], [255, 200, 0]))
|
|
459
|
+
console.log()
|
|
460
|
+
for (const item of items) {
|
|
461
|
+
console.log(` ${yellow("●")} ${bold(item.originalPath)}`)
|
|
462
|
+
console.log(` ${dim("Дата:")} ${item.timestamp}`)
|
|
463
|
+
console.log()
|
|
464
|
+
}
|
|
465
|
+
rl.question(` Восстановить файл? (y/n): `, (ans) => {
|
|
466
|
+
if (ans.toLowerCase() === "y") {
|
|
467
|
+
rl.question(` Номер файла (1-${items.length}): `, (num) => {
|
|
468
|
+
const idx = parseInt(num) - 1
|
|
469
|
+
if (idx >= 0 && idx < items.length) {
|
|
470
|
+
const quarantinePath = path.join(QUARANTINE_DIR, items[idx].file)
|
|
471
|
+
const result = restoreFromQuarantine(quarantinePath)
|
|
472
|
+
if (result.error) {
|
|
473
|
+
console.log(` ${red("✗")} ${result.error}`)
|
|
474
|
+
} else {
|
|
475
|
+
console.log(` ${green("✓")} Восстановлено: ${result.restored}`)
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
ask()
|
|
479
|
+
})
|
|
480
|
+
return
|
|
481
|
+
}
|
|
482
|
+
ask()
|
|
483
|
+
})
|
|
484
|
+
return
|
|
485
|
+
}
|
|
486
|
+
break
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
case "9":
|
|
490
|
+
case "exclusions":
|
|
491
|
+
case "исключения": {
|
|
492
|
+
const excs = getExclusions()
|
|
493
|
+
console.log()
|
|
494
|
+
console.log(box("Исключения", [`Правил: ${excs.length}`], [0, 200, 255]))
|
|
495
|
+
console.log()
|
|
496
|
+
for (const exc of excs) {
|
|
497
|
+
console.log(` ${cyan("●")} ${exc}`)
|
|
498
|
+
}
|
|
499
|
+
console.log()
|
|
500
|
+
rl.question(` Добавить исключение (Enter = пропустить): `, (pattern) => {
|
|
501
|
+
if (pattern.trim()) {
|
|
502
|
+
addExclusion(pattern.trim())
|
|
503
|
+
console.log(` ${green("✓")} Добавлено: ${pattern.trim()}`)
|
|
504
|
+
}
|
|
505
|
+
ask()
|
|
506
|
+
})
|
|
507
|
+
return
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
case "10":
|
|
511
|
+
case "report":
|
|
512
|
+
case "отчёт": {
|
|
513
|
+
console.log()
|
|
514
|
+
rl.question(` Тип отчёта (full/quick): `, (type) => {
|
|
515
|
+
console.log(` ${cyan("▶")} ${bold("Создание отчёта...")}`)
|
|
516
|
+
console.log()
|
|
517
|
+
|
|
518
|
+
const scanFunc = type === "quick" ? quickScan : fullSystemScan
|
|
519
|
+
const { results } = scanFunc()
|
|
520
|
+
const report = generateReport(results, type || "full")
|
|
521
|
+
|
|
522
|
+
const filename = `stellar-report-${new Date().toISOString().slice(0, 10)}.json`
|
|
523
|
+
saveReport(report, filename)
|
|
524
|
+
console.log(` ${green("✓")} Отчёт сохранён: ${filename}`)
|
|
525
|
+
console.log()
|
|
526
|
+
ask()
|
|
527
|
+
})
|
|
528
|
+
return
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
case "/exit":
|
|
532
|
+
case "exit":
|
|
533
|
+
case "выход": {
|
|
534
|
+
console.log()
|
|
535
|
+
console.log(` ${magenta("✦")} ${dim("Stellar Antivirus — до встречи!")}`)
|
|
536
|
+
console.log()
|
|
537
|
+
rl.close()
|
|
538
|
+
return
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
case "/help":
|
|
542
|
+
case "help":
|
|
543
|
+
case "помощь": {
|
|
544
|
+
printMenu()
|
|
545
|
+
break
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
default: {
|
|
549
|
+
console.log(` ${yellow("⚠")} Неизвестная команда. Введите ${cyan("/help")} для справки.`)
|
|
550
|
+
console.log()
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
ask()
|
|
555
|
+
})
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
ask()
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// ═══════════════════════════════════════════════════════════════
|
|
562
|
+
// EXPORTS
|
|
563
|
+
// ═══════════════════════════════════════════════════════════════
|
|
564
|
+
|
|
565
|
+
export { printBanner, displayScanResults, box, statusIcon, severityColor }
|
|
566
|
+
|
|
567
|
+
// Run if executed directly
|
|
568
|
+
if (process.argv[1] && process.argv[1].includes("ui.mjs")) {
|
|
569
|
+
startInteractive()
|
|
570
|
+
}
|
package/components.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "base-nova",
|
|
4
|
+
"rsc": true,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "",
|
|
8
|
+
"css": "app/globals.css",
|
|
9
|
+
"baseColor": "neutral",
|
|
10
|
+
"cssVariables": true,
|
|
11
|
+
"prefix": ""
|
|
12
|
+
},
|
|
13
|
+
"aliases": {
|
|
14
|
+
"components": "@/components",
|
|
15
|
+
"utils": "@/lib/utils",
|
|
16
|
+
"ui": "@/components/ui",
|
|
17
|
+
"lib": "@/lib",
|
|
18
|
+
"hooks": "@/hooks"
|
|
19
|
+
},
|
|
20
|
+
"iconLibrary": "lucide"
|
|
21
|
+
}
|