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 CHANGED
@@ -1,5 +1,6 @@
1
1
  # 🧟 OpenCode Zombie Monitor
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/opencode-zombie-monitor)](https://www.npmjs.com/package/opencode-zombie-monitor)
3
4
  [![GitHub](https://img.shields.io/github/license/Matroskin86/opencode-zombie-monitor)](https://github.com/Matroskin86/opencode-zombie-monitor/blob/main/LICENSE)
4
5
  [![Platform](https://img.shields.io/badge/platform-macOS%20%7C%20Linux-blue)]()
5
6
  [![OpenCode](https://img.shields.io/badge/opencode-plugin-purple)]()
@@ -39,7 +40,7 @@ Add to your `opencode.json`:
39
40
 
40
41
  ```json
41
42
  {
42
- "plugin": ["github:Matroskin86/opencode-zombie-monitor"]
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 opencode processes | Freed ~300MB RAM
57
+ 🧟 Killed 3 zombie processes | Freed 300MB RAM | Headshot! 💥
57
58
  ```
58
59
 
59
- ### Manual Mode
60
+ ### Commands
60
61
 
61
- Want to check the situation yourself?
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
- Response when trouble brewing:
79
+ **Manual kill:**
80
+ ```
81
+ /kill-zombies
82
+ ```
73
83
  ```
74
- 🧟 5 zombies of 7 processes | ~500MB RAM | Fix: oc-kill-zombies
84
+ 💥 Headshot! Killed 5 zombies | Freed 500MB RAM
75
85
  ```
76
86
 
77
87
  ## ⚙️ Configuration
78
88
 
79
- Edit `index.mjs` to adjust aggression level:
89
+ Configure in your `opencode.json`:
80
90
 
81
- ```javascript
82
- // RAMBO MODE: Kill on sight (default)
83
- const AUTO_KILL_THRESHOLD = 1
91
+ ```json
92
+ {
93
+ "plugin": [
94
+ ["opencode-zombie-monitor", {
95
+ "autoKill": true,
96
+ "threshold": 1
97
+ }]
98
+ ]
99
+ }
100
+ ```
84
101
 
85
- // PATIENT MODE: Wait until horde forms
86
- const AUTO_KILL_THRESHOLD = 5
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
- // PACIFIST MODE: Only notify, never kill
89
- const AUTO_KILL_THRESHOLD = 999
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 opencode process${n > 1 ? "es" : ""} | Freed ${formatMB(mb)} RAM`,
28
- found: (n, mb) => `🧟 ${n} zombie opencode process${n > 1 ? "es" : ""} | ${formatMB(mb)} RAM | Fix: oc-kill-zombies`,
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 | Fix: oc-kill-zombies`
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} зомби-процессов opencode | Освобождено ${formatMB(mb)} RAM`,
36
- found: (n, mb) => `🧟 ${n} зомби-процессов opencode | ${formatMB(mb)} RAM | Fix: oc-kill-zombies`,
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 | Fix: oc-kill-zombies`
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} 个僵尸 opencode 进程 | 释放 ${formatMB(mb)} 内存`,
44
- found: (n, mb) => `🧟 发现 ${n} 个僵尸 opencode 进程 | ${formatMB(mb)} 内存 | 修复: oc-kill-zombies`,
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)} 内存 | 修复: oc-kill-zombies`
50
+ ? `🧟 ${total} 个进程中有 ${zombies} 个僵尸 | ${formatMB(mb)} 内存 | /kill-zombies`
47
51
  : `✅ ${total} 个进程,没有僵尸`,
48
- commandDesc: "检查僵尸 opencode 进程"
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
- // Auto-kill threshold (1 = kill immediately)
89
- const AUTO_KILL_THRESHOLD = 1
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 >= AUTO_KILL_THRESHOLD) {
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 (but below threshold)
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
- if (input.command !== "zombies") return
149
-
150
- const { count: zombies, mb } = await getZombieStats()
151
- const total = await getTotalCount()
152
-
153
- await sendNotification(client, input.sessionID, t.status(zombies, total, mb))
154
- throw new Error("__ZOMBIES_HANDLED__")
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.2",
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/jenshen86/opencode-zombie-monitor"
12
+ "url": "https://github.com/Matroskin86/opencode-zombie-monitor"
13
13
  },
14
14
  "peerDependencies": {
15
15
  "@opencode-ai/plugin": ">=1.0.0"
@@ -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)