beast-agent 1.5.2 → 1.6.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.
@@ -1,621 +1,621 @@
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
- force: true,
65
- body: `---
66
- name: help
67
- description: Beast Agent'ın İÇ HARİTASI — "kaç bot var", "oturumlar nerede", "x nasıl çalışır" gibi SİSTEM sorularını saniyeler içinde çözer: tüm veri yolları, hazır PowerShell/json one-liner'lar, kod haritası, slash komutları, ayar sekmeleri. Kod tabanını saatlerce taramadan bu haritadan oku.
68
- version: 2.0.0
69
- ---
70
-
71
- # Beast Agent İç Haritası (self-help)
72
-
73
- ## ALTIN KURAL
74
- Sistem sorusu geldiğinde KOD TABANINI TARAMA. Aşağıdaki hazır yolu/one-liner'ı kullan — cevap 1-2 saniyede çıkar.
75
-
76
- ## HIZLI CEVAPLAR (hazır komutlar)
77
-
78
- **"Kaç bot var / botları listele"**
79
- \`\`\`powershell
80
- $b = Get-Content "$env:APPDATA\\beast\\bots.json" | ConvertFrom-Json; $b.bots | Select-Object id,name,admin,code
81
- "Toplam bot: " + $b.bots.Count + " (admin: Beast)"
82
- \`\`\`
83
- (python_run ile: \`import json;d=json.load(open(r'%APPDATA%\\beast\\bots.json',encoding='utf-8'));print(len(d['bots']),[b['name'] for b in d['bots']])\`)
84
-
85
- **"Kaç oturum var / sohbet geçmişi"**
86
- \`\`\`powershell
87
- (Get-ChildItem "$env:APPDATA\\beast\\sessions\\*.jsonl").Count
88
- \`\`\`
89
- Kod ↔ dosya eşleşmesi: jsonl'nin ilk satırı \`{"t":"meta","code":"ABC12"}\` taşır.
90
-
91
- **"Paralel ajanlar ne durumda"** → \`tasks_list\` aracını ÇAĞIR (kod okumaya gerek yok) ya da:
92
- \`\`\`powershell
93
- Get-Content "$env:APPDATA\\beast\\sessions\\bg-jobs.json" | ConvertFrom-Json | % jobs | Select title,status,startedAt,error
94
- \`\`\`
95
-
96
- **"Maliyet/usage"** → \`/usage\` komutu ya da Ayarlar → Maliyet; kayıt dosyası \`usage.json\`.
97
-
98
- **"Cron/izleyici var mı"** → \`cron.json\`, \`watchers.json\`.
99
-
100
- ## DOSYA HARİTASI (%APPDATA%\\beast\\)
101
-
102
- | Yol | Ne İşe Yarar |
103
- |---|---|
104
- | \`config.yaml\` + \`.env\` | Model sağlayıcıları + API anahtarları (\`providers.<id>.base_url/key_env/models\`, aktif: \`model.provider/default\`) |
105
- | \`settings.json\` | App ayarları: theme, workspace, modelOverride, customProviders, roleModels, deletedModels, fallout, waTts, email, waAllow, **searchChain (web arama sırası)**, **obscuraEnabled**, activeBotId |
106
- | \`sessions\\*.jsonl\` | Sohbet oturumları (satır JSON: t:meta kod, t:meta2 paralel-ajan işareti, t:msg mesaj, t:todo, t:notes) |
107
- | \`sessions\\bg-jobs.json\` | Paralel ajan iş kayıtları: id, title, status(queued/running/done/error/aborted), error=İPTAL SEBEBİ, groupId |
108
- | \`memories\\\` | SOUL.md (kişilik), MEMORY.md (uzun hafıza), USER.md (kullanıcı) |
109
- | \`skills\\<ad>\\SKILL.md\` | Skill'ler (isim+açıklama prompt'a girer, gövde okunur) |
110
- | \`bots.json\` | Bot kayıt defteri — max 5 bot, ilk kayıt admin 'Beast' (silinemez). Bot adı HARFLE başlamak ZORUNDA |
111
- | \`bots/<id>/\` | Botun izole klasörü: config.json, memory.md, yetkiler.json, logs/ |
112
- | \`whitelist.json\`, \`wa-auth\\\`, \`wa-chats.json\`, \`wa.log\` | WhatsApp izin listesi, Baileys auth (SİLME), sohbet eşlemesi, log |
113
- | \`cron.json\`, \`watchers.json\`, \`bus.json\` | Zamanlanmış görevler, web/batarya izleyicileri, olay abonelikleri |
114
- | \`scripts\\\` | Python scriptleri (websearch.py, news.py) |
115
- | \`obscura\\obscura.exe\` | Obscura stealth tarayıcı (web arama zincirinin 2. motoru; açılışta otomatik kurulur) |
116
- | \`py\\python.exe\` | Gömülü Python runtime (makinede Python olmasa bile python_run çalışır) |
117
- | \`usage.json\`, \`logs\\\` | Kullanım sayaçları, çalışma logları |
118
- | Masaüstü\\Beast-Backups | Şifreli yedekler (.beastbak) — /backup ile alınır |
119
-
120
- ## KOD HARİTASI (src/)
121
-
122
- | Dosya | Sorumluluk |
123
- |---|---|
124
- | \`main.js\` | Electron ana süreç: tüm IPC, WhatsApp/Telegram/Discord entegrasyonları, onay kapısı (/approve), /stop /restart, splash, gizli araştırma tarayıcısı, obscura kurulumu |
125
- | \`agent/engine.js\` | OTURUM MOTORU: send/_run tur döngüsü, paralel ajanlar (run_background, run_background_many, tasks_list, task_status, task_cancel — task_cancel reason ZORUNLU), superyorizon (takılan işi zorla kapatır), iptal sebep disiplini, flushPendingReports |
126
- | \`agent/tools.js\` | Araçlar + web_search SIRALI ZİNCİRİ (searchChain: 1 dahili tarayıcı(Google) → 2 Obscura(stealth→DuckDuckGo) → 3 TinyFish(anahtarsa) → 4 python çoklu-motor; sıra Ayarlar→Web Arama'dan değişir), run_command, read/write_file, python_run, http_fetch, deep_search |
127
- | \`agent/llm.js\` | Sağlayıcı çağrısı + retry: ağ kopmasında 6 deneme + internet dönene kadar bekler; akış ortası kopmada kaldığı yerden devam |
128
- | \`agent/agentdefs.js\` | Özel ajan tanımları: %APPDATA%\beast\agents\\*.md (frontmatter: model/tools/steps/mode + prompt gövdesi); /agent ile bağlanır, run_background(agent:) ile paralel ajana verilir |
129
- | \`agent/obscura.js\` | Obscura: otomatik kurulum (GitHub releases, stealth zip) + DuckDuckGo html/lite arama ayrıştırıcı |
130
- | \`agent/bots.js\` | Bot CRUD, 5 haneli bot kodu, izolasyon; ad-ilk-harf doğrulaması |
131
- | \`agent/skills.js\` | Builtin tohumlar (help, email, python-web-search, pdf, gold-trading) + SKILL.md taraması + taslaklar |
132
- | \`agent/memory.js\`, \`agent/kb.js\` | Kalıcı hafıza + bilgi bankası |
133
- | \`cron.js\` (src kökü), \`agent/watchers.js\`, \`agent/bus.js\` | Cron, izleyici, olay merkezi — /stop bunlara DOKUNMAZ |
134
- | \`agent/usage.js\` | Token/maliyet sayaçları |
135
- | \`renderer/\` | Arayüz: renderer.js (tüm paneller), i18n.js (TR/EN), index.html, style.css |
136
- | \`bin/beast-agent.js\` | CLI: \`beast\` (başlat), \`beast update\`, \`beast uninstall\` |
137
-
138
- ## SLASH KOMUTLARI
139
- /help · /version · /new · /open <kod> · /sessions · /stop · /start · /restart · /change [n] · /model <isim> · /think 0-5 · /clear · /notes · /rule <metin> · /rules · /notify on|off · /screenshot · /approve [always] · /deny · /update [now] · /usage · /backup · /status · /skills
140
-
141
- ## AYAR SEKMELERİ
142
- Provider · Fallout · Skills · Paralel Ajanlar · TTS · E-posta · Entegrasyonlar · Web Arama (Obscura kurulum + arama sırası) · Olaylar · Cron · Maliyet · Loglar · Panel · Limitler · Güvenlik · Güncelleme
143
-
144
- ## PARALEL AJAN HIZLI BİLGİ
145
- - CEO modu: konuşan ajan iş YAPMAZ, run_background/run_background_many ile devreder; görev tanımı kendine yeterli olmalı (ajan CEO bağlamını GÖREMEZ).
146
- - İş bitince birleşik/bireysel rapor OTOMATİK üst sohbete düşer — takılı iş superyorizon tarafından SEBEPİYLE zorla kapatılır.
147
- - Canlı izleme: tasks_list/task_status; iptal: task_cancel (reason zorunlu, kullanıcıya sebep yazılır).
148
-
149
- ## SAĞLIK KONTROLLERİ
150
- - "Model yapılandırılmadı" → config.yaml/.env dolu mu; Ayarlar → Provider'dan seçim.
151
- - Mail hatası → \`email\` skill'ini oku.
152
- - 401 → Fallout zinciri sıradaki slota otomatik düşer.
153
- - DDG boş/engelli → normaldir; zincir Obscura/Bing/Mojeek'e otomatik geçer.
154
-
155
- ## DAVRANIŞ KURALLARI
156
- - Şifre/API anahtarlarını asla düz metin loglama/tekrarlama.
157
- - settings.json'a el ile yazarken app kapalı olsun.
158
- - Yeni yetenek gerekiyorsa \`%APPDATA%\\beast\\skills\\<ad>\\SKILL.md\` oluşturmayı öner.
159
- - Bu haritadaki yolları ÖNCE dene; kod taraması yalnız haritada olmayan derin sorularda.`,
160
- },
161
- {
162
- folder: 'email',
163
- body: `---
164
- name: email
165
- 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.
166
- version: 1.1.0
167
- ---
168
-
169
- # E-posta Entegrasyonu (IMAP/SMTP)
170
-
171
- ## Credential NEREYE Girilir?
172
-
173
- İki yol var:
174
-
175
- 1. **UI:** Ayarlar (⚙) → **E-posta** sekmesi → alanları doldur → "E-posta Kaydet". Kaydettiği yer: \`%APPDATA%\\beast\\settings.json\` içindeki \`"email"\` bloğu.
176
- 2. **Dosya:** aynı bloğu elle düzenle:
177
-
178
- \`\`\`json
179
- "email": {
180
- "host": "imap.gmail.com",
181
- "port": 993,
182
- "user": "adres@gmail.com",
183
- "pass": "uygulama sifresi (16 hane)",
184
- "smtpHost": "smtp.gmail.com",
185
- "smtpPort": 465
186
- }
187
- \`\`\`
188
-
189
- ÖNEMLİ:
190
- - \`pass\` normal Gmail şifresi DEĞİL — Google Hesabı → Güvenlik → "Uygulama Şifreleri"nden üretilen 16 haneli şifre (2FA açık olmalı).
191
- - UI'da şifre alanı \`***\` maskeli gelir; alanı BOŞ bırakıp kaydetmek mevcut şifreyi KORUR. Yeni şifre girmek için alanı doldur.
192
- - SMTP port: 465 (implicit TLS) veya 587 (STARTTLS) — ikisi de desteklenir, kod otomatik seçer.
193
- - 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.
194
- - settings.json bozulursa app otomatik olarak \`settings.backup.json\`'dan kurtarır; veri kaybı olmaz.
195
-
196
- ## Araçlar
197
-
198
- - \`email_list\` → \`{ "limit": 10, "unread": false }\` — son mailler: uid, from, subject, date döner.
199
- - \`email_read\` → \`{ "uid": 123 }\` — uid'nin tam gövde metnini getirir (önce email_list çağır).
200
- - \`email_send\` → \`{ "to": "a@b.com", "subject": "...", "body": "düz metin" }\`
201
-
202
- ## Hata Giderme
203
-
204
- | Belirti | Sebep / Çözüm |
205
- |---|---|
206
- | \`e-posta ayarlanmamış\` | Credential girilmemiş YA DA settings.json elle düzenlendi ama restart yapılmadı → önce restart öner |
207
- | \`Invalid credentials\` / auth hatası | Uygulama şifresi yanlış/silinmiş → yeni üret |
208
- | \`imap modülü yok\` | node_modules bozuk → \`npm i imapflow\` (repo kökünde) |
209
- | Timeout | Ağ/port sorunu; 993 (IMAP) ve 465 veya 587 (SMTP) erişilebilir mi bak |
210
-
211
- Gönderim (nodemailer) takılırsa son çare PowerShell fallback:
212
-
213
- \`\`\`powershell
214
- $sec = ConvertTo-SecureString 'UYGULAMA_SIFRESI' -AsPlainText -Force
215
- $cred = New-Object System.Management.Automation.PSCredential('adres@gmail.com', $sec)
216
- Send-MailMessage -SmtpServer smtp.gmail.com -Port 587 -UseSsl -From 'adres@gmail.com' -To 'ALICI' -Subject 'Konu' -Body 'Metin' -Credential $cred
217
- \`\`\`
218
-
219
- Şifreyi asla log'a, sohbet çıktısına veya kullanıcıya düz metin olarak gösterme.`,
220
- },
221
- {
222
- folder: 'python-web-search',
223
- force: true,
224
- body: `---
225
- name: python-web-search
226
- 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) Obscura (stealth headless → DuckDuckGo; otomatik kurulur, varsayılan AKTİF) 3) TinyFish (anahtar varsa) 4) python çoklu-motor (ddgs / DDG+Bing+Mojeek, anahtarsız). Sıra Ayarlar → Web Arama'dan değiştirilir. Takılmama kuralları içerir.
227
- version: 1.4.0
228
- ---
229
-
230
- # Web Arama Zinciri (VARSAYILAN)
231
-
232
- web_search aracı çağrıldığında zincir OTOMATİK çalışır — elle motor seçmen gerekmez. Sıra Ayarlar → Web Arama sekmesinden değiştirilebilir:
233
-
234
- ## 1. Dahili tarayıcı (ilk tercih — Google AI)
235
-
236
- 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. CAPTCHA/trafik uyarısı gelirse tarayıcı 10 dk atlanır, sıradaki motor devreye girer.
237
-
238
- ## 2. Obscura (stealth headless — DuckDuckGo)
239
-
240
- Rust tabanlı gizli tarayıcı: anti-detect parmak izi + V8 JS. Beast Agent PAKETİNDE hazır gelir — ilk açılışta %APPDATA%\\beast\\obscura'ya açılır (indirme yok). Varsayılan AKTİF — tarayıcı engellenirse bot korumasını aşarak DuckDuckGo'dan sonuç getirir.
241
-
242
- ## 3. TinyFish API (anahtar varsa)
243
-
244
- Ayarlar → Web Arama'dan anahtar girildiyse kendi sırasında devreye girer; anahtar yoksa otomatik atlanır.
245
-
246
- ## 4. Python çoklu-motor
247
-
248
- 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.
249
-
250
- \`\`\`
251
- web_search {"query": "sorgu", "max_results": 6}
252
- \`\`\`
253
-
254
- - Çoklu sorgu gerekiyorsa: aynı turda birden ÇOK web_search çağrısını PARALEL ver — toplam süre tek arama kadar olur.
255
- - Sonuç listesinden 1-2 URL'nin içeriğini okumak için http_fetch kullan.
256
-
257
- ## Toplu / derin işler (python_run)
258
-
259
- - Haber başlıkları: python_run script=news.py args ["--limit","8","--json"]
260
- - 3+ sorguyu tek betikte taramak için:
261
-
262
- \`\`\`python
263
- # python_run code: çoklu sorgu tek seferde
264
- import subprocess, sys, os
265
- script = os.path.join(os.environ.get('APPDATA', ''), 'beast', 'scripts', 'websearch.py')
266
- for q in ["sorgu 1", "sorgu 2", "sorgu 3"]:
267
- subprocess.run([sys.executable, script, q, "--limit", "4", "--json"])
268
- \`\`\`
269
-
270
- ## TAKILMAMA KURALLARI (ZORUNLU)
271
-
272
- - 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.
273
- - Kapalı/gizli içerik (Instagram gizli hesap, private profil, login arkası veri) ARANMAZ — bulunamayacağı belliyse hemen alternatif kaynağa/geçişe geç.
274
- - DDG boş/engelli dönerse "DuckDuckGo kapandı" DEME: bot koruması devrededir (~1 saatte kendiliğinden açılır) ve zincir otomatik olarak Obscura/Bing/Mojeek/tarayıcıya geçer — bu normal çalışmadır.
275
- - Araştırma hedefi: ~3 dakika, 3-5 kaynak. Derinleşme = zaman kaybı.
276
- - Motor hata verirse bir kez farklı sorguyla dene, sonra elindekiyle raporu kapat.`,
277
- },
278
- {
279
- folder: 'gold-trading',
280
- body: `---
281
- name: gold-trading
282
- description: Kullanıcının GOLD (FxPro MT5) işlem tercihleri — market structure kontrol listesi.
283
- version: 1.0.0
284
- ---
285
-
286
- # GOLD İşlem Tercihleri (Batuhan)
287
-
288
- Sembol: "GOLD" (XAUUSD değil), FxPro MT5 demo, M1.
289
-
290
- Zorunlu kurallar:
291
- - 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.
292
- - Geniş SL: ~4xATR; BE: ~2xATR kârda.
293
- - Yetki sende: açmadan önce sorma; SADECE sonucu (STOP/TP) raporla, giriş/trailing tiklerini anlatma.
294
- - AI komite yaklaşımı: sinyal yakalanır → görsel/teknik/risk analizi → açılış.`,
295
- },
296
- {
297
- folder: 'pdf',
298
- force: true,
299
- body: `---
300
- name: pdf
301
- 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.
302
- version: 1.1.0
303
- ---
304
-
305
- # PDF İşleme Rehberi
306
-
307
- ## EN KRİTİK KURALLAR (önce bunlar)
308
-
309
- 1. Türkçe karakterli PDF için pip\u2019ten paket KURMA/KULLANMA (fpdf, fpdf2, markdown-pdf, weasyprint, reportlab vb.) — bunlar ğ ş ı İ karakterlerini bozar. Doğru kit Node\u2019ta ZATEN kurulu: \`pdf-lib\` + \`@pdf-lib/fontkit\` ve \`pdfkit\`. Script\u2019i write_file ile .js yaz, run_command ile \`node script.js\` çalıştır (python_run İLE DEĞİL).
310
- 2. md→pdf çevirici YOKTUR ve kurulmaz — kullanıcıya rapor/özet/belge çıktısı vereceksen .md gönderme; aynı içeriği doğrudan PDF olarak ÜRET ve o dosyayı send_file ile gönder.
311
- 3. \`pdf-parse\` ile \`pdf-to-img\` aynı process\u2019e yüklenmez (aşağıdaki çakışma kuralı).
312
-
313
- Kurulu paketler ve rolleri:
314
-
315
- | İstek | Paket |
316
- |---|---|
317
- | Metin çıkar / oku | \`pdf-parse\` |
318
- | Yeni PDF üret / mevcut PDF'i değiştir | \`pdf-lib\` (+ \`@pdf-lib/fontkit\`) |
319
- | Düzenli belge üretimi (rapor, özet) | \`pdfkit\` |
320
- | Sayfa→PNG/JPG (taranmış/görsel PDF) | \`pdf-to-img\` |
321
- | Taranmış PDF'den metin (OCR) | \`pdf-to-img\` + \`tesseract.js\` |
322
-
323
- ## KRİTİK KURAL — aynı process çakışması
324
-
325
- \`pdf-parse\` ile \`pdf-to-img\` AYNI node process'inde yüklenirse şuna benzer hata alırsın:
326
- \`\`\`
327
- The API version "5.x.x" does not match the Worker version "5.y.y"
328
- \`\`\`
329
- Çözüm: her iş küçük BAĞIMSIZ bir script olsun; run_command ile ayrı ayrı çalıştır.
330
- Tek process'te sadece BİR pdfjs tabanlı paket kullan (\`read_file\` aracı pdf-parse kullanır — senin render scriptlerin bunu yüklememeli).
331
-
332
- ## 1. Okuma
333
-
334
- \`read_file\` aracı .pdf'i otomatik metne çevirir — önce onu dene. Elle script gerekirse:
335
-
336
- \`\`\`js
337
- // pdf-oku.js → node pdf-oku.js dosya.pdf
338
- const fs = require('fs');
339
- const { PDFParse } = require('pdf-parse');
340
- (async () => {
341
- const parser = new PDFParse({ data: new Uint8Array(fs.readFileSync(process.argv[2])) });
342
- try {
343
- const r = await parser.getText();
344
- console.log('sayfa=' + r.total);
345
- console.log(r.text);
346
- } finally { await parser.destroy(); }
347
- })();
348
- \`\`\`
349
-
350
- Metin boş geliyorsa TARANMIŞ görseldir → 4 ve 5. adımlara geç.
351
-
352
- ## 2. Oluşturma / Değiştirme (pdf-lib)
353
-
354
- 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:
355
-
356
- \`\`\`js
357
- // pdf-yaz.js → node pdf-yaz.js cikti.pdf
358
- const fs = require('fs');
359
- const { PDFDocument, rgb } = require('pdf-lib');
360
- const fontkit = require('@pdf-lib/fontkit');
361
- (async () => {
362
- const doc = await PDFDocument.create();
363
- doc.registerFontkit(fontkit);
364
- const trFont = await doc.embedFont(fs.readFileSync('C:/Windows/Fonts/arial.ttf'), { subset: true });
365
- const page = doc.addPage([595, 842]); // A4
366
- page.drawText('Ders Özeti\\n• Konu: Fonksiyonlar', { x: 50, y: 750, size: 14, font: trFont });
367
- fs.writeFileSync(process.argv[2], await doc.save());
368
- })();
369
- \`\`\`
370
-
371
- Mevcut PDF'i değiştirme / birleştirme:
372
-
373
- \`\`\`js
374
- const hedef = await PDFDocument.load(fs.readFileSync('a.pdf'));
375
- const kaynak = await PDFDocument.load(fs.readFileSync('b.pdf'));
376
- const pages = await hedef.copyPages(kaynak, kaynak.getPageIndices());
377
- pages.forEach((p) => hedef.addPage(p));
378
- fs.writeFileSync('birlesik.pdf', await hedef.save());
379
- \`\`\`
380
-
381
- Form doldurma: \`doc.getForm()\`, \`form.getTextField('Ad').setText(...)\`.
382
-
383
- ## 3. Düzenli belge üretimi (pdfkit)
384
-
385
- \`\`\`js
386
- const PDFDocument = require('pdfkit');
387
- const fs = require('fs');
388
- const doc = new PDFDocument({ size: 'A4', margins: { top: 60, bottom: 60, left: 60, right: 60 } });
389
- doc.pipe(fs.createWriteStream('rapor.pdf'));
390
- doc.font('C:/Windows/Fonts/arial.ttf').fontSize(14).text('YARIYIL RAPORU');
391
- doc.fontSize(11).text('Matematik: 90 Fizik: 78');
392
- doc.end();
393
- \`\`\`
394
-
395
- ## 4. Sayfa → Görsel (taranmış PDF / vision girişi)
396
-
397
- \`\`\`js
398
- // pdf-gorsel.js → node pdf-gorsel.js dosya.pdf [sayfaNo]
399
- const fs = require('fs');
400
- const { pdf } = require('pdf-to-img');
401
- (async () => {
402
- const doc = await pdf(process.argv[2], { scale: 2 });
403
- const n = Number(process.argv[3]) || 1;
404
- const png = await doc.getPage(n);
405
- fs.writeFileSync('sayfa-' + n + '.png', png);
406
- console.log('sayfa-' + n + '.png yazıldı (' + doc.length + ' sayfa toplam)');
407
- doc.destroy();
408
- })();
409
- \`\`\`
410
- @napi-rs/canvas hazır binary gelir — derleme gerektirmez.
411
-
412
- ## 5. OCR (taranmış PDF'den metin)
413
-
414
- Önce 4. adımla PNG üret, sonra:
415
-
416
- \`\`\`js
417
- // ocr.js → node ocr.js sayfa-1.png [dil=tur]
418
- const { createWorker } = require('tesseract.js');
419
- (async () => {
420
- const worker = await createWorker(process.argv[3] || 'tur');
421
- try {
422
- const { data } = await worker.recognize(process.argv[2]);
423
- console.log(data.text.trim());
424
- } finally { await worker.terminate(); }
425
- })();
426
- \`\`\`
427
- İlk çağrıda dil verisi (~15MB) internetten indirilir — sonra offline çalışır.
428
-
429
- ## Eğitim akışı (ders notu → özet/quiz)
430
-
431
- 1. Notu oku: \`read_file\`; boşsa taranmıştır → adım 4 + 5
432
- 2. Konuları/ayrım noktalarını çıkar, anahtar kavramları listele
433
- 3. Quiz/özeti 2. adım deseniniyle PDF olarak oluştur (Türkçe font ZORUNLU)
434
- 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.
435
-
436
- ## Hata giderme
437
-
438
- | Belirti | Çözüm |
439
- |---|---|
440
- | API version ... does not match Worker version ... | pdfjs tabanlı iki paket aynı process'te — ayrı script/process kullan |
441
- | ğ/ş/ı harfleri kayıp veya kutu kutu | standard font yerine C:/Windows/Fonts/arial.ttf embed et |
442
- | read_file boş metin döndü | taranmış PDF → 4. adım (görselleştir) + 5. adım (OCR) |
443
- | tesseract dil verisi hatası | ilk indirme internet ister; tekrar dene |`,
444
- },
445
- ];
446
-
447
- function writeSeed(seed) {
448
- try {
449
- const p = path.join(dir(), seed.folder);
450
- fs.mkdirSync(p, { recursive: true });
451
- fs.writeFileSync(path.join(p, 'SKILL.md'), seed.body);
452
- } catch {}
453
- }
454
-
455
- /* Kaldırılan eski tohumlar: kullanıcı makinesinden de silinir */
456
- const RETIRED_SEEDS = ['free-web-search'];
457
-
458
- function seedIfEmpty() {
459
- if (!scan().length) {
460
- for (const seed of SEEDS) writeSeed(seed);
461
- } else {
462
- /* force tohumlar her açılışta güncellenir: varsayılan web arama skill'inin
463
- içeriği hep güncel kalır (mevcut kurulum dahil) */
464
- for (const seed of SEEDS) {
465
- if (seed.force) writeSeed(seed);
466
- }
467
- }
468
- for (const name of RETIRED_SEEDS) {
469
- try { fs.rmSync(path.join(dir(), name), { recursive: true, force: true }); } catch {}
470
- }
471
- }
472
-
473
- /* ---------- taslaklar (deneyimden doğan skill adayları) ---------- */
474
-
475
- function draftsDir() {
476
- return path.join(dir(), '.drafts');
477
- }
478
-
479
- function slugify(s) {
480
- return (
481
- String(s || '')
482
- .toLowerCase()
483
- .replace(/[çğıöşü]/g, (c) => ({ ç: 'c', ğ: 'g', ı: 'i', ö: 'o', ş: 's', ü: 'u' }[c] || c))
484
- .replace(/[^a-z0-9]+/g, '-')
485
- .replace(/^-+|-+$/g, '')
486
- .slice(0, 40) || 'taslak'
487
- );
488
- }
489
-
490
- /* Yansımadan doğan taslağı kaydet; aynı isim varsa üzerine yazar (revize). */
491
- function addDraft({ name, description, body }) {
492
- try {
493
- const nm = String(name || '').trim() || 'yetenek';
494
- const folder = slugify(nm);
495
- const d = draftsDir();
496
- fs.mkdirSync(d, { recursive: true });
497
- const file = path.join(d, folder + '.draft.md');
498
- const text =
499
- `---\nname: ${nm}\ndescription: ${String(description || '').trim().slice(0, 160)}\nstatus: draft\ngeneratedAt: ${new Date().toISOString()}\n---\n\n` +
500
- String(body || '').trim() +
501
- '\n';
502
- fs.writeFileSync(file, text);
503
- return { ok: true, file };
504
- } catch (e) {
505
- return { ok: false, error: String((e && e.message) || e) };
506
- }
507
- }
508
-
509
- function listDrafts() {
510
- const out = [];
511
- let entries;
512
- try {
513
- entries = fs.readdirSync(draftsDir(), { withFileTypes: true });
514
- } catch {
515
- return out;
516
- }
517
- for (const e of entries) {
518
- if (!e.isFile() || !e.name.endsWith('.draft.md')) continue;
519
- const p = path.join(draftsDir(), e.name);
520
- try {
521
- const text = fs.readFileSync(p, 'utf8');
522
- const fm = parseFrontmatter(text);
523
- out.push({
524
- id: e.name.replace(/\.draft\.md$/, ''),
525
- name: fm.name || e.name,
526
- description: fm.description || '',
527
- generatedAt: fm.generatedAt || '',
528
- body: text,
529
- });
530
- } catch {}
531
- }
532
- return out;
533
- }
534
-
535
- function acceptDraft(id) {
536
- try {
537
- const src = path.join(draftsDir(), String(id) + '.draft.md');
538
- const destDir = path.join(dir(), slugify(String(id)));
539
- fs.mkdirSync(destDir, { recursive: true });
540
- let text = fs.readFileSync(src, 'utf8');
541
- /* status: draft satırını kaldır — artık kurulu skill */
542
- text = text.replace(/^status:\s*draft\r?\n/mi, '');
543
- fs.writeFileSync(path.join(destDir, 'SKILL.md'), text);
544
- fs.unlinkSync(src);
545
- return { ok: true, folder: path.basename(destDir), path: path.join(destDir, 'SKILL.md') };
546
- } catch (e) {
547
- return { ok: false, error: String((e && e.message) || e) };
548
- }
549
- }
550
-
551
- function dropDraft(id) {
552
- try {
553
- fs.unlinkSync(path.join(draftsDir(), String(id) + '.draft.md'));
554
- return { ok: true };
555
- } catch (e) {
556
- return { ok: false, error: String((e && e.message) || e) };
557
- }
558
- }
559
-
560
- /* OTOMATIK SKILL SİSTEMİ: yansımadan doğan prosedürü DOĞRUDAN kurulu skill olarak
561
- yazar (taslak onayı beklemeden). Aynı isimde skill varsa GÜNCELLENİR:
562
- eski içerik .bak'a alınır, created korunur, updated damgası vurulur. */
563
- function upsertSkill({ name, description, body }) {
564
- try {
565
- const nm = String(name || '').trim() || 'yetenek';
566
- const folder = slugify(nm);
567
- const d = path.join(dir(), folder);
568
- const file = path.join(d, 'SKILL.md');
569
- fs.mkdirSync(d, { recursive: true });
570
- let fm = `---\nname: ${nm}\ndescription: ${String(description || '').trim().slice(0, 160)}\n`;
571
- let updated = false;
572
- if (fs.existsSync(file)) {
573
- const old = fs.readFileSync(file, 'utf8');
574
- const oldFm = parseFrontmatter(old);
575
- fm += `created: ${oldFm.created || oldFm.generatedAt || new Date().toISOString()}\n`;
576
- fs.writeFileSync(file + '.bak', old);
577
- updated = true;
578
- }
579
- fm += `updated: ${new Date().toISOString()}\n---\n\n`;
580
- fs.writeFileSync(file, fm + String(body || '').trim() + '\n');
581
- return { ok: true, folder, file, updated };
582
- } catch (e) {
583
- return { ok: false, error: String((e && e.message) || e) };
584
- }
585
- }
586
-
587
- /* Kurulu bir skill'e madde ekler (#3 kural hattı): "## Kurallar" altına yazılır. */
588
- function appendRuleToSkill(nameOrFolder, ruleText) {
589
- try {
590
- const all = scan();
591
- const needle = String(nameOrFolder || '').trim().toLowerCase();
592
- const hit = all.find((s) => s.name.toLowerCase() === needle) ||
593
- all.find((s) => s.path.toLowerCase().includes(needle)) ||
594
- null;
595
- if (!hit) return { ok: false, error: `skill bulunamadı: ${nameOrFolder}` };
596
- let text = fs.readFileSync(hit.path, 'utf8');
597
- const line = `- ${String(ruleText || '').trim()}\n`;
598
- if (/^##\s*Kurallar/im.test(text)) {
599
- text = text.replace(/^(##\s*Kurallar\s*)$/im, `$1\n${line}`);
600
- } else {
601
- text = text.trimEnd() + `\n\n## Kurallar\n${line}`;
602
- }
603
- fs.writeFileSync(hit.path, text);
604
- return { ok: true, skill: hit.name, path: hit.path };
605
- } catch (e) {
606
- return { ok: false, error: String((e && e.message) || e) };
607
- }
608
- }
609
-
610
- module.exports = {
611
- dir,
612
- scan,
613
- seedIfEmpty,
614
- parseFrontmatter,
615
- addDraft,
616
- listDrafts,
617
- acceptDraft,
618
- dropDraft,
619
- appendRuleToSkill,
620
- upsertSkill,
621
- };
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
+ force: true,
65
+ body: `---
66
+ name: help
67
+ description: Beast Agent'ın İÇ HARİTASI — "kaç bot var", "oturumlar nerede", "x nasıl çalışır" gibi SİSTEM sorularını saniyeler içinde çözer: tüm veri yolları, hazır PowerShell/json one-liner'lar, kod haritası, slash komutları, ayar sekmeleri. Kod tabanını saatlerce taramadan bu haritadan oku.
68
+ version: 2.0.0
69
+ ---
70
+
71
+ # Beast Agent İç Haritası (self-help)
72
+
73
+ ## ALTIN KURAL
74
+ Sistem sorusu geldiğinde KOD TABANINI TARAMA. Aşağıdaki hazır yolu/one-liner'ı kullan — cevap 1-2 saniyede çıkar.
75
+
76
+ ## HIZLI CEVAPLAR (hazır komutlar)
77
+
78
+ **"Kaç bot var / botları listele"**
79
+ \`\`\`powershell
80
+ $b = Get-Content "$env:APPDATA\\beast\\bots.json" | ConvertFrom-Json; $b.bots | Select-Object id,name,admin,code
81
+ "Toplam bot: " + $b.bots.Count + " (admin: Beast)"
82
+ \`\`\`
83
+ (python_run ile: \`import json;d=json.load(open(r'%APPDATA%\\beast\\bots.json',encoding='utf-8'));print(len(d['bots']),[b['name'] for b in d['bots']])\`)
84
+
85
+ **"Kaç oturum var / sohbet geçmişi"**
86
+ \`\`\`powershell
87
+ (Get-ChildItem "$env:APPDATA\\beast\\sessions\\*.jsonl").Count
88
+ \`\`\`
89
+ Kod ↔ dosya eşleşmesi: jsonl'nin ilk satırı \`{"t":"meta","code":"ABC12"}\` taşır.
90
+
91
+ **"Paralel ajanlar ne durumda"** → \`tasks_list\` aracını ÇAĞIR (kod okumaya gerek yok) ya da:
92
+ \`\`\`powershell
93
+ Get-Content "$env:APPDATA\\beast\\sessions\\bg-jobs.json" | ConvertFrom-Json | % jobs | Select title,status,startedAt,error
94
+ \`\`\`
95
+
96
+ **"Maliyet/usage"** → \`/usage\` komutu ya da Ayarlar → Maliyet; kayıt dosyası \`usage.json\`.
97
+
98
+ **"Cron/izleyici var mı"** → \`cron.json\`, \`watchers.json\`.
99
+
100
+ ## DOSYA HARİTASI (%APPDATA%\\beast\\)
101
+
102
+ | Yol | Ne İşe Yarar |
103
+ |---|---|
104
+ | \`config.yaml\` + \`.env\` | Model sağlayıcıları + API anahtarları (\`providers.<id>.base_url/key_env/models\`, aktif: \`model.provider/default\`) |
105
+ | \`settings.json\` | App ayarları: theme, workspace, modelOverride, customProviders, roleModels, deletedModels, fallout, waTts, email, waAllow, **searchChain (web arama sırası)**, **obscuraEnabled**, activeBotId |
106
+ | \`sessions\\*.jsonl\` | Sohbet oturumları (satır JSON: t:meta kod, t:meta2 paralel-ajan işareti, t:msg mesaj, t:todo, t:notes) |
107
+ | \`sessions\\bg-jobs.json\` | Paralel ajan iş kayıtları: id, title, status(queued/running/done/error/aborted), error=İPTAL SEBEBİ, groupId |
108
+ | \`memories\\\` | SOUL.md (kişilik), MEMORY.md (uzun hafıza), USER.md (kullanıcı) |
109
+ | \`skills\\<ad>\\SKILL.md\` | Skill'ler (isim+açıklama prompt'a girer, gövde okunur) |
110
+ | \`bots.json\` | Bot kayıt defteri — max 5 bot, ilk kayıt admin 'Beast' (silinemez). Bot adı HARFLE başlamak ZORUNDA |
111
+ | \`bots/<id>/\` | Botun izole klasörü: config.json, memory.md, yetkiler.json, logs/ |
112
+ | \`whitelist.json\`, \`wa-auth\\\`, \`wa-chats.json\`, \`wa.log\` | WhatsApp izin listesi, Baileys auth (SİLME), sohbet eşlemesi, log |
113
+ | \`cron.json\`, \`watchers.json\`, \`bus.json\` | Zamanlanmış görevler, web/batarya izleyicileri, olay abonelikleri |
114
+ | \`scripts\\\` | Python scriptleri (websearch.py, news.py) |
115
+ | \`obscura\\obscura.exe\` | Obscura stealth tarayıcı (web arama zincirinin 2. motoru; açılışta otomatik kurulur) |
116
+ | \`py\\python.exe\` | Gömülü Python runtime (makinede Python olmasa bile python_run çalışır) |
117
+ | \`usage.json\`, \`logs\\\` | Kullanım sayaçları, çalışma logları |
118
+ | Masaüstü\\Beast-Backups | Şifreli yedekler (.beastbak) — /backup ile alınır |
119
+
120
+ ## KOD HARİTASI (src/)
121
+
122
+ | Dosya | Sorumluluk |
123
+ |---|---|
124
+ | \`main.js\` | Electron ana süreç: tüm IPC, WhatsApp/Telegram/Discord entegrasyonları, onay kapısı (/approve), /stop /restart, splash, gizli araştırma tarayıcısı, obscura kurulumu |
125
+ | \`agent/engine.js\` | OTURUM MOTORU: send/_run tur döngüsü, paralel ajanlar (run_background, run_background_many, tasks_list, task_status, task_cancel — task_cancel reason ZORUNLU), superyorizon (takılan işi zorla kapatır), iptal sebep disiplini, flushPendingReports |
126
+ | \`agent/tools.js\` | Araçlar + web_search SIRALI ZİNCİRİ (searchChain: 1 dahili tarayıcı(Google) → 2 Obscura(stealth→DuckDuckGo) → 3 TinyFish(anahtarsa) → 4 python çoklu-motor; sıra Ayarlar→Web Arama'dan değişir), run_command, read/write_file, python_run, http_fetch, deep_search |
127
+ | \`agent/llm.js\` | Sağlayıcı çağrısı + retry: ağ kopmasında 6 deneme + internet dönene kadar bekler; akış ortası kopmada kaldığı yerden devam |
128
+ | \`agent/agentdefs.js\` | Özel ajan tanımları: %APPDATA%\beast\agents\\*.md (frontmatter: model/tools/steps/mode + prompt gövdesi); /agent ile bağlanır, run_background(agent:) ile paralel ajana verilir |
129
+ | \`agent/obscura.js\` | Obscura: otomatik kurulum (GitHub releases, stealth zip) + DuckDuckGo html/lite arama ayrıştırıcı |
130
+ | \`agent/bots.js\` | Bot CRUD, 5 haneli bot kodu, izolasyon; ad-ilk-harf doğrulaması |
131
+ | \`agent/skills.js\` | Builtin tohumlar (help, email, python-web-search, pdf, gold-trading) + SKILL.md taraması + taslaklar |
132
+ | \`agent/memory.js\`, \`agent/kb.js\` | Kalıcı hafıza + bilgi bankası |
133
+ | \`cron.js\` (src kökü), \`agent/watchers.js\`, \`agent/bus.js\` | Cron, izleyici, olay merkezi — /stop bunlara DOKUNMAZ |
134
+ | \`agent/usage.js\` | Token/maliyet sayaçları |
135
+ | \`renderer/\` | Arayüz: renderer.js (tüm paneller), i18n.js (TR/EN), index.html, style.css |
136
+ | \`bin/beast-agent.js\` | CLI: \`beast\` (başlat), \`beast update\`, \`beast uninstall\` |
137
+
138
+ ## SLASH KOMUTLARI
139
+ /help · /version · /new · /open <kod> · /sessions · /stop · /start · /restart · /change [n] · /model <isim> · /think 0-5 · /clear · /notes · /rule <metin> · /rules · /notify on|off · /screenshot · /approve [always] · /deny · /update [now] · /usage · /backup · /status · /skills
140
+
141
+ ## AYAR SEKMELERİ
142
+ Provider · Fallout · Skills · Paralel Ajanlar · TTS · E-posta · Entegrasyonlar · Web Arama (Obscura kurulum + arama sırası) · Olaylar · Cron · Maliyet · Loglar · Panel · Limitler · Güvenlik · Güncelleme
143
+
144
+ ## PARALEL AJAN HIZLI BİLGİ
145
+ - CEO modu: konuşan ajan iş YAPMAZ, run_background/run_background_many ile devreder; görev tanımı kendine yeterli olmalı (ajan CEO bağlamını GÖREMEZ).
146
+ - İş bitince birleşik/bireysel rapor OTOMATİK üst sohbete düşer — takılı iş superyorizon tarafından SEBEPİYLE zorla kapatılır.
147
+ - Canlı izleme: tasks_list/task_status; iptal: task_cancel (reason zorunlu, kullanıcıya sebep yazılır).
148
+
149
+ ## SAĞLIK KONTROLLERİ
150
+ - "Model yapılandırılmadı" → config.yaml/.env dolu mu; Ayarlar → Provider'dan seçim.
151
+ - Mail hatası → \`email\` skill'ini oku.
152
+ - 401 → Fallout zinciri sıradaki slota otomatik düşer.
153
+ - DDG boş/engelli → normaldir; zincir Obscura/Bing/Mojeek'e otomatik geçer.
154
+
155
+ ## DAVRANIŞ KURALLARI
156
+ - Şifre/API anahtarlarını asla düz metin loglama/tekrarlama.
157
+ - settings.json'a el ile yazarken app kapalı olsun.
158
+ - Yeni yetenek gerekiyorsa \`%APPDATA%\\beast\\skills\\<ad>\\SKILL.md\` oluşturmayı öner.
159
+ - Bu haritadaki yolları ÖNCE dene; kod taraması yalnız haritada olmayan derin sorularda.`,
160
+ },
161
+ {
162
+ folder: 'email',
163
+ body: `---
164
+ name: email
165
+ 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.
166
+ version: 1.1.0
167
+ ---
168
+
169
+ # E-posta Entegrasyonu (IMAP/SMTP)
170
+
171
+ ## Credential NEREYE Girilir?
172
+
173
+ İki yol var:
174
+
175
+ 1. **UI:** Ayarlar (⚙) → **E-posta** sekmesi → alanları doldur → "E-posta Kaydet". Kaydettiği yer: \`%APPDATA%\\beast\\settings.json\` içindeki \`"email"\` bloğu.
176
+ 2. **Dosya:** aynı bloğu elle düzenle:
177
+
178
+ \`\`\`json
179
+ "email": {
180
+ "host": "imap.gmail.com",
181
+ "port": 993,
182
+ "user": "adres@gmail.com",
183
+ "pass": "uygulama sifresi (16 hane)",
184
+ "smtpHost": "smtp.gmail.com",
185
+ "smtpPort": 465
186
+ }
187
+ \`\`\`
188
+
189
+ ÖNEMLİ:
190
+ - \`pass\` normal Gmail şifresi DEĞİL — Google Hesabı → Güvenlik → "Uygulama Şifreleri"nden üretilen 16 haneli şifre (2FA açık olmalı).
191
+ - UI'da şifre alanı \`***\` maskeli gelir; alanı BOŞ bırakıp kaydetmek mevcut şifreyi KORUR. Yeni şifre girmek için alanı doldur.
192
+ - SMTP port: 465 (implicit TLS) veya 587 (STARTTLS) — ikisi de desteklenir, kod otomatik seçer.
193
+ - 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.
194
+ - settings.json bozulursa app otomatik olarak \`settings.backup.json\`'dan kurtarır; veri kaybı olmaz.
195
+
196
+ ## Araçlar
197
+
198
+ - \`email_list\` → \`{ "limit": 10, "unread": false }\` — son mailler: uid, from, subject, date döner.
199
+ - \`email_read\` → \`{ "uid": 123 }\` — uid'nin tam gövde metnini getirir (önce email_list çağır).
200
+ - \`email_send\` → \`{ "to": "a@b.com", "subject": "...", "body": "düz metin" }\`
201
+
202
+ ## Hata Giderme
203
+
204
+ | Belirti | Sebep / Çözüm |
205
+ |---|---|
206
+ | \`e-posta ayarlanmamış\` | Credential girilmemiş YA DA settings.json elle düzenlendi ama restart yapılmadı → önce restart öner |
207
+ | \`Invalid credentials\` / auth hatası | Uygulama şifresi yanlış/silinmiş → yeni üret |
208
+ | \`imap modülü yok\` | node_modules bozuk → \`npm i imapflow\` (repo kökünde) |
209
+ | Timeout | Ağ/port sorunu; 993 (IMAP) ve 465 veya 587 (SMTP) erişilebilir mi bak |
210
+
211
+ Gönderim (nodemailer) takılırsa son çare PowerShell fallback:
212
+
213
+ \`\`\`powershell
214
+ $sec = ConvertTo-SecureString 'UYGULAMA_SIFRESI' -AsPlainText -Force
215
+ $cred = New-Object System.Management.Automation.PSCredential('adres@gmail.com', $sec)
216
+ Send-MailMessage -SmtpServer smtp.gmail.com -Port 587 -UseSsl -From 'adres@gmail.com' -To 'ALICI' -Subject 'Konu' -Body 'Metin' -Credential $cred
217
+ \`\`\`
218
+
219
+ Şifreyi asla log'a, sohbet çıktısına veya kullanıcıya düz metin olarak gösterme.`,
220
+ },
221
+ {
222
+ folder: 'python-web-search',
223
+ force: true,
224
+ body: `---
225
+ name: python-web-search
226
+ 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) Obscura (stealth headless → DuckDuckGo; otomatik kurulur, varsayılan AKTİF) 3) TinyFish (anahtar varsa) 4) python çoklu-motor (ddgs / DDG+Bing+Mojeek, anahtarsız). Sıra Ayarlar → Web Arama'dan değiştirilir. Takılmama kuralları içerir.
227
+ version: 1.4.0
228
+ ---
229
+
230
+ # Web Arama Zinciri (VARSAYILAN)
231
+
232
+ web_search aracı çağrıldığında zincir OTOMATİK çalışır — elle motor seçmen gerekmez. Sıra Ayarlar → Web Arama sekmesinden değiştirilebilir:
233
+
234
+ ## 1. Dahili tarayıcı (ilk tercih — Google AI)
235
+
236
+ 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. CAPTCHA/trafik uyarısı gelirse tarayıcı 10 dk atlanır, sıradaki motor devreye girer.
237
+
238
+ ## 2. Obscura (stealth headless — DuckDuckGo)
239
+
240
+ Rust tabanlı gizli tarayıcı: anti-detect parmak izi + V8 JS. Beast Agent PAKETİNDE hazır gelir — ilk açılışta %APPDATA%\\beast\\obscura'ya açılır (indirme yok). Varsayılan AKTİF — tarayıcı engellenirse bot korumasını aşarak DuckDuckGo'dan sonuç getirir.
241
+
242
+ ## 3. TinyFish API (anahtar varsa)
243
+
244
+ Ayarlar → Web Arama'dan anahtar girildiyse kendi sırasında devreye girer; anahtar yoksa otomatik atlanır.
245
+
246
+ ## 4. Python çoklu-motor
247
+
248
+ 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.
249
+
250
+ \`\`\`
251
+ web_search {"query": "sorgu", "max_results": 6}
252
+ \`\`\`
253
+
254
+ - Çoklu sorgu gerekiyorsa: aynı turda birden ÇOK web_search çağrısını PARALEL ver — toplam süre tek arama kadar olur.
255
+ - Sonuç listesinden 1-2 URL'nin içeriğini okumak için http_fetch kullan.
256
+
257
+ ## Toplu / derin işler (python_run)
258
+
259
+ - Haber başlıkları: python_run script=news.py args ["--limit","8","--json"]
260
+ - 3+ sorguyu tek betikte taramak için:
261
+
262
+ \`\`\`python
263
+ # python_run code: çoklu sorgu tek seferde
264
+ import subprocess, sys, os
265
+ script = os.path.join(os.environ.get('APPDATA', ''), 'beast', 'scripts', 'websearch.py')
266
+ for q in ["sorgu 1", "sorgu 2", "sorgu 3"]:
267
+ subprocess.run([sys.executable, script, q, "--limit", "4", "--json"])
268
+ \`\`\`
269
+
270
+ ## TAKILMAMA KURALLARI (ZORUNLU)
271
+
272
+ - 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.
273
+ - Kapalı/gizli içerik (Instagram gizli hesap, private profil, login arkası veri) ARANMAZ — bulunamayacağı belliyse hemen alternatif kaynağa/geçişe geç.
274
+ - DDG boş/engelli dönerse "DuckDuckGo kapandı" DEME: bot koruması devrededir (~1 saatte kendiliğinden açılır) ve zincir otomatik olarak Obscura/Bing/Mojeek/tarayıcıya geçer — bu normal çalışmadır.
275
+ - Araştırma hedefi: ~3 dakika, 3-5 kaynak. Derinleşme = zaman kaybı.
276
+ - Motor hata verirse bir kez farklı sorguyla dene, sonra elindekiyle raporu kapat.`,
277
+ },
278
+ {
279
+ folder: 'gold-trading',
280
+ body: `---
281
+ name: gold-trading
282
+ description: Kullanıcının GOLD (FxPro MT5) işlem tercihleri — market structure kontrol listesi.
283
+ version: 1.0.0
284
+ ---
285
+
286
+ # GOLD İşlem Tercihleri (Batuhan)
287
+
288
+ Sembol: "GOLD" (XAUUSD değil), FxPro MT5 demo, M1.
289
+
290
+ Zorunlu kurallar:
291
+ - 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.
292
+ - Geniş SL: ~4xATR; BE: ~2xATR kârda.
293
+ - Yetki sende: açmadan önce sorma; SADECE sonucu (STOP/TP) raporla, giriş/trailing tiklerini anlatma.
294
+ - AI komite yaklaşımı: sinyal yakalanır → görsel/teknik/risk analizi → açılış.`,
295
+ },
296
+ {
297
+ folder: 'pdf',
298
+ force: true,
299
+ body: `---
300
+ name: pdf
301
+ 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.
302
+ version: 1.1.0
303
+ ---
304
+
305
+ # PDF İşleme Rehberi
306
+
307
+ ## EN KRİTİK KURALLAR (önce bunlar)
308
+
309
+ 1. Türkçe karakterli PDF için pip\u2019ten paket KURMA/KULLANMA (fpdf, fpdf2, markdown-pdf, weasyprint, reportlab vb.) — bunlar ğ ş ı İ karakterlerini bozar. Doğru kit Node\u2019ta ZATEN kurulu: \`pdf-lib\` + \`@pdf-lib/fontkit\` ve \`pdfkit\`. Script\u2019i write_file ile .js yaz, run_command ile \`node script.js\` çalıştır (python_run İLE DEĞİL).
310
+ 2. md→pdf çevirici YOKTUR ve kurulmaz — kullanıcıya rapor/özet/belge çıktısı vereceksen .md gönderme; aynı içeriği doğrudan PDF olarak ÜRET ve o dosyayı send_file ile gönder.
311
+ 3. \`pdf-parse\` ile \`pdf-to-img\` aynı process\u2019e yüklenmez (aşağıdaki çakışma kuralı).
312
+
313
+ Kurulu paketler ve rolleri:
314
+
315
+ | İstek | Paket |
316
+ |---|---|
317
+ | Metin çıkar / oku | \`pdf-parse\` |
318
+ | Yeni PDF üret / mevcut PDF'i değiştir | \`pdf-lib\` (+ \`@pdf-lib/fontkit\`) |
319
+ | Düzenli belge üretimi (rapor, özet) | \`pdfkit\` |
320
+ | Sayfa→PNG/JPG (taranmış/görsel PDF) | \`pdf-to-img\` |
321
+ | Taranmış PDF'den metin (OCR) | \`pdf-to-img\` + \`tesseract.js\` |
322
+
323
+ ## KRİTİK KURAL — aynı process çakışması
324
+
325
+ \`pdf-parse\` ile \`pdf-to-img\` AYNI node process'inde yüklenirse şuna benzer hata alırsın:
326
+ \`\`\`
327
+ The API version "5.x.x" does not match the Worker version "5.y.y"
328
+ \`\`\`
329
+ Çözüm: her iş küçük BAĞIMSIZ bir script olsun; run_command ile ayrı ayrı çalıştır.
330
+ Tek process'te sadece BİR pdfjs tabanlı paket kullan (\`read_file\` aracı pdf-parse kullanır — senin render scriptlerin bunu yüklememeli).
331
+
332
+ ## 1. Okuma
333
+
334
+ \`read_file\` aracı .pdf'i otomatik metne çevirir — önce onu dene. Elle script gerekirse:
335
+
336
+ \`\`\`js
337
+ // pdf-oku.js → node pdf-oku.js dosya.pdf
338
+ const fs = require('fs');
339
+ const { PDFParse } = require('pdf-parse');
340
+ (async () => {
341
+ const parser = new PDFParse({ data: new Uint8Array(fs.readFileSync(process.argv[2])) });
342
+ try {
343
+ const r = await parser.getText();
344
+ console.log('sayfa=' + r.total);
345
+ console.log(r.text);
346
+ } finally { await parser.destroy(); }
347
+ })();
348
+ \`\`\`
349
+
350
+ Metin boş geliyorsa TARANMIŞ görseldir → 4 ve 5. adımlara geç.
351
+
352
+ ## 2. Oluşturma / Değiştirme (pdf-lib)
353
+
354
+ 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:
355
+
356
+ \`\`\`js
357
+ // pdf-yaz.js → node pdf-yaz.js cikti.pdf
358
+ const fs = require('fs');
359
+ const { PDFDocument, rgb } = require('pdf-lib');
360
+ const fontkit = require('@pdf-lib/fontkit');
361
+ (async () => {
362
+ const doc = await PDFDocument.create();
363
+ doc.registerFontkit(fontkit);
364
+ const trFont = await doc.embedFont(fs.readFileSync('C:/Windows/Fonts/arial.ttf'), { subset: true });
365
+ const page = doc.addPage([595, 842]); // A4
366
+ page.drawText('Ders Özeti\\n• Konu: Fonksiyonlar', { x: 50, y: 750, size: 14, font: trFont });
367
+ fs.writeFileSync(process.argv[2], await doc.save());
368
+ })();
369
+ \`\`\`
370
+
371
+ Mevcut PDF'i değiştirme / birleştirme:
372
+
373
+ \`\`\`js
374
+ const hedef = await PDFDocument.load(fs.readFileSync('a.pdf'));
375
+ const kaynak = await PDFDocument.load(fs.readFileSync('b.pdf'));
376
+ const pages = await hedef.copyPages(kaynak, kaynak.getPageIndices());
377
+ pages.forEach((p) => hedef.addPage(p));
378
+ fs.writeFileSync('birlesik.pdf', await hedef.save());
379
+ \`\`\`
380
+
381
+ Form doldurma: \`doc.getForm()\`, \`form.getTextField('Ad').setText(...)\`.
382
+
383
+ ## 3. Düzenli belge üretimi (pdfkit)
384
+
385
+ \`\`\`js
386
+ const PDFDocument = require('pdfkit');
387
+ const fs = require('fs');
388
+ const doc = new PDFDocument({ size: 'A4', margins: { top: 60, bottom: 60, left: 60, right: 60 } });
389
+ doc.pipe(fs.createWriteStream('rapor.pdf'));
390
+ doc.font('C:/Windows/Fonts/arial.ttf').fontSize(14).text('YARIYIL RAPORU');
391
+ doc.fontSize(11).text('Matematik: 90 Fizik: 78');
392
+ doc.end();
393
+ \`\`\`
394
+
395
+ ## 4. Sayfa → Görsel (taranmış PDF / vision girişi)
396
+
397
+ \`\`\`js
398
+ // pdf-gorsel.js → node pdf-gorsel.js dosya.pdf [sayfaNo]
399
+ const fs = require('fs');
400
+ const { pdf } = require('pdf-to-img');
401
+ (async () => {
402
+ const doc = await pdf(process.argv[2], { scale: 2 });
403
+ const n = Number(process.argv[3]) || 1;
404
+ const png = await doc.getPage(n);
405
+ fs.writeFileSync('sayfa-' + n + '.png', png);
406
+ console.log('sayfa-' + n + '.png yazıldı (' + doc.length + ' sayfa toplam)');
407
+ doc.destroy();
408
+ })();
409
+ \`\`\`
410
+ @napi-rs/canvas hazır binary gelir — derleme gerektirmez.
411
+
412
+ ## 5. OCR (taranmış PDF'den metin)
413
+
414
+ Önce 4. adımla PNG üret, sonra:
415
+
416
+ \`\`\`js
417
+ // ocr.js → node ocr.js sayfa-1.png [dil=tur]
418
+ const { createWorker } = require('tesseract.js');
419
+ (async () => {
420
+ const worker = await createWorker(process.argv[3] || 'tur');
421
+ try {
422
+ const { data } = await worker.recognize(process.argv[2]);
423
+ console.log(data.text.trim());
424
+ } finally { await worker.terminate(); }
425
+ })();
426
+ \`\`\`
427
+ İlk çağrıda dil verisi (~15MB) internetten indirilir — sonra offline çalışır.
428
+
429
+ ## Eğitim akışı (ders notu → özet/quiz)
430
+
431
+ 1. Notu oku: \`read_file\`; boşsa taranmıştır → adım 4 + 5
432
+ 2. Konuları/ayrım noktalarını çıkar, anahtar kavramları listele
433
+ 3. Quiz/özeti 2. adım deseniniyle PDF olarak oluştur (Türkçe font ZORUNLU)
434
+ 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.
435
+
436
+ ## Hata giderme
437
+
438
+ | Belirti | Çözüm |
439
+ |---|---|
440
+ | API version ... does not match Worker version ... | pdfjs tabanlı iki paket aynı process'te — ayrı script/process kullan |
441
+ | ğ/ş/ı harfleri kayıp veya kutu kutu | standard font yerine C:/Windows/Fonts/arial.ttf embed et |
442
+ | read_file boş metin döndü | taranmış PDF → 4. adım (görselleştir) + 5. adım (OCR) |
443
+ | tesseract dil verisi hatası | ilk indirme internet ister; tekrar dene |`,
444
+ },
445
+ ];
446
+
447
+ function writeSeed(seed) {
448
+ try {
449
+ const p = path.join(dir(), seed.folder);
450
+ fs.mkdirSync(p, { recursive: true });
451
+ fs.writeFileSync(path.join(p, 'SKILL.md'), seed.body);
452
+ } catch {}
453
+ }
454
+
455
+ /* Kaldırılan eski tohumlar: kullanıcı makinesinden de silinir */
456
+ const RETIRED_SEEDS = ['free-web-search'];
457
+
458
+ function seedIfEmpty() {
459
+ if (!scan().length) {
460
+ for (const seed of SEEDS) writeSeed(seed);
461
+ } else {
462
+ /* force tohumlar her açılışta güncellenir: varsayılan web arama skill'inin
463
+ içeriği hep güncel kalır (mevcut kurulum dahil) */
464
+ for (const seed of SEEDS) {
465
+ if (seed.force) writeSeed(seed);
466
+ }
467
+ }
468
+ for (const name of RETIRED_SEEDS) {
469
+ try { fs.rmSync(path.join(dir(), name), { recursive: true, force: true }); } catch {}
470
+ }
471
+ }
472
+
473
+ /* ---------- taslaklar (deneyimden doğan skill adayları) ---------- */
474
+
475
+ function draftsDir() {
476
+ return path.join(dir(), '.drafts');
477
+ }
478
+
479
+ function slugify(s) {
480
+ return (
481
+ String(s || '')
482
+ .toLowerCase()
483
+ .replace(/[çğıöşü]/g, (c) => ({ ç: 'c', ğ: 'g', ı: 'i', ö: 'o', ş: 's', ü: 'u' }[c] || c))
484
+ .replace(/[^a-z0-9]+/g, '-')
485
+ .replace(/^-+|-+$/g, '')
486
+ .slice(0, 40) || 'taslak'
487
+ );
488
+ }
489
+
490
+ /* Yansımadan doğan taslağı kaydet; aynı isim varsa üzerine yazar (revize). */
491
+ function addDraft({ name, description, body }) {
492
+ try {
493
+ const nm = String(name || '').trim() || 'yetenek';
494
+ const folder = slugify(nm);
495
+ const d = draftsDir();
496
+ fs.mkdirSync(d, { recursive: true });
497
+ const file = path.join(d, folder + '.draft.md');
498
+ const text =
499
+ `---\nname: ${nm}\ndescription: ${String(description || '').trim().slice(0, 160)}\nstatus: draft\ngeneratedAt: ${new Date().toISOString()}\n---\n\n` +
500
+ String(body || '').trim() +
501
+ '\n';
502
+ fs.writeFileSync(file, text);
503
+ return { ok: true, file };
504
+ } catch (e) {
505
+ return { ok: false, error: String((e && e.message) || e) };
506
+ }
507
+ }
508
+
509
+ function listDrafts() {
510
+ const out = [];
511
+ let entries;
512
+ try {
513
+ entries = fs.readdirSync(draftsDir(), { withFileTypes: true });
514
+ } catch {
515
+ return out;
516
+ }
517
+ for (const e of entries) {
518
+ if (!e.isFile() || !e.name.endsWith('.draft.md')) continue;
519
+ const p = path.join(draftsDir(), e.name);
520
+ try {
521
+ const text = fs.readFileSync(p, 'utf8');
522
+ const fm = parseFrontmatter(text);
523
+ out.push({
524
+ id: e.name.replace(/\.draft\.md$/, ''),
525
+ name: fm.name || e.name,
526
+ description: fm.description || '',
527
+ generatedAt: fm.generatedAt || '',
528
+ body: text,
529
+ });
530
+ } catch {}
531
+ }
532
+ return out;
533
+ }
534
+
535
+ function acceptDraft(id) {
536
+ try {
537
+ const src = path.join(draftsDir(), String(id) + '.draft.md');
538
+ const destDir = path.join(dir(), slugify(String(id)));
539
+ fs.mkdirSync(destDir, { recursive: true });
540
+ let text = fs.readFileSync(src, 'utf8');
541
+ /* status: draft satırını kaldır — artık kurulu skill */
542
+ text = text.replace(/^status:\s*draft\r?\n/mi, '');
543
+ fs.writeFileSync(path.join(destDir, 'SKILL.md'), text);
544
+ fs.unlinkSync(src);
545
+ return { ok: true, folder: path.basename(destDir), path: path.join(destDir, 'SKILL.md') };
546
+ } catch (e) {
547
+ return { ok: false, error: String((e && e.message) || e) };
548
+ }
549
+ }
550
+
551
+ function dropDraft(id) {
552
+ try {
553
+ fs.unlinkSync(path.join(draftsDir(), String(id) + '.draft.md'));
554
+ return { ok: true };
555
+ } catch (e) {
556
+ return { ok: false, error: String((e && e.message) || e) };
557
+ }
558
+ }
559
+
560
+ /* OTOMATIK SKILL SİSTEMİ: yansımadan doğan prosedürü DOĞRUDAN kurulu skill olarak
561
+ yazar (taslak onayı beklemeden). Aynı isimde skill varsa GÜNCELLENİR:
562
+ eski içerik .bak'a alınır, created korunur, updated damgası vurulur. */
563
+ function upsertSkill({ name, description, body }) {
564
+ try {
565
+ const nm = String(name || '').trim() || 'yetenek';
566
+ const folder = slugify(nm);
567
+ const d = path.join(dir(), folder);
568
+ const file = path.join(d, 'SKILL.md');
569
+ fs.mkdirSync(d, { recursive: true });
570
+ let fm = `---\nname: ${nm}\ndescription: ${String(description || '').trim().slice(0, 160)}\n`;
571
+ let updated = false;
572
+ if (fs.existsSync(file)) {
573
+ const old = fs.readFileSync(file, 'utf8');
574
+ const oldFm = parseFrontmatter(old);
575
+ fm += `created: ${oldFm.created || oldFm.generatedAt || new Date().toISOString()}\n`;
576
+ fs.writeFileSync(file + '.bak', old);
577
+ updated = true;
578
+ }
579
+ fm += `updated: ${new Date().toISOString()}\n---\n\n`;
580
+ fs.writeFileSync(file, fm + String(body || '').trim() + '\n');
581
+ return { ok: true, folder, file, updated };
582
+ } catch (e) {
583
+ return { ok: false, error: String((e && e.message) || e) };
584
+ }
585
+ }
586
+
587
+ /* Kurulu bir skill'e madde ekler (#3 kural hattı): "## Kurallar" altına yazılır. */
588
+ function appendRuleToSkill(nameOrFolder, ruleText) {
589
+ try {
590
+ const all = scan();
591
+ const needle = String(nameOrFolder || '').trim().toLowerCase();
592
+ const hit = all.find((s) => s.name.toLowerCase() === needle) ||
593
+ all.find((s) => s.path.toLowerCase().includes(needle)) ||
594
+ null;
595
+ if (!hit) return { ok: false, error: `skill bulunamadı: ${nameOrFolder}` };
596
+ let text = fs.readFileSync(hit.path, 'utf8');
597
+ const line = `- ${String(ruleText || '').trim()}\n`;
598
+ if (/^##\s*Kurallar/im.test(text)) {
599
+ text = text.replace(/^(##\s*Kurallar\s*)$/im, `$1\n${line}`);
600
+ } else {
601
+ text = text.trimEnd() + `\n\n## Kurallar\n${line}`;
602
+ }
603
+ fs.writeFileSync(hit.path, text);
604
+ return { ok: true, skill: hit.name, path: hit.path };
605
+ } catch (e) {
606
+ return { ok: false, error: String((e && e.message) || e) };
607
+ }
608
+ }
609
+
610
+ module.exports = {
611
+ dir,
612
+ scan,
613
+ seedIfEmpty,
614
+ parseFrontmatter,
615
+ addDraft,
616
+ listDrafts,
617
+ acceptDraft,
618
+ dropDraft,
619
+ appendRuleToSkill,
620
+ upsertSkill,
621
+ };