beast-agent 2.39.0 → 2.41.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 (41) hide show
  1. package/package.json +1 -1
  2. package/src/agent/bots.js +2 -2
  3. package/src/agent/customtools.js +52 -2
  4. package/src/agent/defaulttools/mt5_barlar2/run.js +188 -0
  5. package/src/agent/defaulttools/mt5_barlar2/tool.json +23 -0
  6. package/src/agent/defaulttools/mt5_bekleyen/run.js +100 -0
  7. package/src/agent/defaulttools/mt5_bekleyen/tool.json +17 -0
  8. package/src/agent/defaulttools/mt5_durum/run.js +93 -0
  9. package/src/agent/defaulttools/mt5_durum/tool.json +10 -0
  10. package/src/agent/defaulttools/mt5_emir/run.js +98 -0
  11. package/src/agent/defaulttools/mt5_emir/tool.json +17 -0
  12. package/src/agent/defaulttools/mt5_emir_iptal/run.js +90 -0
  13. package/src/agent/defaulttools/mt5_emir_iptal/tool.json +12 -0
  14. package/src/agent/defaulttools/mt5_fiyat/run.js +87 -0
  15. package/src/agent/defaulttools/mt5_fiyat/tool.json +16 -0
  16. package/src/agent/defaulttools/mt5_gecmis/run.js +87 -0
  17. package/src/agent/defaulttools/mt5_gecmis/tool.json +12 -0
  18. package/src/agent/defaulttools/mt5_kapat/run.js +92 -0
  19. package/src/agent/defaulttools/mt5_kapat/tool.json +13 -0
  20. package/src/agent/defaulttools/mt5_m15_m5/m15m5.py +60 -0
  21. package/src/agent/defaulttools/mt5_m15_m5/run.js +73 -0
  22. package/src/agent/defaulttools/mt5_m15_m5/tool.json +10 -0
  23. package/src/agent/defaulttools/mt5_pozisyon_gecmis/run.js +144 -0
  24. package/src/agent/defaulttools/mt5_pozisyon_gecmis/tool.json +12 -0
  25. package/src/agent/defaulttools/mt5_shot/run.js +428 -0
  26. package/src/agent/defaulttools/mt5_shot/tool.json +20 -0
  27. package/src/agent/defaulttools/mt5_sltp/run.js +92 -0
  28. package/src/agent/defaulttools/mt5_sltp/tool.json +14 -0
  29. package/src/agent/engine.js +101 -16
  30. package/src/agent/financetools.js +1 -1
  31. package/src/agent/mt5/BeastFinance.mq5 +140 -3
  32. package/src/agent/mt5/BeastShot.tpl +0 -0
  33. package/src/agent/mt5setup.js +22 -0
  34. package/src/agent/opencode/agents.js +4 -3
  35. package/src/agent/seeds/mql5/SKILL.md +4 -2
  36. package/src/agent/tools.js +16 -0
  37. package/src/main.js +307 -39
  38. package/src/preload.js +6 -1
  39. package/src/renderer/index.html +6 -4
  40. package/src/renderer/renderer.js +115 -39
  41. package/src/renderer/style.css +27 -0
