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.
Files changed (62) hide show
  1. package/.env.example +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +94 -0
  4. package/assets/app.ico +0 -0
  5. package/assets/tray.png +0 -0
  6. package/bin/beast-agent.js +23 -0
  7. package/config.example.yaml +26 -0
  8. package/eng.traineddata +0 -0
  9. package/package.json +101 -0
  10. package/scripts/swap-electron.js +40 -0
  11. package/src/agent/bus.js +384 -0
  12. package/src/agent/computeruse.js +200 -0
  13. package/src/agent/config.js +284 -0
  14. package/src/agent/engine.js +3118 -0
  15. package/src/agent/kb.js +123 -0
  16. package/src/agent/llm.js +219 -0
  17. package/src/agent/logger.js +90 -0
  18. package/src/agent/memory.js +383 -0
  19. package/src/agent/pdf.js +20 -0
  20. package/src/agent/scripts/__pycache__/websearch.cpython-312.pyc +0 -0
  21. package/src/agent/scripts/news.py +113 -0
  22. package/src/agent/scripts/websearch.py +225 -0
  23. package/src/agent/skills.js +522 -0
  24. package/src/agent/tokens.js +39 -0
  25. package/src/agent/tools.js +911 -0
  26. package/src/agent/usage.js +125 -0
  27. package/src/agent/watchers.js +312 -0
  28. package/src/agent/watext.js +75 -0
  29. package/src/agent/whatsapp.js +494 -0
  30. package/src/cron.js +245 -0
  31. package/src/main.js +3622 -0
  32. package/src/preload.js +122 -0
  33. package/src/renderer/browserPreload.js +73 -0
  34. package/src/renderer/i18n.js +757 -0
  35. package/src/renderer/index.html +227 -0
  36. package/src/renderer/renderer.js +3250 -0
  37. package/src/renderer/style.css +1559 -0
  38. package/tests/approval.test.js +56 -0
  39. package/tests/bg-jobs.test.js +315 -0
  40. package/tests/bus.test.js +46 -0
  41. package/tests/computeruse-context.test.js +69 -0
  42. package/tests/cron.test.js +95 -0
  43. package/tests/engine.test.js +140 -0
  44. package/tests/eval.test.js +91 -0
  45. package/tests/fallout.test.js +188 -0
  46. package/tests/llm.test.js +56 -0
  47. package/tests/memory.test.js +33 -0
  48. package/tests/memoryloop.test.js +26 -0
  49. package/tests/notegen.test.js +63 -0
  50. package/tests/owner.test.js +32 -0
  51. package/tests/python.test.js +75 -0
  52. package/tests/scenarios.json +81 -0
  53. package/tests/sessioncode.test.js +50 -0
  54. package/tests/setup.js +9 -0
  55. package/tests/tokens.test.js +42 -0
  56. package/tests/tools.test.js +84 -0
  57. package/tests/usage.test.js +44 -0
  58. package/tests/v11.test.js +76 -0
  59. package/tests/watchers.test.js +206 -0
  60. package/tests/watext.test.js +53 -0
  61. package/tests/whatsapp.test.js +103 -0
  62. package/tests/wherewasi.test.js +40 -0
