stella-coder 5.1.2 → 5.2.1
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/package.json +2 -1
- package/stella-cli/browser-control.mjs +274 -0
- package/stella-cli/git-api.mjs +407 -0
- package/stella-cli/index.mjs +687 -1
- package/stella-cli/screen-monitor.mjs +334 -0
- package/stella-cli/web-parser.mjs +229 -0
package/stella-cli/index.mjs
CHANGED
|
@@ -22,6 +22,10 @@ import { runSubagent, listSubagents, parseAgentCommand, SUBAGENTS } from "./suba
|
|
|
22
22
|
import { mcp, MCP_COMMANDS } from "./mcp.mjs"
|
|
23
23
|
import { generatePresentation, createPresentationFromTopic, AVAILABLE_THEMES, exportToPDF } from "./presentations.mjs"
|
|
24
24
|
import { AutonomousAgent } from "./autonomous-agent.mjs"
|
|
25
|
+
import { ScreenMonitor } from "./screen-monitor.mjs"
|
|
26
|
+
import { ChromeBrowser } from "./browser-control.mjs"
|
|
27
|
+
import { GitAPI } from "./git-api.mjs"
|
|
28
|
+
import { WebParser } from "./web-parser.mjs"
|
|
25
29
|
import {
|
|
26
30
|
buildRepoMap, buildProjectContext, compressContext,
|
|
27
31
|
loadSpec, generateSpecTemplate,
|
|
@@ -41,7 +45,7 @@ import {
|
|
|
41
45
|
generateAdminCode, listAuthorizedUsers,
|
|
42
46
|
} from "./telegram-bot.mjs"
|
|
43
47
|
|
|
44
|
-
const VERSION = "5.
|
|
48
|
+
const VERSION = "5.2.0"
|
|
45
49
|
const CONFIG_DIR = path.join(os.homedir(), ".stella")
|
|
46
50
|
const CONFIG_PATH = path.join(CONFIG_DIR, "config.json")
|
|
47
51
|
const HISTORY_PATH = path.join(CONFIG_DIR, "history.json")
|
|
@@ -650,6 +654,44 @@ const COMMANDS = [
|
|
|
650
654
|
["/auto-dashboard", "создать dashboard с результатами"],
|
|
651
655
|
["/auto-history", "история выполненных задач"],
|
|
652
656
|
|
|
657
|
+
// Computer Vision Monitor
|
|
658
|
+
["/monitor", "запустить мониторинг экрана (AI vision)"],
|
|
659
|
+
["/monitor-stop", "остановить мониторинг"],
|
|
660
|
+
["/monitor-status", "статус мониторинга"],
|
|
661
|
+
["/monitor-alerts", "показать обнаруженные проблемы"],
|
|
662
|
+
["/monitor-screenshot", "сделать скриншот и проанализировать"],
|
|
663
|
+
|
|
664
|
+
// Chrome DevTools Protocol
|
|
665
|
+
["/browser", "открыть браузер и перейти на URL"],
|
|
666
|
+
["/browser-click", "кликнуть по элементу (CSS selector)"],
|
|
667
|
+
["/browser-type", "набрать текст в поле"],
|
|
668
|
+
["/browser-scroll", "прокрутить страницу"],
|
|
669
|
+
["/browser-screenshot", "скриншот текущей страницы"],
|
|
670
|
+
["/browser-links", "показать все ссылки"],
|
|
671
|
+
["/browser-forms", "показать формы на странице"],
|
|
672
|
+
["/browser-close", "закрыть браузер"],
|
|
673
|
+
|
|
674
|
+
// Git API (GitHub/GitLab)
|
|
675
|
+
["/git-issues", "список issues"],
|
|
676
|
+
["/git-issue", "создать issue"],
|
|
677
|
+
["/git-prs", "список Pull Requests"],
|
|
678
|
+
["/git-pr-create", "создать Pull Request"],
|
|
679
|
+
["/git-pr-review", "оставить ревью на PR"],
|
|
680
|
+
["/git-pr-merge", "слить PR"],
|
|
681
|
+
["/git-branches", "список веток"],
|
|
682
|
+
["/git-releases", "список релизов"],
|
|
683
|
+
["/git-release", "создать релиз"],
|
|
684
|
+
|
|
685
|
+
// Web Parser
|
|
686
|
+
["/fetch", "загрузить и показать страницу"],
|
|
687
|
+
["/fetch-text", "извлечь текст со страницы"],
|
|
688
|
+
["/fetch-links", "извлечь все ссылки"],
|
|
689
|
+
["/fetch-images", "извлечь все картинки"],
|
|
690
|
+
["/fetch-forms", "извлечь формы"],
|
|
691
|
+
["/fetch-seo", "SEO анализ страницы"],
|
|
692
|
+
["/web-search", "поиск в интернете"],
|
|
693
|
+
["/web-batch", "загрузить несколько страниц"],
|
|
694
|
+
|
|
653
695
|
// Управление компьютером
|
|
654
696
|
["/open", "открыть приложение/файл/URL"],
|
|
655
697
|
["/app", "запустить приложение"],
|
|
@@ -775,6 +817,10 @@ async function handleCommand(line) {
|
|
|
775
817
|
["📱 Telegram", ["/tg", "/tg-stop", "/tg-notify", "/tg-sessions", "/tg-verify", "/tg-code", "/tg-users"]],
|
|
776
818
|
["🔀 Git Экосистема", ["/git-eco", "/git-pr", "/git-merge-auto"]],
|
|
777
819
|
["🤖 Autonomous Agent", ["/auto", "/auto-stop", "/auto-status", "/auto-dashboard", "/auto-history"]],
|
|
820
|
+
["👁️ Computer Vision", ["/monitor", "/monitor-stop", "/monitor-status", "/monitor-alerts", "/monitor-screenshot"]],
|
|
821
|
+
["🌐 Browser Control", ["/browser", "/browser-click", "/browser-type", "/browser-scroll", "/browser-screenshot", "/browser-links", "/browser-forms", "/browser-close"]],
|
|
822
|
+
["🔗 Git API", ["/git-issues", "/git-issue", "/git-prs", "/git-pr-create", "/git-pr-review", "/git-pr-merge", "/git-branches", "/git-releases", "/git-release"]],
|
|
823
|
+
["🔍 Web Parser", ["/fetch", "/fetch-text", "/fetch-links", "/fetch-images", "/fetch-forms", "/fetch-seo", "/web-search", "/web-batch"]],
|
|
778
824
|
["⚙️ Настройки", ["/help", "/model", "/clear", "/compact", "/cost", "/context", "/config", "/version", "/login", "/newkey", "/doctor", "/sessions", "/color", "/lang", "/shortcut"]],
|
|
779
825
|
]
|
|
780
826
|
for (const [cat, cmds] of categories) {
|
|
@@ -3599,6 +3645,646 @@ async function handleCommand(line) {
|
|
|
3599
3645
|
return
|
|
3600
3646
|
}
|
|
3601
3647
|
|
|
3648
|
+
// ═══════════════════════════════════════════════════
|
|
3649
|
+
// COMPUTER VISION MONITOR
|
|
3650
|
+
// ═══════════════════════════════════════════════════
|
|
3651
|
+
case "/monitor": {
|
|
3652
|
+
console.log()
|
|
3653
|
+
const monitor = new ScreenMonitor()
|
|
3654
|
+
|
|
3655
|
+
if (monitor.state.running) {
|
|
3656
|
+
console.log(yellow(" Мониторинг уже запущен! Используй /monitor-stop для остановки.\n"))
|
|
3657
|
+
return
|
|
3658
|
+
}
|
|
3659
|
+
|
|
3660
|
+
const intervalSec = arg ? parseInt(arg) || 2 : 2
|
|
3661
|
+
const intervalMs = intervalSec * 1000
|
|
3662
|
+
|
|
3663
|
+
console.log(box([
|
|
3664
|
+
violet("👁️ COMPUTER VISION MONITOR"),
|
|
3665
|
+
"",
|
|
3666
|
+
white("Интервал: ") + cyan(`${intervalSec} секунды`),
|
|
3667
|
+
white("Режим: ") + cyan("автоматический скриншот + AI анализ"),
|
|
3668
|
+
"",
|
|
3669
|
+
dim("Агент будет делать скриншот каждые") + white(`${intervalSec}с`),
|
|
3670
|
+
dim("и анализировать через AI на наличие ошибок, крашей, алертов."),
|
|
3671
|
+
dim("Все обнаруженные проблемы будут сохранены в лог."),
|
|
3672
|
+
], { color: violet, padding: 2 }))
|
|
3673
|
+
console.log()
|
|
3674
|
+
|
|
3675
|
+
const confirmMonitor = await question(" " + green("Запустить мониторинг? (y/n) › "))
|
|
3676
|
+
if (confirmMonitor.trim().toLowerCase() !== "y" && confirmMonitor.trim().toLowerCase() !== "д") {
|
|
3677
|
+
console.log(dim(" Отменено\n"))
|
|
3678
|
+
return
|
|
3679
|
+
}
|
|
3680
|
+
|
|
3681
|
+
const visionFn = async (imageBase64, recentContext) => {
|
|
3682
|
+
const contextStr = recentContext?.length > 0
|
|
3683
|
+
? `\nRecent history:\n${recentContext.map(r => `- ${r.time}: ${r.summary} (${r.alerts} alerts)`).join("\n")}`
|
|
3684
|
+
: ""
|
|
3685
|
+
|
|
3686
|
+
const prompt = `Analyze this screenshot. Detect:
|
|
3687
|
+
1. Error messages, crash dialogs, exception windows
|
|
3688
|
+
2. Alert popups, warning notifications
|
|
3689
|
+
3. Application crashes or "not responding" dialogs
|
|
3690
|
+
4. System errors, BSOD, kernel panics
|
|
3691
|
+
5. Any other issues or anomalies
|
|
3692
|
+
|
|
3693
|
+
${contextStr}
|
|
3694
|
+
|
|
3695
|
+
Return JSON:
|
|
3696
|
+
{
|
|
3697
|
+
"summary": "one-line description of what's on screen",
|
|
3698
|
+
"text": "detailed analysis of what you see",
|
|
3699
|
+
"issues": ["list of issues found"],
|
|
3700
|
+
"severity": "none|info|warning|critical",
|
|
3701
|
+
"recommendations": ["what to do about issues"]
|
|
3702
|
+
}
|
|
3703
|
+
|
|
3704
|
+
If everything looks normal, set severity to "none" and issues to [].`
|
|
3705
|
+
|
|
3706
|
+
const model = getModel(state.model)
|
|
3707
|
+
const { text } = await generateText({
|
|
3708
|
+
model,
|
|
3709
|
+
messages: [{
|
|
3710
|
+
role: "user",
|
|
3711
|
+
content: [
|
|
3712
|
+
{ type: "text", text: prompt },
|
|
3713
|
+
{ type: "image", image: `data:image/png;base64,${imageBase64}` }
|
|
3714
|
+
]
|
|
3715
|
+
}],
|
|
3716
|
+
maxTokens: 2000,
|
|
3717
|
+
})
|
|
3718
|
+
|
|
3719
|
+
try {
|
|
3720
|
+
let clean = text.trim()
|
|
3721
|
+
if (clean.startsWith("```")) clean = clean.replace(/^```(?:json)?\n?/, "").replace(/\n?```$/, "")
|
|
3722
|
+
return JSON.parse(clean)
|
|
3723
|
+
} catch {
|
|
3724
|
+
return { summary: text.slice(0, 200), text, issues: [], severity: "none", recommendations: [] }
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3727
|
+
|
|
3728
|
+
console.log()
|
|
3729
|
+
startSpinner("Запускаю мониторинг")
|
|
3730
|
+
const result = monitor.start(intervalMs, visionFn)
|
|
3731
|
+
stopSpinner()
|
|
3732
|
+
|
|
3733
|
+
if (result.success) {
|
|
3734
|
+
console.log(green(` ✓ Мониторинг запущен! Интервал: ${intervalSec}с`))
|
|
3735
|
+
console.log(dim(" Скриншоты сохраняются в ~/.stella/monitor/screenshots/"))
|
|
3736
|
+
console.log(dim(" Используй /monitor-stop для остановки"))
|
|
3737
|
+
console.log(dim(" Используй /monitor-status для проверки"))
|
|
3738
|
+
console.log(dim(" Используй /monitor-alerts для просмотра проблем\n"))
|
|
3739
|
+
|
|
3740
|
+
// Show first capture
|
|
3741
|
+
console.log(dim(" Делаю первый скриншот..."))
|
|
3742
|
+
const firstCapture = await monitor.captureAndAnalyze(visionFn)
|
|
3743
|
+
if (firstCapture.success) {
|
|
3744
|
+
console.log(green(` ✓ Скриншот: ${firstCapture.capture.sizeKB}KB`))
|
|
3745
|
+
if (firstCapture.capture.analysis?.summary) {
|
|
3746
|
+
console.log(dim(" Анализ: ") + white(firstCapture.capture.analysis.summary))
|
|
3747
|
+
}
|
|
3748
|
+
if (firstCapture.capture.alerts?.length > 0) {
|
|
3749
|
+
console.log(yellow(` ⚠️ Обнаружено проблем: ${firstCapture.capture.alerts.length}`))
|
|
3750
|
+
}
|
|
3751
|
+
}
|
|
3752
|
+
console.log()
|
|
3753
|
+
} else {
|
|
3754
|
+
console.log(red(` ✗ ${result.error}\n`))
|
|
3755
|
+
}
|
|
3756
|
+
return
|
|
3757
|
+
}
|
|
3758
|
+
|
|
3759
|
+
case "/monitor-stop": {
|
|
3760
|
+
console.log()
|
|
3761
|
+
const monitor = new ScreenMonitor()
|
|
3762
|
+
if (!monitor.state.running) {
|
|
3763
|
+
console.log(dim(" Мониторинг не запущен\n"))
|
|
3764
|
+
return
|
|
3765
|
+
}
|
|
3766
|
+
monitor.stop()
|
|
3767
|
+
console.log(green(" ✓ Мониторинг остановлен"))
|
|
3768
|
+
console.log(dim(` Всего скриншотов: ${monitor.state.totalCaptures}`))
|
|
3769
|
+
console.log(dim(` Всего алертов: ${monitor.state.totalAlerts}`))
|
|
3770
|
+
console.log()
|
|
3771
|
+
return
|
|
3772
|
+
}
|
|
3773
|
+
|
|
3774
|
+
case "/monitor-status": {
|
|
3775
|
+
console.log()
|
|
3776
|
+
const monitor = new ScreenMonitor()
|
|
3777
|
+
const status = monitor.getStatus()
|
|
3778
|
+
|
|
3779
|
+
if (!status.running && status.totalCaptures === 0) {
|
|
3780
|
+
console.log(dim(" Мониторинг не запущен. Используй /monitor для запуска.\n"))
|
|
3781
|
+
return
|
|
3782
|
+
}
|
|
3783
|
+
|
|
3784
|
+
const items = [
|
|
3785
|
+
dim("Статус: ") + (status.running ? green("● RUNNING") : red("○ STOPPED")),
|
|
3786
|
+
dim("Интервал: ") + cyan(`every ${status.interval / 1000}s`),
|
|
3787
|
+
dim("Начато: ") + white(status.startedAt ? new Date(status.startedAt).toLocaleString() : "—"),
|
|
3788
|
+
dim("Скриншотов: ") + cyan(String(status.totalCaptures)),
|
|
3789
|
+
dim("Алертов: ") + (status.totalAlerts > 0 ? red(String(status.totalAlerts)) : green("0")),
|
|
3790
|
+
dim("PID: ") + white(String(status.pid || "—")),
|
|
3791
|
+
]
|
|
3792
|
+
|
|
3793
|
+
if (status.recentAlerts.length > 0) {
|
|
3794
|
+
items.push("", violet("Последние алерты:"))
|
|
3795
|
+
for (const a of status.recentAlerts.slice(-5)) {
|
|
3796
|
+
const icon = a.severity === "critical" ? red("🔴") : yellow("🟡")
|
|
3797
|
+
items.push(` ${icon} [${new Date(a.timestamp).toLocaleTimeString()}] ${a.type}`)
|
|
3798
|
+
if (a.description) items.push(dim(` ${a.description.slice(0, 100)}`))
|
|
3799
|
+
}
|
|
3800
|
+
}
|
|
3801
|
+
|
|
3802
|
+
if (status.recentCaptures.length > 0) {
|
|
3803
|
+
items.push("", dim("Последние скриншоты:"))
|
|
3804
|
+
for (const c of status.recentCaptures) {
|
|
3805
|
+
const icon = c.alertCount > 0 ? yellow("⚠️") : green("✅")
|
|
3806
|
+
items.push(` ${icon} [${new Date(c.time).toLocaleTimeString()}] ${c.summary}`)
|
|
3807
|
+
}
|
|
3808
|
+
}
|
|
3809
|
+
|
|
3810
|
+
console.log(box(items, { title: "👁️ Monitor Status", color: violet, padding: 2 }))
|
|
3811
|
+
console.log()
|
|
3812
|
+
return
|
|
3813
|
+
}
|
|
3814
|
+
|
|
3815
|
+
case "/monitor-alerts": {
|
|
3816
|
+
console.log()
|
|
3817
|
+
const monitor = new ScreenMonitor()
|
|
3818
|
+
const alerts = monitor.getAlerts()
|
|
3819
|
+
|
|
3820
|
+
if (alerts.length === 0) {
|
|
3821
|
+
console.log(dim(" Нет обнаруженных проблем. Всё чисто! ✅\n"))
|
|
3822
|
+
return
|
|
3823
|
+
}
|
|
3824
|
+
|
|
3825
|
+
console.log(box([
|
|
3826
|
+
violet(`Всего алертов: ${alerts.length}`),
|
|
3827
|
+
"",
|
|
3828
|
+
...alerts.slice(-15).map(a => {
|
|
3829
|
+
const icon = a.severity === "critical" ? red("🔴") : yellow("🟡")
|
|
3830
|
+
return `${icon} [${new Date(a.timestamp).toLocaleTimeString()}] ${a.type}`
|
|
3831
|
+
}),
|
|
3832
|
+
], { title: "⚠️ Monitor Alerts", color: yellow, padding: 2 }))
|
|
3833
|
+
console.log()
|
|
3834
|
+
return
|
|
3835
|
+
}
|
|
3836
|
+
|
|
3837
|
+
case "/monitor-screenshot": {
|
|
3838
|
+
console.log()
|
|
3839
|
+
startSpinner("Делаю скриншот")
|
|
3840
|
+
const monitor = new ScreenMonitor()
|
|
3841
|
+
|
|
3842
|
+
const visionFn = async (imageBase64) => {
|
|
3843
|
+
const prompt = `Analyze this screenshot in detail. What's on screen? Any errors, issues, or anomalies? Return JSON with "summary", "text", "issues", "severity".`
|
|
3844
|
+
const model = getModel(state.model)
|
|
3845
|
+
const { text } = await generateText({
|
|
3846
|
+
model,
|
|
3847
|
+
messages: [{
|
|
3848
|
+
role: "user",
|
|
3849
|
+
content: [
|
|
3850
|
+
{ type: "text", text: prompt },
|
|
3851
|
+
{ type: "image", image: `data:image/png;base64,${imageBase64}` }
|
|
3852
|
+
]
|
|
3853
|
+
}],
|
|
3854
|
+
maxTokens: 2000,
|
|
3855
|
+
})
|
|
3856
|
+
try { return JSON.parse(text) } catch { return { summary: text.slice(0, 200), text } }
|
|
3857
|
+
}
|
|
3858
|
+
|
|
3859
|
+
const capture = await monitor.captureAndAnalyze(visionFn)
|
|
3860
|
+
stopSpinner()
|
|
3861
|
+
|
|
3862
|
+
if (capture.success) {
|
|
3863
|
+
console.log(box([
|
|
3864
|
+
green("✓ Скриншот сделан!"),
|
|
3865
|
+
"",
|
|
3866
|
+
dim("Файл: ") + cyan(capture.capture.path),
|
|
3867
|
+
dim("Размер: ") + white(`${capture.capture.sizeKB}KB`),
|
|
3868
|
+
"",
|
|
3869
|
+
violet("Анализ:"),
|
|
3870
|
+
white(capture.capture.analysis?.summary || "analyzing..."),
|
|
3871
|
+
capture.capture.analysis?.text ? dim(capture.capture.analysis.text.slice(0, 500)) : "",
|
|
3872
|
+
"",
|
|
3873
|
+
capture.capture.alerts?.length > 0
|
|
3874
|
+
? yellow(`⚠️ Обнаружено проблем: ${capture.capture.alerts.length}`)
|
|
3875
|
+
: green("✅ Проблем не обнаружено"),
|
|
3876
|
+
], { title: "📸 Screenshot Analysis", color: cyan, padding: 2 }))
|
|
3877
|
+
} else {
|
|
3878
|
+
console.log(red(` ✗ ${capture.error}\n`))
|
|
3879
|
+
}
|
|
3880
|
+
console.log()
|
|
3881
|
+
return
|
|
3882
|
+
}
|
|
3883
|
+
|
|
3884
|
+
// ═══════════════════════════════════════════════════
|
|
3885
|
+
// CHROME DEVTOOLS PROTOCOL (BROWSER CONTROL)
|
|
3886
|
+
// ═══════════════════════════════════════════════════
|
|
3887
|
+
case "/browser": {
|
|
3888
|
+
console.log()
|
|
3889
|
+
const browser = new ChromeBrowser()
|
|
3890
|
+
const url = arg || "about:blank"
|
|
3891
|
+
startSpinner("Launching browser")
|
|
3892
|
+
const launch = await browser.launch(false)
|
|
3893
|
+
stopSpinner()
|
|
3894
|
+
if (!launch.success) { console.log(red(` ✗ ${launch.error}\n`)); return }
|
|
3895
|
+
startSpinner("Connecting")
|
|
3896
|
+
const conn = await browser.connect()
|
|
3897
|
+
stopSpinner()
|
|
3898
|
+
if (!conn.success) { console.log(red(` ✗ ${conn.error}\n`)); return }
|
|
3899
|
+
startSpinner(`Navigating to ${url}`)
|
|
3900
|
+
await browser.navigate(url)
|
|
3901
|
+
stopSpinner()
|
|
3902
|
+
const title = await browser.getTitle()
|
|
3903
|
+
console.log(box([
|
|
3904
|
+
green("✓ Browser launched!"), "",
|
|
3905
|
+
dim("URL: ") + cyan(url),
|
|
3906
|
+
dim("Title: ") + white(title.title || "—"), "",
|
|
3907
|
+
dim("Commands: /browser-click, /browser-type, /browser-scroll"),
|
|
3908
|
+
dim(" /browser-screenshot, /browser-links, /browser-close"),
|
|
3909
|
+
], { title: "🌐 Browser", color: cyan, padding: 2 }))
|
|
3910
|
+
console.log()
|
|
3911
|
+
return
|
|
3912
|
+
}
|
|
3913
|
+
|
|
3914
|
+
case "/browser-click": {
|
|
3915
|
+
if (!arg) { console.log(dim("\n Usage: /browser-click <css-selector>\n")); return }
|
|
3916
|
+
const browser = new ChromeBrowser()
|
|
3917
|
+
const result = await browser.click(arg.trim())
|
|
3918
|
+
if (result.success) console.log(green(` ✓ Clicked at (${result.x}, ${result.y})\n`))
|
|
3919
|
+
else console.log(red(` ✗ ${result.error}\n`))
|
|
3920
|
+
return
|
|
3921
|
+
}
|
|
3922
|
+
|
|
3923
|
+
case "/browser-type": {
|
|
3924
|
+
if (!arg) { console.log(dim("\n Usage: /browser-type <selector> <text>\n")); return }
|
|
3925
|
+
const [sel, ...textParts] = arg.split(" ")
|
|
3926
|
+
const text = textParts.join(" ")
|
|
3927
|
+
const browser = new ChromeBrowser()
|
|
3928
|
+
const result = await browser.type(sel, text)
|
|
3929
|
+
if (result.success) console.log(green(` ✓ Typed "${text}" in ${sel}\n`))
|
|
3930
|
+
else console.log(red(` ✗ ${result.error}\n`))
|
|
3931
|
+
return
|
|
3932
|
+
}
|
|
3933
|
+
|
|
3934
|
+
case "/browser-scroll": {
|
|
3935
|
+
const browser = new ChromeBrowser()
|
|
3936
|
+
const delta = arg ? parseInt(arg) || 300 : 300
|
|
3937
|
+
await browser.scroll(0, delta)
|
|
3938
|
+
console.log(green(` ✓ Scrolled ${delta}px\n`))
|
|
3939
|
+
return
|
|
3940
|
+
}
|
|
3941
|
+
|
|
3942
|
+
case "/browser-screenshot": {
|
|
3943
|
+
const browser = new ChromeBrowser()
|
|
3944
|
+
const ss = await browser.screenshot()
|
|
3945
|
+
if (ss.success) {
|
|
3946
|
+
console.log(green(` ✓ Screenshot saved: ${ss.path}\n`))
|
|
3947
|
+
try { execSync(`start "" "${ss.path}"`, { shell: "cmd.exe" }) } catch {}
|
|
3948
|
+
} else {
|
|
3949
|
+
console.log(red(` ✗ ${ss.error}\n`))
|
|
3950
|
+
}
|
|
3951
|
+
return
|
|
3952
|
+
}
|
|
3953
|
+
|
|
3954
|
+
case "/browser-links": {
|
|
3955
|
+
const browser = new ChromeBrowser()
|
|
3956
|
+
const links = await browser.getLinks()
|
|
3957
|
+
if (links.success && links.links.length > 0) {
|
|
3958
|
+
console.log(box([
|
|
3959
|
+
...links.links.slice(0, 20).map(l => cyan(l.text.slice(0, 50)) + dim(` → ${l.href.slice(0, 60)}`)),
|
|
3960
|
+
links.links.length > 20 ? dim(`... and ${links.links.length - 20} more`) : "",
|
|
3961
|
+
].filter(Boolean), { title: `🔗 Links (${links.links.length})`, color: cyan, padding: 2 }))
|
|
3962
|
+
} else {
|
|
3963
|
+
console.log(dim(" No links found\n"))
|
|
3964
|
+
}
|
|
3965
|
+
console.log()
|
|
3966
|
+
return
|
|
3967
|
+
}
|
|
3968
|
+
|
|
3969
|
+
case "/browser-forms": {
|
|
3970
|
+
const browser = new ChromeBrowser()
|
|
3971
|
+
const forms = await browser.getForms()
|
|
3972
|
+
if (forms.success && forms.forms.length > 0) {
|
|
3973
|
+
console.log(box([
|
|
3974
|
+
...forms.forms.map((f, i) => [
|
|
3975
|
+
violet(`Form ${i + 1}: ${f.method} ${f.action?.slice(0, 50)}`),
|
|
3976
|
+
...f.inputs.map(inp => dim(` ${inp.name || inp.type}: ${inp.placeholder || ""}`)),
|
|
3977
|
+
].join("\n")),
|
|
3978
|
+
], { title: `📝 Forms (${forms.forms.length})`, color: violet, padding: 2 }))
|
|
3979
|
+
} else {
|
|
3980
|
+
console.log(dim(" No forms found\n"))
|
|
3981
|
+
}
|
|
3982
|
+
console.log()
|
|
3983
|
+
return
|
|
3984
|
+
}
|
|
3985
|
+
|
|
3986
|
+
case "/browser-close": {
|
|
3987
|
+
const browser = new ChromeBrowser()
|
|
3988
|
+
browser.close()
|
|
3989
|
+
console.log(green(" ✓ Browser closed\n"))
|
|
3990
|
+
return
|
|
3991
|
+
}
|
|
3992
|
+
|
|
3993
|
+
// ═══════════════════════════════════════════════════
|
|
3994
|
+
// GIT API (GitHub/GitLab)
|
|
3995
|
+
// ═══════════════════════════════════════════════════
|
|
3996
|
+
case "/git-issues": {
|
|
3997
|
+
console.log()
|
|
3998
|
+
const api = new GitAPI()
|
|
3999
|
+
if (!api.isConfigured()) { console.log(red(" ✗ No GitHub/GitLab remote found\n")); return }
|
|
4000
|
+
if (!api.hasToken()) { console.log(yellow(" ⚠ No token. Set GITHUB_TOKEN or GITLAB_TOKEN\n")); return }
|
|
4001
|
+
startSpinner("Fetching issues")
|
|
4002
|
+
const result = await api.listIssues()
|
|
4003
|
+
stopSpinner()
|
|
4004
|
+
if (result.success && result.issues.length > 0) {
|
|
4005
|
+
console.log(box([
|
|
4006
|
+
...result.issues.slice(0, 15).map(i => {
|
|
4007
|
+
const num = i.number || i.iid
|
|
4008
|
+
const state = i.state === "open" ? green("●") : red("●")
|
|
4009
|
+
return `${state} ${violet(`#${num}`)} ${white(i.title?.slice(0, 60))}`
|
|
4010
|
+
}),
|
|
4011
|
+
], { title: `📋 Issues (${result.issues.length})`, color: cyan, padding: 2 }))
|
|
4012
|
+
} else {
|
|
4013
|
+
console.log(dim(" No issues found\n"))
|
|
4014
|
+
}
|
|
4015
|
+
console.log()
|
|
4016
|
+
return
|
|
4017
|
+
}
|
|
4018
|
+
|
|
4019
|
+
case "/git-issue": {
|
|
4020
|
+
if (!arg) { console.log(dim("\n Usage: /git-issue <title>\n")); return }
|
|
4021
|
+
const api = new GitAPI()
|
|
4022
|
+
if (!api.isConfigured() || !api.hasToken()) { console.log(red(" ✗ Not configured\n")); return }
|
|
4023
|
+
startSpinner("Creating issue")
|
|
4024
|
+
const result = await api.createIssue(arg.trim(), `Created by Stella Coder\n\n_${new Date().toISOString()}_`)
|
|
4025
|
+
stopSpinner()
|
|
4026
|
+
if (result.success) {
|
|
4027
|
+
console.log(green(` ✓ Issue created: ${result.url}\n`))
|
|
4028
|
+
} else {
|
|
4029
|
+
console.log(red(` ✗ Failed to create issue\n`))
|
|
4030
|
+
}
|
|
4031
|
+
return
|
|
4032
|
+
}
|
|
4033
|
+
|
|
4034
|
+
case "/git-prs": {
|
|
4035
|
+
console.log()
|
|
4036
|
+
const api = new GitAPI()
|
|
4037
|
+
if (!api.isConfigured() || !api.hasToken()) { console.log(red(" ✗ Not configured\n")); return }
|
|
4038
|
+
startSpinner("Fetching PRs")
|
|
4039
|
+
const result = await api.listPRs()
|
|
4040
|
+
stopSpinner()
|
|
4041
|
+
if (result.success && result.prs.length > 0) {
|
|
4042
|
+
console.log(box([
|
|
4043
|
+
...result.prs.slice(0, 15).map(pr => {
|
|
4044
|
+
const num = pr.number || pr.iid
|
|
4045
|
+
const state = pr.merged_at ? violet("merged") : pr.state === "open" ? green("open") : red("closed")
|
|
4046
|
+
return `${state} ${violet(`#${num}`)} ${white(pr.title?.slice(0, 60))}`
|
|
4047
|
+
}),
|
|
4048
|
+
], { title: `🔀 Pull Requests (${result.prs.length})`, color: violet, padding: 2 }))
|
|
4049
|
+
} else {
|
|
4050
|
+
console.log(dim(" No PRs found\n"))
|
|
4051
|
+
}
|
|
4052
|
+
console.log()
|
|
4053
|
+
return
|
|
4054
|
+
}
|
|
4055
|
+
|
|
4056
|
+
case "/git-pr-create": {
|
|
4057
|
+
if (!arg) { console.log(dim("\n Usage: /git-pr-create <title>\n")); return }
|
|
4058
|
+
const api = new GitAPI()
|
|
4059
|
+
if (!api.isConfigured() || !api.hasToken()) { console.log(red(" ✗ Not configured\n")); return }
|
|
4060
|
+
startSpinner("Creating PR")
|
|
4061
|
+
const result = await api.createPR(arg.trim(), `Created by Stella Coder\n\n_${new Date().toISOString()}_`)
|
|
4062
|
+
stopSpinner()
|
|
4063
|
+
if (result.success) {
|
|
4064
|
+
console.log(green(` ✓ PR created: ${result.url}\n`))
|
|
4065
|
+
} else {
|
|
4066
|
+
console.log(red(` ✗ Failed to create PR\n`))
|
|
4067
|
+
}
|
|
4068
|
+
return
|
|
4069
|
+
}
|
|
4070
|
+
|
|
4071
|
+
case "/git-pr-review": {
|
|
4072
|
+
if (!arg) { console.log(dim("\n Usage: /git-pr-review <number> <APPROVE|REQUEST_CHANGES|COMMENT>\n")); return }
|
|
4073
|
+
const [prNum, ...reviewParts] = arg.split(" ")
|
|
4074
|
+
const reviewEvent = reviewParts.join(" ") || "COMMENT"
|
|
4075
|
+
const api = new GitAPI()
|
|
4076
|
+
if (!api.isConfigured() || !api.hasToken()) { console.log(red(" ✗ Not configured\n")); return }
|
|
4077
|
+
startSpinner("Submitting review")
|
|
4078
|
+
const result = await api.reviewPR(parseInt(prNum), reviewEvent, `Review by Stella Coder`)
|
|
4079
|
+
stopSpinner()
|
|
4080
|
+
if (result.success) console.log(green(` ✓ Review submitted on #${prNum}\n`))
|
|
4081
|
+
else console.log(red(` ✗ Failed\n`))
|
|
4082
|
+
return
|
|
4083
|
+
}
|
|
4084
|
+
|
|
4085
|
+
case "/git-pr-merge": {
|
|
4086
|
+
if (!arg) { console.log(dim("\n Usage: /git-pr-merge <number>\n")); return }
|
|
4087
|
+
const api = new GitAPI()
|
|
4088
|
+
if (!api.isConfigured() || !api.hasToken()) { console.log(red(" ✗ Not configured\n")); return }
|
|
4089
|
+
startSpinner("Merging PR")
|
|
4090
|
+
const result = await api.mergePR(parseInt(arg))
|
|
4091
|
+
stopSpinner()
|
|
4092
|
+
if (result.success) console.log(green(` ✓ PR #${arg} merged\n`))
|
|
4093
|
+
else console.log(red(` ✗ Failed\n`))
|
|
4094
|
+
return
|
|
4095
|
+
}
|
|
4096
|
+
|
|
4097
|
+
case "/git-branches": {
|
|
4098
|
+
console.log()
|
|
4099
|
+
const api = new GitAPI()
|
|
4100
|
+
if (!api.isConfigured()) { console.log(red(" ✗ Not configured\n")); return }
|
|
4101
|
+
startSpinner("Fetching branches")
|
|
4102
|
+
const result = await api.listBranches()
|
|
4103
|
+
stopSpinner()
|
|
4104
|
+
if (result.success) {
|
|
4105
|
+
console.log(box(result.branches.map(b => cyan(b)), { title: "🌿 Branches", color: cyan, padding: 2 }))
|
|
4106
|
+
}
|
|
4107
|
+
console.log()
|
|
4108
|
+
return
|
|
4109
|
+
}
|
|
4110
|
+
|
|
4111
|
+
case "/git-releases": {
|
|
4112
|
+
console.log()
|
|
4113
|
+
const api = new GitAPI()
|
|
4114
|
+
if (!api.isConfigured()) { console.log(red(" ✗ Not configured\n")); return }
|
|
4115
|
+
startSpinner("Fetching releases")
|
|
4116
|
+
const result = await api.listReleases()
|
|
4117
|
+
stopSpinner()
|
|
4118
|
+
if (result.success && result.releases.length > 0) {
|
|
4119
|
+
console.log(box([
|
|
4120
|
+
...result.releases.slice(0, 10).map(r => green(r.tag_name) + dim(` — ${r.name || ""}`)),
|
|
4121
|
+
], { title: `📦 Releases (${result.releases.length})`, color: green, padding: 2 }))
|
|
4122
|
+
} else {
|
|
4123
|
+
console.log(dim(" No releases found\n"))
|
|
4124
|
+
}
|
|
4125
|
+
console.log()
|
|
4126
|
+
return
|
|
4127
|
+
}
|
|
4128
|
+
|
|
4129
|
+
case "/git-release": {
|
|
4130
|
+
if (!arg) { console.log(dim("\n Usage: /git-release <tag> [name]\n")); return }
|
|
4131
|
+
const [tag, ...nameParts] = arg.split(" ")
|
|
4132
|
+
const name = nameParts.join(" ") || tag
|
|
4133
|
+
const api = new GitAPI()
|
|
4134
|
+
if (!api.isConfigured() || !api.hasToken()) { console.log(red(" ✗ Not configured\n")); return }
|
|
4135
|
+
startSpinner("Creating release")
|
|
4136
|
+
const result = await api.createRelease(tag, name, `Release ${tag} created by Stella Coder`)
|
|
4137
|
+
stopSpinner()
|
|
4138
|
+
if (result.success) {
|
|
4139
|
+
console.log(green(` ✓ Release created: ${result.url}\n`))
|
|
4140
|
+
} else {
|
|
4141
|
+
console.log(red(` ✗ Failed\n`))
|
|
4142
|
+
}
|
|
4143
|
+
return
|
|
4144
|
+
}
|
|
4145
|
+
|
|
4146
|
+
// ═══════════════════════════════════════════════════
|
|
4147
|
+
// WEB PARSER
|
|
4148
|
+
// ═══════════════════════════════════════════════════
|
|
4149
|
+
case "/fetch": {
|
|
4150
|
+
if (!arg) { console.log(dim("\n Usage: /fetch <url>\n")); return }
|
|
4151
|
+
console.log()
|
|
4152
|
+
const parser = new WebParser()
|
|
4153
|
+
startSpinner("Fetching page")
|
|
4154
|
+
const result = await parser.fetchAndParse(arg.trim())
|
|
4155
|
+
stopSpinner()
|
|
4156
|
+
if (result.success) {
|
|
4157
|
+
console.log(box([
|
|
4158
|
+
green(`✓ ${result.page.title || "No title"}`),
|
|
4159
|
+
dim(`URL: ${result.page.url}`),
|
|
4160
|
+
dim(`Length: ${result.page.text?.length || 0} chars`),
|
|
4161
|
+
"",
|
|
4162
|
+
result.page.text?.slice(0, 500) || "No text content",
|
|
4163
|
+
result.page.text?.length > 500 ? dim("\n... (truncated)") : "",
|
|
4164
|
+
], { title: "🌐 Fetched Page", color: cyan, padding: 2 }))
|
|
4165
|
+
} else {
|
|
4166
|
+
console.log(red(` ✗ ${result.error}\n`))
|
|
4167
|
+
}
|
|
4168
|
+
console.log()
|
|
4169
|
+
return
|
|
4170
|
+
}
|
|
4171
|
+
|
|
4172
|
+
case "/fetch-text": {
|
|
4173
|
+
if (!arg) { console.log(dim("\n Usage: /fetch-text <url>\n")); return }
|
|
4174
|
+
const parser = new WebParser()
|
|
4175
|
+
const result = await parser.fetchAndParse(arg.trim())
|
|
4176
|
+
if (result.success) {
|
|
4177
|
+
console.log(result.page.text || "No text")
|
|
4178
|
+
} else {
|
|
4179
|
+
console.log(red(` ✗ ${result.error}`))
|
|
4180
|
+
}
|
|
4181
|
+
console.log()
|
|
4182
|
+
return
|
|
4183
|
+
}
|
|
4184
|
+
|
|
4185
|
+
case "/fetch-links": {
|
|
4186
|
+
if (!arg) { console.log(dim("\n Usage: /fetch-links <url>\n")); return }
|
|
4187
|
+
const parser = new WebParser()
|
|
4188
|
+
const result = await parser.fetchAndParse(arg.trim())
|
|
4189
|
+
if (result.success && result.page.links?.length > 0) {
|
|
4190
|
+
console.log(box(result.page.links.slice(0, 30).map(l => cyan(l.text.slice(0, 50)) + dim(` → ${l.href}`)), { title: `🔗 Links (${result.page.links.length})`, color: cyan, padding: 2 }))
|
|
4191
|
+
} else {
|
|
4192
|
+
console.log(dim(" No links found\n"))
|
|
4193
|
+
}
|
|
4194
|
+
console.log()
|
|
4195
|
+
return
|
|
4196
|
+
}
|
|
4197
|
+
|
|
4198
|
+
case "/fetch-images": {
|
|
4199
|
+
if (!arg) { console.log(dim("\n Usage: /fetch-images <url>\n")); return }
|
|
4200
|
+
const parser = new WebParser()
|
|
4201
|
+
const result = await parser.fetchAndParse(arg.trim())
|
|
4202
|
+
if (result.success && result.page.images?.length > 0) {
|
|
4203
|
+
console.log(box(result.page.images.slice(0, 20).map(i => {
|
|
4204
|
+
const src = i.src?.length > 80 ? i.src.slice(0, 80) + "..." : i.src
|
|
4205
|
+
return violet(src) + dim(` alt="${i.alt || ""}"`)
|
|
4206
|
+
}), { title: `🖼️ Images (${result.page.images.length})`, color: violet, padding: 2 }))
|
|
4207
|
+
} else {
|
|
4208
|
+
console.log(dim(" No images found\n"))
|
|
4209
|
+
}
|
|
4210
|
+
console.log()
|
|
4211
|
+
return
|
|
4212
|
+
}
|
|
4213
|
+
|
|
4214
|
+
case "/fetch-forms": {
|
|
4215
|
+
if (!arg) { console.log(dim("\n Usage: /fetch-forms <url>\n")); return }
|
|
4216
|
+
const parser = new WebParser()
|
|
4217
|
+
const result = await parser.fetchAndParse(arg.trim())
|
|
4218
|
+
if (result.success && result.page.forms?.length > 0) {
|
|
4219
|
+
console.log(box(result.page.forms.map((f, i) => [
|
|
4220
|
+
violet(`Form ${i + 1}: ${f.method} ${f.action}`),
|
|
4221
|
+
...f.fields.map(field => dim(` ${field.name || field.type}: ${field.placeholder || ""}`)),
|
|
4222
|
+
].join("\n")), { title: `📝 Forms (${result.page.forms.length})`, color: violet, padding: 2 }))
|
|
4223
|
+
} else {
|
|
4224
|
+
console.log(dim(" No forms found\n"))
|
|
4225
|
+
}
|
|
4226
|
+
console.log()
|
|
4227
|
+
return
|
|
4228
|
+
}
|
|
4229
|
+
|
|
4230
|
+
case "/fetch-seo": {
|
|
4231
|
+
if (!arg) { console.log(dim("\n Usage: /fetch-seo <url>\n")); return }
|
|
4232
|
+
console.log()
|
|
4233
|
+
const parser = new WebParser()
|
|
4234
|
+
const result = await parser.fetchAndParse(arg.trim())
|
|
4235
|
+
if (!result.success) { console.log(red(` ✗ ${result.error}\n`)); return }
|
|
4236
|
+
const seo = parser.analyzeSEO(result.page)
|
|
4237
|
+
console.log(box([
|
|
4238
|
+
`Score: ${seo.score}/100`,
|
|
4239
|
+
"",
|
|
4240
|
+
...seo.checks.map(c => `${c.pass ? green("✓") : red("✗")} ${c.label}: ${dim(c.detail)}`),
|
|
4241
|
+
"",
|
|
4242
|
+
...seo.recommendations.map(r => dim(`• ${r}`)),
|
|
4243
|
+
], { title: `🔍 SEO Analysis`, color: seo.score >= 70 ? green : yellow, padding: 2 }))
|
|
4244
|
+
console.log()
|
|
4245
|
+
return
|
|
4246
|
+
}
|
|
4247
|
+
|
|
4248
|
+
case "/web-search": {
|
|
4249
|
+
if (!arg) { console.log(dim("\n Usage: /web-search <query>\n")); return }
|
|
4250
|
+
console.log()
|
|
4251
|
+
const parser = new WebParser()
|
|
4252
|
+
startSpinner("Searching")
|
|
4253
|
+
const results = await parser.search(arg.trim())
|
|
4254
|
+
stopSpinner()
|
|
4255
|
+
if (results.length > 0) {
|
|
4256
|
+
console.log(box(results.slice(0, 8).map((r, i) => [
|
|
4257
|
+
violet(`${i + 1}. `) + white(r.title),
|
|
4258
|
+
dim(` ${r.url}`),
|
|
4259
|
+
dim(` ${r.snippet?.slice(0, 100) || ""}`),
|
|
4260
|
+
].join("\n")), { title: `🔍 Search Results (${results.length})`, color: violet, padding: 2 }))
|
|
4261
|
+
} else {
|
|
4262
|
+
console.log(dim(" No results found\n"))
|
|
4263
|
+
}
|
|
4264
|
+
console.log()
|
|
4265
|
+
return
|
|
4266
|
+
}
|
|
4267
|
+
|
|
4268
|
+
case "/web-batch": {
|
|
4269
|
+
if (!arg) { console.log(dim("\n Usage: /web-batch <url1> <url2> ...\n")); return }
|
|
4270
|
+
const urls = arg.split(/\s+/).filter(Boolean)
|
|
4271
|
+
console.log()
|
|
4272
|
+
const parser = new WebParser()
|
|
4273
|
+
startSpinner(`Fetching ${urls.length} pages`)
|
|
4274
|
+
const results = await parser.batchFetch(urls)
|
|
4275
|
+
stopSpinner()
|
|
4276
|
+
const ok = results.filter(r => r.success)
|
|
4277
|
+
const fail = results.filter(r => !r.success)
|
|
4278
|
+
console.log(box([
|
|
4279
|
+
green(`✓ Fetched: ${ok.length}`),
|
|
4280
|
+
fail.length > 0 ? red(`✗ Failed: ${fail.length}`) : "",
|
|
4281
|
+
"",
|
|
4282
|
+
...ok.map(r => dim(`• ${r.page?.title || r.page?.url}`)),
|
|
4283
|
+
].filter(Boolean), { title: "📦 Batch Fetch", color: cyan, padding: 2 }))
|
|
4284
|
+
console.log()
|
|
4285
|
+
return
|
|
4286
|
+
}
|
|
4287
|
+
|
|
3602
4288
|
default:
|
|
3603
4289
|
console.log(dim(`\n Неизвестная команда: ${cmd}. Смотри /help\n`))
|
|
3604
4290
|
}
|