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
@@ -18,8 +18,29 @@ import {
18
18
  verifyCodeIntegrity, getApiKey, saveApiKey, deleteApiKey,
19
19
  getHardwareInfo, saveIntegrityHash,
20
20
  } from "./security.mjs"
21
+ import { runSubagent, listSubagents, parseAgentCommand, SUBAGENTS } from "./subagents.mjs"
22
+ import { mcp, MCP_COMMANDS } from "./mcp.mjs"
23
+ import { generatePresentation, createPresentationFromTopic, AVAILABLE_THEMES, exportToPDF } from "./presentations.mjs"
24
+ import {
25
+ buildRepoMap, buildProjectContext, compressContext,
26
+ loadSpec, generateSpecTemplate,
27
+ detectTestFramework, detectLinter, detectFormatter, detectTypeChecker,
28
+ generateTestPrompt,
29
+ gitStatus, gitDiff, gitLog, gitBranches, gitCreateBranch, gitCheckout,
30
+ gitMerge, gitStash, gitStashPop, gitCommit, gitPush, gitPull,
31
+ gitCreatePR, gitListPRs, gitResolveConflicts,
32
+ runLinter, runFormatter, runTypeChecker, runTests,
33
+ applyEdits, diagnoseProject,
34
+ CODING_BRAIN_COMMANDS,
35
+ } from "./coding-brain.mjs"
36
+ import {
37
+ startBot, stopBot, notifyUser, notifyAll,
38
+ getBotStatus, TELEGRAM_BRAIN_COMMANDS,
39
+ verifyAuthCode, getPendingCodes,
40
+ generateAdminCode, listAuthorizedUsers,
41
+ } from "./telegram-bot.mjs"
21
42
 
22
- const VERSION = "3.9.1"
43
+ const VERSION = "5.0.0"
23
44
  const CONFIG_DIR = path.join(os.homedir(), ".stella")
24
45
  const CONFIG_PATH = path.join(CONFIG_DIR, "config.json")
25
46
  const HISTORY_PATH = path.join(CONFIG_DIR, "history.json")
@@ -125,17 +146,41 @@ if (args.includes("--model")) state.model = args[args.indexOf("--model") + 1] ||
125
146
  function systemPrompt() {
126
147
  let projectContext = ""
127
148
  try {
128
- if (fs.existsSync("STELLA.md")) projectContext = "\n\n# Контекст проекта (STELLA.md)\n" + fs.readFileSync("STELLA.md", "utf8")
149
+ // Auto-load SPEC.md, CLAUDE.md, AGENTS.md, STELLA.md
150
+ for (const specFile of ["SPEC.md", "CLAUDE.md", "AGENTS.md", "STELLA.md"]) {
151
+ const p = path.join(process.cwd(), specFile)
152
+ if (fs.existsSync(p)) {
153
+ projectContext += `\n\n# ${specFile}\n` + fs.readFileSync(p, "utf8").slice(0, 10000)
154
+ }
155
+ }
156
+ } catch {}
157
+
158
+ // Build repo map for context
159
+ let repoContext = ""
160
+ try {
161
+ const map = buildRepoMap(process.cwd())
162
+ repoContext = `\n\n# Карта проекта (${map.summary})\n\`\`\`\n${map.tree.slice(0, 200).join("\n")}\n\`\`\``
129
163
  } catch {}
130
- return `Ты — Stella, ИИ-агент терминального агента Stella Coder 3.9 (аналог Claude Code).
131
- Тыэксперт-программист, работающий в директории: ${process.cwd()} (ОС: ${os.platform()}).
164
+
165
+ return `Ты Stella, ИИ-агент терминального агента Stella Coder 4.0 (аналог Claude Code / Cursor).
166
+ Ты — эксперт-программист с огромным контекстом, работающий в: ${process.cwd()} (ОС: ${os.platform()}).
167
+
168
+ Твои возможности:
169
+ - Огромный контекст: ты "держишь в голове" структуру всего репозитория
170
+ - Spec-Driven Development: работай по SPEC.md / CLAUDE.md
171
+ - TDD: пиши тесты, прав код, запускай линтеры — всё автономно
172
+ - Multi-file editing: правь 15+ файлов за раз
173
+ - Git экосистема: ветки, PR, мержи, разрешение конфликтов
174
+ - Автосжатие контекста при необходимости
132
175
 
133
176
  Правила:
134
- - Используй инструменты (read_file, edit_file, write_file, bash, grep, glob, list_dir), чтобы РЕАЛЬНО выполнять задачи, а не просто описывать их.
177
+ - Используй инструменты (read_file, edit_file, write_file, bash, grep, glob, list_dir), чтобы РЕАЛЬНО выполнять задачи.
135
178
  - Перед изменением файла всегда читай его.
136
- - Для многошаговых задач используй todo_write, чтобы показать план.
179
+ - Для многошаговых задач используй todo_write.
180
+ - Автоматически определяй и запускай линтеры/тесты после изменений.
181
+ - При большом контексте — сжимай автоматически.
137
182
  - Отвечай кратко и по делу, в формате markdown. Код — в блоках с указанием языка.
138
- - Отвечай на языке пользователя.${projectContext}`
183
+ - Отвечай на языке пользователя.${projectContext}${repoContext}`
139
184
  }
140
185
 
141
186
  // ---------- spinner ----------
