stella-coder 5.3.1 → 5.3.3

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.
Files changed (52) hide show
  1. package/COMMANDS.md +47 -0
  2. package/install-av.bat +17 -0
  3. package/install-stella-pkg.bat +21 -0
  4. package/oneline-av.ps1 +7 -0
  5. package/oneline-stella.ps1 +2 -0
  6. package/package.json +1 -1
  7. package/releases/stella-antivirus/database.mjs +871 -0
  8. package/releases/stella-antivirus/index.mjs +8 -0
  9. package/releases/stella-antivirus/install-av.bat +12 -0
  10. package/releases/stella-antivirus/scanner.mjs +591 -0
  11. package/releases/stella-antivirus/ui.mjs +570 -0
  12. package/releases/stella-antivirus.zip +0 -0
  13. package/releases/stella-coder/README.md +67 -0
  14. package/releases/stella-coder/adb.mjs +200 -0
  15. package/releases/stella-coder/autonomous-agent.mjs +550 -0
  16. package/releases/stella-coder/banner.mjs +46 -0
  17. package/releases/stella-coder/browser-control.mjs +274 -0
  18. package/releases/stella-coder/build.mjs +151 -0
  19. package/releases/stella-coder/charts.mjs +411 -0
  20. package/releases/stella-coder/coding-brain.mjs +753 -0
  21. package/releases/stella-coder/game-engine.mjs +708 -0
  22. package/releases/stella-coder/gdrive-backup.mjs +338 -0
  23. package/releases/stella-coder/git-api.mjs +407 -0
  24. package/releases/stella-coder/gmail.mjs +415 -0
  25. package/releases/stella-coder/home-assistant.mjs +168 -0
  26. package/releases/stella-coder/index.mjs +5810 -0
  27. package/releases/stella-coder/install-stella.bat +12 -0
  28. package/releases/stella-coder/markdown.mjs +100 -0
  29. package/releases/stella-coder/mcp.mjs +296 -0
  30. package/releases/stella-coder/package.json +67 -0
  31. package/releases/stella-coder/presentations.mjs +1106 -0
  32. package/releases/stella-coder/protect.mjs +182 -0
  33. package/releases/stella-coder/screen-monitor.mjs +334 -0
  34. package/releases/stella-coder/sea-config.json +5 -0
  35. package/releases/stella-coder/security.mjs +237 -0
  36. package/releases/stella-coder/subagents.mjs +142 -0
  37. package/releases/stella-coder/telegram-bot.mjs +824 -0
  38. package/releases/stella-coder/tg-server.mjs +116 -0
  39. package/releases/stella-coder/theme.mjs +91 -0
  40. package/releases/stella-coder/tools.mjs +3143 -0
  41. package/releases/stella-coder/web-parser.mjs +229 -0
  42. package/releases/stella-coder/yandex-maps.mjs +426 -0
  43. package/releases/stella-coder.zip +0 -0
  44. package/run-antivirus.bat +4 -0
  45. package/setup-antivirus.bat +64 -0
  46. package/setup-full.bat +65 -0
  47. package/setup-full.ps1 +84 -0
  48. package/setup-stella.bat +46 -0
  49. package/stella-cli/index.mjs +12 -2
  50. package/stella-cli/protect.mjs +182 -0
  51. package/stella.bat +3 -0
  52. package/tests/test-modules.mjs +101 -0
