beast-agent 2.13.0 → 2.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,215 @@
1
+ 'use strict';
2
+
3
+ /* Beast Finance — MT5 köprü yöneticisi (main süreç).
4
+ Python stdio köprüsünü (mt5_bridge.py) spawn eder; JSON satır istek/yanıt
5
+ ve event akışını yönetir. Python bulunamazsa aday listesinde sıradakini
6
+ dener; süreç düşerse backoff ile yeniden başlatır. */
7
+
8
+ const { spawn } = require('child_process');
9
+ const path = require('path');
10
+ const fs = require('fs');
11
+ const { EventEmitter } = require('events');
12
+
13
+ const BUNDLED_SCRIPT = path.join(__dirname, 'agent', 'scripts', 'mt5_bridge.py');
14
+
15
+ /* Paketli uygulamada köprü scripti app.asar İÇİNDE kalır — harici Python
16
+ onu OKUYAMAZ. Her başlatmada %APPDATA%\beast\finance altına güncel haliyle
17
+ yazılır (Node asar içini okuyabilir) ve oradan çalıştırılır. */
18
+ function scriptPath() {
19
+ try {
20
+ const dest = path.join(process.env.APPDATA || process.env.TMP || process.cwd(), 'beast', 'finance', 'mt5_bridge.py');
21
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
22
+ fs.copyFileSync(BUNDLED_SCRIPT, dest);
23
+ return dest;
24
+ } catch {
25
+ return BUNDLED_SCRIPT;
26
+ }
27
+ }
28
+
29
+ class MT5Bridge extends EventEmitter {
30
+ constructor() {
31
+ super();
32
+ this.proc = null;
33
+ this._buf = '';
34
+ this._reqId = 1;
35
+ this._pending = new Map();
36
+ this._stopped = true;
37
+ this._backoff = 1500;
38
+ this._lastError = '';
39
+ this._terminal = null;
40
+ this._account = null;
41
+ this._candidates = ['python', 'py -3'];
42
+ this._candIdx = 0;
43
+ this._terminalPath = '';
44
+ this._everConnected = false;
45
+ }
46
+
47
+ get running() {
48
+ return !!this.proc;
49
+ }
50
+
51
+ status() {
52
+ return {
53
+ running: this.running,
54
+ connected: !!this._terminal,
55
+ terminal: this._terminal,
56
+ account: this._account,
57
+ error: this._lastError,
58
+ python: this._candidates[this._candIdx] || '',
59
+ };
60
+ }
61
+
62
+ /* opts: { candidates: ['python','py -3'], terminal: 'C:\\...\\terminal64.exe' } */
63
+ start(opts = {}) {
64
+ if (Array.isArray(opts.candidates) && opts.candidates.length) {
65
+ const next = opts.candidates.map((c) => String(c).trim()).filter(Boolean);
66
+ if (next.join('|') !== this._candidates.join('|')) {
67
+ this._candidates = next;
68
+ this._candIdx = 0;
69
+ }
70
+ }
71
+ this._terminalPath = String(opts.terminal || '').trim();
72
+ this._stopped = false;
73
+ if (this.running) return;
74
+ this._spawn();
75
+ }
76
+
77
+ _spawn() {
78
+ if (this._stopped) return;
79
+ const cand = this._candidates[this._candIdx] || 'python';
80
+ /* 'py -3' gibi çok kelimeli kısa komutlar bölünür; tam yol tek exe'dir */
81
+ const parts = /[\\/]/.test(cand) ? [cand] : cand.split(/\s+/);
82
+ const exe = parts[0];
83
+ const preArgs = parts.slice(1);
84
+ let proc;
85
+ try {
86
+ proc = spawn(exe, [...preArgs, scriptPath(), ...(this._terminalPath ? ['--terminal', this._terminalPath] : [])], {
87
+ stdio: ['pipe', 'pipe', 'pipe'],
88
+ windowsHide: true,
89
+ });
90
+ } catch (e) {
91
+ this._lastError = 'Python başlatılamadı: ' + String((e && e.message) || e);
92
+ this.emit('log', { level: 'error', line: this._lastError });
93
+ this._nextCandidateOrRetry();
94
+ return;
95
+ }
96
+ this.proc = proc;
97
+ this._buf = '';
98
+ proc.stdout.setEncoding('utf8');
99
+ proc.stdout.on('data', (chunk) => {
100
+ this._buf += chunk;
101
+ let i;
102
+ while ((i = this._buf.indexOf('\n')) >= 0) {
103
+ const line = this._buf.slice(0, i).trim();
104
+ this._buf = this._buf.slice(i + 1);
105
+ if (line) this._onLine(line);
106
+ }
107
+ });
108
+ proc.stderr.setEncoding('utf8');
109
+ proc.stderr.on('data', (c) => {
110
+ const t = String(c).trim();
111
+ if (t) this.emit('log', { level: 'error', line: t.slice(0, 400) });
112
+ });
113
+ proc.on('error', (e) => {
114
+ this._lastError = `Python (${exe}) bulunamadı/başlatılamadı: ` + String((e && e.message) || e);
115
+ this.emit('log', { level: 'error', line: this._lastError });
116
+ try { this._failAll(this._lastError); } catch {}
117
+ if (this.proc === proc) this.proc = null;
118
+ this._nextCandidateOrRetry();
119
+ });
120
+ proc.on('exit', (code) => {
121
+ if (this.proc === proc) this.proc = null;
122
+ this._terminal = null;
123
+ this._account = null;
124
+ try { this._failAll('MT5 köprüsü kapandı (kod ' + code + ')'); } catch {}
125
+ this.emit('bridge', { connected: false, error: this._lastError || ('süreç kapandı (kod ' + code + ')') });
126
+ this._nextCandidateOrRetry();
127
+ });
128
+ }
129
+
130
+ /* aday python'ları sırayla dener; hepsi tükendiyse backoff ile yeniden dener */
131
+ _nextCandidateOrRetry() {
132
+ if (this._stopped) return;
133
+ if (this._candIdx < this._candidates.length - 1) {
134
+ this._candIdx += 1;
135
+ this.emit('log', { level: 'info', line: 'python adayı deneniyor: ' + this._candidates[this._candIdx] });
136
+ setTimeout(() => { try { this._spawn(); } catch {} }, 300);
137
+ return;
138
+ }
139
+ this._candIdx = 0;
140
+ setTimeout(() => { try { if (!this._stopped && !this.running) this._spawn(); } catch {} }, this._backoff);
141
+ this._backoff = Math.min(this._backoff * 2, 15000);
142
+ }
143
+
144
+ _onLine(line) {
145
+ let msg;
146
+ try { msg = JSON.parse(line); } catch {
147
+ this.emit('log', { level: 'info', line: line.slice(0, 240) });
148
+ return;
149
+ }
150
+ if (msg.event) {
151
+ if (msg.event === 'bridge') {
152
+ this._terminal = msg.terminal || null;
153
+ this._account = msg.account || null;
154
+ this._lastError = String(msg.error || '');
155
+ if (msg.connected) {
156
+ this._everConnected = true;
157
+ this._backoff = 1500;
158
+ }
159
+ this.emit('bridge', msg);
160
+ } else if (msg.event === 'log') {
161
+ this.emit('log', msg);
162
+ } else {
163
+ this.emit('event', msg);
164
+ }
165
+ return;
166
+ }
167
+ if (msg.id != null && this._pending.has(msg.id)) {
168
+ const en = this._pending.get(msg.id);
169
+ this._pending.delete(msg.id);
170
+ clearTimeout(en.timer);
171
+ en.resolve(msg);
172
+ }
173
+ }
174
+
175
+ call(method, params, timeoutMs = 12000) {
176
+ if (!this.running) return Promise.resolve({ ok: false, error: 'MT5 köprüsü çalışmıyor' });
177
+ const id = this._reqId++;
178
+ let resolveFn = null;
179
+ const p = new Promise((resolve) => { resolveFn = resolve; });
180
+ const timer = setTimeout(() => {
181
+ this._pending.delete(id);
182
+ resolveFn({ ok: false, error: 'MT5 zaman aşımı: ' + method });
183
+ }, Math.max(2000, Number(timeoutMs) || 12000));
184
+ this._pending.set(id, { timer, resolve: resolveFn });
185
+ try {
186
+ this.proc.stdin.write(JSON.stringify({ id, method, params: params || {} }) + '\n');
187
+ } catch (e) {
188
+ clearTimeout(timer);
189
+ this._pending.delete(id);
190
+ resolveFn({ ok: false, error: 'köprü yazma hatası: ' + String((e && e.message) || e) });
191
+ }
192
+ return p;
193
+ }
194
+
195
+ stop() {
196
+ this._stopped = true;
197
+ try { this._failAll('köprü kapatıldı'); } catch {}
198
+ if (this.proc) {
199
+ try { this.proc.kill(); } catch {}
200
+ this.proc = null;
201
+ }
202
+ this._terminal = null;
203
+ this._account = null;
204
+ }
205
+
206
+ _failAll(err) {
207
+ for (const [, en] of this._pending) {
208
+ clearTimeout(en.timer);
209
+ en.resolve({ ok: false, error: err });
210
+ }
211
+ this._pending.clear();
212
+ }
213
+ }
214
+
215
+ module.exports = new MT5Bridge();
package/src/preload.js CHANGED
@@ -94,6 +94,17 @@ contextBridge.exposeInMainWorld('beast', {
94
94
  studioTree: (rel) => ipcRenderer.invoke('studio:tree', rel),
95
95
  studioPickVideo: () => ipcRenderer.invoke('studio:pickvideo'),
96
96
  studioDelete: (rel) => ipcRenderer.invoke('studio:delete', rel),
97
+
98
+ financeState: () => ipcRenderer.invoke('finance:state'),
99
+ financeSnapshot: () => ipcRenderer.invoke('finance:snapshot'),
100
+ financeMode: (on, sessionId) => ipcRenderer.invoke('finance:mode', { on, sessionId }),
101
+ financeSettings: (patch) => ipcRenderer.invoke('finance:settings', patch),
102
+ financeTraderStart: () => ipcRenderer.invoke('finance:trader:start'),
103
+ financeTraderStop: () => ipcRenderer.invoke('finance:trader:stop'),
104
+ financeConnect: () => ipcRenderer.invoke('finance:connect'),
105
+ financeClose: (ticket, volume) => ipcRenderer.invoke('finance:close', { ticket, volume }),
106
+ financeInstall: () => ipcRenderer.invoke('finance:install'),
107
+ financeSymbolsList: (filter) => ipcRenderer.invoke('finance:symbols:list', filter),
97
108
  clipRead: () => ipcRenderer.invoke('clip:read'),
98
109
  clipWrite: (t) => ipcRenderer.invoke('clip:write', t),
99
110
  thinkSet: (v) => ipcRenderer.invoke('think:set', v),
@@ -540,6 +540,8 @@
540
540
  bot_plugin_sub: 'Aktif plugin\u2019leri seç (değişiklik loglanır).',
541
541
  bot_watcher_sub: 'Bu botun oturumlarına bağlı izleyiciler.',
542
542
  bot_no_watchers: 'Bu bota ait izleyici yok.',
543
+ w_pane: 'İZLEYİCİLER',
544
+ w_empty: 'Kayıtlı izleyici yok.',
543
545
  bot_stats_sub: 'Bu botun kullanım özeti.',
544
546
  bot_stat_numbers: 'Numara',
545
547
  bot_stat_sessions: 'Sohbet',
@@ -1035,6 +1037,7 @@
1035
1037
  cr_ran: 'Started',
1036
1038
  cr_toggle_pause: 'Pause',
1037
1039
  w_empty: 'No saved watchers — add from the form above.',
1040
+ w_pane: 'WATCHERS',
1038
1041
  wa_vazgec: 'Cancel',
1039
1042
  wa_kaydet: 'save',
1040
1043
  wa_updated: 'Updated',
@@ -1065,7 +1068,6 @@
1065
1068
  cr_modal_empty: 'Kayıtlı cron görev yok — Ayarlar → Cron sekmesinden ekle.',
1066
1069
  cr_ran: 'Çalıştırıldı',
1067
1070
  cr_toggle_pause: 'Duraklat',
1068
- w_empty: 'Kayıtlı izleyici yok — yukarıdaki formla ekle.',
1069
1071
  wa_vazgec: 'Vazgeç',
1070
1072
  wa_kaydet: 'kaydet',
1071
1073
  wa_updated: 'Güncellendi',
@@ -13,6 +13,14 @@
13
13
  <div id="brand"><span class="brand-beast">BEAST</span><span class="brand-sub">Agent</span></div>
14
14
  <button id="newChat" title="Yeni sohbet" data-i18n="newChat">+ Yeni Sohbet</button>
15
15
  <div id="sessList"></div>
16
+ <div id="watchPane">
17
+ <div id="watchPaneHead">
18
+ <span id="watchPaneTitle" data-i18n="w_pane">İZLEYİCİLER</span>
19
+ <span class="file-spacer"></span>
20
+ <button id="watchPaneOpen" title="İzleyicileri yönet">+</button>
21
+ </div>
22
+ <div id="watchPaneList"></div>
23
+ </div>
16
24
  <div id="botBar">
17
25
  <div id="botBarHead">
18
26
  <span id="botBarTitle" data-i18n="bot_head">BOTLAR</span>
@@ -82,6 +90,7 @@
82
90
  </div>
83
91
  </div>
84
92
  <button id="studioBtn" class="dd-btn dd-icon-btn" title="Beast Studio — video yapma/düzenleme" data-i18n-title="tipStudio"><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="6" width="13" height="12" rx="2"/><path d="M15 10l7-4v12l-7-4"/></svg></button>
93
+ <button id="finBtn" class="dd-btn dd-icon-btn" title="Beast Finance — MT5 trade paneli (bilgisayardaki MetaTrader 5 ile çalışır)"><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3v18h18"/><path d="M7 15l4-5 3 3 5-7"/></svg></button>
85
94
  <button id="sbBtn" class="dd-btn dd-icon-btn" title="Beast Sandbox — GitHub reposu indir &amp; içinde çalış" data-i18n-title="tipSandbox"><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg></button>
86
95
  <button id="railBtn" class="dd-btn dd-icon-btn" title="Paralel Ajan Konsolu" data-i18n-title="tipRail">&#x25A4;</button>
87
96
  <button id="watchBtn" class="dd-btn dd-icon-btn" title="İzleyiciler (Watchers)" data-i18n-title="tipWatch">&#x25CE;</button>
@@ -122,6 +131,57 @@
122
131
  </div>
123
132
  </div>
124
133
 
134
+ <section id="finPanel">
135
+ <div id="finHead">
136
+ <span id="finTitle">BEAST FINANCE</span>
137
+ <span id="finDot" class="fin-dot off" title="MT5 köprüsü"></span>
138
+ <span id="finTerm" title="">MT5</span>
139
+ <span class="file-spacer"></span>
140
+ <button id="finReconnect" title="MT5'e yeniden bağlan">&#x27F3;</button>
141
+ </div>
142
+ <div id="finBody">
143
+ <div class="fin-card">
144
+ <div class="fin-card-title">HESAP</div>
145
+ <div class="fin-grid">
146
+ <div class="fin-kv"><span>Bakiye</span><b id="finBalance">—</b></div>
147
+ <div class="fin-kv"><span>Özkaynak</span><b id="finEquity">—</b></div>
148
+ <div class="fin-kv"><span>Kâr/Zarar</span><b id="finProfit">—</b></div>
149
+ <div class="fin-kv"><span>Serbest Marj</span><b id="finFree">—</b></div>
150
+ </div>
151
+ </div>
152
+ <div class="fin-card">
153
+ <div class="fin-card-title">TRADE AJANI <span id="finTraderDot" class="fin-dot off" title="Trader durumu"></span></div>
154
+ <div class="fin-row"><select id="finTraderModel" title="Trader ajanının modeli — farklı API/model seçilebilir"></select></div>
155
+ <div class="fin-row fin-mini">
156
+ <label>Tur (sn)</label><input id="finInterval" type="number" min="30" max="3600" step="10" />
157
+ <label>Max lot</label><input id="finMaxLot" type="number" min="0.01" max="100" step="0.01" />
158
+ </div>
159
+ <div class="fin-row fin-mini"><label>Semboller</label><button id="finSymBtn" class="fin-sym-select" title="MT5 sembol listesinden seç — tıkla">&#8230;</button></div>
160
+ <div class="fin-row">
161
+ <label class="fin-check"><input id="finAllow" type="checkbox" /> Otomatik işlem (ajan trade açabilir)</label>
162
+ <button id="finTraderBtn" class="fin-start">&#9654;&#xFE0E; Ajanı Başlat</button>
163
+ </div>
164
+ <div id="finTraderStatus">Kapalı</div>
165
+ </div>
166
+ <div class="fin-card">
167
+ <div class="fin-card-title">POZİSYONLAR <span id="finPosCount"></span></div>
168
+ <div id="finPosList"><div class="fin-empty">Açık pozisyon yok</div></div>
169
+ </div>
170
+ <div class="fin-card">
171
+ <div class="fin-card-title">
172
+ PİYASA
173
+ <span class="file-spacer"></span>
174
+ <button id="finSymAdd" title="Sembol ekle/çıkar — MT5 sembol listesinden seç">&#43;</button>
175
+ </div>
176
+ <div id="finSymList"><div class="fin-empty">—</div></div>
177
+ </div>
178
+ <div class="fin-card fin-grow">
179
+ <div class="fin-card-title">AKIŞ</div>
180
+ <div id="finLog"></div>
181
+ </div>
182
+ </div>
183
+ </section>
184
+
125
185
  <div id="ideRow">
126
186
  <section id="codePane">
127
187
  <div id="codeTabsBar">
@@ -177,7 +237,7 @@
177
237
  </div>
178
238
  <img id="bcQrImg" alt="Expo QR" draggable="false" />
179
239
  <div id="bcQrUrl" title=""></div>
180
- <div class="bq-hint">Telefonda <b>Expo Go</b> uygulamasıyla okut</div>
240
+ <div class="bq-hint">Telefonda <b>Expo Go</b> ile okut — tünel modunda her ağdan çalışır</div>
181
241
  </div>
182
242
  <div id="bcInputRow">
183
243
  <button id="bcAttach" title="Dosya / resim ekle" data-i18n-title="tipAttach">+</button>
@@ -468,6 +528,21 @@
468
528
  </div>
469
529
  </div>
470
530
 
531
+ <div id="finSymOverlay" class="mini-overlay" hidden>
532
+ <div class="mini-dialog fin-sym-dialog">
533
+ <div class="mini-head">
534
+ <span class="mini-title">MT5 SEMBOLLERİ</span>
535
+ <span id="finSymCount" class="fin-sym-count"></span>
536
+ <button class="mini-close" id="finSymClose" title="Kapat">&#x00D7;</button>
537
+ </div>
538
+ <div class="mini-body">
539
+ <input id="finSymSearch" placeholder="Sembol ara… (örn: XAU, BTC, EUR, US500)" spellcheck="false" autocomplete="off" />
540
+ <div id="finSymPick" class="mini-list"></div>
541
+ <div class="fin-sym-hint">Tıkla: izleme listesine ekle/çıkar — PİYASA kartına anında düşer</div>
542
+ </div>
543
+ </div>
544
+ </div>
545
+
471
546
  <div id="cronOverlay" class="mini-overlay" hidden>
472
547
  <div class="mini-dialog">
473
548
  <div class="mini-head">