stella-coder 3.9.2 → 5.0.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.
Files changed (176) hide show
  1. package/.mcp.json +11 -0
  2. package/.opencode/skills/banner-design/SKILL.md +196 -0
  3. package/.opencode/skills/banner-design/references/banner-sizes-and-styles.md +118 -0
  4. package/.opencode/skills/brand/SKILL.md +97 -0
  5. package/.opencode/skills/brand/references/approval-checklist.md +169 -0
  6. package/.opencode/skills/brand/references/asset-organization.md +157 -0
  7. package/.opencode/skills/brand/references/brand-guideline-template.md +140 -0
  8. package/.opencode/skills/brand/references/color-palette-management.md +186 -0
  9. package/.opencode/skills/brand/references/consistency-checklist.md +94 -0
  10. package/.opencode/skills/brand/references/logo-usage-rules.md +185 -0
  11. package/.opencode/skills/brand/references/messaging-framework.md +85 -0
  12. package/.opencode/skills/brand/references/typography-specifications.md +214 -0
  13. package/.opencode/skills/brand/references/update.md +118 -0
  14. package/.opencode/skills/brand/references/visual-identity.md +96 -0
  15. package/.opencode/skills/brand/references/voice-framework.md +88 -0
  16. package/.opencode/skills/brand/scripts/extract-colors.cjs +341 -0
  17. package/.opencode/skills/brand/scripts/inject-brand-context.cjs +349 -0
  18. package/.opencode/skills/brand/scripts/sync-brand-to-tokens.cjs +248 -0
  19. package/.opencode/skills/brand/scripts/tests/test_sync_brand_to_tokens.py +52 -0
  20. package/.opencode/skills/brand/scripts/validate-asset.cjs +387 -0
  21. package/.opencode/skills/brand/templates/brand-guidelines-starter.md +275 -0
  22. package/.opencode/skills/design/SKILL.md +313 -0
  23. package/.opencode/skills/design/data/cip/deliverables.csv +51 -0
  24. package/.opencode/skills/design/data/cip/industries.csv +21 -0
  25. package/.opencode/skills/design/data/cip/mockup-contexts.csv +21 -0
  26. package/.opencode/skills/design/data/cip/styles.csv +21 -0
  27. package/.opencode/skills/design/data/icon/styles.csv +16 -0
  28. package/.opencode/skills/design/data/logo/colors.csv +56 -0
  29. package/.opencode/skills/design/data/logo/industries.csv +56 -0
  30. package/.opencode/skills/design/data/logo/styles.csv +56 -0
  31. package/.opencode/skills/design/references/banner-sizes-and-styles.md +118 -0
  32. package/.opencode/skills/design/references/cip-deliverable-guide.md +95 -0
  33. package/.opencode/skills/design/references/cip-design.md +121 -0
  34. package/.opencode/skills/design/references/cip-prompt-engineering.md +84 -0
  35. package/.opencode/skills/design/references/cip-style-guide.md +68 -0
  36. package/.opencode/skills/design/references/design-routing.md +207 -0
  37. package/.opencode/skills/design/references/icon-design.md +122 -0
  38. package/.opencode/skills/design/references/logo-color-psychology.md +101 -0
  39. package/.opencode/skills/design/references/logo-design.md +92 -0
  40. package/.opencode/skills/design/references/logo-prompt-engineering.md +158 -0
  41. package/.opencode/skills/design/references/logo-style-guide.md +109 -0
  42. package/.opencode/skills/design/references/slides-copywriting-formulas.md +84 -0
  43. package/.opencode/skills/design/references/slides-create.md +4 -0
  44. package/.opencode/skills/design/references/slides-html-template.md +295 -0
  45. package/.opencode/skills/design/references/slides-layout-patterns.md +137 -0
  46. package/.opencode/skills/design/references/slides-strategies.md +94 -0
  47. package/.opencode/skills/design/references/slides.md +42 -0
  48. package/.opencode/skills/design/references/social-photos-design.md +329 -0
  49. package/.opencode/skills/design/scripts/cip/core.py +215 -0
  50. package/.opencode/skills/design/scripts/cip/generate.py +484 -0
  51. package/.opencode/skills/design/scripts/cip/render-html.py +424 -0
  52. package/.opencode/skills/design/scripts/cip/search.py +127 -0
  53. package/.opencode/skills/design/scripts/icon/generate.py +487 -0
  54. package/.opencode/skills/design/scripts/logo/core.py +175 -0
  55. package/.opencode/skills/design/scripts/logo/generate.py +362 -0
  56. package/.opencode/skills/design/scripts/logo/search.py +114 -0
  57. package/.opencode/skills/design-system/SKILL.md +244 -0
  58. package/.opencode/skills/design-system/data/slide-backgrounds.csv +11 -0
  59. package/.opencode/skills/design-system/data/slide-charts.csv +26 -0
  60. package/.opencode/skills/design-system/data/slide-color-logic.csv +14 -0
  61. package/.opencode/skills/design-system/data/slide-copy.csv +26 -0
  62. package/.opencode/skills/design-system/data/slide-layout-logic.csv +16 -0
  63. package/.opencode/skills/design-system/data/slide-layouts.csv +26 -0
  64. package/.opencode/skills/design-system/data/slide-strategies.csv +16 -0
  65. package/.opencode/skills/design-system/data/slide-typography.csv +15 -0
  66. package/.opencode/skills/design-system/references/component-specs.md +236 -0
  67. package/.opencode/skills/design-system/references/component-tokens.md +214 -0
  68. package/.opencode/skills/design-system/references/primitive-tokens.md +203 -0
  69. package/.opencode/skills/design-system/references/semantic-tokens.md +215 -0
  70. package/.opencode/skills/design-system/references/states-and-variants.md +241 -0
  71. package/.opencode/skills/design-system/references/tailwind-integration.md +251 -0
  72. package/.opencode/skills/design-system/references/token-architecture.md +224 -0
  73. package/.opencode/skills/design-system/scripts/embed-tokens.cjs +99 -0
  74. package/.opencode/skills/design-system/scripts/fetch-background.py +317 -0
  75. package/.opencode/skills/design-system/scripts/generate-slide.py +770 -0
  76. package/.opencode/skills/design-system/scripts/generate-tokens.cjs +205 -0
  77. package/.opencode/skills/design-system/scripts/html-token-validator.py +327 -0
  78. package/.opencode/skills/design-system/scripts/search-slides.py +218 -0
  79. package/.opencode/skills/design-system/scripts/slide-token-validator.py +35 -0
  80. package/.opencode/skills/design-system/scripts/slide_search_core.py +453 -0
  81. package/.opencode/skills/design-system/scripts/tests/test_validate_tokens.py +48 -0
  82. package/.opencode/skills/design-system/scripts/validate-tokens.cjs +246 -0
  83. package/.opencode/skills/design-system/templates/design-tokens-starter.json +143 -0
  84. package/.opencode/skills/slides/SKILL.md +40 -0
  85. package/.opencode/skills/slides/references/copywriting-formulas.md +84 -0
  86. package/.opencode/skills/slides/references/create.md +4 -0
  87. package/.opencode/skills/slides/references/html-template.md +295 -0
  88. package/.opencode/skills/slides/references/layout-patterns.md +137 -0
  89. package/.opencode/skills/slides/references/slide-strategies.md +94 -0
  90. package/.opencode/skills/ui-styling/LICENSE.txt +202 -0
  91. package/.opencode/skills/ui-styling/SKILL.md +324 -0
  92. package/.opencode/skills/ui-styling/references/canvas-design-system.md +320 -0
  93. package/.opencode/skills/ui-styling/references/shadcn-accessibility.md +471 -0
  94. package/.opencode/skills/ui-styling/references/shadcn-components.md +424 -0
  95. package/.opencode/skills/ui-styling/references/shadcn-theming.md +373 -0
  96. package/.opencode/skills/ui-styling/references/tailwind-customization.md +483 -0
  97. package/.opencode/skills/ui-styling/references/tailwind-responsive.md +382 -0
  98. package/.opencode/skills/ui-styling/references/tailwind-utilities.md +455 -0
  99. package/.opencode/skills/ui-styling/scripts/requirements.txt +17 -0
  100. package/.opencode/skills/ui-styling/scripts/shadcn_add.py +308 -0
  101. package/.opencode/skills/ui-styling/scripts/tailwind_config_gen.py +473 -0
  102. package/.opencode/skills/ui-styling/scripts/tests/coverage-ui.json +1 -0
  103. package/.opencode/skills/ui-styling/scripts/tests/requirements.txt +3 -0
  104. package/.opencode/skills/ui-styling/scripts/tests/test_shadcn_add.py +266 -0
  105. package/.opencode/skills/ui-styling/scripts/tests/test_tailwind_config_gen.py +394 -0
  106. package/.opencode/skills/ui-ux-pro-max/SKILL.md +396 -0
  107. package/.opencode/skills/ui-ux-pro-max/data/_sync_all.py +414 -0
  108. package/.opencode/skills/ui-ux-pro-max/data/app-interface.csv +31 -0
  109. package/.opencode/skills/ui-ux-pro-max/data/charts.csv +26 -0
  110. package/.opencode/skills/ui-ux-pro-max/data/colors.csv +193 -0
  111. package/.opencode/skills/ui-ux-pro-max/data/design.csv +1776 -0
  112. package/.opencode/skills/ui-ux-pro-max/data/draft.csv +1779 -0
  113. package/.opencode/skills/ui-ux-pro-max/data/google-fonts.csv +1924 -0
  114. package/.opencode/skills/ui-ux-pro-max/data/icons.csv +106 -0
  115. package/.opencode/skills/ui-ux-pro-max/data/landing.csv +35 -0
  116. package/.opencode/skills/ui-ux-pro-max/data/motion.csv +17 -0
  117. package/.opencode/skills/ui-ux-pro-max/data/products.csv +193 -0
  118. package/.opencode/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
  119. package/.opencode/skills/ui-ux-pro-max/data/stacks/angular.csv +51 -0
  120. package/.opencode/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
  121. package/.opencode/skills/ui-ux-pro-max/data/stacks/avalonia.csv +57 -0
  122. package/.opencode/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  123. package/.opencode/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  124. package/.opencode/skills/ui-ux-pro-max/data/stacks/javafx.csv +76 -0
  125. package/.opencode/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  126. package/.opencode/skills/ui-ux-pro-max/data/stacks/laravel.csv +51 -0
  127. package/.opencode/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  128. package/.opencode/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +71 -0
  129. package/.opencode/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  130. package/.opencode/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  131. package/.opencode/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
  132. package/.opencode/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  133. package/.opencode/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  134. package/.opencode/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  135. package/.opencode/skills/ui-ux-pro-max/data/stacks/threejs.csv +54 -0
  136. package/.opencode/skills/ui-ux-pro-max/data/stacks/uno.csv +60 -0
  137. package/.opencode/skills/ui-ux-pro-max/data/stacks/uwp.csv +56 -0
  138. package/.opencode/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  139. package/.opencode/skills/ui-ux-pro-max/data/stacks/winui.csv +60 -0
  140. package/.opencode/skills/ui-ux-pro-max/data/stacks/wpf.csv +57 -0
  141. package/.opencode/skills/ui-ux-pro-max/data/styles.csv +85 -0
  142. package/.opencode/skills/ui-ux-pro-max/data/typography.csv +75 -0
  143. package/.opencode/skills/ui-ux-pro-max/data/ui-reasoning.csv +162 -0
  144. package/.opencode/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  145. package/.opencode/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-312.pyc +0 -0
  146. package/.opencode/skills/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-312.pyc +0 -0
  147. package/.opencode/skills/ui-ux-pro-max/scripts/core.py +274 -0
  148. package/.opencode/skills/ui-ux-pro-max/scripts/design_system.py +1329 -0
  149. package/.opencode/skills/ui-ux-pro-max/scripts/search.py +127 -0
  150. package/README.md +25 -30
  151. package/STELLA.md +1 -1
  152. package/dist/antimalware/database.mjs +871 -0
  153. package/dist/antimalware/index.mjs +8 -0
  154. package/dist/antimalware/scanner.mjs +591 -0
  155. package/dist/antimalware/ui.mjs +570 -0
  156. package/dist/banner.mjs +46 -0
  157. package/dist/index.mjs +3073 -0
  158. package/dist/markdown.mjs +100 -0
  159. package/dist/sea-config.json +4 -0
  160. package/dist/security.mjs +237 -0
  161. package/dist/theme.mjs +89 -0
  162. package/dist/tools.mjs +3145 -0
  163. package/install-stella.bat +44 -0
  164. package/package.json +5 -4
  165. package/stella-cli/coding-brain.mjs +753 -0
  166. package/stella-cli/index.mjs +931 -15
  167. package/stella-cli/mcp.mjs +296 -0
  168. package/stella-cli/presentations.mjs +1106 -0
  169. package/stella-cli/subagents.mjs +142 -0
  170. package/stella-cli/telegram-bot.mjs +824 -0
  171. package/stella-cli/tg-bg.bat +13 -0
  172. package/stella-cli/tg-server.bat +15 -0
  173. package/stella-cli/tg-server.mjs +116 -0
  174. package/stella-cli/theme.mjs +6 -4
  175. package/create_cats_pptx.py +0 -121
  176. package/hello.py +0 -2
