stella-coder 5.3.0 → 5.3.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/COMMANDS.md +47 -0
- package/install-av.bat +17 -0
- package/install-stella-pkg.bat +21 -0
- package/oneline-av.ps1 +7 -0
- package/oneline-stella.ps1 +2 -0
- package/package.json +1 -1
- package/releases/stella-antivirus/database.mjs +871 -0
- package/releases/stella-antivirus/index.mjs +8 -0
- package/releases/stella-antivirus/install-av.bat +12 -0
- package/releases/stella-antivirus/scanner.mjs +591 -0
- package/releases/stella-antivirus/ui.mjs +570 -0
- package/releases/stella-antivirus.zip +0 -0
- package/releases/stella-coder/README.md +67 -0
- package/releases/stella-coder/adb.mjs +200 -0
- package/releases/stella-coder/autonomous-agent.mjs +550 -0
- package/releases/stella-coder/banner.mjs +46 -0
- package/releases/stella-coder/browser-control.mjs +274 -0
- package/releases/stella-coder/build.mjs +151 -0
- package/releases/stella-coder/charts.mjs +411 -0
- package/releases/stella-coder/coding-brain.mjs +753 -0
- package/releases/stella-coder/game-engine.mjs +708 -0
- package/releases/stella-coder/gdrive-backup.mjs +338 -0
- package/releases/stella-coder/git-api.mjs +407 -0
- package/releases/stella-coder/gmail.mjs +415 -0
- package/releases/stella-coder/home-assistant.mjs +168 -0
- package/releases/stella-coder/index.mjs +5810 -0
- package/releases/stella-coder/install-stella.bat +12 -0
- package/releases/stella-coder/markdown.mjs +100 -0
- package/releases/stella-coder/mcp.mjs +296 -0
- package/releases/stella-coder/package.json +67 -0
- package/releases/stella-coder/presentations.mjs +1106 -0
- package/releases/stella-coder/protect.mjs +182 -0
- package/releases/stella-coder/screen-monitor.mjs +334 -0
- package/releases/stella-coder/sea-config.json +5 -0
- package/releases/stella-coder/security.mjs +237 -0
- package/releases/stella-coder/subagents.mjs +142 -0
- package/releases/stella-coder/telegram-bot.mjs +824 -0
- package/releases/stella-coder/tg-server.mjs +116 -0
- package/releases/stella-coder/theme.mjs +91 -0
- package/releases/stella-coder/tools.mjs +3143 -0
- package/releases/stella-coder/web-parser.mjs +229 -0
- package/releases/stella-coder/yandex-maps.mjs +426 -0
- package/releases/stella-coder.zip +0 -0
- package/run-antivirus.bat +4 -0
- package/setup-antivirus.bat +64 -0
- package/setup-full.bat +65 -0
- package/setup-stella.bat +46 -0
- package/stella-cli/game-engine.mjs +2 -2
- package/stella-cli/index.mjs +11 -1
- package/stella-cli/protect.mjs +182 -0
- package/stella.bat +3 -0
- package/tests/test-modules.mjs +101 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Stella Coder — Code Protector & Packager
|
|
3
|
+
import fs from "node:fs"
|
|
4
|
+
import path from "node:path"
|
|
5
|
+
import { execSync } from "node:child_process"
|
|
6
|
+
import { fileURLToPath } from "node:url"
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
9
|
+
const ROOT = path.resolve(__dirname, "..")
|
|
10
|
+
const RELEASES = path.join(ROOT, "releases")
|
|
11
|
+
const STELLA_PKG = path.join(RELEASES, "stella-coder")
|
|
12
|
+
const AV_PKG = path.join(RELEASES, "stella-antivirus")
|
|
13
|
+
|
|
14
|
+
function cleanDir(dir) {
|
|
15
|
+
if (fs.existsSync(dir)) fs.rmSync(dir, { recursive: true })
|
|
16
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function copy(src, dst) {
|
|
20
|
+
if (fs.existsSync(src)) {
|
|
21
|
+
if (fs.statSync(src).isDirectory()) {
|
|
22
|
+
if (!fs.existsSync(dst)) fs.mkdirSync(dst, { recursive: true })
|
|
23
|
+
for (const item of fs.readdirSync(src)) copy(path.join(src, item), path.join(dst, item))
|
|
24
|
+
} else {
|
|
25
|
+
fs.cpSync(src, dst)
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function stripComments(code) {
|
|
31
|
+
return code
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function makeReadable(code) {
|
|
35
|
+
return code
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function addGuard(code) {
|
|
39
|
+
return code
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log("\n ✦ Stella Coder — Protector & Packager\n")
|
|
43
|
+
|
|
44
|
+
// Create release directories
|
|
45
|
+
cleanDir(STELLA_PKG)
|
|
46
|
+
cleanDir(AV_PKG)
|
|
47
|
+
|
|
48
|
+
// 1. Package Stella CLI
|
|
49
|
+
console.log(" [1/3] Packaging Stella CLI...")
|
|
50
|
+
const cliFiles = fs.readdirSync(path.join(ROOT, "stella-cli")).filter(f => f.endsWith(".mjs"))
|
|
51
|
+
for (const f of cliFiles) {
|
|
52
|
+
const src = path.join(ROOT, "stella-cli", f)
|
|
53
|
+
let code = fs.readFileSync(src, "utf8")
|
|
54
|
+
code = addGuard(code)
|
|
55
|
+
code = stripComments(code)
|
|
56
|
+
fs.writeFileSync(path.join(STELLA_PKG, f), code)
|
|
57
|
+
}
|
|
58
|
+
// Copy package.json and README
|
|
59
|
+
copy(path.join(ROOT, "package.json"), path.join(STELLA_PKG, "package.json"))
|
|
60
|
+
copy(path.join(ROOT, "stella-cli", "sea-config.json"), path.join(STELLA_PKG, "sea-config.json"))
|
|
61
|
+
copy(path.join(ROOT, "README.md"), path.join(STELLA_PKG, "README.md"))
|
|
62
|
+
|
|
63
|
+
// Create install-stella.bat
|
|
64
|
+
fs.writeFileSync(path.join(STELLA_PKG, "install-stella.bat"), `@echo off
|
|
65
|
+
chcp 65001 >nul
|
|
66
|
+
echo.
|
|
67
|
+
echo ✦ Stella Coder — Installation
|
|
68
|
+
echo =============================
|
|
69
|
+
echo.
|
|
70
|
+
echo Install: npm i -g stella-coder
|
|
71
|
+
echo Or run: node stella-cli/index.mjs
|
|
72
|
+
echo.
|
|
73
|
+
echo Docs: https://a1x10.github.io/stella/
|
|
74
|
+
echo.
|
|
75
|
+
pause
|
|
76
|
+
`)
|
|
77
|
+
|
|
78
|
+
// 2. Package Antivirus separately
|
|
79
|
+
console.log(" [2/3] Packaging Stella Antivirus...")
|
|
80
|
+
const avFiles = fs.readdirSync(path.join(ROOT, "antimalware")).filter(f => f.endsWith(".mjs"))
|
|
81
|
+
for (const f of avFiles) {
|
|
82
|
+
const src = path.join(ROOT, "antimalware", f)
|
|
83
|
+
let code = fs.readFileSync(src, "utf8")
|
|
84
|
+
code = addGuard(code)
|
|
85
|
+
code = stripComments(code)
|
|
86
|
+
fs.writeFileSync(path.join(AV_PKG, f), code)
|
|
87
|
+
}
|
|
88
|
+
// Copy antimalware/index.mjs as entry point
|
|
89
|
+
copy(path.join(ROOT, "antimalware", "index.mjs"), path.join(AV_PKG, "index.mjs"))
|
|
90
|
+
|
|
91
|
+
// Create standalone install-antivirus.bat
|
|
92
|
+
fs.writeFileSync(path.join(AV_PKG, "install-av.bat"), `@echo off
|
|
93
|
+
chcp 65001 >nul
|
|
94
|
+
echo.
|
|
95
|
+
echo ✦ Stella Antivirus — Installation
|
|
96
|
+
echo =================================
|
|
97
|
+
echo.
|
|
98
|
+
echo Quick run: node index.mjs
|
|
99
|
+
echo.
|
|
100
|
+
echo Scans your system for threats using
|
|
101
|
+
echo signature-based detection + AI analysis.
|
|
102
|
+
echo.
|
|
103
|
+
pause
|
|
104
|
+
`)
|
|
105
|
+
|
|
106
|
+
// 3. Create combined installers for root
|
|
107
|
+
console.log(" [3/3] Creating root installers...")
|
|
108
|
+
|
|
109
|
+
// Stella installer
|
|
110
|
+
fs.writeFileSync(path.join(ROOT, "install-stella-pkg.bat"), `@echo off
|
|
111
|
+
chcp 65001 >nul
|
|
112
|
+
echo.
|
|
113
|
+
echo ✦ Stella Coder v5.3.1 — AI Coding Agent
|
|
114
|
+
echo =========================================
|
|
115
|
+
echo.
|
|
116
|
+
echo [1] npm install -g stella-coder
|
|
117
|
+
call npm install -g stella-coder
|
|
118
|
+
echo.
|
|
119
|
+
echo [2] Verify installation
|
|
120
|
+
call stella --version
|
|
121
|
+
echo.
|
|
122
|
+
if %errorlevel% equ 0 (
|
|
123
|
+
echo ✓ Stella Coder installed!
|
|
124
|
+
echo.
|
|
125
|
+
echo Type "stella" to start
|
|
126
|
+
) else (
|
|
127
|
+
echo ✗ Try: node %~dp0stella-cli/index.mjs
|
|
128
|
+
)
|
|
129
|
+
echo.
|
|
130
|
+
pause
|
|
131
|
+
`)
|
|
132
|
+
|
|
133
|
+
// Antivirus standalone installer
|
|
134
|
+
fs.writeFileSync(path.join(ROOT, "install-av.bat"), `@echo off
|
|
135
|
+
chcp 65001 >nul
|
|
136
|
+
echo.
|
|
137
|
+
echo ✦ Stella Antivirus — Standalone Scanner
|
|
138
|
+
echo =========================================
|
|
139
|
+
echo.
|
|
140
|
+
echo Quick start:
|
|
141
|
+
echo node "%CD%\\antimalware\\index.mjs"
|
|
142
|
+
echo.
|
|
143
|
+
echo Or add to PATH:
|
|
144
|
+
echo set PATH=%%PATH%%;"%CD%\\antimalware"
|
|
145
|
+
echo stella-antivirus
|
|
146
|
+
echo.
|
|
147
|
+
echo Scans: files, processes, registry, startup
|
|
148
|
+
echo Method: signature database + AI heuristic
|
|
149
|
+
echo.
|
|
150
|
+
pause
|
|
151
|
+
`)
|
|
152
|
+
|
|
153
|
+
// Create shortcuts
|
|
154
|
+
fs.writeFileSync(path.join(ROOT, "run-antivirus.bat"), `@echo off
|
|
155
|
+
chcp 65001 >nul
|
|
156
|
+
node "%~dp0antimalware\\index.mjs"
|
|
157
|
+
pause
|
|
158
|
+
`)
|
|
159
|
+
|
|
160
|
+
console.log(fs.existsSync(path.join(AV_PKG, "index.mjs")) ? " ✓" : " ✗")
|
|
161
|
+
console.log()
|
|
162
|
+
|
|
163
|
+
// Create archive versions for GitHub release
|
|
164
|
+
console.log(" Creating archives for GitHub Release...")
|
|
165
|
+
const zipDir = path.join(ROOT, "releases")
|
|
166
|
+
if (!fs.existsSync(zipDir)) fs.mkdirSync(zipDir, { recursive: true })
|
|
167
|
+
|
|
168
|
+
// ZIP Stella
|
|
169
|
+
try {
|
|
170
|
+
execSync(`powershell -Command "Compress-Archive -Path '${STELLA_PKG}\\*' -DestinationPath '${path.join(zipDir, 'stella-coder.zip')}' -Force"`, { timeout: 15000 })
|
|
171
|
+
console.log(" ✓ stella-coder.zip")
|
|
172
|
+
} catch { console.log(" ✗ stella-coder.zip (use 7zip or manual)") }
|
|
173
|
+
|
|
174
|
+
// ZIP Antivirus
|
|
175
|
+
try {
|
|
176
|
+
execSync(`powershell -Command "Compress-Archive -Path '${AV_PKG}\\*' -DestinationPath '${path.join(zipDir, 'stella-antivirus.zip')}' -Force"`, { timeout: 15000 })
|
|
177
|
+
console.log(" ✓ stella-antivirus.zip")
|
|
178
|
+
} catch { console.log(" ✗ stella-antivirus.zip") }
|
|
179
|
+
|
|
180
|
+
console.log()
|
|
181
|
+
console.log(" ✓ Done! Packages in releases/")
|
|
182
|
+
console.log()
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, unlinkSync, statSync } from "fs"
|
|
2
|
+
import { join } from "path"
|
|
3
|
+
import { homedir } from "os"
|
|
4
|
+
import { execSync } from "child_process"
|
|
5
|
+
|
|
6
|
+
const STELLA_DIR = join(homedir(), ".stella")
|
|
7
|
+
const MONITOR_DIR = join(STELLA_DIR, "monitor")
|
|
8
|
+
const HISTORY_FILE = join(MONITOR_DIR, "history.json")
|
|
9
|
+
const ALERTS_FILE = join(MONITOR_DIR, "alerts.json")
|
|
10
|
+
const SCREENSHOTS_DIR = join(MONITOR_DIR, "screenshots")
|
|
11
|
+
const MONITOR_STATE = join(MONITOR_DIR, "state.json")
|
|
12
|
+
|
|
13
|
+
function ensureDir() {
|
|
14
|
+
if (!existsSync(MONITOR_DIR)) mkdirSync(MONITOR_DIR, { recursive: true })
|
|
15
|
+
if (!existsSync(SCREENSHOTS_DIR)) mkdirSync(SCREENSHOTS_DIR, { recursive: true })
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function loadJSON(file, fallback) {
|
|
19
|
+
try { return JSON.parse(readFileSync(file, "utf-8")) } catch { return fallback }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function saveJSON(file, data) {
|
|
23
|
+
ensureDir()
|
|
24
|
+
writeFileSync(file, JSON.stringify(data, null, 2))
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function captureScreenSync() {
|
|
28
|
+
const ts = Date.now()
|
|
29
|
+
const outPath = join(SCREENSHOTS_DIR, `screen_${ts}.png`)
|
|
30
|
+
const tempPath = join(SCREENSHOTS_DIR, `_temp.png`)
|
|
31
|
+
const psScriptPath = join(SCREENSHOTS_DIR, `_capture.ps1`)
|
|
32
|
+
|
|
33
|
+
// Write PowerShell script to file (avoids escaping issues)
|
|
34
|
+
const psScript = `
|
|
35
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
36
|
+
Add-Type -AssemblyName System.Drawing
|
|
37
|
+
try {
|
|
38
|
+
$bounds = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds
|
|
39
|
+
$bitmap = New-Object System.Drawing.Bitmap($bounds.Width, $bounds.Height)
|
|
40
|
+
$graphics = [System.Drawing.Graphics]::FromImage($bitmap)
|
|
41
|
+
$graphics.CopyFromScreen($bounds.Location, [System.Drawing.Point]::Empty, $bounds.Size)
|
|
42
|
+
$bitmap.Save("${tempPath.replace(/\\/g, "\\\\")}")
|
|
43
|
+
$graphics.Dispose()
|
|
44
|
+
$bitmap.Dispose()
|
|
45
|
+
Write-Output "OK"
|
|
46
|
+
} catch {
|
|
47
|
+
Write-Output "FAIL:$($_.Exception.Message)"
|
|
48
|
+
}
|
|
49
|
+
`.trim()
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
writeFileSync(psScriptPath, psScript, "utf-8")
|
|
53
|
+
const result = execSync(`powershell -NoProfile -ExecutionPolicy Bypass -File "${psScriptPath}"`, {
|
|
54
|
+
encoding: "utf-8",
|
|
55
|
+
timeout: 10000,
|
|
56
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
57
|
+
}).trim()
|
|
58
|
+
|
|
59
|
+
if (result.startsWith("OK") && existsSync(tempPath)) {
|
|
60
|
+
const data = readFileSync(tempPath)
|
|
61
|
+
writeFileSync(outPath, data)
|
|
62
|
+
try { unlinkSync(tempPath) } catch {}
|
|
63
|
+
try { unlinkSync(psScriptPath) } catch {}
|
|
64
|
+
return outPath
|
|
65
|
+
}
|
|
66
|
+
try { unlinkSync(psScriptPath) } catch {}
|
|
67
|
+
return null
|
|
68
|
+
} catch {
|
|
69
|
+
return null
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function compressImage(imagePath, maxWidth = 800) {
|
|
74
|
+
const compressedPath = imagePath.replace(".png", "_small.png")
|
|
75
|
+
const psScriptPath = join(SCREENSHOTS_DIR, `_compress.ps1`)
|
|
76
|
+
|
|
77
|
+
const psScript = `
|
|
78
|
+
Add-Type -AssemblyName System.Drawing
|
|
79
|
+
try {
|
|
80
|
+
$img = [System.Drawing.Image]::FromFile("${imagePath.replace(/\\/g, "\\\\")}")
|
|
81
|
+
$ratio = ${maxWidth} / $img.Width
|
|
82
|
+
if ($ratio -ge 1) { $ratio = 1 }
|
|
83
|
+
$newW = [int]($img.Width * $ratio)
|
|
84
|
+
$newH = [int]($img.Height * $ratio)
|
|
85
|
+
$bmp = New-Object System.Drawing.Bitmap($newW, $newH)
|
|
86
|
+
$g = [System.Drawing.Graphics]::FromImage($bmp)
|
|
87
|
+
$g.InterpolationMode = [System.Drawing.Drawing2D.InterpolationMode]::HighQualityBicubic
|
|
88
|
+
$g.DrawImage($img, 0, 0, $newW, $newH)
|
|
89
|
+
$bmp.Save("${compressedPath.replace(/\\/g, "\\\\")}")
|
|
90
|
+
$g.Dispose()
|
|
91
|
+
$bmp.Dispose()
|
|
92
|
+
$img.Dispose()
|
|
93
|
+
Write-Output "OK"
|
|
94
|
+
} catch {
|
|
95
|
+
Write-Output "FAIL"
|
|
96
|
+
}
|
|
97
|
+
`.trim()
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
writeFileSync(psScriptPath, psScript, "utf-8")
|
|
101
|
+
execSync(`powershell -NoProfile -ExecutionPolicy Bypass -File "${psScriptPath}"`, {
|
|
102
|
+
encoding: "utf-8",
|
|
103
|
+
timeout: 10000,
|
|
104
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
105
|
+
})
|
|
106
|
+
try { unlinkSync(psScriptPath) } catch {}
|
|
107
|
+
if (existsSync(compressedPath)) {
|
|
108
|
+
return compressedPath
|
|
109
|
+
}
|
|
110
|
+
} catch {}
|
|
111
|
+
return imagePath
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export class ScreenMonitor {
|
|
115
|
+
constructor() {
|
|
116
|
+
ensureDir()
|
|
117
|
+
this.state = loadJSON(MONITOR_STATE, {
|
|
118
|
+
running: false,
|
|
119
|
+
interval: 2000,
|
|
120
|
+
startedAt: null,
|
|
121
|
+
totalCaptures: 0,
|
|
122
|
+
totalAlerts: 0,
|
|
123
|
+
pid: null,
|
|
124
|
+
})
|
|
125
|
+
this.history = loadJSON(HISTORY_FILE, [])
|
|
126
|
+
this.alerts = loadJSON(ALERTS_FILE, [])
|
|
127
|
+
this.intervalId = null
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
save() {
|
|
131
|
+
saveJSON(MONITOR_STATE, this.state)
|
|
132
|
+
saveJSON(HISTORY_FILE, this.history.slice(-500))
|
|
133
|
+
saveJSON(ALERTS_FILE, this.alerts.slice(-200))
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async captureAndAnalyze(visionFn) {
|
|
137
|
+
const imagePath = captureScreenSync()
|
|
138
|
+
if (!imagePath) {
|
|
139
|
+
return { success: false, error: "Failed to capture screen" }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const stats = statSync(imagePath)
|
|
143
|
+
const sizeKB = Math.round(stats.size / 1024)
|
|
144
|
+
|
|
145
|
+
// Compress for AI analysis
|
|
146
|
+
const compressedPath = compressImage(imagePath, 600)
|
|
147
|
+
let imageBase64
|
|
148
|
+
try {
|
|
149
|
+
imageBase64 = readFileSync(compressedPath).toString("base64")
|
|
150
|
+
} catch {
|
|
151
|
+
imageBase64 = readFileSync(imagePath).toString("base64")
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const ts = new Date().toISOString()
|
|
155
|
+
const capture = {
|
|
156
|
+
id: Date.now().toString(36),
|
|
157
|
+
timestamp: ts,
|
|
158
|
+
path: imagePath,
|
|
159
|
+
sizeKB,
|
|
160
|
+
analysis: null,
|
|
161
|
+
alerts: [],
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Analyze with vision AI
|
|
165
|
+
if (visionFn) {
|
|
166
|
+
try {
|
|
167
|
+
const analysis = await visionFn(imageBase64, this.getRecentContext())
|
|
168
|
+
capture.analysis = analysis
|
|
169
|
+
|
|
170
|
+
// Detect alerts
|
|
171
|
+
const alertKeywords = [
|
|
172
|
+
"error", "crash", "exception", "fatal", "alert", "warning", "failed",
|
|
173
|
+
"ошибка", "краш", "исключение", "фатальная", "предупреждение", "сбой",
|
|
174
|
+
"not responding", "has stopped", "dead", "kill", "terminate",
|
|
175
|
+
"не отвечает", "остановлен", "завершить",
|
|
176
|
+
]
|
|
177
|
+
|
|
178
|
+
const textLower = (analysis.text || "").toLowerCase()
|
|
179
|
+
const detected = alertKeywords.filter(k => textLower.includes(k))
|
|
180
|
+
|
|
181
|
+
if (detected.length > 0) {
|
|
182
|
+
const alert = {
|
|
183
|
+
id: capture.id,
|
|
184
|
+
timestamp: ts,
|
|
185
|
+
type: detected.join(", "),
|
|
186
|
+
severity: detected.some(k => ["fatal", "crash", "exception", "фатальная", "краш", "исключение"].includes(k)) ? "critical" : "warning",
|
|
187
|
+
description: analysis.text?.slice(0, 500),
|
|
188
|
+
screenshot: imagePath,
|
|
189
|
+
}
|
|
190
|
+
capture.alerts.push(alert)
|
|
191
|
+
this.alerts.push(alert)
|
|
192
|
+
this.state.totalAlerts++
|
|
193
|
+
}
|
|
194
|
+
} catch (err) {
|
|
195
|
+
capture.analysis = { error: err.message }
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
this.history.push(capture)
|
|
200
|
+
this.state.totalCaptures++
|
|
201
|
+
this.save()
|
|
202
|
+
|
|
203
|
+
// Clean old screenshots (keep last 50)
|
|
204
|
+
this.cleanOldScreenshots(50)
|
|
205
|
+
|
|
206
|
+
return { success: true, capture }
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
getRecentContext() {
|
|
210
|
+
const recent = this.history.slice(-5)
|
|
211
|
+
return recent.map(h => ({
|
|
212
|
+
time: h.timestamp,
|
|
213
|
+
analysis: h.analysis?.summary || h.analysis?.text?.slice(0, 200) || "no analysis",
|
|
214
|
+
alerts: h.alerts?.length || 0,
|
|
215
|
+
}))
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
cleanOldScreenshots(keep = 50) {
|
|
219
|
+
try {
|
|
220
|
+
const files = readdirSync(SCREENSHOTS_DIR)
|
|
221
|
+
.filter(f => f.startsWith("screen_") && f.endsWith(".png"))
|
|
222
|
+
.sort()
|
|
223
|
+
|
|
224
|
+
if (files.length > keep) {
|
|
225
|
+
const toDelete = files.slice(0, files.length - keep)
|
|
226
|
+
for (const f of toDelete) {
|
|
227
|
+
try { unlinkSync(join(SCREENSHOTS_DIR, f)) } catch {}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
} catch {}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
start(intervalMs, visionFn) {
|
|
234
|
+
if (this.state.running) {
|
|
235
|
+
return { success: false, error: "Monitor already running" }
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
this.state.running = true
|
|
239
|
+
this.state.startedAt = new Date().toISOString()
|
|
240
|
+
this.state.interval = intervalMs || 2000
|
|
241
|
+
this.state.pid = process.pid
|
|
242
|
+
this.save()
|
|
243
|
+
|
|
244
|
+
this.intervalId = setInterval(async () => {
|
|
245
|
+
if (!this.state.running) {
|
|
246
|
+
this.stop()
|
|
247
|
+
return
|
|
248
|
+
}
|
|
249
|
+
await this.captureAndAnalyze(visionFn)
|
|
250
|
+
}, this.state.interval)
|
|
251
|
+
|
|
252
|
+
return { success: true, interval: this.state.interval }
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
stop() {
|
|
256
|
+
if (this.intervalId) {
|
|
257
|
+
clearInterval(this.intervalId)
|
|
258
|
+
this.intervalId = null
|
|
259
|
+
}
|
|
260
|
+
this.state.running = false
|
|
261
|
+
this.state.pid = null
|
|
262
|
+
this.save()
|
|
263
|
+
return { success: true }
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
getStatus() {
|
|
267
|
+
const recentAlerts = this.alerts.slice(-10)
|
|
268
|
+
const recentCaptures = this.history.slice(-5)
|
|
269
|
+
|
|
270
|
+
return {
|
|
271
|
+
running: this.state.running,
|
|
272
|
+
startedAt: this.state.startedAt,
|
|
273
|
+
interval: this.state.interval,
|
|
274
|
+
totalCaptures: this.state.totalCaptures,
|
|
275
|
+
totalAlerts: this.state.totalAlerts,
|
|
276
|
+
pid: this.state.pid,
|
|
277
|
+
recentAlerts,
|
|
278
|
+
recentCaptures: recentCaptures.map(c => ({
|
|
279
|
+
time: c.timestamp,
|
|
280
|
+
size: c.sizeKB + "KB",
|
|
281
|
+
summary: c.analysis?.summary || c.analysis?.text?.slice(0, 100) || "analyzing...",
|
|
282
|
+
alertCount: c.alerts?.length || 0,
|
|
283
|
+
})),
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
getAlerts(since) {
|
|
288
|
+
if (since) {
|
|
289
|
+
return this.alerts.filter(a => new Date(a.timestamp) > new Date(since))
|
|
290
|
+
}
|
|
291
|
+
return this.alerts
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
getScreenshotPath() {
|
|
295
|
+
const files = readdirSync(SCREENSHOTS_DIR)
|
|
296
|
+
.filter(f => f.startsWith("screen_") && f.endsWith(".png"))
|
|
297
|
+
.sort()
|
|
298
|
+
return files.length > 0 ? join(SCREENSHOTS_DIR, files[files.length - 1]) : null
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
generateReport() {
|
|
302
|
+
const status = this.getStatus()
|
|
303
|
+
const alertsByType = {}
|
|
304
|
+
for (const a of this.alerts) {
|
|
305
|
+
alertsByType[a.type] = (alertsByType[a.type] || 0) + 1
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const report = `
|
|
309
|
+
═══════════════════════════════════════════
|
|
310
|
+
👁️ SCREEN MONITOR REPORT
|
|
311
|
+
═══════════════════════════════════════════
|
|
312
|
+
|
|
313
|
+
Status: ${status.running ? "🟢 RUNNING" : "🔴 STOPPED"}
|
|
314
|
+
Started: ${status.startedAt ? new Date(status.startedAt).toLocaleString() : "Never"}
|
|
315
|
+
Interval: every ${status.interval / 1000}s
|
|
316
|
+
Total captures: ${status.totalCaptures}
|
|
317
|
+
Total alerts: ${status.totalAlerts}
|
|
318
|
+
|
|
319
|
+
${Object.keys(alertsByType).length > 0 ? `
|
|
320
|
+
Alert breakdown:
|
|
321
|
+
${Object.entries(alertsByType).map(([type, count]) => ` ⚠️ ${type}: ${count}`).join("\n")}
|
|
322
|
+
` : "No alerts detected."}
|
|
323
|
+
|
|
324
|
+
Recent captures:
|
|
325
|
+
${status.recentCaptures.map(c => ` [${new Date(c.time).toLocaleTimeString()}] ${c.summary} ${c.alertCount > 0 ? `⚠️ ${c.alertCount} alerts` : "✅"}`).join("\n")}
|
|
326
|
+
|
|
327
|
+
═══════════════════════════════════════════
|
|
328
|
+
`.trim()
|
|
329
|
+
|
|
330
|
+
return report
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export default ScreenMonitor
|