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.
@@ -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
  }