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.
- package/.mcp.json +11 -0
- package/.opencode/skills/banner-design/SKILL.md +196 -0
- package/.opencode/skills/banner-design/references/banner-sizes-and-styles.md +118 -0
- package/.opencode/skills/brand/SKILL.md +97 -0
- package/.opencode/skills/brand/references/approval-checklist.md +169 -0
- package/.opencode/skills/brand/references/asset-organization.md +157 -0
- package/.opencode/skills/brand/references/brand-guideline-template.md +140 -0
- package/.opencode/skills/brand/references/color-palette-management.md +186 -0
- package/.opencode/skills/brand/references/consistency-checklist.md +94 -0
- package/.opencode/skills/brand/references/logo-usage-rules.md +185 -0
- package/.opencode/skills/brand/references/messaging-framework.md +85 -0
- package/.opencode/skills/brand/references/typography-specifications.md +214 -0
- package/.opencode/skills/brand/references/update.md +118 -0
- package/.opencode/skills/brand/references/visual-identity.md +96 -0
- package/.opencode/skills/brand/references/voice-framework.md +88 -0
- package/.opencode/skills/brand/scripts/extract-colors.cjs +341 -0
- package/.opencode/skills/brand/scripts/inject-brand-context.cjs +349 -0
- package/.opencode/skills/brand/scripts/sync-brand-to-tokens.cjs +248 -0
- package/.opencode/skills/brand/scripts/tests/test_sync_brand_to_tokens.py +52 -0
- package/.opencode/skills/brand/scripts/validate-asset.cjs +387 -0
- package/.opencode/skills/brand/templates/brand-guidelines-starter.md +275 -0
- package/.opencode/skills/design/SKILL.md +313 -0
- package/.opencode/skills/design/data/cip/deliverables.csv +51 -0
- package/.opencode/skills/design/data/cip/industries.csv +21 -0
- package/.opencode/skills/design/data/cip/mockup-contexts.csv +21 -0
- package/.opencode/skills/design/data/cip/styles.csv +21 -0
- package/.opencode/skills/design/data/icon/styles.csv +16 -0
- package/.opencode/skills/design/data/logo/colors.csv +56 -0
- package/.opencode/skills/design/data/logo/industries.csv +56 -0
- package/.opencode/skills/design/data/logo/styles.csv +56 -0
- package/.opencode/skills/design/references/banner-sizes-and-styles.md +118 -0
- package/.opencode/skills/design/references/cip-deliverable-guide.md +95 -0
- package/.opencode/skills/design/references/cip-design.md +121 -0
- package/.opencode/skills/design/references/cip-prompt-engineering.md +84 -0
- package/.opencode/skills/design/references/cip-style-guide.md +68 -0
- package/.opencode/skills/design/references/design-routing.md +207 -0
- package/.opencode/skills/design/references/icon-design.md +122 -0
- package/.opencode/skills/design/references/logo-color-psychology.md +101 -0
- package/.opencode/skills/design/references/logo-design.md +92 -0
- package/.opencode/skills/design/references/logo-prompt-engineering.md +158 -0
- package/.opencode/skills/design/references/logo-style-guide.md +109 -0
- package/.opencode/skills/design/references/slides-copywriting-formulas.md +84 -0
- package/.opencode/skills/design/references/slides-create.md +4 -0
- package/.opencode/skills/design/references/slides-html-template.md +295 -0
- package/.opencode/skills/design/references/slides-layout-patterns.md +137 -0
- package/.opencode/skills/design/references/slides-strategies.md +94 -0
- package/.opencode/skills/design/references/slides.md +42 -0
- package/.opencode/skills/design/references/social-photos-design.md +329 -0
- package/.opencode/skills/design/scripts/cip/core.py +215 -0
- package/.opencode/skills/design/scripts/cip/generate.py +484 -0
- package/.opencode/skills/design/scripts/cip/render-html.py +424 -0
- package/.opencode/skills/design/scripts/cip/search.py +127 -0
- package/.opencode/skills/design/scripts/icon/generate.py +487 -0
- package/.opencode/skills/design/scripts/logo/core.py +175 -0
- package/.opencode/skills/design/scripts/logo/generate.py +362 -0
- package/.opencode/skills/design/scripts/logo/search.py +114 -0
- package/.opencode/skills/design-system/SKILL.md +244 -0
- package/.opencode/skills/design-system/data/slide-backgrounds.csv +11 -0
- package/.opencode/skills/design-system/data/slide-charts.csv +26 -0
- package/.opencode/skills/design-system/data/slide-color-logic.csv +14 -0
- package/.opencode/skills/design-system/data/slide-copy.csv +26 -0
- package/.opencode/skills/design-system/data/slide-layout-logic.csv +16 -0
- package/.opencode/skills/design-system/data/slide-layouts.csv +26 -0
- package/.opencode/skills/design-system/data/slide-strategies.csv +16 -0
- package/.opencode/skills/design-system/data/slide-typography.csv +15 -0
- package/.opencode/skills/design-system/references/component-specs.md +236 -0
- package/.opencode/skills/design-system/references/component-tokens.md +214 -0
- package/.opencode/skills/design-system/references/primitive-tokens.md +203 -0
- package/.opencode/skills/design-system/references/semantic-tokens.md +215 -0
- package/.opencode/skills/design-system/references/states-and-variants.md +241 -0
- package/.opencode/skills/design-system/references/tailwind-integration.md +251 -0
- package/.opencode/skills/design-system/references/token-architecture.md +224 -0
- package/.opencode/skills/design-system/scripts/embed-tokens.cjs +99 -0
- package/.opencode/skills/design-system/scripts/fetch-background.py +317 -0
- package/.opencode/skills/design-system/scripts/generate-slide.py +770 -0
- package/.opencode/skills/design-system/scripts/generate-tokens.cjs +205 -0
- package/.opencode/skills/design-system/scripts/html-token-validator.py +327 -0
- package/.opencode/skills/design-system/scripts/search-slides.py +218 -0
- package/.opencode/skills/design-system/scripts/slide-token-validator.py +35 -0
- package/.opencode/skills/design-system/scripts/slide_search_core.py +453 -0
- package/.opencode/skills/design-system/scripts/tests/test_validate_tokens.py +48 -0
- package/.opencode/skills/design-system/scripts/validate-tokens.cjs +246 -0
- package/.opencode/skills/design-system/templates/design-tokens-starter.json +143 -0
- package/.opencode/skills/slides/SKILL.md +40 -0
- package/.opencode/skills/slides/references/copywriting-formulas.md +84 -0
- package/.opencode/skills/slides/references/create.md +4 -0
- package/.opencode/skills/slides/references/html-template.md +295 -0
- package/.opencode/skills/slides/references/layout-patterns.md +137 -0
- package/.opencode/skills/slides/references/slide-strategies.md +94 -0
- package/.opencode/skills/ui-styling/LICENSE.txt +202 -0
- package/.opencode/skills/ui-styling/SKILL.md +324 -0
- package/.opencode/skills/ui-styling/references/canvas-design-system.md +320 -0
- package/.opencode/skills/ui-styling/references/shadcn-accessibility.md +471 -0
- package/.opencode/skills/ui-styling/references/shadcn-components.md +424 -0
- package/.opencode/skills/ui-styling/references/shadcn-theming.md +373 -0
- package/.opencode/skills/ui-styling/references/tailwind-customization.md +483 -0
- package/.opencode/skills/ui-styling/references/tailwind-responsive.md +382 -0
- package/.opencode/skills/ui-styling/references/tailwind-utilities.md +455 -0
- package/.opencode/skills/ui-styling/scripts/requirements.txt +17 -0
- package/.opencode/skills/ui-styling/scripts/shadcn_add.py +308 -0
- package/.opencode/skills/ui-styling/scripts/tailwind_config_gen.py +473 -0
- package/.opencode/skills/ui-styling/scripts/tests/coverage-ui.json +1 -0
- package/.opencode/skills/ui-styling/scripts/tests/requirements.txt +3 -0
- package/.opencode/skills/ui-styling/scripts/tests/test_shadcn_add.py +266 -0
- package/.opencode/skills/ui-styling/scripts/tests/test_tailwind_config_gen.py +394 -0
- package/.opencode/skills/ui-ux-pro-max/SKILL.md +396 -0
- package/.opencode/skills/ui-ux-pro-max/data/_sync_all.py +414 -0
- package/.opencode/skills/ui-ux-pro-max/data/app-interface.csv +31 -0
- package/.opencode/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/.opencode/skills/ui-ux-pro-max/data/colors.csv +193 -0
- package/.opencode/skills/ui-ux-pro-max/data/design.csv +1776 -0
- package/.opencode/skills/ui-ux-pro-max/data/draft.csv +1779 -0
- package/.opencode/skills/ui-ux-pro-max/data/google-fonts.csv +1924 -0
- package/.opencode/skills/ui-ux-pro-max/data/icons.csv +106 -0
- package/.opencode/skills/ui-ux-pro-max/data/landing.csv +35 -0
- package/.opencode/skills/ui-ux-pro-max/data/motion.csv +17 -0
- package/.opencode/skills/ui-ux-pro-max/data/products.csv +193 -0
- package/.opencode/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/angular.csv +51 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/avalonia.csv +57 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/javafx.csv +76 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/laravel.csv +51 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv +71 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/threejs.csv +54 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/uno.csv +60 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/uwp.csv +56 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/winui.csv +60 -0
- package/.opencode/skills/ui-ux-pro-max/data/stacks/wpf.csv +57 -0
- package/.opencode/skills/ui-ux-pro-max/data/styles.csv +85 -0
- package/.opencode/skills/ui-ux-pro-max/data/typography.csv +75 -0
- package/.opencode/skills/ui-ux-pro-max/data/ui-reasoning.csv +162 -0
- package/.opencode/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.opencode/skills/ui-ux-pro-max/scripts/__pycache__/core.cpython-312.pyc +0 -0
- package/.opencode/skills/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-312.pyc +0 -0
- package/.opencode/skills/ui-ux-pro-max/scripts/core.py +274 -0
- package/.opencode/skills/ui-ux-pro-max/scripts/design_system.py +1329 -0
- package/.opencode/skills/ui-ux-pro-max/scripts/search.py +127 -0
- package/README.md +25 -30
- package/STELLA.md +1 -1
- package/dist/antimalware/database.mjs +871 -0
- package/dist/antimalware/index.mjs +8 -0
- package/dist/antimalware/scanner.mjs +591 -0
- package/dist/antimalware/ui.mjs +570 -0
- package/dist/banner.mjs +46 -0
- package/dist/index.mjs +3073 -0
- package/dist/markdown.mjs +100 -0
- package/dist/sea-config.json +4 -0
- package/dist/security.mjs +237 -0
- package/dist/theme.mjs +89 -0
- package/dist/tools.mjs +3145 -0
- package/install-stella.bat +44 -0
- package/package.json +5 -4
- package/stella-cli/coding-brain.mjs +753 -0
- package/stella-cli/index.mjs +931 -15
- package/stella-cli/mcp.mjs +296 -0
- package/stella-cli/presentations.mjs +1106 -0
- package/stella-cli/subagents.mjs +142 -0
- package/stella-cli/telegram-bot.mjs +824 -0
- package/stella-cli/tg-bg.bat +13 -0
- package/stella-cli/tg-server.bat +15 -0
- package/stella-cli/tg-server.mjs +116 -0
- package/stella-cli/theme.mjs +6 -4
- package/create_cats_pptx.py +0 -121
- package/hello.py +0 -2
package/stella-cli/index.mjs
CHANGED
|
@@ -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 = "
|
|
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
|
-
|
|
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
|
-
|
|
131
|
-
|
|
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
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
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
|
}
|