Skip to content

Commit 92c5a9f

Browse files
committed
feat(bot): add /shutdown command with optional shutdown script in config
1 parent ed678c3 commit 92c5a9f

4 files changed

Lines changed: 75 additions & 44 deletions

File tree

bot/config.json.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
}
2727
},
2828
"default_model": "qwen3.6-claude",
29+
"shutdown": "/home/grishberg/shutdown-pc.sh",
2930
"mcp_servers": {
3031
"ya-disk-uploader": {
3132
"type": "local",

bot/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ def load_config(config_path: str = "config.json") -> dict:
7979
SUBAGENT_PREFIX = CONFIG.get("subagent_prefix", "[subagent] ")
8080
OPENCODE_APP_NAME = CONFIG.get("opencode-app-name")
8181
ALLOWED_FOLDERS = CONFIG.get("allowed_folders", ["/tmp"])
82+
SHUTDOWN_SCRIPT = CONFIG.get("shutdown")
8283

8384
if not VK_TOKEN:
8485
raise ValueError("VK_TOKEN is required in config file")
@@ -156,6 +157,7 @@ def switch_config(config_name: str) -> bool:
156157
current_module.SUBAGENT_PREFIX = new_config.get("subagent_prefix", "[subagent] ")
157158
current_module.OPENCODE_APP_NAME = new_config.get("opencode-app-name")
158159
current_module.ALLOWED_FOLDERS = new_config.get("allowed_folders", ["/tmp"])
160+
current_module.SHUTDOWN_SCRIPT = new_config.get("shutdown")
159161
current_module.OPENCODE_BIN = Path(new_config["opencode_bin_path"])
160162
if not current_module.OPENCODE_BIN.is_absolute():
161163
current_module.OPENCODE_BIN = (SCRIPT_DIR / current_module.OPENCODE_BIN).resolve()

bot/vk_keyboards.py

Lines changed: 54 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -13,52 +13,62 @@ def get_main_keyboard() -> dict:
1313
# Если sysmon настроен - показываем /sysmon вместо /status
1414
status_button_label = "/sysmon" if config.CONFIG.get("sysmon", "").strip() else "/status"
1515

16+
buttons = [
17+
[
18+
{
19+
"action": {"type": "text", "label": "/help"},
20+
"color": "primary",
21+
},
22+
{
23+
"action": {"type": "text", "label": "/gpu"},
24+
"color": "primary",
25+
},
26+
{
27+
"action": {"type": "text", "label": status_button_label},
28+
"color": "primary",
29+
},
30+
],
31+
[
32+
{
33+
"action": {"type": "text", "label": "/logs"},
34+
"color": "secondary",
35+
},
36+
{
37+
"action": {"type": "text", "label": "/history"},
38+
"color": "secondary",
39+
},
40+
{
41+
"action": {"type": "text", "label": "/sessions"},
42+
"color": "secondary",
43+
},
44+
],
45+
[
46+
{
47+
"action": {"type": "text", "label": "/newsession"},
48+
"color": "positive",
49+
},
50+
{
51+
"action": {"type": "text", "label": "/models"},
52+
"color": "secondary",
53+
},
54+
{
55+
"action": {"type": "text", "label": "/test-llama"},
56+
"color": "positive",
57+
},
58+
],
59+
]
60+
61+
if config.CONFIG.get("shutdown"):
62+
buttons.append([
63+
{
64+
"action": {"type": "text", "label": "/shutdown"},
65+
"color": "negative",
66+
},
67+
])
68+
1669
return {
1770
"inline": False,
18-
"buttons": [
19-
[
20-
{
21-
"action": {"type": "text", "label": "/help"},
22-
"color": "primary",
23-
},
24-
{
25-
"action": {"type": "text", "label": "/gpu"},
26-
"color": "primary",
27-
},
28-
{
29-
"action": {"type": "text", "label": status_button_label},
30-
"color": "primary",
31-
},
32-
],
33-
[
34-
{
35-
"action": {"type": "text", "label": "/logs"},
36-
"color": "secondary",
37-
},
38-
{
39-
"action": {"type": "text", "label": "/history"},
40-
"color": "secondary",
41-
},
42-
{
43-
"action": {"type": "text", "label": "/sessions"},
44-
"color": "secondary",
45-
},
46-
],
47-
[
48-
{
49-
"action": {"type": "text", "label": "/newsession"},
50-
"color": "positive",
51-
},
52-
{
53-
"action": {"type": "text", "label": "/models"},
54-
"color": "secondary",
55-
},
56-
{
57-
"action": {"type": "text", "label": "/test-llama"},
58-
"color": "positive",
59-
},
60-
],
61-
],
71+
"buttons": buttons,
6272
}
6373

6474

bot/vk_longpoll.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
LONGPOLL_WAIT,
2323
OPENCODE_URL,
2424
SCRIPT_DIR,
25+
SHUTDOWN_SCRIPT,
2526
SUBAGENT_PREFIX,
2627
THINKING_PEER_ID,
2728
getCwd,
@@ -676,6 +677,9 @@ async def _handle_message_new(self, event: list):
676677
if cmd == "/sysmon":
677678
await self._handle_sysmon_command(user_id)
678679
return
680+
681+
if cmd == "/shutdown":
682+
await self._handle_shutdown_command(user_id)
679683
return
680684

681685
if cmd == "/clean_attaches":
@@ -1080,6 +1084,20 @@ async def _handle_gpu_command(self, user_id: int):
10801084
else:
10811085
await self.vk.send_message(user_id, error)
10821086

1087+
async def _handle_shutdown_command(self, user_id: int):
1088+
"""Обрабатывает команду /shutdown"""
1089+
if not SHUTDOWN_SCRIPT:
1090+
await self.vk.send_message(user_id, "❌ shutdown не определен в конфиге")
1091+
return
1092+
1093+
await self.vk.send_message(user_id, "shutdown pc...")
1094+
process = await asyncio.create_subprocess_exec(
1095+
"sudo", SHUTDOWN_SCRIPT,
1096+
stdout=asyncio.subprocess.PIPE,
1097+
stderr=asyncio.subprocess.PIPE,
1098+
)
1099+
await asyncio.wait_for(process.communicate(), timeout=30)
1100+
10831101
async def _handle_sysmon_command(self, user_id: int):
10841102
"""Обрабатывает команду /sysmon - показывает статус системы через sysmon сервер"""
10851103
import config

0 commit comments

Comments
 (0)