beast-agent 2.76.5 → 2.77.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 (54) hide show
  1. package/package.json +1 -1
  2. package/src/agent/bots.js +669 -664
  3. package/src/agent/customtools.js +6 -19
  4. package/src/agent/engine.js +55 -307
  5. package/src/agent/seeds/tool-yazma/SKILL.md +13 -16
  6. package/src/agent/skills.js +14 -35
  7. package/src/agent/supertonic.js +622 -0
  8. package/src/agent/tools.js +3 -7
  9. package/src/main.js +77 -6068
  10. package/src/preload.js +2 -17
  11. package/src/renderer/i18n.js +14 -8
  12. package/src/renderer/index.html +0 -297
  13. package/src/renderer/renderer.js +142 -2280
  14. package/src/renderer/style.css +3 -825
  15. package/src/agent/defaulttools/mt5_barlar2/run.js +0 -188
  16. package/src/agent/defaulttools/mt5_barlar2/tool.json +0 -23
  17. package/src/agent/defaulttools/mt5_bekleyen/run.js +0 -105
  18. package/src/agent/defaulttools/mt5_bekleyen/tool.json +0 -18
  19. package/src/agent/defaulttools/mt5_durum/run.js +0 -93
  20. package/src/agent/defaulttools/mt5_durum/tool.json +0 -10
  21. package/src/agent/defaulttools/mt5_emir/run.js +0 -98
  22. package/src/agent/defaulttools/mt5_emir/tool.json +0 -17
  23. package/src/agent/defaulttools/mt5_emir_iptal/run.js +0 -90
  24. package/src/agent/defaulttools/mt5_emir_iptal/tool.json +0 -12
  25. package/src/agent/defaulttools/mt5_fiyat/run.js +0 -87
  26. package/src/agent/defaulttools/mt5_fiyat/tool.json +0 -16
  27. package/src/agent/defaulttools/mt5_gecmis/run.js +0 -87
  28. package/src/agent/defaulttools/mt5_gecmis/tool.json +0 -12
  29. package/src/agent/defaulttools/mt5_kapat/run.js +0 -108
  30. package/src/agent/defaulttools/mt5_kapat/tool.json +0 -15
  31. package/src/agent/defaulttools/mt5_m15_m5/m15m5.py +0 -60
  32. package/src/agent/defaulttools/mt5_m15_m5/run.js +0 -73
  33. package/src/agent/defaulttools/mt5_m15_m5/tool.json +0 -10
  34. package/src/agent/defaulttools/mt5_pozisyon_gecmis/run.js +0 -144
  35. package/src/agent/defaulttools/mt5_pozisyon_gecmis/tool.json +0 -12
  36. package/src/agent/defaulttools/mt5_shot/run.js +0 -649
  37. package/src/agent/defaulttools/mt5_shot/stitch.js +0 -151
  38. package/src/agent/defaulttools/mt5_shot/tool.json +0 -25
  39. package/src/agent/defaulttools/mt5_sltp/run.js +0 -92
  40. package/src/agent/defaulttools/mt5_sltp/tool.json +0 -14
  41. package/src/agent/financetools.js +0 -1025
  42. package/src/agent/finindicators.js +0 -254
  43. package/src/agent/finrisk.js +0 -289
  44. package/src/agent/finstats.js +0 -239
  45. package/src/agent/finwatch.js +0 -302
  46. package/src/agent/mt5/BeastFinance.mq5 +0 -498
  47. package/src/agent/mt5/BeastShot.tpl +0 -0
  48. package/src/agent/mt5setup.js +0 -542
  49. package/src/agent/scripts/mt5_bridge.py +0 -575
  50. package/src/agent/seeds/haber-duygu/SKILL.md +0 -49
  51. package/src/agent/seeds/mql5/SKILL.md +0 -154
  52. package/src/agent/seeds/price-action/SKILL.md +0 -60
  53. package/src/agent/seeds/risk-yonetimi/SKILL.md +0 -52
  54. package/src/mt5bridge.js +0 -215