@@ -0,0 +1,144 @@
1
+ 'use strict';
2
+ // MT5 POZISYON BAZLI GECMIS — açılış+kapanış tek satırda birleşik.
3
+ // stdin: JSON args · stdout: JSON sonuç (SADECE JSON yaz!)
4
+ const { spawn } = require('child_process');
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+
8
+ /* TAŞINABİLİR YOL ÇÖZÜMÜ: Beast gömülü python → sistem python'ları → PATH.
9
+ Köprü dosyası %APPDATA%\beast\finance\mt5_bridge.py'den (veya BEAST_FINANCE_DIR)
10
+ okunur; ortam değişkenleriyle geçersiz kılınabilir. */
11
+ function resolvePython() {
12
+ const cands = [];
13
+ if (process.env.BEAST_PYTHON) cands.push({ exe: String(process.env.BEAST_PYTHON), args: [] });
14
+ if (process.env.APPDATA) cands.push({ exe: path.join(process.env.APPDATA, 'beast', 'py', 'python.exe'), args: [] });
15
+ for (const v of ['314', '313', '312', '311', '310']) cands.push({ exe: 'C:\\Python' + v + '\\python.exe', args: [] });
16
+ cands.push({ exe: 'python', args: [] }, { exe: 'py', args: ['-3'] });
17
+ for (const c of cands) {
18
+ if (c.exe === 'python' || c.exe === 'py') return c;
19
+ try { if (fs.existsSync(c.exe)) return c; } catch (e) {}
20
+ }
21
+ return { exe: 'python', args: [] };
22
+ }
23
+ function resolveBridge() {
24
+ const cands = [];
25
+ if (process.env.BEAST_BRIDGE) cands.push(String(process.env.BEAST_BRIDGE));
26
+ if (process.env.BEAST_FINANCE_DIR) cands.push(path.join(String(process.env.BEAST_FINANCE_DIR), 'mt5_bridge.py'));
27
+ if (process.env.APPDATA) cands.push(path.join(process.env.APPDATA, 'beast', 'finance', 'mt5_bridge.py'));
28
+ for (const p of cands) { try { if (p && fs.existsSync(p)) return p; } catch (e) {} }
29
+ return cands[0] || '';
30
+ }
31
+ const PYSPEC = resolvePython();
32
+ const PY = PYSPEC.exe;
33
+ const PY_ARGS = PYSPEC.args;
34
+ const BRIDGE = resolveBridge();
35
+
36
+ const TIMEOUT = 35000;
37
+
38
+ function call(requests) {
39
+ return new Promise((resolve) => {
40
+ let py;
41
+ try { py = spawn(PY, [...PY_ARGS, BRIDGE], { windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'] }); }
42
+ catch (e) { return resolve({ ok: false, error: 'python baslatilamadi: ' + e.message }); }
43
+ const results = {};
44
+ let buf = '';
45
+ let errTail = '';
46
+ let settled = false;
47
+ const finish = (obj) => { if (!settled) { settled = true; clearTimeout(timer); try { py.kill(); } catch (e) {} resolve(obj); } };
48
+ const timer = setTimeout(() => finish({ ok: false, error: 'zaman asimi: MT5 terminali acik ve yanit veriyor mu?' }), TIMEOUT);
49
+ py.stdout.on('data', (d) => {
50
+ buf += d.toString('utf8');
51
+ let i;
52
+ while ((i = buf.indexOf('\n')) >= 0) {
53
+ const line = buf.slice(0, i).trim(); buf = buf.slice(i + 1);
54
+ if (!line) continue;
55
+ let o = null;
56
+ try { o = JSON.parse(line); } catch (e) { continue; }
57
+ if (o && o.event) continue;
58
+ if (o && typeof o.id === 'number' && Object.prototype.hasOwnProperty.call(o, 'ok')) {
59
+ results[o.id] = o;
60
+ if (Object.keys(results).length >= requests.length) {
61
+ const arr = requests.map((_, k) => results[k + 1]);
62
+ finish(arr.length === 1 ? arr[0] : { ok: true, results: arr });
63
+ }
64
+ }
65
+ }
66
+ });
67
+ py.stderr.on('data', (d) => { errTail = (errTail + d.toString('utf8')).slice(-800); });
68
+ py.on('error', (e) => finish({ ok: false, error: 'python baslatilamadi: ' + e.message }));
69
+ py.on('close', (code) => { if (Object.keys(results).length < requests.length) finish({ ok: false, error: 'bridge erken cikti code=' + code + ' ' + errTail }); });
70
+ requests.forEach((r, k) => py.stdin.write(JSON.stringify({ id: k + 1, method: r.method, params: r.params || {} }) + '\n'));
71
+ py.stdin.end();
72
+ });
73
+ }
74
+
75
+ const r2 = (n) => Math.round((Number(n) || 0) * 100) / 100;
76
+ const fmt = (t) => {
77
+ if (!t) return '';
78
+ const d = new Date(Number(t) * 1000);
79
+ const p = (x) => String(x).padStart(2, '0');
80
+ return d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate()) + ' ' + p(d.getHours()) + ':' + p(d.getMinutes());
81
+ };
82
+
83
+ (async function main() {
84
+ let args = {};
85
+ try { args = JSON.parse(fs.readFileSync(0, 'utf8') || '{}'); } catch (e) {}
86
+ let days = Number(args.days || 1);
87
+ if (!(days > 0)) days = 1;
88
+ days = Math.min(days, 90);
89
+ const r = await call([{ method: 'deals', params: { days: days } }]);
90
+ if (!r.ok || !r.data) return console.log(JSON.stringify({ ok: false, error: r.error || 'gecmis alinamadi' }));
91
+
92
+ const deals = r.data.deals || [];
93
+ if (!deals.length) return console.log(JSON.stringify({ ok: true, count: 0, positions: [], summary: { net: 0, wins: 0, losses: 0 } }));
94
+
95
+ // Ayni position_id -> tek pozisyon. INOUT (entry=2) deal'ler kendi basina pozisyon.
96
+ const byPos = new Map();
97
+ for (const dl of deals) {
98
+ const pid = (dl.position_id != null && dl.position_id !== 0) ? dl.position_id : dl.ticket;
99
+ if (!byPos.has(pid)) byPos.set(pid, []);
100
+ byPos.get(pid).push(dl);
101
+ }
102
+
103
+ const positions = [];
104
+ for (const [pid, arr] of byPos) {
105
+ arr.sort((a, b) => (a.time || 0) - (b.time || 0));
106
+ const first = arr[0];
107
+ const entryDeal = arr.find((x) => x.entry === 0) || first;
108
+ const exitDeal = arr.find((x) => x.entry === 1) || arr[arr.length - 1];
109
+ let profit = 0, commission = 0, swap = 0;
110
+ for (const x of arr) { profit += Number(x.profit) || 0; commission += Number(x.commission) || 0; swap += Number(x.swap) || 0; }
111
+ positions.push({
112
+ position_id: pid,
113
+ symbol: first.symbol,
114
+ side: Number(first.type) === 0 ? 'buy' : 'sell',
115
+ volume: r2(first.volume),
116
+ open_price: entryDeal.price,
117
+ open_time: entryDeal.time,
118
+ open_time_str: fmt(entryDeal.time),
119
+ close_price: exitDeal.price,
120
+ close_time: exitDeal.time,
121
+ close_time_str: fmt(exitDeal.time),
122
+ profit: r2(profit),
123
+ commission: r2(commission),
124
+ swap: r2(swap),
125
+ net: r2(profit + commission + swap),
126
+ deal_count: arr.length
127
+ });
128
+ }
129
+ positions.sort((a, b) => (b.close_time || 0) - (a.close_time || 0));
130
+
131
+ let net = 0, wins = 0, losses = 0;
132
+ for (const p of positions) { net += p.net; if (p.net > 0) wins++; else if (p.net < 0) losses++; }
133
+
134
+ console.log(JSON.stringify({
135
+ ok: true,
136
+ count: positions.length,
137
+ positions: positions,
138
+ summary: { net: r2(net), wins: wins, losses: losses }
139
+ }));
140
+ })();
141
+
142
+ /* PATCH 2026-09-10b: electron app-mode cocuk prosesi kapanmiyordu (90sn timeout).
143
+ stdout'un boruya bosalmasi icin kisa bekleme, sonra zorla cik. */
144
+ setTimeout(() => process.exit(0), 1500);
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "mt5_pozisyon_gecmis",
3
+ "description": "Kapanan pozisyon geçmişi — açılış ve kapanış TEK SATIRDA birleşik gösterilir (deal'ler ayrı ayrı değil). Sembol, yön, lot, açılış/kapanış fiyatı ve zamanı, net kâr/zarar (profit+komisyon+swap). days verilmezse bugün (en fazla 90).",
4
+ "parameters": {
5
+ "type": "object",
6
+ "properties": {
7
+ "days": { "type": "number", "description": "Kac gun geriye bakilsin (1-90, varsayilan 1 = bugun)" }
8
+ },
9
+ "required": []
10
+ },
11
+ "updatedAt": "2026-09-09T22:10:00.000Z"
12
+ }
@@ -0,0 +1,428 @@
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?, timeframe?, width?, height?, file?, template?, timeoutSec?, outdir?, diag?, embed?}
6
+ Çıktı : {ok:true, path, symbol, timeframe, ...} | {ok:false, err, error, cmdId, filesDir, ...}
7
+ err kodları: no_terminal | ea_offline | cmd_write_fail | ea_no_ack | ea_error | png_yok | timeout
8
+
9
+ Notlar:
10
+ - beast_cmd.json'a {id, ts, cmd, params} yazılır (UTF-16 BOM; EA FILE_UNICODE okur).
11
+ - Ack dosyası POLL edilir; PNG MQL5\Files altında belirene kadar beklenir.
12
+ - EA v1.20 şu şablonu işler: {"id":"..","ts":..,"cmd":"shot","params":{file,width,height,symbol?,timeframe?,template?}}
13
+ symbol/timeframe aktif grafikten farklıysa EA ChartOpen+ChartSetSymbolPeriod ile geçici grafik açar.
14
+ - PNG, send_file'ın engellediği %APPDATA%\beast altından çıkarılıp kullanıcı klasörüne
15
+ (varsayılan <home>\beast_shots) kopyalanır; dönen "path" doğrudan send_file'a verilebilir.
16
+ - GÖRSEL ENJEKSİYONU: başarılı sonuçta PNG data URL olarak __injectImage alanına gömülür;
17
+ engine bunu sonraki turda ajana gösterir (embed:false kapatır) — ajan grafiği görerek analiz eder.
18
+ - Watchdog: araç hiçbir koşulda asılı kalmaz; 80 sn'de {ok:false, err:"timeout"} döner.
19
+ */
20
+ const fs = require('fs');
21
+ const os = require('os');
22
+ const path = require('path');
23
+
24
+ const SHOT_DIR = path.join(__dirname, 'shots'); // yedek kopya (Beast altı)
25
+ const PUB_DIR_DEFAULT = path.join(os.homedir(), 'beast_shots'); // send_file edilebilir kopya
26
+ const LOCK_FILE = path.join(__dirname, '.shot.lock');
27
+ const LOCK_STALE_MS = 45000;
28
+ const HARD_MS = 80000;
29
+ const DEFAULT_TIMEOUT_SEC = 20;
30
+
31
+ /* ---------------- temel yardımcılar ---------------- */
32
+ function syncSleep(ms) {
33
+ try { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); }
34
+ catch (e) { const t = Date.now(); while (Date.now() - t < ms) { /* busy wait */ } }
35
+ }
36
+
37
+ let CTX = { cmdId: null, filesDir: null, started: Date.now() };
38
+ let ARGS = {};
39
+
40
+ function out(o) {
41
+ try { fs.writeSync(1, JSON.stringify(o) + '\n'); }
42
+ catch (e) { try { console.log(JSON.stringify(o)); } catch (x) {} }
43
+ try { clearTimeout(WATCHDOG); } catch (e) {}
44
+ try { releaseLock(); } catch (e) {}
45
+ process.exit(0);
46
+ }
47
+
48
+ // her hata yolu TEK yerden geçer: ok:false + err kodu + cmdId + filesDir
49
+ function fail(code, msg, extra) {
50
+ const o = Object.assign({
51
+ ok: false,
52
+ err: code,
53
+ error: msg,
54
+ cmdId: CTX.cmdId,
55
+ filesDir: CTX.filesDir,
56
+ ms: Date.now() - CTX.started
57
+ }, extra || {});
58
+ console.error('[mt5_shot] ' + code + ': ' + msg);
59
+ out(o);
60
+ }
61
+
62
+ /* ---------------- kilit (eşzamanlı çağrılar ack'i ezmesin) ---------------- */
63
+ function releaseLock() { try { fs.unlinkSync(LOCK_FILE); } catch (e) {} }
64
+
65
+ function acquireLock(waitMs) {
66
+ const deadline = Date.now() + waitMs;
67
+ for (;;) {
68
+ try { fs.writeFileSync(LOCK_FILE, JSON.stringify({ pid: process.pid, at: Date.now() }), { flag: 'wx' }); return true; }
69
+ catch (e) {
70
+ let st = null;
71
+ try { st = fs.statSync(LOCK_FILE); } catch (x) {
72
+ // kilit kayboldu/kilitli kaldı: kısa bekle, sonsuz döngüye girme
73
+ if (Date.now() >= deadline) return false;
74
+ syncSleep(200);
75
+ continue;
76
+ }
77
+ if (Date.now() - st.mtimeMs > LOCK_STALE_MS) { // ölü kilit: devral
78
+ try { fs.unlinkSync(LOCK_FILE); } catch (x) {}
79
+ continue;
80
+ }
81
+ if (Date.now() >= deadline) return false;
82
+ syncSleep(400);
83
+ }
84
+ }
85
+ }
86
+
87
+ /* ---------------- MT5 köprüsü ---------------- */
88
+ function findTerminalData() {
89
+ const base = path.join(process.env.APPDATA || '', 'MetaQuotes', 'Terminal');
90
+ if (!fs.existsSync(base)) return null;
91
+ let best = null;
92
+ for (const d of fs.readdirSync(base)) {
93
+ const files = path.join(base, d, 'MQL5', 'Files');
94
+ let fresh = 0;
95
+ for (const f of ['beast_ea.json', 'beast_cmd.json', 'beast_cmd_ack.json']) {
96
+ try {
97
+ const p = path.join(files, f);
98
+ if (fs.existsSync(p)) fresh = Math.max(fresh, fs.statSync(p).mtimeMs);
99
+ } catch (e) {}
100
+ }
101
+ if (!fresh) continue;
102
+ if (!best || fresh > best.m) best = { dir: path.join(base, d), files, m: fresh };
103
+ }
104
+ return best;
105
+ }
106
+
107
+ function readMaybeUtf16(fp) {
108
+ const buf = fs.readFileSync(fp);
109
+ let s;
110
+ if (buf.length >= 2 && buf[0] === 0xFF && buf[1] === 0xFE) s = buf.slice(2).toString('utf16le');
111
+ else s = buf.toString('utf8');
112
+ return s.replace(/^\uFEFF/, '').replace(/\0/g, '');
113
+ }
114
+
115
+ // EA FILE_UNICODE ile okur -> BOM'lu UTF-16 yaz; rename EBUSY olursa doğrudan yaz
116
+ function writeUtf16(fp, str) {
117
+ const buf = Buffer.concat([Buffer.from([0xFF, 0xFE]), Buffer.from(str, 'utf16le')]);
118
+ const tmp = fp + '.tmp' + process.pid;
119
+ try {
120
+ fs.writeFileSync(tmp, buf);
121
+ fs.renameSync(tmp, fp);
122
+ } catch (e) {
123
+ try { fs.unlinkSync(tmp); } catch (x) {}
124
+ fs.writeFileSync(fp, buf);
125
+ }
126
+ }
127
+
128
+ function readJson(fp) {
129
+ try { return JSON.parse(readMaybeUtf16(fp)); } catch (e) { return null; }
130
+ }
131
+
132
+ function heartbeatAge(beatFile) {
133
+ try { return Math.round((Date.now() - fs.statSync(beatFile).mtimeMs) / 1000); } catch (e) { return null; }
134
+ }
135
+
136
+ // MT5 period kodu -> okunabilir periyot adı (16385 = M15)
137
+ 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',
138
+ 16385:'H1',16386:'H2',16387:'H3',16388:'H4',16390:'H6',16392:'H8',16396:'H12',16408:'D1',32769:'W1',49153:'MN1' };
139
+ function tfName(p) {
140
+ const n = Number(p);
141
+ return TF_NAMES[n] || (TF_NAMES[Math.round(n)] || String(p == null ? '' : p));
142
+ }
143
+
144
+ /* ack yoksa: kanal sağlam mı? kısa bir ping ile ayırt et */
145
+ function eaPing(filesDir, budgetMs) {
146
+ const id = 'P' + Date.now().toString(36);
147
+ const cmdFile = path.join(filesDir, 'beast_cmd.json');
148
+ const ackFile = path.join(filesDir, 'beast_cmd_ack.json');
149
+ const t0 = Date.now();
150
+ try { writeUtf16(cmdFile, JSON.stringify({ id, ts: Date.now(), cmd: 'ping' })); }
151
+ catch (e) { return { ok: false, error: 'ping yazılamadı: ' + e.message }; }
152
+ const dl = Date.now() + budgetMs;
153
+ while (Date.now() < dl) {
154
+ syncSleep(300);
155
+ const a = readJson(ackFile);
156
+ if (a && a.id === id) return { ok: true, ack: a, ms: Date.now() - t0 };
157
+ }
158
+ return { ok: false, error: 'ping yanıtı yok (' + Math.round((Date.now() - t0) / 1000) + ' sn)' };
159
+ }
160
+
161
+ /* ---------------- ana akış ---------------- */
162
+ function run() {
163
+ const timeoutSec = Number(ARGS.timeoutSec) > 0
164
+ ? Math.max(5, Math.min(70, Math.round(Number(ARGS.timeoutSec))))
165
+ : DEFAULT_TIMEOUT_SEC;
166
+ const diag = ARGS.diag !== false;
167
+
168
+ const term = findTerminalData();
169
+ if (!term) return fail('no_terminal', 'MT5 terminal veri klasörü bulunamadı (beast_ea.json yok) — MT5 kurulu/çalışıyor mu?');
170
+ const filesDir = term.files;
171
+ CTX.filesDir = filesDir;
172
+
173
+ const cmdFile = path.join(filesDir, 'beast_cmd.json');
174
+ const ackFile = path.join(filesDir, 'beast_cmd_ack.json');
175
+ const beatFile = path.join(filesDir, 'beast_ea.json');
176
+ const beatAge = heartbeatAge(beatFile);
177
+ if (beatAge !== null && beatAge > 60) {
178
+ return fail('ea_offline', 'BeastFinance EA canlı değil (son heartbeat ' + beatAge + ' sn önce) — grafikte EA yüklü ve terminal açık mı?',
179
+ { heartbeat_age_sec: beatAge });
180
+ }
181
+
182
+ const locked = acquireLock(Math.min(15000, timeoutSec * 500));
183
+ if (!locked) console.error('[mt5_shot] uyarı: kilit alınamadı, yine de devam ediliyor');
184
+
185
+ const id = 'S' + Date.now().toString(36);
186
+ CTX.cmdId = id;
187
+
188
+ let file = (ARGS.file && String(ARGS.file).trim()) ? String(ARGS.file).trim() : ('beast_shot_' + id + '.png');
189
+ file = path.basename(file); // yol kaçışını engelle
190
+ if (!/\.(png|gif|bmp|jpg|jpeg)$/i.test(file)) file += '.png';
191
+
192
+ const params = { file };
193
+ if (ARGS.timeframe) params.timeframe = String(ARGS.timeframe).toUpperCase();
194
+ if (ARGS.symbol) params.symbol = String(ARGS.symbol).toUpperCase();
195
+ if (ARGS.template !== undefined) params.template = String(ARGS.template);
196
+ params.width = Number(ARGS.width) > 0 ? Math.min(10000, Math.round(Number(ARGS.width))) : 1600;
197
+ params.height = Number(ARGS.height) > 0 ? Math.min(10000, Math.round(Number(ARGS.height))) : 900;
198
+
199
+ const payload = { id, ts: Date.now(), cmd: 'shot', params };
200
+
201
+ try { if (fs.existsSync(ackFile)) fs.unlinkSync(ackFile); } catch (e) {}
202
+ try { writeUtf16(cmdFile, JSON.stringify(payload)); }
203
+ catch (e) { return fail('cmd_write_fail', 'beast_cmd.json yazılamadı: ' + e.message, { sent: payload }); }
204
+
205
+ /* --- ack POLL (cmd dosyasının EA tarafından tüketilmesi de izlenir) --- */
206
+ let ack = null, cmdConsumed = false;
207
+ const ackDeadline = Date.now() + timeoutSec * 1000;
208
+ while (Date.now() < ackDeadline) {
209
+ syncSleep(250);
210
+ const a = readJson(ackFile);
211
+ if (a && a.id === id) { ack = a; break; }
212
+ try {
213
+ if (!fs.existsSync(cmdFile)) cmdConsumed = true;
214
+ else {
215
+ const c = readJson(cmdFile);
216
+ if (c && c.id && c.id !== id) cmdConsumed = true; // başka bir çağrı ezip geçti
217
+ }
218
+ } catch (e) {}
219
+ }
220
+
221
+ if (!ack) {
222
+ const extra = {
223
+ heartbeat_age_sec: heartbeatAge(beatFile),
224
+ cmd_consumed: cmdConsumed,
225
+ timeout_sec: timeoutSec,
226
+ sent: payload
227
+ };
228
+ if (diag) {
229
+ extra.diagnostic = cmdConsumed
230
+ ? 'EA komutu aldı ama ack yazmadı (shot işleyicisi hata verdi / PNG üretimi bloke)'
231
+ : 'EA komutu hiç almadı (zamanlayıcı durmuş ya da komut dosyası okunamadı)';
232
+ extra.ping = eaPing(filesDir, 6000);
233
+ }
234
+ return fail('ea_no_ack', 'EA ' + timeoutSec + ' sn içinde ack vermedi (id=' + id + ', heartbeat ' +
235
+ (extra.heartbeat_age_sec === null ? '?' : extra.heartbeat_age_sec) + ' sn, cmd tüketildi=' + cmdConsumed + ')', extra);
236
+ }
237
+
238
+ if (ack.ok === false) {
239
+ return fail('ea_error', 'EA hata döndürdü: ' + (ack.error || 'bilinmeyen'), { ack });
240
+ }
241
+
242
+ /* --- PNG dosyasını bekle --- */
243
+ const shotFile = (ack.result && ack.result.file) ? path.basename(String(ack.result.file)) : file;
244
+ const src = path.join(filesDir, shotFile);
245
+ let size = 0;
246
+ const pngDeadline = Math.max(ackDeadline, Date.now() + 8000);
247
+ for (;;) {
248
+ try { if (fs.existsSync(src)) { size = fs.statSync(src).size; if (size > 1500) break; } } catch (e) {}
249
+ if (Date.now() >= pngDeadline) break;
250
+ syncSleep(250);
251
+ }
252
+ if (size <= 1500) {
253
+ return fail('png_yok', 'PNG oluşmadı ya da çok küçük (' + size + ' byte): ' + shotFile,
254
+ { ack, expected_file: shotFile, bytes: size });
255
+ }
256
+
257
+ /* --- kopyala: Beast altı yedek + send_file edilebilir kullanıcı klasörü --- */
258
+ const pubDir = (ARGS.outdir && String(ARGS.outdir).trim()) ? path.resolve(String(ARGS.outdir).trim()) : PUB_DIR_DEFAULT;
259
+ let pubPath = null, beastPath = null, pubErr = null;
260
+ try {
261
+ if (!fs.existsSync(SHOT_DIR)) fs.mkdirSync(SHOT_DIR, { recursive: true });
262
+ beastPath = path.join(SHOT_DIR, shotFile);
263
+ fs.copyFileSync(src, beastPath);
264
+ } catch (e) { /* yedek kopya zorunlu değil */ }
265
+ try {
266
+ if (!fs.existsSync(pubDir)) fs.mkdirSync(pubDir, { recursive: true });
267
+ pubPath = path.join(pubDir, shotFile);
268
+ fs.copyFileSync(src, pubPath);
269
+ } catch (e) { pubErr = e.message; }
270
+
271
+ const res = (ack.result || {});
272
+ const tfTxt = (res.period ? tfName(res.period) : null) || params.timeframe || null;
273
+ const wantSym = params.symbol || res.symbol || null;
274
+ const mismatch = !!(params.symbol && res.symbol && String(res.symbol).toUpperCase() !== String(params.symbol).toUpperCase());
275
+
276
+ /* --- GÖRSELİ ÇAĞIRAN AJANA GÖSTER: PNG data URL olarak sonuca gömülür;
277
+ engine bunu __injectImage sözleşmesiyle sonraki turda vision mesajı yapar
278
+ (ajan grafiği GERÇEKTEN görür). embed:false ile kapatılır; ~3.5MB üstü
279
+ bağlam/dekont şişmesin diye gömülmez (path yine döner). --- */
280
+ let dataUrl = null;
281
+ try {
282
+ if (ARGS.embed !== false && size > 0 && size <= 3500000) {
283
+ dataUrl = 'data:image/png;base64,' + fs.readFileSync(src).toString('base64');
284
+ }
285
+ } catch (e) {}
286
+
287
+ return out({
288
+ ok: true,
289
+ path: pubPath || beastPath || src, // send_file'a verilecek yol
290
+ path_mt5: src,
291
+ path_beast: beastPath,
292
+ file: shotFile,
293
+ bytes: size,
294
+ image_embedded: !!dataUrl,
295
+ ...(dataUrl ? { __injectImage: dataUrl } : {}),
296
+ cmdId: id,
297
+ filesDir: filesDir,
298
+ symbol: res.symbol || wantSym || null,
299
+ timeframe: tfTxt,
300
+ period: res.period || null,
301
+ requested: { symbol: params.symbol || null, timeframe: params.timeframe || null },
302
+ width: params.width,
303
+ height: params.height,
304
+ temp_chart: res.temp_chart === true,
305
+ template_applied: res.template_applied === true,
306
+ symbol_mismatch: mismatch,
307
+ heartbeat_age_sec: heartbeatAge(beatFile),
308
+ ms: Date.now() - CTX.started,
309
+ pubErr: pubErr || undefined
310
+ });
311
+ }
312
+
313
+ /* ---------------- stdin / argv (asılmaya karşı sigortalı + çok kanallı) ---------------- */
314
+ function parseArgs(s) {
315
+ const t = String(s || '').trim();
316
+ if (!t) return {};
317
+ try { return JSON.parse(t) || {}; } catch (e) {}
318
+ const m = t.match(/\{[\s\S]*\}/); // kesik/gürültülü girişten kurtar
319
+ if (m) { try { return JSON.parse(m[0]) || {}; } catch (e) {} }
320
+ return {};
321
+ }
322
+
323
+ // {args:{...}} / {parameters:{...}} gibi sarmalayıcıları düzleştir (her durumda flat args'a in)
324
+ function unwrap(o) {
325
+ let x = (o && typeof o === 'object' && !Array.isArray(o)) ? o : {};
326
+ const WRAP = ['args', 'arguments', 'params', 'parameters', 'input'];
327
+ for (let i = 0; i < 3; i++) {
328
+ const inner = WRAP.map((k) => x[k]).find((v) => v && typeof v === 'object' && !Array.isArray(v));
329
+ if (!inner) break;
330
+ x = Object.assign({}, x, inner);
331
+ for (const k of WRAP) delete x[k];
332
+ }
333
+ if (x.args && typeof x.args === 'object') x = Object.assign({}, x, x.args);
334
+ delete x.args;
335
+ return x;
336
+ }
337
+
338
+ function argsFromArgv() {
339
+ const av = process.argv.slice(2);
340
+ const joined = av.join(' ').trim();
341
+ if (joined) {
342
+ const j = parseArgs(joined);
343
+ if (Object.keys(j).length) return j;
344
+ }
345
+ const kv = {};
346
+ for (const a of av) {
347
+ const m = /^--?([a-zA-Z_][a-zA-Z0-9_]*)=(.+)$/.exec(a);
348
+ if (m) {
349
+ const v = m[2];
350
+ kv[m[1]] = /^-?\d+(\.\d+)?$/.test(v) ? Number(v) : (v === 'true' ? true : (v === 'false' ? false : v));
351
+ }
352
+ }
353
+ return kv;
354
+ }
355
+
356
+ function loadArgs(rawStdin) {
357
+ const cands = [rawStdin];
358
+ try { cands.push(process.env.BEAST_TOOL_ARGS, process.env.TOOL_ARGS, process.env.TOOL_ARGUMENTS); } catch (e) {}
359
+ const av = argsFromArgv();
360
+ let src = 'none', parsed = {};
361
+ for (const c of cands) {
362
+ if (!c) continue;
363
+ const o = parseArgs(c);
364
+ if (o && Object.keys(o).length) { parsed = o; src = 'stdin/env'; break; }
365
+ }
366
+ if (!src || src === 'none') {
367
+ if (av && Object.keys(av).length) { parsed = av; src = 'argv'; }
368
+ }
369
+ const final = unwrap(parsed);
370
+ // teşhis izi: argüman nereden geldi / hiç geldi mi (asla dışarı gönderilmez)
371
+ try {
372
+ let f0 = null, pcmd = null;
373
+ try {
374
+ const st = fs.fstatSync(0);
375
+ f0 = { isFIFO: st.isFIFO(), isCharacterDevice: st.isCharacterDevice(), isFile: st.isFile(), size: st.size };
376
+ } catch (e) { f0 = { error: String(e && e.message) }; }
377
+ try {
378
+ const { execSync } = require('child_process');
379
+ pcmd = execSync('powershell -NoProfile -Command "(Get-CimInstance Win32_Process -Filter \\"ProcessId=' +
380
+ process.ppid + '\\").CommandLine"', { timeout: 5000 }).toString().trim();
381
+ } catch (e) { pcmd = 'okunamadı: ' + String(e && e.message).slice(0, 120); }
382
+ const envJson = {};
383
+ for (const k of Object.keys(process.env)) {
384
+ if (/key|token|secret|pass|auth|credential/i.test(k)) continue;
385
+ const v = String(process.env[k] || '');
386
+ if (/tool|beast|arg|param|call|json/i.test(k) || v.trim().startsWith('{')) envJson[k] = v.slice(0, 300);
387
+ }
388
+ fs.writeFileSync(path.join(__dirname, 'last-args.json'), JSON.stringify({
389
+ at: new Date().toISOString(), source: src,
390
+ stdin_raw: String(rawStdin || '').slice(0, 1500),
391
+ stdin_fd0: f0, ppid: process.ppid, parent_cmd: pcmd,
392
+ argv: process.argv.slice(2), env_json: envJson, parsed, final
393
+ }, null, 1));
394
+ } catch (e) {}
395
+ if (src === 'none' || !Object.keys(final).length) {
396
+ console.error('[mt5_shot] uyarı: çağrı argümanı alınamadı (stdin/argv boş) — varsayılanlar kullanılıyor');
397
+ }
398
+ return final;
399
+ }
400
+
401
+ let raw = '', resolved = false, silenceTimer = null;
402
+ function begin(resolve) { if (resolved) return; resolved = true; resolve(); }
403
+ function finish() {
404
+ ARGS = loadArgs(raw);
405
+ try { run(); }
406
+ catch (e) { fail('timeout', 'beklenmeyen hata: ' + (e && e.message ? e.message : String(e))); }
407
+ }
408
+ try {
409
+ process.stdin.setEncoding('utf8');
410
+ process.stdin.on('data', (d) => {
411
+ raw += d;
412
+ if (silenceTimer) clearTimeout(silenceTimer);
413
+ // JSON tamamlandıysa EOF beklemeden başla; değilse kısa bir sessizlik penceresi daha bekle
414
+ const o = parseArgs(raw);
415
+ if (raw.trim() && Object.keys(o).length) return begin(finish);
416
+ silenceTimer = setTimeout(() => begin(finish), 1200);
417
+ });
418
+ process.stdin.on('end', () => begin(finish));
419
+ process.stdin.on('error', () => begin(finish));
420
+ process.stdin.on('close', () => begin(finish));
421
+ process.stdin.resume();
422
+ setTimeout(() => begin(finish), 2500); // stdin hiç açılmaz/kapanmazsa da devam et (asılmaya izin yok)
423
+ } catch (e) { begin(finish); }
424
+
425
+ /* watchdog: hiçbir koşulda 80 sn üzeri asılı kalma */
426
+ const WATCHDOG = setTimeout(() => {
427
+ fail('timeout', 'mt5_shot ' + Math.round((Date.now() - CTX.started) / 1000) + ' sn içinde tamamlanamadı (watchdog) — ack/PNG beklemesi aşıldı');
428
+ }, HARD_MS);
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "mt5_shot",
3
+ "description": "MT5 grafiğinin GERÇEK ekran görüntüsünü alır ve GÖRSELİ ÇAĞIRAN AJANA GÖSTERİR: MT5\\MQL5\\Files\\beast_cmd.json üzerinden BeastFinance EA'ya 'shot' komutu yazar, EA ChartScreenShot ile PNG üretir; PNG hem tool klasörüne hem <kullanıcı>\\beast_shots altına kopyalanır (dönen path send_file'a verilebilir) hem de data URL olarak sonuca gömülür — engine bunu sonraki turda ajana görsel olarak enjekte eder (embed:false kapatır). Aktif grafik istenen sembol/periyot ile aynıysa ondan çekilir (Beast panosu + seviye çizgileri görünür), değilse EA geçici grafik açar (ChartOpen/ChartSetSymbolPeriod + BeastShot.tpl açık tema). Komut id/ts ile yazılır, ack poll edilir ve PNG MQL5\\Files'ta belirene kadar beklenir; hata halinde gövde şu kodu taşır: err = no_terminal|ea_offline|cmd_write_fail|ea_no_ack|ea_error|png_yok|timeout (+ cmdId, filesDir, diagnostic, ping). Grafik analizi/raporu ve ekip paylaşımı için birincil screenshot aracıdır.",
4
+ "parameters": {
5
+ "type": "object",
6
+ "properties": {
7
+ "symbol": { "type": "string", "description": "Boş = aktif grafik sembolü (örn GOLD)" },
8
+ "timeframe": { "type": "string", "description": "M1|M5|M15|M30|H1|H4|D1 (boş = aktif periyot)" },
9
+ "width": { "type": "number", "description": "Görüntü genişliği (varsayılan 1600)" },
10
+ "height": { "type": "number", "description": "Görüntü yüksekliği (varsayılan 900)" },
11
+ "file": { "type": "string", "description": "Opsiyonel dosya adı (varsayılan beast_shot_<id>.png)" },
12
+ "timeoutSec": { "type": "number", "description": "Ack/PNG bekleme süresi sn (varsayılan 20, 5-70)" },
13
+ "outdir": { "type": "string", "description": "PNG'nin kopyalanacağı klasör (varsayılan <kullanıcı>\\beast_shots — send_file buradan gönderebilir)" },
14
+ "diag": { "type": "boolean", "description": "Ack gelmezse teşhis amaçlı ping at (varsayılan true)" },
15
+ "embed": { "type": "boolean", "description": "PNG'yi ajana görsel olarak enjekte et (varsayılan true; false = yalnız dosya yolu döner)" }
16
+ },
17
+ "required": []
18
+ },
19
+ "updatedAt": "2026-09-11T23:15:00.000Z"
20
+ }