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.
@@ -10,9 +10,11 @@ const fs = require('fs');
10
10
  const path = require('path');
11
11
  const os = require('os');
12
12
 
13
- const ROOT = process.env.APPDATA
14
- ? path.join(process.env.APPDATA, 'beast')
15
- : path.join(os.homedir(), 'AppData', 'Roaming', 'beast');
13
+ const ROOT = process.env.BEAST_DATA
14
+ ? process.env.BEAST_DATA
15
+ : process.env.APPDATA
16
+ ? path.join(process.env.APPDATA, 'beast')
17
+ : path.join(os.homedir(), 'AppData', 'Roaming', 'beast');
16
18
  const LOG_DIR = path.join(ROOT, 'logs');
17
19
  const KEEP_DAYS = 14;
18
20
  const RING_MAX = 1000;
@@ -74,7 +76,6 @@ function dir() {
74
76
  function recent() {
75
77
  return ring.slice(-200);
76
78
  }
77
-
78
79
  function clear() {
79
80
  try { ring.length = 0; } catch {}
80
81
  /* tail() bugün + dünün dosyasını okur; ikisini de boşalt ki
@@ -87,4 +88,136 @@ function clear() {
87
88
  return true;
88
89
  }
89
90
 
90
- module.exports = { info, warn, error, tail, dir, recent, clear, LOG_DIR };
91
+ /* ---------- LOG ZEKASI: desen analizi + zaman penceresi sayacı ---------- */
92
+
93
+ /* Satırı parçala: [ts] [LEVEL] [tag] msg — biçime uymayan satır null */
94
+ function parseLine(line) {
95
+ const m = /^\[([^\]]+)\] \[([A-Za-z]+)\] \[([^\]]+)\] (.*)$/.exec(String(line || ''));
96
+ if (!m) return null;
97
+ const ts = Date.parse(m[1]);
98
+ return { ts: Number.isFinite(ts) ? ts : null, level: m[2].toLowerCase(), tag: m[3], msg: m[4] };
99
+ }
100
+
101
+ /* Değişken kısımları maskele → aynı hatanın farklı örnekleri tek desene düşer */
102
+ function normalizeMsg(msg) {
103
+ return String(msg || '')
104
+ .replace(/https?:\/\/\S+/g, '<url>')
105
+ .replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, '<guid>')
106
+ .replace(/\b0x[0-9a-f]+\b/gi, '<hex>')
107
+ .replace(/(["'`]).*?\1/g, '<q>')
108
+ .replace(/\d+/g, 'N')
109
+ .replace(/\s+/g, ' ')
110
+ .trim()
111
+ .slice(0, 160);
112
+ }
113
+
114
+ /* Tail'i tara → seviye sayıları, en çok tekrarlanan desenler, son kayıtlar.
115
+ opts: { last, level: 'error'|'warn'|'info', query: regex } */
116
+ function analyze(opts = {}) {
117
+ const last = Math.min(Math.max(Number(opts.last) || 1500, 1), 5000);
118
+ const level = String(opts.level || '').toLowerCase();
119
+ let query = null;
120
+ if (opts.query !== undefined && opts.query !== null && String(opts.query).trim() !== '') {
121
+ try {
122
+ query = new RegExp(String(opts.query), 'i');
123
+ } catch {
124
+ return { ok: false, error: 'geçersiz regex (query)' };
125
+ }
126
+ }
127
+ const counts = {};
128
+ const groups = new Map();
129
+ const recent = [];
130
+ let scanned = 0;
131
+ for (const raw of tail(last)) {
132
+ const p = parseLine(raw);
133
+ if (!p) continue;
134
+ if ((level === 'error' || level === 'warn' || level === 'info') && p.level !== level) continue;
135
+ if (query && !(query.test(p.msg) || query.test(p.tag))) continue;
136
+ scanned++;
137
+ counts[p.level] = (counts[p.level] || 0) + 1;
138
+ const pattern = normalizeMsg(p.msg);
139
+ const key = p.tag + ' :: ' + pattern;
140
+ const g = groups.get(key);
141
+ if (g) {
142
+ g.count++;
143
+ g.levels[p.level] = (g.levels[p.level] || 0) + 1;
144
+ if (p.ts && (!g.firstTs || p.ts < g.firstTs)) g.firstTs = p.ts;
145
+ if (p.ts && (!g.lastTs || p.ts > g.lastTs)) g.lastTs = p.ts;
146
+ } else {
147
+ groups.set(key, {
148
+ pattern,
149
+ tag: p.tag,
150
+ count: 1,
151
+ levels: { [p.level]: 1 },
152
+ firstTs: p.ts,
153
+ lastTs: p.ts,
154
+ sample: p.msg.slice(0, 300),
155
+ });
156
+ }
157
+ if (recent.length < 12) recent.push(p);
158
+ }
159
+ const top = [...groups.values()]
160
+ .sort((a, b) => b.count - a.count)
161
+ .slice(0, 15)
162
+ .map((g) => ({
163
+ pattern: g.pattern,
164
+ tag: g.tag,
165
+ count: g.count,
166
+ levels: g.levels,
167
+ first: g.firstTs ? new Date(g.firstTs).toISOString() : null,
168
+ last: g.lastTs ? new Date(g.lastTs).toISOString() : null,
169
+ sample: g.sample,
170
+ }));
171
+ return {
172
+ ok: true,
173
+ scanned,
174
+ counts,
175
+ top,
176
+ recent: recent.map((p) => ({
177
+ ts: p.ts ? new Date(p.ts).toISOString() : null,
178
+ level: p.level,
179
+ tag: p.tag,
180
+ msg: p.msg.slice(0, 300),
181
+ })),
182
+ };
183
+ }
184
+
185
+ /* Son windowMin dakikadaki eşleşen kayıt sayısı (izleyici + hızlı kontrol için).
186
+ opts: { windowMin, level: 'error'|'warn'|'info', re: regex } */
187
+ function countSince(opts = {}) {
188
+ const windowMin = Math.min(Math.max(Number(opts.windowMin) || 10, 1), 720);
189
+ const level = String(opts.level || 'error').toLowerCase();
190
+ let re = null;
191
+ if (opts.re !== undefined && opts.re !== null && String(opts.re).trim() !== '') {
192
+ try {
193
+ re = new RegExp(String(opts.re), 'i');
194
+ } catch {
195
+ re = null;
196
+ }
197
+ }
198
+ const since = Date.now() - windowMin * 60000;
199
+ let n = 0;
200
+ for (const raw of tail(4000)) {
201
+ const p = parseLine(raw);
202
+ if (!p || p.ts === null || p.ts < since) continue;
203
+ if ((level === 'error' || level === 'warn' || level === 'info') && p.level !== level) continue;
204
+ if (re && !(re.test(p.msg) || re.test(p.tag))) continue;
205
+ n++;
206
+ }
207
+ return n;
208
+ }
209
+
210
+ module.exports = {
211
+ info,
212
+ warn,
213
+ error,
214
+ tail,
215
+ dir,
216
+ recent,
217
+ clear,
218
+ analyze,
219
+ countSince,
220
+ parseLine,
221
+ normalizeMsg,
222
+ LOG_DIR,
223
+ };
@@ -0,0 +1,11 @@
1
+ {
2
+ "id": "hesap-makinesi",
3
+ "name": "Hesap Makinesi",
4
+ "version": "1.0.0",
5
+ "description": "Güvenli matematik hesaplayıcı — hem ajan aracı (hesapla) hem tıklanabilir arayüz. Geçmiş app storage'da saklanır.",
6
+ "author": "beast",
7
+ "icon": "🧮",
8
+ "permissions": ["tools", "storage", "notify"],
9
+ "ui": "ui/index.html",
10
+ "builtin": true
11
+ }
@@ -0,0 +1,41 @@
1
+ 'use strict';
2
+
3
+ /* Hesap Makinesi — Beast App örneği.
4
+ Ajan için güvenli hesaplama aracı kaydeder; UI'daki geçmiş app storage'da yaşar. */
5
+
6
+ module.exports = (beast) => {
7
+ const SAFE_RE = /^[\s+\-*/().,%0-9e]*$/i;
8
+
9
+ function calc(expr) {
10
+ const cleaned = String(expr || '').replace(/,/g, '.').trim();
11
+ if (!cleaned) return { ok: false, error: 'ifade boş' };
12
+ if (!SAFE_RE.test(cleaned.replace(/e[+-]?\d+/gi, 'N'))) {
13
+ return { ok: false, error: 'yalnızca sayılar ve + - * / ( ) % , e işlemlerine izin var' };
14
+ }
15
+ try {
16
+ const val = Function('"use strict"; return (' + cleaned + ')')();
17
+ if (typeof val !== 'number' || !isFinite(val)) return { ok: false, error: 'sonuç sayı değil' };
18
+ return { ok: true, expression: cleaned, result: val };
19
+ } catch (e) {
20
+ return { ok: false, error: 'geçersiz ifade: ' + String((e && e.message) || e).slice(0, 120) };
21
+ }
22
+ }
23
+
24
+ beast.tools.register('hesapla', {
25
+ description: 'Güvenli matematik hesaplayıcı. Örnek: "(1200*1.2)/3". Sadece sayılar ve + - * / ( ) % e desteklenir.',
26
+ parameters: {
27
+ type: 'object',
28
+ properties: {
29
+ expression: { type: 'string', description: 'Hesaplanacak matematiksel ifade' },
30
+ },
31
+ required: ['expression'],
32
+ },
33
+ handler: (args) => calc(args.expression),
34
+ });
35
+
36
+ /* Geçmişi yükle (UI ile aynı storage'ı paylaşır) */
37
+ const history = beast.storage.get('history', []);
38
+ beast.log(`hazır — geçmişte ${history.length} hesap var`);
39
+
40
+ beast.notify('Hesap Makinesi hazır — araç: app__hesap-makinesi__hesapla');
41
+ };
@@ -0,0 +1,151 @@
1
+ <!DOCTYPE html>
2
+ <html lang="tr">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Hesap Makinesi</title>
6
+ <style>
7
+ :root { color-scheme: dark; }
8
+ * { box-sizing: border-box; margin: 0; padding: 0; user-select: none; }
9
+ body {
10
+ font-family: 'Segoe UI', system-ui, sans-serif;
11
+ background: #14151a; color: #e8e8ec;
12
+ display: flex; flex-direction: column; align-items: center;
13
+ padding: 18px 14px; gap: 14px; height: 100vh;
14
+ }
15
+ h1 { font-size: 15px; font-weight: 600; color: #9aa0b0; letter-spacing: .4px; }
16
+ #screen {
17
+ width: 100%; max-width: 320px; background: #0d0e12; border: 1px solid #26282f;
18
+ border-radius: 12px; padding: 14px; text-align: right; min-height: 74px;
19
+ display: flex; flex-direction: column; justify-content: center;
20
+ }
21
+ #expr { font-size: 14px; color: #8b90a0; min-height: 18px; word-break: break-all; }
22
+ #result { font-size: 28px; font-weight: 600; margin-top: 4px; word-break: break-all; }
23
+ #pad {
24
+ display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; width: 100%; max-width: 320px;
25
+ }
26
+ button {
27
+ height: 46px; border: 1px solid #26282f; border-radius: 10px; font-size: 17px;
28
+ background: #1c1e25; color: #e8e8ec; cursor: pointer; transition: background .12s;
29
+ }
30
+ button:hover { background: #262933; }
31
+ button:active { background: #2f3340; }
32
+ .op { color: #ffb454; }
33
+ .eq { background: #4f7cff; border-color: #4f7cff; color: #fff; grid-column: span 2; }
34
+ .clr { color: #ff6b6b; }
35
+ #hist { width: 100%; max-width: 320px; overflow-y: auto; flex: 1; display: flex; flex-direction: column; gap: 6px; }
36
+ .h-row {
37
+ background: #17181e; border: 1px solid #23252d; border-radius: 8px;
38
+ padding: 8px 10px; font-size: 13px; display: flex; justify-content: space-between; gap: 8px; cursor: pointer;
39
+ }
40
+ .h-row:hover { background: #1e2028; }
41
+ .h-e { color: #8b90a0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
42
+ .h-r { color: #9ecbff; font-weight: 600; }
43
+ #histHead { width: 100%; max-width: 320px; display: flex; justify-content: space-between; align-items: center; font-size: 12px; color: #7d8292; }
44
+ #histClear { background: none; border: none; color: #ff6b6b; font-size: 12px; cursor: pointer; height: auto; }
45
+ </style>
46
+ </head>
47
+ <body>
48
+ <h1>🧮 HESAP MAKİNESİ</h1>
49
+ <div id="screen">
50
+ <div id="expr">&nbsp;</div>
51
+ <div id="result">0</div>
52
+ </div>
53
+ <div id="pad"></div>
54
+ <div id="histHead"><span>GEÇMİŞ</span><button id="histClear">temizle</button></div>
55
+ <div id="hist"></div>
56
+ <script>
57
+ (function () {
58
+ const exprEl = document.getElementById('expr');
59
+ const resEl = document.getElementById('result');
60
+ const pad = document.getElementById('pad');
61
+ const hist = document.getElementById('hist');
62
+ let expr = '';
63
+ let history = [];
64
+
65
+ const store = window.beastApp;
66
+ function fmt(n) {
67
+ if (typeof n !== 'number' || !isFinite(n)) return '—';
68
+ return Math.abs(n) > 1e15 ? n.toExponential(6) : String(+n.toFixed(10));
69
+ }
70
+ function evalLocal(e) {
71
+ const cleaned = String(e || '').replace(/,/g, '.').trim();
72
+ if (!cleaned || !/^[\s+\-*/().,%0-9e]*$/i.test(cleaned.replace(/e[+-]?\d+/gi, 'N'))) return null;
73
+ try {
74
+ const v = Function('"use strict"; return (' + cleaned + ')')();
75
+ return typeof v === 'number' && isFinite(v) ? v : null;
76
+ } catch { return null; }
77
+ }
78
+ function render() {
79
+ exprEl.textContent = expr || '\u00A0';
80
+ const v = evalLocal(expr);
81
+ resEl.textContent = expr ? (v === null ? '…' : fmt(v)) : '0';
82
+ }
83
+ function save() {
84
+ try { store && store.storageSet('history', history.slice(0, 30)); } catch {}
85
+ }
86
+ function pushHistory(e, r) {
87
+ history.unshift({ e, r });
88
+ history = history.slice(0, 30);
89
+ save();
90
+ renderHist();
91
+ }
92
+ function renderHist() {
93
+ hist.innerHTML = '';
94
+ for (const h of history) {
95
+ const row = document.createElement('div');
96
+ row.className = 'h-row';
97
+ row.innerHTML = '<span class="h-e"></span><span class="h-r"></span>';
98
+ row.querySelector('.h-e').textContent = h.e;
99
+ row.querySelector('.h-r').textContent = fmt(h.r);
100
+ row.addEventListener('click', () => { expr = h.e; render(); });
101
+ hist.appendChild(row);
102
+ }
103
+ }
104
+ function equals() {
105
+ if (!expr) return;
106
+ const v = evalLocal(expr);
107
+ if (v === null) { resEl.textContent = 'HATA'; return; }
108
+ pushHistory(expr, v);
109
+ if (store) store.notify('Hesap: ' + expr + ' = ' + fmt(v));
110
+ resEl.textContent = fmt(v);
111
+ expr = String(v);
112
+ render();
113
+ }
114
+ const keys = [
115
+ ['C', 'clr'], ['(', 'op'], [')', 'op'], ['⌫', 'clr'],
116
+ ['7'], ['8'], ['9'], ['/', 'op'],
117
+ ['4'], ['5'], ['6'], ['*', 'op'],
118
+ ['1'], ['2'], ['3'], ['-', 'op'],
119
+ ['0'], ['.', '%'], ['=', 'eq'],
120
+ ];
121
+ for (const [label, cls] of keys) {
122
+ const b = document.createElement('button');
123
+ b.textContent = label;
124
+ if (cls) b.className = cls;
125
+ b.addEventListener('click', () => {
126
+ if (label === 'C') { expr = ''; }
127
+ else if (label === '⌫') { expr = expr.slice(0, -1); }
128
+ else if (label === '=') { equals(); return; }
129
+ else { expr += label; }
130
+ render();
131
+ });
132
+ pad.appendChild(b);
133
+ }
134
+ document.getElementById('histClear').addEventListener('click', () => { history = []; save(); renderHist(); });
135
+ document.addEventListener('keydown', (e) => {
136
+ if (/^[0-9+\-*/().%]$/.test(e.key)) { expr += e.key; render(); }
137
+ else if (e.key === 'Enter') { e.preventDefault(); equals(); }
138
+ else if (e.key === 'Backspace') { expr = expr.slice(0, -1); render(); }
139
+ else if (e.key === 'Escape') { expr = ''; render(); }
140
+ });
141
+ (async () => {
142
+ try {
143
+ if (store) history = (await store.storageGet('history', [])) || [];
144
+ } catch {}
145
+ renderHist();
146
+ render();
147
+ })();
148
+ })();
149
+ </script>
150
+ </body>
151
+ </html>
@@ -3,6 +3,8 @@
3
3
  /* Beast izleyiciler (watchers): arka planda periyodik kontrol.
4
4
  kind=web → URL periyodik çekilir, değer çıkarılır (json path / regex), koşul karşılaştırılır
5
5
  kind=battery → yerel pil yüzdesi izlenir
6
+ kind=logs → Beast log dosyası taranır; son windowMin dakikadaki error/warn sayısı
7
+ değer olur ("10 dk içinde 3+ hata olursa bağır")
6
8
  Koşul sağlanınca ilgili oturuma mesaj düşer (WA köprüsüne de otomatik akar).
7
9
  Depo: %APPDATA%\beast\watchers.json */
8
10
 
@@ -10,6 +12,7 @@ const fs = require('fs');
10
12
  const path = require('path');
11
13
  const { execFile } = require('child_process');
12
14
  const { beastRoot } = require('./memory');
15
+ const logger = require('./logger');
13
16
 
14
17
  function file() {
15
18
  return path.join(beastRoot(), 'watchers.json');
@@ -43,18 +46,28 @@ const OPS = ['lt', 'lte', 'gt', 'gte', 'eq', 'neq', 'changed'];
43
46
  function normalize(input) {
44
47
  const i = input || {};
45
48
  const name = String(i.name || '').trim().slice(0, 80);
46
- const kind = String(i.kind || '').trim().toLowerCase();
47
49
  if (!name) return { error: 'isim gerekli' };
48
- if (kind !== 'web' && kind !== 'battery') return { error: "kind 'web' ya da 'battery' olmalı" };
50
+ const kind = String(i.kind || '').trim().toLowerCase();
51
+ if (kind !== 'web' && kind !== 'battery' && kind !== 'logs') {
52
+ return { error: "kind 'web', 'battery' ya da 'logs' olmalı" };
53
+ }
49
54
  let url = '';
50
55
  if (kind === 'web') {
51
56
  url = String(i.url || '').trim();
52
57
  if (!/^https?:\/\//i.test(url)) return { error: 'web izleyicisi için geçerli http(s) url gerekli' };
53
58
  if (url.length > 2000) return { error: 'url çok uzun' };
54
59
  }
55
- const op = OPS.includes(String(i.op || '').toLowerCase()) ? String(i.op).toLowerCase() : 'lte';
56
- const value = i.value === undefined || i.value === null || i.value === '' ? null : i.value;
60
+ const opRaw = String(i.op || '').toLowerCase();
61
+ const op = OPS.includes(opRaw) ? opRaw : kind === 'logs' ? 'gt' : 'lte';
62
+ let value = i.value === undefined || i.value === null || i.value === '' ? null : i.value;
63
+ if (kind === 'logs' && value === null) value = 0; // varsayılan: 0'dan çoksa (yani 1+ kayıt)
57
64
  if (op !== 'changed' && value === null) return { error: 'bu op için value (eşik) gerekli' };
65
+ /* logs türü: seviye + kayan pencere (dakika) */
66
+ const level = ['error', 'warn', 'info'].includes(String(i.level || '').toLowerCase())
67
+ ? String(i.level).toLowerCase()
68
+ : 'error';
69
+ const rawWin = Number(i.windowMin ?? i.window_min);
70
+ const windowMin = Math.min(Math.max(Number.isFinite(rawWin) ? Math.round(rawWin) : 10, 1), 720);
58
71
  let re = '';
59
72
  if (i.re !== undefined && i.re !== null && String(i.re).trim() !== '') {
60
73
  try {
@@ -89,6 +102,8 @@ function normalize(input) {
89
102
  re,
90
103
  op,
91
104
  value,
105
+ level: kind === 'logs' ? level : undefined,
106
+ windowMin: kind === 'logs' ? windowMin : undefined,
92
107
  everyMin: Math.max(1, Math.round(everySec / 60)), // geriye dönük uyum
93
108
  everySec,
94
109
  cooldownMin,
@@ -242,10 +257,16 @@ function batteryLevel() {
242
257
  });
243
258
  }
244
259
 
260
+ /* LOG ZEKASI: son windowMin dakikada eşleşen log kaydı sayısı (değer = sayı) */
261
+ function logCount(w) {
262
+ return logger.countSince({ windowMin: w.windowMin, level: w.level, re: w.re });
263
+ }
264
+
245
265
  async function defaultCheck(w, deps = {}) {
246
266
  /* tek noktadan taklit (testler / özel köprüler) */
247
267
  if (typeof deps.check === 'function') return deps.check({ ...w });
248
268
  if (w.kind === 'battery') return deps.battery ? deps.battery() : batteryLevel();
269
+ if (w.kind === 'logs') return deps.logs ? deps.logs({ ...w }) : logCount(w);
249
270
  return deps.web ? deps.web({ ...w }) : webValue(w, deps.fetch);
250
271
  }
251
272
 
@@ -61,6 +61,7 @@ const TOOL_LABELS = {
61
61
  watcher_add: (a) => `İzleyici kur: ${a.name || ''}`,
62
62
  watcher_list: () => 'İzleyiciler',
63
63
  watcher_remove: (a) => `İzleyici sil: ${a.id || ''}`,
64
+ log_analyze: (a) => `Log analizi${a.level ? ' [' + a.level + ']' : ''}`,
64
65
  event_subscribe: (a) => `Olay aboneliği: ${a.type || ''}`,
65
66
  event_list: () => 'Olay abonelikleri',
66
67
  event_unsubscribe: (a) => `Abonelik sil: ${a.id || ''}`,