stella-coder 5.2.0 → 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 +441 -0
- package/stella-cli/web-parser.mjs +229 -0
- package/capture.ps1 +0 -14
- package/test_screen.png +0 -0
package/stella-cli/index.mjs
CHANGED
|
@@ -23,6 +23,9 @@ 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
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"
|
|
26
29
|
import {
|
|
27
30
|
buildRepoMap, buildProjectContext, compressContext,
|
|
28
31
|
loadSpec, generateSpecTemplate,
|
|
@@ -658,6 +661,37 @@ const COMMANDS = [
|
|
|
658
661
|
["/monitor-alerts", "показать обнаруженные проблемы"],
|
|
659
662
|
["/monitor-screenshot", "сделать скриншот и проанализировать"],
|
|
660
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
|
+
|
|
661
695
|
// Управление компьютером
|
|
662
696
|
["/open", "открыть приложение/файл/URL"],
|
|
663
697
|
["/app", "запустить приложение"],
|
|
@@ -784,6 +818,9 @@ async function handleCommand(line) {
|
|
|
784
818
|
["🔀 Git Экосистема", ["/git-eco", "/git-pr", "/git-merge-auto"]],
|
|
785
819
|
["🤖 Autonomous Agent", ["/auto", "/auto-stop", "/auto-status", "/auto-dashboard", "/auto-history"]],
|
|
786
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"]],
|
|
787
824
|
["⚙️ Настройки", ["/help", "/model", "/clear", "/compact", "/cost", "/context", "/config", "/version", "/login", "/newkey", "/doctor", "/sessions", "/color", "/lang", "/shortcut"]],
|
|
788
825
|
]
|
|
789
826
|
for (const [cat, cmds] of categories) {
|
|
@@ -3844,6 +3881,410 @@ If everything looks normal, set severity to "none" and issues to [].`
|
|
|
3844
3881
|
return
|
|
3845
3882
|
}
|
|
3846
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
|
+
|
|
3847
4288
|
default:
|
|
3848
4289
|
console.log(dim(`\n Неизвестная команда: ${cmd}. Смотри /help\n`))
|
|
3849
4290
|
}
|