beast-agent 0.15.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.
Files changed (62) hide show
  1. package/.env.example +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +94 -0
  4. package/assets/app.ico +0 -0
  5. package/assets/tray.png +0 -0
  6. package/bin/beast-agent.js +23 -0
  7. package/config.example.yaml +26 -0
  8. package/eng.traineddata +0 -0
  9. package/package.json +101 -0
  10. package/scripts/swap-electron.js +40 -0
  11. package/src/agent/bus.js +384 -0
  12. package/src/agent/computeruse.js +200 -0
  13. package/src/agent/config.js +284 -0
  14. package/src/agent/engine.js +3118 -0
  15. package/src/agent/kb.js +123 -0
  16. package/src/agent/llm.js +219 -0
  17. package/src/agent/logger.js +90 -0
  18. package/src/agent/memory.js +383 -0
  19. package/src/agent/pdf.js +20 -0
  20. package/src/agent/scripts/__pycache__/websearch.cpython-312.pyc +0 -0
  21. package/src/agent/scripts/news.py +113 -0
  22. package/src/agent/scripts/websearch.py +225 -0
  23. package/src/agent/skills.js +522 -0
  24. package/src/agent/tokens.js +39 -0
  25. package/src/agent/tools.js +911 -0
  26. package/src/agent/usage.js +125 -0
  27. package/src/agent/watchers.js +312 -0
  28. package/src/agent/watext.js +75 -0
  29. package/src/agent/whatsapp.js +494 -0
  30. package/src/cron.js +245 -0
  31. package/src/main.js +3622 -0
  32. package/src/preload.js +122 -0
  33. package/src/renderer/browserPreload.js +73 -0
  34. package/src/renderer/i18n.js +757 -0
  35. package/src/renderer/index.html +227 -0
  36. package/src/renderer/renderer.js +3250 -0
  37. package/src/renderer/style.css +1559 -0
  38. package/tests/approval.test.js +56 -0
  39. package/tests/bg-jobs.test.js +315 -0
  40. package/tests/bus.test.js +46 -0
  41. package/tests/computeruse-context.test.js +69 -0
  42. package/tests/cron.test.js +95 -0
  43. package/tests/engine.test.js +140 -0
  44. package/tests/eval.test.js +91 -0
  45. package/tests/fallout.test.js +188 -0
  46. package/tests/llm.test.js +56 -0
  47. package/tests/memory.test.js +33 -0
  48. package/tests/memoryloop.test.js +26 -0
  49. package/tests/notegen.test.js +63 -0
  50. package/tests/owner.test.js +32 -0
  51. package/tests/python.test.js +75 -0
  52. package/tests/scenarios.json +81 -0
  53. package/tests/sessioncode.test.js +50 -0
  54. package/tests/setup.js +9 -0
  55. package/tests/tokens.test.js +42 -0
  56. package/tests/tools.test.js +84 -0
  57. package/tests/usage.test.js +44 -0
  58. package/tests/v11.test.js +76 -0
  59. package/tests/watchers.test.js +206 -0
  60. package/tests/watext.test.js +53 -0
  61. package/tests/whatsapp.test.js +103 -0
  62. package/tests/wherewasi.test.js +40 -0
