beast-agent 1.7.0 → 1.8.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/bin/beast-agent.js +12 -0
- package/package.json +1 -1
- package/src/agent/engine.js +7 -7
- package/src/agent/llm.js +46 -34
- package/src/agent/scripts/stealthsearch.py +30 -0
- package/src/agent/searxng.js +325 -0
- package/src/agent/seeds/brainstorming/SKILL.md +90 -0
- package/src/agent/seeds/dispatching-parallel-agents/SKILL.md +120 -0
- package/src/agent/seeds/executing-plans/SKILL.md +60 -0
- package/src/agent/seeds/subagent-driven-development/SKILL.md +167 -0
- package/src/agent/seeds/systematic-debugging/SKILL.md +131 -0
- package/src/agent/seeds/test-driven-development/SKILL.md +152 -0
- package/src/agent/seeds/verification-before-completion/SKILL.md +63 -0
- package/src/agent/seeds/writing-plans/SKILL.md +162 -0
- package/src/agent/seeds/writing-skills/SKILL.md +229 -0
- package/src/agent/skills.js +39 -9
- package/src/agent/tools.js +2340 -2273
- package/src/main.js +7048 -7106
- package/src/preload.js +188 -192
- package/src/renderer/i18n.js +1186 -1226
- package/src/renderer/index.html +0 -6
- package/src/renderer/renderer.js +7389 -7480
- package/src/renderer/style.css +3383 -3430
- package/src/agent/obscura.js +0 -292
package/bin/beast-agent.js
CHANGED
|
@@ -87,6 +87,18 @@ if (process.argv[2] === 'update') {
|
|
|
87
87
|
process.exit(0);
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
/* SearXNG: yerel arama motoru — kur + arka planda başlat (127.0.0.1:8888)
|
|
91
|
+
beast searxng → gerekirse kur, başlat
|
|
92
|
+
beast searxng status → durum
|
|
93
|
+
beast searxng stop → kulla */
|
|
94
|
+
if (process.argv[2] === 'searxng') {
|
|
95
|
+
require('../src/agent/searxng').cli(process.argv.slice(3)).catch((e) => {
|
|
96
|
+
console.error('\u2717 ' + String((e && e.message) || e));
|
|
97
|
+
process.exit(1);
|
|
98
|
+
});
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
90
102
|
/* detached başlatıcı + kendini onaran electron:
|
|
91
103
|
taze makinalarda npm postinstall sırasında electron binary indirmesi
|
|
92
104
|
sessizce başarısız olmuş olabilir → burada otomatik tamir edilir. */
|
package/package.json
CHANGED
package/src/agent/engine.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
'use strict';
|
|
1
|
+
'use strict';
|
|
2
2
|
|
|
3
3
|
/* Beast engine: sessions, frugal context, streaming tool loop.
|
|
4
4
|
v2: token-bazlı bağlam bütçesi (kullanım kalibrasyonlu), paralel tool
|
|
@@ -1519,7 +1519,7 @@ class Engine {
|
|
|
1519
1519
|
'Tarayıcı eylemlerinin yanıtındaki recent günlüğü ve navigated bilgisini takip et; eylem yanıtları zaten taze snapshot içerir — refler tutarsız görünürse yeni snapshot al.',
|
|
1520
1520
|
'CONUŞMA ODAĞI SENDE KALSIN: kullanıcı seninle konuşurken iş çıkmışsa — uzun da olsa UFACIK da (tek komutluk dizin listesi, tek dosya okuma, tek arama…) — run_background ile PARALEL ajana devret; ana sohbet hiçbir işi beklemez; bittiğinde özet otomatik düşer.',
|
|
1521
1521
|
'Python işleri için python_run kullan: küçük betikler inline code ile; tekrarlayan işler %APPDATA%\\beast\\scripts klasöründeki dosyalarla (ör. news.py = RSS haber toplayıcı: args ["--limit","8","--json"]). Python kurulu olmasa bile ilk çağrıda taşınabilir gömülü runtime otomatik iner.',
|
|
1522
|
-
'PYTHON DURUMU: makinede sistem Python\'u görünmese bile ŞAŞIRMA ve "python yok" DEME — python_run aracı kendi taşınabilir runtime\'ını (%APPDATA%\\beast\\py\\python.exe) otomatik indirir/kullanır ve bu klasör run_command PATH\'inde önceliklidir; yani run_command içinde de `python` çalışır. Ham Google/Bing scrape yerine önce web_search aracını kullan (
|
|
1522
|
+
'PYTHON DURUMU: makinede sistem Python\'u görünmese bile ŞAŞIRMA ve "python yok" DEME — python_run aracı kendi taşınabilir runtime\'ını (%APPDATA%\\beast\\py\\python.exe) otomatik indirir/kullanır ve bu klasör run_command PATH\'inde önceliklidir; yani run_command içinde de `python` çalışır. Ham Google/Bing scrape yerine önce web_search aracını kullan (SearXNG + stealth TLS + TinyFish + Python çoklu-motor destekli), script gerektiğinde python_run yaz.',
|
|
1523
1523
|
'PDF ÇIKTI KURALI: PDF üretirken pip\u2019ten pdf paketi (fpdf, fpdf2, markdown-pdf, weasyprint, reportlab vb.) KURMA/KULLANMA — bunlar Türkçe karakterleri bozar. Doğru kit Node tarafında ZATEN kurulu: `pdf-lib` + `@pdf-lib/fontkit` (Türkçe font gömme) ve `pdfkit`. python_run ile DEĞİL; write_file ile .js script yazıp run_command ile `node script.js` çalıştır. md→pdf çevirici YOKTUR ve kurulmaz: kullanıcıya rapor/özet/belge çıktısı vereceksen .md dosyası gönderme — aynı içeriği DOĞRUDAN pdf-lib/pdfkit ile PDF olarak üret ve send_file ile o PDF\u2019i gönder. Ayrıntılı örnekler: pdf skill\u2019i (SKILL.md).',
|
|
1524
1524
|
'Eski bir hafıza kaydına ihtiyacın olursa memory_search ile ara; kalıcı bilgi/birikim için kb_search kullan, yeni bilgi öğrenirsen kb_add ile kaynak belirt.',
|
|
1525
1525
|
'Bir oturumda 3+ kez memory_write yaptıysan iş bitince memory_hygiene çağır (duplike/eskime temizliği).',
|
|
@@ -3685,10 +3685,10 @@ class Engine {
|
|
|
3685
3685
|
|
|
3686
3686
|
/* web_search zinciri — web_search VE deep_search aynı zinciri kullanır.
|
|
3687
3687
|
Sıra + aç/kapa Ayarlar → Web Arama'dan değiştirilir (tools.searchChainWeb):
|
|
3688
|
-
varsayılan: 1)
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
atlanır — sıradaki motor hemen devreye girer. */
|
|
3688
|
+
varsayılan: 1) SearXNG (yerel, ayaktaysa otomatik öne alınır) 2) stealth
|
|
3689
|
+
(curl_cffi Chrome TLS taklidi → DDG html) 3) dahili tarayıcı (DİREK
|
|
3690
|
+
GOOGLE) 4) TinyFish (anahtar girildiyse) 5) python çoklu-motor. Tarayıcı
|
|
3691
|
+
CAPTCHA/trafik verirse 10 dk atlanır — sıradaki motor hemen devreye girer. */
|
|
3692
3692
|
_webSearchChain(q, n, sessionId, signal) {
|
|
3693
3693
|
const browserUsable = this.browser && typeof this.browser.search === 'function';
|
|
3694
3694
|
return tools.searchChainWeb(q, n, {
|
|
@@ -4086,7 +4086,7 @@ class Engine {
|
|
|
4086
4086
|
if (name === 'deep_search') {
|
|
4087
4087
|
/* agentic derin araştırma: çoklu sorgu + gizli tarayıcıda sayfa okuma.
|
|
4088
4088
|
ARAMA zinciri web_search ile BİREBİR AYNI — sıralı zincir
|
|
4089
|
-
(
|
|
4089
|
+
(SearXNG → stealth → tarayıcı → TinyFish → python; Ayarlar'dan değiştirilir).. */
|
|
4090
4090
|
try {
|
|
4091
4091
|
emitSafe(this, sessionId, { type: 'status', status: 'derin araştırma: çoklu sorgu + gizli sayfa okuma' });
|
|
4092
4092
|
} catch {}
|
package/src/agent/llm.js
CHANGED
|
@@ -249,6 +249,44 @@ async function streamOnce(sel, body, { signal, onDelta, onRetry } = {}, omitReas
|
|
|
249
249
|
signal.addEventListener('abort', onAbort, { once: true });
|
|
250
250
|
}
|
|
251
251
|
try {
|
|
252
|
+
/* tek satır SSE olayını işle — hem okuma döngüsü hem SON FLUSH kullanır */
|
|
253
|
+
const handleLine = (line) => {
|
|
254
|
+
line = line.trim();
|
|
255
|
+
if (!line.startsWith('data:')) return;
|
|
256
|
+
const data = line.slice(5).trim();
|
|
257
|
+
if (!data || data === '[DONE]') return;
|
|
258
|
+
|
|
259
|
+
let json;
|
|
260
|
+
try {
|
|
261
|
+
json = JSON.parse(data);
|
|
262
|
+
} catch {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (json.usage) state.usage = json.usage;
|
|
267
|
+
const ch = json.choices && json.choices[0];
|
|
268
|
+
if (!ch) return;
|
|
269
|
+
if (ch.finish_reason) state.finishReason = ch.finish_reason;
|
|
270
|
+
const d = ch.delta || {};
|
|
271
|
+
if (d.content) {
|
|
272
|
+
state.content += d.content;
|
|
273
|
+
onDelta && onDelta(d.content, state.content);
|
|
274
|
+
}
|
|
275
|
+
if (d.reasoning_content) state.reasoning += d.reasoning_content;
|
|
276
|
+
if (d.reasoning) state.reasoning += d.reasoning;
|
|
277
|
+
for (const tc of d.tool_calls || []) {
|
|
278
|
+
const i = typeof tc.index === 'number' ? tc.index : state.toolCalls.length;
|
|
279
|
+
while (state.toolCalls.length <= i) {
|
|
280
|
+
state.toolCalls.push({ id: '', type: 'function', function: { name: '', arguments: '' } });
|
|
281
|
+
}
|
|
282
|
+
if (tc.id) state.toolCalls[i].id = tc.id;
|
|
283
|
+
if (tc.function) {
|
|
284
|
+
if (tc.function.name) state.toolCalls[i].function.name += tc.function.name;
|
|
285
|
+
if (tc.function.arguments) state.toolCalls[i].function.arguments += tc.function.arguments;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
|
|
252
290
|
while (true) {
|
|
253
291
|
const { done, value } = await reader.read();
|
|
254
292
|
if (signal && signal.aborted) {
|
|
@@ -261,44 +299,18 @@ async function streamOnce(sel, body, { signal, onDelta, onRetry } = {}, omitReas
|
|
|
261
299
|
|
|
262
300
|
let idx;
|
|
263
301
|
while ((idx = buf.indexOf('\n')) !== -1) {
|
|
264
|
-
const line = buf.slice(0, idx)
|
|
302
|
+
const line = buf.slice(0, idx);
|
|
265
303
|
buf = buf.slice(idx + 1);
|
|
266
|
-
|
|
267
|
-
const data = line.slice(5).trim();
|
|
268
|
-
if (!data || data === '[DONE]') continue;
|
|
269
|
-
|
|
270
|
-
let json;
|
|
271
|
-
try {
|
|
272
|
-
json = JSON.parse(data);
|
|
273
|
-
} catch {
|
|
274
|
-
continue;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
if (json.usage) state.usage = json.usage;
|
|
278
|
-
const ch = json.choices && json.choices[0];
|
|
279
|
-
if (!ch) continue;
|
|
280
|
-
if (ch.finish_reason) state.finishReason = ch.finish_reason;
|
|
281
|
-
const d = ch.delta || {};
|
|
282
|
-
if (d.content) {
|
|
283
|
-
state.content += d.content;
|
|
284
|
-
onDelta && onDelta(d.content, state.content);
|
|
285
|
-
}
|
|
286
|
-
if (d.reasoning_content) state.reasoning += d.reasoning_content;
|
|
287
|
-
if (d.reasoning) state.reasoning += d.reasoning;
|
|
288
|
-
for (const tc of d.tool_calls || []) {
|
|
289
|
-
const i = typeof tc.index === 'number' ? tc.index : state.toolCalls.length;
|
|
290
|
-
while (state.toolCalls.length <= i) {
|
|
291
|
-
state.toolCalls.push({ id: '', type: 'function', function: { name: '', arguments: '' } });
|
|
292
|
-
}
|
|
293
|
-
if (tc.id) state.toolCalls[i].id = tc.id;
|
|
294
|
-
if (tc.function) {
|
|
295
|
-
if (tc.function.name) state.toolCalls[i].function.name += tc.function.name;
|
|
296
|
-
if (tc.function.arguments) state.toolCalls[i].function.arguments += tc.function.arguments;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
304
|
+
handleLine(line);
|
|
299
305
|
}
|
|
300
306
|
}
|
|
301
307
|
|
|
308
|
+
/* SON FLUSH — KRİTİK: akış son parçası '\n' ile bitmeyebilir.
|
|
309
|
+
Okuma bitince kalan buf'u Decoder'dan boşaltıp işlemezsek
|
|
310
|
+
cevabın SON kelimesi/cümlesi kaybolur (SSE sonu öyle gelir). */
|
|
311
|
+
buf += decoder.decode();
|
|
312
|
+
if (buf.trim()) handleLine(buf);
|
|
313
|
+
|
|
302
314
|
return { ...state };
|
|
303
315
|
} finally {
|
|
304
316
|
if (signal && onAbort) {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Beast stealth search - Obscura yaklasimi: Chrome TLS parmak izi taklidi.
|
|
3
|
+
# curl_cffi (curl-impersonate) ile html.duckduckgo.com'a gercek Chrome kimligiyle girer.
|
|
4
|
+
# stdout: ham DDG HTML - ayristirma JS tarafinda (tools.parseDdgResults) yapilir.
|
|
5
|
+
# Kullanim: stealthsearch.py "<url-encoded-sorgu>"
|
|
6
|
+
import sys
|
|
7
|
+
import urllib.parse
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main():
|
|
11
|
+
if len(sys.argv) < 2:
|
|
12
|
+
return
|
|
13
|
+
q = urllib.parse.quote_plus(sys.argv[1])
|
|
14
|
+
try:
|
|
15
|
+
from curl_cffi import requests
|
|
16
|
+
except ImportError:
|
|
17
|
+
return # kurulu degil - JS tarafi sessizce atlar
|
|
18
|
+
try:
|
|
19
|
+
r = requests.get(
|
|
20
|
+
"https://html.duckduckgo.com/html/?q=" + q,
|
|
21
|
+
impersonate="chrome",
|
|
22
|
+
headers={"Accept-Language": "tr-TR,tr;q=0.9,en;q=0.8"},
|
|
23
|
+
timeout=15,
|
|
24
|
+
)
|
|
25
|
+
sys.stdout.write(r.text or "")
|
|
26
|
+
except Exception:
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
main()
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* SearXNG — yerel metasearch entegrasyonu (github.com/searxng/searxng)
|
|
4
|
+
SearXNG resmi olarak Linux/Docker hedefler; Windows'ta `import pwd`
|
|
5
|
+
yüzünden patlar. Bu modül Beast'ın GÖMÜLÜ Python'ına gerçek searxng'yi
|
|
6
|
+
(GitHub master) kurar + pwd shim'i yazar + 127.0.0.1:8888'de arka planda
|
|
7
|
+
çalıştırır. Arama zinciri 'searxng' motorunu yalnız AYAKTAYSA kullanır.
|
|
8
|
+
|
|
9
|
+
CLI: beast searxng → gerekirse kur + başlat
|
|
10
|
+
beast searxng status → durum
|
|
11
|
+
beast searxng stop → kulla */
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
const { spawn, execFile, execSync } = require('child_process');
|
|
17
|
+
|
|
18
|
+
const REQS_URL = 'https://raw.githubusercontent.com/searxng/searxng/master/requirements.txt';
|
|
19
|
+
const PKG_URL = 'https://github.com/searxng/searxng/archive/refs/heads/master.zip';
|
|
20
|
+
const GETPIP_URL = 'https://bootstrap.pypa.io/get-pip.py';
|
|
21
|
+
const DEFAULT_URL = 'http://127.0.0.1:8888';
|
|
22
|
+
|
|
23
|
+
/* ---------- yollar ---------- */
|
|
24
|
+
|
|
25
|
+
function beastAppDir() {
|
|
26
|
+
if (process.env.BEAST_DATA) return process.env.BEAST_DATA;
|
|
27
|
+
return process.env.APPDATA
|
|
28
|
+
? path.join(process.env.APPDATA, 'beast')
|
|
29
|
+
: path.join(os.homedir(), 'AppData', 'Roaming', 'beast');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function dir() {
|
|
33
|
+
const d = path.join(beastAppDir(), 'searxng');
|
|
34
|
+
try { fs.mkdirSync(d, { recursive: true }); } catch {}
|
|
35
|
+
return d;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function settingsPath() { return path.join(dir(), 'settings.yml'); }
|
|
39
|
+
function runPyPath() { return path.join(dir(), 'run.py'); }
|
|
40
|
+
function pidPath() { return path.join(dir(), 'server.pid'); }
|
|
41
|
+
function pyExe() { return path.join(beastAppDir(), 'py', 'python.exe'); }
|
|
42
|
+
|
|
43
|
+
/* ---------- durum yoklama (cache'li) ---------- */
|
|
44
|
+
|
|
45
|
+
let _probe = { at: 0, up: false, url: '' };
|
|
46
|
+
const PROBE_UP_TTL = 5 * 60 * 1000;
|
|
47
|
+
const PROBE_DOWN_TTL = 2 * 60 * 1000;
|
|
48
|
+
|
|
49
|
+
function resetProbeCache() { _probe = { at: 0, up: false, url: '' }; }
|
|
50
|
+
|
|
51
|
+
async function isUp(baseUrl = DEFAULT_URL, { force = false } = {}) {
|
|
52
|
+
const base = String(baseUrl || DEFAULT_URL).replace(/\/+$/, '');
|
|
53
|
+
const ttl = _probe.up ? PROBE_UP_TTL : PROBE_DOWN_TTL;
|
|
54
|
+
if (!force && _probe.url === base && Date.now() - _probe.at < ttl) return _probe.up;
|
|
55
|
+
let up = false;
|
|
56
|
+
try {
|
|
57
|
+
const r = await fetch(`${base}/search?q=beast&format=json`, { signal: AbortSignal.timeout(1800) });
|
|
58
|
+
if (r.ok) {
|
|
59
|
+
const j = await r.json();
|
|
60
|
+
up = j && Array.isArray(j.results);
|
|
61
|
+
}
|
|
62
|
+
} catch {}
|
|
63
|
+
_probe = { at: Date.now(), up, url: base };
|
|
64
|
+
return up;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* ---------- arama (zincir motoru) ---------- */
|
|
68
|
+
|
|
69
|
+
async function search(query, { maxResults = 8, signal, baseUrl } = {}) {
|
|
70
|
+
const base = String(baseUrl || process.env.BEAST_SEARXNG_URL || DEFAULT_URL).replace(/\/+$/, '');
|
|
71
|
+
if (!(await isUp(base))) return null; // ayakta değil → zincir sıradaki motora geçer
|
|
72
|
+
try {
|
|
73
|
+
const sig =
|
|
74
|
+
signal && typeof AbortSignal !== 'undefined' && AbortSignal.any
|
|
75
|
+
? AbortSignal.any([signal, AbortSignal.timeout(15000)])
|
|
76
|
+
: AbortSignal.timeout(15000);
|
|
77
|
+
const r = await fetch(
|
|
78
|
+
`${base}/search?q=${encodeURIComponent(String(query || ''))}&format=json&safesearch=0`,
|
|
79
|
+
{ signal: sig }
|
|
80
|
+
);
|
|
81
|
+
if (!r.ok) return null;
|
|
82
|
+
const j = await r.json();
|
|
83
|
+
const results = (j.results || [])
|
|
84
|
+
.map((x) => ({
|
|
85
|
+
title: String(x.title || ''),
|
|
86
|
+
url: String(x.url || ''),
|
|
87
|
+
snippet: String(x.content || '').replace(/\s+/g, ' ').slice(0, 400),
|
|
88
|
+
engine: 'searxng',
|
|
89
|
+
}))
|
|
90
|
+
.filter((x) => x.url && x.title);
|
|
91
|
+
if (!results.length) return null;
|
|
92
|
+
return {
|
|
93
|
+
ok: true,
|
|
94
|
+
engine: 'searxng',
|
|
95
|
+
query,
|
|
96
|
+
results: results.slice(0, Math.max(1, Math.min(12, Number(maxResults) || 8))),
|
|
97
|
+
};
|
|
98
|
+
} catch {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/* ---------- kurulum ---------- */
|
|
104
|
+
|
|
105
|
+
function sh(exe, args, timeoutMs = 600000) {
|
|
106
|
+
return new Promise((resolve, reject) => {
|
|
107
|
+
execFile(exe, args, { timeout: timeoutMs, windowsHide: true, maxBuffer: 8 * 1024 * 1024 }, (err, stdout, stderr) => {
|
|
108
|
+
if (err) {
|
|
109
|
+
const tail = String((stderr || stdout || err.message) || '').slice(-400);
|
|
110
|
+
return reject(new Error(tail || String(err)));
|
|
111
|
+
}
|
|
112
|
+
resolve(String(stdout || ''));
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function downloadTo(url, dest) {
|
|
118
|
+
const r = await fetch(url, { signal: AbortSignal.timeout(120000) });
|
|
119
|
+
if (!r.ok) throw new Error(`indirme başarısız: HTTP ${r.status}`);
|
|
120
|
+
fs.writeFileSync(dest, Buffer.from(await r.arrayBuffer()));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function sitePackagesDir(py) {
|
|
124
|
+
return sh(py, ['-c', 'import sysconfig;print(sysconfig.get_paths()["purelib"])']).then((s) => s.trim());
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* gömülü python'un ._pth dosyasında `import site` açık değilse pip/paketler görünmez */
|
|
128
|
+
function fixEmbeddedPth(py) {
|
|
129
|
+
try {
|
|
130
|
+
const dirPy = path.dirname(py);
|
|
131
|
+
for (const f of fs.readdirSync(dirPy)) {
|
|
132
|
+
if (/^python\d+\._pth$/.test(f)) {
|
|
133
|
+
const p = path.join(dirPy, f);
|
|
134
|
+
const cur = fs.readFileSync(p, 'utf8');
|
|
135
|
+
if (!/^import site$/m.test(cur)) {
|
|
136
|
+
fs.writeFileSync(p, cur.replace(/^#\s*import site/m, 'import site'), 'utf8');
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
} catch {}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function ensurePip(py, onLog) {
|
|
144
|
+
try {
|
|
145
|
+
await sh(py, ['-m', 'pip', '--version'], 60000);
|
|
146
|
+
return;
|
|
147
|
+
} catch {}
|
|
148
|
+
onLog('pip kuruluyor…');
|
|
149
|
+
const gp = path.join(os.tmpdir(), 'beast-get-pip.py');
|
|
150
|
+
await downloadTo(GETPIP_URL, gp);
|
|
151
|
+
await sh(py, [gp, '--no-warn-script-location'], 300000);
|
|
152
|
+
fixEmbeddedPth(py);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Tam kurulum — `beast searxng` komutunun yaptığı iş. */
|
|
156
|
+
async function install({ onLog } = {}) {
|
|
157
|
+
const log = (m) => { try { (onLog || ((s) => console.log(' • ' + s)))(m); } catch {} };
|
|
158
|
+
const tools = require('./tools');
|
|
159
|
+
const probe = await tools.ensurePython();
|
|
160
|
+
const py = probe.exe;
|
|
161
|
+
log('python: ' + py);
|
|
162
|
+
|
|
163
|
+
await ensurePip(py, log);
|
|
164
|
+
log('setuptools/wheel/tzdata kuruluyor…');
|
|
165
|
+
await sh(py, ['-m', 'pip', 'install', '--no-warn-script-location', 'setuptools', 'wheel', 'tzdata'], 600000);
|
|
166
|
+
|
|
167
|
+
log('searxng bağımlılıkları indiriliyor (~40 MB)…');
|
|
168
|
+
const reqs = path.join(os.tmpdir(), 'beast-searxng-requirements.txt');
|
|
169
|
+
await downloadTo(REQS_URL, reqs);
|
|
170
|
+
await sh(py, ['-m', 'pip', 'install', '--no-warn-script-location', '-r', reqs], 900000);
|
|
171
|
+
|
|
172
|
+
log('searxng motoru kuruluyor…');
|
|
173
|
+
await sh(py, ['-m', 'pip', 'install', '--no-warn-script-location', '--no-build-isolation', PKG_URL], 900000);
|
|
174
|
+
|
|
175
|
+
/* pwd shim — searxng/valkeydb.py unix socket sahipliği için pwd import eder */
|
|
176
|
+
const sp = (await sitePackagesDir(py)) || path.join(path.dirname(py), 'Lib', 'site-packages');
|
|
177
|
+
const shim = [
|
|
178
|
+
'"""Windows shim: SearXNG valkeydb.py yalnizca unix-socket sahipligi icin pwd',
|
|
179
|
+
'kullanir; Windows\'ta valkey devre disi oldugundan donuk yanit yeterlidir."""',
|
|
180
|
+
'import os',
|
|
181
|
+
'',
|
|
182
|
+
'class _Pw:',
|
|
183
|
+
' pw_name = "beast"',
|
|
184
|
+
' pw_uid = 0',
|
|
185
|
+
' pw_gid = 0',
|
|
186
|
+
' pw_dir = os.path.expanduser("~")',
|
|
187
|
+
' pw_shell = ""',
|
|
188
|
+
'',
|
|
189
|
+
'def getpwuid(uid):',
|
|
190
|
+
' return _Pw()',
|
|
191
|
+
'',
|
|
192
|
+
'def getpwnam(name):',
|
|
193
|
+
' return _Pw()',
|
|
194
|
+
'',
|
|
195
|
+
'def getgrgid(gid):',
|
|
196
|
+
' class _Gr:',
|
|
197
|
+
' gr_name = "beast"',
|
|
198
|
+
' gr_gid = 0',
|
|
199
|
+
' return _Gr()',
|
|
200
|
+
'',
|
|
201
|
+
'def getgrnam(name):',
|
|
202
|
+
' return getgrgid(0)',
|
|
203
|
+
].join('\n');
|
|
204
|
+
fs.writeFileSync(path.join(sp, 'pwd.py'), shim, 'utf8');
|
|
205
|
+
|
|
206
|
+
/* ayarlar + çalıştırıcı */
|
|
207
|
+
const secret = 'beast-' + Math.random().toString(36).slice(2, 14);
|
|
208
|
+
fs.writeFileSync(
|
|
209
|
+
settingsPath(),
|
|
210
|
+
[
|
|
211
|
+
'use_default_settings: true',
|
|
212
|
+
'server:',
|
|
213
|
+
` secret_key: "${secret}"`,
|
|
214
|
+
' port: 8888',
|
|
215
|
+
' bind_address: "127.0.0.1"',
|
|
216
|
+
' limiter: false',
|
|
217
|
+
' image_proxy: false',
|
|
218
|
+
'search:',
|
|
219
|
+
' formats:',
|
|
220
|
+
' - html',
|
|
221
|
+
' - json',
|
|
222
|
+
'',
|
|
223
|
+
].join('\n'),
|
|
224
|
+
'utf8'
|
|
225
|
+
);
|
|
226
|
+
fs.writeFileSync(runPyPath(), 'from searx.webapp import run; run()', 'utf8');
|
|
227
|
+
resetProbeCache();
|
|
228
|
+
log('kurulum tamam');
|
|
229
|
+
return { ok: true };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/* ---------- başlat / durdur / durum ---------- */
|
|
233
|
+
|
|
234
|
+
function readPid() {
|
|
235
|
+
try {
|
|
236
|
+
const s = fs.readFileSync(pidPath(), 'utf8').trim();
|
|
237
|
+
return /^\d+$/.test(s) ? Number(s) : null;
|
|
238
|
+
} catch {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async function start({ installIfMissing = true, onLog } = {}) {
|
|
244
|
+
if (await isUp(DEFAULT_URL, { force: true })) return { ok: true, already: true, url: DEFAULT_URL };
|
|
245
|
+
if (!fs.existsSync(settingsPath()) || !fs.existsSync(runPyPath())) {
|
|
246
|
+
if (!installIfMissing) return { ok: false, error: 'kurulu değil — önce: beast searxng' };
|
|
247
|
+
await install({ onLog });
|
|
248
|
+
}
|
|
249
|
+
const tools = require('./tools');
|
|
250
|
+
const probe = await tools.ensurePython();
|
|
251
|
+
const child = spawn(probe.exe, [runPyPath()], {
|
|
252
|
+
detached: true,
|
|
253
|
+
stdio: 'ignore',
|
|
254
|
+
windowsHide: true,
|
|
255
|
+
env: { ...process.env, SEARXNG_SETTINGS_PATH: settingsPath(), PYTHONIOENCODING: 'utf-8' },
|
|
256
|
+
});
|
|
257
|
+
try { fs.writeFileSync(pidPath(), String(child.pid)); } catch {}
|
|
258
|
+
child.unref();
|
|
259
|
+
/* ayağa kalkmasını bekle (ilk açılış motor listesini derler — birkaç sn) */
|
|
260
|
+
for (let i = 0; i < 20; i++) {
|
|
261
|
+
await new Promise((r) => setTimeout(r, 1000));
|
|
262
|
+
if (await isUp(DEFAULT_URL, { force: true })) return { ok: true, url: DEFAULT_URL, pid: child.pid };
|
|
263
|
+
}
|
|
264
|
+
return { ok: false, error: 'searxng başlatılamadı (20 sn içinde yanıt yok)' };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function stop() {
|
|
268
|
+
const pid = readPid();
|
|
269
|
+
if (!pid) return { ok: true, note: 'çalışmıyor' };
|
|
270
|
+
try {
|
|
271
|
+
execSync(`taskkill /PID ${pid} /T /F`, { stdio: 'ignore' });
|
|
272
|
+
} catch {}
|
|
273
|
+
try { fs.unlinkSync(pidPath()); } catch {}
|
|
274
|
+
resetProbeCache();
|
|
275
|
+
return { ok: true, stopped: pid };
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async function status() {
|
|
279
|
+
return {
|
|
280
|
+
up: await isUp(DEFAULT_URL, { force: true }),
|
|
281
|
+
url: DEFAULT_URL,
|
|
282
|
+
pid: readPid(),
|
|
283
|
+
installed: fs.existsSync(settingsPath()) && fs.existsSync(runPyPath()),
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/* ---------- CLI (bin/beast-agent.js → beast searxng) ---------- */
|
|
288
|
+
|
|
289
|
+
async function cli(argv) {
|
|
290
|
+
const sub = String(argv[0] || 'start').toLowerCase();
|
|
291
|
+
console.log('\u27F3 SearXNG (yerel arama motoru)');
|
|
292
|
+
if (sub === 'stop') {
|
|
293
|
+
const r = stop();
|
|
294
|
+
console.log(r.ok ? '\u2713 durduruldu' : '\u2717 durdurulamadı');
|
|
295
|
+
process.exit(r.ok ? 0 : 1);
|
|
296
|
+
}
|
|
297
|
+
if (sub === 'status') {
|
|
298
|
+
const s = await status();
|
|
299
|
+
console.log(` kurulu : ${s.installed ? 'evet' : 'hayır'}`);
|
|
300
|
+
console.log(` çalışıyor: ${s.up ? 'evet (' + s.url + ')' : 'hayır'}`);
|
|
301
|
+
process.exit(0);
|
|
302
|
+
}
|
|
303
|
+
/* start */
|
|
304
|
+
const r = await start({ installIfMissing: true, onLog: (m) => console.log(' \u2022 ' + m) });
|
|
305
|
+
if (r.ok) {
|
|
306
|
+
console.log(r.already ? '\u2713 SearXNG zaten çalışıyor — ' + r.url : '\u2713 SearXNG başlatıldı — ' + r.url);
|
|
307
|
+
console.log('\u2139 arama zincirinde kullanılmaya hazır (Ayarlar → Web Arama sırası)');
|
|
308
|
+
process.exit(0);
|
|
309
|
+
}
|
|
310
|
+
console.error('\u2717 ' + (r.error || 'başlatılamadı'));
|
|
311
|
+
process.exit(1);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
module.exports = {
|
|
315
|
+
search,
|
|
316
|
+
isUp,
|
|
317
|
+
resetProbeCache,
|
|
318
|
+
install,
|
|
319
|
+
start,
|
|
320
|
+
stop,
|
|
321
|
+
status,
|
|
322
|
+
cli,
|
|
323
|
+
settingsPath,
|
|
324
|
+
DEFAULT_URL,
|
|
325
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: brainstorming
|
|
3
|
+
description: Yaratıcı işe KOD YAZMADAN ÖNCE kullan — yeni özellik, yeni proje, bileşen, davranış değişikliği istendiğinde; niyeti, gereksinimleri ve tasarımı kullanıcıyla netleştirmeden implementasyona geçmek yasaktır.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Brainstorming: Fikirden Tasarıma
|
|
7
|
+
|
|
8
|
+
Fikirleri doğal diyalogla tam oluşmuş tasarımlara dönüştür.
|
|
9
|
+
|
|
10
|
+
Akış: bağlamı anla → fikri netleştir → tasarımı sun → kullanıcı onayı.
|
|
11
|
+
|
|
12
|
+
<ZOR KAPI>
|
|
13
|
+
Kullanıcıya ne yapacağını söyleyip onayı almadan HİÇBİR implementasyon
|
|
14
|
+
eylemi yok: kod yazma, proje scaffold'ı, dosya üretme. Bu kapı HER görev
|
|
15
|
+
için geçerlidir; törenin boyutu göreve göre ölçeklenir ama ONAY KAPISI
|
|
16
|
+
ölçeklenmez.
|
|
17
|
+
</ZOR KAPI>
|
|
18
|
+
|
|
19
|
+
## Üç Yol
|
|
20
|
+
|
|
21
|
+
İlk sorudan önce isteği SINIFLANDIR ve sınıfı sesli söyle — "bu sınırlı
|
|
22
|
+
kapsamlı görünüyor, spec dosyası yazmak yerine kısa tasarımı burada sunacağım"
|
|
23
|
+
— kullanıcı override edebilsin:
|
|
24
|
+
|
|
25
|
+
- **Spike** — fizibilite sorusu ("yapılabilir mi", "hızlı-çirkin olsun").
|
|
26
|
+
Çıktısı tuttuğun kod değil, bir CEVAPTIR. Soru ve deneme planını 2-3
|
|
27
|
+
cümleyle sun, onay al, en ucuz yolla araştır. Tasarım dosyası yok.
|
|
28
|
+
Bulgu → öneri olarak raporla; bir şey inşa ettiysen "atılabilir" etiketiyle.
|
|
29
|
+
- **Sınırlı (Bounded)** — mevcut koda iyi tanımlı değişiklik: yeni flag,
|
|
30
|
+
küçük endpoint, tek dosyalık fix. "Sınırlı" için DEĞİŞTİRECEĞİN AKIŞIN
|
|
31
|
+
Bu projede zaten var olması şart. Önemli netleştirme sorularını sor,
|
|
32
|
+
KISA tasarımı SOHBETTE sun (birkaç cümle - paragraf) ve DUR. Kullanıcı
|
|
33
|
+
"evet" demeden implementasyon yok. Spec dosyası yok, plan dosyası yok.
|
|
34
|
+
- **Mimari** — yeni projeler, yeni alt sistemler, bileşen ilişkilerini
|
|
35
|
+
değiştiren işler. Tam süreç: sorular → 2-3 yaklaşım → bölümlü tasarım →
|
|
36
|
+
yazılı spec → writing-plans skill'i.
|
|
37
|
+
|
|
38
|
+
İki yol arasında tereddüt varsa AĞIRINI seç. Ortasında gizli karmaşıklık
|
|
39
|
+
çıkarsa yol YÜKSELTİLİR (dur, söyle, büyüt); asla aşağı inmez.
|
|
40
|
+
|
|
41
|
+
## Anti-Desen: "Onaya Gerek Yok Kadar Basit"
|
|
42
|
+
|
|
43
|
+
Her yol onayla biter. Todo listesi, tek fonksiyon, config değişikliği —
|
|
44
|
+
tasarım sohbette iki cümle olabilir ama SUN ve ONAY AL. "Basit" işler,
|
|
45
|
+
incelenmemiş varsayımların en çok iş boşa çıkardığı yerdir. Basitlikle
|
|
46
|
+
ölçeklenen şey artefaktır; onay asla değil.
|
|
47
|
+
|
|
48
|
+
## Kırmızı Bayraklar
|
|
49
|
+
|
|
50
|
+
| Düşünce | Gerçek |
|
|
51
|
+
|---|---|
|
|
52
|
+
| "Bu, tasarım gerektirmeyecek kadar basit" | Basitlik kısa tasarım demektir, tasarım yok demek değil. |
|
|
53
|
+
| "Sınırlı der spec'i atlarım" | Etiket arayışı şüphenin ta kendisi — ağır yolu seç. |
|
|
54
|
+
| "Tasarım bariz, onu okurken başlarım" | Kapı onaydır, tasarımın uzunluğu değil. Sun ve evet bekle. |
|
|
55
|
+
| "Bu tür app'leri biliyorum, sınırlıdır" | Sınırlı olma projeyi ölçer, bilgini değil. Akış yoksa mimaridir. |
|
|
56
|
+
| "Spike çalıştı, kodu tutayım" | Spike'ın çıktısı cevaptır. Kodu tutmak YENİ istektir — sınıflandır. |
|
|
57
|
+
| "Büyüdü ama az kaldı, yeniden sınıflandırmaya gerek yok" | Gizli karmaşıklık yolu ortada yükseltir. Dur ve söyle. |
|
|
58
|
+
|
|
59
|
+
## Kontrol Listesi
|
|
60
|
+
|
|
61
|
+
**Spike:** bağlamı gör → soru+plan sun (2-3 cümle) → onay → en ucuz yolla araştır → öneri raporla.
|
|
62
|
+
|
|
63
|
+
**Sınırlı:** bağlamı gör (dosyalar, dokümanlar, son commit'ler) → netleştirme
|
|
64
|
+
soruları TEK TEK → kısa tasarımı sohbette sun (yaklaşım, dokunulan dosyalar,
|
|
65
|
+
test stratejisi) → açık "evet" bekle (sunup hemen başlamak = kapıyı atlamak) →
|
|
66
|
+
normal iş akışıyla uygula (TDD geçerli).
|
|
67
|
+
|
|
68
|
+
**Mimari:**
|
|
69
|
+
1. Bağlamı gör: mevcut yapıyı list_dir/grep/read_file ile incele, deseni takip et
|
|
70
|
+
2. Netleştirme soruları — TEK SORU PER MESAJ; amaç/kısıt/başarı kriterini anla;
|
|
71
|
+
çoklu seçmeli soru tercih et
|
|
72
|
+
3. Kapsam çok buysa parçala: bağımsız alt-projelere böl, sırayı belirle,
|
|
73
|
+
ilk alt-projeyle normal akışa gir
|
|
74
|
+
4. 2-3 yaklaşım öner — artı/eksileriyle; önerini BAŞA koy ve nedenini söyle;
|
|
75
|
+
her yaklaşımda YAGNI: gereksiz özelliği çıkar
|
|
76
|
+
5. Tasarımı bölümler halinde sun — mimari, bileşenler, veri akışı, hata
|
|
77
|
+
yönetimi, test stratejisi; her bölümün ardından "şimdiye kadar doğru mu?" de
|
|
78
|
+
6. Onaylı tasarımı yaz: `<workspace>/docs/plans/specs/YYYY-MM-DD-<konu>-design.md`
|
|
79
|
+
7. Spec öz-denetim: "TBD/TODO" var mı, iç tutarlılık, kapsam tek plana sığıyor mu,
|
|
80
|
+
iki şekilde yorumlanabilir gereksinim var mı → düzelt
|
|
81
|
+
8. Kullanıcıya spec dosyasını göster, incelemesini iste
|
|
82
|
+
9. Onay gelince writing-plans skill'iyle implementasyon planına geç — başka hiçbir
|
|
83
|
+
implementasyon adımı bu kapıdan önce gelmez
|
|
84
|
+
|
|
85
|
+
## Mevcut Kodda Çalışırken
|
|
86
|
+
|
|
87
|
+
- Değişiklik önermeden önce mevcut yapıyı incele; var olan deseni takip et
|
|
88
|
+
- İşini etkileyen sorunlu noktalar varsa (şişmiş dosya, bulanık sınır) hedefli
|
|
89
|
+
iyileştirmeyi tasarıma dahil et
|
|
90
|
+
- Alakasız refactoring önerme — mevcut hedefe hizmet eden kal
|