beast-agent 2.5.1 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "beast-agent",
3
3
  "productName": "Beast Agent",
4
- "version": "2.5.1",
4
+ "version": "2.6.1",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -0,0 +1,587 @@
1
+ 'use strict';
2
+
3
+ /* Beast App: %APPDATA%\beast\apps\<id>\ — JS uygulama/extension çalıştırıcı.
4
+ Her app: app.json (manifest) + main.js (arka uç, beast API'si) + ui/ (isteğe
5
+ bağlı arayüz — Beast App panelinde webview olarak açılır).
6
+
7
+ app main.js şablonu:
8
+ module.exports = (beast) => {
9
+ beast.tools.register('hesapla', { description, parameters, handler });
10
+ beast.every(60000, () => { ... });
11
+ beast.notify('hazır');
12
+ };
13
+
14
+ Araçlar modele `app__<id>__<tool>` adıyla açılır (engine app__* isimlerini
15
+ buraya dispatch eder). Kurulum: `beast install github.com/u/r` / npm:paket. */
16
+
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+ const os = require('os');
20
+ const { beastRoot } = require('./memory');
21
+ const log = require('./logger');
22
+
23
+ const TOOL_PREFIX = 'app__';
24
+ const MAX_TOOLS_TOTAL = 24; // tüm app'lerin toplam araç tavanı (prompt şişmesin)
25
+ const CALL_TIMEOUT_MS = 15000;
26
+ const ID_RE = /^[a-z0-9][a-z0-9_-]{0,31}$/;
27
+
28
+ /* ---------- yollar + durum ---------- */
29
+
30
+ function dir() {
31
+ return path.join(beastRoot(), 'apps');
32
+ }
33
+
34
+ function stateFile() {
35
+ return path.join(beastRoot(), 'apps-state.json');
36
+ }
37
+
38
+ let state = null; // id -> { enabled, installedAt, source }
39
+
40
+ function loadState() {
41
+ if (state) return state;
42
+ try {
43
+ state = JSON.parse(fs.readFileSync(stateFile(), 'utf8')) || {};
44
+ } catch {
45
+ state = {};
46
+ }
47
+ return state;
48
+ }
49
+
50
+ function saveState() {
51
+ try {
52
+ fs.mkdirSync(beastRoot(), { recursive: true });
53
+ fs.writeFileSync(stateFile(), JSON.stringify(state || {}, null, 2));
54
+ } catch (e) {
55
+ log.warn('apps', 'state yazılamadı: ' + String((e && e.message) || e));
56
+ }
57
+ }
58
+
59
+ function isEnabled(id) {
60
+ const st = loadState()[id];
61
+ return !st || st.enabled !== false; // varsayılan: etkin
62
+ }
63
+
64
+ /* ---------- manifest ---------- */
65
+
66
+ function readManifest(appDir) {
67
+ const m = JSON.parse(fs.readFileSync(path.join(appDir, 'app.json'), 'utf8'));
68
+ const id = String(m.id || path.basename(appDir)).toLowerCase();
69
+ if (!ID_RE.test(id)) throw new Error('geçersiz app id: ' + id);
70
+ return {
71
+ id,
72
+ name: String(m.name || id).slice(0, 60),
73
+ version: String(m.version || '0.0.0').slice(0, 16),
74
+ description: String(m.description || '').slice(0, 300),
75
+ author: String(m.author || '').slice(0, 60),
76
+ icon: String(m.icon || '🧩').slice(0, 8),
77
+ permissions: Array.isArray(m.permissions) ? m.permissions.map(String).slice(0, 8) : [],
78
+ ui: m.ui && typeof m.ui === 'string' ? m.ui : null,
79
+ main: m.main && typeof m.main === 'string' ? m.main : 'main.js',
80
+ builtin: !!m.builtin,
81
+ };
82
+ }
83
+
84
+ /* Tüm kurulu app'ler — UI'ın beklediği düz liste */
85
+ function scan() {
86
+ const out = [];
87
+ let entries;
88
+ try {
89
+ entries = fs.readdirSync(dir(), { withFileTypes: true });
90
+ } catch {
91
+ return out;
92
+ }
93
+ for (const e of entries) {
94
+ if (!e.isDirectory()) continue;
95
+ const appDir = path.join(dir(), e.name);
96
+ try {
97
+ const m = readManifest(appDir);
98
+ const inst = registry.get(m.id);
99
+ const st = loadState()[m.id];
100
+ out.push({
101
+ ...m,
102
+ dir: appDir,
103
+ uiPath: m.ui && fs.existsSync(path.join(appDir, m.ui)) ? m.ui : null,
104
+ enabled: isEnabled(m.id),
105
+ loaded: !!inst,
106
+ tools: inst ? [...inst.tools.keys()] : [],
107
+ error: (inst && inst.error) || null,
108
+ installedAt: (st && st.installedAt) || null,
109
+ });
110
+ } catch (er) {
111
+ out.push({
112
+ id: e.name,
113
+ name: e.name,
114
+ version: '?',
115
+ description: '',
116
+ author: '',
117
+ icon: '⚠️',
118
+ permissions: [],
119
+ ui: null,
120
+ main: 'main.js',
121
+ builtin: false,
122
+ dir: appDir,
123
+ uiPath: null,
124
+ enabled: isEnabled(e.name),
125
+ loaded: false,
126
+ tools: [],
127
+ error: 'manifest hatası: ' + String((er && er.message) || er),
128
+ installedAt: null,
129
+ });
130
+ }
131
+ }
132
+ return out.sort((a, b) => a.name.localeCompare(b.name));
133
+ }
134
+
135
+ /* ---------- çalışma zamanı (host) ---------- */
136
+
137
+ /* id -> { api, tools: Map(name → def), timers: [], error } */
138
+ const registry = new Map();
139
+
140
+ /* main.js'ten gelir: bildirim + log köprüsü */
141
+ let bridges = { notify: null };
142
+
143
+ function init(bridgeOverrides) {
144
+ bridges = { ...bridges, ...(bridgeOverrides || {}) };
145
+ }
146
+
147
+ function storageFile(id) {
148
+ return path.join(dir(), id, 'data.json');
149
+ }
150
+
151
+ function makeApi(id) {
152
+ const tools = new Map();
153
+ const timers = [];
154
+ const api = {
155
+ id,
156
+ tools: {
157
+ register(name, def) {
158
+ const nm = String(name || '');
159
+ if (!/^[A-Za-z0-9_-]{1,40}$/.test(nm)) throw new Error('geçersiz araç adı: ' + nm);
160
+ if (typeof (def && def.handler) !== 'function') throw new Error("'" + nm + "' için handler fonksiyonu gerekli");
161
+ tools.set(nm, {
162
+ name: nm,
163
+ description: String(def.description || nm).slice(0, 300),
164
+ parameters: def.parameters && typeof def.parameters === 'object' ? def.parameters : { type: 'object', properties: {} },
165
+ handler: def.handler,
166
+ });
167
+ return true;
168
+ },
169
+ list() {
170
+ return [...tools.keys()];
171
+ },
172
+ },
173
+ storage: {
174
+ get(key, dflt) {
175
+ try {
176
+ const data = JSON.parse(fs.readFileSync(storageFile(id), 'utf8'));
177
+ return key in data ? data[key] : dflt;
178
+ } catch {
179
+ return dflt;
180
+ }
181
+ },
182
+ set(key, value) {
183
+ try {
184
+ let data = {};
185
+ try {
186
+ data = JSON.parse(fs.readFileSync(storageFile(id), 'utf8'));
187
+ } catch {}
188
+ data[key] = value;
189
+ fs.mkdirSync(path.dirname(storageFile(id)), { recursive: true });
190
+ fs.writeFileSync(storageFile(id), JSON.stringify(data));
191
+ return true;
192
+ } catch (e) {
193
+ log.warn('apps:' + id, 'storage yazılamadı: ' + String((e && e.message) || e));
194
+ return false;
195
+ }
196
+ },
197
+ all() {
198
+ try {
199
+ return JSON.parse(fs.readFileSync(storageFile(id), 'utf8'));
200
+ } catch {
201
+ return {};
202
+ }
203
+ },
204
+ },
205
+ notify(text) {
206
+ const t = String(text || '').slice(0, 300);
207
+ log.info('apps:' + id, 'bildirim: ' + t);
208
+ try {
209
+ if (typeof bridges.notify === 'function') bridges.notify(id, t);
210
+ } catch {}
211
+ return true;
212
+ },
213
+ log(...args) {
214
+ log.info(
215
+ 'apps:' + id,
216
+ args.map((a) => (typeof a === 'string' ? a : JSON.stringify(a))).join(' ').slice(0, 500)
217
+ );
218
+ },
219
+ every(ms, fn) {
220
+ const iv = setInterval(() => {
221
+ try {
222
+ fn();
223
+ } catch (e) {
224
+ log.warn('apps:' + id, 'interval hatası: ' + String((e && e.message) || e));
225
+ }
226
+ }, Math.max(1000, Number(ms) || 60000));
227
+ timers.push(iv);
228
+ return iv;
229
+ },
230
+ };
231
+ return { api, tools, timers, error: null };
232
+ }
233
+
234
+ function startApp(id) {
235
+ const appDir = path.join(dir(), id);
236
+ let m;
237
+ try {
238
+ m = readManifest(appDir);
239
+ } catch (e) {
240
+ return { ok: false, error: 'manifest okunamadı: ' + String((e && e.message) || e) };
241
+ }
242
+ stopApp(id);
243
+ const mainPath = path.join(appDir, m.main);
244
+ if (!fs.existsSync(mainPath)) return { ok: false, error: m.main + ' bulunamadı' };
245
+ let inst;
246
+ try {
247
+ delete require.cache[require.resolve(mainPath)];
248
+ const mod = require(mainPath);
249
+ inst = makeApi(id);
250
+ const fn = typeof mod === 'function' ? mod : mod && (mod.activate || mod.default);
251
+ if (typeof fn !== 'function') throw new Error('main.js bir fonksiyon export etmeli: module.exports = (beast) => {...}');
252
+ fn(inst.api);
253
+ } catch (e) {
254
+ if (inst) for (const t of inst.timers) clearInterval(t);
255
+ return { ok: false, error: 'yüklenemedi: ' + String((e && e.message) || e) };
256
+ }
257
+ registry.set(id, inst);
258
+ log.info('apps', "'" + id + "' yüklendi — araçlar: " + ([...inst.tools.keys()].join(', ') || '(yok)'));
259
+ return { ok: true, tools: [...inst.tools.keys()] };
260
+ }
261
+
262
+ function stopApp(id) {
263
+ const inst = registry.get(id);
264
+ if (inst) {
265
+ for (const t of inst.timers) clearInterval(t);
266
+ registry.delete(id);
267
+ }
268
+ try {
269
+ const appDir = path.join(dir(), id);
270
+ const m = readManifest(appDir);
271
+ delete require.cache[require.resolve(path.join(appDir, m.main))];
272
+ } catch {}
273
+ if (inst) log.info('apps', "'" + id + "' durduruldu");
274
+ return { ok: true };
275
+ }
276
+
277
+ function startAll() {
278
+ const results = {};
279
+ for (const app of scan()) {
280
+ if (!app.enabled || app.error) continue;
281
+ const r = startApp(app.id);
282
+ if (!r.ok) {
283
+ results[app.id] = r.error;
284
+ log.warn('apps', "'" + app.id + "' yüklenemedi: " + r.error);
285
+ }
286
+ }
287
+ return results;
288
+ }
289
+
290
+ function stopAll() {
291
+ for (const id of [...registry.keys()]) stopApp(id);
292
+ }
293
+
294
+ function reloadAll() {
295
+ stopAll();
296
+ return startAll();
297
+ }
298
+
299
+ /* ---------- engine köprüsü (MCP deseni) ---------- */
300
+
301
+ function schemas() {
302
+ const out = [];
303
+ for (const [id, inst] of registry) {
304
+ for (const t of inst.tools.values()) {
305
+ out.push({
306
+ type: 'function',
307
+ function: {
308
+ name: TOOL_PREFIX + id + '__' + t.name,
309
+ description: '[app:' + id + '] ' + t.description,
310
+ parameters: t.parameters,
311
+ },
312
+ });
313
+ }
314
+ }
315
+ return out.slice(0, MAX_TOOLS_TOTAL);
316
+ }
317
+
318
+ function mergeTools(toolsList) {
319
+ const extra = schemas();
320
+ return extra.length ? toolsList.concat(extra) : toolsList;
321
+ }
322
+
323
+ function parseFullName(fullName) {
324
+ const m = String(fullName || '').match(/^app__([a-z0-9][a-z0-9_-]{0,31})__([A-Za-z0-9_-]{1,40})$/);
325
+ return m ? { id: m[1], tool: m[2] } : null;
326
+ }
327
+
328
+ async function call(fullName, args, signal) {
329
+ const parsed = parseFullName(fullName);
330
+ if (!parsed) return { ok: false, error: 'geçersiz app araç adı: ' + fullName };
331
+ let inst = registry.get(parsed.id);
332
+ if (!inst) {
333
+ const appDir = path.join(dir(), parsed.id);
334
+ if (fs.existsSync(path.join(appDir, 'app.json')) && isEnabled(parsed.id)) {
335
+ const r = startApp(parsed.id);
336
+ if (r.ok) inst = registry.get(parsed.id);
337
+ else return { ok: false, error: "app '" + parsed.id + "' başlatılamadı: " + r.error };
338
+ } else {
339
+ return { ok: false, error: "app '" + parsed.id + "' yüklü değil" };
340
+ }
341
+ }
342
+ const def = inst.tools.get(parsed.tool);
343
+ if (!def) return { ok: false, error: "araç '" + parsed.tool + "' app '" + parsed.id + "' içinde kayıtlı değil" };
344
+ try {
345
+ const res = await Promise.race([
346
+ Promise.resolve(def.handler(args || {})),
347
+ new Promise((_, rej) => {
348
+ const timer = setTimeout(() => rej(new Error('zaman aşımı (' + CALL_TIMEOUT_MS / 1000 + 's)')), CALL_TIMEOUT_MS);
349
+ if (signal)
350
+ signal.addEventListener(
351
+ 'abort',
352
+ () => {
353
+ clearTimeout(timer);
354
+ rej(new Error('iptal edildi'));
355
+ },
356
+ { once: true }
357
+ );
358
+ }),
359
+ ]);
360
+ if (res && typeof res === 'object' && 'ok' in res) return res;
361
+ return { ok: true, result: typeof res === 'string' ? res : JSON.stringify(res) };
362
+ } catch (e) {
363
+ return { ok: false, error: String((e && e.message) || e) };
364
+ }
365
+ }
366
+
367
+ /* ---------- kurulum (github / npm / yerel klasör) ---------- */
368
+
369
+ function slugify(s) {
370
+ return (
371
+ String(s || '')
372
+ .toLowerCase()
373
+ .replace(/[çğıöşü]/g, (c) => ({ ç: 'c', ğ: 'g', ı: 'i', ö: 'o', ş: 's', ü: 'u' }[c] || c))
374
+ .replace(/[^a-z0-9]+/g, '-')
375
+ .replace(/^-+|-+$/g, '')
376
+ .slice(0, 32) || 'app'
377
+ );
378
+ }
379
+
380
+ function sh(cmd, args, opts) {
381
+ return new Promise((resolve) => {
382
+ const { spawn } = require('child_process');
383
+ const p = spawn(cmd, args, { windowsHide: true, ...(opts || {}) });
384
+ let out = '';
385
+ let err = '';
386
+ p.stdout.on('data', (d) => (out += d));
387
+ p.stderr.on('data', (d) => (err += d));
388
+ p.on('error', (e) => resolve({ ok: false, out, err: String(e.message || e) }));
389
+ p.on('close', (code) => resolve({ ok: code === 0, out, err, code }));
390
+ });
391
+ }
392
+
393
+ /* NOT: fs.cpSync Node v22.20.0 + Windows'ta process'i çökertiyor (0xC0000409)
394
+ — burada VEYA başka yerde KULLANMA. Kopyalama daima bu yardımcıyla: */
395
+ function copyDir(src, dest) {
396
+ fs.mkdirSync(dest, { recursive: true });
397
+ for (const e of fs.readdirSync(src, { withFileTypes: true })) {
398
+ const s = path.join(src, e.name);
399
+ const d = path.join(dest, e.name);
400
+ if (e.isDirectory()) copyDir(s, d);
401
+ else fs.copyFileSync(s, d);
402
+ }
403
+ }
404
+
405
+ async function fetchZip(gitUrl, destDir) {
406
+ /* git yoksa codeload zip fallback (main → master) */
407
+ const m = String(gitUrl).match(/github\.com[/:]([^/]+)\/([^/#?]+?)(?:\.git)?\/?$/i);
408
+ if (!m) return null;
409
+ const zipPath = path.join(destDir, 'repo.zip');
410
+ for (const branch of ['main', 'master']) {
411
+ try {
412
+ const res = await fetch('https://codeload.github.com/' + m[1] + '/' + m[2] + '/zip/refs/heads/' + branch, {
413
+ signal: AbortSignal.timeout(30000),
414
+ });
415
+ if (!res.ok) continue;
416
+ fs.writeFileSync(zipPath, Buffer.from(await res.arrayBuffer()));
417
+ const r = await sh('powershell', [
418
+ '-NoProfile',
419
+ '-Command',
420
+ "Expand-Archive -LiteralPath '" + zipPath + "' -DestinationPath '" + path.join(destDir, 'unzipped') + "' -Force",
421
+ ]);
422
+ if (r.ok) {
423
+ const un = path.join(destDir, 'unzipped');
424
+ const subs = fs.readdirSync(un, { withFileTypes: true }).filter((e) => e.isDirectory());
425
+ return subs.length === 1 ? path.join(un, subs[0].name) : un;
426
+ }
427
+ } catch {}
428
+ }
429
+ return null;
430
+ }
431
+
432
+ /* source: github URL/kısaltma | npm:<paket> | yerel klasör yolu | npm paket adı */
433
+ async function install(source) {
434
+ const raw = String(source || '').trim();
435
+ if (!raw) return { ok: false, error: 'kaynak boş — örnek: github.com/kullanici/repo' };
436
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'beast-app-'));
437
+ let srcDir = null;
438
+ try {
439
+ const isShorthand = /^[\w.-]+\/[\w.-]+$/.test(raw);
440
+ const isUrl = /^https?:\/\//i.test(raw) || /^github\.com/i.test(raw) || isShorthand;
441
+ const isNpm = /^npm:/i.test(raw);
442
+ if (isNpm || (!isUrl && !fs.existsSync(raw))) {
443
+ /* npm paketi: npm pack + tar çıkarımı (Win10+ bsdtar) */
444
+ const pkg = raw.replace(/^npm:/i, '').split(/\s+/)[0];
445
+ if (!/^[@a-zA-Z0-9][@a-zA-Z0-9._/-]*$/.test(pkg)) return { ok: false, error: 'geçersiz paket adı: ' + pkg };
446
+ const pr = await sh('npm', ['pack', pkg, '--pack-destination', tmp], { cwd: tmp });
447
+ const tgz = (pr.out || '')
448
+ .split(/\r?\n/)
449
+ .map((s) => s.trim())
450
+ .filter((s) => s.endsWith('.tgz'))
451
+ .pop();
452
+ if (!pr.ok || !tgz) return { ok: false, error: 'npm pack başarısız: ' + ((pr.err || pr.out || '').slice(-300) || pkg) };
453
+ const xr = await sh('tar', ['-xzf', tgz, '-C', tmp]);
454
+ if (!xr.ok) return { ok: false, error: 'paket açılamadı: ' + (xr.err || '').slice(-300) };
455
+ srcDir = path.join(tmp, 'package');
456
+ } else if (isUrl) {
457
+ const url = isShorthand ? 'https://github.com/' + raw : raw;
458
+ const gr = await sh('git', ['clone', '--depth', '1', url, path.join(tmp, 'repo')], { cwd: tmp });
459
+ if (gr.ok) srcDir = path.join(tmp, 'repo');
460
+ else {
461
+ const un = await fetchZip(url, tmp);
462
+ if (!un) return { ok: false, error: 'repo indirilemedi (git yok ve zip erişilemedi): ' + ((gr.err || '').slice(-200)) };
463
+ srcDir = un;
464
+ }
465
+ } else {
466
+ srcDir = path.resolve(raw);
467
+ }
468
+ /* app.json hangi derinlikte? (kök veya tek alt klasör) */
469
+ let appDir = null;
470
+ const candidates = [srcDir];
471
+ try {
472
+ for (const e of fs.readdirSync(srcDir, { withFileTypes: true })) {
473
+ if (e.isDirectory()) candidates.push(path.join(srcDir, e.name));
474
+ }
475
+ } catch {}
476
+ for (const c of candidates) {
477
+ if (fs.existsSync(path.join(c, 'app.json'))) {
478
+ appDir = c;
479
+ break;
480
+ }
481
+ }
482
+ if (!appDir) return { ok: false, error: 'repoda app.json bulunamadı — bu klasör Beast App manifesti içermiyor' };
483
+ const m = readManifest(appDir);
484
+ const dest = path.join(dir(), m.id);
485
+ if (fs.existsSync(dest)) stopApp(m.id);
486
+ fs.rmSync(dest, { recursive: true, force: true });
487
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
488
+ copyDir(appDir, dest);
489
+ loadState()[m.id] = { enabled: true, installedAt: new Date().toISOString(), source: raw.slice(0, 200) };
490
+ saveState();
491
+ const r = startApp(m.id);
492
+ log.info('apps', 'kuruldu: ' + m.id + ' v' + m.version + ' (' + raw + ')');
493
+ return { ok: true, id: m.id, name: m.name, version: m.version, start: r };
494
+ } catch (e) {
495
+ return { ok: false, error: String((e && e.message) || e) };
496
+ } finally {
497
+ try {
498
+ fs.rmSync(tmp, { recursive: true, force: true });
499
+ } catch {}
500
+ }
501
+ }
502
+
503
+ async function remove(id) {
504
+ const appDir = path.join(dir(), String(id || ''));
505
+ if (!fs.existsSync(path.join(appDir, 'app.json'))) return { ok: false, error: 'app bulunamadı: ' + id };
506
+ stopApp(id);
507
+ try {
508
+ fs.rmSync(appDir, { recursive: true, force: true });
509
+ } catch (e) {
510
+ return { ok: false, error: String((e && e.message) || e) };
511
+ }
512
+ delete loadState()[id];
513
+ saveState();
514
+ return { ok: true };
515
+ }
516
+
517
+ function toggle(id, enabled) {
518
+ const st = loadState();
519
+ st[id] = { ...(st[id] || { installedAt: new Date().toISOString() }), enabled: !!enabled };
520
+ saveState();
521
+ if (enabled) return startApp(id);
522
+ stopApp(id);
523
+ return { ok: true, disabled: true };
524
+ }
525
+
526
+ /* ---------- slash komut çıktıları ---------- */
527
+
528
+ function listText() {
529
+ const all = scan();
530
+ if (!all.length) return 'Kurulu app yok — Beast App panelinden veya `/install github.com/kullanici/repo` ile kur.';
531
+ const lines = ['*Beast App — kurulu uygulamalar*'];
532
+ for (const a of all) {
533
+ const st = a.error ? ' ⚠️ ' + a.error : a.enabled ? (a.loaded ? ' ✅' : ' (etkin)') : ' ⏸ kapalı';
534
+ lines.push('• *' + a.icon + ' ' + a.name + '* v' + a.version + ' — ' + (a.description || 'açıklama yok') + st);
535
+ if (a.tools.length) lines.push(' araçlar: ' + a.tools.map((t) => '`app__' + a.id + '__' + t + '`').join(', '));
536
+ }
537
+ lines.push('', 'Kurulum: `/install github.com/kullanici/repo` · panel: sol altta 🧊 Beast App');
538
+ return lines.join('\n');
539
+ }
540
+
541
+ /* ---------- builtin seed app'ler ---------- */
542
+
543
+ function seedAppsDir() {
544
+ return path.join(__dirname, 'seed-apps');
545
+ }
546
+
547
+ function seedApps() {
548
+ let entries;
549
+ try {
550
+ entries = fs.readdirSync(seedAppsDir(), { withFileTypes: true });
551
+ } catch {
552
+ return;
553
+ }
554
+ fs.mkdirSync(dir(), { recursive: true });
555
+ for (const e of entries) {
556
+ if (!e.isDirectory()) continue;
557
+ const dest = path.join(dir(), e.name);
558
+ if (fs.existsSync(path.join(dest, 'app.json'))) continue; // kullanıcı app'i korunur
559
+ try {
560
+ copyDir(path.join(seedAppsDir(), e.name), dest);
561
+ log.info('apps', 'seed kuruldu: ' + e.name);
562
+ } catch (er) {
563
+ log.warn('apps', 'seed kurulamadı ' + e.name + ': ' + String((er && er.message) || er));
564
+ }
565
+ }
566
+ }
567
+
568
+ module.exports = {
569
+ dir,
570
+ init,
571
+ scan,
572
+ seedApps,
573
+ startAll,
574
+ stopAll,
575
+ reloadAll,
576
+ startApp,
577
+ stopApp,
578
+ schemas,
579
+ mergeTools,
580
+ call,
581
+ install,
582
+ remove,
583
+ toggle,
584
+ listText,
585
+ isEnabled,
586
+ TOOL_PREFIX,
587
+ };
@@ -1640,6 +1640,7 @@ class Engine {
1640
1640
  'Kullanıcı bir tarihte/saatte hatırlatılmasını isterse set_reminder kullan; when değerini ORTAMdaki bugüne göre hesapla (yerel saat). "Her sabah/gün/hafta" gibi tekrarlı isteklerde repeat alanını da ver (daily/weekly/monthly/weekdays veya cron).',
1641
1641
  'Kullanıcı kalıcı bir arka plan takibi isterse (fiyat eşiği, pil seviyesi, sayfa değişikliği) watcher_add ile izleyici kur; kurduktan sonra watcher_list ile doğrula ve kullanıcıya koşulu + kontrol sıklığını kısaca bildir.',
1642
1642
  'Anlık olay takipleri için (yeni mail, fiyat eşiği, dosya değişimi, webhook) event_subscribe kullan — cron/polling gerekmez; listeyi event_list ile göster, vazgeçirirse event_unsubscribe.',
1643
+ 'LOG ZEKASI: "hata var mı / ne oldu / neden çalışmadı" sorularında ya da bir iş beklenmedik bittiğinde/hata verdiğini fark ettiğinde log_analyze ile logları tara — top desenleri okuyup en olası KÖK NEDENİ tek cümlede söyle, somut çözüm öner; aynı desen 3+ tekrarlıysa bunu vurgula. Sürekli log gözetimi istenirse watcher_add ile kind:"logs" kur (örn "hata artarsa bağır" → level:"error", windowMin:10, op:"gt", value:2) ve koşulu kısaca bildir.',
1643
1644
  'Kullanıcı "artık hep böyle yap / bunu unutma" tarzı kalıcı talimat verirse kural olarak kaydet: sohbette /rule <metin> kullanmasını söyle VEYA kullanıcı isterse event_subscribe ile olaya bağlan (mail/fiyat/dosya/webhook).',
1644
1645
  'Kullanıcının mesajında 2+ ayrı iş/hedef varsa (örn "X yap ve sonra Y\u2019i kontrol et") KODLAMAYA/İŞE BAŞLAMADAN önce todo_write ile plan çıkar ve sırayla yürüt; her adımı tamamlarken güncelle. LİSTE DİSİPLİNİ: her adım bittiği AN status:"done" yap; son cevabını vermeden önce tüm maddeler done olmalı — yapılmayacaksa listeden düş. Listeyi yarım bırakma.',
1645
1646
  'HIZ KURALI: Bağımsız işleri AYNI turda birden çok tool_calls ile PARALEL ver. Küçük işleri tek tek çağırma — her ayrı araç turu 5-15 sn LLM gecikmesidir: 3+ küçük komutu TEK run_command\u2019te `;` ile zincirle (örn `git status; node -v; dir`), döngülü/çoklu işleri TEK python_run betiğinde topla, çok dosyalık değişikliği TEK script ile DEĞİL AYNI turda PARALEL edit_file çağrılarıyla yap (dosya düzenlemeyi komut/scripte yedirme). Her küçük işlem için ayrı araç çağrısı açmak yavaşlığın 1 numaralı sebebidir.',
@@ -4264,6 +4265,8 @@ const skills = require('./skills');
4264
4265
  url: w.url || undefined,
4265
4266
  op: w.op,
4266
4267
  value: w.value,
4268
+ level: w.level,
4269
+ windowMin: w.windowMin,
4267
4270
  everyMin: w.everyMin,
4268
4271
  enabled: w.enabled,
4269
4272
  lastValue: w.lastValue,
@@ -4276,6 +4279,16 @@ const skills = require('./skills');
4276
4279
  }
4277
4280
  return JSON.stringify(this.watchers.remove(String((args && args.id) || '')));
4278
4281
  }
