opencode-zombie-monitor 1.0.2 → 1.1.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/README.md +48 -16
- package/index.mjs +55 -25
- package/package.json +2 -2
- package/download-emoji.mjs +0 -115
package/README.md
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# 🧟 OpenCode Zombie Monitor
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/opencode-zombie-monitor)
|
|
3
4
|
[](https://github.com/Matroskin86/opencode-zombie-monitor/blob/main/LICENSE)
|
|
4
5
|
[]()
|
|
5
6
|
[]()
|
|
@@ -39,7 +40,7 @@ Add to your `opencode.json`:
|
|
|
39
40
|
|
|
40
41
|
```json
|
|
41
42
|
{
|
|
42
|
-
"plugin": ["
|
|
43
|
+
"plugin": ["opencode-zombie-monitor"]
|
|
43
44
|
}
|
|
44
45
|
```
|
|
45
46
|
|
|
@@ -53,40 +54,71 @@ Just chat normally. The plugin silently patrols your system and eliminates zombi
|
|
|
53
54
|
|
|
54
55
|
When zombies are neutralized, you'll see:
|
|
55
56
|
```
|
|
56
|
-
🧟 Killed 3 zombie
|
|
57
|
+
🧟 Killed 3 zombie processes | Freed 300MB RAM | Headshot! 💥
|
|
57
58
|
```
|
|
58
59
|
|
|
59
|
-
###
|
|
60
|
+
### Commands
|
|
60
61
|
|
|
61
|
-
|
|
62
|
+
| Command | Description |
|
|
63
|
+
|---------|-------------|
|
|
64
|
+
| `/zombies` | Check zombie status |
|
|
65
|
+
| `/kill-zombies` | Manually kill all zombies |
|
|
62
66
|
|
|
67
|
+
**Check status:**
|
|
63
68
|
```
|
|
64
69
|
/zombies
|
|
65
70
|
```
|
|
66
|
-
|
|
67
|
-
Response when all clear:
|
|
68
71
|
```
|
|
69
72
|
✅ 2 processes, no zombies
|
|
70
73
|
```
|
|
74
|
+
or
|
|
75
|
+
```
|
|
76
|
+
🧟 5 zombies of 7 processes | 500MB RAM | /kill-zombies
|
|
77
|
+
```
|
|
71
78
|
|
|
72
|
-
|
|
79
|
+
**Manual kill:**
|
|
80
|
+
```
|
|
81
|
+
/kill-zombies
|
|
82
|
+
```
|
|
73
83
|
```
|
|
74
|
-
|
|
84
|
+
💥 Headshot! Killed 5 zombies | Freed 500MB RAM
|
|
75
85
|
```
|
|
76
86
|
|
|
77
87
|
## ⚙️ Configuration
|
|
78
88
|
|
|
79
|
-
|
|
89
|
+
Configure in your `opencode.json`:
|
|
80
90
|
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"plugin": [
|
|
94
|
+
["opencode-zombie-monitor", {
|
|
95
|
+
"autoKill": true,
|
|
96
|
+
"threshold": 1
|
|
97
|
+
}]
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
```
|
|
84
101
|
|
|
85
|
-
|
|
86
|
-
|
|
102
|
+
| Option | Default | Description |
|
|
103
|
+
|--------|---------|-------------|
|
|
104
|
+
| `autoKill` | `true` | Auto-kill zombies on every message |
|
|
105
|
+
| `threshold` | `1` | Minimum zombies to trigger action |
|
|
87
106
|
|
|
88
|
-
|
|
89
|
-
|
|
107
|
+
### Modes
|
|
108
|
+
|
|
109
|
+
**RAMBO MODE** (default) — Kill on sight:
|
|
110
|
+
```json
|
|
111
|
+
{ "autoKill": true, "threshold": 1 }
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**PATIENT MODE** — Wait until horde forms:
|
|
115
|
+
```json
|
|
116
|
+
{ "autoKill": true, "threshold": 5 }
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**MANUAL MODE** — Only notify, kill with `/kill-zombies`:
|
|
120
|
+
```json
|
|
121
|
+
{ "autoKill": false }
|
|
90
122
|
```
|
|
91
123
|
|
|
92
124
|
## 🖥️ Supported Platforms
|
package/index.mjs
CHANGED
|
@@ -24,28 +24,34 @@ const formatMB = (mb) => mb >= 1024 ? `${(mb / 1024).toFixed(1)}GB` : `${Math.ro
|
|
|
24
24
|
// Localized messages
|
|
25
25
|
const i18n = {
|
|
26
26
|
en: {
|
|
27
|
-
killed: (n, mb) => `🧟 Killed ${n} zombie
|
|
28
|
-
found: (n, mb) => `🧟 ${n} zombie
|
|
27
|
+
killed: (n, mb) => `🧟 Killed ${n} zombie process${n > 1 ? "es" : ""} | Freed ${formatMB(mb)} RAM | Headshot! 💥`,
|
|
28
|
+
found: (n, mb) => `🧟 ${n} zombie process${n > 1 ? "es" : ""} | ${formatMB(mb)} RAM | /kill-zombies`,
|
|
29
29
|
status: (zombies, total, mb) => zombies > 0
|
|
30
|
-
? `🧟 ${zombies} zombie${zombies > 1 ? "s" : ""} of ${total} process${total > 1 ? "es" : ""} | ${formatMB(mb)} RAM |
|
|
30
|
+
? `🧟 ${zombies} zombie${zombies > 1 ? "s" : ""} of ${total} process${total > 1 ? "es" : ""} | ${formatMB(mb)} RAM | /kill-zombies`
|
|
31
31
|
: `✅ ${total} process${total > 1 ? "es" : ""}, no zombies`,
|
|
32
|
-
commandDesc: "Check zombie opencode processes"
|
|
32
|
+
commandDesc: "Check zombie opencode processes",
|
|
33
|
+
killDesc: "Kill zombie opencode processes",
|
|
34
|
+
manualKilled: (n, mb) => `💥 Headshot! Killed ${n} zombie${n > 1 ? "s" : ""} | Freed ${formatMB(mb)} RAM`
|
|
33
35
|
},
|
|
34
36
|
ru: {
|
|
35
|
-
killed: (n, mb) => `🧟 Убито ${n}
|
|
36
|
-
found: (n, mb) => `🧟 ${n}
|
|
37
|
+
killed: (n, mb) => `🧟 Убито ${n} зомби-процесс${n > 1 ? "ов" : ""} | Освобождено ${formatMB(mb)} RAM | Headshot! 💥`,
|
|
38
|
+
found: (n, mb) => `🧟 ${n} зомби-процесс${n > 1 ? "ов" : ""} | ${formatMB(mb)} RAM | /kill-zombies`,
|
|
37
39
|
status: (zombies, total, mb) => zombies > 0
|
|
38
|
-
? `🧟 ${zombies} зомби из ${total} процессов | ${formatMB(mb)} RAM |
|
|
40
|
+
? `🧟 ${zombies} зомби из ${total} процессов | ${formatMB(mb)} RAM | /kill-zombies`
|
|
39
41
|
: `✅ ${total} процессов, зомби нет`,
|
|
40
|
-
commandDesc: "Проверить зомби-процессы opencode"
|
|
42
|
+
commandDesc: "Проверить зомби-процессы opencode",
|
|
43
|
+
killDesc: "Убить зомби-процессы opencode",
|
|
44
|
+
manualKilled: (n, mb) => `💥 Headshot! Убито ${n} зомби | Освобождено ${formatMB(mb)} RAM`
|
|
41
45
|
},
|
|
42
46
|
zh: {
|
|
43
|
-
killed: (n, mb) => `🧟 已击杀 ${n}
|
|
44
|
-
found: (n, mb) => `🧟 发现 ${n}
|
|
47
|
+
killed: (n, mb) => `🧟 已击杀 ${n} 个僵尸进程 | 释放 ${formatMB(mb)} 内存 | Headshot! 💥`,
|
|
48
|
+
found: (n, mb) => `🧟 发现 ${n} 个僵尸进程 | ${formatMB(mb)} 内存 | /kill-zombies`,
|
|
45
49
|
status: (zombies, total, mb) => zombies > 0
|
|
46
|
-
? `🧟 ${total} 个进程中有 ${zombies} 个僵尸 | ${formatMB(mb)} 内存 |
|
|
50
|
+
? `🧟 ${total} 个进程中有 ${zombies} 个僵尸 | ${formatMB(mb)} 内存 | /kill-zombies`
|
|
47
51
|
: `✅ ${total} 个进程,没有僵尸`,
|
|
48
|
-
commandDesc: "
|
|
52
|
+
commandDesc: "检查僵尸进程",
|
|
53
|
+
killDesc: "击杀僵尸进程",
|
|
54
|
+
manualKilled: (n, mb) => `💥 Headshot! 已击杀 ${n} 个僵尸 | 释放 ${formatMB(mb)} 内存`
|
|
49
55
|
}
|
|
50
56
|
}
|
|
51
57
|
|
|
@@ -85,8 +91,13 @@ const killZombies = async () => {
|
|
|
85
91
|
}
|
|
86
92
|
}
|
|
87
93
|
|
|
88
|
-
//
|
|
89
|
-
|
|
94
|
+
// Configuration defaults
|
|
95
|
+
// autoKill: true = kill automatically, false = notify only (manual mode)
|
|
96
|
+
// threshold: minimum zombies to trigger action (1 = immediate)
|
|
97
|
+
const DEFAULT_CONFIG = {
|
|
98
|
+
autoKill: true,
|
|
99
|
+
threshold: 1
|
|
100
|
+
}
|
|
90
101
|
|
|
91
102
|
const sendNotification = async (client, sessionId, text) => {
|
|
92
103
|
await client.session.prompt({
|
|
@@ -107,8 +118,11 @@ const getSessionIdFromMessages = (messages) => {
|
|
|
107
118
|
return null
|
|
108
119
|
}
|
|
109
120
|
|
|
110
|
-
export const ZombieMonitor = async ({ client }) => {
|
|
121
|
+
export const ZombieMonitor = async ({ client, config: pluginConfig }) => {
|
|
111
122
|
let lastNotifiedCount = 0
|
|
123
|
+
|
|
124
|
+
// Merge user config with defaults
|
|
125
|
+
const cfg = { ...DEFAULT_CONFIG, ...pluginConfig }
|
|
112
126
|
|
|
113
127
|
return {
|
|
114
128
|
config: async (opencodeConfig) => {
|
|
@@ -117,6 +131,10 @@ export const ZombieMonitor = async ({ client }) => {
|
|
|
117
131
|
template: "",
|
|
118
132
|
description: t.commandDesc
|
|
119
133
|
}
|
|
134
|
+
opencodeConfig.command["kill-zombies"] = {
|
|
135
|
+
template: "",
|
|
136
|
+
description: t.killDesc
|
|
137
|
+
}
|
|
120
138
|
},
|
|
121
139
|
|
|
122
140
|
"experimental.chat.messages.transform": async (input, output) => {
|
|
@@ -125,15 +143,15 @@ export const ZombieMonitor = async ({ client }) => {
|
|
|
125
143
|
|
|
126
144
|
const { count: zombies, mb } = await getZombieStats()
|
|
127
145
|
|
|
128
|
-
// Auto-kill if zombies >= threshold
|
|
129
|
-
if (zombies >=
|
|
146
|
+
// Auto-kill if enabled and zombies >= threshold
|
|
147
|
+
if (cfg.autoKill && zombies >= cfg.threshold) {
|
|
130
148
|
await killZombies()
|
|
131
149
|
try {
|
|
132
150
|
await sendNotification(client, sessionId, t.killed(zombies, mb))
|
|
133
151
|
} catch (e) {}
|
|
134
152
|
lastNotifiedCount = 0
|
|
135
153
|
}
|
|
136
|
-
// Notify if zombies appeared (
|
|
154
|
+
// Notify if zombies appeared (manual mode or below threshold)
|
|
137
155
|
else if (zombies > 0 && zombies > lastNotifiedCount) {
|
|
138
156
|
lastNotifiedCount = zombies
|
|
139
157
|
try {
|
|
@@ -145,13 +163,25 @@ export const ZombieMonitor = async ({ client }) => {
|
|
|
145
163
|
},
|
|
146
164
|
|
|
147
165
|
"command.execute.before": async (input) => {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
166
|
+
// /zombies - check status
|
|
167
|
+
if (input.command === "zombies") {
|
|
168
|
+
const { count: zombies, mb } = await getZombieStats()
|
|
169
|
+
const total = await getTotalCount()
|
|
170
|
+
await sendNotification(client, input.sessionID, t.status(zombies, total, mb))
|
|
171
|
+
throw new Error("__ZOMBIES_HANDLED__")
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// /kill-zombies - manual kill
|
|
175
|
+
if (input.command === "kill-zombies") {
|
|
176
|
+
const { count: zombies, mb } = await getZombieStats()
|
|
177
|
+
if (zombies > 0) {
|
|
178
|
+
await killZombies()
|
|
179
|
+
await sendNotification(client, input.sessionID, t.manualKilled(zombies, mb))
|
|
180
|
+
} else {
|
|
181
|
+
await sendNotification(client, input.sessionID, t.status(0, await getTotalCount(), 0))
|
|
182
|
+
}
|
|
183
|
+
throw new Error("__ZOMBIES_HANDLED__")
|
|
184
|
+
}
|
|
155
185
|
}
|
|
156
186
|
}
|
|
157
187
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-zombie-monitor",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Auto-kill zombie opencode processes. Supports macOS and Linux with auto language detection (EN/RU).",
|
|
5
5
|
"main": "index.mjs",
|
|
6
6
|
"type": "module",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
|
-
"url": "https://github.com/
|
|
12
|
+
"url": "https://github.com/Matroskin86/opencode-zombie-monitor"
|
|
13
13
|
},
|
|
14
14
|
"peerDependencies": {
|
|
15
15
|
"@opencode-ai/plugin": ">=1.0.0"
|
package/download-emoji.mjs
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Скрипт для скачивания Telegram custom emoji
|
|
3
|
-
*
|
|
4
|
-
* 1. Отправь эмодзи боту @your_bot
|
|
5
|
-
* 2. Запусти: node download-emoji.mjs
|
|
6
|
-
* 3. Эмодзи сохранится в ./assets/emoji.webp
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import https from 'https'
|
|
10
|
-
import fs from 'fs'
|
|
11
|
-
import path from 'path'
|
|
12
|
-
|
|
13
|
-
const BOT_TOKEN = '8061734285:AAHeLB0YHXomdjPPyTg18GM0_RrdvTbp4aA'
|
|
14
|
-
const API_URL = `https://api.telegram.org/bot${BOT_TOKEN}`
|
|
15
|
-
|
|
16
|
-
async function fetchJson(url) {
|
|
17
|
-
return new Promise((resolve, reject) => {
|
|
18
|
-
https.get(url, (res) => {
|
|
19
|
-
let data = ''
|
|
20
|
-
res.on('data', chunk => data += chunk)
|
|
21
|
-
res.on('end', () => resolve(JSON.parse(data)))
|
|
22
|
-
}).on('error', reject)
|
|
23
|
-
})
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
async function downloadFile(filePath, destPath) {
|
|
27
|
-
const fileUrl = `https://api.telegram.org/file/bot${BOT_TOKEN}/${filePath}`
|
|
28
|
-
return new Promise((resolve, reject) => {
|
|
29
|
-
const file = fs.createWriteStream(destPath)
|
|
30
|
-
https.get(fileUrl, (res) => {
|
|
31
|
-
res.pipe(file)
|
|
32
|
-
file.on('finish', () => {
|
|
33
|
-
file.close()
|
|
34
|
-
resolve(destPath)
|
|
35
|
-
})
|
|
36
|
-
}).on('error', reject)
|
|
37
|
-
})
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
async function main() {
|
|
41
|
-
console.log('🔍 Получаю последние сообщения...')
|
|
42
|
-
|
|
43
|
-
// Получить updates
|
|
44
|
-
const updates = await fetchJson(`${API_URL}/getUpdates?limit=10`)
|
|
45
|
-
|
|
46
|
-
if (!updates.ok || !updates.result.length) {
|
|
47
|
-
console.log('❌ Нет сообщений. Отправь эмодзи боту и попробуй снова.')
|
|
48
|
-
return
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Найти сообщение с custom emoji
|
|
52
|
-
let customEmojiId = null
|
|
53
|
-
let messageText = null
|
|
54
|
-
|
|
55
|
-
for (const update of updates.result.reverse()) {
|
|
56
|
-
const msg = update.message
|
|
57
|
-
if (msg?.entities) {
|
|
58
|
-
for (const entity of msg.entities) {
|
|
59
|
-
if (entity.type === 'custom_emoji') {
|
|
60
|
-
customEmojiId = entity.custom_emoji_id
|
|
61
|
-
messageText = msg.text
|
|
62
|
-
break
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
if (customEmojiId) break
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (!customEmojiId) {
|
|
70
|
-
console.log('❌ Custom emoji не найден. Отправь эмодзи из платного/премиум пака.')
|
|
71
|
-
console.log('📋 Последние сообщения:', updates.result.map(u => u.message?.text).filter(Boolean))
|
|
72
|
-
return
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
console.log(`✅ Найден emoji: "${messageText}" (ID: ${customEmojiId})`)
|
|
76
|
-
|
|
77
|
-
// Получить стикер по emoji ID
|
|
78
|
-
console.log('📥 Получаю файл стикера...')
|
|
79
|
-
const stickers = await fetchJson(`${API_URL}/getCustomEmojiStickers?custom_emoji_ids=["${customEmojiId}"]`)
|
|
80
|
-
|
|
81
|
-
if (!stickers.ok || !stickers.result.length) {
|
|
82
|
-
console.log('❌ Не удалось получить стикер:', stickers)
|
|
83
|
-
return
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const sticker = stickers.result[0]
|
|
87
|
-
console.log(`📦 Стикер: ${sticker.file_id}`)
|
|
88
|
-
|
|
89
|
-
// Получить путь к файлу
|
|
90
|
-
const fileInfo = await fetchJson(`${API_URL}/getFile?file_id=${sticker.file_id}`)
|
|
91
|
-
|
|
92
|
-
if (!fileInfo.ok) {
|
|
93
|
-
console.log('❌ Не удалось получить файл:', fileInfo)
|
|
94
|
-
return
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Создать папку assets
|
|
98
|
-
const assetsDir = './assets'
|
|
99
|
-
if (!fs.existsSync(assetsDir)) {
|
|
100
|
-
fs.mkdirSync(assetsDir)
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Скачать файл
|
|
104
|
-
const ext = path.extname(fileInfo.result.file_path) || '.webp'
|
|
105
|
-
const destPath = `${assetsDir}/emoji${ext}`
|
|
106
|
-
|
|
107
|
-
console.log(`💾 Скачиваю в ${destPath}...`)
|
|
108
|
-
await downloadFile(fileInfo.result.file_path, destPath)
|
|
109
|
-
|
|
110
|
-
console.log(`✅ Готово! Файл сохранён: ${destPath}`)
|
|
111
|
-
console.log(`\n📝 Для README используй:`)
|
|
112
|
-
console.log(`<img src="${destPath}" width="20" height="20">`)
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
main().catch(console.error)
|