beast-agent 0.24.4 → 0.25.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/package.json +1 -1
- package/src/agent/engine.js +34 -59
- package/src/agent/tools.js +1 -1
- package/src/main.js +211 -45
- package/src/preload.js +1 -1
- package/src/renderer/i18n.js +2 -4
- package/src/renderer/index.html +45 -9
- package/src/renderer/renderer.js +346 -138
- package/src/renderer/style.css +195 -29
package/package.json
CHANGED
package/src/agent/engine.js
CHANGED
|
@@ -333,41 +333,6 @@ class Engine {
|
|
|
333
333
|
else this.sessionTools.delete(id);
|
|
334
334
|
}
|
|
335
335
|
|
|
336
|
-
/* ANA SOHBETE DAVETLİ BOTLAR: oturuma guest bot ekle/çıkar (persist) */
|
|
337
|
-
setSessionGuests(sessionId, guests) {
|
|
338
|
-
const s = this._load(String(sessionId || ''));
|
|
339
|
-
if (!s) return false;
|
|
340
|
-
const items = (Array.isArray(guests) ? guests : [])
|
|
341
|
-
.slice(0, 4)
|
|
342
|
-
.map((g) => ({ id: String((g && g.id) || ''), code: String((g && g.code) || ''), name: String((g && g.name) || '').slice(0, 40) }))
|
|
343
|
-
.filter((g) => g.id && g.code);
|
|
344
|
-
s.guests = items;
|
|
345
|
-
try {
|
|
346
|
-
const file = this._file(s.id);
|
|
347
|
-
const lines = fs.readFileSync(file, 'utf8').split('\n').filter((l) => l.trim());
|
|
348
|
-
const kept = lines.filter((l) => {
|
|
349
|
-
try { return JSON.parse(l).t !== 'guests'; } catch { return true; }
|
|
350
|
-
});
|
|
351
|
-
const out = kept.join('\n') + (items.length ? '\n' + JSON.stringify({ t: 'guests', items }) : '') + '\n';
|
|
352
|
-
const tmp = file + '.tmp';
|
|
353
|
-
fs.writeFileSync(tmp, out);
|
|
354
|
-
fs.renameSync(tmp, file);
|
|
355
|
-
} catch {}
|
|
356
|
-
return true;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
/* Davetli botlar system prompt bloğu — ajan bot_dm ile onlara danışır */
|
|
360
|
-
_guestsBlock(session) {
|
|
361
|
-
const g = Array.isArray(session && session.guests) ? session.guests : [];
|
|
362
|
-
if (!g.length) return '';
|
|
363
|
-
const lines = g.map((x) => `- ${x.name} (kod: ${x.code})`).join('\n');
|
|
364
|
-
return (
|
|
365
|
-
`# DAVETLİ BOTLAR\n` +
|
|
366
|
-
`Bu sohbete şu botlar davet edildi. Kullanıcı onlara hitap ederse veya uzmanlıkları gereken bir konu olursa ` +
|
|
367
|
-
`bot_dm aracıyla (to=5 haneli kod) onlara yaz; aldığın cevabı [BotAdı] etiketiyle kullanıcıya aktar:\n${lines}`
|
|
368
|
-
);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
336
|
/* Oturumun bağlı olduğu MÜŞTERİ botu (admin/seasız → null → global hafıza) */
|
|
372
337
|
_sessionBotCtx(session) {
|
|
373
338
|
if (!session || !session.botId || typeof this.resolveBot !== 'function') return null;
|
|
@@ -947,9 +912,6 @@ class Engine {
|
|
|
947
912
|
session.isBotDm = true;
|
|
948
913
|
session.dmA = String(rec.a || '');
|
|
949
914
|
session.dmB = String(rec.b || '');
|
|
950
|
-
} else if (rec.t === 'guests') {
|
|
951
|
-
/* ana sohbete davetli botlar */
|
|
952
|
-
session.guests = Array.isArray(rec.items) ? rec.items : [];
|
|
953
915
|
} else if (rec.t === 'msg') {
|
|
954
916
|
delete rec.t;
|
|
955
917
|
session.messages.push(rec);
|
|
@@ -1086,7 +1048,6 @@ class Engine {
|
|
|
1086
1048
|
count: s.messages.length,
|
|
1087
1049
|
isBg,
|
|
1088
1050
|
isBotDm: !!s.isBotDm,
|
|
1089
|
-
guests: Array.isArray(s.guests) ? s.guests : [],
|
|
1090
1051
|
bgStatus: isBg ? (job ? job.status : null) : null,
|
|
1091
1052
|
};
|
|
1092
1053
|
}
|
|
@@ -1330,11 +1291,11 @@ class Engine {
|
|
|
1330
1291
|
: 'Bağımsız alt-işleri delegate_task ile devret; kendi başına halledebileceğin işleri devretme.',
|
|
1331
1292
|
'Güncel/dış bilgi gerekiyorsa web_search kullan (zincir DAHİLİ tarayıcıyla başlar — gerçek Chromium ile Google); hızlı ham metin okuması için http_fetch kullan.',
|
|
1332
1293
|
'ARAMA-DİSİPLİN: bir bilgiyi 2-3 denemede bulamazsan TAKILMA — farklı bir açıya/sorguya geç, yine yoksa bulabildiğin kısmi bilgiyle cevap ver ve neyi bulamadığını açıkça söyle. Kapalı/gizli içerik (private profil, login arkası veri) peşinde KOŞMA — bulunamayacağı belliyse hemen vazgeç.',
|
|
1333
|
-
'TARAYICI VARSAYILANI (ZORUNLU): kullanıcı "şu siteyi aç", "bunu ara / google\u2019da ara", "şu sayfaya git" gibi bir web isteği verdiğinde HEP DAHİLİ TARAYICIYI KULLAN (browser_open ile aç →
|
|
1294
|
+
'TARAYICI VARSAYILANI (ZORUNLU): kullanıcı "şu siteyi aç", "bunu ara / google\u2019da ara", "şu sayfaya git" gibi bir web isteği verdiğinde HEP DAHİLİ TARAYICIYI KULLAN (browser_open ile aç → açılış yanıtında hazır snapshot gelir → ref numaralarıyla browser_click/browser_type/browser_select ile hareket et; HER eylem yanıtında taze snapshot döner — ayrıca browser_snapshot çağırma, yanıttaki refleri kullan; browser_read ile metin oku; sadece görsel yerleşim/grafik gerekiyorsa browser_screenshot çek). Tarih/saat alanlarını (type=date/time) browser_type ile DÜZ METİN yaz ("15.03.2026", "2026-03-15") — takvimden tıklamaya çalışma, alan programatik ayarlanır. Panel ekranın sağında açılır ve kullanıcı da sayfayı anında görür; JS/login/SPA/dinamik içerik için idealdir. Bu kural TÜM oturumları kapsar — masaüstü sohbeti ve çok kullanıcılı botlar (WhatsApp vb.) dahil.' +
|
|
1334
1295
|
' DIŞ TARAYICI İSTİSNASI: kullanıcı AÇIKÇA "chrome\u2019da aç", "firefox\u2019ta aç", "başka tarayıcıda aç", "normal tarayıcıda aç", "kendi tarayıcımda aç" derse O ZAMAN run_command ile `start "" <url>` çalıştır — sistem varsayılan (dış) tarayıcısında açılır. Kullanıcı dış tarayıcı istemedikçe ASLA dış tarayıcı açma.' +
|
|
1335
1296
|
' Görselleri GÖREMEMİYORSAN (metin-model) görüntüdeki metni okumak için ocr_read kullan: source:"browser" ile tarayıcı sayfasını, "screen" ile masaüstünü OCR\u2019la okursun (captcha/canvas/görsel metin dahil).' +
|
|
1336
1297
|
(this.ceoMode ? ' (CEO: bu araçları KENDİN ÇAĞIRMA — içinde tarayıcı geçen işi run_background ile paralel ajana devret.)' : ''),
|
|
1337
|
-
'Tarayıcı eylemlerinin yanıtındaki recent günlüğü ve navigated bilgisini takip et;
|
|
1298
|
+
'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.',
|
|
1338
1299
|
'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.',
|
|
1339
1300
|
'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.',
|
|
1340
1301
|
'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 (zaten Python çoklu-motor + Exa destekli), script gerektiğinde python_run yaz.',
|
|
@@ -2220,8 +2181,6 @@ class Engine {
|
|
|
2220
2181
|
/* bot kimliği: bota bağlı oturumlarda kişilik + izolasyon kuralları */
|
|
2221
2182
|
const botBlock = this._botSystemBlock(session);
|
|
2222
2183
|
if (botBlock) system += '\n\n' + botBlock;
|
|
2223
|
-
const guestBlock = this._guestsBlock(session);
|
|
2224
|
-
if (guestBlock) system += '\n\n' + guestBlock;
|
|
2225
2184
|
if (session.notes) {
|
|
2226
2185
|
system +=
|
|
2227
2186
|
`\n\n# OTURUM NOTLARI (oturum ${session.code || '?'} — bu oturumun önceki konuşma özeti; birebir geçmişi buradan hatırla)\n` +
|
|
@@ -3112,23 +3071,38 @@ class Engine {
|
|
|
3112
3071
|
return JSON.stringify(r);
|
|
3113
3072
|
}
|
|
3114
3073
|
if (name === 'web_search') {
|
|
3115
|
-
/* SIRA:
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3074
|
+
/* SIRA: 1) dahili tarayıcı (DİREK GOOGLE — ücretsiz + AI cevabı)
|
|
3075
|
+
2) TinyFish (tarayıcı sorun çıkarsa HEMEN devreye girer — anahtar girildiyse)
|
|
3076
|
+
3) python çoklu-motor (ddgs / DDG+Bing+Mojeek)
|
|
3077
|
+
4) Exa (anahtar girildiyse, son çare)
|
|
3078
|
+
Tarayıcıda CAPTCHA/"olağandışı trafik"/boş sonuç algılanırsa 10 dk boyunca
|
|
3079
|
+
tarayıcı ATLANIR — her arama TinyFish'ten başlar, süre dolunca tarayıcı geri döner. */
|
|
3080
|
+
const q = String((args && args.query) || '');
|
|
3081
|
+
const n = Number((args && args.max_results) || 8);
|
|
3119
3082
|
let r = null;
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3083
|
+
let banned = false;
|
|
3084
|
+
const browserUsable = this.browser && typeof this.browser.search === 'function';
|
|
3085
|
+
if (Date.now() < (this._webSearchBrowserBanUntil || 0)) banned = true;
|
|
3086
|
+
if (browserUsable && !banned) {
|
|
3087
|
+
try { r = await this.browser.search(q, signal); } catch {}
|
|
3088
|
+
if (!r || !r.ok || !(r.results || []).length) {
|
|
3089
|
+
/* tarayıcı sorunlu — 10 dk atla, alternatif zincir hemen devrede */
|
|
3090
|
+
this._webSearchBrowserBanUntil = Date.now() + 10 * 60 * 1000;
|
|
3091
|
+
r = null;
|
|
3092
|
+
banned = true;
|
|
3124
3093
|
}
|
|
3125
3094
|
}
|
|
3095
|
+
if (!r || !r.ok || !(r.results || []).length) {
|
|
3096
|
+
try { r = await tools.tinyfishSearch(q, n, signal); } catch {}
|
|
3097
|
+
}
|
|
3126
3098
|
if (!r || !r.ok || !(r.results || []).length) {
|
|
3127
3099
|
try {
|
|
3128
3100
|
r = JSON.parse(await tools.exec('web_search', args, { cwd: this._sessionWorkspace(sessionId), signal }));
|
|
3129
3101
|
} catch {}
|
|
3130
3102
|
}
|
|
3131
|
-
|
|
3103
|
+
const out = r || { ok: false, error: 'web arama başarısız — tüm motorlar boş döndü' };
|
|
3104
|
+
if (out && out.ok && banned) out.note = 'tarayıcı araması 10 dk askıda (CAPTCHA/trafik) — TinyFish/alternatif zincir kullanıldı';
|
|
3105
|
+
return JSON.stringify(out);
|
|
3132
3106
|
}
|
|
3133
3107
|
if (name === 'ocr_read') {
|
|
3134
3108
|
return JSON.stringify(await this._ocrRead(args || {}, signal));
|
|
@@ -3676,7 +3650,7 @@ const TOOLS = [
|
|
|
3676
3650
|
function: {
|
|
3677
3651
|
name: 'browser_open',
|
|
3678
3652
|
description:
|
|
3679
|
-
'Open a URL in the built-in visible browser panel (JS-rendered pages work). This is the DEFAULT for every "open this site / search this / go to page" request — the user watches the page live in the side panel; ideal for login, SPA and dynamic content.
|
|
3653
|
+
'Open a URL in the built-in visible browser panel (JS-rendered pages work). This is the DEFAULT for every "open this site / search this / go to page" request — the user watches the page live in the side panel; ideal for login, SPA and dynamic content. The response ALREADY includes a fresh snapshot with numbered refs — act directly with browser_click/browser_type/browser_select; no separate browser_snapshot needed. Open the OS default (external) browser ONLY when the user explicitly asks for chrome/firefox/another/normal/my-own browser — in that case use run_command with `start "" <url>` instead of this tool.',
|
|
3680
3654
|
parameters: {
|
|
3681
3655
|
type: 'object',
|
|
3682
3656
|
properties: {
|
|
@@ -3709,7 +3683,7 @@ const TOOLS = [
|
|
|
3709
3683
|
function: {
|
|
3710
3684
|
name: 'browser_snapshot',
|
|
3711
3685
|
description:
|
|
3712
|
-
'List all interactive elements of the built-in browser page as numbered refs: [3] <button> "Gönder".
|
|
3686
|
+
'List all interactive elements of the built-in browser page as numbered refs: [3] <button> "Gönder". NOTE: every browser action response already includes a fresh snapshot — call this separately only when refs seem stale or you need a re-scan (e.g. after an action whose response had no snapshot). Open popups/calendars/datepickers are listed FIRST. Much more reliable than CSS selectors.',
|
|
3713
3687
|
parameters: { type: 'object', properties: {}, required: [] },
|
|
3714
3688
|
},
|
|
3715
3689
|
},
|
|
@@ -3733,7 +3707,7 @@ const TOOLS = [
|
|
|
3733
3707
|
function: {
|
|
3734
3708
|
name: 'browser_click',
|
|
3735
3709
|
description:
|
|
3736
|
-
'Click an element in the built-in browser. Prefer ref from
|
|
3710
|
+
'Click an element in the built-in browser. Prefer ref from the latest snapshot (e.g. {"ref":3}); CSS selector or text=X also accepted. The response includes a FRESH snapshot with new refs — continue with those directly instead of calling browser_snapshot again.',
|
|
3737
3711
|
parameters: {
|
|
3738
3712
|
type: 'object',
|
|
3739
3713
|
properties: {
|
|
@@ -3748,7 +3722,7 @@ const TOOLS = [
|
|
|
3748
3722
|
function: {
|
|
3749
3723
|
name: 'browser_type',
|
|
3750
3724
|
description:
|
|
3751
|
-
'Type text into an input/textarea/contenteditable in the built-in browser (prefer ref). Set submit=true to press Enter afterwards.',
|
|
3725
|
+
'Type text into an input/textarea/contenteditable in the built-in browser (prefer ref). Set submit=true to press Enter afterwards. Date/time fields (input type=date/time/month/datetime-local) are set PROGRAMMATICALLY — just send the date as text in any common format ("2026-03-15", "15.03.2026", "15 Mart 2026"); do NOT click the calendar popup. The response includes a FRESH snapshot with new refs.',
|
|
3752
3726
|
parameters: {
|
|
3753
3727
|
type: 'object',
|
|
3754
3728
|
properties: {
|
|
@@ -3766,7 +3740,7 @@ const TOOLS = [
|
|
|
3766
3740
|
function: {
|
|
3767
3741
|
name: 'browser_press',
|
|
3768
3742
|
description:
|
|
3769
|
-
'Press a key (Enter, Tab, Escape, ArrowDown…) on the focused element in the built-in browser; optionally focus a ref first.',
|
|
3743
|
+
'Press a key (Enter, Tab, Escape, ArrowDown…) on the focused element in the built-in browser; optionally focus a ref first. The response includes a FRESH snapshot with new refs.',
|
|
3770
3744
|
parameters: {
|
|
3771
3745
|
type: 'object',
|
|
3772
3746
|
properties: {
|
|
@@ -3781,7 +3755,8 @@ const TOOLS = [
|
|
|
3781
3755
|
type: 'function',
|
|
3782
3756
|
function: {
|
|
3783
3757
|
name: 'browser_scroll',
|
|
3784
|
-
description:
|
|
3758
|
+
description:
|
|
3759
|
+
'Scroll the built-in browser page. direction: up | down. The response includes a FRESH snapshot with newly visible refs.',
|
|
3785
3760
|
parameters: {
|
|
3786
3761
|
type: 'object',
|
|
3787
3762
|
properties: {
|
|
@@ -3797,7 +3772,7 @@ const TOOLS = [
|
|
|
3797
3772
|
function: {
|
|
3798
3773
|
name: 'browser_select',
|
|
3799
3774
|
description:
|
|
3800
|
-
'Pick an <option> of a dropdown (<select>) in the built-in browser by value/text (prefer ref).',
|
|
3775
|
+
'Pick an <option> of a dropdown (<select>) in the built-in browser by value/text (prefer ref). The response includes a FRESH snapshot with new refs. For CUSTOM (JS) dropdowns that are not <select>, click the trigger, then click the [role=option] ref from the snapshot (popups are listed first).',
|
|
3801
3776
|
parameters: {
|
|
3802
3777
|
type: 'object',
|
|
3803
3778
|
properties: {
|
package/src/agent/tools.js
CHANGED
|
@@ -620,7 +620,7 @@ const definitions = [
|
|
|
620
620
|
function: {
|
|
621
621
|
name: 'web_search',
|
|
622
622
|
description:
|
|
623
|
-
'FAST web search with an automatic chain: built-in browser first (real Chromium searching GOOGLE directly with AI Mode — no bot protection; the response may include an `ai` field holding Google\'s own AI answer, ideal for "who/what is X" questions), then Python multi-engine (ddgs / DuckDuckGo+Bing+Mojeek parallel), then Exa API as last resort if a key is configured (Ayarlar → Web Arama). Returns {ai?, results[{title,url,snippet}]}. Use when fresh or external info is needed; skip for things you already know.',
|
|
623
|
+
'FAST web search with an automatic chain: built-in browser first (real Chromium searching GOOGLE directly with AI Mode — no bot protection; the response may include an `ai` field holding Google\'s own AI answer, ideal for "who/what is X" questions), then TinyFish API (if a key is configured, used IMMEDIATELY whenever the browser hits CAPTCHA/unusual traffic — the browser is then skipped for 10 minutes), then Python multi-engine (ddgs / DuckDuckGo+Bing+Mojeek parallel), then Exa API as last resort if a key is configured (Ayarlar → Web Arama). Returns {ai?, results[{title,url,snippet}]}. Use when fresh or external info is needed; skip for things you already know.',
|
|
624
624
|
parameters: {
|
|
625
625
|
type: 'object',
|
|
626
626
|
properties: {
|
package/src/main.js
CHANGED
|
@@ -2285,11 +2285,24 @@ function ensureDesktopShortcut() {
|
|
|
2285
2285
|
try {
|
|
2286
2286
|
const desktop = app.getPath('desktop');
|
|
2287
2287
|
const lnk = path.join(desktop, 'Beast Agent.lnk');
|
|
2288
|
-
|
|
2288
|
+
/* çalışma klasörü kullanıcı home'u olsun: paket klasöründe başlarsa güncelleme
|
|
2289
|
+
cmd'si orada açılır ve npm install -g klasör kilidi (EBUSY) yemek zorunda kalır */
|
|
2290
|
+
const goodCwd = app.getPath('home');
|
|
2291
|
+
if (fs.existsSync(lnk)) {
|
|
2292
|
+
/* eski kısayollar paket klasörünü çalışma klasörü olarak taşıyordu — onar */
|
|
2293
|
+
try {
|
|
2294
|
+
const cur = shell.readShortcutLink(lnk);
|
|
2295
|
+
if (cur && cur.cwd && /node_modules[\\/]beast-agent/i.test(cur.cwd)) {
|
|
2296
|
+
shell.writeShortcutLink(lnk, 'update', { ...cur, cwd: goodCwd });
|
|
2297
|
+
log.info('main', 'Masaüstü kısayolu çalışma klasörü home\u2019a taşındı');
|
|
2298
|
+
}
|
|
2299
|
+
} catch {}
|
|
2300
|
+
return;
|
|
2301
|
+
}
|
|
2289
2302
|
const ok = shell.writeShortcutLink(lnk, 'create', {
|
|
2290
2303
|
target: process.execPath,
|
|
2291
2304
|
args: app.getAppPath(),
|
|
2292
|
-
cwd:
|
|
2305
|
+
cwd: goodCwd,
|
|
2293
2306
|
description: 'Beast Agent — hızlı, hafif ve becerikli',
|
|
2294
2307
|
icon: path.join(__dirname, '..', 'assets', 'app.ico'),
|
|
2295
2308
|
iconIndex: 0,
|
|
@@ -2542,7 +2555,31 @@ function setBrowserOpen(v, forceVisible) {
|
|
|
2542
2555
|
browserEmit({ open: true, width: browser.width, url });
|
|
2543
2556
|
}
|
|
2544
2557
|
|
|
2558
|
+
/* --- Paralel ajan trafik düzeni: ajanlar aynı saniyede sorgu atınca Google
|
|
2559
|
+
"olağandışı trafik" uyarısı veriyor. Gezinme/arama isteklerini tek kuyrukta
|
|
2560
|
+
sıraya alıp aralarına min 1.2 sn + rastgele 0-600 ms jitter koyuyoruz.
|
|
2561
|
+
Kuyruğu tutan işin İÇİNDEN yapılan gezinmeler kapıyı atlar (kilitlenme olmaz). --- */
|
|
2562
|
+
const BROWSER_TRAFFIC_GAP = 1200;
|
|
2563
|
+
let __trafficLast = 0;
|
|
2564
|
+
let __trafficTail = Promise.resolve();
|
|
2565
|
+
function browserTrafficWait() {
|
|
2566
|
+
const wait = Math.max(0, __trafficLast + BROWSER_TRAFFIC_GAP + Math.floor(Math.random() * 600) - Date.now());
|
|
2567
|
+
__trafficLast = Date.now() + wait;
|
|
2568
|
+
return wait > 0 ? new Promise((r) => setTimeout(r, wait)) : Promise.resolve();
|
|
2569
|
+
}
|
|
2570
|
+
function browserGate(job) {
|
|
2571
|
+
const p = __trafficTail.then(job, job);
|
|
2572
|
+
__trafficTail = p.catch(() => {});
|
|
2573
|
+
return p;
|
|
2574
|
+
}
|
|
2545
2575
|
async function browserNavigate(raw, signal) {
|
|
2576
|
+
return browserGate(async () => {
|
|
2577
|
+
await browserTrafficWait();
|
|
2578
|
+
return browserNavigateNow(raw, signal);
|
|
2579
|
+
});
|
|
2580
|
+
}
|
|
2581
|
+
|
|
2582
|
+
async function browserNavigateNow(raw, signal) {
|
|
2546
2583
|
let url = String(raw || '').trim();
|
|
2547
2584
|
if (!url) return { ok: false, error: 'boş adres' };
|
|
2548
2585
|
if (!/^https?:\/\//i.test(url)) {
|
|
@@ -2572,7 +2609,21 @@ async function browserNavigate(raw, signal) {
|
|
|
2572
2609
|
try { title = wc.getTitle(); finalUrl = wc.getURL() || url; } catch {}
|
|
2573
2610
|
browserEmit({ open: true, width: browser.width, url: finalUrl });
|
|
2574
2611
|
flushBrowserStorage(); // oturum çerezleri diske — ani kapanışta kaybolmasın
|
|
2575
|
-
|
|
2612
|
+
/* açılışta snapshot da göm — model ayrı snapshot çağırmadan ref'lere başlar */
|
|
2613
|
+
let snap = null;
|
|
2614
|
+
try {
|
|
2615
|
+
const sraw = await wc.executeJavaScript(BROWSER_SNAPSHOT_JS, true);
|
|
2616
|
+
const sobj = JSON.parse(sraw);
|
|
2617
|
+
if (sobj && typeof sobj.count === 'number') snap = sobj;
|
|
2618
|
+
} catch {}
|
|
2619
|
+
return {
|
|
2620
|
+
ok: true,
|
|
2621
|
+
url: finalUrl,
|
|
2622
|
+
title,
|
|
2623
|
+
...(snap
|
|
2624
|
+
? { snapshot: snap.snapshot, refCount: snap.count, note: 'sayfa acildi — güncel snapshot hazır (' + snap.count + ' ref); ref numarasıyla browser_click/browser_type ile devam et' }
|
|
2625
|
+
: { note: 'sayfa acildi — etkileşimli elemanlar için browser_snapshot al' }),
|
|
2626
|
+
};
|
|
2576
2627
|
}
|
|
2577
2628
|
|
|
2578
2629
|
async function browserRead(signal) {
|
|
@@ -2637,6 +2688,14 @@ async function ocrRead({ image, lang = 'tur+eng' } = {}) {
|
|
|
2637
2688
|
yapmak (kullanıcının console'da yaptığı gibi) — sayfa bile değişmeden
|
|
2638
2689
|
DOMParser ile sonuç çekilir. Fetch olmazsa direk gezinme fallback'i var. */
|
|
2639
2690
|
async function browserSearch(query, signal) {
|
|
2691
|
+
/* paralel ajan sorguları trafik kapısından sırayla geçer */
|
|
2692
|
+
return browserGate(async () => {
|
|
2693
|
+
await browserTrafficWait();
|
|
2694
|
+
return browserSearchNow(query, signal);
|
|
2695
|
+
});
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2698
|
+
async function browserSearchNow(query, signal) {
|
|
2640
2699
|
try {
|
|
2641
2700
|
if (!win || win.isDestroyed()) return null;
|
|
2642
2701
|
if (signal && signal.aborted) return null;
|
|
@@ -2650,7 +2709,7 @@ async function browserSearch(query, signal) {
|
|
|
2650
2709
|
let cur = '';
|
|
2651
2710
|
try { cur = wc.getURL() || ''; } catch {}
|
|
2652
2711
|
if (!cur.startsWith('https://www.google.com')) {
|
|
2653
|
-
await
|
|
2712
|
+
await browserNavigateNow('https://www.google.com/', signal);
|
|
2654
2713
|
}
|
|
2655
2714
|
if (!browser.view || !browser.open) return null;
|
|
2656
2715
|
|
|
@@ -2720,7 +2779,7 @@ async function browserSearch(query, signal) {
|
|
|
2720
2779
|
|
|
2721
2780
|
/* fallback: fetch yerine direk gezinme + DOM'dan çek */
|
|
2722
2781
|
if (!results.length) {
|
|
2723
|
-
await
|
|
2782
|
+
await browserNavigateNow(
|
|
2724
2783
|
'https://www.google.com/search?q=' + encodeURIComponent(q) + '&num=10&hl=tr&pws=0&aep=1',
|
|
2725
2784
|
signal
|
|
2726
2785
|
);
|
|
@@ -2776,7 +2835,14 @@ async function browserSearch(query, signal) {
|
|
|
2776
2835
|
results = parsed.rows || [];
|
|
2777
2836
|
}
|
|
2778
2837
|
|
|
2779
|
-
if (!results.length && !parsed.ai)
|
|
2838
|
+
if (!results.length && !parsed.ai) {
|
|
2839
|
+
/* CAPTCHA / olağandışı trafik sinyali — zincir TinyFish'e kaymalı */
|
|
2840
|
+
let blocked = false;
|
|
2841
|
+
try { blocked = /google\.com\/sorry/i.test(wc.getURL() || ''); } catch {}
|
|
2842
|
+
return blocked
|
|
2843
|
+
? { ok: false, blocked: true, engine: 'browser-google', query: q, error: 'unusual traffic (CAPTCHA)' }
|
|
2844
|
+
: null;
|
|
2845
|
+
}
|
|
2780
2846
|
flushBrowserStorage();
|
|
2781
2847
|
const out = { ok: true, engine: 'browser-google', query: q, results };
|
|
2782
2848
|
if (parsed.ai) out.ai = parsed.ai;
|
|
@@ -2840,26 +2906,37 @@ const BROWSER_JS_HELPERS = `
|
|
|
2840
2906
|
|
|
2841
2907
|
const BROWSER_SNAPSHOT_JS = `(function(){
|
|
2842
2908
|
${BROWSER_JS_HELPERS}
|
|
2843
|
-
const sel='a[href],button,input:not([type="hidden"]),textarea,select,[role="button"],[role="link"],[role="tab"],[contenteditable="true"],summary';
|
|
2844
|
-
|
|
2909
|
+
const sel='a[href],button,input:not([type="hidden"]),textarea,select,[role="button"],[role="link"],[role="tab"],[role="option"],[role="menuitem"],[role="gridcell"],[role="checkbox"],[role="switch"],[role="combobox"],[role="textbox"],[role="searchbox"],[contenteditable="true"],summary';
|
|
2910
|
+
/* açık popup/takvim/dialog varsa içindekiler ÖNCE listelenir (tarih seçici, özel dropdown vb.) */
|
|
2911
|
+
const popSel='[role="dialog"],dialog,[role="listbox"],[role="menu"],.flatpickr-calendar,.ui-datepicker,[class*="datepicker" i],[class*="calendar" i],[class*="dropdown" i],[class*="popup" i]';
|
|
2912
|
+
const pops=[...document.querySelectorAll(popSel)].filter(__vis);
|
|
2913
|
+
const inPop=new Set();
|
|
2914
|
+
for(const p of pops){[...p.querySelectorAll(sel)].forEach(e=>inPop.add(e));}
|
|
2845
2915
|
window.__beMap={};
|
|
2916
|
+
const seen=new Set();
|
|
2846
2917
|
const lines=[];
|
|
2847
2918
|
let i=0;
|
|
2848
|
-
|
|
2849
|
-
if(!
|
|
2850
|
-
|
|
2919
|
+
function add(e){
|
|
2920
|
+
if(!e||seen.has(e)) return;
|
|
2921
|
+
seen.add(e);
|
|
2922
|
+
if(!__vis(e)||e.disabled||e.getAttribute('aria-disabled')==='true') return;
|
|
2851
2923
|
i++;
|
|
2852
2924
|
window.__beMap[i]=e;
|
|
2853
2925
|
const tag=e.tagName.toLowerCase();
|
|
2854
|
-
const type=e.getAttribute('type');
|
|
2926
|
+
const type=(e.getAttribute&&e.getAttribute('type'))||'';
|
|
2855
2927
|
let s='['+i+'] <'+tag+(type?' type='+type:'')+'>';
|
|
2856
2928
|
const l=__label(e);
|
|
2857
2929
|
if(l) s+=' "'+l+'"';
|
|
2858
|
-
if(tag==='input'
|
|
2930
|
+
if(tag==='input'&&/^(text|email|password|search|tel|url|number|date|time|month|datetime-local)$/.test(type)&&e.value) s+=' deger="'+String(e.value).slice(0,30)+'"';
|
|
2859
2931
|
if(tag==='select'){s+=' secenekler=['+[...e.options].slice(0,6).map(o=>o.text.trim()).filter(Boolean).join('|').slice(0,60)+']';}
|
|
2860
2932
|
lines.push(s);
|
|
2861
|
-
if(i>=100) break;
|
|
2862
2933
|
}
|
|
2934
|
+
if(inPop.size){
|
|
2935
|
+
lines.push('--- ACIK POPUP/TAKVIM ICINDEKILER (once bunlari kullan) ---');
|
|
2936
|
+
for(const e of inPop){ add(e); if(i>=60) break; }
|
|
2937
|
+
lines.push('--- SAYFA ---');
|
|
2938
|
+
}
|
|
2939
|
+
for(const e of document.querySelectorAll(sel)){ if(i>=100) break; add(e); }
|
|
2863
2940
|
return JSON.stringify({count:i,title:document.title,url:location.href,snapshot:lines.join('\\n')});
|
|
2864
2941
|
})()`;
|
|
2865
2942
|
|
|
@@ -2892,10 +2969,52 @@ function browserActionJs(kind, args) {
|
|
|
2892
2969
|
if (kind === 'type') {
|
|
2893
2970
|
const text = JSON.stringify(String(args.text ?? ''));
|
|
2894
2971
|
const submit = args.submit ? 'true' : 'false';
|
|
2895
|
-
return `(function(){${BROWSER_JS_HELPERS}${resolveTarget}
|
|
2972
|
+
return `(function(){${BROWSER_JS_HELPERS}${resolveTarget}
|
|
2973
|
+
/*__DT_HELPERS_START__*/
|
|
2974
|
+
function __norm(s){return String(s).toLowerCase().split('').map(function(c){return {'ş':'s','ğ':'g','ü':'u','ı':'i','ö':'o','ç':'c','İ':'i'}[c]||c;}).join('');}
|
|
2975
|
+
function __parseDate(raw){
|
|
2976
|
+
raw=String(raw||'').trim(); if(!raw) return null;
|
|
2977
|
+
const MON={ocak:1,subat:2,mart:3,nisan:4,mayis:5,haziran:6,temmuz:7,agustos:8,eylul:9,ekim:10,kasim:11,aralik:12,jan:1,feb:2,mar:3,apr:4,may:5,jun:6,jul:7,aug:8,sep:9,oct:10,nov:11,dec:12,january:1,february:2,march:3,april:4,june:6,july:7,august:8,september:9,october:10,november:11,december:12};
|
|
2978
|
+
let m=raw.match(/^(\\d{4})[\\-.\\/,](\\d{1,2})[\\-.\\/,](\\d{1,2})$/);
|
|
2979
|
+
if(m) return [+m[1],+m[2],+m[3]];
|
|
2980
|
+
m=raw.match(/^(\\d{1,2})[\\-.\\/ ](\\d{1,2})[\\-.\\/ ](\\d{2,4})$/);
|
|
2981
|
+
if(m){let d=+m[1],mo=+m[2],y=+m[3];if(y<100)y+=2000;if(mo>12&&d<=12){const t=d;d=mo;mo=t;}return [y,mo,d];}
|
|
2982
|
+
m=raw.match(/^(\\d{1,2})[\\-.\\/ ]+([a-zçğıöşü]+)[\\-.\\/ ]+(\\d{2,4})$/i);
|
|
2983
|
+
if(m){const mo=MON[__norm(m[2])];if(mo){let y=+m[3];if(y<100)y+=2000;return [y,mo,+m[1]];}}
|
|
2984
|
+
return null;
|
|
2985
|
+
}
|
|
2986
|
+
function __parseTime(raw){
|
|
2987
|
+
const m=String(raw||'').trim().match(/^(\\d{1,2})[:.h](\\d{2})/);
|
|
2988
|
+
if(!m) return null;
|
|
2989
|
+
return [Math.min(23,+m[1]),Math.min(59,+m[2])];
|
|
2990
|
+
}
|
|
2991
|
+
/*__DT_HELPERS_END__*/
|
|
2992
|
+
return new Promise((res)=>{try{
|
|
2896
2993
|
const t=__target();
|
|
2897
2994
|
if(!t) return res(JSON.stringify({typed:false,reason:'eleman bulunamadi (ref eski olabilir - browser_snapshot al)'}));
|
|
2898
2995
|
const how=t.how,el=t.el;
|
|
2996
|
+
/* tarih/saat alanları: programatik değer + input/change event — native takvim popup'ı hiç açılmaz */
|
|
2997
|
+
const it=(el.tagName==='INPUT'?(el.type||'').toLowerCase():'');
|
|
2998
|
+
if(it==='date'||it==='month'||it==='time'||it==='datetime-local'){
|
|
2999
|
+
const RAW=${text};
|
|
3000
|
+
let v=null;
|
|
3001
|
+
if(it==='time'){
|
|
3002
|
+
const p=__parseTime(RAW); if(p) v=p.map(function(n){return String(n).padStart(2,'0');}).join(':');
|
|
3003
|
+
} else {
|
|
3004
|
+
const p=__parseDate(RAW);
|
|
3005
|
+
if(p&&p.every(Number.isFinite)&&p[1]>=1&&p[1]<=12&&p[2]>=1&&p[2]<=31){
|
|
3006
|
+
const pad=function(n){return String(n).padStart(2,'0');};
|
|
3007
|
+
const ymd=p[0]+'-'+pad(p[1])+'-'+pad(p[2]);
|
|
3008
|
+
v = it==='month' ? (p[0]+'-'+pad(p[1])) : it==='datetime-local' ? (ymd+'T'+((__parseTime(RAW)||[12,0])).map(pad).join(':')) : ymd;
|
|
3009
|
+
}
|
|
3010
|
+
}
|
|
3011
|
+
if(!v) return res(JSON.stringify({typed:false,inputType:it,reason:'tarih/saat alanı — metin anlaşılamadı. "2026-03-15", "15.03.2026" veya "15 Mart 2026" gibi gönder'}));
|
|
3012
|
+
const setter=Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,'value').set;
|
|
3013
|
+
setter.call(el,v);
|
|
3014
|
+
el.dispatchEvent(new Event('input',{bubbles:true}));
|
|
3015
|
+
el.dispatchEvent(new Event('change',{bubbles:true}));
|
|
3016
|
+
return res(JSON.stringify({typed:true,target:how+' <input type='+it+'>',value:v,note:'tarih/saat programatik ayarlandi — takvim tiklamak gerekmez'}));
|
|
3017
|
+
}
|
|
2899
3018
|
el.scrollIntoView({block:'center'});el.focus();
|
|
2900
3019
|
setTimeout(()=>{try{
|
|
2901
3020
|
if(el.isContentEditable){el.textContent=${text};}
|
|
@@ -3022,6 +3141,14 @@ async function browserAct(kind, args, signal) {
|
|
|
3022
3141
|
: (obj.reason || 'tamam')
|
|
3023
3142
|
);
|
|
3024
3143
|
|
|
3144
|
+
/* HIZ: eylem cevabına taze snapshot göm — model ayrı browser_snapshot çağırmaz (tur sayısı yarıya iner) */
|
|
3145
|
+
let freshSnap = null;
|
|
3146
|
+
try {
|
|
3147
|
+
const sraw = await wc.executeJavaScript(BROWSER_SNAPSHOT_JS, true);
|
|
3148
|
+
const sobj = JSON.parse(sraw);
|
|
3149
|
+
if (sobj && typeof sobj.count === 'number') freshSnap = sobj;
|
|
3150
|
+
} catch {}
|
|
3151
|
+
|
|
3025
3152
|
return {
|
|
3026
3153
|
ok: true,
|
|
3027
3154
|
action: kind,
|
|
@@ -3029,7 +3156,12 @@ async function browserAct(kind, args, signal) {
|
|
|
3029
3156
|
url: wc.getURL(),
|
|
3030
3157
|
title: wc.getTitle(),
|
|
3031
3158
|
navigated,
|
|
3032
|
-
...(
|
|
3159
|
+
...(freshSnap ? { snapshot: freshSnap.snapshot, refCount: freshSnap.count } : {}),
|
|
3160
|
+
...(navigated
|
|
3161
|
+
? { note: 'sayfa degisti — yanıtta güncel snapshot var; refler eskiyse yeni browser_snapshot al' }
|
|
3162
|
+
: freshSnap
|
|
3163
|
+
? { note: 'yanıtta güncel snapshot (' + freshSnap.count + ' ref) — sonraki hamlede bunları kullan, ayrıca snapshot alma' }
|
|
3164
|
+
: {}),
|
|
3033
3165
|
recent: recentLog(),
|
|
3034
3166
|
};
|
|
3035
3167
|
} catch (e) {
|
|
@@ -4105,14 +4237,14 @@ const NPM_ONLY_TEXT =
|
|
|
4105
4237
|
/* buton akışı: görünür cmd + "beast update" + uygulama kapanışı */
|
|
4106
4238
|
function npmUpdateViaCmd() {
|
|
4107
4239
|
try {
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4240
|
+
/* cmd KULLANICININ kendi klasöründe açılır (cd /d %USERPROFILE%): Electron kısayoldan
|
|
4241
|
+
paket klasörü (node_modules\beast-agent) içinde başlatıldıysa cmd orada açılır ve
|
|
4242
|
+
npm install -g değiştirmeye çalıştığı paketi KİLİTLER (EBUSY) — 5 denemede de
|
|
4243
|
+
patlar. Home'da açılınca kurulum sorunsuz akar; "beast" PATH'te %APPDATA%\npm'de.
|
|
4244
|
+
NOT: spawn argümanlarında ÇİFT TIRNAK kullanma — node \" olarak escape'ler ve cmd bozar. */
|
|
4113
4245
|
spawn(
|
|
4114
4246
|
'cmd.exe',
|
|
4115
|
-
['/c', 'start', 'Beast Guncelleme', 'cmd', '/k',
|
|
4247
|
+
['/c', 'start', 'Beast Guncelleme', 'cmd', '/k', 'cd /d %USERPROFILE% && beast update'],
|
|
4116
4248
|
{ detached: true, stdio: 'ignore', windowsHide: false }
|
|
4117
4249
|
).unref();
|
|
4118
4250
|
} catch {}
|
|
@@ -4390,22 +4522,9 @@ ipcMain.handle('terminal:toggle', () => {
|
|
|
4390
4522
|
return { ok: true, cwd: (engine && engine.workspace) || settings.workspace || app.getPath('home') };
|
|
4391
4523
|
});
|
|
4392
4524
|
|
|
4393
|
-
/* Git Bash kuruluysa yolunu bulur; yoksa null döner */
|
|
4394
|
-
function findGitBash() {
|
|
4395
|
-
const cands = [];
|
|
4396
|
-
if (process.env['ProgramFiles']) cands.push(path.join(process.env['ProgramFiles'], 'Git', 'bin', 'bash.exe'));
|
|
4397
|
-
if (process.env['ProgramFiles(x86)']) cands.push(path.join(process.env['ProgramFiles(x86)'], 'Git', 'bin', 'bash.exe'));
|
|
4398
|
-
if (process.env['LocalAppData']) cands.push(path.join(process.env['LocalAppData'], 'Programs', 'Git', 'bin', 'bash.exe'));
|
|
4399
|
-
cands.push('C:\\Program Files\\Git\\bin\\bash.exe');
|
|
4400
|
-
for (const c of cands) {
|
|
4401
|
-
try { if (fs.existsSync(c)) return c; } catch {}
|
|
4402
|
-
}
|
|
4403
|
-
return null;
|
|
4404
|
-
}
|
|
4405
|
-
|
|
4406
4525
|
ipcMain.handle('terminal:run', (_e, payload) => {
|
|
4407
4526
|
const cmd = String((payload && payload.cmd) || '').trim();
|
|
4408
|
-
const shell = String((payload && payload.shell) || '
|
|
4527
|
+
const shell = String((payload && payload.shell) || 'cmd');
|
|
4409
4528
|
if (!cmd) return { ok: false, error: 'boş komut' };
|
|
4410
4529
|
if (termChild) return { ok: false, error: 'önceki komut sürüyor — ■ ile durdurabilirsin' };
|
|
4411
4530
|
const cwd = (engine && engine.workspace) || settings.workspace || app.getPath('home');
|
|
@@ -4414,12 +4533,8 @@ ipcMain.handle('terminal:run', (_e, payload) => {
|
|
|
4414
4533
|
if (shell === 'cmd') {
|
|
4415
4534
|
file = 'cmd.exe';
|
|
4416
4535
|
args = ['/d', '/s', '/c', cmd];
|
|
4417
|
-
} else if (shell === 'bash') {
|
|
4418
|
-
const bash = findGitBash();
|
|
4419
|
-
if (!bash) return { ok: false, error: 'Git Bash bulunamadı — Git for Windows kurulu mu? (https://git-scm.com)' };
|
|
4420
|
-
file = bash;
|
|
4421
|
-
args = ['-c', cmd];
|
|
4422
4536
|
} else {
|
|
4537
|
+
/* geriye dönük uyumluluk — UI artık yalnız CMD gönderir */
|
|
4423
4538
|
file = 'powershell.exe';
|
|
4424
4539
|
args = ['-NoProfile', '-NoLogo', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', cmd];
|
|
4425
4540
|
}
|
|
@@ -5412,10 +5527,61 @@ ipcMain.handle('bots:add', (_e, input) => {
|
|
|
5412
5527
|
ipcMain.handle('bots:dm:list', () => engine.listBotDmSessions());
|
|
5413
5528
|
ipcMain.handle('bots:dm:read', (_e, id) => engine.readBotDm(id));
|
|
5414
5529
|
|
|
5415
|
-
/*
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5530
|
+
/* GITHUB MODALI → Trending / Tüm Repolar: GitHub Search API.
|
|
5531
|
+
opts.mode: 'trend' (varsayılan) | 'all' (tüm repolar — yıldız tabanı yok)
|
|
5532
|
+
opts.range: all | 6m | 1m | 2w | 1w (tüm zamanlar / 6 ay / 1 ay / 2 hafta / 1 hafta)
|
|
5533
|
+
opts.order: desc (azalan) | asc (artan) — yıldız sırası
|
|
5534
|
+
opts.q: arama terimi (trend modunda tarih filtresi UYGULANMAZ — her zaman tüm zamanlar)
|
|
5535
|
+
Kimliksiz 60 istek/saat limiti var — 403'te UI bilgi gösterir. */
|
|
5536
|
+
ipcMain.handle('github:trending', async (_e, opts) => {
|
|
5537
|
+
const o = opts && typeof opts === 'object' ? opts : {};
|
|
5538
|
+
const mode = o.mode === 'all' ? 'all' : 'trend';
|
|
5539
|
+
const range = ['all', '6m', '1m', '2w', '1w'].includes(o.range) ? o.range : '2w';
|
|
5540
|
+
const order = o.order === 'asc' ? 'asc' : 'desc';
|
|
5541
|
+
const q = String(o.q || '').trim().slice(0, 120);
|
|
5542
|
+
const days = { all: 0, '6m': 180, '1m': 30, '2w': 14, '1w': 7 }[range];
|
|
5543
|
+
const since = days ? new Date(Date.now() - days * 86400000).toISOString().slice(0, 10) : '';
|
|
5544
|
+
/* aralık başına yıldız tabanı: listeler anlamlı kalsın diye */
|
|
5545
|
+
const floor = { all: 5000, '6m': 200, '1m': 50, '2w': 20, '1w': 10 }[range];
|
|
5546
|
+
let query;
|
|
5547
|
+
let useSort = true; /* 'all' + best eşleşmede sort param gönderilmez */
|
|
5548
|
+
if (mode === 'all') {
|
|
5549
|
+
if (!q) return { ok: true, items: [] };
|
|
5550
|
+
query = q; /* yıldız tabanı YOK — 0 yıldızlı dahil tüm repolar bulunur */
|
|
5551
|
+
if (o.allSort === 'best') useSort = false;
|
|
5552
|
+
} else if (q) {
|
|
5553
|
+
/* arama modu: tarih filtresi YOK — her zaman tüm zamanlar içinde arar,
|
|
5554
|
+
yıldız sırasına göre döner (küçük taban: çöp listelenmesin diye) */
|
|
5555
|
+
query = q + ' stars:>=10';
|
|
5556
|
+
} else if (range === 'all') {
|
|
5557
|
+
query = 'stars:>=' + floor;
|
|
5558
|
+
} else {
|
|
5559
|
+
query = 'created:>=' + since + ' stars:>=' + floor;
|
|
5560
|
+
}
|
|
5561
|
+
let url = 'https://api.github.com/search/repositories?q=' + encodeURIComponent(query).replace(/%20/g, '+') + '&per_page=20';
|
|
5562
|
+
if (useSort) url += '&sort=stars&order=' + order;
|
|
5563
|
+
try {
|
|
5564
|
+
const res = await fetch(url, {
|
|
5565
|
+
headers: { Accept: 'application/vnd.github+json', 'User-Agent': 'beast-agent' },
|
|
5566
|
+
signal: AbortSignal.timeout(15000),
|
|
5567
|
+
});
|
|
5568
|
+
if (res.status === 403 || res.status === 429) {
|
|
5569
|
+
return { ok: false, error: 'GitHub API limiti doldu (403) — bir saat sonra tekrar dene' };
|
|
5570
|
+
}
|
|
5571
|
+
if (!res.ok) return { ok: false, error: 'GitHub API ' + res.status };
|
|
5572
|
+
const data = await res.json();
|
|
5573
|
+
const items = (data.items || []).map((r) => ({
|
|
5574
|
+
full_name: r.full_name,
|
|
5575
|
+
html_url: r.html_url,
|
|
5576
|
+
description: String(r.description || '').slice(0, 240),
|
|
5577
|
+
language: r.language || '',
|
|
5578
|
+
stars: r.stargazers_count || 0,
|
|
5579
|
+
pushed_at: r.pushed_at || '',
|
|
5580
|
+
}));
|
|
5581
|
+
return { ok: true, items };
|
|
5582
|
+
} catch (e) {
|
|
5583
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
5584
|
+
}
|
|
5419
5585
|
});
|
|
5420
5586
|
|
|
5421
5587
|
ipcMain.handle('bots:update', (_e, { id, patch }) => {
|
package/src/preload.js
CHANGED
|
@@ -143,7 +143,7 @@ contextBridge.exposeInMainWorld('beast', {
|
|
|
143
143
|
botsAdd: (input) => ipcRenderer.invoke('bots:add', input),
|
|
144
144
|
botsDmList: () => ipcRenderer.invoke('bots:dm:list'),
|
|
145
145
|
botsDmRead: (id) => ipcRenderer.invoke('bots:dm:read', id),
|
|
146
|
-
|
|
146
|
+
githubTrending: (opts) => ipcRenderer.invoke('github:trending', opts),
|
|
147
147
|
botsUpdate: (id, patch) => ipcRenderer.invoke('bots:update', { id, patch }),
|
|
148
148
|
botsRemove: (id) => ipcRenderer.invoke('bots:remove', id),
|
|
149
149
|
botsStats: () => ipcRenderer.invoke('bots:stats'),
|