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.
- package/package.json +1 -1
- package/src/agent/bots.js +669 -664
- package/src/agent/customtools.js +6 -19
- package/src/agent/engine.js +55 -307
- package/src/agent/seeds/tool-yazma/SKILL.md +13 -16
- package/src/agent/skills.js +14 -35
- package/src/agent/supertonic.js +622 -0
- package/src/agent/tools.js +3 -7
- package/src/main.js +77 -6068
- package/src/preload.js +2 -17
- package/src/renderer/i18n.js +14 -8
- package/src/renderer/index.html +0 -297
- package/src/renderer/renderer.js +142 -2280
- package/src/renderer/style.css +3 -825
- package/src/agent/defaulttools/mt5_barlar2/run.js +0 -188
- package/src/agent/defaulttools/mt5_barlar2/tool.json +0 -23
- package/src/agent/defaulttools/mt5_bekleyen/run.js +0 -105
- package/src/agent/defaulttools/mt5_bekleyen/tool.json +0 -18
- package/src/agent/defaulttools/mt5_durum/run.js +0 -93
- package/src/agent/defaulttools/mt5_durum/tool.json +0 -10
- package/src/agent/defaulttools/mt5_emir/run.js +0 -98
- package/src/agent/defaulttools/mt5_emir/tool.json +0 -17
- package/src/agent/defaulttools/mt5_emir_iptal/run.js +0 -90
- package/src/agent/defaulttools/mt5_emir_iptal/tool.json +0 -12
- package/src/agent/defaulttools/mt5_fiyat/run.js +0 -87
- package/src/agent/defaulttools/mt5_fiyat/tool.json +0 -16
- package/src/agent/defaulttools/mt5_gecmis/run.js +0 -87
- package/src/agent/defaulttools/mt5_gecmis/tool.json +0 -12
- package/src/agent/defaulttools/mt5_kapat/run.js +0 -108
- package/src/agent/defaulttools/mt5_kapat/tool.json +0 -15
- package/src/agent/defaulttools/mt5_m15_m5/m15m5.py +0 -60
- package/src/agent/defaulttools/mt5_m15_m5/run.js +0 -73
- package/src/agent/defaulttools/mt5_m15_m5/tool.json +0 -10
- package/src/agent/defaulttools/mt5_pozisyon_gecmis/run.js +0 -144
- package/src/agent/defaulttools/mt5_pozisyon_gecmis/tool.json +0 -12
- package/src/agent/defaulttools/mt5_shot/run.js +0 -649
- package/src/agent/defaulttools/mt5_shot/stitch.js +0 -151
- package/src/agent/defaulttools/mt5_shot/tool.json +0 -25
- package/src/agent/defaulttools/mt5_sltp/run.js +0 -92
- package/src/agent/defaulttools/mt5_sltp/tool.json +0 -14
- package/src/agent/financetools.js +0 -1025
- package/src/agent/finindicators.js +0 -254
- package/src/agent/finrisk.js +0 -289
- package/src/agent/finstats.js +0 -239
- package/src/agent/finwatch.js +0 -302
- package/src/agent/mt5/BeastFinance.mq5 +0 -498
- package/src/agent/mt5/BeastShot.tpl +0 -0
- package/src/agent/mt5setup.js +0 -542
- package/src/agent/scripts/mt5_bridge.py +0 -575
- package/src/agent/seeds/haber-duygu/SKILL.md +0 -49
- package/src/agent/seeds/mql5/SKILL.md +0 -154
- package/src/agent/seeds/price-action/SKILL.md +0 -60
- package/src/agent/seeds/risk-yonetimi/SKILL.md +0 -52
- 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();
|