package/dist/tools.mjs ADDED
@@ -0,0 +1,3145 @@
1
+ import { tool } from "ai"
2
+ import { z } from "zod"
3
+ import fs from "node:fs"
4
+ import path from "node:path"
5
+ import { execSync } from "node:child_process"
6
+
7
+ const MAX_OUTPUT = 30000
8
+
9
+ function clamp(s) {
10
+ if (s.length > MAX_OUTPUT) return s.slice(0, MAX_OUTPUT) + `\n... [обрезано, всего ${s.length} символов]`
11
+ return s
12
+ }
13
+
14
+ function resolveSafe(p) {
15
+ return path.resolve(process.cwd(), p)
16
+ }
17
+
18
+ // permissions: { ask: async (kind, summary) => boolean }
19
+ export function createTools(permissions) {
20
+ return {
21
+ read_file: tool({
22
+ description: "Прочитать файл. Возвращает содержимое с номерами строк.",
23
+ inputSchema: z.object({
24
+ path: z.string().describe("Путь к файлу"),
25
+ offset: z.number().optional().describe("Начальная строка (с 1)"),
26
+ limit: z.number().optional().describe("Максимум строк"),
27
+ }),
28
+ execute: async ({ path: p, offset = 1, limit = 2000 }) => {
29
+ const abs = resolveSafe(p)
30
+ if (!fs.existsSync(abs)) return { error: `Файл не найден: ${p}` }
31
+ const lines = fs.readFileSync(abs, "utf8").split("\n")
32
+ const slice = lines.slice(offset - 1, offset - 1 + limit)
33
+ const numbered = slice.map((l, i) => `${String(i + offset).padStart(5)}→${l}`).join("\n")
34
+ return { content: clamp(numbered), totalLines: lines.length }
35
+ },
36
+ }),
37
+
38
+ write_file: tool({
39
+ description: "Создать или перезаписать файл целиком.",
40
+ inputSchema: z.object({
41
+ path: z.string(),
42
+ content: z.string(),
43
+ }),
44
+ execute: async ({ path: p, content }) => {
45
+ const ok = await permissions.ask("write", `Write(${p}) — ${content.split("\n").length} строк`)
46
+ if (!ok) return { error: "Пользователь отклонил запись файла" }
47
+ const abs = resolveSafe(p)
48
+ fs.mkdirSync(path.dirname(abs), { recursive: true })
49
+ fs.writeFileSync(abs, content, "utf8")
50
+ return { success: true, path: p, lines: content.split("\n").length }
51
+ },
52
+ }),
53
+
54
+ edit_file: tool({
55
+ description:
56
+ "Точечная замена в файле: заменяет old_string на new_string. old_string должен встречаться ровно один раз (или используй replace_all).",
57
+ inputSchema: z.object({
58
+ path: z.string(),
59
+ old_string: z.string(),
60
+ new_string: z.string(),
61
+ replace_all: z.boolean().optional(),
62
+ }),
63
+ execute: async ({ path: p, old_string, new_string, replace_all }) => {
64
+ const abs = resolveSafe(p)
65
+ if (!fs.existsSync(abs)) return { error: `Файл не найден: ${p}` }
66
+ const src = fs.readFileSync(abs, "utf8")
67
+ const count = src.split(old_string).length - 1
68
+ if (count === 0) return { error: "old_string не найден в файле" }
69
+ if (count > 1 && !replace_all) return { error: `old_string встречается ${count} раз — уточни контекст или replace_all` }
70
+ const ok = await permissions.ask("write", `Edit(${p})`)
71
+ if (!ok) return { error: "Пользователь отклонил правку" }
72
+ const out = replace_all ? src.split(old_string).join(new_string) : src.replace(old_string, new_string)
73
+ fs.writeFileSync(abs, out, "utf8")
74
+ return { success: true, replacements: replace_all ? count : 1 }
75
+ },
76
+ }),
77
+
78
+ list_dir: tool({
79
+ description: "Показать содержимое директории.",
80
+ inputSchema: z.object({ path: z.string().optional() }),
81
+ execute: async ({ path: p = "." }) => {
82
+ const abs = resolveSafe(p)
83
+ if (!fs.existsSync(abs)) return { error: `Не найдено: ${p}` }
84
+ const entries = fs.readdirSync(abs, { withFileTypes: true })
85
+ return {
86
+ entries: entries
87
+ .filter((e) => e.name !== "node_modules" && e.name !== ".git")
88
+ .map((e) => (e.isDirectory() ? e.name + "/" : e.name))
89
+ .sort(),
90
+ }
91
+ },
92
+ }),
93
+
94
+ glob: tool({
95
+ description: "Найти файлы по glob-шаблону, например **/*.ts",
96
+ inputSchema: z.object({ pattern: z.string(), path: z.string().optional() }),
97
+ execute: async ({ pattern, path: p = "." }) => {
98
+ try {
99
+ const base = resolveSafe(p)
100
+ const SKIP = new Set(["node_modules", ".git", ".next", ".vercel"])
101
+ const rx = new RegExp(
102
+ "^" +
103
+ pattern
104
+ .replace(/[.+^${}()|[\]\\]/g, "\\$&")
105
+ .replace(/\*\*\//g, "(.*/)?")
106
+ .replace(/\*\*/g, ".*")
107
+ .replace(/\*/g, "[^/]*")
108
+ .replace(/\?/g, ".") +
109
+ "$",
110
+ )
111
+ function walk(dir) {
112
+ const results = []
113
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
114
+ if (SKIP.has(entry.name)) continue
115
+ const full = path.join(dir, entry.name)
116
+ if (entry.isDirectory()) {
117
+ results.push(...walk(full))
118
+ } else {
119
+ results.push(full)
120
+ }
121
+ if (results.length >= 500) break
122
+ }
123
+ return results
124
+ }
125
+ const all = walk(base)
126
+ const files = all
127
+ .map((f) => path.relative(base, f).replace(/\\/g, "/"))
128
+ .filter((f) => rx.test(f))
129
+ return { files: files.slice(0, 200), total: files.length }
130
+ } catch (e) {
131
+ return { error: String(e.message || e) }
132
+ }
133
+ },
134
+ }),
135
+
136
+ grep: tool({
137
+ description: "Поиск по содержимому файлов (регулярное выражение).",
138
+ inputSchema: z.object({
139
+ pattern: z.string(),
140
+ path: z.string().optional(),
141
+ glob: z.string().optional().describe("фильтр файлов, напр. *.ts"),
142
+ }),
143
+ execute: async ({ pattern, path: p = ".", glob: g }) => {
144
+ try {
145
+ const base = resolveSafe(p)
146
+ const SKIP = new Set(["node_modules", ".git", ".next", ".vercel"])
147
+ const rx = new RegExp(pattern)
148
+ const globRx = g
149
+ ? new RegExp(
150
+ "^" +
151
+ g.replace(/[.+^${}()|[\]\\]/g, "\\$&")
152
+ .replace(/\*\*\//g, "(.*/)?")
153
+ .replace(/\*\*/g, ".*")
154
+ .replace(/\*/g, "[^/]*")
155
+ .replace(/\?/g, ".") +
156
+ "$",
157
+ )
158
+ : null
159
+ const lines = []
160
+ function walk(dir) {
161
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
162
+ if (SKIP.has(entry.name)) continue
163
+ const full = path.join(dir, entry.name)
164
+ if (entry.isDirectory()) {
165
+ walk(full)
166
+ } else {
167
+ if (globRx && !globRx.test(entry.name)) continue
168
+ try {
169
+ const content = fs.readFileSync(full, "utf8")
170
+ const rel = path.relative(base, full).replace(/\\/g, "/")
171
+ for (let i = 0; i < content.split("\n").length; i++) {
172
+ const line = content.split("\n")[i]
173
+ if (rx.test(line)) {
174
+ lines.push(`${rel}:${i + 1}:${line}`)
175
+ if (lines.length >= 100) return
176
+ }
177
+ }
178
+ } catch {}
179
+ }
180
+ if (lines.length >= 100) return
181
+ }
182
+ }
183
+ walk(base)
184
+ return { matches: clamp(lines.join("\n")) || "(нет совпадений)" }
185
+ } catch {
186
+ return { matches: "(нет совпадений)" }
187
+ }
188
+ },
189
+ }),
190
+
191
+ bash: tool({
192
+ description: "Выполнить shell-команду в рабочей директории. Возвращает stdout/stderr.",
193
+ inputSchema: z.object({
194
+ command: z.string(),
195
+ timeout_ms: z.number().optional(),
196
+ }),
197
+ execute: async ({ command, timeout_ms = 120000 }) => {
198
+ const ok = await permissions.ask("bash", `Bash(${command})`)
199
+ if (!ok) return { error: "Пользователь отклонил выполнение команды" }
200
+ try {
201
+ const out = execSync(command, {
202
+ encoding: "utf8",
203
+ timeout: timeout_ms,
204
+ maxBuffer: 10 * 1024 * 1024,
205
+ cwd: process.cwd(),
206
+ })
207
+ return { output: clamp(out) || "(пустой вывод)" }
208
+ } catch (e) {
209
+ return {
210
+ error: clamp(String(e.stderr || e.message || e)),
211
+ output: clamp(String(e.stdout || "")),
212
+ exitCode: e.status ?? 1,
213
+ }
214
+ }
215
+ },
216
+ }),
217
+
218
+ todo_write: tool({
219
+ description: "Обновить список задач текущей сессии (план работы). Показывается пользователю.",
220
+ inputSchema: z.object({
221
+ todos: z.array(
222
+ z.object({
223
+ content: z.string(),
224
+ status: z.enum(["pending", "in_progress", "completed"]),
225
+ }),
226
+ ),
227
+ }),
228
+ execute: async ({ todos }) => {
229
+ permissions.onTodos?.(todos)
230
+ return { success: true, count: todos.length }
231
+ },
232
+ }),
233
+
234
+ // ═══════════════════════════════════════════════════
235
+ // ВЕБ И ИНТЕРНЕТ
236
+ // ═══════════════════════════════════════════════════
237
+ web_search: tool({
238
+ description: "Поиск в интернете по запросу (бесплатно, без API ключа).",
239
+ inputSchema: z.object({
240
+ query: z.string().describe("Поисковый запрос"),
241
+ num_results: z.number().optional().describe("Количество результатов (по умолчанию 5)"),
242
+ }),
243
+ execute: async ({ query, num_results = 5 }) => {
244
+ try {
245
+ // Используем DuckDuckGo Instant Answers API (бесплатно, без ключа)
246
+ const res = await fetch(`https://api.duckduckgo.com/?q=${encodeURIComponent(query)}&format=json&no_html=1&skip_disambig=1`, {
247
+ signal: AbortSignal.timeout(10000),
248
+ headers: { "User-Agent": "Stella/3.9" },
249
+ })
250
+
251
+ if (!res.ok) {
252
+ // Fallback: поиск через HTML
253
+ const htmlRes = await fetch(`https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`, {
254
+ signal: AbortSignal.timeout(10000),
255
+ headers: { "User-Agent": "Mozilla/5.0" },
256
+ })
257
+ const html = await htmlRes.text()
258
+ const results = []
259
+ const matches = html.matchAll(/class="result__a"[^>]*href="([^"]*)"[^>]*>([^<]*)</g)
260
+ for (const match of matches) {
261
+ if (results.length >= num_results) break
262
+ results.push({ url: match[1], title: match[2].trim(), snippet: "" })
263
+ }
264
+ return { results, total: results.length }
265
+ }
266
+
267
+ const data = await res.json()
268
+ const results = []
269
+
270
+ if (data.AbstractText) {
271
+ results.push({
272
+ title: data.Heading || query,
273
+ url: data.AbstractURL || "",
274
+ snippet: data.AbstractText,
275
+ })
276
+ }
277
+
278
+ if (data.RelatedTopics) {
279
+ for (const topic of data.RelatedTopics) {
280
+ if (results.length >= num_results) break
281
+ if (topic.Text && topic.FirstURL) {
282
+ results.push({
283
+ title: topic.Text.slice(0, 100),
284
+ url: topic.FirstURL,
285
+ snippet: topic.Text,
286
+ })
287
+ }
288
+ }
289
+ }
290
+
291
+ return { results: results.slice(0, num_results), total: results.length }
292
+ } catch (e) {
293
+ return { error: `Ошибка поиска: ${e.message}` }
294
+ }
295
+ },
296
+ }),
297
+
298
+ web_fetch: tool({
299
+ description: "Загрузить и прочитать содержимое URL. Возвращает текст страницы.",
300
+ inputSchema: z.object({
301
+ url: z.string().describe("URL для загрузки"),
302
+ max_length: z.number().optional().describe("Максимальная длина (символов)"),
303
+ }),
304
+ execute: async ({ url, max_length = 10000 }) => {
305
+ try {
306
+ const res = await fetch(url, {
307
+ signal: AbortSignal.timeout(15000),
308
+ headers: { "User-Agent": "Stella/3.9" },
309
+ })
310
+ if (!res.ok) return { error: `HTTP ${res.status}` }
311
+ const text = await res.text()
312
+ const content = text.length > max_length ? text.slice(0, max_length) + "..." : text
313
+ return { content, length: text.length }
314
+ } catch (e) {
315
+ return { error: String(e.message || e) }
316
+ }
317
+ },
318
+ }),
319
+
320
+ // ═══════════════════════════════════════════════════
321
+ // ФАЙЛЫ И ДАННЫЕ
322
+ // ═══════════════════════════════════════════════════
323
+ file_info: tool({
324
+ description: "Получить информацию о файле: размер, тип, даты, права.",
325
+ inputSchema: z.object({ path: z.string() }),
326
+ execute: async ({ path: p }) => {
327
+ const abs = resolveSafe(p)
328
+ if (!fs.existsSync(abs)) return { error: `Файл не найден: ${p}` }
329
+ const stat = fs.statSync(abs)
330
+ return {
331
+ name: path.basename(abs),
332
+ type: stat.isDirectory() ? "directory" : "file",
333
+ size: stat.size,
334
+ sizeHuman: `${(stat.size / 1024).toFixed(1)} KB`,
335
+ created: stat.birthtime.toISOString(),
336
+ modified: stat.mtime.toISOString(),
337
+ permissions: stat.mode.toString(8),
338
+ }
339
+ },
340
+ }),
341
+
342
+ file_hash: tool({
343
+ description: "Вычислить хеш файла (SHA-256, MD5).",
344
+ inputSchema: z.object({ path: z.string() }),
345
+ execute: async ({ path: p }) => {
346
+ const crypto = await import("node:crypto")
347
+ const abs = resolveSafe(p)
348
+ if (!fs.existsSync(abs)) return { error: `Файл не найден: ${p}` }
349
+ const content = fs.readFileSync(abs)
350
+ return {
351
+ sha256: crypto.createHash("sha256").update(content).digest("hex"),
352
+ md5: crypto.createHash("md5").update(content).digest("hex"),
353
+ size: content.length,
354
+ }
355
+ },
356
+ }),
357
+
358
+ file_copy: tool({
359
+ description: "Скопировать файл из источника в назначение.",
360
+ inputSchema: z.object({
361
+ source: z.string().describe("Исходный файл"),
362
+ destination: z.string().describe("Назначение"),
363
+ }),
364
+ execute: async ({ source, destination }) => {
365
+ const src = resolveSafe(source)
366
+ const dst = resolveSafe(destination)
367
+ if (!fs.existsSync(src)) return { error: `Файл не найден: ${source}` }
368
+ fs.mkdirSync(path.dirname(dst), { recursive: true })
369
+ fs.copyFileSync(src, dst)
370
+ return { success: true, source, destination }
371
+ },
372
+ }),
373
+
374
+ file_move: tool({
375
+ description: "Переместить/переименовать файл.",
376
+ inputSchema: z.object({
377
+ source: z.string().describe("Исходный файл"),
378
+ destination: z.string().describe("Назначение"),
379
+ }),
380
+ execute: async ({ source, destination }) => {
381
+ const src = resolveSafe(source)
382
+ const dst = resolveSafe(destination)
383
+ if (!fs.existsSync(src)) return { error: `Файл не найден: ${source}` }
384
+ fs.mkdirSync(path.dirname(dst), { recursive: true })
385
+ fs.renameSync(src, dst)
386
+ return { success: true, source, destination }
387
+ },
388
+ }),
389
+
390
+ file_delete: tool({
391
+ description: "Удалить файл.",
392
+ inputSchema: z.object({ path: z.string() }),
393
+ execute: async ({ path: p }) => {
394
+ const abs = resolveSafe(p)
395
+ if (!fs.existsSync(abs)) return { error: `Файл не найден: ${p}` }
396
+ fs.unlinkSync(abs)
397
+ return { success: true, deleted: p }
398
+ },
399
+ }),
400
+
401
+ mkdir: tool({
402
+ description: "Создать директорию (включая родительские).",
403
+ inputSchema: z.object({ path: z.string() }),
404
+ execute: async ({ path: p }) => {
405
+ const abs = resolveSafe(p)
406
+ fs.mkdirSync(abs, { recursive: true })
407
+ return { success: true, created: p }
408
+ },
409
+ }),
410
+
411
+ // ═══════════════════════════════════════════════════
412
+ // GIT
413
+ // ═══════════════════════════════════════════════════
414
+ git_status: tool({
415
+ description: "Показать статус git репозитория.",
416
+ inputSchema: z.object({}),
417
+ execute: async () => {
418
+ try {
419
+ const out = execSync("git status", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
420
+ return { status: out }
421
+ } catch (e) {
422
+ return { error: "Git репозиторий не найден или ошибка" }
423
+ }
424
+ },
425
+ }),
426
+
427
+ git_diff: tool({
428
+ description: "Показать различия git ( unstaged, staged, или конкретный коммит).",
429
+ inputSchema: z.object({
430
+ target: z.string().optional().describe("Цель diff (HEAD, staged, конкретный коммит)"),
431
+ }),
432
+ execute: async ({ target = "" }) => {
433
+ try {
434
+ const cmd = target ? `git diff ${target}` : "git diff"
435
+ const out = execSync(cmd, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
436
+ return { diff: out || "(нет изменений)" }
437
+ } catch (e) {
438
+ return { error: String(e.message) }
439
+ }
440
+ },
441
+ }),
442
+
443
+ git_log: tool({
444
+ description: "Показать историю коммитов.",
445
+ inputSchema: z.object({
446
+ count: z.number().optional().describe("Количество коммитов (по умолчанию 10)"),
447
+ }),
448
+ execute: async ({ count = 10 }) => {
449
+ try {
450
+ const out = execSync(`git log --oneline -${count}`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
451
+ return { log: out }
452
+ } catch (e) {
453
+ return { error: "Git репозиторий не найден" }
454
+ }
455
+ },
456
+ }),
457
+
458
+ git_commit: tool({
459
+ description: "Создать git коммит с сообщением.",
460
+ inputSchema: z.object({
461
+ message: z.string().describe("Сообщение коммита"),
462
+ files: z.array(z.string()).optional().describe("Файлы для добавления (по умолчанию все изменённые)"),
463
+ }),
464
+ execute: async ({ message, files }) => {
465
+ try {
466
+ if (files && files.length > 0) {
467
+ execSync(`git add ${files.join(" ")}`, { encoding: "utf8" })
468
+ } else {
469
+ execSync("git add -A", { encoding: "utf8" })
470
+ }
471
+ execSync(`git commit -m "${message.replace(/"/g, '\\"')}"`, { encoding: "utf8" })
472
+ return { success: true, message }
473
+ } catch (e) {
474
+ return { error: String(e.stderr || e.message) }
475
+ }
476
+ },
477
+ }),
478
+
479
+ // ═══════════════════════════════════════════════════
480
+ // ПАМЯТЬ И КОНТЕКСТ
481
+ // ═══════════════════════════════════════════════════
482
+ memory_read: tool({
483
+ description: "Прочитать память проекта (STELLA.md).",
484
+ inputSchema: z.object({}),
485
+ execute: async () => {
486
+ const memoryPath = path.join(process.cwd(), "STELLA.md")
487
+ if (!fs.existsSync(memoryPath)) return { content: "(память проекта не создана)" }
488
+ return { content: fs.readFileSync(memoryPath, "utf8") }
489
+ },
490
+ }),
491
+
492
+ memory_write: tool({
493
+ description: "Записать информацию в память проекта (STELLA.md).",
494
+ inputSchema: z.object({
495
+ content: z.string().describe("Информация для сохранения"),
496
+ append: z.boolean().optional().describe("Добавить в конец (по умолчанию заменить)"),
497
+ }),
498
+ execute: async ({ content, append = false }) => {
499
+ const memoryPath = path.join(process.cwd(), "STELLA.md")
500
+ if (append && fs.existsSync(memoryPath)) {
501
+ const existing = fs.readFileSync(memoryPath, "utf8")
502
+ fs.writeFileSync(memoryPath, existing + "\n\n" + content)
503
+ } else {
504
+ fs.writeFileSync(memoryPath, content)
505
+ }
506
+ return { success: true }
507
+ },
508
+ }),
509
+
510
+ // ═══════════════════════════════════════════════════
511
+ // ЗАДАЧИ (TODO)
512
+ // ═══════════════════════════════════════════════════
513
+ todo_read: tool({
514
+ description: "Прочитать текущий список задач.",
515
+ inputSchema: z.object({}),
516
+ execute: async () => {
517
+ const todoPath = path.join(process.cwd(), ".stella-todos.json")
518
+ try {
519
+ const todos = JSON.parse(fs.readFileSync(todoPath, "utf8"))
520
+ return { todos }
521
+ } catch {
522
+ return { todos: [] }
523
+ }
524
+ },
525
+ }),
526
+
527
+ // ═══════════════════════════════════════════════════
528
+ // ИНСТРУМЕНТЫ РАЗРАБОТЧИКА
529
+ // ═══════════════════════════════════════════════════
530
+ package_json: tool({
531
+ description: "Прочитать информацию из package.json проекта.",
532
+ inputSchema: z.object({}),
533
+ execute: async () => {
534
+ const pkgPath = path.join(process.cwd(), "package.json")
535
+ if (!fs.existsSync(pkgPath)) return { error: "package.json не найден" }
536
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"))
537
+ return {
538
+ name: pkg.name,
539
+ version: pkg.version,
540
+ description: pkg.description,
541
+ scripts: pkg.scripts || {},
542
+ dependencies: Object.keys(pkg.dependencies || {}),
543
+ devDependencies: Object.keys(pkg.devDependencies || {}),
544
+ }
545
+ },
546
+ }),
547
+
548
+ env_get: tool({
549
+ description: "Получить значение переменной окружения.",
550
+ inputSchema: z.object({
551
+ name: z.string().describe("Имя переменной окружения"),
552
+ }),
553
+ execute: async ({ name }) => {
554
+ return { name, value: process.env[name] || "(не задана)" }
555
+ },
556
+ }),
557
+
558
+ env_set: tool({
559
+ description: "Установить переменную окружения.",
560
+ inputSchema: z.object({
561
+ name: z.string().describe("Имя переменной"),
562
+ value: z.string().describe("Значение переменной"),
563
+ }),
564
+ execute: async ({ name, value }) => {
565
+ process.env[name] = value
566
+ return { success: true, name, value }
567
+ },
568
+ }),
569
+
570
+ process_list: tool({
571
+ description: "Показать список запущенных процессов.",
572
+ inputSchema: z.object({
573
+ filter: z.string().optional().describe("Фильтр по имени процесса"),
574
+ }),
575
+ execute: async ({ filter }) => {
576
+ try {
577
+ let cmd = "tasklist /FO CSV /NH"
578
+ if (filter) cmd = `tasklist /FO CSV /NH /FI "IMAGENAME eq ${filter}*"`
579
+ const out = execSync(cmd, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
580
+ return { processes: out }
581
+ } catch (e) {
582
+ return { error: "Не удалось получить список процессов" }
583
+ }
584
+ },
585
+ }),
586
+
587
+ process_kill: tool({
588
+ description: "Завершить процесс по PID или имени.",
589
+ inputSchema: z.object({
590
+ pid: z.number().optional().describe("PID процесса"),
591
+ name: z.string().optional().describe("Имя процесса"),
592
+ }),
593
+ execute: async ({ pid, name }) => {
594
+ try {
595
+ const target = pid ? `/PID ${pid}` : `/IM ${name}`
596
+ execSync(`taskkill /F ${target}`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
597
+ return { success: true, killed: pid || name }
598
+ } catch (e) {
599
+ return { error: String(e.stderr || e.message) }
600
+ }
601
+ },
602
+ }),
603
+
604
+ // ═══════════════════════════════════════════════════════════════
605
+ // ПОЛНОЕ УПРАВЛЕНИЕ КОМПЬЮТЕРОМ
606
+ // ═══════════════════════════════════════════════════════════════
607
+
608
+ // ── ЗАПУСК ПРИЛОЖЕНИЙ ──
609
+ open_app: tool({
610
+ description: "Открыть приложение или файл. Поддерживает любые установленные приложения.",
611
+ inputSchema: z.object({
612
+ app: z.string().describe("Имя приложения (chrome, notepad, explorer,calc, word, excel, code, spotify, steam) или путь к файлу"),
613
+ args: z.string().optional().describe("Аргументы запуска"),
614
+ }),
615
+ execute: async ({ app, args = "" }) => {
616
+ try {
617
+ const appMap = {
618
+ "chrome": "chrome.exe",
619
+ "google chrome": "chrome.exe",
620
+ "firefox": "firefox.exe",
621
+ "edge": "msedge.exe",
622
+ "browser": "msedge.exe",
623
+ "notepad": "notepad.exe",
624
+ "explorer": "explorer.exe",
625
+ "files": "explorer.exe",
626
+ "calc": "calc.exe",
627
+ "калькулятор": "calc.exe",
628
+ "paint": "mspaint.exe",
629
+ "word": "winword.exe",
630
+ "excel": "excel.exe",
631
+ "powerpoint": "powerpnt.exe",
632
+ "code": "code.cmd",
633
+ "vscode": "code.cmd",
634
+ "visual studio code": "code.cmd",
635
+ "spotify": "Spotify.exe",
636
+ "steam": "steam.exe",
637
+ "discord": "Discord.exe",
638
+ "telegram": "Telegram.exe",
639
+ "whatsapp": "WhatsApp.exe",
640
+ "teams": "Teams.exe",
641
+ "zoom": "Zoom.exe",
642
+ "slack": "Slack.exe",
643
+ "photoshop": "Photoshop.exe",
644
+ "figma": "Figma.exe",
645
+ "blender": "blender.exe",
646
+ "terminal": "wt.exe",
647
+ "powershell": "pwsh.exe",
648
+ "cmd": "cmd.exe",
649
+ "task manager": "taskmgr.exe",
650
+ "диспетчер": "taskmgr.exe",
651
+ "control panel": "control.exe",
652
+ "панель управления": "control.exe",
653
+ "settings": "ms-settings:",
654
+ "настройки": "ms-settings:",
655
+ "registry": "regedit.exe",
656
+ "реестр": "regedit.exe",
657
+ "cmd": "cmd.exe",
658
+ "system info": "msinfo32.exe",
659
+ "info": "msinfo32.exe",
660
+ }
661
+ const appName = appMap[app.toLowerCase()] || app
662
+ const cmd = args ? `start "" "${appName}" ${args}` : `start "" "${appName}"`
663
+ execSync(cmd, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], shell: "cmd.exe" })
664
+ return { success: true, app: appName }
665
+ } catch (e) {
666
+ return { error: `Не удалось открыть ${app}: ${e.message}` }
667
+ }
668
+ },
669
+ }),
670
+
671
+ open_url: tool({
672
+ description: "Открыть URL в браузере по умолчанию.",
673
+ inputSchema: z.object({
674
+ url: z.string().describe("URL для открытия"),
675
+ }),
676
+ execute: async ({ url }) => {
677
+ try {
678
+ execSync(`start "" "${url}"`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], shell: "cmd.exe" })
679
+ return { success: true, url }
680
+ } catch (e) {
681
+ return { error: `Не удалось открыть URL: ${e.message}` }
682
+ }
683
+ },
684
+ }),
685
+
686
+ open_kinopoisk: tool({
687
+ description: "Открыть Кинопоиск в браузере.",
688
+ inputSchema: z.object({
689
+ query: z.string().optional().describe("Поисковый запрос (название фильма)"),
690
+ }),
691
+ execute: async ({ query }) => {
692
+ try {
693
+ const url = query
694
+ ? `https://www.kinopoisk.ru/index.php?what=${encodeURIComponent(query)}`
695
+ : "https://www.kinopoisk.ru"
696
+ execSync(`start "" "${url}"`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], shell: "cmd.exe" })
697
+ return { success: true, url }
698
+ } catch (e) {
699
+ return { error: `Не удалось открыть Кинопоиск: ${e.message}` }
700
+ }
701
+ },
702
+ }),
703
+
704
+ open_youtube: tool({
705
+ description: "Открыть YouTube в браузере.",
706
+ inputSchema: z.object({
707
+ query: z.string().optional().describe("Поисковый запрос"),
708
+ }),
709
+ execute: async ({ query }) => {
710
+ try {
711
+ const url = query
712
+ ? `https://www.youtube.com/results?search_query=${encodeURIComponent(query)}`
713
+ : "https://www.youtube.com"
714
+ execSync(`start "" "${url}"`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], shell: "cmd.exe" })
715
+ return { success: true, url }
716
+ } catch (e) {
717
+ return { error: `Не удалось открыть YouTube: ${e.message}` }
718
+ }
719
+ },
720
+ }),
721
+
722
+ open_google: tool({
723
+ description: "Открыть Google в браузере.",
724
+ inputSchema: z.object({
725
+ query: z.string().optional().describe("Поисковый запрос"),
726
+ }),
727
+ execute: async ({ query }) => {
728
+ try {
729
+ const url = query
730
+ ? `https://www.google.com/search?q=${encodeURIComponent(query)}`
731
+ : "https://www.google.com"
732
+ execSync(`start "" "${url}"`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], shell: "cmd.exe" })
733
+ return { success: true, url }
734
+ } catch (e) {
735
+ return { error: `Не удалось открыть Google: ${e.message}` }
736
+ }
737
+ },
738
+ }),
739
+
740
+ // ── УПРАВЛЕНИЕ ФАЙЛАМИ ──
741
+ file_open: tool({
742
+ description: "Открыть файл关联ным приложением.",
743
+ inputSchema: z.object({ path: z.string().describe("Путь к файлу") }),
744
+ execute: async ({ path: p }) => {
745
+ try {
746
+ execSync(`start "" "${p}"`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], shell: "cmd.exe" })
747
+ return { success: true, opened: p }
748
+ } catch (e) {
749
+ return { error: `Не удалось открыть файл: ${e.message}` }
750
+ }
751
+ },
752
+ }),
753
+
754
+ file_explorer: tool({
755
+ description: "Открыть папку в проводнике.",
756
+ inputSchema: z.object({ path: z.string().describe("Путь к папке") }),
757
+ execute: async ({ path: p }) => {
758
+ try {
759
+ execSync(`explorer "${p}"`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
760
+ return { success: true, folder: p }
761
+ } catch (e) {
762
+ return { error: `Не удалось открыть папку: ${e.message}` }
763
+ }
764
+ },
765
+ }),
766
+
767
+ file_create: tool({
768
+ description: "Создать файл с содержимым.",
769
+ inputSchema: z.object({
770
+ path: z.string().describe("Путь к файлу"),
771
+ content: z.string().describe("Содержимое файла"),
772
+ }),
773
+ execute: async ({ path: p, content }) => {
774
+ try {
775
+ const abs = resolveSafe(p)
776
+ fs.mkdirSync(path.dirname(abs), { recursive: true })
777
+ fs.writeFileSync(abs, content, "utf8")
778
+ return { success: true, created: p }
779
+ } catch (e) {
780
+ return { error: `Не удалось создать файл: ${e.message}` }
781
+ }
782
+ },
783
+ }),
784
+
785
+ file_rename: tool({
786
+ description: "Переименовать файл или папку.",
787
+ inputSchema: z.object({
788
+ old_path: z.string().describe("Старый путь"),
789
+ new_path: z.string().describe("Новый путь"),
790
+ }),
791
+ execute: async ({ old_path, new_path }) => {
792
+ try {
793
+ fs.renameSync(resolveSafe(old_path), resolveSafe(new_path))
794
+ return { success: true, from: old_path, to: new_path }
795
+ } catch (e) {
796
+ return { error: `Не удалось переименовать: ${e.message}` }
797
+ }
798
+ },
799
+ }),
800
+
801
+ // ── БУФЕР ОБМЕНА ──
802
+ clipboard_get: tool({
803
+ description: "Получить текст из буфера обмена.",
804
+ inputSchema: z.object({}),
805
+ execute: async () => {
806
+ try {
807
+ const text = execSync("powershell -command \"Get-Clipboard\"", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
808
+ return { clipboard: text.trim() }
809
+ } catch (e) {
810
+ return { error: `Не удалось прочитать буфер: ${e.message}` }
811
+ }
812
+ },
813
+ }),
814
+
815
+ clipboard_set: tool({
816
+ description: "Скопировать текст в буфер обмена.",
817
+ inputSchema: z.object({ text: z.string().describe("Текст для копирования") }),
818
+ execute: async ({ text }) => {
819
+ try {
820
+ execSync(`powershell -command "Set-Clipboard -Value '${text.replace(/'/g, "''")}'"`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
821
+ return { success: true, copied: text.slice(0, 100) }
822
+ } catch (e) {
823
+ return { error: `Не удалось скопировать: ${e.message}` }
824
+ }
825
+ },
826
+ }),
827
+
828
+ // ── СКРИНШОТЫ ──
829
+ screenshot: tool({
830
+ description: "Сделать скриншот экрана и сохранить в файл.",
831
+ inputSchema: z.object({
832
+ path: z.string().optional().describe("Путь для сохранения (по умолчанию screenshot.png)"),
833
+ }),
834
+ execute: async ({ path: p }) => {
835
+ try {
836
+ const savePath = p || resolveSafe("screenshot.png")
837
+ execSync(`powershell -command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Screen]::PrimaryScreen | ForEach-Object { $bounds = $_.Bounds; Add-Type -TypeDefinition 'using System; using System.Drawing; using System.Drawing.Imaging; public class ScreenCapture { public static void Capture(string f) { var bmp = new Bitmap($bounds.Width, $bounds.Height); Graphics.FromImage(bmp).CopyFromScreen($bounds.Location, Point.Empty, $bounds.Size); bmp.Save(f, ImageFormat.Png); } }'; [ScreenCapture]::Capture('${savePath.replace(/\\/g, "\\\\")}')"`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
838
+ return { success: true, saved: savePath }
839
+ } catch (e) {
840
+ return { error: `Не удалось сделать скриншот: ${e.message}` }
841
+ }
842
+ },
843
+ }),
844
+
845
+ // ── НАСТРОЙКИ СИСТЕМЫ ──
846
+ volume_set: tool({
847
+ description: "Установить громкость (0-100).",
848
+ inputSchema: z.object({
849
+ level: z.number().describe("Уровень громкости от 0 до 100"),
850
+ }),
851
+ execute: async ({ level }) => {
852
+ try {
853
+ const vol = Math.max(0, Math.min(100, level))
854
+ execSync(`powershell -command "$wsh = New-Object -ComObject WScript.Shell; 1..50 | ForEach-Object { $wsh.SendKeys([char]174) }; 1..${Math.round(vol / 2)} | ForEach-Object { $wsh.SendKeys([char]175) }"`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
855
+ return { success: true, volume: vol }
856
+ } catch (e) {
857
+ return { error: `Не удалось установить громкость: ${e.message}` }
858
+ }
859
+ },
860
+ }),
861
+
862
+ volume_mute: tool({
863
+ description: "Включить/выключить звук.",
864
+ inputSchema: z.object({
865
+ mute: z.boolean().describe("true = выключить, false = включить"),
866
+ }),
867
+ execute: async ({ mute }) => {
868
+ try {
869
+ execSync(`powershell -command "$wsh = New-Object -ComObject WScript.Shell; $wsh.SendKeys([char]173)"`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
870
+ return { success: true, muted: mute }
871
+ } catch (e) {
872
+ return { error: `Не удалось изменить звук: ${e.message}` }
873
+ }
874
+ },
875
+ }),
876
+
877
+ brightness_set: tool({
878
+ description: "Установить яркость экрана (0-100).",
879
+ inputSchema: z.object({
880
+ level: z.number().describe("Уровень яркости от 0 до 100"),
881
+ }),
882
+ execute: async ({ level }) => {
883
+ try {
884
+ execSync(`powershell -command "Get-WmiObject -Namespace root\\wmi -Class WmiMonitorBrightnessMethods | ForEach-Object { $_.WmiSetBrightness(10, ${level}) }"`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
885
+ return { success: true, brightness: level }
886
+ } catch (e) {
887
+ return { error: `Не удалось установить яркость: ${e.message}` }
888
+ }
889
+ },
890
+ }),
891
+
892
+ // ── УПРАВЛЕНИЕ ПРОЦЕССАМИ ──
893
+ process_start: tool({
894
+ description: "Запустить процесс.",
895
+ inputSchema: z.object({
896
+ command: z.string().describe("Команда или путь к приложению"),
897
+ }),
898
+ execute: async ({ command }) => {
899
+ try {
900
+ execSync(`start "" "${command}"`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], shell: "cmd.exe" })
901
+ return { success: true, started: command }
902
+ } catch (e) {
903
+ return { error: `Не удалось запустить: ${e.message}` }
904
+ }
905
+ },
906
+ }),
907
+
908
+ // ── СЕТЬ ──
909
+ wifi_scan: tool({
910
+ description: "Показать доступные Wi-Fi сети.",
911
+ inputSchema: z.object({}),
912
+ execute: async () => {
913
+ try {
914
+ const out = execSync("netsh wlan show networks mode=bssid", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
915
+ return { networks: out }
916
+ } catch (e) {
917
+ return { error: "Не удалось получить список сетей" }
918
+ }
919
+ },
920
+ }),
921
+
922
+ wifi_connect: tool({
923
+ description: "Подключиться к Wi-Fi сети.",
924
+ inputSchema: z.object({
925
+ ssid: z.string().describe("Имя сети (SSID)"),
926
+ password: z.string().optional().describe("Пароль"),
927
+ }),
928
+ execute: async ({ ssid, password }) => {
929
+ try {
930
+ if (password) {
931
+ execSync(`netsh wlan connect name="${ssid}"`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
932
+ } else {
933
+ execSync(`netsh wlan connect name="${ssid}"`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
934
+ }
935
+ return { success: true, connected: ssid }
936
+ } catch (e) {
937
+ return { error: `Не удалось подключиться: ${e.message}` }
938
+ }
939
+ },
940
+ }),
941
+
942
+ // ── ПРОВЕРКА СИСТЕМЫ ──
943
+ system_info: tool({
944
+ description: "Получить полную информацию о системе.",
945
+ inputSchema: z.object({}),
946
+ execute: async () => {
947
+ try {
948
+ const info = {
949
+ platform: process.platform,
950
+ arch: process.arch,
951
+ nodeVersion: process.version,
952
+ hostname: require("os").hostname(),
953
+ username: require("os").userInfo().username,
954
+ cpus: require("os").cpus().length,
955
+ totalMemory: `${(require("os").totalmem() / 1e9).toFixed(1)} GB`,
956
+ freeMemory: `${(require("os").freemem() / 1e9).toFixed(1)} GB`,
957
+ uptime: `${Math.floor(require("os").uptime() / 3600)}h ${Math.floor((require("os").uptime() % 3600) / 60)}m`,
958
+ }
959
+ return info
960
+ } catch (e) {
961
+ return { error: `Не удалось получить информацию: ${e.message}` }
962
+ }
963
+ },
964
+ }),
965
+
966
+ // ── ПЕРЕКЛЮЧЕНИЕ ОКОН ──
967
+ window_list: tool({
968
+ description: "Показать список открытых окон.",
969
+ inputSchema: z.object({}),
970
+ execute: async () => {
971
+ try {
972
+ const out = execSync(`powershell -command "Get-Process | Where-Object {$_.MainWindowTitle -ne ''} | Select-Object ProcessName, MainWindowTitle | Format-Table -AutoSize"`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
973
+ return { windows: out }
974
+ } catch (e) {
975
+ return { error: "Не удалось получить список окон" }
976
+ }
977
+ },
978
+ }),
979
+
980
+ window_focus: tool({
981
+ description: "Переключиться на окно приложения.",
982
+ inputSchema: z.object({
983
+ name: z.string().describe("Имя приложения или заголовок окна"),
984
+ }),
985
+ execute: async ({ name }) => {
986
+ try {
987
+ execSync(`powershell -command "Get-Process | Where-Object {$_.MainWindowTitle -like '*${name}*'} | ForEach-Object { $_.MainWindowHandle } | ForEach-Object { Add-Type 'user32.dll'; [User32]::SetForegroundWindow($_) }"`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
988
+ return { success: true, focused: name }
989
+ } catch (e) {
990
+ return { error: `Не удалось переключиться на окно: ${e.message}` }
991
+ }
992
+ },
993
+ }),
994
+
995
+ // ── ВВОД ТЕКСТА ──
996
+ type_text: tool({
997
+ description: "Набрать текст на клавиатуре (имитация ввода).",
998
+ inputSchema: z.object({
999
+ text: z.string().describe("Текст для набора"),
1000
+ }),
1001
+ execute: async ({ text }) => {
1002
+ try {
1003
+ execSync(`powershell -command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${text.replace(/'/g, "''").replace(/"/g, '""')}')"`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
1004
+ return { success: true, typed: text.slice(0, 50) }
1005
+ } catch (e) {
1006
+ return { error: `Не удалось набрать текст: ${e.message}` }
1007
+ }
1008
+ },
1009
+ }),
1010
+
1011
+ press_key: tool({
1012
+ description: "Нажать клавишу или комбинацию клавиш.",
1013
+ inputSchema: z.object({
1014
+ key: z.string().describe("Клавиша (enter, tab, esc, delete, F5, ^c, ^v, ^s, ^a, ^z)"),
1015
+ }),
1016
+ execute: async ({ key }) => {
1017
+ try {
1018
+ execSync(`powershell -command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${key}')"`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
1019
+ return { success: true, pressed: key }
1020
+ } catch (e) {
1021
+ return { error: `Не удалось нажать клавишу: ${e.message}` }
1022
+ }
1023
+ },
1024
+ }),
1025
+
1026
+ // ── УВЕДОМЛЕНИЯ ──
1027
+ notify: tool({
1028
+ description: "Показать уведомление Windows.",
1029
+ inputSchema: z.object({
1030
+ title: z.string().describe("Заголовок уведомления"),
1031
+ message: z.string().describe("Текст уведомления"),
1032
+ }),
1033
+ execute: async ({ title, message }) => {
1034
+ try {
1035
+ execSync(`powershell -command "[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); $notify = New-Object System.Windows.Forms.NotifyIcon; $notify.Icon = [System.Drawing.SystemIcons]::Information; $notify.BalloonTipTitle = '${title}'; $notify.BalloonTipText = '${message}'; $notify.Visible = $true; $notify.ShowBalloonTip(5000)"`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
1036
+ return { success: true, title, message }
1037
+ } catch (e) {
1038
+ return { error: `Не удалось показать уведомление: ${e.message}` }
1039
+ }
1040
+ },
1041
+ }),
1042
+
1043
+ // ── СЛУЖЕБНЫЕ ──
1044
+ lock_screen: tool({
1045
+ description: "Заблокировать экран.",
1046
+ inputSchema: z.object({}),
1047
+ execute: async () => {
1048
+ try {
1049
+ execSync("rundll32.exe user32.dll,LockWorkStation", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
1050
+ return { success: true }
1051
+ } catch (e) {
1052
+ return { error: `Не удалось заблокировать экран: ${e.message}` }
1053
+ }
1054
+ },
1055
+ }),
1056
+
1057
+ shutdown: tool({
1058
+ description: "Выключить/перезагрузить компьютер.",
1059
+ inputSchema: z.object({
1060
+ action: z.enum(["shutdown", "restart", "sleep", "hibernate"]).describe("Действие"),
1061
+ delay: z.number().optional().describe("Задержка в секундах (по умолчанию 0)"),
1062
+ }),
1063
+ execute: async ({ action, delay = 0 }) => {
1064
+ try {
1065
+ const cmds = {
1066
+ shutdown: `shutdown /s /t ${delay}`,
1067
+ restart: `shutdown /r /t ${delay}`,
1068
+ sleep: "rundll32.exe powrprof.dll,SetSuspendState 0,1,0",
1069
+ hibernate: "rundll32.exe powrprof.dll,SetSuspendState",
1070
+ }
1071
+ execSync(cmds[action], { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
1072
+ return { success: true, action }
1073
+ } catch (e) {
1074
+ return { error: `Не удалось выполнить: ${e.message}` }
1075
+ }
1076
+ },
1077
+ }),
1078
+
1079
+ cancel_shutdown: tool({
1080
+ description: "Отменить запланированное выключение/перезагрузку.",
1081
+ inputSchema: z.object({}),
1082
+ execute: async () => {
1083
+ try {
1084
+ execSync("shutdown /a", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
1085
+ return { success: true }
1086
+ } catch (e) {
1087
+ return { error: `Не удалось отменить: ${e.message}` }
1088
+ }
1089
+ },
1090
+ }),
1091
+
1092
+ // ── ПОИСК ──
1093
+ search_files: tool({
1094
+ description: "Быстрый поиск файлов по имени.",
1095
+ inputSchema: z.object({
1096
+ query: z.string().describe("Имя файла или паттерн"),
1097
+ path: z.string().optional().describe("Директория поиска"),
1098
+ }),
1099
+ execute: async ({ query, path: p = "C:\\" }) => {
1100
+ try {
1101
+ const out = execSync(`dir /s /b "${p}\\*${query}*"` , { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 10000 })
1102
+ const files = out.split("\n").filter(f => f.trim()).slice(0, 50)
1103
+ return { files, total: files.length }
1104
+ } catch (e) {
1105
+ return { error: "Поиск занял слишком много времени" }
1106
+ }
1107
+ },
1108
+ }),
1109
+
1110
+ search_text: tool({
1111
+ description: "Поиск текста в файлах (grep).",
1112
+ inputSchema: z.object({
1113
+ query: z.string().describe("Текст для поиска"),
1114
+ path: z.string().optional().describe("Директория поиска"),
1115
+ glob: z.string().optional().describe("Фильтр файлов (*.js, *.ts)"),
1116
+ }),
1117
+ execute: async ({ query, path: p = ".", glob: g }) => {
1118
+ try {
1119
+ const filter = g ? `/S "${g}"` : "/S *.*"
1120
+ const out = execSync(`findstr /N /I /S "${query}" "${p}\\${g || '*.*'}"` , { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 15000 })
1121
+ const lines = out.split("\n").filter(l => l.trim()).slice(0, 100)
1122
+ return { matches: lines, total: lines.length }
1123
+ } catch (e) {
1124
+ return { error: "Поиск не дал результатов" }
1125
+ }
1126
+ },
1127
+ }),
1128
+
1129
+ // ═══════════════════════════════════════════════════════════════
1130
+ // ПОЛНОЕ УПРАВЛЕНИЕ СЕРВЕРОМ / ТЕРМИНАЛ
1131
+ // ═══════════════════════════════════════════════════════════════
1132
+
1133
+ // ── ТЕРМИНАЛ И КОМАНДЫ ──
1134
+ terminal_exec: tool({
1135
+ description: "Выполнить команду в терминале. Полный доступ к системе.",
1136
+ inputSchema: z.object({
1137
+ command: z.string().describe("Команда для выполнения"),
1138
+ cwd: z.string().optional().describe("Рабочая директория"),
1139
+ timeout: z.number().optional().describe("Таймаут в мс (по умолчанию 60000)"),
1140
+ }),
1141
+ execute: async ({ command, cwd, timeout = 60000 }) => {
1142
+ try {
1143
+ const out = execSync(command, {
1144
+ encoding: "utf8",
1145
+ timeout,
1146
+ maxBuffer: 50 * 1024 * 1024,
1147
+ cwd: cwd || process.cwd(),
1148
+ stdio: ["pipe", "pipe", "pipe"],
1149
+ })
1150
+ return { output: out || "(пустой вывод)", exitCode: 0 }
1151
+ } catch (e) {
1152
+ return {
1153
+ output: String(e.stdout || ""),
1154
+ error: String(e.stderr || e.message),
1155
+ exitCode: e.status ?? 1,
1156
+ }
1157
+ }
1158
+ },
1159
+ }),
1160
+
1161
+ terminal_powershell: tool({
1162
+ description: "Выполнить PowerShell команду.",
1163
+ inputSchema: z.object({
1164
+ command: z.string().describe("PowerShell команда"),
1165
+ execution_policy: z.boolean().optional().describe("Bypass execution policy"),
1166
+ }),
1167
+ execute: async ({ command, execution_policy = true }) => {
1168
+ try {
1169
+ const policy = execution_policy ? "-ExecutionPolicy Bypass" : ""
1170
+ const out = execSync(`powershell ${policy} -Command "${command.replace(/"/g, '\\"')}"`, {
1171
+ encoding: "utf8",
1172
+ timeout: 60000,
1173
+ maxBuffer: 50 * 1024 * 1024,
1174
+ stdio: ["pipe", "pipe", "pipe"],
1175
+ })
1176
+ return { output: out || "(пустой вывод)", exitCode: 0 }
1177
+ } catch (e) {
1178
+ return {
1179
+ output: String(e.stdout || ""),
1180
+ error: String(e.stderr || e.message),
1181
+ exitCode: e.status ?? 1,
1182
+ }
1183
+ }
1184
+ },
1185
+ }),
1186
+
1187
+ terminal_cmd: tool({
1188
+ description: "Выполнить cmd команду.",
1189
+ inputSchema: z.object({
1190
+ command: z.string().describe("cmd команда"),
1191
+ }),
1192
+ execute: async ({ command }) => {
1193
+ try {
1194
+ const out = execSync(`cmd /c "${command}"`, {
1195
+ encoding: "utf8",
1196
+ timeout: 60000,
1197
+ maxBuffer: 50 * 1024 * 1024,
1198
+ stdio: ["pipe", "pipe", "pipe"],
1199
+ })
1200
+ return { output: out || "(пустой вывод)", exitCode: 0 }
1201
+ } catch (e) {
1202
+ return {
1203
+ output: String(e.stdout || ""),
1204
+ error: String(e.stderr || e.message),
1205
+ exitCode: e.status ?? 1,
1206
+ }
1207
+ }
1208
+ },
1209
+ }),
1210
+
1211
+ // ── УПРАВЛЕНИЕ СЕРВЕРОМ ──
1212
+ server_status: tool({
1213
+ description: "Показать статус сервера: порты, соединения, ресурсы.",
1214
+ inputSchema: z.object({}),
1215
+ execute: async () => {
1216
+ try {
1217
+ const netstat = execSync("netstat -ano", { encoding: "utf8", timeout: 10000, stdio: ["pipe", "pipe", "pipe"] })
1218
+ const connections = netstat.split("\n").filter(l => l.includes("ESTABLISHED")).length
1219
+ const listening = netstat.split("\n").filter(l => l.includes("LISTENING")).length
1220
+
1221
+ const cpuUsage = execSync("wmic cpu get loadpercentage /value", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
1222
+ const cpuMatch = cpuUsage.match(/LoadPercentage=(\d+)/)
1223
+ const cpu = cpuMatch ? parseInt(cpuMatch[1]) : 0
1224
+
1225
+ const memInfo = execSync("wmic OS get FreePhysicalMemory,TotalVisibleMemorySize /value", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
1226
+ const totalMemMatch = memInfo.match(/TotalVisibleMemorySize=(\d+)/)
1227
+ const freeMemMatch = memInfo.match(/FreePhysicalMemory=(\d+)/)
1228
+ const totalMem = totalMemMatch ? parseInt(totalMemMatch[1]) / 1024 / 1024 : 0
1229
+ const freeMem = freeMemMatch ? parseInt(freeMemMatch[1]) / 1024 / 1024 : 0
1230
+ const usedMem = totalMem - freeMem
1231
+
1232
+ const uptime = execSync("systeminfo | findstr /C:\"System Boot Time\"", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
1233
+
1234
+ return {
1235
+ connections,
1236
+ listening,
1237
+ cpu: `${cpu}%`,
1238
+ memory: `${usedMem.toFixed(1)} / ${totalMem.toFixed(1)} GB (${((usedMem / totalMem) * 100).toFixed(1)}%)`,
1239
+ uptime: uptime.trim(),
1240
+ }
1241
+ } catch (e) {
1242
+ return { error: `Не удалось получить статус: ${e.message}` }
1243
+ }
1244
+ },
1245
+ }),
1246
+
1247
+ server_ports: tool({
1248
+ description: "Показать открытые порты и процессы.",
1249
+ inputSchema: z.object({}),
1250
+ execute: async () => {
1251
+ try {
1252
+ const out = execSync("netstat -ano | findstr LISTENING", { encoding: "utf8", timeout: 10000, stdio: ["pipe", "pipe", "pipe"] })
1253
+ const lines = out.split("\n").filter(l => l.trim()).slice(0, 50)
1254
+ return { ports: lines, total: lines.length }
1255
+ } catch (e) {
1256
+ return { error: "Не удалось получить порты" }
1257
+ }
1258
+ },
1259
+ }),
1260
+
1261
+ server_connections: tool({
1262
+ description: "Показать активные соединения.",
1263
+ inputSchema: z.object({}),
1264
+ execute: async () => {
1265
+ try {
1266
+ const out = execSync("netstat -ano | findstr ESTABLISHED", { encoding: "utf8", timeout: 10000, stdio: ["pipe", "pipe", "pipe"] })
1267
+ const lines = out.split("\n").filter(l => l.trim()).slice(0, 50)
1268
+ return { connections: lines, total: lines.length }
1269
+ } catch (e) {
1270
+ return { error: "Не удалось получить соединения" }
1271
+ }
1272
+ },
1273
+ }),
1274
+
1275
+ server_kill_port: tool({
1276
+ description: "Завершить процесс на порту.",
1277
+ inputSchema: z.object({
1278
+ port: z.number().describe("Номер порта"),
1279
+ }),
1280
+ execute: async ({ port }) => {
1281
+ try {
1282
+ const out = execSync(`netstat -ano | findstr :${port}`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
1283
+ const lines = out.split("\n").filter(l => l.includes("LISTENING"))
1284
+ if (lines.length === 0) return { error: `Порт ${port} не используется` }
1285
+
1286
+ const pidMatch = lines[0].match(/(\d+)\s*$/)
1287
+ if (!pidMatch) return { error: "Не удалось определить PID" }
1288
+
1289
+ const pid = pidMatch[1]
1290
+ execSync(`taskkill /F /PID ${pid}`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
1291
+ return { success: true, port, pid, killed: true }
1292
+ } catch (e) {
1293
+ return { error: `Не удалось завершить процесс: ${e.message}` }
1294
+ }
1295
+ },
1296
+ }),
1297
+
1298
+ // ── СЕТЬ ──
1299
+ network_interfaces: tool({
1300
+ description: "Показать сетевые интерфейсы.",
1301
+ inputSchema: z.object({}),
1302
+ execute: async () => {
1303
+ try {
1304
+ const out = execSync("ipconfig /all", { encoding: "utf8", timeout: 10000, stdio: ["pipe", "pipe", "pipe"] })
1305
+ return { interfaces: out }
1306
+ } catch (e) {
1307
+ return { error: "Не удалось получить интерфейсы" }
1308
+ }
1309
+ },
1310
+ }),
1311
+
1312
+ network_dhcp: tool({
1313
+ description: "Показать/обновить DHCP настройки.",
1314
+ inputSchema: z.object({
1315
+ action: z.enum(["release", "renew", "show"]).describe("Действие"),
1316
+ }),
1317
+ execute: async ({ action }) => {
1318
+ try {
1319
+ const cmds = {
1320
+ release: "ipconfig /release",
1321
+ renew: "ipconfig /renew",
1322
+ show: "ipconfig",
1323
+ }
1324
+ const out = execSync(cmds[action], { encoding: "utf8", timeout: 30000, stdio: ["pipe", "pipe", "pipe"] })
1325
+ return { output: out, action }
1326
+ } catch (e) {
1327
+ return { error: `Не удалось выполнить: ${e.message}` }
1328
+ }
1329
+ },
1330
+ }),
1331
+
1332
+ network_dns: tool({
1333
+ description: "Показать/изменить DNS серверы.",
1334
+ inputSchema: z.object({
1335
+ action: z.enum(["show", "flush", "set"]).describe("Действие"),
1336
+ server: z.string().optional().describe("DNS сервер (для set)"),
1337
+ }),
1338
+ execute: async ({ action, server }) => {
1339
+ try {
1340
+ if (action === "flush") {
1341
+ execSync("ipconfig /flushdns", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
1342
+ return { success: true, action: "flush" }
1343
+ }
1344
+ if (action === "set" && server) {
1345
+ execSync(`netsh interface ip set dns "Wi-Fi" static ${server}`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
1346
+ return { success: true, action: "set", server }
1347
+ }
1348
+ const out = execSync("ipconfig /displaydns", { encoding: "utf8", timeout: 10000, stdio: ["pipe", "pipe", "pipe"] })
1349
+ return { dns: out.slice(0, 5000) }
1350
+ } catch (e) {
1351
+ return { error: `Не удалось: ${e.message}` }
1352
+ }
1353
+ },
1354
+ }),
1355
+
1356
+ network_ping: tool({
1357
+ description: "Пинговать хост.",
1358
+ inputSchema: z.object({
1359
+ host: z.string().describe("Хост или IP"),
1360
+ count: z.number().optional().describe("Количество пингов (по умолчанию 4)"),
1361
+ }),
1362
+ execute: async ({ host, count = 4 }) => {
1363
+ try {
1364
+ const out = execSync(`ping -n ${count} ${host}`, { encoding: "utf8", timeout: 30000, stdio: ["pipe", "pipe", "pipe"] })
1365
+ return { output: out }
1366
+ } catch (e) {
1367
+ return { error: `Пинг не удался: ${e.message}` }
1368
+ }
1369
+ },
1370
+ }),
1371
+
1372
+ network_traceroute: tool({
1373
+ description: "Трассировка маршрута до хоста.",
1374
+ inputSchema: z.object({
1375
+ host: z.string().describe("Хост или IP"),
1376
+ }),
1377
+ execute: async ({ host }) => {
1378
+ try {
1379
+ const out = execSync(`tracert -d ${host}`, { encoding: "utf8", timeout: 60000, stdio: ["pipe", "pipe", "pipe"] })
1380
+ return { trace: out }
1381
+ } catch (e) {
1382
+ return { error: `Трассировка не удалась: ${e.message}` }
1383
+ }
1384
+ },
1385
+ }),
1386
+
1387
+ network_download: tool({
1388
+ description: "Скачать файл по URL.",
1389
+ inputSchema: z.object({
1390
+ url: z.string().describe("URL файла"),
1391
+ dest: z.string().optional().describe("Путь сохранения"),
1392
+ }),
1393
+ execute: async ({ url, dest }) => {
1394
+ try {
1395
+ const destPath = dest || url.split("/").pop()
1396
+ execSync(`curl -L -o "${destPath}" "${url}"`, { encoding: "utf8", timeout: 120000, stdio: ["pipe", "pipe", "pipe"] })
1397
+ return { success: true, downloaded: destPath, url }
1398
+ } catch (e) {
1399
+ return { error: `Не удалось скачать: ${e.message}` }
1400
+ }
1401
+ },
1402
+ }),
1403
+
1404
+ network_upload: tool({
1405
+ description: "Загрузить файл на сервер.",
1406
+ inputSchema: z.object({
1407
+ file: z.string().describe("Путь к файлу"),
1408
+ url: z.string().describe("URL для загрузки"),
1409
+ method: z.enum(["PUT", "POST"]).optional().describe("HTTP метод"),
1410
+ }),
1411
+ execute: async ({ file, url, method = "PUT" }) => {
1412
+ try {
1413
+ execSync(`curl -X ${method} -T "${file}" "${url}"`, { encoding: "utf8", timeout: 120000, stdio: ["pipe", "pipe", "pipe"] })
1414
+ return { success: true, uploaded: file, url, method }
1415
+ } catch (e) {
1416
+ return { error: `Не удалось загрузить: ${e.message}` }
1417
+ }
1418
+ },
1419
+ }),
1420
+
1421
+ network_http_server: tool({
1422
+ description: "Запустить HTTP сервер на указанном порту.",
1423
+ inputSchema: z.object({
1424
+ port: z.number().describe("Порт сервера"),
1425
+ dir: z.string().optional().describe("Директория для раздачи"),
1426
+ }),
1427
+ execute: async ({ port, dir = "." }) => {
1428
+ try {
1429
+ const { spawn } = await import("node:child_process")
1430
+ const server = spawn("python", ["-m", "http.server", String(port)], {
1431
+ cwd: dir,
1432
+ detached: true,
1433
+ stdio: "ignore",
1434
+ })
1435
+ server.unref()
1436
+ return { success: true, port, pid: server.pid, dir }
1437
+ } catch (e) {
1438
+ return { error: `Не удалось запустить сервер: ${e.message}` }
1439
+ }
1440
+ },
1441
+ }),
1442
+
1443
+ // ── БАЗЫ ДАННЫХ ──
1444
+ db_mysql: tool({
1445
+ description: "Выполнить MySQL запрос.",
1446
+ inputSchema: z.object({
1447
+ host: z.string().optional().describe("Хост (по умолчанию localhost)"),
1448
+ user: z.string().optional().describe("Пользователь"),
1449
+ password: z.string().optional().describe("Пароль"),
1450
+ database: z.string().optional().describe("База данных"),
1451
+ query: z.string().describe("SQL запрос"),
1452
+ }),
1453
+ execute: async ({ host = "localhost", user = "root", password = "", database = "", query }) => {
1454
+ try {
1455
+ const passArg = password ? `-p"${password}"` : ""
1456
+ const dbArg = database ? `-D ${database}` : ""
1457
+ const out = execSync(`mysql -h ${host} -u ${user} ${passArg} ${dbArg} -e "${query.replace(/"/g, '\\"')}"`, {
1458
+ encoding: "utf8",
1459
+ timeout: 30000,
1460
+ stdio: ["pipe", "pipe", "pipe"],
1461
+ })
1462
+ return { result: out }
1463
+ } catch (e) {
1464
+ return { error: `MySQL ошибка: ${e.message}` }
1465
+ }
1466
+ },
1467
+ }),
1468
+
1469
+ db_postgres: tool({
1470
+ description: "Выполнить PostgreSQL запрос.",
1471
+ inputSchema: z.object({
1472
+ host: z.string().optional().describe("Хост"),
1473
+ user: z.string().optional().describe("Пользователь"),
1474
+ database: z.string().optional().describe("База данных"),
1475
+ query: z.string().describe("SQL запрос"),
1476
+ }),
1477
+ execute: async ({ host = "localhost", user = "postgres", database = "postgres", query }) => {
1478
+ try {
1479
+ const out = execSync(`psql -h ${host} -U ${user} -d ${database} -c "${query.replace(/"/g, '\\"')}"`, {
1480
+ encoding: "utf8",
1481
+ timeout: 30000,
1482
+ stdio: ["pipe", "pipe", "pipe"],
1483
+ })
1484
+ return { result: out }
1485
+ } catch (e) {
1486
+ return { error: `PostgreSQL ошибка: ${e.message}` }
1487
+ }
1488
+ },
1489
+ }),
1490
+
1491
+ db_redis: tool({
1492
+ description: "Выполнить Redis команду.",
1493
+ inputSchema: z.object({
1494
+ host: z.string().optional().describe("Хост"),
1495
+ port: z.number().optional().describe("Порт"),
1496
+ command: z.string().describe("Redis команда"),
1497
+ }),
1498
+ execute: async ({ host = "localhost", port = 6379, command }) => {
1499
+ try {
1500
+ const out = execSync(`redis-cli -h ${host} -p ${port} ${command}`, {
1501
+ encoding: "utf8",
1502
+ timeout: 10000,
1503
+ stdio: ["pipe", "pipe", "pipe"],
1504
+ })
1505
+ return { result: out }
1506
+ } catch (e) {
1507
+ return { error: `Redis ошибка: ${e.message}` }
1508
+ }
1509
+ },
1510
+ }),
1511
+
1512
+ db_mongo: tool({
1513
+ description: "Выполнить MongoDB запрос.",
1514
+ inputSchema: z.object({
1515
+ uri: z.string().optional().describe("URI подключения"),
1516
+ query: z.string().describe("MongoDB команда"),
1517
+ }),
1518
+ execute: async ({ uri = "mongodb://localhost:27017", query }) => {
1519
+ try {
1520
+ const out = execSync(`mongosh "${uri}" --eval "${query.replace(/"/g, '\\"')}"`, {
1521
+ encoding: "utf8",
1522
+ timeout: 30000,
1523
+ stdio: ["pipe", "pipe", "pipe"],
1524
+ })
1525
+ return { result: out }
1526
+ } catch (e) {
1527
+ return { error: `MongoDB ошибка: ${e.message}` }
1528
+ }
1529
+ },
1530
+ }),
1531
+
1532
+ // ── DOCKER ──
1533
+ docker_ps: tool({
1534
+ description: "Показать запущенные Docker контейнеры.",
1535
+ inputSchema: z.object({
1536
+ all: z.boolean().optional().describe("Показать все контейнеры"),
1537
+ }),
1538
+ execute: async ({ all = false }) => {
1539
+ try {
1540
+ const flag = all ? "-a" : ""
1541
+ const out = execSync(`docker ps ${flag}`, { encoding: "utf8", timeout: 10000, stdio: ["pipe", "pipe", "pipe"] })
1542
+ return { containers: out }
1543
+ } catch (e) {
1544
+ return { error: `Docker ошибка: ${e.message}` }
1545
+ }
1546
+ },
1547
+ }),
1548
+
1549
+ docker_run: tool({
1550
+ description: "Запустить Docker контейнер.",
1551
+ inputSchema: z.object({
1552
+ image: z.string().describe("Образ"),
1553
+ name: z.string().optional().describe("Имя контейнера"),
1554
+ ports: z.string().optional().describe("Порты (-p 8080:80)"),
1555
+ detach: z.boolean().optional().describe("Запустить в фоне"),
1556
+ }),
1557
+ execute: async ({ image, name, ports, detach = true }) => {
1558
+ try {
1559
+ let cmd = "docker run"
1560
+ if (detach) cmd += " -d"
1561
+ if (name) cmd += ` --name ${name}`
1562
+ if (ports) cmd += ` -p ${ports}`
1563
+ cmd += ` ${image}`
1564
+ const out = execSync(cmd, { encoding: "utf8", timeout: 120000, stdio: ["pipe", "pipe", "pipe"] })
1565
+ return { success: true, containerId: out.trim(), image }
1566
+ } catch (e) {
1567
+ return { error: `Docker ошибка: ${e.message}` }
1568
+ }
1569
+ },
1570
+ }),
1571
+
1572
+ docker_stop: tool({
1573
+ description: "Остановить Docker контейнер.",
1574
+ inputSchema: z.object({
1575
+ container: z.string().describe("ID или имя контейнера"),
1576
+ }),
1577
+ execute: async ({ container }) => {
1578
+ try {
1579
+ execSync(`docker stop ${container}`, { encoding: "utf8", timeout: 30000, stdio: ["pipe", "pipe", "pipe"] })
1580
+ return { success: true, stopped: container }
1581
+ } catch (e) {
1582
+ return { error: `Docker ошибка: ${e.message}` }
1583
+ }
1584
+ },
1585
+ }),
1586
+
1587
+ docker_logs: tool({
1588
+ description: "Показать логи Docker контейнера.",
1589
+ inputSchema: z.object({
1590
+ container: z.string().describe("ID или имя контейнера"),
1591
+ lines: z.number().optional().describe("Количество строк"),
1592
+ }),
1593
+ execute: async ({ container, lines = 100 }) => {
1594
+ try {
1595
+ const out = execSync(`docker logs --tail ${lines} ${container}`, { encoding: "utf8", timeout: 10000, stdio: ["pipe", "pipe", "pipe"] })
1596
+ return { logs: out }
1597
+ } catch (e) {
1598
+ return { error: `Docker ошибка: ${e.message}` }
1599
+ }
1600
+ },
1601
+ }),
1602
+
1603
+ docker_exec: tool({
1604
+ description: "Выполнить команду в контейнере.",
1605
+ inputSchema: z.object({
1606
+ container: z.string().describe("ID или имя контейнера"),
1607
+ command: z.string().describe("Команда"),
1608
+ }),
1609
+ execute: async ({ container, command }) => {
1610
+ try {
1611
+ const out = execSync(`docker exec ${container} ${command}`, { encoding: "utf8", timeout: 60000, stdio: ["pipe", "pipe", "pipe"] })
1612
+ return { output: out }
1613
+ } catch (e) {
1614
+ return { error: `Docker ошибка: ${e.message}` }
1615
+ }
1616
+ },
1617
+ }),
1618
+
1619
+ // ── PM2 / NODE.JS ──
1620
+ pm2_list: tool({
1621
+ description: "Показать PM2 процессы.",
1622
+ inputSchema: z.object({}),
1623
+ execute: async () => {
1624
+ try {
1625
+ const out = execSync("pm2 list", { encoding: "utf8", timeout: 10000, stdio: ["pipe", "pipe", "pipe"] })
1626
+ return { processes: out }
1627
+ } catch (e) {
1628
+ return { error: `PM2 ошибка: ${e.message}` }
1629
+ }
1630
+ },
1631
+ }),
1632
+
1633
+ pm2_start: tool({
1634
+ description: "Запустить приложение через PM2.",
1635
+ inputSchema: z.object({
1636
+ script: z.string().describe("Путь к скрипту"),
1637
+ name: z.string().optional().describe("Имя процесса"),
1638
+ }),
1639
+ execute: async ({ script, name }) => {
1640
+ try {
1641
+ let cmd = `pm2 start ${script}`
1642
+ if (name) cmd += ` --name ${name}`
1643
+ const out = execSync(cmd, { encoding: "utf8", timeout: 30000, stdio: ["pipe", "pipe", "pipe"] })
1644
+ return { success: true, output: out }
1645
+ } catch (e) {
1646
+ return { error: `PM2 ошибка: ${e.message}` }
1647
+ }
1648
+ },
1649
+ }),
1650
+
1651
+ pm2_stop: tool({
1652
+ description: "Остановить PM2 процесс.",
1653
+ inputSchema: z.object({
1654
+ name: z.string().describe("Имя или ID процесса"),
1655
+ }),
1656
+ execute: async ({ name }) => {
1657
+ try {
1658
+ execSync(`pm2 stop ${name}`, { encoding: "utf8", timeout: 10000, stdio: ["pipe", "pipe", "pipe"] })
1659
+ return { success: true, stopped: name }
1660
+ } catch (e) {
1661
+ return { error: `PM2 ошибка: ${e.message}` }
1662
+ }
1663
+ },
1664
+ }),
1665
+
1666
+ pm2_logs: tool({
1667
+ description: "Показать логи PM2.",
1668
+ inputSchema: z.object({
1669
+ name: z.string().optional().describe("Имя процесса"),
1670
+ lines: z.number().optional().describe("Количество строк"),
1671
+ }),
1672
+ execute: async ({ name, lines = 50 }) => {
1673
+ try {
1674
+ const target = name || ""
1675
+ const out = execSync(`pm2 logs ${target} --lines ${lines} --nostream`, { encoding: "utf8", timeout: 10000, stdio: ["pipe", "pipe", "pipe"] })
1676
+ return { logs: out }
1677
+ } catch (e) {
1678
+ return { error: `PM2 ошибка: ${e.message}` }
1679
+ }
1680
+ },
1681
+ }),
1682
+
1683
+ // ── SYSTEMD / СЛУЖБЫ WINDOWS ──
1684
+ service_list: tool({
1685
+ description: "Показать запущенные службы Windows.",
1686
+ inputSchema: z.object({}),
1687
+ execute: async () => {
1688
+ try {
1689
+ const out = execSync("net start", { encoding: "utf8", timeout: 10000, stdio: ["pipe", "pipe", "pipe"] })
1690
+ const services = out.split("\n").filter(l => l.trim()).slice(0, 50)
1691
+ return { services, total: services.length }
1692
+ } catch (e) {
1693
+ return { error: "Не удалось получить службы" }
1694
+ }
1695
+ },
1696
+ }),
1697
+
1698
+ service_start: tool({
1699
+ description: "Запустить службу Windows.",
1700
+ inputSchema: z.object({
1701
+ name: z.string().describe("Имя службы"),
1702
+ }),
1703
+ execute: async ({ name }) => {
1704
+ try {
1705
+ execSync(`net start "${name}"`, { encoding: "utf8", timeout: 30000, stdio: ["pipe", "pipe", "pipe"] })
1706
+ return { success: true, started: name }
1707
+ } catch (e) {
1708
+ return { error: `Не удалось запустить: ${e.message}` }
1709
+ }
1710
+ },
1711
+ }),
1712
+
1713
+ service_stop: tool({
1714
+ description: "Остановить службу Windows.",
1715
+ inputSchema: z.object({
1716
+ name: z.string().describe("Имя службы"),
1717
+ }),
1718
+ execute: async ({ name }) => {
1719
+ try {
1720
+ execSync(`net stop "${name}"`, { encoding: "utf8", timeout: 30000, stdio: ["pipe", "pipe", "pipe"] })
1721
+ return { success: true, stopped: name }
1722
+ } catch (e) {
1723
+ return { error: `Не удалось остановить: ${e.message}` }
1724
+ }
1725
+ },
1726
+ }),
1727
+
1728
+ // ── РЕЕСТР ──
1729
+ registry_read: tool({
1730
+ description: "Прочитать значение из реестра Windows.",
1731
+ inputSchema: z.object({
1732
+ key: z.string().describe("Путь к ключу (HKLM\\SOFTWARE\\...)"),
1733
+ value: z.string().optional().describe("Имя значения"),
1734
+ }),
1735
+ execute: async ({ key, value }) => {
1736
+ try {
1737
+ const valArg = value ? `/v "${value}"` : ""
1738
+ const out = execSync(`reg query "${key}" ${valArg}`, { encoding: "utf8", timeout: 10000, stdio: ["pipe", "pipe", "pipe"] })
1739
+ return { result: out }
1740
+ } catch (e) {
1741
+ return { error: `Не удалось прочитать реестр: ${e.message}` }
1742
+ }
1743
+ },
1744
+ }),
1745
+
1746
+ registry_write: tool({
1747
+ description: "Записать значение в реестр Windows.",
1748
+ inputSchema: z.object({
1749
+ key: z.string().describe("Путь к ключу"),
1750
+ value: z.string().describe("Имя значения"),
1751
+ type: z.string().optional().describe("Тип (REG_SZ, REG_DWORD, etc)"),
1752
+ data: z.string().describe("Данные"),
1753
+ }),
1754
+ execute: async ({ key, value, type = "REG_SZ", data }) => {
1755
+ try {
1756
+ execSync(`reg add "${key}" /v "${value}" /t ${type} /d "${data}" /f`, { encoding: "utf8", timeout: 10000, stdio: ["pipe", "pipe", "pipe"] })
1757
+ return { success: true, key, value, data }
1758
+ } catch (e) {
1759
+ return { error: `Не удалось записать в реестр: ${e.message}` }
1760
+ }
1761
+ },
1762
+ }),
1763
+
1764
+ // ── СКРИПТЫ И АВТОМАТИЗАЦИЯ ──
1765
+ script_run: tool({
1766
+ description: "Запустить скрипт (.ps1, .bat, .cmd, .sh, .py, .js).",
1767
+ inputSchema: z.object({
1768
+ path: z.string().describe("Путь к скрипту"),
1769
+ args: z.string().optional().describe("Аргументы"),
1770
+ }),
1771
+ execute: async ({ path: p, args = "" }) => {
1772
+ try {
1773
+ const ext = p.split(".").pop().toLowerCase()
1774
+ const runners = {
1775
+ ps1: "powershell -ExecutionPolicy Bypass -File",
1776
+ bat: "",
1777
+ cmd: "",
1778
+ sh: "bash",
1779
+ py: "python",
1780
+ js: "node",
1781
+ mjs: "node",
1782
+ }
1783
+ const runner = runners[ext] || ""
1784
+ const cmd = runner ? `${runner} "${p}" ${args}` : `"${p}" ${args}`
1785
+ const out = execSync(cmd, { encoding: "utf8", timeout: 120000, stdio: ["pipe", "pipe", "pipe"] })
1786
+ return { output: out, script: p }
1787
+ } catch (e) {
1788
+ return { error: `Ошибка скрипта: ${e.message}` }
1789
+ }
1790
+ },
1791
+ }),
1792
+
1793
+ script_schedule: tool({
1794
+ description: "Запланировать выполнение скрипта по расписанию.",
1795
+ inputSchema: z.object({
1796
+ script: z.string().describe("Путь к скрипту"),
1797
+ schedule: z.string().describe("Расписание (daily, weekly, once, onlogon)"),
1798
+ time: z.string().optional().describe("Время (HH:MM)"),
1799
+ name: z.string().optional().describe("Имя задачи"),
1800
+ }),
1801
+ execute: async ({ script, schedule, time = "09:00", name }) => {
1802
+ try {
1803
+ const taskName = name || `Stella_${Date.now()}`
1804
+ execSync(`schtasks /create /tn "${taskName}" /tr "${script}" /sc ${schedule} /st ${time} /f`, {
1805
+ encoding: "utf8",
1806
+ timeout: 10000,
1807
+ stdio: ["pipe", "pipe", "pipe"],
1808
+ })
1809
+ return { success: true, taskName, script, schedule, time }
1810
+ } catch (e) {
1811
+ return { error: `Не удалось запланировать: ${e.message}` }
1812
+ }
1813
+ },
1814
+ }),
1815
+
1816
+ script_cron: tool({
1817
+ description: "Показать/добавить cron запись (Linux/WSL).",
1818
+ inputSchema: z.object({
1819
+ action: z.enum(["list", "add", "remove"]).describe("Действие"),
1820
+ entry: z.string().optional().describe("Cron запись (для add)"),
1821
+ }),
1822
+ execute: async ({ action, entry }) => {
1823
+ try {
1824
+ if (action === "list") {
1825
+ const out = execSync("crontab -l", { encoding: "utf8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"] })
1826
+ return { crontab: out }
1827
+ }
1828
+ if (action === "add" && entry) {
1829
+ execSync(`(crontab -l 2>/dev/null; echo "${entry}") | crontab -`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
1830
+ return { success: true, added: entry }
1831
+ }
1832
+ return { error: "Неверные аргументы" }
1833
+ } catch (e) {
1834
+ return { error: `Cron ошибка: ${e.message}` }
1835
+ }
1836
+ },
1837
+ }),
1838
+
1839
+ // ── МОНИТОРИНГ ──
1840
+ monitor_cpu: tool({
1841
+ description: "Мониторинг CPU в реальном времени.",
1842
+ inputSchema: z.object({
1843
+ duration: z.number().optional().describe("Длительность в секундах"),
1844
+ }),
1845
+ execute: async ({ duration = 5 }) => {
1846
+ try {
1847
+ const out = execSync(`powershell -command "1..${duration} | ForEach-Object { (Get-WmiObject Win32_Processor).LoadMeasure; Start-Sleep 1 }"`, {
1848
+ encoding: "utf8",
1849
+ timeout: (duration + 5) * 1000,
1850
+ stdio: ["pipe", "pipe", "pipe"],
1851
+ })
1852
+ return { cpu_usage: out.split("\n").filter(l => l.trim()) }
1853
+ } catch (e) {
1854
+ return { error: `Не удалось мониторить CPU: ${e.message}` }
1855
+ }
1856
+ },
1857
+ }),
1858
+
1859
+ monitor_disk: tool({
1860
+ description: "Мониторинг дисковой активности.",
1861
+ inputSchema: z.object({}),
1862
+ execute: async () => {
1863
+ try {
1864
+ const out = execSync("wmic diskdrive get size,freespace,model,status", { encoding: "utf8", timeout: 10000, stdio: ["pipe", "pipe", "pipe"] })
1865
+ return { disks: out }
1866
+ } catch (e) {
1867
+ return { error: "Не удалось получить информацию о дисках" }
1868
+ }
1869
+ },
1870
+ }),
1871
+
1872
+ monitor_network: tool({
1873
+ description: "Мониторинг сетевой активности.",
1874
+ inputSchema: z.object({}),
1875
+ execute: async () => {
1876
+ try {
1877
+ const out = execSync("netstat -e", { encoding: "utf8", timeout: 10000, stdio: ["pipe", "pipe", "pipe"] })
1878
+ return { stats: out }
1879
+ } catch (e) {
1880
+ return { error: "Не удалось получить сетевую статистику" }
1881
+ }
1882
+ },
1883
+ }),
1884
+
1885
+ // ── БЕЗОПАСНОСТЬ СЕРВЕРА ──
1886
+ firewall_status: tool({
1887
+ description: "Показать статус файрвола.",
1888
+ inputSchema: z.object({}),
1889
+ execute: async () => {
1890
+ try {
1891
+ const out = execSync("netsh advfirewall show allprofiles state", { encoding: "utf8", timeout: 10000, stdio: ["pipe", "pipe", "pipe"] })
1892
+ return { firewall: out }
1893
+ } catch (e) {
1894
+ return { error: "Не удалось получить статус файрвола" }
1895
+ }
1896
+ },
1897
+ }),
1898
+
1899
+ firewall_block: tool({
1900
+ description: "Заблокировать порт в файрволе.",
1901
+ inputSchema: z.object({
1902
+ port: z.number().describe("Номер порта"),
1903
+ protocol: z.string().optional().describe("TCP или UDP"),
1904
+ }),
1905
+ execute: async ({ port, protocol = "TCP" }) => {
1906
+ try {
1907
+ execSync(`netsh advfirewall firewall add rule name="Block ${port}" dir=in action=block protocol=${protocol} localport=${port}`, {
1908
+ encoding: "utf8",
1909
+ timeout: 10000,
1910
+ stdio: ["pipe", "pipe", "pipe"],
1911
+ })
1912
+ return { success: true, blocked: port, protocol }
1913
+ } catch (e) {
1914
+ return { error: `Не удалось заблокировать: ${e.message}` }
1915
+ }
1916
+ },
1917
+ }),
1918
+
1919
+ firewall_allow: tool({
1920
+ description: "Разрешить порт в файрволе.",
1921
+ inputSchema: z.object({
1922
+ port: z.number().describe("Номер порта"),
1923
+ protocol: z.string().optional().describe("TCP или UDP"),
1924
+ }),
1925
+ execute: async ({ port, protocol = "TCP" }) => {
1926
+ try {
1927
+ execSync(`netsh advfirewall firewall add rule name="Allow ${port}" dir=in action=allow protocol=${protocol} localport=${port}`, {
1928
+ encoding: "utf8",
1929
+ timeout: 10000,
1930
+ stdio: ["pipe", "pipe", "pipe"],
1931
+ })
1932
+ return { success: true, allowed: port, protocol }
1933
+ } catch (e) {
1934
+ return { error: `Не удалось разрешить: ${e.message}` }
1935
+ }
1936
+ },
1937
+ }),
1938
+
1939
+ // ── SSH ──
1940
+ ssh_connect: tool({
1941
+ description: "Подключиться по SSH к серверу.",
1942
+ inputSchema: z.object({
1943
+ host: z.string().describe("Хост"),
1944
+ user: z.string().optional().describe("Пользователь"),
1945
+ port: z.number().optional().describe("Порт"),
1946
+ command: z.string().optional().describe("Команда для выполнения"),
1947
+ }),
1948
+ execute: async ({ host, user = "root", port = 22, command }) => {
1949
+ try {
1950
+ const cmd = command
1951
+ ? `ssh -p ${port} ${user}@${host} "${command}"`
1952
+ : `ssh -p ${port} ${user}@${host}`
1953
+ const out = execSync(cmd, { encoding: "utf8", timeout: 60000, stdio: ["pipe", "pipe", "pipe"] })
1954
+ return { output: out, host, user }
1955
+ } catch (e) {
1956
+ return { error: `SSH ошибка: ${e.message}` }
1957
+ }
1958
+ },
1959
+ }),
1960
+
1961
+ ssh_copy: tool({
1962
+ description: "Копировать файлы по SCP.",
1963
+ inputSchema: z.object({
1964
+ local: z.string().describe("Локальный путь"),
1965
+ remote: z.string().describe("Удалённый путь (user@host:path)"),
1966
+ }),
1967
+ execute: async ({ local, remote }) => {
1968
+ try {
1969
+ execSync(`scp "${local}" ${remote}`, { encoding: "utf8", timeout: 120000, stdio: ["pipe", "pipe", "pipe"] })
1970
+ return { success: true, local, remote }
1971
+ } catch (e) {
1972
+ return { error: `SCP ошибка: ${e.message}` }
1973
+ }
1974
+ },
1975
+ }),
1976
+
1977
+ // ── NPM / YARN / PNPM ──
1978
+ npm_scripts: tool({
1979
+ description: "Показать npm скрипты проекта.",
1980
+ inputSchema: z.object({}),
1981
+ execute: async () => {
1982
+ try {
1983
+ const pkgPath = path.join(process.cwd(), "package.json")
1984
+ if (!fs.existsSync(pkgPath)) return { error: "package.json не найден" }
1985
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"))
1986
+ return { scripts: pkg.scripts || {}, name: pkg.name }
1987
+ } catch (e) {
1988
+ return { error: `Ошибка: ${e.message}` }
1989
+ }
1990
+ },
1991
+ }),
1992
+
1993
+ npm_install: tool({
1994
+ description: "Установить npm зависимости.",
1995
+ inputSchema: z.object({
1996
+ package: z.string().optional().describe("Имя пакета (пусто = все)"),
1997
+ dev: z.boolean().optional().describe("Dev зависимости"),
1998
+ }),
1999
+ execute: async ({ package: pkg, dev }) => {
2000
+ try {
2001
+ const pkgManager = fs.existsSync(path.join(process.cwd(), "pnpm-lock.yaml")) ? "pnpm"
2002
+ : fs.existsSync(path.join(process.cwd(), "yarn.lock")) ? "yarn" : "npm"
2003
+ let cmd = `${pkgManager} install`
2004
+ if (pkg) cmd += ` ${pkg}`
2005
+ if (dev && pkgManager === "npm") cmd += " --save-dev"
2006
+ const out = execSync(cmd, { encoding: "utf8", timeout: 300000, stdio: ["pipe", "pipe", "pipe"] })
2007
+ return { success: true, manager: pkgManager, output: out.slice(-500) }
2008
+ } catch (e) {
2009
+ return { error: `Ошибка установки: ${e.message}` }
2010
+ }
2011
+ },
2012
+ }),
2013
+
2014
+ npm_run: tool({
2015
+ description: "Запустить npm скрипт.",
2016
+ inputSchema: z.object({
2017
+ script: z.string().describe("Имя скрипта"),
2018
+ }),
2019
+ execute: async ({ script }) => {
2020
+ try {
2021
+ const pkgManager = fs.existsSync(path.join(process.cwd(), "pnpm-lock.yaml")) ? "pnpm"
2022
+ : fs.existsSync(path.join(process.cwd(), "yarn.lock")) ? "yarn" : "npm"
2023
+ const out = execSync(`${pkgManager} run ${script}`, { encoding: "utf8", timeout: 300000, stdio: ["pipe", "pipe", "pipe"] })
2024
+ return { output: out, script }
2025
+ } catch (e) {
2026
+ return { error: `Ошибка: ${e.message}` }
2027
+ }
2028
+ },
2029
+ }),
2030
+
2031
+ // ── GIT SERVER ──
2032
+ git_remote_add: tool({
2033
+ description: "Добавить git remote.",
2034
+ inputSchema: z.object({
2035
+ name: z.string().describe("Имя remote"),
2036
+ url: z.string().describe("URL репозитория"),
2037
+ }),
2038
+ execute: async ({ name, url }) => {
2039
+ try {
2040
+ execSync(`git remote add ${name} ${url}`, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] })
2041
+ return { success: true, name, url }
2042
+ } catch (e) {
2043
+ return { error: `Ошибка: ${e.message}` }
2044
+ }
2045
+ },
2046
+ }),
2047
+
2048
+ git_push_force: tool({
2049
+ description: "Force push (опасно!).",
2050
+ inputSchema: z.object({
2051
+ branch: z.string().optional().describe("Ветка"),
2052
+ }),
2053
+ execute: async ({ branch = "main" }) => {
2054
+ try {
2055
+ execSync(`git push --force origin ${branch}`, { encoding: "utf8", timeout: 60000, stdio: ["pipe", "pipe", "pipe"] })
2056
+ return { success: true, branch, force: true }
2057
+ } catch (e) {
2058
+ return { error: `Ошибка: ${e.message}` }
2059
+ }
2060
+ },
2061
+ }),
2062
+
2063
+ git_clone_ssh: tool({
2064
+ description: "Клонировать по SSH.",
2065
+ inputSchema: z.object({
2066
+ repo: z.string().describe("SSH URL репозитория"),
2067
+ dir: z.string().optional().describe("Директория"),
2068
+ }),
2069
+ execute: async ({ repo, dir }) => {
2070
+ try {
2071
+ const cmd = dir ? `git clone ${repo} ${dir}` : `git clone ${repo}`
2072
+ execSync(cmd, { encoding: "utf8", timeout: 120000, stdio: ["pipe", "pipe", "pipe"] })
2073
+ return { success: true, repo }
2074
+ } catch (e) {
2075
+ return { error: `Ошибка: ${e.message}` }
2076
+ }
2077
+ },
2078
+ }),
2079
+
2080
+ // ── ПОЛНЫЙ ДОСТУП К СИСТЕМЕ ──
2081
+ system_command: tool({
2082
+ description: "Выполнить ЛЮБУЮ системную команду. Полный доступ.",
2083
+ inputSchema: z.object({
2084
+ command: z.string().describe("Любая команда ОС"),
2085
+ elevated: z.boolean().optional().describe("Запуск от администратора"),
2086
+ }),
2087
+ execute: async ({ command, elevated = false }) => {
2088
+ try {
2089
+ const cmd = elevated
2090
+ ? `powershell -Command "Start-Process cmd -ArgumentList '/c ${command.replace(/"/g, '\\"')}' -Verb RunAs -Wait"`
2091
+ : command
2092
+ const out = execSync(cmd, {
2093
+ encoding: "utf8",
2094
+ timeout: 300000,
2095
+ maxBuffer: 100 * 1024 * 1024,
2096
+ stdio: ["pipe", "pipe", "pipe"],
2097
+ })
2098
+ return { output: out || "(пустой вывод)", exitCode: 0 }
2099
+ } catch (e) {
2100
+ return {
2101
+ output: String(e.stdout || ""),
2102
+ error: String(e.stderr || e.message),
2103
+ exitCode: e.status ?? 1,
2104
+ }
2105
+ }
2106
+ },
2107
+ }),
2108
+
2109
+ // ═══════════════════════════════════════════════════════════════
2110
+ // УМНЫЙ ДОМ — SONY TV, HDMI-CEC, SMART DEVICES
2111
+ // ═══════════════════════════════════════════════════════════════
2112
+
2113
+ // ── SONY BRAVIA TV ──
2114
+ sony_tv_discover: tool({
2115
+ description: "Найти Sony TV в локальной сети (SSDP/UPnP).",
2116
+ inputSchema: z.object({}),
2117
+ execute: async () => {
2118
+ try {
2119
+ // SSDP M-SEARCH for Sony BRAVIA
2120
+ const dgram = await import("node:dgram")
2121
+ const socket = dgram.createSocket("udp4")
2122
+ const msg = [
2123
+ "M-SEARCH * HTTP/1.1",
2124
+ "HOST: 239.255.255.250:1900",
2125
+ "MAN: \"ssdp:discover\"",
2126
+ "ST: urn:schemas-upnp-org:device:Basic:1",
2127
+ "MX: 3",
2128
+ "",
2129
+ "",
2130
+ ].join("\r\n")
2131
+
2132
+ return new Promise((resolve) => {
2133
+ const found = []
2134
+ socket.on("message", (buf, rinfo) => {
2135
+ const data = buf.toString()
2136
+ if (data.includes("Sony") || data.includes("BRAVIA") || data.includes("XBR") || data.includes("KD-") || data.includes("KJ-")) {
2137
+ found.push({ ip: rinfo.address, response: data.slice(0, 200) })
2138
+ }
2139
+ })
2140
+ socket.send(msg, 1900, "239.255.255.250")
2141
+ setTimeout(() => {
2142
+ socket.close()
2143
+ resolve({ devices: found, total: found.length })
2144
+ }, 4000)
2145
+ })
2146
+ } catch (e) {
2147
+ return { error: `Не удалось найти TV: ${e.message}` }
2148
+ }
2149
+ },
2150
+ }),
2151
+
2152
+ sony_tv_command: tool({
2153
+ description: "Управление Sony BRAVIA TV через REST API. Включи/выключи/переключи канал/громкость.",
2154
+ inputSchema: z.object({
2155
+ ip: z.string().describe("IP адрес TV"),
2156
+ command: z.string().describe("Команда: power, volume_up, volume_down, mute, channel_up, channel_down, input, back, home, up, down, left, right, confirm"),
2157
+ value: z.string().optional().describe("Значение (для channel, volume, input)"),
2158
+ psk: z.string().optional().describe("Pre-Shared Key (если настроен)"),
2159
+ }),
2160
+ execute: async ({ ip, command, value, psk }) => {
2161
+ try {
2162
+ // Sony BRAVIA REST API
2163
+ const commands = {
2164
+ power: { method: "setPowerStatus", params: [{ status: false }] },
2165
+ power_on: { method: "setPowerStatus", params: [{ status: true }] },
2166
+ volume_up: { method: "setAudioVolume", params: [{ volume: "+1", ui: "on" }] },
2167
+ volume_down: { method: "setAudioVolume", params: [{ volume: "-1", ui: "on" }] },
2168
+ volume_set: { method: "setAudioVolume", params: [{ volume: value || "10", ui: "on" }] },
2169
+ mute: { method: "setAudioMute", params: [{ mute: true }] },
2170
+ unmute: { method: "setAudioMute", params: [{ mute: false }] },
2171
+ channel_up: { method: "setChannelPosition", params: [{ position: "+1" }] },
2172
+ channel_down: { method: "setChannelPosition", params: [{ position: "-1" }] },
2173
+ channel: { method: "setChannelPosition", params: [{ position: value || "1" }] },
2174
+ input: { method: "setPlayContent", params: [{ uri: `extInput:hdmi?port=${value || "1"}` }] },
2175
+ hdmi1: { method: "setPlayContent", params: [{ uri: "extInput:hdmi?port=1" }] },
2176
+ hdmi2: { method: "setPlayContent", params: [{ uri: "extInput:hdmi?port=2" }] },
2177
+ hdmi3: { method: "setPlayContent", params: [{ uri: "extInput:hdmi?port=3" }] },
2178
+ tv: { method: "setPlayContent", params: [{ uri: "tv:dvbt" }] },
2179
+ back: { method: "setPlayContent", params: [{ uri: "action:back" }] },
2180
+ home: { method: "setPlayContent", params: [{ uri: "action:home" }] },
2181
+ up: { method: "setPlayContent", params: [{ uri: "action:up" }] },
2182
+ down: { method: "setPlayContent", params: [{ uri: "action:down" }] },
2183
+ left: { method: "setPlayContent", params: [{ uri: "action:left" }] },
2184
+ right: { method: "setPlayContent", params: [{ uri: "action:right" }] },
2185
+ confirm: { method: "setPlayContent", params: [{ uri: "action:confirm" }] },
2186
+ pause: { method: "setPlayContent", params: [{ uri: "action:pause" }] },
2187
+ play: { method: "setPlayContent", params: [{ uri: "action:play" }] },
2188
+ stop: { method: "setPlayContent", params: [{ uri: "action:stop" }] },
2189
+ }
2190
+
2191
+ const cmd = commands[command] || commands[command + "_on"]
2192
+ if (!cmd) return { error: `Неизвестная команда: ${command}` }
2193
+
2194
+ const authHeader = psk ? { "X-Auth-PSK": psk } : {}
2195
+ const url = `http://${ip}/sony/videoControl`
2196
+
2197
+ // First try to get the API version
2198
+ const verRes = await fetch(`http://${ip}/sony/videoControl`, {
2199
+ method: "POST",
2200
+ headers: { "Content-Type": "application/json", ...authHeader },
2201
+ body: JSON.stringify({ method: "getVersions", params: [] }),
2202
+ signal: AbortSignal.timeout(5000),
2203
+ }).catch(() => null)
2204
+
2205
+ // Send the command
2206
+ const res = await fetch(url, {
2207
+ method: "POST",
2208
+ headers: { "Content-Type": "application/json", ...authHeader },
2209
+ body: JSON.stringify({
2210
+ method: cmd.method,
2211
+ params: cmd.params,
2212
+ id: 1,
2213
+ version: "1.0",
2214
+ }),
2215
+ signal: AbortSignal.timeout(5000),
2216
+ })
2217
+
2218
+ const data = await res.json().catch(() => ({}))
2219
+
2220
+ if (data.error && data.error.length > 0) {
2221
+ return { success: false, error: data.error, command }
2222
+ }
2223
+
2224
+ return { success: true, command, response: data }
2225
+ } catch (e) {
2226
+ return { error: `Ошибка Sony TV: ${e.message}` }
2227
+ }
2228
+ },
2229
+ }),
2230
+
2231
+ sony_tv_info: tool({
2232
+ description: "Получить информацию о Sony TV (модель, статус, источник).",
2233
+ inputSchema: z.object({
2234
+ ip: z.string().describe("IP адрес TV"),
2235
+ psk: z.string().optional().describe("Pre-Shared Key"),
2236
+ }),
2237
+ execute: async ({ ip, psk }) => {
2238
+ try {
2239
+ const authHeader = psk ? { "X-Auth-PSK": psk } : {}
2240
+ const url = `http://${ip}/sony/videoControl`
2241
+
2242
+ // Get power status
2243
+ const powerRes = await fetch(url, {
2244
+ method: "POST",
2245
+ headers: { "Content-Type": "application/json", ...authHeader },
2246
+ body: JSON.stringify({ method: "getPowerStatus", params: [], id: 1 }),
2247
+ signal: AbortSignal.timeout(5000),
2248
+ })
2249
+ const power = await powerRes.json().catch(() => ({}))
2250
+
2251
+ // Get current content
2252
+ const contentRes = await fetch(url, {
2253
+ method: "POST",
2254
+ headers: { "Content-Type": "application/json", ...authHeader },
2255
+ body: JSON.stringify({ method: "getCurrentExternalInputsStatus", params: [], id: 2 }),
2256
+ signal: AbortSignal.timeout(5000),
2257
+ })
2258
+ const content = await contentRes.json().catch(() => ({}))
2259
+
2260
+ // Get volume
2261
+ const volRes = await fetch(url, {
2262
+ method: "POST",
2263
+ headers: { "Content-Type": "application/json", ...authHeader },
2264
+ body: JSON.stringify({ method: "getVolumeInformation", params: [], id: 3 }),
2265
+ signal: AbortSignal.timeout(5000),
2266
+ })
2267
+ const volume = await volRes.json().catch(() => ({}))
2268
+
2269
+ return {
2270
+ ip,
2271
+ power: power.result?.[0]?.status || "unknown",
2272
+ inputs: content.result || [],
2273
+ volume: volume.result?.[0] || {},
2274
+ }
2275
+ } catch (e) {
2276
+ return { error: `Не удалось получить инфо: ${e.message}` }
2277
+ }
2278
+ },
2279
+ }),
2280
+
2281
+ // ── HDMI-CEC (любое устройство) ──
2282
+ hdmi_cec_send: tool({
2283
+ description: "Отправить HDMI-CEC команду (включить/выключить любое устройство с CEC).",
2284
+ inputSchema: z.object({
2285
+ device: z.number().optional().describe("CEC адрес устройства (0=TV, 1-14=устройства)"),
2286
+ command: z.string().describe("Команда: on, off, standby, text, menu, up, down, left, right, select"),
2287
+ }),
2288
+ execute: async ({ device = 0, command }) => {
2289
+ try {
2290
+ // Try using cec-client or python-cec
2291
+ const cecCommands = {
2292
+ on: `echo "on ${device.toString(16).toUpperCase()}" | cec-client -s -d 1`,
2293
+ off: `echo "standby ${device.toString(16).toUpperCase()}" | cec-client -s -d 1`,
2294
+ standby: `echo "standby ${device.toString(16).toUpperCase()}" | cec-client -s -d 1`,
2295
+ }
2296
+
2297
+ const cmd = cecCommands[command]
2298
+ if (!cmd) return { error: `CEC команда не поддерживается: ${command}` }
2299
+
2300
+ execSync(cmd, { encoding: "utf8", timeout: 10000, stdio: ["pipe", "pipe", "pipe"] })
2301
+ return { success: true, command, device }
2302
+ } catch (e) {
2303
+ // Fallback: try powershell WMI
2304
+ try {
2305
+ if (command === "on" || command === "off") {
2306
+ execSync(`powershell -command "Get-CimInstance -Namespace root\\wmi -ClassName WmiMonitorBasicDisplayParams"`, {
2307
+ encoding: "utf8",
2308
+ stdio: "pipe",
2309
+ })
2310
+ }
2311
+ return { success: true, command, note: "CEC может не поддерживаться на этом устройстве" }
2312
+ } catch {
2313
+ return { error: `HDMI-CEC недоступен: ${e.message}` }
2314
+ }
2315
+ }
2316
+ },
2317
+ }),
2318
+
2319
+ // ── SMART HOME ОБЩЕЕ ──
2320
+ smart_device_scan: tool({
2321
+ description: "Сканировать сеть на умные устройства (UPnP/SSDP).",
2322
+ inputSchema: z.object({}),
2323
+ execute: async () => {
2324
+ try {
2325
+ const dgram = await import("node:dgram")
2326
+ const socket = dgram.createSocket("udp4")
2327
+ const msg = [
2328
+ "M-SEARCH * HTTP/1.1",
2329
+ "HOST: 239.255.255.250:1900",
2330
+ "MAN: \"ssdp:discover\"",
2331
+ "ST: ssdp:all",
2332
+ "MX: 3",
2333
+ "", "",
2334
+ ].join("\r\n")
2335
+
2336
+ return new Promise((resolve) => {
2337
+ const found = []
2338
+ const seen = new Set()
2339
+ socket.on("message", (buf, rinfo) => {
2340
+ if (seen.has(rinfo.address)) return
2341
+ seen.add(rinfo.address)
2342
+ const data = buf.toString()
2343
+ const stMatch = data.match(/ST:\s*(.*)/i)
2344
+ const serverMatch = data.match(/Server:\s*(.*)/i)
2345
+ found.push({
2346
+ ip: rinfo.address,
2347
+ type: stMatch?.[1]?.trim() || "unknown",
2348
+ server: serverMatch?.[1]?.trim() || "unknown",
2349
+ })
2350
+ })
2351
+ socket.send(msg, 1900, "239.255.255.250")
2352
+ setTimeout(() => {
2353
+ socket.close()
2354
+ resolve({ devices: found, total: found.length })
2355
+ }, 4000)
2356
+ })
2357
+ } catch (e) {
2358
+ return { error: `Ошибка сканирования: ${e.message}` }
2359
+ }
2360
+ },
2361
+ }),
2362
+
2363
+ smart_light_control: tool({
2364
+ description: "Управление умными лампами (Hue, Yeelight, TP-Link).",
2365
+ inputSchema: z.object({
2366
+ brand: z.string().describe("Бренд: hue, yeelight, tplink"),
2367
+ ip: z.string().optional().describe("IP лампы"),
2368
+ command: z.string().describe("Команда: on, off, dim, color, scene"),
2369
+ value: z.string().optional().describe("Значение (0-100 для яркости, hex для цвета)"),
2370
+ }),
2371
+ execute: async ({ brand, ip, command, value }) => {
2372
+ try {
2373
+ if (brand === "yeelight") {
2374
+ const cmd = {
2375
+ on: '{"id":1,"method":"set_power","params":["on","smooth",500]}',
2376
+ off: '{"id":1,"method":"set_power","params":["off","smooth",500]}',
2377
+ dim: `{"id":1,"method":"set_bright","params":[${value || 50},"smooth",500]}`,
2378
+ color: `{"id":1,"method":"set_rgb","params":[${parseInt(value || "16777215", 16)},"smooth",500]}`,
2379
+ }
2380
+ const net = await import("node:net")
2381
+ return new Promise((resolve) => {
2382
+ const client = net.createConnection({ port: 55443, host: ip }, () => {
2383
+ client.write(cmd[command] || cmd.on + "\r\n")
2384
+ })
2385
+ client.on("data", (data) => {
2386
+ client.destroy()
2387
+ resolve({ success: true, response: data.toString() })
2388
+ })
2389
+ client.on("error", (e) => {
2390
+ resolve({ error: `Yeelight ошибка: ${e.message}` })
2391
+ })
2392
+ setTimeout(() => { client.destroy(); resolve({ success: true }) }, 3000)
2393
+ })
2394
+ }
2395
+ if (brand === "tplink") {
2396
+ // TP-Link smart plug/bulb
2397
+ const cmd = {
2398
+ on: '{"system":{"set_relay_state":{"state":1}}}',
2399
+ off: '{"system":{"set_relay_state":{"state":0}}}',
2400
+ }
2401
+ const net = await import("node:net")
2402
+ // TP-Link uses XOR encryption
2403
+ const encrypt = (data) => {
2404
+ let key = 171
2405
+ const buf = Buffer.alloc(data.length)
2406
+ for (let i = 0; i < data.length; i++) {
2407
+ buf[i] = key ^ data.charCodeAt(i)
2408
+ key = buf[i]
2409
+ }
2410
+ return buf
2411
+ }
2412
+ return new Promise((resolve) => {
2413
+ const client = net.createConnection({ port: 9999, host: ip }, () => {
2414
+ const payload = cmd[command] || cmd.on
2415
+ const encrypted = encrypt(payload)
2416
+ const header = Buffer.alloc(4)
2417
+ header.writeUInt32BE(encrypted.length)
2418
+ client.write(Buffer.concat([header, encrypted]))
2419
+ })
2420
+ client.on("data", () => {
2421
+ client.destroy()
2422
+ resolve({ success: true, command })
2423
+ })
2424
+ client.on("error", (e) => {
2425
+ resolve({ error: `TP-Link ошибка: ${e.message}` })
2426
+ })
2427
+ setTimeout(() => { client.destroy(); resolve({ success: true }) }, 3000)
2428
+ })
2429
+ }
2430
+ return { error: `Бренд ${brand} пока не поддерживается. Доступны: yeelight, tplink` }
2431
+ } catch (e) {
2432
+ return { error: `Ошибка: ${e.message}` }
2433
+ }
2434
+ },
2435
+ }),
2436
+
2437
+ smart_ac_control: tool({
2438
+ description: "Управление кондиционером (Broadlink IR).",
2439
+ inputSchema: z.object({
2440
+ brand: z.string().optional().describe("Бренд кондиционера"),
2441
+ command: z.string().describe("Команда: on, off, temp, mode, fan"),
2442
+ value: z.string().optional().describe("Значение (температура, режим)"),
2443
+ }),
2444
+ execute: async ({ brand, command, value }) => {
2445
+ return {
2446
+ note: "Для IR-управления кондиционером нужен Broadlink RM Pro. Команда: /exec broadlink_cli --host IP --sendir КОД",
2447
+ command,
2448
+ value,
2449
+ }
2450
+ },
2451
+ }),
2452
+
2453
+ // ── CHROMECAST / AIRPLAY ──
2454
+ chromecast_cast: tool({
2455
+ description: "Транслировать на Chromecast.",
2456
+ inputSchema: z.object({
2457
+ ip: z.string().describe("IP Chromecast"),
2458
+ url: z.string().describe("URL для трансляции"),
2459
+ }),
2460
+ execute: async ({ ip, url }) => {
2461
+ try {
2462
+ // Use cast CLI or catt
2463
+ execSync(`catt cast "${url}" -d ${ip}`, { encoding: "utf8", timeout: 30000, stdio: "pipe" })
2464
+ return { success: true, url, device: ip }
2465
+ } catch (e) {
2466
+ return { error: `Chromecast ошибка: ${e.message}. Установи: pip install catt` }
2467
+ }
2468
+ },
2469
+ }),
2470
+
2471
+ // ── WAKE ON LAN ──
2472
+ wake_on_lan: tool({
2473
+ description: "Включить устройство по Wake-on-LAN (MAC адрес).",
2474
+ inputSchema: z.object({
2475
+ mac: z.string().describe("MAC адрес устройства"),
2476
+ }),
2477
+ execute: async ({ mac }) => {
2478
+ try {
2479
+ const cleanMac = mac.replace(/[:-]/g, "")
2480
+ const magicPacket = Buffer.alloc(6 + 16 * 6, 0xFF)
2481
+ for (let i = 0; i < 16; i++) {
2482
+ Buffer.from(cleanMac, "hex").copy(magicPacket, 6 + i * 6)
2483
+ }
2484
+ const dgram = await import("node:dgram")
2485
+ const socket = dgram.createSocket("udp4")
2486
+ return new Promise((resolve) => {
2487
+ socket.send(magicPacket, 9, "255.255.255.255", () => {
2488
+ socket.close()
2489
+ resolve({ success: true, mac, sent: true })
2490
+ })
2491
+ })
2492
+ } catch (e) {
2493
+ return { error: `WOL ошибка: ${e.message}` }
2494
+ }
2495
+ },
2496
+ }),
2497
+
2498
+ // ═══════════════════════════════════════════════════════════════
2499
+ // УПРАВЛЕНИЕ ПРИЛОЖЕНИЯМИ (COM, UI AUTOMATION, VISION)
2500
+ // ═══════════════════════════════════════════════════════════════
2501
+
2502
+ // ── OFFICE: POWERPOINT ──
2503
+ ppt_create: tool({
2504
+ description: "Создать новую презентацию PowerPoint.",
2505
+ inputSchema: z.object({
2506
+ path: z.string().optional().describe("Путь для сохранения"),
2507
+ }),
2508
+ execute: async ({ path: p }) => {
2509
+ try {
2510
+ const savePath = p || "presentation.pptx"
2511
+ const ps = `
2512
+ $ppt = New-Object -ComObject PowerPoint.Application
2513
+ $ppt.Visible = 1
2514
+ $pres = $ppt.Presentations.Add()
2515
+ $pres.SaveAs("${savePath.replace(/\\/g, "\\\\")}")
2516
+ Write-Output "Created: ${savePath}"
2517
+ `
2518
+ execSync(`powershell -ExecutionPolicy Bypass -Command "${ps.replace(/"/g, '\\"')}"`, {
2519
+ encoding: "utf8", timeout: 30000, stdio: ["pipe", "pipe", "pipe"],
2520
+ })
2521
+ return { success: true, path: savePath }
2522
+ } catch (e) {
2523
+ return { error: `PowerPoint ошибка: ${e.message}` }
2524
+ }
2525
+ },
2526
+ }),
2527
+
2528
+ ppt_add_slide: tool({
2529
+ description: "Добавить слайд в презентацию PowerPoint.",
2530
+ inputSchema: z.object({
2531
+ path: z.string().describe("Путь к презентации"),
2532
+ layout: z.number().optional().describe("Тип слайда (1=Title, 2=Title+Content, 3=TitleOnly, 7=Blank)"),
2533
+ title: z.string().optional().describe("Заголовок слайда"),
2534
+ content: z.string().optional().describe("Содержимое слайда"),
2535
+ }),
2536
+ execute: async ({ path: p, layout = 2, title, content }) => {
2537
+ try {
2538
+ const ps = `
2539
+ $ppt = New-Object -ComObject PowerPoint.Application
2540
+ $ppt.Visible = 1
2541
+ $pres = $ppt.Presentations.Open("${p.replace(/\\/g, "\\\\")}")
2542
+ $slide = $pres.Slides.Add($pres.Slides.Count + 1, ${layout})
2543
+ ${title ? `$slide.Shapes.Title.TextFrame.TextRange.Text = "${title.replace(/"/g, '""')}"` : ""}
2544
+ ${content ? `$slide.Shapes[2].TextFrame.TextRange.Text = "${content.replace(/"/g, '""')}"` : ""}
2545
+ $pres.Save()
2546
+ Write-Output "Slide added"
2547
+ `
2548
+ execSync(`powershell -ExecutionPolicy Bypass -Command "${ps.replace(/"/g, '\\"')}"`, {
2549
+ encoding: "utf8", timeout: 30000, stdio: ["pipe", "pipe", "pipe"],
2550
+ })
2551
+ return { success: true, layout, title }
2552
+ } catch (e) {
2553
+ return { error: `PowerPoint ошибка: ${e.message}` }
2554
+ }
2555
+ },
2556
+ }),
2557
+
2558
+ ppt_export: tool({
2559
+ description: "Экспортировать презентацию в PDF.",
2560
+ inputSchema: z.object({
2561
+ path: z.string().describe("Путь к презентации"),
2562
+ output: z.string().optional().describe("Путь для PDF"),
2563
+ }),
2564
+ execute: async ({ path: p, output }) => {
2565
+ try {
2566
+ const pdfPath = output || p.replace(/\.pptx?$/i, ".pdf")
2567
+ const ps = `
2568
+ $ppt = New-Object -ComObject PowerPoint.Application
2569
+ $pres = $ppt.Presentations.Open("${p.replace(/\\/g, "\\\\")}")
2570
+ $pres.ExportAsFixedFormat(${pdfPath.replace(/\\/g, "\\\\")}, 32, 1, 1)
2571
+ $pres.Close()
2572
+ Write-Output "Exported to PDF"
2573
+ `
2574
+ execSync(`powershell -ExecutionPolicy Bypass -Command "${ps.replace(/"/g, '\\"')}"`, {
2575
+ encoding: "utf8", timeout: 60000, stdio: ["pipe", "pipe", "pipe"],
2576
+ })
2577
+ return { success: true, pdf: pdfPath }
2578
+ } catch (e) {
2579
+ return { error: `PowerPoint ошибка: ${e.message}` }
2580
+ }
2581
+ },
2582
+ }),
2583
+
2584
+ // ── OFFICE: WORD ──
2585
+ word_create: tool({
2586
+ description: "Создать новый документ Word.",
2587
+ inputSchema: z.object({
2588
+ path: z.string().optional().describe("Путь для сохранения"),
2589
+ content: z.string().optional().describe("Начальное содержимое"),
2590
+ }),
2591
+ execute: async ({ path: p, content }) => {
2592
+ try {
2593
+ const savePath = p || "document.docx"
2594
+ const ps = `
2595
+ $word = New-Object -ComObject Word.Application
2596
+ $word.Visible = 1
2597
+ $doc = $word.Documents.Add()
2598
+ ${content ? `$doc.Content.Text = "${content.replace(/"/g, '""').replace(/\n/g, "`n")}"` : ""}
2599
+ $doc.SaveAs("${savePath.replace(/\\/g, "\\\\")}")
2600
+ Write-Output "Created: ${savePath}"
2601
+ `
2602
+ execSync(`powershell -ExecutionPolicy Bypass -Command "${ps.replace(/"/g, '\\"')}"`, {
2603
+ encoding: "utf8", timeout: 30000, stdio: ["pipe", "pipe", "pipe"],
2604
+ })
2605
+ return { success: true, path: savePath }
2606
+ } catch (e) {
2607
+ return { error: `Word ошибка: ${e.message}` }
2608
+ }
2609
+ },
2610
+ }),
2611
+
2612
+ word_read: tool({
2613
+ description: "Прочитать текст из документа Word.",
2614
+ inputSchema: z.object({
2615
+ path: z.string().describe("Путь к документу"),
2616
+ }),
2617
+ execute: async ({ path: p }) => {
2618
+ try {
2619
+ const ps = `
2620
+ $word = New-Object -ComObject Word.Application
2621
+ $word.Visible = 0
2622
+ $doc = $word.Documents.Open("${p.replace(/\\/g, "\\\\")}")
2623
+ $text = $doc.Content.Text
2624
+ $doc.Close(0)
2625
+ $word.Quit()
2626
+ Write-Output $text
2627
+ `
2628
+ const out = execSync(`powershell -ExecutionPolicy Bypass -Command "${ps.replace(/"/g, '\\"')}"`, {
2629
+ encoding: "utf8", timeout: 30000, stdio: ["pipe", "pipe", "pipe"],
2630
+ })
2631
+ return { content: out.trim() }
2632
+ } catch (e) {
2633
+ return { error: `Word ошибка: ${e.message}` }
2634
+ }
2635
+ },
2636
+ }),
2637
+
2638
+ word_format: tool({
2639
+ description: "Отформатировать документ Word (шрифт, размер, жирный).",
2640
+ inputSchema: z.object({
2641
+ path: z.string().describe("Путь к документу"),
2642
+ font: z.string().optional().describe("Шрифт"),
2643
+ size: z.number().optional().describe("Размер шрифта"),
2644
+ bold: z.boolean().optional().describe("Жирный текст"),
2645
+ }),
2646
+ execute: async ({ path: p, font, size, bold: b }) => {
2647
+ try {
2648
+ const ps = `
2649
+ $word = New-Object -ComObject Word.Application
2650
+ $word.Visible = 1
2651
+ $doc = $word.Documents.Open("${p.replace(/\\/g, "\\\\")}")
2652
+ $sel = $word.Selection
2653
+ $sel.WholeStory()
2654
+ ${font ? `$sel.Font.Name = "${font}"` : ""}
2655
+ ${size ? `$sel.Font.Size = ${size}` : ""}
2656
+ ${b !== undefined ? `$sel.Font.Bold = ${b ? 1 : 0}` : ""}
2657
+ $doc.Save()
2658
+ Write-Output "Formatted"
2659
+ `
2660
+ execSync(`powershell -ExecutionPolicy Bypass -Command "${ps.replace(/"/g, '\\"')}"`, {
2661
+ encoding: "utf8", timeout: 30000, stdio: ["pipe", "pipe", "pipe"],
2662
+ })
2663
+ return { success: true, font, size, bold: b }
2664
+ } catch (e) {
2665
+ return { error: `Word ошибка: ${e.message}` }
2666
+ }
2667
+ },
2668
+ }),
2669
+
2670
+ // ── OFFICE: EXCEL ──
2671
+ excel_create: tool({
2672
+ description: "Создать новую книгу Excel.",
2673
+ inputSchema: z.object({
2674
+ path: z.string().optional().describe("Путь для сохранения"),
2675
+ }),
2676
+ execute: async ({ path: p }) => {
2677
+ try {
2678
+ const savePath = p || "workbook.xlsx"
2679
+ const ps = `
2680
+ $excel = New-Object -ComObject Excel.Application
2681
+ $excel.Visible = 1
2682
+ $wb = $excel.Workbooks.Add()
2683
+ $wb.SaveAs("${savePath.replace(/\\/g, "\\\\")}")
2684
+ Write-Output "Created: ${savePath}"
2685
+ `
2686
+ execSync(`powershell -ExecutionPolicy Bypass -Command "${ps.replace(/"/g, '\\"')}"`, {
2687
+ encoding: "utf8", timeout: 30000, stdio: ["pipe", "pipe", "pipe"],
2688
+ })
2689
+ return { success: true, path: savePath }
2690
+ } catch (e) {
2691
+ return { error: `Excel ошибка: ${e.message}` }
2692
+ }
2693
+ },
2694
+ }),
2695
+
2696
+ excel_write: tool({
2697
+ description: "Записать данные в ячейку Excel.",
2698
+ inputSchema: z.object({
2699
+ path: z.string().describe("Путь к книге"),
2700
+ sheet: z.number().optional().describe("Номер листа"),
2701
+ cell: z.string().describe("Ячейка (A1, B2)"),
2702
+ value: z.string().describe("Значение"),
2703
+ }),
2704
+ execute: async ({ path: p, sheet = 1, cell, value }) => {
2705
+ try {
2706
+ const ps = `
2707
+ $excel = New-Object -ComObject Excel.Application
2708
+ $excel.Visible = 1
2709
+ $wb = $excel.Workbooks.Open("${p.replace(/\\/g, "\\\\")}")
2710
+ $ws = $wb.Sheets.Item(${sheet})
2711
+ $ws.Range("${cell}").Value = "${value.replace(/"/g, '""')}"
2712
+ $wb.Save()
2713
+ Write-Output "Written to ${cell}"
2714
+ `
2715
+ execSync(`powershell -ExecutionPolicy Bypass -Command "${ps.replace(/"/g, '\\"')}"`, {
2716
+ encoding: "utf8", timeout: 30000, stdio: ["pipe", "pipe", "pipe"],
2717
+ })
2718
+ return { success: true, cell, value }
2719
+ } catch (e) {
2720
+ return { error: `Excel ошибка: ${e.message}` }
2721
+ }
2722
+ },
2723
+ }),
2724
+
2725
+ excel_read: tool({
2726
+ description: "Прочитать данные из ячейки Excel.",
2727
+ inputSchema: z.object({
2728
+ path: z.string().describe("Путь к книге"),
2729
+ sheet: z.number().optional().describe("Номер листа"),
2730
+ cell: z.string().describe("Ячейка (A1)"),
2731
+ }),
2732
+ execute: async ({ path: p, sheet = 1, cell }) => {
2733
+ try {
2734
+ const ps = `
2735
+ $excel = New-Object -ComObject Excel.Application
2736
+ $excel.Visible = 0
2737
+ $wb = $excel.Workbooks.Open("${p.replace(/\\/g, "\\\\")}")
2738
+ $ws = $wb.Sheets.Item(${sheet})
2739
+ $val = $ws.Range("${cell}").Value
2740
+ $wb.Close(0)
2741
+ $excel.Quit()
2742
+ Write-Output $val
2743
+ `
2744
+ const out = execSync(`powershell -ExecutionPolicy Bypass -Command "${ps.replace(/"/g, '\\"')}"`, {
2745
+ encoding: "utf8", timeout: 30000, stdio: ["pipe", "pipe", "pipe"],
2746
+ })
2747
+ return { cell, value: out.trim() }
2748
+ } catch (e) {
2749
+ return { error: `Excel ошибка: ${e.message}` }
2750
+ }
2751
+ },
2752
+ }),
2753
+
2754
+ excel_chart: tool({
2755
+ description: "Создать диаграмму в Excel.",
2756
+ inputSchema: z.object({
2757
+ path: z.string().describe("Путь к книге"),
2758
+ sheet: z.number().optional().describe("Номер листа"),
2759
+ range: z.string().describe("Диапазон данных (A1:B5)"),
2760
+ type: z.number().optional().describe("Тип (1=Столбцы, 2=Линии, 3=Круговая)"),
2761
+ }),
2762
+ execute: async ({ path: p, sheet = 1, range: r, type = 1 }) => {
2763
+ try {
2764
+ const ps = `
2765
+ $excel = New-Object -ComObject Excel.Application
2766
+ $excel.Visible = 1
2767
+ $wb = $excel.Workbooks.Open("${p.replace(/\\/g, "\\\\")}")
2768
+ $ws = $wb.Sheets.Item(${sheet})
2769
+ $chart = $ws.ChartObjects().Add(100, 100, 400, 300).Chart
2770
+ $chart.SetSourceData($ws.Range("${r}"))
2771
+ $chart.ChartType = ${type}
2772
+ $wb.Save()
2773
+ Write-Output "Chart created"
2774
+ `
2775
+ execSync(`powershell -ExecutionPolicy Bypass -Command "${ps.replace(/"/g, '\\"')}"`, {
2776
+ encoding: "utf8", timeout: 30000, stdio: ["pipe", "pipe", "pipe"],
2777
+ })
2778
+ return { success: true, range: r, type }
2779
+ } catch (e) {
2780
+ return { error: `Excel ошибка: ${e.message}` }
2781
+ }
2782
+ },
2783
+ }),
2784
+
2785
+ // ── УПРАВЛЕНИЕ ЛЮБЫМИ ПРИЛОЖЕНИЯМИ ──
2786
+ app_focus: tool({
2787
+ description: "Переключиться на окно приложения.",
2788
+ inputSchema: z.object({
2789
+ name: z.string().describe("Имя процесса или заголовок окна"),
2790
+ }),
2791
+ execute: async ({ name }) => {
2792
+ try {
2793
+ const ps = `
2794
+ $proc = Get-Process | Where-Object {$_.ProcessName -like "*${name}*" -or $_.MainWindowTitle -like "*${name}*"} | Select-Object -First 1
2795
+ if ($proc) {
2796
+ Add-Type -Name Win -Namespace User32 -MemberDefinition '[DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr hWnd);'
2797
+ [User32.Win]::SetForegroundWindow($proc.MainWindowHandle)
2798
+ Write-Output "Focused: $($proc.ProcessName)"
2799
+ } else {
2800
+ Write-Output "Not found: ${name}"
2801
+ }
2802
+ `
2803
+ const out = execSync(`powershell -ExecutionPolicy Bypass -Command "${ps.replace(/"/g, '\\"')}"`, {
2804
+ encoding: "utf8", timeout: 10000, stdio: ["pipe", "pipe", "pipe"],
2805
+ })
2806
+ return { success: true, focused: name, output: out.trim() }
2807
+ } catch (e) {
2808
+ return { error: `Ошибка: ${e.message}` }
2809
+ }
2810
+ },
2811
+ }),
2812
+
2813
+ app_type_text: tool({
2814
+ description: "Набрать текст в активном окне.",
2815
+ inputSchema: z.object({
2816
+ text: z.string().describe("Текст для набора"),
2817
+ delay: z.number().optional().describe("Задержка между символами (мс)"),
2818
+ }),
2819
+ execute: async ({ text, delay = 0 }) => {
2820
+ try {
2821
+ const ps = `
2822
+ Add-Type -AssemblyName System.Windows.Forms
2823
+ ${delay > 0 ? `
2824
+ foreach ($char in "${text.replace(/"/g, '""')}") {
2825
+ [System.Windows.Forms.SendKeys]::SendWait($char)
2826
+ Start-Sleep -Milliseconds ${delay}
2827
+ }
2828
+ ` : `[System.Windows.Forms.SendKeys]::SendWait("${text.replace(/"/g, '""').replace(/\{/g, "{").replace(/\}/g, "}")}")`}
2829
+ `
2830
+ execSync(`powershell -ExecutionPolicy Bypass -Command "${ps.replace(/"/g, '\\"')}"`, {
2831
+ encoding: "utf8", timeout: 60000, stdio: ["pipe", "pipe", "pipe"],
2832
+ })
2833
+ return { success: true, typed: text.length + " символов" }
2834
+ } catch (e) {
2835
+ return { error: `Ошибка: ${e.message}` }
2836
+ }
2837
+ },
2838
+ }),
2839
+
2840
+ app_hotkey: tool({
2841
+ description: "Нажать комбинацию клавиш.",
2842
+ inputSchema: z.object({
2843
+ keys: z.string().describe("Комбинация: ^c (Ctrl+C), ^v, ^s, ^z, ^a, {ENTER}, {TAB}, {F5}, {DELETE}"),
2844
+ }),
2845
+ execute: async ({ keys }) => {
2846
+ try {
2847
+ execSync(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('${keys}')"`, {
2848
+ encoding: "utf8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"],
2849
+ })
2850
+ return { success: true, keys }
2851
+ } catch (e) {
2852
+ return { error: `Ошибка: ${e.message}` }
2853
+ }
2854
+ },
2855
+ }),
2856
+
2857
+ app_click: tool({
2858
+ description: "Кликнуть по координатам экрана.",
2859
+ inputSchema: z.object({
2860
+ x: z.number().describe("X координата"),
2861
+ y: z.number().describe("Y координата"),
2862
+ button: z.enum(["left", "right", "middle"]).optional().describe("Кнопка мыши"),
2863
+ }),
2864
+ execute: async ({ x, y, button = "left" }) => {
2865
+ try {
2866
+ const btn = button === "right" ? 2 : button === "middle" ? 4 : 1
2867
+ const ps = `
2868
+ Add-Type -TypeDefinition '
2869
+ using System; using System.Runtime.InteropServices;
2870
+ public class Mouse {
2871
+ [DllImport("user32.dll")] public static extern void mouse_event(int flags, int dx, int dy, int data, int info);
2872
+ public static void Click(int x, int y, int btn) {
2873
+ System.Windows.Forms.Cursor.Position = new System.Drawing.Point(x, y);
2874
+ mouse_event(btn, 0, 0, 0, 0);
2875
+ mouse_event(btn + 2, 0, 0, 0, 0);
2876
+ }
2877
+ }'
2878
+ [Mouse]::Click(${x}, ${y}, ${btn})
2879
+ `
2880
+ execSync(`powershell -ExecutionPolicy Bypass -Command "${ps.replace(/"/g, '\\"')}"`, {
2881
+ encoding: "utf8", timeout: 10000, stdio: ["pipe", "pipe", "pipe"],
2882
+ })
2883
+ return { success: true, x, y, button }
2884
+ } catch (e) {
2885
+ return { error: `Ошибка: ${e.message}` }
2886
+ }
2887
+ },
2888
+ }),
2889
+
2890
+ app_move_mouse: tool({
2891
+ description: "Переместить курсор мыши.",
2892
+ inputSchema: z.object({
2893
+ x: z.number().describe("X координата"),
2894
+ y: z.number().describe("Y координата"),
2895
+ }),
2896
+ execute: async ({ x, y }) => {
2897
+ try {
2898
+ execSync(`powershell -Command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Cursor.Position] = New-Object System.Drawing.Point(${x}, ${y})"`, {
2899
+ encoding: "utf8", timeout: 5000, stdio: ["pipe", "pipe", "pipe"],
2900
+ })
2901
+ return { success: true, x, y }
2902
+ } catch (e) {
2903
+ return { error: `Ошибка: ${e.message}` }
2904
+ }
2905
+ },
2906
+ }),
2907
+
2908
+ app_screenshot: tool({
2909
+ description: "Сделать скриншот экрана и вернуть путь к файлу.",
2910
+ inputSchema: z.object({
2911
+ path: z.string().optional().describe("Путь для сохранения"),
2912
+ region: z.object({
2913
+ x: z.number(),
2914
+ y: z.number(),
2915
+ width: z.number(),
2916
+ height: z.number(),
2917
+ }).optional().describe("Область экрана"),
2918
+ }),
2919
+ execute: async ({ path: p, region }) => {
2920
+ try {
2921
+ const savePath = p || `screenshot_${Date.now()}.png`
2922
+ let ps = ""
2923
+ if (region) {
2924
+ ps = `
2925
+ Add-Type -AssemblyName System.Windows.Forms
2926
+ Add-Type -AssemblyName System.Drawing
2927
+ $bmp = New-Object System.Drawing.Bitmap(${region.width}, ${region.height})
2928
+ $gfx = [System.Drawing.Graphics]::FromImage($bmp)
2929
+ $gfx.CopyFromScreen(${region.x}, ${region.y}, 0, 0, [System.Drawing.Size]::new(${region.width}, ${region.height}))
2930
+ $bmp.Save("${savePath.replace(/\\/g, "\\\\")}", [System.Drawing.Imaging.ImageFormat]::Png)
2931
+ Write-Output "Saved: ${savePath}"
2932
+ `
2933
+ } else {
2934
+ ps = `
2935
+ Add-Type -AssemblyName System.Windows.Forms
2936
+ Add-Type -AssemblyName System.Drawing
2937
+ $screen = [System.Windows.Forms.Screen]::PrimaryScreen
2938
+ $bmp = New-Object System.Drawing.Bitmap($screen.Bounds.Width, $screen.Bounds.Height)
2939
+ $gfx = [System.Drawing.Graphics]::FromImage($bmp)
2940
+ $gfx.CopyFromScreen($screen.Bounds.Location, [System.Drawing.Point]::Empty, $screen.Bounds.Size)
2941
+ $bmp.Save("${savePath.replace(/\\/g, "\\\\")}", [System.Drawing.Imaging.ImageFormat]::Png)
2942
+ Write-Output "Saved: ${savePath}"
2943
+ `
2944
+ }
2945
+ execSync(`powershell -ExecutionPolicy Bypass -Command "${ps.replace(/"/g, '\\"')}"`, {
2946
+ encoding: "utf8", timeout: 15000, stdio: ["pipe", "pipe", "pipe"],
2947
+ })
2948
+ return { success: true, path: savePath }
2949
+ } catch (e) {
2950
+ return { error: `Ошибка: ${e.message}` }
2951
+ }
2952
+ },
2953
+ }),
2954
+
2955
+ app_find_window: tool({
2956
+ description: "Найти окно по заголовку или классу.",
2957
+ inputSchema: z.object({
2958
+ title: z.string().optional().describe("Заголовок окна"),
2959
+ class: z.string().optional().describe("Класс окна"),
2960
+ }),
2961
+ execute: async ({ title, class: cls }) => {
2962
+ try {
2963
+ const filter = title ? `MainWindowTitle -like "*${title}*"` : `ClassName -like "*${cls}*"`
2964
+ const ps = `
2965
+ $procs = Get-Process | Where-Object { $_.MainWindowHandle -ne 0 -and (${filter}) }
2966
+ foreach ($p in $procs) {
2967
+ Write-Output "$($p.ProcessName) | $($p.MainWindowTitle) | PID:$($p.Id)"
2968
+ }
2969
+ `
2970
+ const out = execSync(`powershell -ExecutionPolicy Bypass -Command "${ps.replace(/"/g, '\\"')}"`, {
2971
+ encoding: "utf8", timeout: 10000, stdio: ["pipe", "pipe", "pipe"],
2972
+ })
2973
+ const windows = out.trim().split("\n").filter(l => l.trim())
2974
+ return { windows, total: windows.length }
2975
+ } catch (e) {
2976
+ return { error: `Ошибка: ${e.message}` }
2977
+ }
2978
+ },
2979
+ }),
2980
+
2981
+ app_list_windows: tool({
2982
+ description: "Показать все открытые окна.",
2983
+ inputSchema: z.object({}),
2984
+ execute: async () => {
2985
+ try {
2986
+ const ps = `
2987
+ Get-Process | Where-Object { $_.MainWindowTitle -ne '' } |
2988
+ Select-Object ProcessName, MainWindowTitle, Id |
2989
+ Format-Table -AutoSize
2990
+ `
2991
+ const out = execSync(`powershell -ExecutionPolicy Bypass -Command "${ps.replace(/"/g, '\\"')}"`, {
2992
+ encoding: "utf8", timeout: 10000, stdio: ["pipe", "pipe", "pipe"],
2993
+ })
2994
+ return { windows: out }
2995
+ } catch (e) {
2996
+ return { error: `Ошибка: ${e.message}` }
2997
+ }
2998
+ },
2999
+ }),
3000
+
3001
+ app_close: tool({
3002
+ description: "Закрыть приложение.",
3003
+ inputSchema: z.object({
3004
+ name: z.string().describe("Имя процесса"),
3005
+ }),
3006
+ execute: async ({ name }) => {
3007
+ try {
3008
+ execSync(`taskkill /IM "${name}.exe" /F`, { encoding: "utf8", timeout: 10000, stdio: ["pipe", "pipe", "pipe"] })
3009
+ return { success: true, closed: name }
3010
+ } catch (e) {
3011
+ return { error: `Ошибка: ${e.message}` }
3012
+ }
3013
+ },
3014
+ }),
3015
+
3016
+ // ── ВИЗУАЛ (СКРИНШОТ + АНАЛИЗ) ──
3017
+ vision_analyze: tool({
3018
+ description: "Сделать скриншот и проанализировать что на экране.",
3019
+ inputSchema: z.object({
3020
+ prompt: z.string().optional().describe("Что анализировать"),
3021
+ }),
3022
+ execute: async ({ prompt }) => {
3023
+ try {
3024
+ const ssPath = `vision_${Date.now()}.png`
3025
+ const ps = `
3026
+ Add-Type -AssemblyName System.Windows.Forms
3027
+ Add-Type -AssemblyName System.Drawing
3028
+ $screen = [System.Windows.Forms.Screen]::PrimaryScreen
3029
+ $bmp = New-Object System.Drawing.Bitmap($screen.Bounds.Width, $screen.Bounds.Height)
3030
+ $gfx = [System.Drawing.Graphics]::FromImage($bmp)
3031
+ $gfx.CopyFromScreen($screen.Bounds.Location, [System.Drawing.Point]::Empty, $screen.Bounds.Size)
3032
+ $bmp.Save("${ssPath.replace(/\\/g, "\\\\")}", [System.Drawing.Imaging.ImageFormat]::Png)
3033
+ `
3034
+ execSync(`powershell -ExecutionPolicy Bypass -Command "${ps.replace(/"/g, '\\"')}"`, {
3035
+ encoding: "utf8", timeout: 15000, stdio: ["pipe", "pipe", "pipe"],
3036
+ })
3037
+ return { screenshot: ssPath, prompt: prompt || "Опиши что на экране" }
3038
+ } catch (e) {
3039
+ return { error: `Ошибка: ${e.message}` }
3040
+ }
3041
+ },
3042
+ }),
3043
+
3044
+ // ── NOTEPAD++ / БЛОКНОТ ──
3045
+ notepad_open: tool({
3046
+ description: "Открыть файл в Блокноте или Notepad++.",
3047
+ inputSchema: z.object({
3048
+ path: z.string().describe("Путь к файлу"),
3049
+ editor: z.enum(["notepad", "notepad++", "code"]).optional().describe("Редактор"),
3050
+ }),
3051
+ execute: async ({ path: p, editor = "notepad" }) => {
3052
+ try {
3053
+ const editors = {
3054
+ notepad: "notepad.exe",
3055
+ "notepad++": '"C:\\Program Files\\Notepad++\\notepad++.exe"',
3056
+ code: "code.cmd",
3057
+ }
3058
+ execSync(`start "" ${editors[editor]} "${p}"`, { shell: "cmd.exe", stdio: "pipe" })
3059
+ return { success: true, opened: p, editor }
3060
+ } catch (e) {
3061
+ return { error: `Ошибка: ${e.message}` }
3062
+ }
3063
+ },
3064
+ }),
3065
+
3066
+ // ── БРАУЗЕР АВТОМАТИЗАЦИЯ ──
3067
+ browser_open: tool({
3068
+ description: "Открыть URL в браузере.",
3069
+ inputSchema: z.object({
3070
+ url: z.string().describe("URL"),
3071
+ browser: z.string().optional().describe("Браузер (chrome, edge, firefox)"),
3072
+ }),
3073
+ execute: async ({ url, browser: b = "chrome" }) => {
3074
+ try {
3075
+ const browsers = { chrome: "chrome.exe", edge: "msedge.exe", firefox: "firefox.exe" }
3076
+ execSync(`start "" "${browsers[b]}" "${url}"`, { shell: "cmd.exe", stdio: "pipe" })
3077
+ return { success: true, url, browser: b }
3078
+ } catch (e) {
3079
+ return { error: `Ошибка: ${e.message}` }
3080
+ }
3081
+ },
3082
+ }),
3083
+
3084
+ browser_tabs: tool({
3085
+ description: "Показать вкладки браузера Chrome.",
3086
+ inputSchema: z.object({}),
3087
+ execute: async () => {
3088
+ try {
3089
+ const ps = `
3090
+ $chrome = Get-Process chrome -ErrorAction SilentlyContinue | Where-Object { $_.MainWindowTitle -ne '' }
3091
+ foreach ($c in $chrome) {
3092
+ Write-Output $c.MainWindowTitle
3093
+ }
3094
+ `
3095
+ const out = execSync(`powershell -Command "${ps.replace(/"/g, '\\"')}"`, {
3096
+ encoding: "utf8", timeout: 10000, stdio: ["pipe", "pipe", "pipe"],
3097
+ })
3098
+ return { tabs: out.trim().split("\n").filter(t => t.trim()) }
3099
+ } catch (e) {
3100
+ return { error: `Ошибка: ${e.message}` }
3101
+ }
3102
+ },
3103
+ }),
3104
+
3105
+ // ── PAINT ──
3106
+ paint_draw: tool({
3107
+ description: "Открыть Paint и нарисовать/вставить изображение.",
3108
+ inputSchema: z.object({
3109
+ image: z.string().optional().describe("Путь к изображению для открытия"),
3110
+ }),
3111
+ execute: async ({ image }) => {
3112
+ try {
3113
+ const cmd = image ? `mspaint "${image}"` : "mspaint"
3114
+ execSync(`start "" ${cmd}`, { shell: "cmd.exe", stdio: "pipe" })
3115
+ return { success: true, opened: image || "new" }
3116
+ } catch (e) {
3117
+ return { error: `Ошибка: ${e.message}` }
3118
+ }
3119
+ },
3120
+ }),
3121
+
3122
+ // ── CALCULATOR ──
3123
+ calc: tool({
3124
+ description: "Вычислить математическое выражение.",
3125
+ inputSchema: z.object({
3126
+ expression: z.string().describe("Выражение (2+2, 100*5, sqrt(144))"),
3127
+ }),
3128
+ execute: async ({ expression }) => {
3129
+ try {
3130
+ const ps = `
3131
+ Add-Type -AssemblyName Microsoft.VisualBasic
3132
+ $result = [Microsoft.VisualBasic.Interaction]::Eval("${expression.replace(/"/g, '""')}")
3133
+ Write-Output $result
3134
+ `
3135
+ const out = execSync(`powershell -ExecutionPolicy Bypass -Command "${ps.replace(/"/g, '\\"')}"`, {
3136
+ encoding: "utf8", timeout: 10000, stdio: ["pipe", "pipe", "pipe"],
3137
+ })
3138
+ return { expression, result: out.trim() }
3139
+ } catch (e) {
3140
+ return { error: `Ошибка: ${e.message}` }
3141
+ }
3142
+ },
3143
+ }),
3144
+ }
3145
+ }