4282
+ if (name === 'log_analyze') {
4283
+ /* LOG ZEKASI: log dosyasını tara → seviye sayıları + desen grupları + son kayıtlar */
4284
+ return JSON.stringify(
4285
+ log.analyze({
4286
+ last: args ? args.last : undefined,
4287
+ level: args ? args.level : undefined,
4288
+ query: args ? args.query : undefined,
4289
+ })
4290
+ );
4291
+ }
4279
4292
  if (name === 'kb_add' || name === 'kb_search') {
4280
4293
  const kb = require('./kb');
4281
4294
  if (name === 'kb_add') {
@@ -4657,25 +4670,31 @@ const TOOLS = [
4657
4670
  function: {
4658
4671
  name: 'watcher_add',
4659
4672
  description:
4660
- 'Create a background watcher that periodically checks something and notifies THIS chat when a condition becomes true (edge-triggered: fires once per crossing; cooldown limits repeats). Use for price alarms ("X altına düşerse haber ver"), battery levels, page change detection.',
4673
+ 'Create a background watcher that periodically checks something and notifies THIS chat when a condition becomes true (edge-triggered: fires once per crossing; cooldown limits repeats). Use for price alarms ("X altına düşerse haber ver"), battery levels, page change detection, or Beast log surveillance (kind=logs: "son 10 dakikada 3+ hata olursa bağır" → level:error, windowMin:10, op:gt, value:2).',
4661
4674
  parameters: {
4662
4675
  type: 'object',
4663
4676
  properties: {
4664
4677
  name: { type: 'string', description: 'Short label, e.g. "GOLD 2300 alarmı"' },
4665
- kind: { type: 'string', enum: ['web', 'battery'], description: 'web: fetch URL and extract value; battery: local battery percent' },
4678
+ kind: {
4679
+ type: 'string',
4680
+ enum: ['web', 'battery', 'logs'],
4681
+ description: 'web: fetch URL and extract value; battery: local battery percent; logs: count Beast error/warn entries in a sliding time window',
4682
+ },
4666
4683
  url: { type: 'string', description: 'http(s) URL to poll (kind=web, required)' },
4667
4684
  path: {
4668
4685
  type: 'string',
4669
4686
  description:
4670
4687
  "If response is JSON: dot-path to watched value, e.g. 'price.usd' or 'data.0.close' (recommended over re)",
4671
4688
  },
4672
- re: { type: 'string', description: 'If response is HTML/text: regex; first capture group becomes the value' },
4689
+ re: { type: 'string', description: 'If response is HTML/text: regex; first capture group becomes the value. kind=logs: optional regex to count only matching entries' },
4673
4690
  op: {
4674
4691
  type: 'string',
4675
4692
  enum: ['lt', 'lte', 'gt', 'gte', 'eq', 'neq', 'changed'],
4676
- description: "Comparison vs value. Use 'changed' with no value for page-change alerts.",
4693
+ description: "Comparison vs value. Use 'changed' with no value for page-change alerts. kind=logs default: gt",
4677
4694
  },
4678
4695
  value: { type: 'number', description: 'Threshold for numeric ops (not needed for changed)' },
4696
+ level: { type: 'string', enum: ['error', 'warn', 'info'], description: 'kind=logs: which log level to count (default error)' },
4697
+ windowMin: { type: 'number', description: 'kind=logs: sliding window in minutes (default 10, max 720)' },
4679
4698
  everyMin: { type: 'number', description: 'Check interval in minutes (default 15, min 1, max 1440)' },
4680
4699
  everySec: { type: 'number', description: 'Check interval in SECONDS (10-8640) — overrides everyMin for fast watchers (e.g. 30 = every 30s)' },
4681
4700
  cooldownMin: { type: 'number', description: 'Min minutes between notifications (default 60)' },
@@ -4700,6 +4719,22 @@ const TOOLS = [
4700
4719
  parameters: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] },
4701
4720
  },
4702
4721
  },
4722
+ {
4723
+ type: 'function',
4724
+ function: {
4725
+ name: 'log_analyze',
4726
+ description:
4727
+ 'LOG ZEKASI — analyze Beast application logs: level counts, error patterns grouped by normalized shape (variable parts masked → same failure collapses into one pattern with a count), and the raw recent tail. Use when the user asks "hata var mı / ne oldu / neden çalışmadı", when a task fails unexpectedly, or for self-diagnosis. Read the top patterns, name the single most likely root cause in one sentence and offer a concrete fix; offer watcher_add kind=logs for permanent surveillance.',
4728
+ parameters: {
4729
+ type: 'object',
4730
+ properties: {
4731
+ last: { type: 'number', description: 'How many recent log lines to scan (default 1500, max 5000)' },
4732
+ level: { type: 'string', enum: ['error', 'warn', 'info'], description: 'Only this level (omit = all levels)' },
4733
+ query: { type: 'string', description: 'Optional regex filter applied to message and tag' },
4734
+ },
4735
+ },
4736
+ },
4737
+ },
4703
4738
  {
4704
4739
  type: 'function',
4705
4740
  function: {