@@ -0,0 +1,522 @@
1
+ 'use strict';
2
+
3
+ /* Beast skills: %APPDATA%\beast\skills\**\SKILL.md
4
+ Only name+description go into the system prompt (few tokens).
5
+ The model reads the full SKILL.md on demand via read_file. */
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const { beastRoot } = require('./memory');
10
+
11
+ function dir() {
12
+ return path.join(beastRoot(), 'skills');
13
+ }
14
+
15
+ function parseFrontmatter(text) {
16
+ const m = String(text || '')
17
+ .replace(/^\uFEFF/, '')
18
+ .match(/^---\r?\n([\s\S]*?)\r?\n---/);
19
+ if (!m) return {};
20
+ const out = {};
21
+ for (const line of m[1].split(/\r?\n/)) {
22
+ const mm = line.match(/^([A-Za-z_-]+):\s*(.*)$/);
23
+ if (mm && !(mm[1] in out)) out[mm[1]] = mm[2].trim().replace(/^["']|["']$/g, '');
24
+ }
25
+ return out;
26
+ }
27
+
28
+ function walk(d, depth, out) {
29
+ if (depth > 4) return;
30
+ let entries;
31
+ try {
32
+ entries = fs.readdirSync(d, { withFileTypes: true });
33
+ } catch {
34
+ return;
35
+ }
36
+ for (const e of entries) {
37
+ const p = path.join(d, e.name);
38
+ if (e.isDirectory()) {
39
+ /* .drafts: yansıma taslakları — kurulu skill listesine karışmaz */
40
+ if (e.name === '.drafts') continue;
41
+ walk(p, depth + 1, out);
42
+ } else if (e.name === 'SKILL.md') {
43
+ try {
44
+ const fm = parseFrontmatter(fs.readFileSync(p, 'utf8'));
45
+ out.push({
46
+ name: fm.name || path.basename(path.dirname(p)),
47
+ description: fm.description || '',
48
+ path: p,
49
+ });
50
+ } catch {}
51
+ }
52
+ }
53
+ }
54
+
55
+ function scan() {
56
+ const out = [];
57
+ walk(dir(), 0, out);
58
+ return out.sort((a, b) => a.name.localeCompare(b.name));
59
+ }
60
+
61
+ const SEEDS = [
62
+ {
63
+ folder: 'help',
64
+ body: `---
65
+ name: help
66
+ description: Beast Agent genel yardım — tüm veri yolları (%APPDATA%\\beast), ayar/config dosyaları, memory, cron, watcher, WhatsApp, TTS, araç özeti.
67
+ version: 1.0.0
68
+ ---
69
+
70
+ # Beast Agent Yardım
71
+
72
+ Tüm veri tek kökte: \`%APPDATA%\\beast\\\` (test/taşınabilir için \`BEAST_DATA\` env override).
73
+
74
+ ## Dosya Haritası
75
+
76
+ | Yol | Ne İşe Yarar |
77
+ |---|---|
78
+ | \`config.yaml\` + \`.env\` | Model sağlayıcıları + API anahtarları. Provider tanımı: \`providers.<id>.base_url/key_env/models\`; aktif seçim: \`model.provider/default\`. \`.env\`'de \`<PROVIDER>_API_KEY\` veya \`key_env\` ile eşleşen anahtar |
79
+ | \`settings.json\` | App ayarları: theme, workspace, modelOverride, customProviders, roleModels, deletedModels, fallout, waTts (TTS), email (mail credential), waAllow, waLockdown |
80
+ | \`sessions\\\` | Sohbet oturumları (mesaj JSON'ları) |
81
+ | \`memories\\\` | SOUL.md (kişilik), MEMORY.md (uzun hafıza kayıtları), USER.md (kullanıcı bilgisi) |
82
+ | \`skills\\\` | \`**\\\\SKILL.md\` — isim+açıklama system prompt'a gider, gövde okunur |
83
+ | \`cron.json\` | Zamanlanmış görevler (5 alanlı cron) |
84
+ | \`watchers.json\` | Web/batarya izleyicileri |
85
+ | \`wa-auth\\\` | WhatsApp Baileys eşleme auth'u — SİLME |
86
+ | \`wa-chats.json\`, \`wa.log\` | WA sohbet eşlemesi ve log |
87
+ | \`logs\\\`, \`scripts\\\` | Çalışma logları / kullanıcı scriptleri |
88
+
89
+ ## Sağlık Kontrolleri
90
+
91
+ - "Model yapılandırılmadı" hatası → \`config.yaml\`/\`.env\` dolu mu bak; yoksa kullanıcıdan Ayarlar → Provider'dan model seçmesini ya da dosyaları doldurmasını iste (\`config.example.yaml\` repo kökünde örnek var).
92
+ - Mail sorunları → \`email\` skill'ini oku.
93
+ - fallout zinciri → Ayarlar → Fallout; 401'de sıradaki slota otomatik düşer.
94
+
95
+ ## Davranış Kuralları
96
+
97
+ - Şifre/API anahtarlarını asla düz metin loglama/tekrarlama.
98
+ - settings.json'a el ile yazarken app kapalı olsun (kapanışta ezme riski).
99
+ - Yeni yetenek gerekiyorsa \`%APPDATA%\\beast\\skills\\<ad>\\\\SKILL.md\` oluşturmayı önerebilirsin.`,
100
+ },
101
+ {
102
+ folder: 'email',
103
+ body: `---
104
+ name: email
105
+ description: E-posta entegrasyonu — credential nerede girilir, Gmail uygulama şifresi, email_list/email_read/email_send araçlarının kullanımı ve hata giderme.
106
+ version: 1.1.0
107
+ ---
108
+
109
+ # E-posta Entegrasyonu (IMAP/SMTP)
110
+
111
+ ## Credential NEREYE Girilir?
112
+
113
+ İki yol var:
114
+
115
+ 1. **UI:** Ayarlar (⚙) → **E-posta** sekmesi → alanları doldur → "E-posta Kaydet". Kaydettiği yer: \`%APPDATA%\\beast\\settings.json\` içindeki \`"email"\` bloğu.
116
+ 2. **Dosya:** aynı bloğu elle düzenle:
117
+
118
+ \`\`\`json
119
+ "email": {
120
+ "host": "imap.gmail.com",
121
+ "port": 993,
122
+ "user": "adres@gmail.com",
123
+ "pass": "uygulama sifresi (16 hane)",
124
+ "smtpHost": "smtp.gmail.com",
125
+ "smtpPort": 465
126
+ }
127
+ \`\`\`
128
+
129
+ ÖNEMLİ:
130
+ - \`pass\` normal Gmail şifresi DEĞİL — Google Hesabı → Güvenlik → "Uygulama Şifreleri"nden üretilen 16 haneli şifre (2FA açık olmalı).
131
+ - UI'da şifre alanı \`***\` maskeli gelir; alanı BOŞ bırakıp kaydetmek mevcut şifreyi KORUR. Yeni şifre girmek için alanı doldur.
132
+ - SMTP port: 465 (implicit TLS) veya 587 (STARTTLS) — ikisi de desteklenir, kod otomatik seçer.
133
+ - UI'dan kaydetme ANINDA geçerlidir. Dosyayı ELLE düzenlediysen uygulamayı yeniden başlatmak ŞARTTIR — app ayarları başlangıçta belleğe yükler, çalışan süreç eski değerleri kullanmaya devam eder.
134
+ - settings.json bozulursa app otomatik olarak \`settings.backup.json\`'dan kurtarır; veri kaybı olmaz.
135
+
136
+ ## Araçlar
137
+
138
+ - \`email_list\` → \`{ "limit": 10, "unread": false }\` — son mailler: uid, from, subject, date döner.
139
+ - \`email_read\` → \`{ "uid": 123 }\` — uid'nin tam gövde metnini getirir (önce email_list çağır).
140
+ - \`email_send\` → \`{ "to": "a@b.com", "subject": "...", "body": "düz metin" }\`
141
+
142
+ ## Hata Giderme
143
+
144
+ | Belirti | Sebep / Çözüm |
145
+ |---|---|
146
+ | \`e-posta ayarlanmamış\` | Credential girilmemiş YA DA settings.json elle düzenlendi ama restart yapılmadı → önce restart öner |
147
+ | \`Invalid credentials\` / auth hatası | Uygulama şifresi yanlış/silinmiş → yeni üret |
148
+ | \`imap modülü yok\` | node_modules bozuk → \`npm i imapflow\` (repo kökünde) |
149
+ | Timeout | Ağ/port sorunu; 993 (IMAP) ve 465 veya 587 (SMTP) erişilebilir mi bak |
150
+
151
+ Gönderim (nodemailer) takılırsa son çare PowerShell fallback:
152
+
153
+ \`\`\`powershell
154
+ $sec = ConvertTo-SecureString 'UYGULAMA_SIFRESI' -AsPlainText -Force
155
+ $cred = New-Object System.Management.Automation.PSCredential('adres@gmail.com', $sec)
156
+ Send-MailMessage -SmtpServer smtp.gmail.com -Port 587 -UseSsl -From 'adres@gmail.com' -To 'ALICI' -Subject 'Konu' -Body 'Metin' -Credential $cred
157
+ \`\`\`
158
+
159
+ Şifreyi asla log'a, sohbet çıktısına veya kullanıcıya düz metin olarak gösterme.`,
160
+ },
161
+ {
162
+ folder: 'python-web-search',
163
+ force: true,
164
+ body: `---
165
+ name: python-web-search
166
+ description: VARSAYILAN web arama zinciri — 1) dahili tarayıcı DİREK GOOGLE (gerçek Chromium, bot koruması yok; Google AI cevabi 'ai' alanında hazır gelir) 2) python çoklu-motor (ddgs / DDG+Bing+Mojeek, anahtarsız) 3) Exa (son çare, anahtar varsa). Takılmama kuralları içerir.
167
+ version: 1.3.2
168
+ ---
169
+
170
+ # Web Arama Zinciri (VARSAYILAN)
171
+
172
+ web_search aracı çağrıldığında zincir OTOMATİK çalışır — elle motor seçmen gerekmez:
173
+
174
+ ## 1. Dahili tarayıcı (ilk tercih — Google AI)
175
+
176
+ Gerçek Chromium olduğu için Google'a bot koruması uygulamaz. Arama aep=1 ile açılır: Google'ın KENDİ AI cevabı yanıtın 'ai' alanında HAZIR gelir — "kimdir/nedir" sorularında önce onu kullan, kaynak linkleri de 'results' alanında gelir. Sağ panelde açılır, kullanıcı da görebilir.
177
+
178
+ ## 2. Python çoklu-motor (tarayıcı sonuç vermezse)
179
+
180
+ ddgs kütüphanesi (github.com/deedy5/ddgs — anti-bot korumalı metasearch) veya DuckDuckGo + Bing + Mojeek PARALEL — anahtar gerekmez. Gömülü Python runtime otomatik yönetilir.
181
+
182
+ \`\`\`
183
+ web_search {"query": "sorgu", "max_results": 6}
184
+ \`\`\`
185
+
186
+ - Çoklu sorgu gerekiyorsa: aynı turda birden ÇOK web_search çağrısını PARALEL ver — toplam süre tek arama kadar olur.
187
+ - Sonuç listesinden 1-2 URL'nin içeriğini okumak için http_fetch kullan.
188
+
189
+ ## 3. Exa (son çare)
190
+
191
+ Yukarıdakiler boş dönerse ve Ayarlar → Web Arama'ya Exa anahtarı girildiyse devreye girer.
192
+
193
+ ## Toplu / derin işler (python_run)
194
+
195
+ - Haber başlıkları: python_run script=news.py args ["--limit","8","--json"]
196
+ - 3+ sorguyu tek betikte taramak için:
197
+
198
+ \`\`\`python
199
+ # python_run code: çoklu sorgu tek seferde
200
+ import subprocess, sys, os
201
+ script = os.path.join(os.environ.get('APPDATA', ''), 'beast', 'scripts', 'websearch.py')
202
+ for q in ["sorgu 1", "sorgu 2", "sorgu 3"]:
203
+ subprocess.run([sys.executable, script, q, "--limit", "4", "--json"])
204
+ \`\`\`
205
+
206
+ ## TAKILMAMA KURALLARI (ZORUNLU)
207
+
208
+ - Bir bilgi 2-3 farklı sorguyla bulunamıyorsa PEŞİNİ BIRAK: bulduğun kısmi bilgiyle devam et, neyi bulamadığını raporda açıkça yaz.
209
+ - Kapalı/gizli içerik (Instagram gizli hesap, private profil, login arkası veri) ARANMAZ — bulunamayacağı belliyse hemen alternatif kaynağa/geçişe geç.
210
+ - DDG boş/engelli dönerse "DuckDuckGo kapandı" DEME: bot koruması devrededir (~1 saatte kendiliğinden açılır) ve zincir otomatik olarak Bing/Mojeek/tarayıcıya geçer — bu normal çalışmadır.
211
+ - Araştırma hedefi: ~3 dakika, 3-5 kaynak. Derinleşme = zaman kaybı.
212
+ - Motor hata verirse bir kez farklı sorguyla dene, sonra elindekiyle raporu kapat.`,
213
+ },
214
+ {
215
+ folder: 'gold-trading',
216
+ body: `---
217
+ name: gold-trading
218
+ description: Kullanıcının GOLD (FxPro MT5) işlem tercihleri — market structure kontrol listesi.
219
+ version: 1.0.0
220
+ ---
221
+
222
+ # GOLD İşlem Tercihleri (Batuhan)
223
+
224
+ Sembol: "GOLD" (XAUUSD değil), FxPro MT5 demo, M1.
225
+
226
+ Zorunlu kurallar:
227
+ - Market structure şart: swing/HH-LL analizi olmadan ve BOS teyidi olmadan İŞLEM YOK. MA kesişimi tek başına gürültüdür.
228
+ - Geniş SL: ~4xATR; BE: ~2xATR kârda.
229
+ - Yetki sende: açmadan önce sorma; SADECE sonucu (STOP/TP) raporla, giriş/trailing tiklerini anlatma.
230
+ - AI komite yaklaşımı: sinyal yakalanır → görsel/teknik/risk analizi → açılış.`,
231
+ },
232
+ {
233
+ folder: 'pdf',
234
+ body: `---
235
+ name: pdf
236
+ description: PDF işleme rehberi — metin çıkarma (pdf-parse), Türkçe karakterli PDF oluşturma/değiştirme (pdf-lib/pdfkit), sayfa→görsel (pdf-to-img) ve taranmış PDF için OCR (tesseract.js). Kullanıcı bir .pdf dosyasından bahsederse ÖNCE bu skill'i oku.
237
+ version: 1.0.0
238
+ ---
239
+
240
+ # PDF İşleme Rehberi
241
+
242
+ Kurulu paketler ve rolleri:
243
+
244
+ | İstek | Paket |
245
+ |---|---|
246
+ | Metin çıkar / oku | \`pdf-parse\` |
247
+ | Yeni PDF üret / mevcut PDF'i değiştir | \`pdf-lib\` (+ \`@pdf-lib/fontkit\`) |
248
+ | Düzenli belge üretimi (rapor, özet) | \`pdfkit\` |
249
+ | Sayfa→PNG/JPG (taranmış/görsel PDF) | \`pdf-to-img\` |
250
+ | Taranmış PDF'den metin (OCR) | \`pdf-to-img\` + \`tesseract.js\` |
251
+
252
+ ## KRİTİK KURAL — aynı process çakışması
253
+
254
+ \`pdf-parse\` ile \`pdf-to-img\` AYNI node process'inde yüklenirse şuna benzer hata alırsın:
255
+ \`\`\`
256
+ The API version "5.x.x" does not match the Worker version "5.y.y"
257
+ \`\`\`
258
+ Çözüm: her iş küçük BAĞIMSIZ bir script olsun; run_command ile ayrı ayrı çalıştır.
259
+ Tek process'te sadece BİR pdfjs tabanlı paket kullan (\`read_file\` aracı pdf-parse kullanır — senin render scriptlerin bunu yüklememeli).
260
+
261
+ ## 1. Okuma
262
+
263
+ \`read_file\` aracı .pdf'i otomatik metne çevirir — önce onu dene. Elle script gerekirse:
264
+
265
+ \`\`\`js
266
+ // pdf-oku.js → node pdf-oku.js dosya.pdf
267
+ const fs = require('fs');
268
+ const { PDFParse } = require('pdf-parse');
269
+ (async () => {
270
+ const parser = new PDFParse({ data: new Uint8Array(fs.readFileSync(process.argv[2])) });
271
+ try {
272
+ const r = await parser.getText();
273
+ console.log('sayfa=' + r.total);
274
+ console.log(r.text);
275
+ } finally { await parser.destroy(); }
276
+ })();
277
+ \`\`\`
278
+
279
+ Metin boş geliyorsa TARANMIŞ görseldir → 4 ve 5. adımlara geç.
280
+
281
+ ## 2. Oluşturma / Değiştirme (pdf-lib)
282
+
283
+ TÜRKÇE KARAKTER UYARISI: standard fontlar (Helvetica vb.) ğ ş ı İ karakterlerini BASAMAZ — betik sessizce bozuk çıktı üretir. Türkçe içerikte MUTLAKA Windows TTF embed et:
284
+
285
+ \`\`\`js
286
+ // pdf-yaz.js → node pdf-yaz.js cikti.pdf
287
+ const fs = require('fs');
288
+ const { PDFDocument, rgb } = require('pdf-lib');
289
+ const fontkit = require('@pdf-lib/fontkit');
290
+ (async () => {
291
+ const doc = await PDFDocument.create();
292
+ doc.registerFontkit(fontkit);
293
+ const trFont = await doc.embedFont(fs.readFileSync('C:/Windows/Fonts/arial.ttf'), { subset: true });
294
+ const page = doc.addPage([595, 842]); // A4
295
+ page.drawText('Ders Özeti\\n• Konu: Fonksiyonlar', { x: 50, y: 750, size: 14, font: trFont });
296
+ fs.writeFileSync(process.argv[2], await doc.save());
297
+ })();
298
+ \`\`\`
299
+
300
+ Mevcut PDF'i değiştirme / birleştirme:
301
+
302
+ \`\`\`js
303
+ const hedef = await PDFDocument.load(fs.readFileSync('a.pdf'));
304
+ const kaynak = await PDFDocument.load(fs.readFileSync('b.pdf'));
305
+ const pages = await hedef.copyPages(kaynak, kaynak.getPageIndices());
306
+ pages.forEach((p) => hedef.addPage(p));
307
+ fs.writeFileSync('birlesik.pdf', await hedef.save());
308
+ \`\`\`
309
+
310
+ Form doldurma: \`doc.getForm()\`, \`form.getTextField('Ad').setText(...)\`.
311
+
312
+ ## 3. Düzenli belge üretimi (pdfkit)
313
+
314
+ \`\`\`js
315
+ const PDFDocument = require('pdfkit');
316
+ const fs = require('fs');
317
+ const doc = new PDFDocument({ size: 'A4', margins: { top: 60, bottom: 60, left: 60, right: 60 } });
318
+ doc.pipe(fs.createWriteStream('rapor.pdf'));
319
+ doc.font('C:/Windows/Fonts/arial.ttf').fontSize(14).text('YARIYIL RAPORU');
320
+ doc.fontSize(11).text('Matematik: 90 Fizik: 78');
321
+ doc.end();
322
+ \`\`\`
323
+
324
+ ## 4. Sayfa → Görsel (taranmış PDF / vision girişi)
325
+
326
+ \`\`\`js
327
+ // pdf-gorsel.js → node pdf-gorsel.js dosya.pdf [sayfaNo]
328
+ const fs = require('fs');
329
+ const { pdf } = require('pdf-to-img');
330
+ (async () => {
331
+ const doc = await pdf(process.argv[2], { scale: 2 });
332
+ const n = Number(process.argv[3]) || 1;
333
+ const png = await doc.getPage(n);
334
+ fs.writeFileSync('sayfa-' + n + '.png', png);
335
+ console.log('sayfa-' + n + '.png yazıldı (' + doc.length + ' sayfa toplam)');
336
+ doc.destroy();
337
+ })();
338
+ \`\`\`
339
+ @napi-rs/canvas hazır binary gelir — derleme gerektirmez.
340
+
341
+ ## 5. OCR (taranmış PDF'den metin)
342
+
343
+ Önce 4. adımla PNG üret, sonra:
344
+
345
+ \`\`\`js
346
+ // ocr.js → node ocr.js sayfa-1.png [dil=tur]
347
+ const { createWorker } = require('tesseract.js');
348
+ (async () => {
349
+ const worker = await createWorker(process.argv[3] || 'tur');
350
+ try {
351
+ const { data } = await worker.recognize(process.argv[2]);
352
+ console.log(data.text.trim());
353
+ } finally { await worker.terminate(); }
354
+ })();
355
+ \`\`\`
356
+ İlk çağrıda dil verisi (~15MB) internetten indirilir — sonra offline çalışır.
357
+
358
+ ## Eğitim akışı (ders notu → özet/quiz)
359
+
360
+ 1. Notu oku: \`read_file\`; boşsa taranmıştır → adım 4 + 5
361
+ 2. Konuları/ayrım noktalarını çıkar, anahtar kavramları listele
362
+ 3. Quiz/özeti 2. adım deseniniyle PDF olarak oluştur (Türkçe font ZORUNLU)
363
+ 4. Çıktı yolunu kullanıcıya bildir. Büyük PDF'lerde \`read_file\` çıktısı kırpılır — bölüm bölüm oku.
364
+
365
+ ## Hata giderme
366
+
367
+ | Belirti | Çözüm |
368
+ |---|---|
369
+ | API version ... does not match Worker version ... | pdfjs tabanlı iki paket aynı process'te — ayrı script/process kullan |
370
+ | ğ/ş/ı harfleri kayıp veya kutu kutu | standard font yerine C:/Windows/Fonts/arial.ttf embed et |
371
+ | read_file boş metin döndü | taranmış PDF → 4. adım (görselleştir) + 5. adım (OCR) |
372
+ | tesseract dil verisi hatası | ilk indirme internet ister; tekrar dene |`,
373
+ },
374
+ ];
375
+
376
+ function writeSeed(seed) {
377
+ try {
378
+ const p = path.join(dir(), seed.folder);
379
+ fs.mkdirSync(p, { recursive: true });
380
+ fs.writeFileSync(path.join(p, 'SKILL.md'), seed.body);
381
+ } catch {}
382
+ }
383
+
384
+ /* Kaldırılan eski tohumlar: kullanıcı makinesinden de silinir */
385
+ const RETIRED_SEEDS = ['free-web-search'];
386
+
387
+ function seedIfEmpty() {
388
+ if (!scan().length) {
389
+ for (const seed of SEEDS) writeSeed(seed);
390
+ } else {
391
+ /* force tohumlar her açılışta güncellenir: varsayılan web arama skill'inin
392
+ içeriği hep güncel kalır (mevcut kurulum dahil) */
393
+ for (const seed of SEEDS) {
394
+ if (seed.force) writeSeed(seed);
395
+ }
396
+ }
397
+ for (const name of RETIRED_SEEDS) {
398
+ try { fs.rmSync(path.join(dir(), name), { recursive: true, force: true }); } catch {}
399
+ }
400
+ }
401
+
402
+ /* ---------- taslaklar (deneyimden doğan skill adayları) ---------- */
403
+
404
+ function draftsDir() {
405
+ return path.join(dir(), '.drafts');
406
+ }
407
+
408
+ function slugify(s) {
409
+ return (
410
+ String(s || '')
411
+ .toLowerCase()
412
+ .replace(/[çğıöşü]/g, (c) => ({ ç: 'c', ğ: 'g', ı: 'i', ö: 'o', ş: 's', ü: 'u' }[c] || c))
413
+ .replace(/[^a-z0-9]+/g, '-')
414
+ .replace(/^-+|-+$/g, '')
415
+ .slice(0, 40) || 'taslak'
416
+ );
417
+ }
418
+
419
+ /* Yansımadan doğan taslağı kaydet; aynı isim varsa üzerine yazar (revize). */
420
+ function addDraft({ name, description, body }) {
421
+ try {
422
+ const nm = String(name || '').trim() || 'yetenek';
423
+ const folder = slugify(nm);
424
+ const d = draftsDir();
425
+ fs.mkdirSync(d, { recursive: true });
426
+ const file = path.join(d, folder + '.draft.md');
427
+ const text =
428
+ `---\nname: ${nm}\ndescription: ${String(description || '').trim().slice(0, 160)}\nstatus: draft\ngeneratedAt: ${new Date().toISOString()}\n---\n\n` +
429
+ String(body || '').trim() +
430
+ '\n';
431
+ fs.writeFileSync(file, text);
432
+ return { ok: true, file };
433
+ } catch (e) {
434
+ return { ok: false, error: String((e && e.message) || e) };
435
+ }
436
+ }
437
+
438
+ function listDrafts() {
439
+ const out = [];
440
+ let entries;
441
+ try {
442
+ entries = fs.readdirSync(draftsDir(), { withFileTypes: true });
443
+ } catch {
444
+ return out;
445
+ }
446
+ for (const e of entries) {
447
+ if (!e.isFile() || !e.name.endsWith('.draft.md')) continue;
448
+ const p = path.join(draftsDir(), e.name);
449
+ try {
450
+ const text = fs.readFileSync(p, 'utf8');
451
+ const fm = parseFrontmatter(text);
452
+ out.push({
453
+ id: e.name.replace(/\.draft\.md$/, ''),
454
+ name: fm.name || e.name,
455
+ description: fm.description || '',
456
+ generatedAt: fm.generatedAt || '',
457
+ body: text,
458
+ });
459
+ } catch {}
460
+ }
461
+ return out;
462
+ }
463
+
464
+ function acceptDraft(id) {
465
+ try {
466
+ const src = path.join(draftsDir(), String(id) + '.draft.md');
467
+ const destDir = path.join(dir(), slugify(String(id)));
468
+ fs.mkdirSync(destDir, { recursive: true });
469
+ let text = fs.readFileSync(src, 'utf8');
470
+ /* status: draft satırını kaldır — artık kurulu skill */
471
+ text = text.replace(/^status:\s*draft\r?\n/mi, '');
472
+ fs.writeFileSync(path.join(destDir, 'SKILL.md'), text);
473
+ fs.unlinkSync(src);
474
+ return { ok: true, folder: path.basename(destDir), path: path.join(destDir, 'SKILL.md') };
475
+ } catch (e) {
476
+ return { ok: false, error: String((e && e.message) || e) };
477
+ }
478
+ }
479
+
480
+ function dropDraft(id) {
481
+ try {
482
+ fs.unlinkSync(path.join(draftsDir(), String(id) + '.draft.md'));
483
+ return { ok: true };
484
+ } catch (e) {
485
+ return { ok: false, error: String((e && e.message) || e) };
486
+ }
487
+ }
488
+
489
+ /* Kurulu bir skill'e madde ekler (#3 kural hattı): "## Kurallar" altına yazılır. */
490
+ function appendRuleToSkill(nameOrFolder, ruleText) {
491
+ try {
492
+ const all = scan();
493
+ const needle = String(nameOrFolder || '').trim().toLowerCase();
494
+ const hit = all.find((s) => s.name.toLowerCase() === needle) ||
495
+ all.find((s) => s.path.toLowerCase().includes(needle)) ||
496
+ null;
497
+ if (!hit) return { ok: false, error: `skill bulunamadı: ${nameOrFolder}` };
498
+ let text = fs.readFileSync(hit.path, 'utf8');
499
+ const line = `- ${String(ruleText || '').trim()}\n`;
500
+ if (/^##\s*Kurallar/im.test(text)) {
501
+ text = text.replace(/^(##\s*Kurallar\s*)$/im, `$1\n${line}`);
502
+ } else {
503
+ text = text.trimEnd() + `\n\n## Kurallar\n${line}`;
504
+ }
505
+ fs.writeFileSync(hit.path, text);
506
+ return { ok: true, skill: hit.name, path: hit.path };
507
+ } catch (e) {
508
+ return { ok: false, error: String((e && e.message) || e) };
509
+ }
510
+ }
511
+
512
+ module.exports = {
513
+ dir,
514
+ scan,
515
+ seedIfEmpty,
516
+ parseFrontmatter,
517
+ addDraft,
518
+ listDrafts,
519
+ acceptDraft,
520
+ dropDraft,
521
+ appendRuleToSkill,
522
+ };
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+
3
+ /* Beast tokens: dependency-free token estimation for context budgeting.
4
+ Accuracy is calibrated at runtime with real usage.prompt_tokens values
5
+ fed back from the API (see Engine.tokRatio). */
6
+
7
+ function estTokens(text) {
8
+ const s = String(text || '');
9
+ if (!s) return 0;
10
+ let narrow = 0;
11
+ let wide = 0;
12
+ for (let i = 0; i < s.length; i++) {
13
+ // CJK ve emoji gibi geniş karakterler token başına daha fazla maliyetlidir
14
+ if (s.charCodeAt(i) > 0x2e7f) wide++;
15
+ else narrow++;
16
+ }
17
+ return Math.ceil((narrow + wide * 2.5) / 4);
18
+ }
19
+
20
+ function estMsgTokens(m) {
21
+ if (!m) return 0;
22
+ let t = 4; // mesaj zarfı (role vb.)
23
+ const content = m.content;
24
+ if (typeof content === 'string') {
25
+ t += estTokens(content);
26
+ } else if (Array.isArray(content)) {
27
+ for (const p of content) {
28
+ if (p && p.type === 'text') t += estTokens(p.text);
29
+ else if (p && p.type === 'image_url') t += 600; // düşük detaylı görsel yaklaşımı
30
+ else t += estTokens(JSON.stringify(p || ''));
31
+ }
32
+ }
33
+ if (m.tool_calls && m.tool_calls.length) {
34
+ t += estTokens(JSON.stringify(m.tool_calls)) + 3 * m.tool_calls.length;
35
+ }
36
+ return t;
37
+ }
38
+
39
+ module.exports = { estTokens, estMsgTokens };