@@ -566,6 +611,37 @@ const COMMANDS = [
566
611
  // Ollama
567
612
  ["/ollama", "подключить локальную LLM (Ollama)"],
568
613
 
614
+ // Презентации
615
+ ["/presentation", "создать презентацию из темы"],
616
+ ["/presentation-theme", "показать доступные темы оформления"],
617
+ ["/presentation-list", "показать созданные презентации"],
618
+ ["/presentation-custom", "создать кастомную презентацию"],
619
+ ["/presentation-export", "экспортировать презентацию в PDF"],
620
+
621
+ // Coding Brain
622
+ ["/brain", "показать контекст и инструменты проекта"],
623
+ ["/brain-map", "построить карту репозитория"],
624
+ ["/brain-compress", "сжать контекст сессии"],
625
+ ["/spec", "показать/создать SPEC.md"],
626
+ ["/tdd", "автономный TDD-цикл"],
627
+ ["/lint-auto", "автоматический линтер + исправление"],
628
+ ["/format-auto", "автоформатирование кода"],
629
+ ["/typecheck-auto", "автопроверка типов"],
630
+ ["/test-auto", "автозапуск тестов"],
631
+ ["/git-eco", "полная информация о Git"],
632
+ ["/git-pr", "создать Pull Request"],
633
+ ["/git-merge-auto", "автоматический merge с разрешением конфликтов"],
634
+ ["/fix-all", "полный цикл: линтер → форматер → типы → тесты"],
635
+
636
+ // Telegram Bot
637
+ ["/tg", "запустить Telegram бота"],
638
+ ["/tg-stop", "остановить Telegram бота"],
639
+ ["/tg-notify", "отправить уведомление в Telegram"],
640
+ ["/tg-sessions", "показать Telegram сессии"],
641
+ ["/tg-verify", "привязать Telegram аккаунт по коду"],
642
+ ["/tg-code", "сгенерировать код для продажи доступа"],
643
+ ["/tg-users", "показать кто подключён"],
644
+
569
645
  // Управление компьютером
570
646
  ["/open", "открыть приложение/файл/URL"],
571
647
  ["/app", "запустить приложение"],
@@ -647,6 +723,29 @@ const COMMANDS = [
647
723
  async function handleCommand(line) {
648
724
  const [cmd, ...rest] = line.trim().split(/\s+/)
649
725
  const arg = rest.join(" ")
726
+
727
+ // Handle @agent commands
728
+ if (cmd.startsWith("@")) {
729
+ const agentName = cmd.slice(1)
730
+ const agents = listSubagents()
731
+ const agent = agents.find(a => a.id === agentName)
732
+ if (agent) {
733
+ if (!arg) {
734
+ console.log(dim(`\n Использование: @${agentName} <задача>\n`))
735
+ return
736
+ }
737
+ const result = await runSubagent(agentName, arg, {
738
+ apiKey,
739
+ model: state.model,
740
+ tools: createTools({ ask: askPermission }),
741
+ })
742
+ if (result.result) {
743
+ console.log("\n" + renderMarkdown(result.result) + "\n")
744
+ }
745
+ return
746
+ }
747
+ }
748
+
650
749
  switch (cmd) {
651
750
  case "/help": {
652
751
  console.log()
@@ -663,6 +762,10 @@ async function handleCommand(line) {
663
762
  ["📦 Пакеты", ["/install", "/uninstall", "/outdated", "/deps"]],
664
763
  ["💻 Система", ["/env", "/path", "/which", "/whoami", "/hostname", "/uname", "/uptime", "/disk", "/mem", "/cpu", "/net", "/ps", "/kill"]],
665
764
  ["📚 Документация", ["/docs", "/readme", "/changelog", "/examples", "/tutorial", "/wizard"]],
765
+ ["📊 Презентации", ["/presentation", "/presentation-theme", "/presentation-list", "/presentation-custom", "/presentation-export"]],
766
+ ["🧠 Coding Brain", ["/brain", "/brain-map", "/brain-compress", "/spec", "/tdd", "/lint-auto", "/format-auto", "/typecheck-auto", "/test-auto", "/fix-all"]],
767
+ ["📱 Telegram", ["/tg", "/tg-stop", "/tg-notify", "/tg-sessions", "/tg-verify", "/tg-code", "/tg-users"]],
768
+ ["🔀 Git Экосистема", ["/git-eco", "/git-pr", "/git-merge-auto"]],
666
769
  ["⚙️ Настройки", ["/help", "/model", "/clear", "/compact", "/cost", "/context", "/config", "/version", "/login", "/newkey", "/doctor", "/sessions", "/color", "/lang", "/shortcut"]],
667
770
  ]
668
771
  for (const [cat, cmds] of categories) {
@@ -1293,17 +1396,83 @@ async function handleCommand(line) {
1293
1396
  return
1294
1397
  }
1295
1398
  case "/mcp": {
1296
- console.log(box([
1297
- violet("/mcp list") + dim(" — список MCP серверов"),
1298
- violet("/mcp add <url>") + dim(" — добавить сервер"),
1299
- violet("/mcp remove <url>") + dim(" — удалить сервер"),
1300
- violet("/mcp status") + dim(" — статус подключений"),
1301
- "",
1302
- dim("MCP (Model Context Protocol) расширяет инструменты AI."),
1303
- ], { title: "MCP Серверы", padding: 2 }))
1399
+ // Delegate to MCP command handler
1400
+ const mcpHandler = MCP_COMMANDS["/mcp"]?.handler
1401
+ if (mcpHandler) {
1402
+ await mcpHandler(arg || "help")
1403
+ } else {
1404
+ console.log(box([
1405
+ violet("/mcp list") + dim(" список серверов"),
1406
+ violet("/mcp start <имя>") + dim(" запустить сервер"),
1407
+ violet("/mcp stop <имя>") + dim(" — остановить сервер"),
1408
+ violet("/mcp add <имя> <команда> [args]") + dim(" — добавить сервер"),
1409
+ violet("/mcp remove <имя>") + dim(" — удалить сервер"),
1410
+ violet("/mcp tools") + dim(" — список инструментов"),
1411
+ "",
1412
+ dim("MCP (Model Context Protocol) расширяет инструменты AI."),
1413
+ ], { title: "MCP Серверы", padding: 2 }))
1414
+ }
1304
1415
  console.log()
1305
1416
  return
1306
1417
  }
1418
+ case "/agent": {
1419
+ const agents = listSubagents()
1420
+ if (!arg || arg === "list" || arg === "список") {
1421
+ console.log(box([
1422
+ ...agents.map(a => `${a.icon} ${violet(a.name)}` + dim(` — ${a.description}`)),
1423
+ "",
1424
+ dim("Запуск: ") + violet("@имя_агента задача"),
1425
+ ], { title: "Субагенты Stella", padding: 2 }))
1426
+ console.log()
1427
+ return
1428
+ }
1429
+ // Parse @agent task format
1430
+ const agentMatch = parseAgentCommand(`@${arg}`)
1431
+ if (agentMatch) {
1432
+ const result = await runSubagent(agentMatch.agent, agentMatch.task, {
1433
+ apiKey,
1434
+ model: state.model,
1435
+ tools: createTools({ ask: askPermission }),
1436
+ })
1437
+ if (result.result) {
1438
+ console.log("\n" + renderMarkdown(result.result) + "\n")
1439
+ }
1440
+ } else {
1441
+ // Run as general subagent task
1442
+ const result = await runSubagent("codebase-investigator", arg, {
1443
+ apiKey,
1444
+ model: state.model,
1445
+ tools: createTools({ ask: askPermission }),
1446
+ })
1447
+ if (result.result) {
1448
+ console.log("\n" + renderMarkdown(result.result) + "\n")
1449
+ }
1450
+ }
1451
+ return
1452
+ }
1453
+ case "@codebase-investigator":
1454
+ case "@security-auditor":
1455
+ case "@test-writer":
1456
+ case "@docs-writer":
1457
+ case "@refactor":
1458
+ case "@debugger":
1459
+ case "@performance":
1460
+ case "@git-expert": {
1461
+ const agentName = command.slice(1) // Remove @
1462
+ if (!arg) {
1463
+ console.log(dim(`\n Использование: @${agentName} <задача>\n`))
1464
+ return
1465
+ }
1466
+ const result = await runSubagent(agentName, arg, {
1467
+ apiKey,
1468
+ model: state.model,
1469
+ tools: createTools({ ask: askPermission }),
1470
+ })
1471
+ if (result.result) {
1472
+ console.log("\n" + renderMarkdown(result.result) + "\n")
1473
+ }
1474
+ return
1475
+ }
1307
1476
 
1308
1477
  // ═══════════════════════════════════════════════════
1309
1478
  // ВЕБ И КОНТЕНТ
@@ -2471,6 +2640,753 @@ async function handleCommand(line) {
2471
2640
  goodbye()
2472
2641
  return
2473
2642
  }
2643
+
2644
+ // ═══════════════════════════════════════════════════
2645
+ // ПРЕЗЕНТАЦИИ
2646
+ // ═══════════════════════════════════════════════════
2647
+ case "/presentation": {
2648
+ if (!arg) {
2649
+ console.log(dim("\n Использование: /presentation <тема> [тема_оформления]\n"))
2650
+ console.log(dim(" Пример: /presentation \"Машинное обучение\" modern\n"))
2651
+ return
2652
+ }
2653
+ const parts = arg.split(/\s+/)
2654
+ const topic = parts[0]
2655
+ const theme = parts[1] || "modern"
2656
+
2657
+ console.log()
2658
+ startSpinner("Создаю презентацию")
2659
+
2660
+ try {
2661
+ const result = createPresentationFromTopic(topic, {
2662
+ theme,
2663
+ slidesCount: 8,
2664
+ author: "Stella Coder",
2665
+ })
2666
+
2667
+ stopSpinner()
2668
+ console.log(box([
2669
+ green("✓ Презентация создана!"),
2670
+ "",
2671
+ dim("Тема: ") + white(topic),
2672
+ dim("Тема оформления: ") + white(result.theme),
2673
+ dim("Слайдов: ") + white(String(result.slidesCount)),
2674
+ "",
2675
+ dim("Путь: ") + cyan(result.outputDir),
2676
+ "",
2677
+ white("Откройте index.html в браузере для просмотра"),
2678
+ ], { title: "Презентация", color: green, padding: 2 }))
2679
+ console.log()
2680
+
2681
+ // Open in browser
2682
+ const openBrowser = await question(" " + green("Открыть в браузере? (y/n) › "))
2683
+ if (openBrowser.trim().toLowerCase() === "y" || openBrowser.trim().toLowerCase() === "д") {
2684
+ try {
2685
+ const indexPath = result.outputDir + "/index.html"
2686
+ execSync(`start "" "${indexPath}"`, { shell: "cmd.exe", stdio: "pipe" })
2687
+ console.log(green(" ✓ Презентация открыта в браузере\n"))
2688
+ } catch (e) {
2689
+ console.log(yellow(" ⚠ Не удалось открыть автоматически: ") + String(e?.message || e).slice(0, 100) + "\n")
2690
+ }
2691
+ }
2692
+ } catch (e) {
2693
+ stopSpinner()
2694
+ console.log(red(" ✗ Ошибка: ") + String(e?.message || e).slice(0, 200) + "\n")
2695
+ }
2696
+ return
2697
+ }
2698
+
2699
+ case "/presentation-theme": {
2700
+ console.log(box([
2701
+ ...AVAILABLE_THEMES.map(t =>
2702
+ violet(t.id) + dim(" — ") + white(t.name) + dim(` (${t.colors.primary})`)
2703
+ ),
2704
+ ], { title: "Доступные темы презентаций", padding: 2 }))
2705
+ console.log()
2706
+ return
2707
+ }
2708
+
2709
+ case "/presentation-custom": {
2710
+ console.log(dim("\n Создание кастомной презентации"))
2711
+ console.log(dim(" Введите данные для каждого слайда:\n"))
2712
+
2713
+ const title = await question(" " + green("Заголовок презентации › "))
2714
+ if (!title.trim()) {
2715
+ console.log(red(" Заголовок не может быть пустым\n"))
2716
+ return
2717
+ }
2718
+
2719
+ const author = await question(" " + green("Автор › "))
2720
+ const themeChoice = await question(" " + green("Тема (modern/elegant/creative/minimal/tech) › "))
2721
+ const theme = themeChoice.trim() || "modern"
2722
+
2723
+ const slides = []
2724
+ let slideNum = 1
2725
+
2726
+ while (true) {
2727
+ console.log(dim(`\n Слайд ${slideNum}:`))
2728
+ const slideTitle = await question(" " + green("Заголовок слайда (пусто для завершения) › "))
2729
+ if (!slideTitle.trim()) break
2730
+
2731
+ const slideType = await question(" " + green("Тип (content/twoColumn/quote) › "))
2732
+ const slideContent = await question(" " + green("Содержимое (через запятую) › "))
2733
+
2734
+ slides.push({
2735
+ type: slideType.trim() || "content",
2736
+ title: slideTitle.trim(),
2737
+ items: slideContent.split(",").map(i => i.trim()).filter(i => i),
2738
+ })
2739
+
2740
+ slideNum++
2741
+ }
2742
+
2743
+ if (slides.length === 0) {
2744
+ console.log(red(" Не добавлено ни одного слайда\n"))
2745
+ return
2746
+ }
2747
+
2748
+ console.log()
2749
+ startSpinner("Создаю кастомную презентацию")
2750
+
2751
+ try {
2752
+ const result = generatePresentation({
2753
+ title: title.trim(),
2754
+ author: author.trim(),
2755
+ slides,
2756
+ theme,
2757
+ outputDir: `presentation-${title.trim().toLowerCase().replace(/\s+/g, '-')}`,
2758
+ })
2759
+
2760
+ stopSpinner()
2761
+ console.log(box([
2762
+ green("✓ Кастомная презентация создана!"),
2763
+ "",
2764
+ dim("Тема: ") + white(title.trim()),
2765
+ dim("Автор: ") + white(author.trim() || "Не указан"),
2766
+ dim("Тема оформления: ") + white(result.theme),
2767
+ dim("Слайдов: ") + white(String(result.slidesCount)),
2768
+ "",
2769
+ dim("Путь: ") + cyan(result.outputDir),
2770
+ ], { title: "Кастомная презентация", color: green, padding: 2 }))
2771
+ console.log()
2772
+ } catch (e) {
2773
+ stopSpinner()
2774
+ console.log(red(" ✗ Ошибка: ") + String(e?.message || e).slice(0, 200) + "\n")
2775
+ }
2776
+ return
2777
+ }
2778
+
2779
+ case "/presentation-list": {
2780
+ const outputDirs = fs.readdirSync(process.cwd()).filter(f =>
2781
+ f.startsWith("presentation-") && fs.statSync(f).isDirectory()
2782
+ )
2783
+
2784
+ if (outputDirs.length === 0) {
2785
+ console.log(dim("\n Нет созданных презентаций\n"))
2786
+ } else {
2787
+ console.log(box([
2788
+ ...outputDirs.map((d, i) =>
2789
+ violet(String(i + 1)) + dim(" — ") + white(d)
2790
+ ),
2791
+ ], { title: "Ваши презентации", padding: 2 }))
2792
+ console.log()
2793
+ }
2794
+ return
2795
+ }
2796
+
2797
+ case "/presentation-export": {
2798
+ if (!arg) {
2799
+ console.log(dim("\n Использование: /presentation-export <путь_к_презентации>\n"))
2800
+ console.log(dim(" Пример: /presentation-export presentation-моя-презентация\n"))
2801
+ return
2802
+ }
2803
+
2804
+ const presentationDir = arg.trim()
2805
+ if (!fs.existsSync(presentationDir)) {
2806
+ console.log(red(` ✗ Директория не найдена: ${presentationDir}\n`))
2807
+ return
2808
+ }
2809
+
2810
+ console.log()
2811
+ startSpinner("Создаю версию для печати")
2812
+
2813
+ try {
2814
+ const result = exportToPDF(presentationDir)
2815
+ stopSpinner()
2816
+
2817
+ console.log(box([
2818
+ green("✓ Версия для печати создана!"),
2819
+ "",
2820
+ dim("Файл: ") + cyan(result.printPath),
2821
+ "",
2822
+ white("Откройте print.html в браузере"),
2823
+ white("Используйте Ctrl+P для экспорта в PDF"),
2824
+ ], { title: "Экспорт в PDF", color: green, padding: 2 }))
2825
+ console.log()
2826
+
2827
+ // Open print version
2828
+ const openPrint = await question(" " + green("Открыть версию для печати? (y/n) › "))
2829
+ if (openPrint.trim().toLowerCase() === "y" || openPrint.trim().toLowerCase() === "д") {
2830
+ try {
2831
+ execSync(`start "" "${result.printPath}"`, { shell: "cmd.exe", stdio: "pipe" })
2832
+ console.log(green(" ✓ Версия для печати открыта\n"))
2833
+ } catch (e) {
2834
+ console.log(yellow(" ⚠ Не удалось открыть автоматически: ") + String(e?.message || e).slice(0, 100) + "\n")
2835
+ }
2836
+ }
2837
+ } catch (e) {
2838
+ stopSpinner()
2839
+ console.log(red(" ✗ Ошибка: ") + String(e?.message || e).slice(0, 200) + "\n")
2840
+ }
2841
+ return
2842
+ }
2843
+
2844
+ // ═══════════════════════════════════════════════════
2845
+ // CODING BRAIN
2846
+ // ═══════════════════════════════════════════════════
2847
+ case "/brain": {
2848
+ console.log()
2849
+ startSpinner("Анализирую проект")
2850
+ const diag = diagnoseProject(process.cwd())
2851
+ stopSpinner()
2852
+
2853
+ console.log(box([
2854
+ dim("Репозиторий: ") + white(diag.repo),
2855
+ dim("SPEC: ") + (diag.spec !== "not found" ? green(diag.spec) : yellow("не найден")),
2856
+ dim("Тесты: ") + (diag.test !== "not detected" ? green(diag.test) : yellow("не обнаружены")),
2857
+ dim("Линтер: ") + (diag.linter !== "not detected" ? green(diag.linter) : yellow("не обнаружен")),
2858
+ dim("Форматер: ") + (diag.formatter !== "not detected" ? green(diag.formatter) : yellow("не обнаружен")),
2859
+ dim("Типы: ") + (diag.typeChecker !== "not detected" ? green(diag.typeChecker) : yellow("не обнаружен")),
2860
+ "",
2861
+ dim("Git ветка: ") + violet(diag.git.branch),
2862
+ dim("Git статус: ") + (diag.git.clean ? green("чисто") : yellow(`${diag.git.files} файлов`)),
2863
+ diag.git.ahead ? dim("Впереди: ") + cyan(String(diag.git.ahead)) : "",
2864
+ diag.git.behind ? dim("Позади: ") + cyan(String(diag.git.behind)) : "",
2865
+ ], { title: "🧠 Coding Brain", color: violet, padding: 2 }))
2866
+ console.log()
2867
+ return
2868
+ }
2869
+
2870
+ case "/brain-map": {
2871
+ console.log()
2872
+ startSpinner("Строю карту репозитория")
2873
+ const map = buildRepoMap(process.cwd())
2874
+ stopSpinner()
2875
+
2876
+ console.log(box([
2877
+ dim("Файлов: ") + white(String(map.fileCount)),
2878
+ dim("Размер: ") + white(map.totalSize),
2879
+ "",
2880
+ dim("Типы файлов:"),
2881
+ ...Object.entries(map.fileTypes)
2882
+ .sort((a, b) => b[1] - a[1])
2883
+ .slice(0, 15)
2884
+ .map(([ext, count]) => ` ${violet(ext)} ${dim("—")} ${white(String(count))}`),
2885
+ ], { title: "Карта репозитория", color: cyan, padding: 2 }))
2886
+ console.log()
2887
+
2888
+ const showTree = await question(" " + green("Показать дерево файлов? (y/n) › "))
2889
+ if (showTree.trim().toLowerCase() === "y") {
2890
+ console.log()
2891
+ map.tree.slice(0, 100).forEach(l => console.log(dim(" ") + l))
2892
+ if (map.tree.length > 100) console.log(dim(` ... и ещё ${map.tree.length - 100} файлов`))
2893
+ console.log()
2894
+ }
2895
+ return
2896
+ }
2897
+
2898
+ case "/brain-compress": {
2899
+ console.log()
2900
+ const msgCount = state.messages.length
2901
+ const chars = JSON.stringify(state.messages).length
2902
+ console.log(dim(` Контекст: ${msgCount} сообщений, ${Math.round(chars / 1000)}K символов`))
2903
+
2904
+ if (chars < COMPRESS_THRESHOLD) {
2905
+ console.log(green(" Контекст ещё не нуждается в сжатии\n"))
2906
+ return
2907
+ }
2908
+
2909
+ const confirm = await question(" " + yellow(`Сжать контекст? (${Math.round(chars / 1000)}K → ~50K) (y/n) › `))
2910
+ if (confirm.trim().toLowerCase() !== "y") {
2911
+ console.log(dim(" Отмена\n"))
2912
+ return
2913
+ }
2914
+
2915
+ startSpinner("Сжимаю контекст")
2916
+ state.messages = await compressContext(state.messages, getModel(state.model))
2917
+ stopSpinner()
2918
+
2919
+ const newChars = JSON.stringify(state.messages).length
2920
+ console.log(green(` ✓ Контекст сжат: ${Math.round(chars / 1000)}K → ${Math.round(newChars / 1000)}K\n`))
2921
+ return
2922
+ }
2923
+
2924
+ case "/spec": {
2925
+ const spec = loadSpec(process.cwd())
2926
+ if (spec.exists) {
2927
+ console.log(box([
2928
+ dim("Файл: ") + white(spec.file),
2929
+ "",
2930
+ spec.content.slice(0, 2000),
2931
+ spec.content.length > 2000 ? dim(`\n... [показано ${2000} из ${spec.content.length} символов]`) : "",
2932
+ ], { title: "SPEC", color: cyan, padding: 2 }))
2933
+ } else {
2934
+ console.log(yellow("\n SPEC.md не найден в проекте"))
2935
+ const createSpec = await question(" " + green("Создать SPEC.md? (y/n) › "))
2936
+ if (createSpec.trim().toLowerCase() === "y") {
2937
+ const name = await question(" " + green("Название проекта › "))
2938
+ const desc = await question(" " + green("Описание › "))
2939
+ const content = generateSpecTemplate(name, desc)
2940
+ fs.writeFileSync(path.join(process.cwd(), "SPEC.md"), content)
2941
+ console.log(green(" ✓ SPEC.md создан\n"))
2942
+ }
2943
+ }
2944
+ return
2945
+ }
2946
+
2947
+ case "/tdd": {
2948
+ if (!arg) {
2949
+ console.log(dim("\n Использование: /tdd <путь к файлу>\n"))
2950
+ return
2951
+ }
2952
+
2953
+ const filePath = arg.trim()
2954
+ if (!fs.existsSync(filePath)) {
2955
+ console.log(red(` ✗ Файл не найден: ${filePath}\n`))
2956
+ return
2957
+ }
2958
+
2959
+ console.log()
2960
+ const fileContent = fs.readFileSync(filePath, "utf8")
2961
+ const spec = loadSpec(process.cwd())
2962
+ const testPrompt = generateTestPrompt(filePath, fileContent, spec.content)
2963
+
2964
+ startSpinner("Генерирую тесты")
2965
+ try {
2966
+ const { text } = await generateText({
2967
+ model: getModel(state.model),
2968
+ messages: [{ role: "user", content: testPrompt.prompt }],
2969
+ })
2970
+ stopSpinner()
2971
+
2972
+ const testFilePath = path.join(process.cwd(), testPrompt.testFile)
2973
+ fs.writeFileSync(testFilePath, text.replace(/```[\s\S]*?\n/g, "").replace(/```/g, "").trim())
2974
+
2975
+ console.log(green(` ✓ Тесты созданы: ${testPrompt.testFile}`))
2976
+
2977
+ // Run tests
2978
+ const framework = detectTestFramework(process.cwd())
2979
+ if (framework.detected) {
2980
+ startSpinner("Запускаю тесты")
2981
+ const result = runTests(process.cwd(), framework)
2982
+ stopSpinner()
2983
+ if (result.success) {
2984
+ console.log(green(" ✓ Тесты пройдены!"))
2985
+ } else {
2986
+ console.log(red(" ✗ Тесты упали:"))
2987
+ console.log(dim(result.output?.slice(0, 500) || ""))
2988
+ }
2989
+ }
2990
+ } catch (e) {
2991
+ stopSpinner()
2992
+ console.log(red(" ✗ Ошибка: ") + String(e?.message || e).slice(0, 200) + "\n")
2993
+ }
2994
+ console.log()
2995
+ return
2996
+ }
2997
+
2998
+ case "/lint-auto": {
2999
+ console.log()
3000
+ const linter = detectLinter(process.cwd())
3001
+ if (!linter.detected) {
3002
+ console.log(yellow(" Линтер не обнаружен в проекте\n"))
3003
+ return
3004
+ }
3005
+
3006
+ startSpinner(`Запускаю ${linter.name}`)
3007
+ const result = runLinter(process.cwd(), linter)
3008
+ stopSpinner()
3009
+
3010
+ if (result.success && !result.hasErrors) {
3011
+ console.log(green(` ✓ ${linter.name}: ошибок нет`))
3012
+ } else {
3013
+ console.log(yellow(` ⚠ ${linter.name}: найдены проблемы`))
3014
+ console.log(dim(result.output?.slice(0, 1000) || ""))
3015
+
3016
+ const fix = await question(" " + green("Автоматически исправить? (y/n) › "))
3017
+ if (fix.trim().toLowerCase() === "y") {
3018
+ startSpinner("Исправляю")
3019
+ try {
3020
+ execSync(`${linter.cmd} --fix .`, { cwd: process.cwd(), encoding: "utf8", timeout: 60000 })
3021
+ stopSpinner()
3022
+ console.log(green(" ✓ Исправлено\n"))
3023
+ } catch (e) {
3024
+ stopSpinner()
3025
+ console.log(red(" ✗ Ошибка исправления: ") + String(e?.message || e).slice(0, 200) + "\n")
3026
+ }
3027
+ }
3028
+ }
3029
+ return
3030
+ }
3031
+
3032
+ case "/format-auto": {
3033
+ console.log()
3034
+ const formatter = detectFormatter(process.cwd())
3035
+ if (!formatter.detected) {
3036
+ console.log(yellow(" Форматер не обнаружен в проекте\n"))
3037
+ return
3038
+ }
3039
+
3040
+ startSpinner(`Форматирую (${formatter.name})`)
3041
+ const result = runFormatter(process.cwd(), formatter)
3042
+ stopSpinner()
3043
+
3044
+ if (result.success) {
3045
+ console.log(green(` ✓ ${formatter.name}: код отформатирован`))
3046
+ } else {
3047
+ console.log(red(` ✗ ${formatter.name}: `) + String(result.error || "").slice(0, 200))
3048
+ }
3049
+ console.log()
3050
+ return
3051
+ }
3052
+
3053
+ case "/typecheck-auto": {
3054
+ console.log()
3055
+ const checker = detectTypeChecker(process.cwd())
3056
+ if (!checker.detected) {
3057
+ console.log(yellow(" Проверщик типов не обнаружен\n"))
3058
+ return
3059
+ }
3060
+
3061
+ startSpinner(`Проверяю типы (${checker.name})`)
3062
+ const result = runTypeChecker(process.cwd(), checker)
3063
+ stopSpinner()
3064
+
3065
+ if (result.success && !result.hasErrors) {
3066
+ console.log(green(` ✓ ${checker.name}: ошибок нет`))
3067
+ } else {
3068
+ console.log(yellow(` ⚠ ${checker.name}: найдены ошибки`))
3069
+ console.log(dim(result.output?.slice(0, 1000) || ""))
3070
+ }
3071
+ console.log()
3072
+ return
3073
+ }
3074
+
3075
+ case "/test-auto": {
3076
+ console.log()
3077
+ const framework = detectTestFramework(process.cwd())
3078
+ if (!framework.detected) {
3079
+ console.log(yellow(" Фреймворк тестов не обнаружен\n"))
3080
+ return
3081
+ }
3082
+
3083
+ startSpinner(`Запускаю тесты (${framework.framework})`)
3084
+ const result = runTests(process.cwd(), framework)
3085
+ stopSpinner()
3086
+
3087
+ if (result.success) {
3088
+ console.log(green(" ✓ Тесты пройдены!"))
3089
+ console.log(dim(result.output?.slice(-500) || ""))
3090
+ } else {
3091
+ console.log(red(" ✗ Тесты упали:"))
3092
+ console.log(dim(result.output?.slice(0, 1000) || ""))
3093
+ }
3094
+ console.log()
3095
+ return
3096
+ }
3097
+
3098
+ case "/fix-all": {
3099
+ console.log()
3100
+ console.log(dim(" Полный цикл исправления: линтер → форматер → типы → тесты\n"))
3101
+
3102
+ // 1. Linter
3103
+ const linter = detectLinter(process.cwd())
3104
+ if (linter.detected) {
3105
+ startSpinner(`Линтер (${linter.name})`)
3106
+ try { execSync(`${linter.cmd} --fix .`, { cwd: process.cwd(), encoding: "utf8", timeout: 60000 }) } catch {}
3107
+ stopSpinner()
3108
+ console.log(green(` ✓ ${linter.name} выполнен`))
3109
+ }
3110
+
3111
+ // 2. Formatter
3112
+ const formatter = detectFormatter(process.cwd())
3113
+ if (formatter.detected) {
3114
+ startSpinner(`Форматер (${formatter.name})`)
3115
+ runFormatter(process.cwd(), formatter)
3116
+ stopSpinner()
3117
+ console.log(green(` ✓ ${formatter.name} выполнен`))
3118
+ }
3119
+
3120
+ // 3. Type checker
3121
+ const checker = detectTypeChecker(process.cwd())
3122
+ if (checker.detected) {
3123
+ startSpinner(`Типы (${checker.name})`)
3124
+ const typeResult = runTypeChecker(process.cwd(), checker)
3125
+ stopSpinner()
3126
+ if (typeResult.success) {
3127
+ console.log(green(` ✓ ${checker.name}: ошибок нет`))
3128
+ } else {
3129
+ console.log(yellow(` ⚠ ${checker.name}: есть ошибки`))
3130
+ }
3131
+ }
3132
+
3133
+ // 4. Tests
3134
+ const framework = detectTestFramework(process.cwd())
3135
+ if (framework.detected) {
3136
+ startSpinner(`Тесты (${framework.framework})`)
3137
+ const testResult = runTests(process.cwd(), framework)
3138
+ stopSpinner()
3139
+ if (testResult.success) {
3140
+ console.log(green(" ✓ Тесты пройдены"))
3141
+ } else {
3142
+ console.log(red(" ✗ Тесты упали"))
3143
+ }
3144
+ }
3145
+
3146
+ console.log(green("\n ✓ Полный цикл завершён\n"))
3147
+ return
3148
+ }
3149
+
3150
+ case "/git-eco": {
3151
+ console.log()
3152
+ startSpinner("Собираю Git-информацию")
3153
+ const git = gitStatus(process.cwd())
3154
+ const branches = gitBranches(process.cwd())
3155
+ const log = gitLog(process.cwd(), 10)
3156
+ stopSpinner()
3157
+
3158
+ console.log(box([
3159
+ dim("Ветка: ") + violet(git.branch),
3160
+ dim("Статус: ") + (git.clean ? green("чисто") : yellow(`${git.files.length} изменений`)),
3161
+ git.ahead ? dim("Впереди: ") + cyan(String(git.ahead)) : "",
3162
+ git.behind ? dim("Позади: ") + cyan(String(git.behind)) : "",
3163
+ git.stashCount ? dim("Stash: ") + cyan(String(git.stashCount)) : "",
3164
+ "",
3165
+ dim("Ветки:"),
3166
+ ...branches.slice(0, 10).map(b => ` ${b.includes("*") ? green(b) : dim(b)}`),
3167
+ ], { title: "Git Экосистема", color: cyan, padding: 2 }))
3168
+
3169
+ if (log) {
3170
+ console.log(dim("\nПоследние коммиты:"))
3171
+ log.split("\n").slice(0, 5).forEach(l => console.log(dim(" ") + l))
3172
+ }
3173
+ console.log()
3174
+ return
3175
+ }
3176
+
3177
+ case "/git-pr": {
3178
+ console.log()
3179
+ const title = await question(" " + green("Название PR › "))
3180
+ if (!title.trim()) { console.log(red(" Название не может быть пустым\n")); return }
3181
+ const body = await question(" " + green("Описание (необязательно) › "))
3182
+ const base = await question(" " + green("Базовая ветка (main) › "))
3183
+
3184
+ startSpinner("Создаю Pull Request")
3185
+ const result = gitCreatePR(process.cwd(), title.trim(), body || title.trim(), base.trim() || "main")
3186
+ stopSpinner()
3187
+
3188
+ if (result.success) {
3189
+ console.log(green(` ✓ PR создан: ${result.url}`))
3190
+ } else {
3191
+ console.log(red(" ✗ Ошибка: ") + String(result.error || "").slice(0, 200))
3192
+ }
3193
+ console.log()
3194
+ return
3195
+ }
3196
+
3197
+ case "/git-merge-auto": {
3198
+ if (!arg) {
3199
+ console.log(dim("\n Использование: /git-merge-auto <ветка>\n"))
3200
+ return
3201
+ }
3202
+
3203
+ const branch = arg.trim()
3204
+ console.log()
3205
+ startSpinner(`Мержу ${branch}`)
3206
+ const result = gitMerge(process.cwd(), branch)
3207
+ stopSpinner()
3208
+
3209
+ if (result.success) {
3210
+ console.log(green(` ✓ ${branch} успешно смержена`))
3211
+ } else if (result.conflict) {
3212
+ console.log(yellow(` ⚠ Конфликт в ${result.files.length} файлах:`))
3213
+ result.files.forEach(f => console.log(dim(` - ${f}`)))
3214
+
3215
+ const resolve = await question(" " + green("Разрешить конфликты (theirs)? (y/n) › "))
3216
+ if (resolve.trim().toLowerCase() === "y") {
3217
+ startSpinner("Разрешаю конфликты")
3218
+ const resolved = gitResolveConflicts(process.cwd(), "theirs")
3219
+ stopSpinner()
3220
+ if (resolved.success) {
3221
+ console.log(green(` ✓ Конфликты разрешены в ${resolved.resolved?.length || 0} файлах`))
3222
+ } else {
3223
+ console.log(red(" ✗ Ошибка: ") + String(resolved.error || "").slice(0, 200))
3224
+ }
3225
+ }
3226
+ } else {
3227
+ console.log(red(" ✗ Ошибка: ") + String(result.error || "").slice(0, 200))
3228
+ }
3229
+ console.log()
3230
+ return
3231
+ }
3232
+
3233
+ // ═══════════════════════════════════════════════════
3234
+ // TELEGRAM BOT
3235
+ // ═══════════════════════════════════════════════════
3236
+ case "/tg": {
3237
+ console.log()
3238
+ startSpinner("Запускаю Telegram бота")
3239
+ const botResult = await startBot()
3240
+ stopSpinner()
3241
+
3242
+ if (botResult) {
3243
+ const status = getBotStatus()
3244
+ console.log(box([
3245
+ green("✓ Telegram бот запущен!"),
3246
+ "",
3247
+ dim("Бот: ") + cyan("@" + "stella_coder_bot"),
3248
+ dim("Статус: ") + green("онлайн"),
3249
+ "",
3250
+ white("Откройте Telegram и найдите @stella_coder_bot"),
3251
+ white("Нажмите /start для авторизации"),
3252
+ ], { title: "Telegram Bot", color: blue, padding: 2 }))
3253
+ } else {
3254
+ console.log(red(" ✗ Не удалось запустить бота\n"))
3255
+ }
3256
+ console.log()
3257
+ return
3258
+ }
3259
+
3260
+ case "/tg-stop": {
3261
+ console.log()
3262
+ await stopBot()
3263
+ console.log(green(" ✓ Telegram бот остановлен\n"))
3264
+ return
3265
+ }
3266
+
3267
+ case "/tg-notify": {
3268
+ if (!arg) {
3269
+ console.log(dim("\n Использование: /tg-notify <сообщение>\n"))
3270
+ return
3271
+ }
3272
+ console.log()
3273
+ startSpinner("Отправляю уведомление")
3274
+ await notifyAll(arg.trim())
3275
+ stopSpinner()
3276
+ console.log(green(" ✓ Уведомление отправлено всем пользователям\n"))
3277
+ return
3278
+ }
3279
+
3280
+ case "/tg-sessions": {
3281
+ console.log()
3282
+ const status = getBotStatus()
3283
+ const sessions = status.sessions.sessions
3284
+
3285
+ if (sessions.length === 0) {
3286
+ console.log(dim(" Нет Telegram сессий\n"))
3287
+ } else {
3288
+ console.log(box([
3289
+ ...sessions.map((s, i) => {
3290
+ const isActive = s.id === status.sessions.activeSession
3291
+ const msgs = s.stats.userMessages + s.stats.assistantMessages
3292
+ return `${isActive ? green("●") : dim("○")} ${white(s.name)} ${dim(`(${s.model}, ${msgs} msgs)`)}`
3293
+ }),
3294
+ ], { title: "Telegram Sessions", color: blue, padding: 2 }))
3295
+ }
3296
+ console.log()
3297
+
3298
+ // Show auth info
3299
+ const authUsers = Object.keys(status.auth.users).length
3300
+ console.log(dim(` Авторизовано пользователей: ${authUsers}`))
3301
+ console.log()
3302
+ return
3303
+ }
3304
+
3305
+ case "/tg-verify": {
3306
+ if (!arg) {
3307
+ // Show pending codes
3308
+ const pending = getPendingCodes()
3309
+ const pendingList = Object.entries(pending)
3310
+
3311
+ if (pendingList.length === 0) {
3312
+ console.log(dim("\n Нет ожидающих кодов. Пользователь должен нажать /start в Telegram.\n"))
3313
+ } else {
3314
+ console.log(box([
3315
+ ...pendingList.map(([userId, p]) =>
3316
+ violet(p.code) + dim(" — ") + white(p.username) + dim(` (ID: ${userId})`)
3317
+ ),
3318
+ ], { title: "Ожидающие коды", color: blue, padding: 2 }))
3319
+ console.log()
3320
+ console.log(dim(" Введите: /tg-verify <код>"))
3321
+ }
3322
+ console.log()
3323
+ return
3324
+ }
3325
+
3326
+ const code = arg.trim()
3327
+ console.log()
3328
+ startSpinner("Проверяю код")
3329
+ const result = verifyAuthCode(code)
3330
+ stopSpinner()
3331
+
3332
+ if (result.success) {
3333
+ console.log(box([
3334
+ green("✓ Telegram аккаунт привязан!"),
3335
+ "",
3336
+ dim("Пользователь: ") + white(result.username),
3337
+ dim("Telegram ID: ") + white(String(result.userId)),
3338
+ "",
3339
+ white("Теперь вы можете управлять компьютером через Telegram!"),
3340
+ ], { title: "Привязка", color: green, padding: 2 }))
3341
+
3342
+ // Send confirmation to Telegram
3343
+ await notifyUser(result.chatId, "✅ <b>Аккаунт привязан!</b>\n\nТеперь вы можете управлять компьютером через Telegram.")
3344
+ } else {
3345
+ console.log(red(` ✗ ${result.error}\n`))
3346
+ }
3347
+ console.log()
3348
+ return
3349
+ }
3350
+
3351
+ case "/tg-code": {
3352
+ console.log()
3353
+ startSpinner("Генерирую код")
3354
+ const code = generateAdminCode()
3355
+ stopSpinner()
3356
+
3357
+ console.log(box([
3358
+ green("✓ Код сгенерирован!"),
3359
+ "",
3360
+ dim("Ваш код: ") + bold(violet(code)),
3361
+ "",
3362
+ white("Дайте этот код покупателю."),
3363
+ white("Покупатель вводит его в Telegram боте."),
3364
+ "",
3365
+ dim("Код действителен до использования."),
3366
+ dim("Для нового кода: /tg-code"),
3367
+ ], { title: "Код для продажи", color: green, padding: 2 }))
3368
+ console.log()
3369
+ return
3370
+ }
3371
+
3372
+ case "/tg-users": {
3373
+ console.log()
3374
+ const users = listAuthorizedUsers()
3375
+
3376
+ if (users.length === 0) {
3377
+ console.log(dim("\n Нет подключённых пользователей\n"))
3378
+ } else {
3379
+ console.log(box([
3380
+ ...users.map(u =>
3381
+ violet(u.firstName || u.username) + dim(` (ID: ${u.id})`) +
3382
+ dim("\n Подключён: ") + white(u.authenticatedAt)
3383
+ ),
3384
+ ], { title: "Подключённые пользователи", color: blue, padding: 2 }))
3385
+ }
3386
+ console.log()
3387
+ return
3388
+ }
3389
+
2474
3390
  default:
2475
3391
  console.log(dim(`\n Неизвестная команда: ${cmd}. Смотри /help\n`))
2476
3392
  }