@@ -0,0 +1,824 @@
1
+ import fs from "node:fs"
2
+ import path from "node:path"
3
+ import os from "node:os"
4
+ import crypto from "node:crypto"
5
+ import { execSync } from "node:child_process"
6
+
7
+ // ═══════════════════════════════════════════════════════════════════
8
+ // STELLA TELEGRAM BOT — удалённое управление + сессии + premium
9
+ // ═══════════════════════════════════════════════════════════════════
10
+
11
+ const BOT_TOKEN = "8923551485:AAFw4wG8ZwOtp5rzFsnguxhu4AH-2_ebSi0"
12
+ const API_URL = `https://api.telegram.org/bot${BOT_TOKEN}`
13
+ const PREMIUM_CODE = "10102013"
14
+ const PREMIUM_DURATION = 30 * 24 * 60 * 60 * 1000 // 30 дней
15
+
16
+ const CONFIG_DIR = path.join(os.homedir(), ".stella")
17
+ const BOT_CONFIG_PATH = path.join(CONFIG_DIR, "telegram-bot.json")
18
+ const SESSIONS_PATH = path.join(CONFIG_DIR, "sessions.json")
19
+ const AUTH_PATH = path.join(CONFIG_DIR, "telegram-auth.json")
20
+
21
+ // ═══════════════════════════════════════════════════════════════════
22
+ // 1. TELEGRAM API — прямые вызовы без зависимостей
23
+ // ═══════════════════════════════════════════════════════════════════
24
+
25
+ async function tg(method, body = {}) {
26
+ try {
27
+ const res = await fetch(`${API_URL}/${method}`, {
28
+ method: "POST",
29
+ headers: { "Content-Type": "application/json" },
30
+ body: JSON.stringify(body),
31
+ signal: AbortSignal.timeout(30000),
32
+ })
33
+ const data = await res.json()
34
+ if (!data.ok) console.error(`[TG] ${method} error:`, data.description)
35
+ return data
36
+ } catch (e) {
37
+ console.error(`[TG] ${method} fetch error:`, e.message)
38
+ return { ok: false, error: e.message }
39
+ }
40
+ }
41
+
42
+ async function sendMessage(chatId, text, options = {}) {
43
+ return tg("sendMessage", {
44
+ chat_id: chatId,
45
+ text,
46
+ parse_mode: "HTML",
47
+ ...options,
48
+ })
49
+ }
50
+
51
+ async function editMessage(chatId, messageId, text, options = {}) {
52
+ return tg("editMessageText", {
53
+ chat_id: chatId,
54
+ message_id: messageId,
55
+ text,
56
+ parse_mode: "HTML",
57
+ ...options,
58
+ })
59
+ }
60
+
61
+ async function answerCallback(callbackQueryId, text = "") {
62
+ return tg("answerCallbackQuery", { callback_query_id: callbackQueryId, text })
63
+ }
64
+
65
+ // ═══════════════════════════════════════════════════════════════════
66
+ // 2. HARDWARE-BOUND AUTH — привязка к железу
67
+ // ═══════════════════════════════════════════════════════════════════
68
+
69
+ function getHardwareFingerprint() {
70
+ const components = [
71
+ os.hostname(),
72
+ os.platform(),
73
+ os.arch(),
74
+ os.cpus()[0]?.model || "unknown",
75
+ os.totalmem().toString(),
76
+ ]
77
+
78
+ // Try to get MAC address
79
+ try {
80
+ const netInterfaces = os.networkInterfaces()
81
+ for (const name of Object.keys(netInterfaces)) {
82
+ for (const iface of netInterfaces[name]) {
83
+ if (!iface.internal && iface.mac && iface.mac !== "00:00:00:00:00:00") {
84
+ components.push(iface.mac)
85
+ break
86
+ }
87
+ }
88
+ }
89
+ } catch {}
90
+
91
+ // Try to get disk serial (using PowerShell instead of wmic)
92
+ try {
93
+ if (os.platform() === "win32") {
94
+ const diskInfo = execSync('powershell -Command "Get-PhysicalDisk | Select-Object -ExpandProperty SerialNumber"', { encoding: "utf8", timeout: 5000 })
95
+ const serial = diskInfo.trim().split("\n")[0]?.trim()
96
+ if (serial && serial !== "") components.push(serial)
97
+ }
98
+ } catch {}
99
+
100
+ return crypto.createHash("sha256").update(components.join("|")).digest("hex").slice(0, 32)
101
+ }
102
+
103
+ function generateAuthCode(telegramId) {
104
+ // Random 4-разрядный код для каждого пользователя
105
+ const code = String(Math.floor(1000 + Math.random() * 9000))
106
+ return code
107
+ }
108
+
109
+ // ═══════════════════════════════════════════════════════════════════
110
+ // 10. ADMIN CODE — каждый Stella генерирует свой код на своём ПК
111
+ // ═══════════════════════════════════════════════════════════════════
112
+
113
+ export function generateAdminCode() {
114
+ const code = String(Math.floor(1000 + Math.random() * 9000))
115
+ const auth = loadAuth()
116
+
117
+ // Store admin code with hardware fingerprint of THIS computer
118
+ auth.adminCode = {
119
+ code,
120
+ hardwareId: getHardwareFingerprint(),
121
+ hostname: os.hostname(),
122
+ createdAt: Date.now(),
123
+ used: false,
124
+ }
125
+ saveAuth(auth)
126
+
127
+ return code
128
+ }
129
+
130
+ export function verifyAdminCode(code, telegramId, username, firstName, chatId) {
131
+ const auth = loadAuth()
132
+
133
+ if (!auth.adminCode) {
134
+ return { success: false, error: "Код не сгенерирован. Запустите /tg в Stella на вашем компьютере." }
135
+ }
136
+
137
+ if (auth.adminCode.used) {
138
+ return { success: false, error: "Код уже использован. Сгенерируйте новый: /tg-code" }
139
+ }
140
+
141
+ if (auth.adminCode.code !== code) {
142
+ return { success: false, error: "Неверный код." }
143
+ }
144
+
145
+ // Link user to THIS computer
146
+ auth.users[telegramId] = {
147
+ username,
148
+ firstName,
149
+ chatId,
150
+ hardwareId: auth.adminCode.hardwareId,
151
+ hostname: auth.adminCode.hostname,
152
+ authenticatedAt: Date.now(),
153
+ role: "user",
154
+ }
155
+
156
+ // Mark code as used
157
+ auth.adminCode.used = true
158
+ auth.adminCode.usedBy = telegramId
159
+ auth.adminCode.usedAt = Date.now()
160
+
161
+ saveAuth(auth)
162
+
163
+ return {
164
+ success: true,
165
+ userId: telegramId,
166
+ username,
167
+ firstName,
168
+ hostname: auth.adminCode.hostname,
169
+ }
170
+ }
171
+
172
+ export function listAuthorizedUsers() {
173
+ const auth = loadAuth()
174
+ return Object.entries(auth.users).map(([id, u]) => ({
175
+ id,
176
+ username: u.username,
177
+ firstName: u.firstName,
178
+ hostname: u.hostname,
179
+ authenticatedAt: new Date(u.authenticatedAt).toLocaleString("ru-RU"),
180
+ }))
181
+ }
182
+
183
+ function loadAuth() {
184
+ try {
185
+ return JSON.parse(fs.readFileSync(AUTH_PATH, "utf8"))
186
+ } catch {
187
+ return { users: {}, pendingCodes: {} }
188
+ }
189
+ }
190
+
191
+ function saveAuth(auth) {
192
+ fs.mkdirSync(CONFIG_DIR, { recursive: true })
193
+ fs.writeFileSync(AUTH_PATH, JSON.stringify(auth, null, 2))
194
+ }
195
+
196
+ // ═══════════════════════════════════════════════════════════════════
197
+ // 3. SESSION TRACKER — отслеживание сессий
198
+ // ═══════════════════════════════════════════════════════════════════
199
+
200
+ function loadSessions() {
201
+ try {
202
+ return JSON.parse(fs.readFileSync(SESSIONS_PATH, "utf8"))
203
+ } catch {
204
+ return { sessions: [], activeSession: null }
205
+ }
206
+ }
207
+
208
+ function saveSessions(data) {
209
+ fs.mkdirSync(CONFIG_DIR, { recursive: true })
210
+ fs.writeFileSync(SESSIONS_PATH, JSON.stringify(data, null, 2))
211
+ }
212
+
213
+ function createSession(name, model = "MiMo V2.5 Free") {
214
+ const data = loadSessions()
215
+ const session = {
216
+ id: `session-${Date.now()}`,
217
+ name,
218
+ model,
219
+ provider: "OpenCode Zen",
220
+ contextLimit: "200K",
221
+ createdAt: new Date().toISOString(),
222
+ lastActivity: new Date().toISOString(),
223
+ stats: {
224
+ totalTokens: 0,
225
+ inputTokens: 0,
226
+ outputTokens: 0,
227
+ cacheRead: 0,
228
+ cacheWrite: 0,
229
+ reasoningTokens: 0,
230
+ userMessages: 0,
231
+ assistantMessages: 0,
232
+ toolCalls: 0,
233
+ cost: 0,
234
+ contextBreakdown: {
235
+ user: 0,
236
+ assistant: 0,
237
+ tools: 0,
238
+ other: 0,
239
+ },
240
+ },
241
+ }
242
+ data.sessions.push(session)
243
+ data.activeSession = session.id
244
+ saveSessions(data)
245
+ return session
246
+ }
247
+
248
+ function updateSessionStats(sessionId, updates) {
249
+ const data = loadSessions()
250
+ const session = data.sessions.find(s => s.id === sessionId)
251
+ if (!session) return null
252
+
253
+ Object.assign(session.stats, updates)
254
+ session.lastActivity = new Date().toISOString()
255
+ saveSessions(data)
256
+ return session
257
+ }
258
+
259
+ function getActiveSession() {
260
+ const data = loadSessions()
261
+ return data.sessions.find(s => s.id === data.activeSession) || null
262
+ }
263
+
264
+ // ═══════════════════════════════════════════════════════════════════
265
+ // 4. PREMIUM SYSTEM — подписка
266
+ // ═══════════════════════════════════════════════════════════════════
267
+
268
+ function loadPremium() {
269
+ try {
270
+ return JSON.parse(fs.readFileSync(path.join(CONFIG_DIR, "premium.json"), "utf8"))
271
+ } catch {
272
+ return { users: {} }
273
+ }
274
+ }
275
+
276
+ function savePremium(data) {
277
+ fs.mkdirSync(CONFIG_DIR, { recursive: true })
278
+ fs.writeFileSync(path.join(CONFIG_DIR, "premium.json"), JSON.stringify(data, null, 2))
279
+ }
280
+
281
+ function isPremium(telegramId) {
282
+ const data = loadPremium()
283
+ const user = data.users[telegramId]
284
+ if (!user) return false
285
+ return Date.now() < user.expiresAt
286
+ }
287
+
288
+ function activatePremium(telegramId, duration = PREMIUM_DURATION) {
289
+ const data = loadPremium()
290
+ data.users[telegramId] = {
291
+ activatedAt: Date.now(),
292
+ expiresAt: Date.now() + duration,
293
+ }
294
+ savePremium(data)
295
+ }
296
+
297
+ function getPremiumExpiry(telegramId) {
298
+ const data = loadPremium()
299
+ const user = data.users[telegramId]
300
+ if (!user) return null
301
+ return new Date(user.expiresAt).toLocaleDateString("ru-RU")
302
+ }
303
+
304
+ // ═══════════════════════════════════════════════════════════════════
305
+ // 5. BOT HANDLERS — обработка команд
306
+ // ═══════════════════════════════════════════════════════════════════
307
+
308
+ const userStates = new Map()
309
+
310
+ function getMainMenu(telegramId) {
311
+ const premium = isPremium(telegramId)
312
+ return {
313
+ text: `<b>Stella Coder Bot</b>
314
+
315
+ Добро пожаловать! ${premium ? "⭐ Premium" : ""}
316
+
317
+ <b>Команды:</b>
318
+ /start — Начать
319
+ /sessions — Мои сессии
320
+ /new — Новая сессия
321
+ /status — Статус сессии
322
+ /premium — Premium функции
323
+ /help — Помощь
324
+
325
+ <b>Удалённое управление:</b>
326
+ Просто напишите команду, и Stella выполнит её на компьютере.
327
+ Пример: "выключи телевизор", "сделай скриншот"`,
328
+ reply_markup: {
329
+ inline_keyboard: [
330
+ [{ text: "📋 Сессии", callback_data: "sessions" }, { text: "➕ Новая", callback_data: "new_session" }],
331
+ [{ text: "📊 Статус", callback_data: "status" }, { text: "⭐ Premium", callback_data: "premium" }],
332
+ [{ text: "❓ Помощь", callback_data: "help" }],
333
+ ],
334
+ },
335
+ }
336
+ }
337
+
338
+ function getSessionsList(telegramId) {
339
+ const data = loadSessions()
340
+ if (data.sessions.length === 0) {
341
+ return {
342
+ text: "📋 <b>Нет сохранённых сессий</b>\n\nСоздайте новую сессию кнопкой ниже.",
343
+ reply_markup: {
344
+ inline_keyboard: [[{ text: "➕ Создать сессию", callback_data: "new_session" }], [{ text: "◀️ Назад", callback_data: "main_menu" }]],
345
+ },
346
+ }
347
+ }
348
+
349
+ const lines = data.sessions.map((s, i) => {
350
+ const isActive = s.id === data.activeSession
351
+ const date = new Date(s.createdAt).toLocaleDateString("ru-RU")
352
+ const msgs = s.stats.userMessages + s.stats.assistantMessages
353
+ return `${isActive ? "🟢" : "⚪"} <b>${i + 1}. ${s.name}</b>\n ${s.model} · ${msgs} сообщений · ${date}`
354
+ })
355
+
356
+ return {
357
+ text: `<b>📋 Ваши сессии:</b>\n\n${lines.join("\n\n")}`,
358
+ reply_markup: {
359
+ inline_keyboard: [
360
+ ...data.sessions.map((s, i) => [
361
+ { text: `${s.id === data.activeSession ? "🟢" : "⚪"} ${s.name}`, callback_data: `session_${i}` },
362
+ ]),
363
+ [{ text: "➕ Новая сессия", callback_data: "new_session" }],
364
+ [{ text: "◀️ Назад", callback_data: "main_menu" }],
365
+ ],
366
+ },
367
+ }
368
+ }
369
+
370
+ function getSessionStats(session) {
371
+ const stats = session.stats
372
+ const ctxBreakdown = stats.contextBreakdown
373
+ const totalCtx = ctxBreakdown.user + ctxBreakdown.assistant + ctxBreakdown.tools + ctxBreakdown.other
374
+
375
+ const userPct = totalCtx > 0 ? ((ctxBreakdown.user / totalCtx) * 100).toFixed(1) : "0"
376
+ const asstPct = totalCtx > 0 ? ((ctxBreakdown.assistant / totalCtx) * 100).toFixed(1) : "0"
377
+ const toolsPct = totalCtx > 0 ? ((ctxBreakdown.tools / totalCtx) * 100).toFixed(1) : "0"
378
+ const otherPct = totalCtx > 0 ? ((ctxBreakdown.other / totalCtx) * 100).toFixed(1) : "0"
379
+
380
+ const usage = session.contextLimit ? Math.min(100, Math.round((stats.totalTokens / 200000) * 100)) : 0
381
+
382
+ return `<b>📊 Сессия: ${session.name}</b>
383
+
384
+ <b>Провайдер:</b> ${session.provider}
385
+ <b>Лимит контекста:</b> ${session.contextLimit}
386
+ <b>Использование:</b> ${usage}%
387
+ <b>Модель:</b> ${session.model}
388
+
389
+ <b>Статистика:</b>
390
+ ├ Входные токены: ${stats.inputTokens.toLocaleString()}
391
+ ├ Выходные токены: ${stats.outputTokens.toLocaleString()}
392
+ ├ Токены кэша: ${stats.cacheRead.toLocaleString()}/${stats.cacheWrite.toLocaleString()}
393
+ ├ Токены рассуждения: ${stats.reasoningTokens.toLocaleString()}
394
+ └ Всего токенов: ${stats.totalTokens.toLocaleString()}
395
+
396
+ <b>Сообщения:</b>
397
+ ├ Пользователя: ${stats.userMessages}
398
+ ├ Ассистента: ${stats.assistantMessages}
399
+ ├ Всего: ${stats.userMessages + stats.assistantMessages}
400
+ └ Вызовы инструментов: ${stats.toolCalls}
401
+
402
+ <b>Разбивка контекста:</b>
403
+ ├ 🟢 Пользователь ${userPct}%
404
+ ├ 🟣 Ассистент ${asstPct}%
405
+ ├ 🟤 Вызовы инструментов ${toolsPct}%
406
+ └ ⚪ Другое ${otherPct}%
407
+
408
+ <b>Стоимость:</b> $${stats.cost.toFixed(2)}
409
+ <b>Создана:</b> ${new Date(session.createdAt).toLocaleString("ru-RU")}
410
+ <b>Последняя активность:</b> ${new Date(session.lastActivity).toLocaleString("ru-RU")}`
411
+ }
412
+
413
+ // ═══════════════════════════════════════════════════════════════════
414
+ // 6. WEBHOOK / POLLING — запуск бота
415
+ // ═══════════════════════════════════════════════════════════════════
416
+
417
+ let pollingOffset = 0
418
+ let botRunning = false
419
+
420
+ export async function startBot() {
421
+ if (botRunning) {
422
+ console.log("[TG Bot] Already running")
423
+ return
424
+ }
425
+
426
+ // Check bot token
427
+ const me = await tg("getMe")
428
+ if (!me.ok) {
429
+ console.error("[TG Bot] Failed to start:", me.description || "Invalid token")
430
+ return false
431
+ }
432
+
433
+ botRunning = true
434
+ console.log(`[TG Bot] Started: @${me.result.username} (${me.result.first_name})`)
435
+
436
+ // Start polling
437
+ pollUpdates()
438
+ return true
439
+ }
440
+
441
+ export async function stopBot() {
442
+ botRunning = false
443
+ console.log("[TG Bot] Stopped")
444
+ }
445
+
446
+ async function pollUpdates() {
447
+ while (botRunning) {
448
+ try {
449
+ const res = await tg("getUpdates", {
450
+ offset: pollingOffset,
451
+ timeout: 30,
452
+ allowed_updates: ["message", "callback_query"],
453
+ })
454
+
455
+ if (res.ok && res.result) {
456
+ for (const update of res.result) {
457
+ pollingOffset = update.update_id + 1
458
+ await handleUpdate(update)
459
+ }
460
+ }
461
+ } catch (e) {
462
+ if (botRunning) {
463
+ console.error("[TG Bot] Poll error:", e.message)
464
+ await sleep(5000)
465
+ }
466
+ }
467
+ }
468
+ }
469
+
470
+ function sleep(ms) {
471
+ return new Promise(r => setTimeout(r, ms))
472
+ }
473
+
474
+ async function handleUpdate(update) {
475
+ if (update.message) {
476
+ await handleMessage(update.message)
477
+ } else if (update.callback_query) {
478
+ await handleCallback(update.callback_query)
479
+ }
480
+ }
481
+
482
+ async function handleMessage(msg) {
483
+ const chatId = msg.chat.id
484
+ const userId = msg.from?.id
485
+ const text = msg.text || ""
486
+
487
+ // /start or /menu
488
+ if (text === "/start" || text === "/menu") {
489
+ const auth = loadAuth()
490
+
491
+ // Check if already authenticated
492
+ if (auth.users[userId]) {
493
+ await sendMessage(chatId, getMainMenu(userId).text, getMainMenu(userId))
494
+ return
495
+ }
496
+
497
+ // Not authenticated - check for admin code
498
+ if (text === "/start") {
499
+ // Check if there's an active admin code
500
+ if (auth.adminCode && !auth.adminCode.used) {
501
+ await sendMessage(chatId, `<b>Добро пожаловать, ${msg.from.first_name}!</b>
502
+
503
+ Для доступа к Stella введите код, который вам дали.
504
+
505
+ <b>Введите код:</b>`, {
506
+ reply_markup: {
507
+ inline_keyboard: [[{ text: "◀️ Отмена", callback_data: "cancel_auth" }]],
508
+ },
509
+ })
510
+ userStates.set(userId, { awaiting: "admin_code" })
511
+ } else {
512
+ await sendMessage(chatId, `<b>Добро пожаловать, ${msg.from.first_name}!</b>
513
+
514
+ Для получения доступа к Stella:
515
+ 1. Купите доступ у администратора
516
+ 2. Получите код
517
+ 3. Введите его здесь
518
+
519
+ <b>Нет кода?</b> Свяжитесь с администратором.`, {
520
+ reply_markup: {
521
+ inline_keyboard: [[{ text: "◀️ Назад", callback_data: "main_menu" }]],
522
+ },
523
+ })
524
+ }
525
+ }
526
+ return
527
+ }
528
+
529
+ // Check user state for admin code input
530
+ const userState = userStates.get(userId)
531
+ if (userState?.awaiting === "admin_code") {
532
+ userStates.delete(userId)
533
+
534
+ const auth = loadAuth()
535
+ const result = verifyAdminCode(
536
+ text.trim(),
537
+ userId,
538
+ msg.from.username || msg.from.first_name,
539
+ msg.from.first_name,
540
+ chatId
541
+ )
542
+
543
+ if (result.success) {
544
+ await sendMessage(chatId, `✅ <b>Доступ получен!</b>
545
+
546
+ Добро пожаловать, ${result.firstName}!
547
+
548
+ Теперь вы можете:
549
+ • Управлять компьютером через Telegram
550
+ • Получать уведомления
551
+ • Просматривать сессии
552
+
553
+ Нажмите /menu для главного меню.`, getMainMenu(userId))
554
+ } else {
555
+ await sendMessage(chatId, `❌ <b>${result.error}</b>
556
+
557
+ Попробуйте ещё раз или нажмите /start.`)
558
+ }
559
+ return
560
+ }
561
+
562
+ // Check if user is authenticated
563
+ if (!auth.users[userId]) {
564
+ await sendMessage(chatId, "❌ Вы не авторизованы. Нажмите /start для начала.")
565
+ return
566
+ }
567
+
568
+ // Authenticated user — process as Stella command
569
+ const session = getActiveSession()
570
+ if (session) {
571
+ // Update session stats
572
+ updateSessionStats(session.id, {
573
+ userMessages: session.stats.userMessages + 1,
574
+ totalTokens: session.stats.totalTokens + Math.floor(text.length / 4),
575
+ inputTokens: session.stats.inputTokens + Math.floor(text.length / 4),
576
+ })
577
+ }
578
+
579
+ // Send "processing" message
580
+ const processingMsg = await sendMessage(chatId, `⚙️ <b>Выполняю:</b> ${text.slice(0, 100)}...`)
581
+
582
+ // Execute command via Stella
583
+ try {
584
+ const result = execSync(`node "${path.join(process.cwd(), "stella-cli", "index.mjs")}" -p "${text.replace(/"/g, '\\"')}"`, {
585
+ encoding: "utf8",
586
+ timeout: 120000,
587
+ cwd: process.cwd(),
588
+ })
589
+
590
+ // Update stats
591
+ if (session) {
592
+ updateSessionStats(session.id, {
593
+ assistantMessages: session.stats.assistantMessages + 1,
594
+ outputTokens: session.stats.outputTokens + Math.floor(result.length / 4),
595
+ totalTokens: session.stats.totalTokens + Math.floor(result.length / 4),
596
+ })
597
+ }
598
+
599
+ // Send result
600
+ const output = result.trim().slice(0, 4000) // Telegram limit
601
+ await editMessage(chatId, processingMsg.result.message_id, `<b>✅ Результат:</b>\n\n<pre>${escapeHtml(output)}</pre>`)
602
+ } catch (e) {
603
+ await editMessage(chatId, processingMsg.result.message_id, `<b>❌ Ошибка:</b>\n<pre>${escapeHtml(String(e.message).slice(0, 1000))}</pre>`)
604
+ }
605
+ }
606
+
607
+ function escapeHtml(text) {
608
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
609
+ }
610
+
611
+ async function handleCallback(callback) {
612
+ const chatId = callback.message.chat.id
613
+ const userId = callback.from.id
614
+ const data = callback.data
615
+
616
+ await answerCallback(callback.id)
617
+
618
+ // Handle cancel_auth
619
+ if (data === "cancel_auth") {
620
+ const auth = loadAuth()
621
+ delete auth.pendingCodes[userId]
622
+ saveAuth(auth)
623
+ await editMessage(chatId, callback.message.message_id, "❌ Авторизация отменена.\n\nНажмите /start для начала.")
624
+ return
625
+ }
626
+
627
+ // Check auth
628
+ const auth = loadAuth()
629
+ if (!auth.users[userId]) {
630
+ await sendMessage(chatId, "❌ Нажмите /start для авторизации.")
631
+ return
632
+ }
633
+
634
+ switch (data) {
635
+ case "main_menu":
636
+ await editMessage(chatId, callback.message.message_id, getMainMenu(userId).text, getMainMenu(userId))
637
+ break
638
+
639
+ case "sessions":
640
+ await editMessage(chatId, callback.message.message_id, getSessionsList(userId).text, getSessionsList(userId))
641
+ break
642
+
643
+ case "new_session": {
644
+ userStates.set(userId, { awaiting: "session_name" })
645
+ await sendMessage(chatId, "📝 Введите название новой сессии:", {
646
+ reply_markup: { inline_keyboard: [[{ text: "◀️ Отмена", callback_data: "main_menu" }]] },
647
+ })
648
+ break
649
+ }
650
+
651
+ case "status": {
652
+ const session = getActiveSession()
653
+ if (!session) {
654
+ await sendMessage(chatId, "❌ Нет активной сессии. Создайте новую.", {
655
+ reply_markup: { inline_keyboard: [[{ text: "➕ Создать", callback_data: "new_session" }]] },
656
+ })
657
+ return
658
+ }
659
+ await sendMessage(chatId, getSessionStats(session), {
660
+ reply_markup: {
661
+ inline_keyboard: [
662
+ [{ text: "🔄 Обновить", callback_data: "status" }],
663
+ [{ text: "◀️ Назад", callback_data: "main_menu" }],
664
+ ],
665
+ },
666
+ })
667
+ break
668
+ }
669
+
670
+ case "premium": {
671
+ const premium = isPremium(userId)
672
+ const expiry = getPremiumExpiry(userId)
673
+
674
+ let text = ""
675
+ if (premium) {
676
+ text = `<b>⭐ Premium активен</b>\n\nДействует до: ${expiry}\n\nДоступные функции:\n• Удалённое управление компьютером\n• Выполнение команд\n• Мониторинг сессий\n• Приоритетная поддержка`
677
+ } else {
678
+ text = `<b>⭐ Premium</b>\n\nФункции Premium стоят 50 звёзд Telegram.\n\nНо вы можете получить бесплатно, зная код!\n\nВведите код: <code>${PREMIUM_CODE}</code>\n(активация на 1 месяц)`
679
+ }
680
+
681
+ await sendMessage(chatId, text, {
682
+ reply_markup: {
683
+ inline_keyboard: premium
684
+ ? [[{ text: "◀️ Назад", callback_data: "main_menu" }]]
685
+ : [
686
+ [{ text: "🔑 Активировать код", callback_data: "activate_premium" }],
687
+ [{ text: "◀️ Назад", callback_data: "main_menu" }],
688
+ ],
689
+ },
690
+ })
691
+ break
692
+ }
693
+
694
+ case "activate_premium": {
695
+ userStates.set(userId, { awaiting: "premium_code" })
696
+ await sendMessage(chatId, "🔑 Введите код активации:", {
697
+ reply_markup: { inline_keyboard: [[{ text: "◀️ Отмена", callback_data: "premium" }]] },
698
+ })
699
+ break
700
+ }
701
+
702
+ case "help":
703
+ await sendMessage(chatId, `<b>Помощь</b>
704
+
705
+ <b>Команды:</b>
706
+ /start — Меню
707
+ /sessions — Мои сессии
708
+ /new — Новая сессия
709
+ /status — Статус сессии
710
+ /premium — Premium функции
711
+
712
+ <b>Удалённое управление:</b>
713
+ Напишите любую команду, и Stella выполнит её на вашем компьютере.
714
+
715
+ <b>Примеры:</b>
716
+ • "выключи телевизор"
717
+ • "сделай скриншот"
718
+ • "открой браузер"
719
+ • "покажи систему"
720
+ • "обнови код"
721
+
722
+ <b>Код активации Premium:</b>
723
+ <code>${PREMIUM_CODE}</code> (1 месяц бесплатно)`, {
724
+ reply_markup: { inline_keyboard: [[{ text: "◀️ Назад", callback_data: "main_menu" }]] },
725
+ })
726
+ break
727
+
728
+ default:
729
+ // Handle session selection
730
+ if (data.startsWith("session_")) {
731
+ const idx = parseInt(data.replace("session_", ""))
732
+ const sessions = loadSessions()
733
+ if (sessions.sessions[idx]) {
734
+ sessions.activeSession = sessions.sessions[idx].id
735
+ saveSessions(sessions)
736
+ await sendMessage(chatId, getSessionStats(sessions.sessions[idx]), {
737
+ reply_markup: {
738
+ inline_keyboard: [
739
+ [{ text: "🔄 Обновить", callback_data: `session_${idx}` }],
740
+ [{ text: "◀️ К сессиям", callback_data: "sessions" }],
741
+ ],
742
+ },
743
+ })
744
+ }
745
+ }
746
+ break
747
+ }
748
+ }
749
+
750
+ // ═══════════════════════════════════════════════════════════════════
751
+ // 7. NOTIFICATIONS — уведомления о выполнении
752
+ // ═══════════════════════════════════════════════════════════════════
753
+
754
+ export async function notifyUser(telegramId, message) {
755
+ const auth = loadAuth()
756
+ if (!auth.users[telegramId]) return false
757
+
758
+ return sendMessage(telegramId, `📬 <b>Уведомление:</b>\n\n${message}`)
759
+ }
760
+
761
+ export async function notifyAll(message) {
762
+ const auth = loadAuth()
763
+ for (const userId of Object.keys(auth.users)) {
764
+ await sendMessage(auth.users[userId].chatId, `📬 <b>Уведомление:</b>\n\n${message}`)
765
+ }
766
+ }
767
+
768
+ // ═══════════════════════════════════════════════════════════════════
769
+ // 8. PUBLIC API — экспорт для Stella CLI
770
+ // ═══════════════════════════════════════════════════════════════════
771
+
772
+ export const TELEGRAM_BRAIN_COMMANDS = {
773
+ "/tg": "запустить Telegram бота",
774
+ "/tg-stop": "остановить Telegram бота",
775
+ "/tg-notify": "отправить уведомление всем",
776
+ "/tg-sessions": "показать сессии в терминале",
777
+ }
778
+
779
+ export function getBotStatus() {
780
+ return {
781
+ running: botRunning,
782
+ token: BOT_TOKEN.slice(0, 10) + "...",
783
+ auth: loadAuth(),
784
+ sessions: loadSessions(),
785
+ premium: loadPremium(),
786
+ }
787
+ }
788
+
789
+ // ═══════════════════════════════════════════════════════════════════
790
+ // 9. CLI AUTH VERIFICATION — проверка кода из терминала
791
+ // ═══════════════════════════════════════════════════════════════════
792
+
793
+ export function verifyAuthCode(code) {
794
+ const auth = loadAuth()
795
+
796
+ // Find pending code that matches
797
+ for (const [userId, pending] of Object.entries(auth.pendingCodes)) {
798
+ if (pending.code === code) {
799
+ // Link the user
800
+ auth.users[userId] = {
801
+ username: pending.username,
802
+ chatId: pending.chatId,
803
+ hardwareId: getHardwareFingerprint(),
804
+ authenticatedAt: Date.now(),
805
+ }
806
+ delete auth.pendingCodes[userId]
807
+ saveAuth(auth)
808
+
809
+ return {
810
+ success: true,
811
+ userId,
812
+ username: pending.username,
813
+ chatId: pending.chatId,
814
+ }
815
+ }
816
+ }
817
+
818
+ return { success: false, error: "Код не найден или уже использован" }
819
+ }
820
+
821
+ export function getPendingCodes() {
822
+ const auth = loadAuth()
823
+ return auth.pendingCodes
824
+ }