@@ -0,0 +1,384 @@
1
+ 'use strict';
2
+
3
+ /* Beast Olay Merkezi (#4 EventBus proaktif mod)
4
+ Cron'suz olay mimarisi: kaynaklar olay üretir, abonelikler bildirim alır.
5
+
6
+ Kaynaklar:
7
+ mail:new — IMAP IDLE (ayarlı hesapta yeni mail)
8
+ fs:changed — workspace fs.watch (dosya/klasör değişimi)
9
+ webhook — 127.0.0.1 POST /beast-event <token> {type,data}
10
+ price:tick — Binance miniTicker WebSocket (sembol ayarlı)
11
+ wa:presence — WhatsApp karşı taraf yazıyor/kaydediyor köprüsü
12
+
13
+ Abonelik: { id, type, sessionId, op?, value?, cooldownMin?, path? }
14
+ Eşleşme: type birebir; varsa sayısal op/value filtresi event.value üzerinde.
15
+ Bildirim teslimi main'e devredilir (engine.send) — bus saf kalır. */
16
+
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+ const http = require('http');
20
+ const crypto = require('crypto');
21
+ const { beastRoot } = require('./memory');
22
+
23
+ function subsFile() {
24
+ return path.join(beastRoot(), 'subscriptions.json');
25
+ }
26
+
27
+ let subs = []; // aktif abonelikler
28
+ let loaded = false;
29
+ let hooks = {}; // { notify(sub, event), log(line) }
30
+ let running = {};
31
+
32
+ function uid() {
33
+ return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
34
+ }
35
+
36
+ function load() {
37
+ if (loaded) return;
38
+ loaded = true;
39
+ try {
40
+ const raw = JSON.parse(fs.readFileSync(subsFile(), 'utf8'));
41
+ subs = Array.isArray(raw.subscriptions) ? raw.subscriptions : [];
42
+ } catch {
43
+ subs = [];
44
+ }
45
+ }
46
+
47
+ function save() {
48
+ try {
49
+ fs.mkdirSync(beastRoot(), { recursive: true });
50
+ const tmp = subsFile() + '.tmp';
51
+ fs.writeFileSync(tmp, JSON.stringify({ subscriptions: subs }, null, 2));
52
+ fs.renameSync(tmp, subsFile());
53
+ } catch {}
54
+ }
55
+
56
+ function log(line) {
57
+ if (typeof hooks.log === 'function') hooks.log(`[BUS] ${line}`);
58
+ }
59
+
60
+ /* ---------- abonelik CRUD ---------- */
61
+
62
+ function listSubs() {
63
+ load();
64
+ return subs.map((s) => ({ ...s }));
65
+ }
66
+
67
+ function addSub({ type, sessionId, op, value, cooldownMin }) {
68
+ load();
69
+ const t = String(type || '').trim();
70
+ if (!t) return { ok: false, error: 'type gerekli' };
71
+ if (!sessionId) return { ok: false, error: 'sessionId gerekli' };
72
+ const KNOWN = ['mail:new', 'fs:changed', 'webhook', 'price:tick', 'wa:presence'];
73
+ if (!KNOWN.includes(t)) return { ok: false, error: `bilinmeyen olay tipi: ${t} (${KNOWN.join(', ')})` };
74
+ const sub = {
75
+ id: uid(),
76
+ type: t,
77
+ sessionId,
78
+ op: ['lt', 'lte', 'gt', 'gte', 'eq'].includes(op) ? op : null,
79
+ value: Number.isFinite(Number(value)) ? Number(value) : null,
80
+ cooldownMin: Math.max(0, Math.min(Math.round(Number(cooldownMin ?? 10)) || 10, 1440)),
81
+ createdAt: new Date().toISOString(),
82
+ lastNotifiedAt: 0,
83
+ };
84
+ if ((t === 'price:tick') && sub.op && sub.value === null) {
85
+ return { ok: false, error: 'fiyat aboneliğinde op verildiyse value de gerekli' };
86
+ }
87
+ subs.push(sub);
88
+ save();
89
+ log(`abonelik eklendi ${t} sid=${sessionId}`);
90
+ return { ok: true, subscription: { ...sub } };
91
+ }
92
+
93
+ function removeSub(id) {
94
+ load();
95
+ const before = subs.length;
96
+ subs = subs.filter((s) => s.id !== String(id));
97
+ save();
98
+ return { ok: subs.length < before };
99
+ }
100
+
101
+ /* ---------- eşleştirme + cooldown (saf, test edilebilir) ---------- */
102
+
103
+ function compareOp(op, a, b) {
104
+ switch (op) {
105
+ case 'lt': return a < b;
106
+ case 'lte': return a <= b;
107
+ case 'gt': return a > b;
108
+ case 'gte': return a >= b;
109
+ case 'eq': return a === b;
110
+ default: return false;
111
+ }
112
+ }
113
+
114
+ /** Olay bu aboneliği tetikler mi + cooldown'a takıldı mı döner */
115
+ function decide(sub, event, now) {
116
+ if (sub.type !== event.type) return { hit: false };
117
+ if (sub.op !== null && sub.value !== null) {
118
+ const v = Number(event.value);
119
+ if (!Number.isFinite(v) || !compareOp(sub.op, v, sub.value)) return { hit: false };
120
+ }
121
+ const cd = (sub.cooldownMin || 0) * 60000;
122
+ if (cd && now - (sub.lastNotifiedAt || 0) < cd) return { hit: true, cooled: true };
123
+ return { hit: true, cooled: false };
124
+ }
125
+
126
+ /** Bir olayı tüm aboneliklere dağıt; tetiklenenleri döndürür */
127
+ function emitEvent(type, data = {}, now = Date.now()) {
128
+ load();
129
+ const event = { type, data, value: Number(data.value), at: now };
130
+ const fired = [];
131
+ for (const s of subs.slice()) {
132
+ const d = decide(s, event, now);
133
+ if (!d.hit || d.cooled) continue;
134
+ s.lastNotifiedAt = now;
135
+ fired.push(s);
136
+ if (typeof hooks.notify === 'function') {
137
+ try {
138
+ hooks.notify({ ...s }, summarize(type, data));
139
+ } catch {}
140
+ }
141
+ }
142
+ if (fired.length) save();
143
+ return fired.length;
144
+ }
145
+
146
+ function summarize(type, data) {
147
+ switch (type) {
148
+ case 'mail:new':
149
+ return `[OLAY] Yeni e-posta geldi${data.from ? ': ' + data.from : ''}${data.subject ? ' — ' + data.subject : ''}. Kullanıcıya kısaca haber ver; email_read ile gövdeyi isteyebilir.`;
150
+ case 'fs:changed':
151
+ return `[OLAY] Workspace'te dosya değişimi: ${data.path || '?'}. Kullanıcıya kısaca haber ver.`;
152
+ case 'webhook':
153
+ return `[OLAY] Webhook: ${JSON.stringify(data).slice(0, 300)}. Kullanıcıya anlamlı biçimde raporla.`;
154
+ case 'price:tick':
155
+ return `[OLAY] Fiyat güncellemesi ${data.symbol || '?'}: ${data.price} (koşul sağlandı). Kullanıcıya hemen haber ver.`;
156
+ case 'wa:presence':
157
+ return `[OLAY] WhatsApp durum değişimi: ${data.status} (+${data.sender || '?'}). Kısa not düş.`;
158
+ default:
159
+ return `[OLAY] ${type}: ${JSON.stringify(data).slice(0, 200)}`;
160
+ }
161
+ }
162
+
163
+ /* ---------- kaynaklar ---------- */
164
+
165
+ /* IMAP IDLE: yeni mailleri anında yakalar. Koparsa 60 sn sonra yeniden. */
166
+ async function startMailIdle(getCfg) {
167
+ if (running.mailIdle) return;
168
+ running.mailIdle = true;
169
+ const attempt = async () => {
170
+ while (running.mailIdle) {
171
+ let ImapFlow = null;
172
+ try {
173
+ ImapFlow = require('imapflow');
174
+ } catch {
175
+ log('imapflow yok — mail:new kapalı');
176
+ return;
177
+ }
178
+ const cfg = getCfg() || {};
179
+ if (!cfg.host || !cfg.user || !cfg.pass) {
180
+ log('mail credential yok — IDLE beklemeye çekildi');
181
+ await sleep(120000);
182
+ continue;
183
+ }
184
+ let client = null;
185
+ try {
186
+ client = new ImapFlow({
187
+ host: cfg.host,
188
+ port: Number(cfg.port) || 993,
189
+ secure: true,
190
+ auth: { user: cfg.user, pass: cfg.pass },
191
+ logger: false,
192
+ });
193
+ await client.connect();
194
+ const lock = await client.getMailboxLock('INBOX');
195
+ log('IMAP IDLE bağlı');
196
+ /* IDLE akışı: EXISTS görünce kısa IDLE'dan çık, olay üret, tekrar gir */
197
+ for (;;) {
198
+ if (!running.mailIdle) break;
199
+ const lockPromise = new Promise((resolve) => client.once && client.once('exists', resolve));
200
+ // imapflow otomatik IDLE yapar; event tabanlı bekleme + periyodik nocop uyanma
201
+ await Promise.race([lockPromise, sleep(5 * 60000)]);
202
+ if (!running.mailIdle) break;
203
+ emitEvent('mail:new', {}, Date.now());
204
+ }
205
+ try { lock.release(); } catch {}
206
+ try { await client.logout(); } catch {}
207
+ } catch (e) {
208
+ log(`IDLE koptu: ${String((e && e.message) || e)} — 60 sn sonra yeniden`);
209
+ try { if (client) await client.logout(); } catch {}
210
+ await sleep(60000);
211
+ }
212
+ }
213
+ };
214
+ attempt().catch(() => {});
215
+ }
216
+
217
+ function stopMailIdle() {
218
+ running.mailIdle = false;
219
+ }
220
+
221
+ function sleep(ms) {
222
+ return new Promise((r) => setTimeout(r, ms));
223
+ }
224
+
225
+ /* fs.watch: workspace kökünde hafif izleme */
226
+ function startFsWatch(watchPath) {
227
+ if (running.fsWatch) return;
228
+ try {
229
+ running.fsWatch = fs.watch(
230
+ watchPath,
231
+ { recursive: true },
232
+ debounceEmit((eventType, filename) => {
233
+ emitEvent('fs:changed', { eventType, path: String(filename || '') }, Date.now());
234
+ }, 5000)
235
+ );
236
+ log(`fs.watch açık: ${watchPath}`);
237
+ } catch (e) {
238
+ log(`fs.watch hata: ${String((e && e.message) || e)}`);
239
+ }
240
+ }
241
+
242
+ function stopFsWatch() {
243
+ if (running.fsWatch) {
244
+ try { running.fsWatch.close(); } catch {}
245
+ running.fsWatch = null;
246
+ log('fs.watch kapandı');
247
+ }
248
+ }
249
+
250
+ /* aynı anda onlarca olayı tek bildirime indirger */
251
+ function debounceEmit(fn, ms) {
252
+ let t = null;
253
+ let lastArgs = null;
254
+ return (...args) => {
255
+ lastArgs = args;
256
+ clearTimeout(t);
257
+ t = setTimeout(() => {
258
+ fn(...lastArgs);
259
+ lastArgs = null;
260
+ }, ms);
261
+ };
262
+ }
263
+
264
+ /* local webhook: 127.0.0.1 bind, token doğrulaması zorunlu */
265
+ function startWebhook(port, token) {
266
+ if (running.webhookServer) return;
267
+ const server = http.createServer((req, res) => {
268
+ if (req.method !== 'POST' || !req.url.startsWith('/beast-event')) {
269
+ res.writeHead(404).end();
270
+ return;
271
+ }
272
+ if (!token || req.headers['x-beast-token'] !== token) {
273
+ res.writeHead(401).end('bad token');
274
+ return;
275
+ }
276
+ let body = '';
277
+ req.on('data', (c) => {
278
+ body += c;
279
+ if (body.length > 100000) req.destroy();
280
+ });
281
+ req.on('end', () => {
282
+ try {
283
+ const j = JSON.parse(body || '{}');
284
+ emitEvent(String(j.type || 'webhook'), j.data ?? j, Date.now());
285
+ res.writeHead(200).end('ok');
286
+ } catch {
287
+ res.writeHead(400).end('bad json');
288
+ }
289
+ });
290
+ });
291
+ try {
292
+ server.listen(port, '127.0.0.1', () => log(`webhook dinliyor: 127.0.0.1:${port}/beast-event`));
293
+ running.webhookServer = server;
294
+ } catch (e) {
295
+ log(`webhook başlatılamadı: ${String((e && e.message) || e)}`);
296
+ }
297
+ }
298
+
299
+ function stopWebhook() {
300
+ if (running.webhookServer) {
301
+ try { running.webhookServer.close(); } catch {}
302
+ running.webhookServer = null;
303
+ }
304
+ }
305
+
306
+ /* fiyat feed'i: Binance miniTicker WS (örn PAXGUSDT ≈ altın peg'i) */
307
+ function startPriceFeed(symbol = 'PAXGUSDT') {
308
+ if (running.priceWs) stopPriceFeed();
309
+ let Ws;
310
+ try {
311
+ Ws = require('ws');
312
+ } catch {
313
+ log('ws modülü yok — fiyat feed kapalı');
314
+ return;
315
+ }
316
+ const sym = String(symbol).toLowerCase();
317
+ try {
318
+ const ws = new Ws(`wss://stream.binance.com:9443/ws/${sym}@miniTicker`);
319
+ ws.on('open', () => log(`fiyat WS açık: ${symbol}`));
320
+ ws.on('message', (buf) => {
321
+ try {
322
+ const m = JSON.parse(buf.toString());
323
+ const close = Number(m.c);
324
+ if (Number.isFinite(close)) {
325
+ emitEvent('price:tick', { symbol, price: close, value: close }, Date.now());
326
+ }
327
+ } catch {}
328
+ });
329
+ ws.on('error', (e) => log(`fiyat WS hata: ${String((e && e.message) || e)}`));
330
+ ws.on('close', () => {
331
+ if (running.priceWs === ws) {
332
+ running.priceWs = null;
333
+ if (running.priceWanted) setTimeout(() => startPriceFeed(symbol), 15000);
334
+ }
335
+ });
336
+ running.priceWanted = true;
337
+ running.priceSymbol = symbol;
338
+ running.priceWs = ws;
339
+ } catch (e) {
340
+ log(`fiyat WS kurulamadı: ${String((e && e.message) || e)}`);
341
+ }
342
+ }
343
+
344
+ function stopPriceFeed() {
345
+ running.priceWanted = false;
346
+ if (running.priceWs) {
347
+ try { running.priceWs.close(); } catch {}
348
+ running.priceWs = null;
349
+ }
350
+ }
351
+
352
+ /* ---------- master ---------- */
353
+
354
+ function start(hooksIn, opts = {}) {
355
+ hooks = { ...hooks, ...(hooksIn || {}) };
356
+ load();
357
+ const o = opts || {};
358
+ if (o.mailIdle) startMailIdle(o.getCfg || (() => null));
359
+ else stopMailIdle();
360
+ if (o.fsWatch && o.workspace) startFsWatch(o.workspace);
361
+ else stopFsWatch();
362
+ if (o.webhookPort && o.webhookToken) startWebhook(o.webhookPort, o.webhookToken);
363
+ else stopWebhook();
364
+ if (o.priceSymbol) startPriceFeed(o.priceSymbol);
365
+ else stopPriceFeed();
366
+ }
367
+
368
+ function stopAll() {
369
+ stopMailIdle();
370
+ stopFsWatch();
371
+ stopWebhook();
372
+ stopPriceFeed();
373
+ }
374
+
375
+ module.exports = {
376
+ start,
377
+ stopAll,
378
+ listSubs,
379
+ addSub,
380
+ removeSub,
381
+ emitEvent,
382
+ decide,
383
+ compareOp,
384
+ };
@@ -0,0 +1,200 @@
1
+ 'use strict';
2
+
3
+ /* Beast Computer Use (#4): Windows GUI otomasyonu — ekran görüntüsü +
4
+ fare/klavye kontrolü. Ek paket YOK: Electron nativeImage ekranı verir,
5
+ mouse/klavye PowerShell üzerinden user32 API ile sürülür.
6
+
7
+ Güvenlik: yalnızca engine araç çağrılarıyla çalışır; koordinat sınırlı,
8
+ hız limiting'li (aksiyon arası min gecikme) ve yazma metni kırpılır. */
9
+
10
+ const { execFile } = require('child_process');
11
+
12
+ /* aksiyonlar arası minimum boşluk — wx'i art arda tıklarla donatmamak için */
13
+ let lastActionAt = 0;
14
+ function pace() {
15
+ const wait = 120 - (Date.now() - lastActionAt);
16
+ lastActionAt = Date.now();
17
+ if (wait > 0) return new Promise((r) => setTimeout(r, wait));
18
+ return Promise.resolve();
19
+ }
20
+
21
+ function ps(script) {
22
+ return new Promise((resolve, reject) => {
23
+ execFile(
24
+ 'powershell.exe',
25
+ ['-NoProfile', '-NonInteractive', '-Command', script],
26
+ { timeout: 20000, windowsHide: true },
27
+ (err, stdout) => (err ? reject(err) : resolve(String(stdout || '').trim()))
28
+ );
29
+ });
30
+ }
31
+
32
+ /** Sanal masaüstü çözünürlüğü */
33
+ async function screenSize() {
34
+ const out = await ps(
35
+ 'Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Width.ToString() + "x" + [System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Height'
36
+ );
37
+ const m = out.match(/(\d+)x(\d+)/);
38
+ if (!m) throw new Error('çözünürlük alınamadı');
39
+ return { w: Number(m[1]), h: Number(m[2]) };
40
+ }
41
+
42
+ /**
43
+ * Aksiyon uygula. op:
44
+ * click x y → sol tık
45
+ * dblclick x y → çift tık
46
+ * rightclick x y → sağ tık
47
+ * move x y → imleci taşı
48
+ * type text → klavyeyle yaz (Unicode destekli, SendInput WM_CHAR benzeri)
49
+ * key combo → "enter", "ctrl+s", "alt+tab", "win" ...
50
+ * scroll dx dy → tekerlek
51
+ */
52
+ async function act(op, args = {}) {
53
+ await pace();
54
+ const x = Math.max(0, Math.round(Number(args.x) || 0));
55
+ const y = Math.max(0, Math.round(Number(args.y) || 0));
56
+ switch (op) {
57
+ case 'move':
58
+ await mouseMove(x, y);
59
+ return { ok: true };
60
+ case 'click':
61
+ await mouseMove(x, y);
62
+ await clickSimple('left');
63
+ return { ok: true, at: { x, y } };
64
+ case 'dblclick':
65
+ await mouseMove(x, y);
66
+ await clickSimple('left');
67
+ await sleep(70);
68
+ await clickSimple('left');
69
+ return { ok: true, at: { x, y } };
70
+ case 'rightclick':
71
+ await mouseMove(x, y);
72
+ await clickSimple('right');
73
+ return { ok: true, at: { x, y } };
74
+ case 'type': {
75
+ const text = String(args.text ?? '').slice(0, 2000);
76
+ if (!text) return { ok: false, error: 'boş metin' };
77
+ /* Add-Type SendInput tabanlı Unicode typer */
78
+ await ps(buildTyperScript(text));
79
+ return { ok: true, chars: text.length };
80
+ }
81
+ case 'key': {
82
+ const combo = String(args.combo || args.key || '').trim().slice(0, 40);
83
+ if (!combo) return { ok: false, error: 'tuş gerekli' };
84
+ await ps(buildKeyScript(combo));
85
+ return { ok: true, key: combo };
86
+ }
87
+ case 'scroll': {
88
+ const dy = Math.max(-10, Math.min(10, Math.round(Number(args.dy) || 0)));
89
+ if (!dy) return { ok: false, error: 'dy gerekli (-10..10)' };
90
+ await mouseMove(x || 640, y || 360);
91
+ const clicks = Math.abs(dy) * 3;
92
+ for (let i = 0; i < clicks; i++) {
93
+ await wheel(dy > 0 ? 1 : -1); // pozitif dy = aşağı
94
+ }
95
+ return { ok: true, dy };
96
+ }
97
+ default:
98
+ return { ok: false, error: `bilinmeyen op: ${op}` };
99
+ }
100
+ }
101
+
102
+ function sleep(ms) {
103
+ return new Promise((r) => setTimeout(r, ms));
104
+ }
105
+
106
+ /* ---------- PowerShell snippet üreticileri ---------- */
107
+
108
+ const MOUSE_MOVE_TPL = `
109
+ Add-Type @"
110
+ using System;
111
+ using System.Runtime.InteropServices;
112
+ public class BM {
113
+ [DllImport("user32.dll")] public static extern bool SetCursorPos(int X, int Y);
114
+ }
115
+ "@
116
+ [BM]::SetCursorPos(%%X%%, %%Y%%) | Out-Null
117
+ `;
118
+
119
+ const WHEEL_TPL = `
120
+ Add-Type @"
121
+ using System;
122
+ using System.Runtime.InteropServices;
123
+ public class BW {
124
+ [DllImport("user32.dll")] public static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint dwData, UIntPtr dwExtraInfo);
125
+ }
126
+ "@
127
+ [BW]::mouse_event(0x0800, 0, 0, %%DELTA%%, [UIntPtr]::Zero)
128
+ `;
129
+
130
+ /* Unicode güvenli yazım: SendInput yerine pano + Ctrl+V fallback'u en sağlamı.
131
+ Panoyu geri yüklemek yerine tek seferlik kullanıp bırakıyoruz (hız öncelik). */
132
+ function buildTyperScript(text) {
133
+ const b64 = Buffer.from(text, 'utf16le').toString('base64');
134
+ return `
135
+ $t = [System.Text.Encoding]::Unicode.GetString([Convert]::FromBase64String("${b64}"))
136
+ Add-Type -AssemblyName System.Windows.Forms
137
+ [System.Windows.Forms.Clipboard]::SetText($t)
138
+ Start-Sleep -Milliseconds 80
139
+ [System.Windows.Forms.SendKeys]::SendWait("^v")
140
+ `;
141
+ }
142
+
143
+ /* Tuş kombosu: enter/tab/esc/f1-12, ctrl/alt/shift+x, win vb. */
144
+ function buildKeyScript(combo) {
145
+ const map = {
146
+ enter: '{ENTER}', tab: '{TAB}', esc: '{ESC}', escape: '{ESC}',
147
+ backspace: '{BACKSPACE}', delete: '{DELETE}', del: '{DELETE}',
148
+ home: '{HOME}', end: '{END}', pgup: '{PGUP}', pgdn: '{PGDN}',
149
+ up: '{UP}', down: '{DOWN}', left: '{LEFT}', right: '{RIGHT}',
150
+ space: ' ', win: '^({ESC})',
151
+ };
152
+ let out = '';
153
+ const parts = String(combo).toLowerCase().split('+').map((p) => p.trim());
154
+ let mods = '';
155
+ for (const p of parts) {
156
+ if (p === 'ctrl') mods += '^';
157
+ else if (p === 'alt') mods += '%';
158
+ else if (p === 'shift') mods += '+';
159
+ else if (p === 'win') out += ''; // win ayrı ele
160
+ else if (/^f\d{1,2}$/.test(p)) out += '{' + p.toUpperCase() + '}';
161
+ else if (map[p]) out += map[p];
162
+ else out += p.slice(0, 1).toUpperCase(); // tek harf/tuş
163
+ }
164
+ const seq = mods && out ? mods + '(' + out + ')' : mods + out;
165
+ const b64 = Buffer.from(seq, 'utf16le').toString('base64');
166
+ return `
167
+ $s = [System.Text.Encoding]::Unicode.GetString([Convert]::FromBase64String("${b64}"))
168
+ Add-Type -AssemblyName System.Windows.Forms
169
+ [System.Windows.Forms.SendKeys]::SendWait($s)
170
+ `;
171
+ }
172
+
173
+ async function mouseMove(x, y) {
174
+ await ps(MOUSE_MOVE_TPL.replace(/%%X%%/g, String(x)).replace(/%%Y%%/g, String(y)));
175
+ }
176
+
177
+ /* down+up tek script'te — karışık replace zinciri yerine bu kullanılır */
178
+ async function clickSimple(button) {
179
+ const down = button === 'right' ? 0x0008 : 0x0002;
180
+ const up = button === 'right' ? 0x0010 : 0x0004;
181
+ await ps(`
182
+ Add-Type @"
183
+ using System;
184
+ using System.Runtime.InteropServices;
185
+ public class BC2 {
186
+ [DllImport("user32.dll")] public static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint dwData, UIntPtr dwExtraInfo);
187
+ }
188
+ "@
189
+ [BC2]::mouse_event(${down}, 0, 0, 0, [UIntPtr]::Zero)
190
+ Start-Sleep -Milliseconds 45
191
+ [BC2]::mouse_event(${up}, 0, 0, 0, [UIntPtr]::Zero)
192
+ `);
193
+ }
194
+
195
+ async function wheel(dir) {
196
+ const delta = dir < 0 ? -240 : 240; // WHEEL_DELTA=120 katları; negatif=yukarı görüntüde aşağı kayar bazı app'lerde
197
+ await ps(WHEEL_TPL.replace(/%%DELTA%%/g, String(delta)));
198
+ }
199
+
200
+ module.exports = { act, screenSize, clickSimple, mouseMove };