beast-agent 1.8.0 → 2.0.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/LICENSE +21 -21
- package/README.md +130 -128
- package/bin/beast-agent.js +137 -137
- package/package.json +1 -1
- package/scripts/fix-electron.js +138 -138
- package/scripts/release.js +137 -131
- package/scripts/swap-electron.js +40 -40
- package/src/agent/agentdefs.js +122 -122
- package/src/agent/bots.js +580 -580
- package/src/agent/bus.js +389 -389
- package/src/agent/computeruse.js +200 -200
- package/src/agent/config.js +284 -284
- package/src/agent/discord.js +332 -332
- package/src/agent/engine.js +85 -11
- package/src/agent/kb.js +123 -123
- package/src/agent/llm.js +430 -430
- package/src/agent/logger.js +90 -90
- package/src/agent/mcp.js +427 -0
- package/src/agent/mem0.js +605 -605
- package/src/agent/memory.js +427 -427
- package/src/agent/mqueue.js +124 -124
- package/src/agent/pdf.js +20 -20
- package/src/agent/research.js +133 -133
- package/src/agent/scripts/news.py +113 -113
- package/src/agent/scripts/stealthsearch.py +30 -30
- package/src/agent/scripts/websearch.py +225 -225
- package/src/agent/searxng.js +325 -325
- package/src/agent/seeds/brainstorming/SKILL.md +90 -90
- package/src/agent/seeds/dispatching-parallel-agents/SKILL.md +120 -120
- package/src/agent/seeds/executing-plans/SKILL.md +60 -60
- package/src/agent/seeds/subagent-driven-development/SKILL.md +167 -167
- package/src/agent/seeds/systematic-debugging/SKILL.md +131 -131
- package/src/agent/seeds/test-driven-development/SKILL.md +152 -152
- package/src/agent/seeds/verification-before-completion/SKILL.md +63 -63
- package/src/agent/seeds/writing-plans/SKILL.md +162 -162
- package/src/agent/seeds/writing-skills/SKILL.md +229 -229
- package/src/agent/skills.js +652 -651
- package/src/agent/store.js +378 -378
- package/src/agent/telegram.js +155 -155
- package/src/agent/tokens.js +39 -39
- package/src/agent/usage.js +125 -125
- package/src/agent/watchers.js +312 -312
- package/src/agent/watext.js +80 -80
- package/src/agent/whatsapp.js +555 -555
- package/src/cron.js +255 -255
- package/src/main.js +385 -6
- package/src/preload.js +13 -0
- package/src/renderer/browserPreload.js +73 -73
- package/src/renderer/i18n.js +38 -0
- package/src/renderer/index.html +448 -407
- package/src/renderer/renderer.js +897 -42
- package/src/renderer/style.css +275 -11
package/src/agent/store.js
CHANGED
|
@@ -1,378 +1,378 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/* SKILLS STORE: topluluk skill mağazası.
|
|
4
|
-
- Yerel DB : %APPDATA%\beast\store\skills.json (yüklenen skill'ler, dosyalar gömülü)
|
|
5
|
-
- İstatistik DB : %APPDATA%\beast\store\stats.json (installs/likes — makine başına)
|
|
6
|
-
- Topluluk indeksi: GitHub repo /store/skills.json (raw fetch + offline cache)
|
|
7
|
-
- Kurulum hedefi : %APPDATA%\beast\skills\<id>\ (skills.scan() otomatik görür)
|
|
8
|
-
Trending/Stars sıralaması renderer'da skorla yapılır; burada ham veri döner.
|
|
9
|
-
Beast Kodu ASLA paylaşılmaz — yalnız sha256 türevi parmak izi (beastId) kullanılır. */
|
|
10
|
-
|
|
11
|
-
const fs = require('fs');
|
|
12
|
-
const path = require('path');
|
|
13
|
-
const crypto = require('crypto');
|
|
14
|
-
const { beastRoot } = require('./memory');
|
|
15
|
-
const skills = require('./skills');
|
|
16
|
-
|
|
17
|
-
const COMMUNITY_URL = 'https://raw.githubusercontent.com/algokodcom/beast-agent/main/store/skills.json';
|
|
18
|
-
const MAX_FILES = 20;
|
|
19
|
-
const MAX_FILE_BYTES = 200 * 1024;
|
|
20
|
-
const MAX_TOTAL_BYTES = 1024 * 1024;
|
|
21
|
-
const ALLOWED_EXT = new Set(['.md', '.py', '.js', '.ts', '.ps1', '.json', '.yaml', '.yml', '.txt', '.csv', '.sh', '.bat', '.cmd', '.html', '.css']);
|
|
22
|
-
const SKIP_DIRS = new Set(['node_modules', '.git', '__pycache__', '.drafts']);
|
|
23
|
-
|
|
24
|
-
function storeDir() {
|
|
25
|
-
return path.join(beastRoot(), 'store');
|
|
26
|
-
}
|
|
27
|
-
function dbFile() {
|
|
28
|
-
return path.join(storeDir(), 'skills.json');
|
|
29
|
-
}
|
|
30
|
-
function statsFile() {
|
|
31
|
-
return path.join(storeDir(), 'stats.json');
|
|
32
|
-
}
|
|
33
|
-
function communityCacheFile() {
|
|
34
|
-
return path.join(storeDir(), 'community.json');
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function readJson(p, fallback) {
|
|
38
|
-
try {
|
|
39
|
-
return JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
40
|
-
} catch {
|
|
41
|
-
return fallback;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function writeJson(p, obj) {
|
|
46
|
-
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
47
|
-
fs.writeFileSync(p, JSON.stringify(obj, null, 2));
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function loadLocal() {
|
|
51
|
-
const db = readJson(dbFile(), { version: 1, skills: [] });
|
|
52
|
-
if (!Array.isArray(db.skills)) db.skills = [];
|
|
53
|
-
return db;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function saveLocal(db) {
|
|
57
|
-
writeJson(dbFile(), db);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function loadStats() {
|
|
61
|
-
const s = readJson(statsFile(), {});
|
|
62
|
-
return s && typeof s === 'object' ? s : {};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function saveStats(s) {
|
|
66
|
-
writeJson(statsFile(), s);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/* Beast Kodu → genel parmak izi (benzersiz kimlik; gerçek kod saklanmaz) */
|
|
70
|
-
function beastFingerprint(beastCode) {
|
|
71
|
-
const h = crypto.createHash('sha256').update(String(beastCode || 'beast')).digest('hex');
|
|
72
|
-
return 'BEAST-' + h.slice(0, 8).toUpperCase();
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function slugify(s) {
|
|
76
|
-
return (
|
|
77
|
-
String(s || '')
|
|
78
|
-
.toLowerCase()
|
|
79
|
-
.replace(/[çğıöşü]/g, (c) => ({ ç: 'c', ğ: 'g', ı: 'i', ö: 'o', ş: 's', ü: 'u' }[c] || c))
|
|
80
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
81
|
-
.replace(/^-+|-+$/g, '')
|
|
82
|
-
.slice(0, 40) || 'skill'
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/* ---------- topluluk indeksi ---------- */
|
|
87
|
-
|
|
88
|
-
async function fetchCommunity(timeoutMs = 6000) {
|
|
89
|
-
try {
|
|
90
|
-
const ctl = typeof AbortController !== 'undefined' ? new AbortController() : null;
|
|
91
|
-
const t = setTimeout(() => {
|
|
92
|
-
try {
|
|
93
|
-
ctl && ctl.abort();
|
|
94
|
-
} catch {}
|
|
95
|
-
}, timeoutMs);
|
|
96
|
-
const r = await fetch(COMMUNITY_URL, { signal: ctl ? ctl.signal : undefined });
|
|
97
|
-
clearTimeout(t);
|
|
98
|
-
if (!r.ok) throw new Error('HTTP ' + r.status);
|
|
99
|
-
const j = await r.json();
|
|
100
|
-
if (j && Array.isArray(j.skills)) {
|
|
101
|
-
try {
|
|
102
|
-
writeJson(communityCacheFile(), j);
|
|
103
|
-
} catch {}
|
|
104
|
-
return j.skills;
|
|
105
|
-
}
|
|
106
|
-
return [];
|
|
107
|
-
} catch {
|
|
108
|
-
/* offline: son iyi kopyadan devam */
|
|
109
|
-
const cached = readJson(communityCacheFile(), { skills: [] });
|
|
110
|
-
return Array.isArray(cached.skills) ? cached.skills : [];
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function mergeStats(entry, stats) {
|
|
115
|
-
const st = stats[entry.id] || {};
|
|
116
|
-
return {
|
|
117
|
-
...entry,
|
|
118
|
-
installs: (entry.installs || 0) + (st.installs || 0),
|
|
119
|
-
likes: (entry.likes || 0) + (st.likes || 0),
|
|
120
|
-
liked: !!st.liked,
|
|
121
|
-
installedAt: st.installedAt || null,
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/* Birleşik liste: topluluk + yerel (aynı id'de yerel kazanır) */
|
|
126
|
-
async function list(beastCode) {
|
|
127
|
-
const [community] = await Promise.all([fetchCommunity()]);
|
|
128
|
-
const db = loadLocal();
|
|
129
|
-
const stats = loadStats();
|
|
130
|
-
const byId = new Map();
|
|
131
|
-
for (const e of community) {
|
|
132
|
-
if (e && e.id && e.files && e.files['SKILL.md']) byId.set(e.id, e);
|
|
133
|
-
}
|
|
134
|
-
for (const e of db.skills) if (e && e.id) byId.set(e.id, e);
|
|
135
|
-
const entries = [...byId.values()].map((e) => mergeStats(e, stats));
|
|
136
|
-
const installed = new Set(skills.scan().map((s) => path.basename(path.dirname(s.path))));
|
|
137
|
-
return {
|
|
138
|
-
ok: true,
|
|
139
|
-
beastId: beastFingerprint(beastCode),
|
|
140
|
-
entries,
|
|
141
|
-
installed: [...installed],
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/* Kullanıcı adı benzersizliği: aynı ad başka beastId'de kayıtlıysa alınmıştır */
|
|
146
|
-
function usernameTaken(username, beastId) {
|
|
147
|
-
const want = String(username || '').toLowerCase();
|
|
148
|
-
if (!want) return false;
|
|
149
|
-
const db = loadLocal();
|
|
150
|
-
for (const e of db.skills) {
|
|
151
|
-
const a = e.author || {};
|
|
152
|
-
if (String(a.username || '').toLowerCase() === want && a.beastId !== beastId) return true;
|
|
153
|
-
}
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/* ---------- yükleme ---------- */
|
|
158
|
-
|
|
159
|
-
/* Klasör önizleme (commit etmeden önce): SKILL.md frontmatter'ı + dosya dökümü */
|
|
160
|
-
function preview(dirPath) {
|
|
161
|
-
try {
|
|
162
|
-
const p = String(dirPath || '');
|
|
163
|
-
const skillMd = path.join(p, 'SKILL.md');
|
|
164
|
-
if (!fs.existsSync(skillMd)) return { ok: false, error: 'SKILL.md bulunamadı — skill klasörü SKILL.md içermeli' };
|
|
165
|
-
const text = fs.readFileSync(skillMd, 'utf8');
|
|
166
|
-
const fm = skills.parseFrontmatter(text);
|
|
167
|
-
const files = [];
|
|
168
|
-
let total = 0;
|
|
169
|
-
const walk = (d, depth) => {
|
|
170
|
-
if (depth > 3) return;
|
|
171
|
-
let rows;
|
|
172
|
-
try {
|
|
173
|
-
rows = fs.readdirSync(d, { withFileTypes: true });
|
|
174
|
-
} catch {
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
for (const e of rows) {
|
|
178
|
-
if (files.length >= MAX_FILES) return;
|
|
179
|
-
const full = path.join(d, e.name);
|
|
180
|
-
if (e.isDirectory()) {
|
|
181
|
-
if (!SKIP_DIRS.has(e.name)) walk(full, depth + 1);
|
|
182
|
-
continue;
|
|
183
|
-
}
|
|
184
|
-
const ext = path.extname(e.name).toLowerCase();
|
|
185
|
-
if (!ALLOWED_EXT.has(ext)) continue;
|
|
186
|
-
let size = 0;
|
|
187
|
-
try {
|
|
188
|
-
size = fs.statSync(full).size;
|
|
189
|
-
} catch {}
|
|
190
|
-
if (size > MAX_FILE_BYTES) continue;
|
|
191
|
-
total += size;
|
|
192
|
-
files.push({ rel: path.relative(p, full).replace(/\\/g, '/'), size });
|
|
193
|
-
}
|
|
194
|
-
};
|
|
195
|
-
walk(p, 0);
|
|
196
|
-
if (!files.some((f) => f.rel === 'SKILL.md')) files.unshift({ rel: 'SKILL.md', size: Buffer.byteLength(text, 'utf8') });
|
|
197
|
-
return {
|
|
198
|
-
ok: true,
|
|
199
|
-
path: p,
|
|
200
|
-
folderName: path.basename(p),
|
|
201
|
-
name: fm.name || path.basename(p),
|
|
202
|
-
description: fm.description || '',
|
|
203
|
-
version: fm.version || '1.0.0',
|
|
204
|
-
files,
|
|
205
|
-
totalBytes: total,
|
|
206
|
-
};
|
|
207
|
-
} catch (e) {
|
|
208
|
-
return { ok: false, error: String((e && e.message) || e) };
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/* Yüklemeyi yerel DB'ye işler; files utf8 metin olarak gömülür */
|
|
213
|
-
function commit({ dirPath, name, description, tags, author }) {
|
|
214
|
-
try {
|
|
215
|
-
const pv = preview(dirPath);
|
|
216
|
-
if (!pv.ok) return pv;
|
|
217
|
-
const nm = String(name || pv.name || '').trim().slice(0, 60);
|
|
218
|
-
if (!nm) return { ok: false, error: 'skill adı gerekli' };
|
|
219
|
-
const a = author || {};
|
|
220
|
-
const username = String(a.username || '').trim();
|
|
221
|
-
if (!/^[a-zA-Z0-9_-]{3,20}$/.test(username)) {
|
|
222
|
-
return { ok: false, error: 'kullanıcı adı 3-20 karakter (harf/rakam/_/-) olmalı' };
|
|
223
|
-
}
|
|
224
|
-
if (!a.avatar || String(a.avatar).length > 8) return { ok: false, error: 'avatar seç' };
|
|
225
|
-
if (!a.beastId) return { ok: false, error: 'beast kimliği yok' };
|
|
226
|
-
/* KAPAK RESMİ ZORUNLU: kare kırpılmış dataURL (renderer canvas ile üretir) */
|
|
227
|
-
const img = String((a.image || '') || '');
|
|
228
|
-
if (!/^data:image\/(png|jpeg|webp);base64,[A-Za-z0-9+/=]+$/.test(img)) {
|
|
229
|
-
return { ok: false, error: 'kapak resmi zorunlu — kare kırpılmış bir resim eklenmeli' };
|
|
230
|
-
}
|
|
231
|
-
if (img.length > 400 * 1024) return { ok: false, error: 'kapak resmi çok büyük (max ~300KB)' };
|
|
232
|
-
if (usernameTaken(username, a.beastId)) {
|
|
233
|
-
return { ok: false, error: `"${username}" kullanıcı adı başka bir Beast tarafından alınmış — başka ad seç` };
|
|
234
|
-
}
|
|
235
|
-
/* dosyaları oku */
|
|
236
|
-
const files = {};
|
|
237
|
-
let total = 0;
|
|
238
|
-
for (const f of pv.files) {
|
|
239
|
-
const raw = fs.readFileSync(path.join(dirPath, f.rel));
|
|
240
|
-
if (total + raw.length > MAX_TOTAL_BYTES) return { ok: false, error: 'paket çok büyük (max 1MB)' };
|
|
241
|
-
files[f.rel] = raw.toString('utf8');
|
|
242
|
-
total += raw.length;
|
|
243
|
-
}
|
|
244
|
-
if (!files['SKILL.md']) return { ok: false, error: 'SKILL.md okunamadı' };
|
|
245
|
-
const db = loadLocal();
|
|
246
|
-
const id = slugify(nm);
|
|
247
|
-
const now = new Date().toISOString();
|
|
248
|
-
const existing = db.skills.find((e) => e.id === id);
|
|
249
|
-
const entry = {
|
|
250
|
-
id,
|
|
251
|
-
name: nm,
|
|
252
|
-
description: String(description || pv.description || '').trim().slice(0, 200),
|
|
253
|
-
version: pv.version || '1.0.0',
|
|
254
|
-
tags: (Array.isArray(tags) ? tags : []).map((t) => String(t).trim().toLowerCase().slice(0, 20)).filter(Boolean).slice(0, 4),
|
|
255
|
-
author: { username, avatar: String(a.avatar), beastId: a.beastId },
|
|
256
|
-
image: img,
|
|
257
|
-
files,
|
|
258
|
-
installs: existing ? existing.installs || 0 : 0,
|
|
259
|
-
likes: existing ? existing.likes || 0 : 0,
|
|
260
|
-
createdAt: existing ? existing.createdAt : now,
|
|
261
|
-
updatedAt: now,
|
|
262
|
-
};
|
|
263
|
-
if (existing) db.skills.splice(db.skills.indexOf(existing), 1, entry);
|
|
264
|
-
else db.skills.push(entry);
|
|
265
|
-
saveLocal(db);
|
|
266
|
-
return { ok: true, entry: { ...entry, files: undefined, fileCount: Object.keys(files).length } };
|
|
267
|
-
} catch (e) {
|
|
268
|
-
return { ok: false, error: String((e && e.message) || e) };
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/* ---------- kurulum ---------- */
|
|
273
|
-
|
|
274
|
-
async function install(id) {
|
|
275
|
-
try {
|
|
276
|
-
const key = String(id || '');
|
|
277
|
-
if (!/^[a-z0-9_-]{1,40}$/i.test(key)) return { ok: false, error: 'geçersiz id' };
|
|
278
|
-
const db = loadLocal();
|
|
279
|
-
let entry = db.skills.find((e) => e.id === key);
|
|
280
|
-
if (!entry) {
|
|
281
|
-
const comm = await fetchCommunity();
|
|
282
|
-
entry = (comm || []).find((e) => e && e.id === key && e.files && e.files['SKILL.md']);
|
|
283
|
-
}
|
|
284
|
-
if (!entry) return { ok: false, error: 'skill bulunamadı' };
|
|
285
|
-
const dest = path.join(skills.dir(), key);
|
|
286
|
-
const stats = loadStats();
|
|
287
|
-
/* built-in koruması: store'dan hiç kurulmamış klasörün üstüne yazma */
|
|
288
|
-
if (fs.existsSync(dest) && !stats[key]) {
|
|
289
|
-
return { ok: false, error: 'aynı adda kurulu bir skill var — store kurulumu için ad farklı olmalı' };
|
|
290
|
-
}
|
|
291
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
292
|
-
for (const [rel, content] of Object.entries(entry.files || {})) {
|
|
293
|
-
const target = path.join(dest, rel);
|
|
294
|
-
if (!target.startsWith(dest)) continue; // path traversal koruması
|
|
295
|
-
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
296
|
-
fs.writeFileSync(target, String(content ?? ''), 'utf8');
|
|
297
|
-
}
|
|
298
|
-
stats[key] = stats[key] || { installs: 0, likes: 0, liked: false };
|
|
299
|
-
stats[key].installs = (stats[key].installs || 0) + 1;
|
|
300
|
-
stats[key].installedAt = new Date().toISOString();
|
|
301
|
-
saveStats(stats);
|
|
302
|
-
/* yerel yüklemeyse DB'deki sayaç da artsın */
|
|
303
|
-
const local = db.skills.find((e) => e.id === key);
|
|
304
|
-
if (local) {
|
|
305
|
-
local.installs = (local.installs || 0) + 1;
|
|
306
|
-
saveLocal(db);
|
|
307
|
-
}
|
|
308
|
-
return { ok: true, id: key, folder: dest };
|
|
309
|
-
} catch (e) {
|
|
310
|
-
return { ok: false, error: String((e && e.message) || e) };
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
function toggleLike(id) {
|
|
315
|
-
try {
|
|
316
|
-
const key = String(id || '');
|
|
317
|
-
if (!key) return { ok: false, error: 'id gerekli' };
|
|
318
|
-
const stats = loadStats();
|
|
319
|
-
stats[key] = stats[key] || { installs: 0, likes: 0, liked: false };
|
|
320
|
-
const st = stats[key];
|
|
321
|
-
if (st.liked) {
|
|
322
|
-
st.liked = false;
|
|
323
|
-
st.likes = Math.max(0, (st.likes || 0) - 1);
|
|
324
|
-
} else {
|
|
325
|
-
st.liked = true;
|
|
326
|
-
st.likes = (st.likes || 0) + 1;
|
|
327
|
-
}
|
|
328
|
-
saveStats(stats);
|
|
329
|
-
const db = loadLocal();
|
|
330
|
-
const local = db.skills.find((e) => e.id === key);
|
|
331
|
-
if (local) {
|
|
332
|
-
local.likes = Math.max(0, (local.likes || 0) + (st.liked ? 1 : -1));
|
|
333
|
-
saveLocal(db);
|
|
334
|
-
}
|
|
335
|
-
return { ok: true, liked: st.liked, likes: st.likes };
|
|
336
|
-
} catch (e) {
|
|
337
|
-
return { ok: false, error: String((e && e.message) || e) };
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/* kendi yüklediğini kaldırma (yalnız yerel kayıt + kendi beastId) */
|
|
342
|
-
function removeMine(id, beastId) {
|
|
343
|
-
try {
|
|
344
|
-
const db = loadLocal();
|
|
345
|
-
const e = db.skills.find((x) => x.id === String(id || ''));
|
|
346
|
-
if (!e) return { ok: false, error: 'yerel kayıt yok' };
|
|
347
|
-
if (!beastId || (e.author || {}).beastId !== beastId) return { ok: false, error: 'bu skill senin değil' };
|
|
348
|
-
db.skills.splice(db.skills.indexOf(e), 1);
|
|
349
|
-
saveLocal(db);
|
|
350
|
-
return { ok: true };
|
|
351
|
-
} catch (err) {
|
|
352
|
-
return { ok: false, error: String((err && err.message) || err) };
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/* Kendi paylaşımlarını GitHub'da paylaşmak için dışa aktarım (issue gövdesi) */
|
|
357
|
-
function exportEntry(id) {
|
|
358
|
-
try {
|
|
359
|
-
const db = loadLocal();
|
|
360
|
-
const e = db.skills.find((x) => x.id === String(id || ''));
|
|
361
|
-
if (!e) return { ok: false, error: 'yerel kayıt yok' };
|
|
362
|
-
return { ok: true, json: JSON.stringify({ version: 1, skills: [e] }, null, 2) };
|
|
363
|
-
} catch (err) {
|
|
364
|
-
return { ok: false, error: String((err && err.message) || err) };
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
module.exports = {
|
|
369
|
-
beastFingerprint,
|
|
370
|
-
list,
|
|
371
|
-
preview,
|
|
372
|
-
commit,
|
|
373
|
-
install,
|
|
374
|
-
toggleLike,
|
|
375
|
-
removeMine,
|
|
376
|
-
exportEntry,
|
|
377
|
-
usernameTaken,
|
|
378
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/* SKILLS STORE: topluluk skill mağazası.
|
|
4
|
+
- Yerel DB : %APPDATA%\beast\store\skills.json (yüklenen skill'ler, dosyalar gömülü)
|
|
5
|
+
- İstatistik DB : %APPDATA%\beast\store\stats.json (installs/likes — makine başına)
|
|
6
|
+
- Topluluk indeksi: GitHub repo /store/skills.json (raw fetch + offline cache)
|
|
7
|
+
- Kurulum hedefi : %APPDATA%\beast\skills\<id>\ (skills.scan() otomatik görür)
|
|
8
|
+
Trending/Stars sıralaması renderer'da skorla yapılır; burada ham veri döner.
|
|
9
|
+
Beast Kodu ASLA paylaşılmaz — yalnız sha256 türevi parmak izi (beastId) kullanılır. */
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const crypto = require('crypto');
|
|
14
|
+
const { beastRoot } = require('./memory');
|
|
15
|
+
const skills = require('./skills');
|
|
16
|
+
|
|
17
|
+
const COMMUNITY_URL = 'https://raw.githubusercontent.com/algokodcom/beast-agent/main/store/skills.json';
|
|
18
|
+
const MAX_FILES = 20;
|
|
19
|
+
const MAX_FILE_BYTES = 200 * 1024;
|
|
20
|
+
const MAX_TOTAL_BYTES = 1024 * 1024;
|
|
21
|
+
const ALLOWED_EXT = new Set(['.md', '.py', '.js', '.ts', '.ps1', '.json', '.yaml', '.yml', '.txt', '.csv', '.sh', '.bat', '.cmd', '.html', '.css']);
|
|
22
|
+
const SKIP_DIRS = new Set(['node_modules', '.git', '__pycache__', '.drafts']);
|
|
23
|
+
|
|
24
|
+
function storeDir() {
|
|
25
|
+
return path.join(beastRoot(), 'store');
|
|
26
|
+
}
|
|
27
|
+
function dbFile() {
|
|
28
|
+
return path.join(storeDir(), 'skills.json');
|
|
29
|
+
}
|
|
30
|
+
function statsFile() {
|
|
31
|
+
return path.join(storeDir(), 'stats.json');
|
|
32
|
+
}
|
|
33
|
+
function communityCacheFile() {
|
|
34
|
+
return path.join(storeDir(), 'community.json');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function readJson(p, fallback) {
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(fs.readFileSync(p, 'utf8'));
|
|
40
|
+
} catch {
|
|
41
|
+
return fallback;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function writeJson(p, obj) {
|
|
46
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
47
|
+
fs.writeFileSync(p, JSON.stringify(obj, null, 2));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function loadLocal() {
|
|
51
|
+
const db = readJson(dbFile(), { version: 1, skills: [] });
|
|
52
|
+
if (!Array.isArray(db.skills)) db.skills = [];
|
|
53
|
+
return db;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function saveLocal(db) {
|
|
57
|
+
writeJson(dbFile(), db);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function loadStats() {
|
|
61
|
+
const s = readJson(statsFile(), {});
|
|
62
|
+
return s && typeof s === 'object' ? s : {};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function saveStats(s) {
|
|
66
|
+
writeJson(statsFile(), s);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/* Beast Kodu → genel parmak izi (benzersiz kimlik; gerçek kod saklanmaz) */
|
|
70
|
+
function beastFingerprint(beastCode) {
|
|
71
|
+
const h = crypto.createHash('sha256').update(String(beastCode || 'beast')).digest('hex');
|
|
72
|
+
return 'BEAST-' + h.slice(0, 8).toUpperCase();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function slugify(s) {
|
|
76
|
+
return (
|
|
77
|
+
String(s || '')
|
|
78
|
+
.toLowerCase()
|
|
79
|
+
.replace(/[çğıöşü]/g, (c) => ({ ç: 'c', ğ: 'g', ı: 'i', ö: 'o', ş: 's', ü: 'u' }[c] || c))
|
|
80
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
81
|
+
.replace(/^-+|-+$/g, '')
|
|
82
|
+
.slice(0, 40) || 'skill'
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* ---------- topluluk indeksi ---------- */
|
|
87
|
+
|
|
88
|
+
async function fetchCommunity(timeoutMs = 6000) {
|
|
89
|
+
try {
|
|
90
|
+
const ctl = typeof AbortController !== 'undefined' ? new AbortController() : null;
|
|
91
|
+
const t = setTimeout(() => {
|
|
92
|
+
try {
|
|
93
|
+
ctl && ctl.abort();
|
|
94
|
+
} catch {}
|
|
95
|
+
}, timeoutMs);
|
|
96
|
+
const r = await fetch(COMMUNITY_URL, { signal: ctl ? ctl.signal : undefined });
|
|
97
|
+
clearTimeout(t);
|
|
98
|
+
if (!r.ok) throw new Error('HTTP ' + r.status);
|
|
99
|
+
const j = await r.json();
|
|
100
|
+
if (j && Array.isArray(j.skills)) {
|
|
101
|
+
try {
|
|
102
|
+
writeJson(communityCacheFile(), j);
|
|
103
|
+
} catch {}
|
|
104
|
+
return j.skills;
|
|
105
|
+
}
|
|
106
|
+
return [];
|
|
107
|
+
} catch {
|
|
108
|
+
/* offline: son iyi kopyadan devam */
|
|
109
|
+
const cached = readJson(communityCacheFile(), { skills: [] });
|
|
110
|
+
return Array.isArray(cached.skills) ? cached.skills : [];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function mergeStats(entry, stats) {
|
|
115
|
+
const st = stats[entry.id] || {};
|
|
116
|
+
return {
|
|
117
|
+
...entry,
|
|
118
|
+
installs: (entry.installs || 0) + (st.installs || 0),
|
|
119
|
+
likes: (entry.likes || 0) + (st.likes || 0),
|
|
120
|
+
liked: !!st.liked,
|
|
121
|
+
installedAt: st.installedAt || null,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/* Birleşik liste: topluluk + yerel (aynı id'de yerel kazanır) */
|
|
126
|
+
async function list(beastCode) {
|
|
127
|
+
const [community] = await Promise.all([fetchCommunity()]);
|
|
128
|
+
const db = loadLocal();
|
|
129
|
+
const stats = loadStats();
|
|
130
|
+
const byId = new Map();
|
|
131
|
+
for (const e of community) {
|
|
132
|
+
if (e && e.id && e.files && e.files['SKILL.md']) byId.set(e.id, e);
|
|
133
|
+
}
|
|
134
|
+
for (const e of db.skills) if (e && e.id) byId.set(e.id, e);
|
|
135
|
+
const entries = [...byId.values()].map((e) => mergeStats(e, stats));
|
|
136
|
+
const installed = new Set(skills.scan().map((s) => path.basename(path.dirname(s.path))));
|
|
137
|
+
return {
|
|
138
|
+
ok: true,
|
|
139
|
+
beastId: beastFingerprint(beastCode),
|
|
140
|
+
entries,
|
|
141
|
+
installed: [...installed],
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/* Kullanıcı adı benzersizliği: aynı ad başka beastId'de kayıtlıysa alınmıştır */
|
|
146
|
+
function usernameTaken(username, beastId) {
|
|
147
|
+
const want = String(username || '').toLowerCase();
|
|
148
|
+
if (!want) return false;
|
|
149
|
+
const db = loadLocal();
|
|
150
|
+
for (const e of db.skills) {
|
|
151
|
+
const a = e.author || {};
|
|
152
|
+
if (String(a.username || '').toLowerCase() === want && a.beastId !== beastId) return true;
|
|
153
|
+
}
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/* ---------- yükleme ---------- */
|
|
158
|
+
|
|
159
|
+
/* Klasör önizleme (commit etmeden önce): SKILL.md frontmatter'ı + dosya dökümü */
|
|
160
|
+
function preview(dirPath) {
|
|
161
|
+
try {
|
|
162
|
+
const p = String(dirPath || '');
|
|
163
|
+
const skillMd = path.join(p, 'SKILL.md');
|
|
164
|
+
if (!fs.existsSync(skillMd)) return { ok: false, error: 'SKILL.md bulunamadı — skill klasörü SKILL.md içermeli' };
|
|
165
|
+
const text = fs.readFileSync(skillMd, 'utf8');
|
|
166
|
+
const fm = skills.parseFrontmatter(text);
|
|
167
|
+
const files = [];
|
|
168
|
+
let total = 0;
|
|
169
|
+
const walk = (d, depth) => {
|
|
170
|
+
if (depth > 3) return;
|
|
171
|
+
let rows;
|
|
172
|
+
try {
|
|
173
|
+
rows = fs.readdirSync(d, { withFileTypes: true });
|
|
174
|
+
} catch {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
for (const e of rows) {
|
|
178
|
+
if (files.length >= MAX_FILES) return;
|
|
179
|
+
const full = path.join(d, e.name);
|
|
180
|
+
if (e.isDirectory()) {
|
|
181
|
+
if (!SKIP_DIRS.has(e.name)) walk(full, depth + 1);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const ext = path.extname(e.name).toLowerCase();
|
|
185
|
+
if (!ALLOWED_EXT.has(ext)) continue;
|
|
186
|
+
let size = 0;
|
|
187
|
+
try {
|
|
188
|
+
size = fs.statSync(full).size;
|
|
189
|
+
} catch {}
|
|
190
|
+
if (size > MAX_FILE_BYTES) continue;
|
|
191
|
+
total += size;
|
|
192
|
+
files.push({ rel: path.relative(p, full).replace(/\\/g, '/'), size });
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
walk(p, 0);
|
|
196
|
+
if (!files.some((f) => f.rel === 'SKILL.md')) files.unshift({ rel: 'SKILL.md', size: Buffer.byteLength(text, 'utf8') });
|
|
197
|
+
return {
|
|
198
|
+
ok: true,
|
|
199
|
+
path: p,
|
|
200
|
+
folderName: path.basename(p),
|
|
201
|
+
name: fm.name || path.basename(p),
|
|
202
|
+
description: fm.description || '',
|
|
203
|
+
version: fm.version || '1.0.0',
|
|
204
|
+
files,
|
|
205
|
+
totalBytes: total,
|
|
206
|
+
};
|
|
207
|
+
} catch (e) {
|
|
208
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/* Yüklemeyi yerel DB'ye işler; files utf8 metin olarak gömülür */
|
|
213
|
+
function commit({ dirPath, name, description, tags, author }) {
|
|
214
|
+
try {
|
|
215
|
+
const pv = preview(dirPath);
|
|
216
|
+
if (!pv.ok) return pv;
|
|
217
|
+
const nm = String(name || pv.name || '').trim().slice(0, 60);
|
|
218
|
+
if (!nm) return { ok: false, error: 'skill adı gerekli' };
|
|
219
|
+
const a = author || {};
|
|
220
|
+
const username = String(a.username || '').trim();
|
|
221
|
+
if (!/^[a-zA-Z0-9_-]{3,20}$/.test(username)) {
|
|
222
|
+
return { ok: false, error: 'kullanıcı adı 3-20 karakter (harf/rakam/_/-) olmalı' };
|
|
223
|
+
}
|
|
224
|
+
if (!a.avatar || String(a.avatar).length > 8) return { ok: false, error: 'avatar seç' };
|
|
225
|
+
if (!a.beastId) return { ok: false, error: 'beast kimliği yok' };
|
|
226
|
+
/* KAPAK RESMİ ZORUNLU: kare kırpılmış dataURL (renderer canvas ile üretir) */
|
|
227
|
+
const img = String((a.image || '') || '');
|
|
228
|
+
if (!/^data:image\/(png|jpeg|webp);base64,[A-Za-z0-9+/=]+$/.test(img)) {
|
|
229
|
+
return { ok: false, error: 'kapak resmi zorunlu — kare kırpılmış bir resim eklenmeli' };
|
|
230
|
+
}
|
|
231
|
+
if (img.length > 400 * 1024) return { ok: false, error: 'kapak resmi çok büyük (max ~300KB)' };
|
|
232
|
+
if (usernameTaken(username, a.beastId)) {
|
|
233
|
+
return { ok: false, error: `"${username}" kullanıcı adı başka bir Beast tarafından alınmış — başka ad seç` };
|
|
234
|
+
}
|
|
235
|
+
/* dosyaları oku */
|
|
236
|
+
const files = {};
|
|
237
|
+
let total = 0;
|
|
238
|
+
for (const f of pv.files) {
|
|
239
|
+
const raw = fs.readFileSync(path.join(dirPath, f.rel));
|
|
240
|
+
if (total + raw.length > MAX_TOTAL_BYTES) return { ok: false, error: 'paket çok büyük (max 1MB)' };
|
|
241
|
+
files[f.rel] = raw.toString('utf8');
|
|
242
|
+
total += raw.length;
|
|
243
|
+
}
|
|
244
|
+
if (!files['SKILL.md']) return { ok: false, error: 'SKILL.md okunamadı' };
|
|
245
|
+
const db = loadLocal();
|
|
246
|
+
const id = slugify(nm);
|
|
247
|
+
const now = new Date().toISOString();
|
|
248
|
+
const existing = db.skills.find((e) => e.id === id);
|
|
249
|
+
const entry = {
|
|
250
|
+
id,
|
|
251
|
+
name: nm,
|
|
252
|
+
description: String(description || pv.description || '').trim().slice(0, 200),
|
|
253
|
+
version: pv.version || '1.0.0',
|
|
254
|
+
tags: (Array.isArray(tags) ? tags : []).map((t) => String(t).trim().toLowerCase().slice(0, 20)).filter(Boolean).slice(0, 4),
|
|
255
|
+
author: { username, avatar: String(a.avatar), beastId: a.beastId },
|
|
256
|
+
image: img,
|
|
257
|
+
files,
|
|
258
|
+
installs: existing ? existing.installs || 0 : 0,
|
|
259
|
+
likes: existing ? existing.likes || 0 : 0,
|
|
260
|
+
createdAt: existing ? existing.createdAt : now,
|
|
261
|
+
updatedAt: now,
|
|
262
|
+
};
|
|
263
|
+
if (existing) db.skills.splice(db.skills.indexOf(existing), 1, entry);
|
|
264
|
+
else db.skills.push(entry);
|
|
265
|
+
saveLocal(db);
|
|
266
|
+
return { ok: true, entry: { ...entry, files: undefined, fileCount: Object.keys(files).length } };
|
|
267
|
+
} catch (e) {
|
|
268
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/* ---------- kurulum ---------- */
|
|
273
|
+
|
|
274
|
+
async function install(id) {
|
|
275
|
+
try {
|
|
276
|
+
const key = String(id || '');
|
|
277
|
+
if (!/^[a-z0-9_-]{1,40}$/i.test(key)) return { ok: false, error: 'geçersiz id' };
|
|
278
|
+
const db = loadLocal();
|
|
279
|
+
let entry = db.skills.find((e) => e.id === key);
|
|
280
|
+
if (!entry) {
|
|
281
|
+
const comm = await fetchCommunity();
|
|
282
|
+
entry = (comm || []).find((e) => e && e.id === key && e.files && e.files['SKILL.md']);
|
|
283
|
+
}
|
|
284
|
+
if (!entry) return { ok: false, error: 'skill bulunamadı' };
|
|
285
|
+
const dest = path.join(skills.dir(), key);
|
|
286
|
+
const stats = loadStats();
|
|
287
|
+
/* built-in koruması: store'dan hiç kurulmamış klasörün üstüne yazma */
|
|
288
|
+
if (fs.existsSync(dest) && !stats[key]) {
|
|
289
|
+
return { ok: false, error: 'aynı adda kurulu bir skill var — store kurulumu için ad farklı olmalı' };
|
|
290
|
+
}
|
|
291
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
292
|
+
for (const [rel, content] of Object.entries(entry.files || {})) {
|
|
293
|
+
const target = path.join(dest, rel);
|
|
294
|
+
if (!target.startsWith(dest)) continue; // path traversal koruması
|
|
295
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
296
|
+
fs.writeFileSync(target, String(content ?? ''), 'utf8');
|
|
297
|
+
}
|
|
298
|
+
stats[key] = stats[key] || { installs: 0, likes: 0, liked: false };
|
|
299
|
+
stats[key].installs = (stats[key].installs || 0) + 1;
|
|
300
|
+
stats[key].installedAt = new Date().toISOString();
|
|
301
|
+
saveStats(stats);
|
|
302
|
+
/* yerel yüklemeyse DB'deki sayaç da artsın */
|
|
303
|
+
const local = db.skills.find((e) => e.id === key);
|
|
304
|
+
if (local) {
|
|
305
|
+
local.installs = (local.installs || 0) + 1;
|
|
306
|
+
saveLocal(db);
|
|
307
|
+
}
|
|
308
|
+
return { ok: true, id: key, folder: dest };
|
|
309
|
+
} catch (e) {
|
|
310
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function toggleLike(id) {
|
|
315
|
+
try {
|
|
316
|
+
const key = String(id || '');
|
|
317
|
+
if (!key) return { ok: false, error: 'id gerekli' };
|
|
318
|
+
const stats = loadStats();
|
|
319
|
+
stats[key] = stats[key] || { installs: 0, likes: 0, liked: false };
|
|
320
|
+
const st = stats[key];
|
|
321
|
+
if (st.liked) {
|
|
322
|
+
st.liked = false;
|
|
323
|
+
st.likes = Math.max(0, (st.likes || 0) - 1);
|
|
324
|
+
} else {
|
|
325
|
+
st.liked = true;
|
|
326
|
+
st.likes = (st.likes || 0) + 1;
|
|
327
|
+
}
|
|
328
|
+
saveStats(stats);
|
|
329
|
+
const db = loadLocal();
|
|
330
|
+
const local = db.skills.find((e) => e.id === key);
|
|
331
|
+
if (local) {
|
|
332
|
+
local.likes = Math.max(0, (local.likes || 0) + (st.liked ? 1 : -1));
|
|
333
|
+
saveLocal(db);
|
|
334
|
+
}
|
|
335
|
+
return { ok: true, liked: st.liked, likes: st.likes };
|
|
336
|
+
} catch (e) {
|
|
337
|
+
return { ok: false, error: String((e && e.message) || e) };
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/* kendi yüklediğini kaldırma (yalnız yerel kayıt + kendi beastId) */
|
|
342
|
+
function removeMine(id, beastId) {
|
|
343
|
+
try {
|
|
344
|
+
const db = loadLocal();
|
|
345
|
+
const e = db.skills.find((x) => x.id === String(id || ''));
|
|
346
|
+
if (!e) return { ok: false, error: 'yerel kayıt yok' };
|
|
347
|
+
if (!beastId || (e.author || {}).beastId !== beastId) return { ok: false, error: 'bu skill senin değil' };
|
|
348
|
+
db.skills.splice(db.skills.indexOf(e), 1);
|
|
349
|
+
saveLocal(db);
|
|
350
|
+
return { ok: true };
|
|
351
|
+
} catch (err) {
|
|
352
|
+
return { ok: false, error: String((err && err.message) || err) };
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/* Kendi paylaşımlarını GitHub'da paylaşmak için dışa aktarım (issue gövdesi) */
|
|
357
|
+
function exportEntry(id) {
|
|
358
|
+
try {
|
|
359
|
+
const db = loadLocal();
|
|
360
|
+
const e = db.skills.find((x) => x.id === String(id || ''));
|
|
361
|
+
if (!e) return { ok: false, error: 'yerel kayıt yok' };
|
|
362
|
+
return { ok: true, json: JSON.stringify({ version: 1, skills: [e] }, null, 2) };
|
|
363
|
+
} catch (err) {
|
|
364
|
+
return { ok: false, error: String((err && err.message) || err) };
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
module.exports = {
|
|
369
|
+
beastFingerprint,
|
|
370
|
+
list,
|
|
371
|
+
preview,
|
|
372
|
+
commit,
|
|
373
|
+
install,
|
|
374
|
+
toggleLike,
|
|
375
|
+
removeMine,
|
|
376
|
+
exportEntry,
|
|
377
|
+
usernameTaken,
|
|
378
|
+
};
|