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.
- package/.env.example +7 -0
- package/LICENSE +21 -0
- package/README.md +94 -0
- package/assets/app.ico +0 -0
- package/assets/tray.png +0 -0
- package/bin/beast-agent.js +23 -0
- package/config.example.yaml +26 -0
- package/eng.traineddata +0 -0
- package/package.json +101 -0
- package/scripts/swap-electron.js +40 -0
- package/src/agent/bus.js +384 -0
- package/src/agent/computeruse.js +200 -0
- package/src/agent/config.js +284 -0
- package/src/agent/engine.js +3118 -0
- package/src/agent/kb.js +123 -0
- package/src/agent/llm.js +219 -0
- package/src/agent/logger.js +90 -0
- package/src/agent/memory.js +383 -0
- package/src/agent/pdf.js +20 -0
- package/src/agent/scripts/__pycache__/websearch.cpython-312.pyc +0 -0
- package/src/agent/scripts/news.py +113 -0
- package/src/agent/scripts/websearch.py +225 -0
- package/src/agent/skills.js +522 -0
- package/src/agent/tokens.js +39 -0
- package/src/agent/tools.js +911 -0
- package/src/agent/usage.js +125 -0
- package/src/agent/watchers.js +312 -0
- package/src/agent/watext.js +75 -0
- package/src/agent/whatsapp.js +494 -0
- package/src/cron.js +245 -0
- package/src/main.js +3622 -0
- package/src/preload.js +122 -0
- package/src/renderer/browserPreload.js +73 -0
- package/src/renderer/i18n.js +757 -0
- package/src/renderer/index.html +227 -0
- package/src/renderer/renderer.js +3250 -0
- package/src/renderer/style.css +1559 -0
- package/tests/approval.test.js +56 -0
- package/tests/bg-jobs.test.js +315 -0
- package/tests/bus.test.js +46 -0
- package/tests/computeruse-context.test.js +69 -0
- package/tests/cron.test.js +95 -0
- package/tests/engine.test.js +140 -0
- package/tests/eval.test.js +91 -0
- package/tests/fallout.test.js +188 -0
- package/tests/llm.test.js +56 -0
- package/tests/memory.test.js +33 -0
- package/tests/memoryloop.test.js +26 -0
- package/tests/notegen.test.js +63 -0
- package/tests/owner.test.js +32 -0
- package/tests/python.test.js +75 -0
- package/tests/scenarios.json +81 -0
- package/tests/sessioncode.test.js +50 -0
- package/tests/setup.js +9 -0
- package/tests/tokens.test.js +42 -0
- package/tests/tools.test.js +84 -0
- package/tests/usage.test.js +44 -0
- package/tests/v11.test.js +76 -0
- package/tests/watchers.test.js +206 -0
- package/tests/watext.test.js +53 -0
- package/tests/whatsapp.test.js +103 -0
- 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 };
|