@@ -1,649 +0,0 @@
1
- 'use strict';
2
- /* mt5_shot — MT5 grafiğinden GERÇEK PNG screenshot (BeastFinance EA "shot" komutu)
3
- stdin: JSON args -> stdout: SADECE JSON sonuç
4
-
5
- Girdi : {symbol?, symbols?:["GOLD","EURUSD"], timeframe?, timeframes?:["M1","M15"], layout?:"h"|"v", width?, height?, file?, template?, timeoutSec?, outdir?, diag?, embed?}
6
- Çıktı : {ok:true, path, symbol, symbols, timeframe, timeframes, panels:[...], ...} | {ok:false, err, error, cmdId, filesDir, ...}
7
- err kodları: no_terminal | ea_offline | cmd_write_fail | ea_no_ack | ea_error | png_yok | timeout | stitch_fail | stitch_write_fail
8
-
9
- ÇOKLU SEMBOL: symbols:["GOLD","EURUSD","BTCUSD"] (2-4 sembol) verilirse her
10
- sembolden bir kare çekilir ve TEK PNG'de birleştirilir; timeframes ile
11
- birlikte verilirse sembol × periyot ızgarası kurulur (en fazla 6 kare).
12
- İzleme listesinin tamamı TEK görselde ekibe gönderilebilir.
13
-
14
- ÇOKLU PERİYOT: timeframes:["M1","M15"] (2-3 adet) verilirse her periyot ayrı
15
- çekilir ve TEK PNG'de birleştirilir (layout:"h" soldan sağa — varsayılan,
16
- "v" üstten alta). Panellerin SOL ÜSTüne "SEMBOL · PERİYOT" etiketi çizilir;
17
- sonuçta panels[{symbol, timeframe, position, path}] hangi tarafın hangi
18
- sembol/periyot olduğunu söyler. tf2:"M15" kısayolu da (timeframe + tf2) kabul edilir.
19
-
20
- Notlar:
21
- - beast_cmd.json'a {id, ts, cmd, params} yazılır (UTF-16 BOM; EA FILE_UNICODE okur).
22
- - Ack dosyası POLL edilir; PNG MQL5\Files altında belirene kadar beklenir.
23
- - EA v1.22 şu şablonu işler: {"id":"..","ts":..,"cmd":"shot","params":{file,width,height,symbol?,timeframe?,template?}}
24
- symbol/timeframe aktif grafikten farklıysa EA ChartOpen+ChartSetSymbolPeriod ile geçici grafik açar.
25
- EA ack'i PNG üretimini BEKLEMEDEN yazar (ChartScreenShot asenkron; geçici grafik kapatma
26
- bir sonraki timer turuna ertelenir) — ack = "komut işlendi", PNG ayrıca beklenir.
27
- - PNG, send_file'ın engellediği %APPDATA%\beast altından çıkarılıp kullanıcı klasörüne
28
- (varsayılan <home>\beast_shots) kopyalanır; dönen "path" doğrudan send_file'a verilebilir.
29
- - GÖRSEL ENJEKSİYONU: başarılı sonuçta PNG data URL olarak __injectImage alanına gömülür;
30
- engine bunu sonraki turda ajana gösterir (embed:false kapatır) — ajan grafiği görerek analiz eder.
31
- - Watchdog: araç hiçbir koşulda asılı kalmaz; 80 sn'de {ok:false, err:"timeout"} döner.
32
- */
33
- const fs = require('fs');
34
- const os = require('os');
35
- const path = require('path');
36
-
37
- const SHOT_DIR = path.join(__dirname, 'shots'); // yedek kopya (Beast altı)
38
- const PUB_DIR_DEFAULT = path.join(os.homedir(), 'beast_shots'); // send_file edilebilir kopya
39
- const LOCK_FILE = path.join(__dirname, '.shot.lock');
40
- const LOCK_STALE_MS = 45000;
41
- const HARD_MS = 80000;
42
- const DEFAULT_TIMEOUT_SEC = 20;
43
-
44
- /* ---------------- temel yardımcılar ----------------
45
- ÖNEMLİ: beklemeler ASENKRON olmalı — Atomics.wait tabanlı senkron uyku ana
46
- thread'i kilitler, watchdog ZAMANLAYICISI ateşlenemez ve araç 80 sn yerine
47
- customtools'un 90 sn kill'ine takılırdı ("tool zaman aşımı" + ack kaybı). */
48
- function sleep(ms) {
49
- return new Promise((r) => setTimeout(r, ms));
50
- }
51
-
52
- let CTX = { cmdId: null, filesDir: null, started: Date.now() };
53
- let ARGS = {};
54
- let WATCHDOG = null;
55
-
56
- function out(o) {
57
- try { fs.writeSync(1, JSON.stringify(o) + '\n'); }
58
- catch (e) { try { console.log(JSON.stringify(o)); } catch (x) {} }
59
- try { clearTimeout(WATCHDOG); } catch (e) {}
60
- try { releaseLock(); } catch (e) {}
61
- process.exit(0);
62
- }
63
-
64
- // her hata yolu TEK yerden geçer: ok:false + err kodu + cmdId + filesDir
65
- function fail(code, msg, extra) {
66
- const o = Object.assign({
67
- ok: false,
68
- err: code,
69
- error: msg,
70
- cmdId: CTX.cmdId,
71
- filesDir: CTX.filesDir,
72
- ms: Date.now() - CTX.started
73
- }, extra || {});
74
- console.error('[mt5_shot] ' + code + ': ' + msg);
75
- out(o);
76
- }
77
-
78
- /* ---------------- kilit (eşzamanlı çağrılar ack'i ezmesin) ---------------- */
79
- function releaseLock() { try { fs.unlinkSync(LOCK_FILE); } catch (e) {} }
80
-
81
- async function acquireLock(waitMs) {
82
- const deadline = Date.now() + waitMs;
83
- for (;;) {
84
- try { fs.writeFileSync(LOCK_FILE, JSON.stringify({ pid: process.pid, at: Date.now() }), { flag: 'wx' }); return true; }
85
- catch (e) {
86
- let st = null;
87
- try { st = fs.statSync(LOCK_FILE); } catch (x) {
88
- // kilit kayboldu/kilitli kaldı: kısa bekle, sonsuz döngüye girme
89
- if (Date.now() >= deadline) return false;
90
- await sleep(200);
91
- continue;
92
- }
93
- if (Date.now() - st.mtimeMs > LOCK_STALE_MS) { // ölü kilit: devral
94
- try { fs.unlinkSync(LOCK_FILE); } catch (x) {}
95
- continue;
96
- }
97
- if (Date.now() >= deadline) return false;
98
- await sleep(400);
99
- }
100
- }
101
- }
102
-
103
- /* ---------------- MT5 köprüsü ---------------- */
104
- function findTerminalData() {
105
- const base = path.join(process.env.APPDATA || '', 'MetaQuotes', 'Terminal');
106
- if (!fs.existsSync(base)) return null;
107
- let best = null;
108
- for (const d of fs.readdirSync(base)) {
109
- const files = path.join(base, d, 'MQL5', 'Files');
110
- let fresh = 0;
111
- for (const f of ['beast_ea.json', 'beast_cmd.json', 'beast_cmd_ack.json']) {
112
- try {
113
- const p = path.join(files, f);
114
- if (fs.existsSync(p)) fresh = Math.max(fresh, fs.statSync(p).mtimeMs);
115
- } catch (e) {}
116
- }
117
- if (!fresh) continue;
118
- if (!best || fresh > best.m) best = { dir: path.join(base, d), files, m: fresh };
119
- }
120
- return best;
121
- }
122
-
123
- function readMaybeUtf16(fp) {
124
- const buf = fs.readFileSync(fp);
125
- let s;
126
- if (buf.length >= 2 && buf[0] === 0xFF && buf[1] === 0xFE) s = buf.slice(2).toString('utf16le');
127
- else s = buf.toString('utf8');
128
- return s.replace(/^\uFEFF/, '').replace(/\0/g, '');
129
- }
130
-
131
- // EA FILE_UNICODE ile okur -> BOM'lu UTF-16 yaz; rename EBUSY olursa doğrudan yaz
132
- function writeUtf16(fp, str) {
133
- const buf = Buffer.concat([Buffer.from([0xFF, 0xFE]), Buffer.from(str, 'utf16le')]);
134
- const tmp = fp + '.tmp' + process.pid;
135
- try {
136
- fs.writeFileSync(tmp, buf);
137
- fs.renameSync(tmp, fp);
138
- } catch (e) {
139
- try { fs.unlinkSync(tmp); } catch (x) {}
140
- fs.writeFileSync(fp, buf);
141
- }
142
- }
143
-
144
- function readJson(fp) {
145
- try { return JSON.parse(readMaybeUtf16(fp)); } catch (e) { return null; }
146
- }
147
-
148
- function heartbeatAge(beatFile) {
149
- try { return Math.round((Date.now() - fs.statSync(beatFile).mtimeMs) / 1000); } catch (e) { return null; }
150
- }
151
-
152
- // MT5 period kodu -> okunabilir periyot adı (16385 = M15)
153
- const TF_NAMES = { 1:'M1',2:'M2',3:'M3',4:'M4',5:'M5',6:'M6',10:'M10',12:'M12',15:'M15',20:'M20',30:'M30',
154
- 16385:'H1',16386:'H2',16387:'H3',16388:'H4',16390:'H6',16392:'H8',16396:'H12',16408:'D1',32769:'W1',49153:'MN1' };
155
- function tfName(p) {
156
- const n = Number(p);
157
- return TF_NAMES[n] || (TF_NAMES[Math.round(n)] || String(p == null ? '' : p));
158
- }
159
-
160
- /* ack yoksa: kanal sağlam mı? kısa bir ping ile ayırt et */
161
- async function eaPing(filesDir, budgetMs) {
162
- const id = 'P' + Date.now().toString(36);
163
- const cmdFile = path.join(filesDir, 'beast_cmd.json');
164
- const ackFile = path.join(filesDir, 'beast_cmd_ack.json');
165
- const t0 = Date.now();
166
- try { writeUtf16(cmdFile, JSON.stringify({ id, ts: Date.now(), cmd: 'ping' })); }
167
- catch (e) { return { ok: false, error: 'ping yazılamadı: ' + e.message }; }
168
- const dl = Date.now() + budgetMs;
169
- while (Date.now() < dl) {
170
- await sleep(300);
171
- const a = readJson(ackFile);
172
- if (a && a.id === id) return { ok: true, ack: a, ms: Date.now() - t0 };
173
- }
174
- return { ok: false, error: 'ping yanıtı yok (' + Math.round((Date.now() - t0) / 1000) + ' sn)' };
175
- }
176
-
177
- /* ---------------- ana akış ---------------- */
178
-
179
- /* çoklu periyot listesi: ["M1","M15"] ya da "M1,M15"; tf2 kısayolu destekli */
180
- function normalizeTfs(a) {
181
- let t = a.timeframes;
182
- if ((t == null || t === '') && a.tf2) t = [a.timeframe, a.tf2];
183
- if (typeof t === 'string') t = t.split(/[,\s|+/]+/);
184
- if (!Array.isArray(t)) return [];
185
- const out = [];
186
- for (const x of t) {
187
- const s = String(x == null ? '' : x).trim().toUpperCase();
188
- if (s && !out.includes(s)) out.push(s);
189
- }
190
- return out.slice(0, 3); /* 80 sn watchdog bütçesi: en fazla 3 panel */
191
- }
192
-
193
- /* çoklu sembol listesi: ["GOLD","EURUSD"] ya da "GOLD,EURUSD";
194
- symbol alanı da listeye katılır (symbol + symbols birlikte verilebilir) */
195
- function normalizeSymbols(a) {
196
- const out = [];
197
- const add = (x) => {
198
- const s = String(x == null ? '' : x).trim().toUpperCase();
199
- if (s && !out.includes(s)) out.push(s);
200
- };
201
- add(a.symbol);
202
- let s = a.symbols;
203
- if (typeof s === 'string') s = s.split(/[,\s|+/]+/);
204
- if (Array.isArray(s)) for (const x of s) add(x);
205
- return out.slice(0, 4);
206
- }
207
-
208
- /* PANEL PLANI: symbols × timeframes çarpımı → sırayla çekilecek kareler.
209
- - symbols 2+ → her sembolden bir kare (timeframes da verilmişse her periyot)
210
- - tek sembol + timeframes 2+ → eski çoklu periyot davranışı
211
- - tek karelik işler plana girmez: [] döner (tek çekim yolu kullanılır)
212
- Dönüş: [{symbol, timeframe}] — en fazla MAX_PANELS kare (watchdog bütçesi) */
213
- const MAX_PANELS = 6;
214
- function buildPlan(a) {
215
- const A = a || ARGS;
216
- const symbols = normalizeSymbols(A);
217
- const tfs = normalizeTfs(A);
218
- const out = [];
219
- const push = (symbol, timeframe) => {
220
- if (out.length < MAX_PANELS) out.push({ symbol, timeframe });
221
- };
222
- if (symbols.length >= 2) {
223
- const per = tfs.length ? tfs : [null];
224
- for (const s of symbols) for (const tf of per) push(s, tf);
225
- return out;
226
- }
227
- if (tfs.length >= 2) for (const tf of tfs) push(symbols[0] || null, tf);
228
- return out;
229
- }
230
-
231
- function sanitizeFile(name, fallback) {
232
- let f = (name && String(name).trim()) ? path.basename(String(name).trim()) : fallback;
233
- if (!/\.(png|gif|bmp|jpg|jpeg)$/i.test(f)) f += '.png';
234
- return f;
235
- }
236
-
237
- /* tek kare: EA'ya shot komutu yaz → ack bekle → PNG'yi bekle */
238
- async function captureOne(o) {
239
- CTX.cmdId = o.id;
240
- const payload = { id: o.id, ts: Date.now(), cmd: 'shot', params: o.params };
241
- try { if (fs.existsSync(o.ackFile)) fs.unlinkSync(o.ackFile); } catch (e) {}
242
- try { writeUtf16(o.cmdFile, JSON.stringify(payload)); }
243
- catch (e) { return { ok: false, err: 'cmd_write_fail', error: 'beast_cmd.json yazılamadı: ' + e.message, extra: { sent: payload } }; }
244
-
245
- /* --- ack POLL (cmd dosyasının EA tarafından tüketilmesi de izlenir) --- */
246
- let ack = null, cmdConsumed = false;
247
- const ackDeadline = Date.now() + o.timeoutSec * 1000;
248
- while (Date.now() < ackDeadline) {
249
- await sleep(250);
250
- const a = readJson(o.ackFile);
251
- if (a && a.id === o.id) { ack = a; break; }
252
- try {
253
- if (!fs.existsSync(o.cmdFile)) cmdConsumed = true;
254
- else {
255
- const c = readJson(o.cmdFile);
256
- if (c && c.id && c.id !== o.id) cmdConsumed = true; // başka bir çağrı ezip geçti
257
- }
258
- } catch (e) {}
259
- }
260
-
261
- if (!ack) {
262
- const extra = {
263
- heartbeat_age_sec: heartbeatAge(o.beatFile),
264
- cmd_consumed: cmdConsumed,
265
- timeout_sec: o.timeoutSec,
266
- sent: payload
267
- };
268
- if (o.diag) {
269
- extra.diagnostic = cmdConsumed
270
- ? 'EA komutu aldı ama ack yazmadı (shot işleyicisi hata verdi / PNG üretimi bloke)'
271
- : 'EA komutu hiç almadı (zamanlayıcı durmuş ya da komut dosyası okunamadı)';
272
- extra.ping = await eaPing(o.filesDir, 6000);
273
- }
274
- return { ok: false, err: 'ea_no_ack', error: 'EA ' + o.timeoutSec + ' sn içinde ack vermedi (id=' + o.id + ', heartbeat ' +
275
- (extra.heartbeat_age_sec === null ? '?' : extra.heartbeat_age_sec) + ' sn, cmd tüketildi=' + cmdConsumed + ')', extra };
276
- }
277
-
278
- if (ack.ok === false) {
279
- return { ok: false, err: 'ea_error', error: 'EA hata döndürdü: ' + (ack.error || 'bilinmeyen'), extra: { ack } };
280
- }
281
-
282
- /* --- PNG dosyasını bekle --- */
283
- const shotFile = (ack.result && ack.result.file) ? path.basename(String(ack.result.file)) : o.params.file;
284
- const src = path.join(o.filesDir, shotFile);
285
- let size = 0;
286
- const pngDeadline = Math.max(ackDeadline, Date.now() + 10000);
287
- for (;;) {
288
- try { if (fs.existsSync(src)) { size = fs.statSync(src).size; if (size > 1500) break; } } catch (e) {}
289
- if (Date.now() >= pngDeadline) break;
290
- await sleep(250);
291
- }
292
- if (size <= 1500) {
293
- return { ok: false, err: 'png_yok', error: 'PNG oluşmadı ya da çok küçük (' + size + ' byte): ' + shotFile, extra: { ack, expected_file: shotFile, bytes: size } };
294
- }
295
- return { ok: true, src, shotFile, size, res: ack.result || {}, params: o.params, id: o.id };
296
- }
297
-
298
- /* --- kopyala: Beast altı yedek + send_file edilebilir kullanıcı klasörü --- */
299
- function copyOut(src, shotFile, outdir) {
300
- const pubDir = (outdir && String(outdir).trim()) ? path.resolve(String(outdir).trim()) : PUB_DIR_DEFAULT;
301
- let pubPath = null, beastPath = null, pubErr = null;
302
- try {
303
- if (!fs.existsSync(SHOT_DIR)) fs.mkdirSync(SHOT_DIR, { recursive: true });
304
- beastPath = path.join(SHOT_DIR, shotFile);
305
- fs.copyFileSync(src, beastPath);
306
- } catch (e) { /* yedek kopya zorunlu değil */ }
307
- try {
308
- if (!fs.existsSync(pubDir)) fs.mkdirSync(pubDir, { recursive: true });
309
- pubPath = path.join(pubDir, shotFile);
310
- fs.copyFileSync(src, pubPath);
311
- } catch (e) { pubErr = e.message; }
312
- return { pubPath, beastPath, pubErr };
313
- }
314
-
315
- /* --- PNG data URL (engine __injectImage sözleşmesiyle ajana görsel enjekte eder) --- */
316
- function embedData(src, size) {
317
- let dataUrl = null;
318
- try {
319
- if (ARGS.embed !== false && size > 0 && size <= 3500000) {
320
- dataUrl = 'data:image/png;base64,' + fs.readFileSync(src).toString('base64');
321
- }
322
- } catch (e) {}
323
- return dataUrl;
324
- }
325
-
326
- /* ---- ÇOKLU KARE: plan (sembol × periyot) sırayla çekilir ve TEK PNG'de
327
- birleştirilir — kilit zaten alınmış (ack çakışması yok); her panelin SOL
328
- ÜSTüne "SEMBOL · PERİYOT" etiketi çizilir; soldan sağa (layout:"v" ile
329
- üstten alta). plan: [{symbol, timeframe}] — boş alan = aktif grafik. */
330
- async function runPanels(plan, filesDir, cmdFile, ackFile, beatFile, timeoutSec, diag) {
331
- let compose = null;
332
- try { compose = require('./stitch.js').compose; } catch (e) {}
333
- if (!compose) return { err: 'stitch_fail', error: 'stitch.js bulunamadı (mt5_shot güncel kurulum gerekiyor)' };
334
-
335
- const n = plan.length;
336
- const baseId = 'S' + Date.now().toString(36);
337
- const fileArg = ARGS.file && String(ARGS.file).trim() ? String(ARGS.file).trim() : '';
338
- const uniqSym = [...new Set(plan.map((p) => p.symbol).filter(Boolean))];
339
- const base = fileArg
340
- ? fileArg.replace(/\.[a-z0-9]+$/i, '')
341
- : (uniqSym.length > 1 ? 'beast_symbols_' : 'beast_mtf_') + baseId;
342
- const perAck = Math.max(5, Math.min(15, Math.floor(45 / n)));
343
- const width = Number(ARGS.width) > 0 ? Math.min(10000, Math.round(Number(ARGS.width))) : 1600;
344
- const height = Number(ARGS.height) > 0 ? Math.min(10000, Math.round(Number(ARGS.height))) : 900;
345
- const layout = String(ARGS.layout || 'h').toLowerCase() === 'v' ? 'v' : 'h';
346
-
347
- const panels = [];
348
- const cmdIds = [];
349
- for (let i = 0; i < n; i++) {
350
- const spec = plan[i] || {};
351
- const symArg = spec.symbol ? String(spec.symbol).toUpperCase() : null;
352
- const tfArg = spec.timeframe ? String(spec.timeframe).toUpperCase() : null;
353
- const id = baseId + 'p' + (i + 1);
354
- const tag = [symArg, tfArg].filter(Boolean).join('_') || 'aktif';
355
- const params = { file: sanitizeFile(base + '_' + tag, 'beast_shot_' + id), width, height };
356
- if (tfArg) params.timeframe = tfArg;
357
- if (symArg) params.symbol = symArg;
358
- if (ARGS.template !== undefined) params.template = String(ARGS.template);
359
- cmdIds.push(id);
360
- const r = await captureOne({ id, file: params.file, params, filesDir, cmdFile, ackFile, beatFile, timeoutSec: perAck, diag });
361
- if (!r.ok) {
362
- return {
363
- err: r.err,
364
- error: '[' + tag + '] ' + r.error,
365
- extra: Object.assign({ panel: tag, cmdId: id, requested: { symbol: symArg, timeframe: tfArg } }, r.extra || {})
366
- };
367
- }
368
- const cp = copyOut(r.src, r.shotFile, ARGS.outdir);
369
- const tfDone = (r.res && r.res.period ? tfName(r.res.period) : null) || tfArg || null;
370
- const symDone = (r.res && r.res.symbol ? String(r.res.symbol).toUpperCase() : null) || symArg || null;
371
- panels.push({ spec, sym: symDone, tf: tfDone, src: r.src, file: r.shotFile, size: r.size, res: r.res, pubPath: cp.pubPath, beastPath: cp.beastPath });
372
- }
373
-
374
- /* birleştir: etiketler çekimden SONRA gerçek sembol/periyotla kurulur */
375
- let composed = null;
376
- try {
377
- composed = await compose(
378
- panels.map((p) => ({ path: p.src, label: [p.sym, p.tf].filter(Boolean).join(' · ') || 'aktif grafik' })),
379
- { layout }
380
- );
381
- } catch (e) { composed = { ok: false, error: String((e && e.message) || e) }; }
382
- if (!composed || !composed.ok) {
383
- return { err: 'stitch_fail', error: 'görüntüler birleştirilemedi: ' + ((composed && composed.error) || 'bilinmeyen') };
384
- }
385
-
386
- const outFile = sanitizeFile(fileArg, base + '.png');
387
- const pathMt5 = path.join(filesDir, outFile);
388
- try { fs.writeFileSync(pathMt5, composed.buffer); }
389
- catch (e) { return { err: 'stitch_write_fail', error: 'birleşik PNG yazılamadı: ' + e.message }; }
390
- const size = composed.buffer.length;
391
- const cp = copyOut(pathMt5, outFile, ARGS.outdir);
392
- const dataUrl = embedData(pathMt5, size);
393
- const symDone = [...new Set(panels.map((p) => p.sym).filter(Boolean))];
394
- const tfList = [...new Set(plan.map((p) => p.timeframe).filter(Boolean))];
395
-
396
- return {
397
- ok: true,
398
- payload: {
399
- ok: true,
400
- path: cp.pubPath || cp.beastPath || pathMt5, // send_file / agent_dm image:"<path>"
401
- path_mt5: pathMt5,
402
- path_beast: cp.beastPath,
403
- file: outFile,
404
- bytes: size,
405
- image_embedded: !!dataUrl,
406
- ...(dataUrl ? { __injectImage: dataUrl } : {}),
407
- cmdIds,
408
- filesDir,
409
- symbol: symDone.length === 1 ? symDone[0] : null,
410
- symbols: symDone,
411
- timeframes: tfList,
412
- layout,
413
- panels: panels.map((p, i) => ({
414
- symbol: p.sym,
415
- timeframe: p.tf,
416
- position: layout === 'h'
417
- ? (i === 0 ? 'left' : (i === n - 1 ? 'right' : 'middle'))
418
- : (i === 0 ? 'top' : (i === n - 1 ? 'bottom' : 'middle')),
419
- file: p.file,
420
- path: p.pubPath || p.beastPath || p.src,
421
- bytes: p.size,
422
- period: (p.res && p.res.period) || null
423
- })),
424
- width: composed.width,
425
- height: composed.height,
426
- requested: {
427
- symbol: ARGS.symbol ? String(ARGS.symbol).toUpperCase() : null,
428
- symbols: Array.isArray(ARGS.symbols) ? ARGS.symbols.map((s) => String(s).toUpperCase()) : (ARGS.symbols || null),
429
- timeframes: tfList,
430
- width, height, layout
431
- },
432
- heartbeat_age_sec: heartbeatAge(beatFile),
433
- ms: Date.now() - CTX.started,
434
- pubErr: cp.pubErr || undefined
435
- }
436
- };
437
- }
438
-
439
- async function run() {
440
- const timeoutSec = Number(ARGS.timeoutSec) > 0
441
- ? Math.max(5, Math.min(70, Math.round(Number(ARGS.timeoutSec))))
442
- : DEFAULT_TIMEOUT_SEC;
443
- const diag = ARGS.diag !== false;
444
-
445
- const term = findTerminalData();
446
- if (!term) return fail('no_terminal', 'MT5 terminal veri klasörü bulunamadı (beast_ea.json yok) — MT5 kurulu/çalışıyor mu?');
447
- const filesDir = term.files;
448
- CTX.filesDir = filesDir;
449
-
450
- const cmdFile = path.join(filesDir, 'beast_cmd.json');
451
- const ackFile = path.join(filesDir, 'beast_cmd_ack.json');
452
- const beatFile = path.join(filesDir, 'beast_ea.json');
453
- const beatAge = heartbeatAge(beatFile);
454
- if (beatAge !== null && beatAge > 60) {
455
- return fail('ea_offline', 'BeastFinance EA canlı değil (son heartbeat ' + beatAge + ' sn önce) — grafikte EA yüklü ve terminal açık mı?',
456
- { heartbeat_age_sec: beatAge });
457
- }
458
-
459
- const locked = await acquireLock(Math.min(15000, timeoutSec * 500));
460
- if (!locked) console.error('[mt5_shot] uyarı: kilit alınamadı, yine de devam ediliyor');
461
-
462
- /* ÇOKLU KARE: symbols:["GOLD","EURUSD"] ve/veya timeframes:["M1","M15"]
463
- → sırayla çekilip tek PNG'de etiketli birleştirilir */
464
- const plan = buildPlan();
465
- if (plan.length >= 2) {
466
- const r = await runPanels(plan, filesDir, cmdFile, ackFile, beatFile, timeoutSec, diag);
467
- if (!r.ok) return fail(r.err, r.error, r.extra);
468
- return out(r.payload);
469
- }
470
-
471
- const id = 'S' + Date.now().toString(36);
472
- CTX.cmdId = id;
473
-
474
- const file = sanitizeFile(ARGS.file, 'beast_shot_' + id + '.png');
475
-
476
- const params = { file };
477
- if (ARGS.timeframe) params.timeframe = String(ARGS.timeframe).toUpperCase();
478
- if (ARGS.symbol) params.symbol = String(ARGS.symbol).toUpperCase();
479
- if (ARGS.template !== undefined) params.template = String(ARGS.template);
480
- params.width = Number(ARGS.width) > 0 ? Math.min(10000, Math.round(Number(ARGS.width))) : 1600;
481
- params.height = Number(ARGS.height) > 0 ? Math.min(10000, Math.round(Number(ARGS.height))) : 900;
482
-
483
- const cap = await captureOne({ id, file, params, filesDir, cmdFile, ackFile, beatFile, timeoutSec, diag });
484
- if (!cap.ok) return fail(cap.err, cap.error, cap.extra);
485
-
486
- const cp = copyOut(cap.src, cap.shotFile, ARGS.outdir);
487
- const res = cap.res;
488
- const tfTxt = (res.period ? tfName(res.period) : null) || params.timeframe || null;
489
- const wantSym = params.symbol || res.symbol || null;
490
- const mismatch = !!(params.symbol && res.symbol && String(res.symbol).toUpperCase() !== String(params.symbol).toUpperCase());
491
- const dataUrl = embedData(cap.src, cap.size);
492
-
493
- return out({
494
- ok: true,
495
- path: cp.pubPath || cp.beastPath || cap.src, // send_file'a verilecek yol
496
- path_mt5: cap.src,
497
- path_beast: cp.beastPath,
498
- file: cap.shotFile,
499
- bytes: cap.size,
500
- image_embedded: !!dataUrl,
501
- ...(dataUrl ? { __injectImage: dataUrl } : {}),
502
- cmdId: id,
503
- filesDir: filesDir,
504
- symbol: res.symbol || wantSym || null,
505
- timeframe: tfTxt,
506
- period: res.period || null,
507
- requested: { symbol: params.symbol || null, timeframe: params.timeframe || null },
508
- width: params.width,
509
- height: params.height,
510
- temp_chart: res.temp_chart === true,
511
- template_applied: res.template_applied === true,
512
- symbol_mismatch: mismatch,
513
- heartbeat_age_sec: heartbeatAge(beatFile),
514
- ms: Date.now() - CTX.started,
515
- pubErr: cp.pubErr || undefined
516
- });
517
- }
518
-
519
- /* ---------------- stdin / argv (asılmaya karşı sigortalı + çok kanallı) ---------------- */
520
- function parseArgs(s) {
521
- const t = String(s || '').trim();
522
- if (!t) return {};
523
- try { return JSON.parse(t) || {}; } catch (e) {}
524
- const m = t.match(/\{[\s\S]*\}/); // kesik/gürültülü girişten kurtar
525
- if (m) { try { return JSON.parse(m[0]) || {}; } catch (e) {} }
526
- return {};
527
- }
528
-
529
- // {args:{...}} / {parameters:{...}} gibi sarmalayıcıları düzleştir (her durumda flat args'a in)
530
- function unwrap(o) {
531
- let x = (o && typeof o === 'object' && !Array.isArray(o)) ? o : {};
532
- const WRAP = ['args', 'arguments', 'params', 'parameters', 'input'];
533
- for (let i = 0; i < 3; i++) {
534
- const inner = WRAP.map((k) => x[k]).find((v) => v && typeof v === 'object' && !Array.isArray(v));
535
- if (!inner) break;
536
- x = Object.assign({}, x, inner);
537
- for (const k of WRAP) delete x[k];
538
- }
539
- if (x.args && typeof x.args === 'object') x = Object.assign({}, x, x.args);
540
- delete x.args;
541
- return x;
542
- }
543
-
544
- function argsFromArgv() {
545
- const av = process.argv.slice(2);
546
- const joined = av.join(' ').trim();
547
- if (joined) {
548
- const j = parseArgs(joined);
549
- if (Object.keys(j).length) return j;
550
- }
551
- const kv = {};
552
- for (const a of av) {
553
- const m = /^--?([a-zA-Z_][a-zA-Z0-9_]*)=(.+)$/.exec(a);
554
- if (m) {
555
- const v = m[2];
556
- kv[m[1]] = /^-?\d+(\.\d+)?$/.test(v) ? Number(v) : (v === 'true' ? true : (v === 'false' ? false : v));
557
- }
558
- }
559
- return kv;
560
- }
561
-
562
- function loadArgs(rawStdin) {
563
- const cands = [rawStdin];
564
- try { cands.push(process.env.BEAST_TOOL_ARGS, process.env.TOOL_ARGS, process.env.TOOL_ARGUMENTS); } catch (e) {}
565
- const av = argsFromArgv();
566
- let src = 'none', parsed = {};
567
- for (const c of cands) {
568
- if (!c) continue;
569
- const o = parseArgs(c);
570
- if (o && Object.keys(o).length) { parsed = o; src = 'stdin/env'; break; }
571
- }
572
- if (!src || src === 'none') {
573
- if (av && Object.keys(av).length) { parsed = av; src = 'argv'; }
574
- }
575
- const final = unwrap(parsed);
576
- // teşhis izi: argüman nereden geldi / hiç geldi mi (asla dışarı gönderilmez)
577
- try {
578
- let f0 = null, pcmd = null;
579
- try {
580
- const st = fs.fstatSync(0);
581
- f0 = { isFIFO: st.isFIFO(), isCharacterDevice: st.isCharacterDevice(), isFile: st.isFile(), size: st.size };
582
- } catch (e) { f0 = { error: String(e && e.message) }; }
583
- /* Parent komut satırı teşhisi pahalıdır (PowerShell ~1-5 sn) — yalnız
584
- BEAST_TOOL_DIAG=1 iken koşar; normal çekimlerde sn cinsinden hız kazandırır. */
585
- if (process.env.BEAST_TOOL_DIAG === '1') {
586
- try {
587
- const { execSync } = require('child_process');
588
- pcmd = execSync('powershell -NoProfile -Command "(Get-CimInstance Win32_Process -Filter \\"ProcessId=' +
589
- process.ppid + '\\").CommandLine"', { timeout: 5000 }).toString().trim();
590
- } catch (e) { pcmd = 'okunamadı: ' + String(e && e.message).slice(0, 120); }
591
- }
592
- const envJson = {};
593
- for (const k of Object.keys(process.env)) {
594
- if (/key|token|secret|pass|auth|credential/i.test(k)) continue;
595
- const v = String(process.env[k] || '');
596
- if (/tool|beast|arg|param|call|json/i.test(k) || v.trim().startsWith('{')) envJson[k] = v.slice(0, 300);
597
- }
598
- fs.writeFileSync(path.join(__dirname, 'last-args.json'), JSON.stringify({
599
- at: new Date().toISOString(), source: src,
600
- run_as_node: process.env.ELECTRON_RUN_AS_NODE || null, // engine electron'u node modunda mı başlattı (1 = evet; CLI'da null)
601
- electron: process.versions.electron || null,
602
- stdin_raw: String(rawStdin || '').slice(0, 1500),
603
- stdin_fd0: f0, ppid: process.ppid, parent_cmd: pcmd,
604
- argv: process.argv.slice(2), env_json: envJson, parsed, final
605
- }, null, 1));
606
- } catch (e) {}
607
- if (src === 'none' || !Object.keys(final).length) {
608
- console.error('[mt5_shot] uyarı: çağrı argümanı alınamadı (stdin/argv boş) — varsayılanlar kullanılıyor');
609
- }
610
- return final;
611
- }
612
-
613
- let raw = '', resolved = false, silenceTimer = null;
614
- function begin(resolve) { if (resolved) return; resolved = true; resolve(); }
615
- function finish() {
616
- ARGS = loadArgs(raw);
617
- Promise.resolve()
618
- .then(run)
619
- .catch((e) => fail('timeout', 'beklenmeyen hata: ' + (e && e.message ? e.message : String(e))));
620
- }
621
-
622
- function main() {
623
- try {
624
- process.stdin.setEncoding('utf8');
625
- process.stdin.on('data', (d) => {
626
- raw += d;
627
- if (silenceTimer) clearTimeout(silenceTimer);
628
- // JSON tamamlandıysa EOF beklemeden başla; değilse kısa bir sessizlik penceresi daha bekle
629
- const o = parseArgs(raw);
630
- if (raw.trim() && Object.keys(o).length) return begin(finish);
631
- silenceTimer = setTimeout(() => begin(finish), 1200);
632
- });
633
- process.stdin.on('end', () => begin(finish));
634
- process.stdin.on('error', () => begin(finish));
635
- process.stdin.on('close', () => begin(finish));
636
- process.stdin.resume();
637
- setTimeout(() => begin(finish), 2500); // stdin hiç açılmaz/kapanmazsa da devam et (asılmaya izin yok)
638
- } catch (e) { begin(finish); }
639
-
640
- /* watchdog: hiçbir koşulda 80 sn üzeri asılı kalma */
641
- WATCHDOG = setTimeout(() => {
642
- fail('timeout', 'mt5_shot ' + Math.round((Date.now() - CTX.started) / 1000) + ' sn içinde tamamlanamadı (watchdog) — ack/PNG beklemesi aşıldı');
643
- }, HARD_MS);
644
- }
645
-
646
- /* saf yardımcılar test için dışa açılır; runner yalnız doğrudan çalıştırılınca başlar */
647
- module.exports = { normalizeTfs, normalizeSymbols, buildPlan, sanitizeFile };
648
-
649
- if (require.main === module) main();