beast-agent 2.14.0 → 2.16.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/engine.js +499 -154
  3. package/src/agent/financetools.js +307 -0
  4. package/src/agent/opencode/agents.js +161 -0
  5. package/src/agent/opencode/askperm.js +135 -0
  6. package/src/agent/opencode/index.js +51 -0
  7. package/src/agent/opencode/permission.js +108 -0
  8. package/src/agent/opencode/prompts/anthropic.txt +105 -0
  9. package/src/agent/opencode/prompts/beast.txt +147 -0
  10. package/src/agent/opencode/prompts/build-switch.txt +5 -0
  11. package/src/agent/opencode/prompts/codex.txt +79 -0
  12. package/src/agent/opencode/prompts/default.txt +95 -0
  13. package/src/agent/opencode/prompts/edit.txt +10 -0
  14. package/src/agent/opencode/prompts/explore.txt +18 -0
  15. package/src/agent/opencode/prompts/gemini.txt +155 -0
  16. package/src/agent/opencode/prompts/glob.txt +6 -0
  17. package/src/agent/opencode/prompts/gpt.txt +107 -0
  18. package/src/agent/opencode/prompts/grep.txt +8 -0
  19. package/src/agent/opencode/prompts/kimi.txt +95 -0
  20. package/src/agent/opencode/prompts/max-steps.txt +16 -0
  21. package/src/agent/opencode/prompts/meta.txt +65 -0
  22. package/src/agent/opencode/prompts/plan.txt +26 -0
  23. package/src/agent/opencode/prompts/read.txt +14 -0
  24. package/src/agent/opencode/prompts/skill.txt +5 -0
  25. package/src/agent/opencode/prompts/task.txt +19 -0
  26. package/src/agent/opencode/prompts/todowrite.txt +44 -0
  27. package/src/agent/opencode/prompts/trinity.txt +97 -0
  28. package/src/agent/opencode/prompts/webfetch.txt +13 -0
  29. package/src/agent/opencode/prompts/websearch.txt +14 -0
  30. package/src/agent/opencode/prompts/write.txt +8 -0
  31. package/src/agent/opencode/systemprompt.js +157 -0
  32. package/src/agent/opencode/toolmap.js +316 -0
  33. package/src/agent/scripts/mt5_bridge.py +381 -0
  34. package/src/agent/tools.js +10 -3
  35. package/src/main.js +503 -69
  36. package/src/mt5bridge.js +215 -0
  37. package/src/preload.js +13 -3
  38. package/src/renderer/index.html +67 -0
  39. package/src/renderer/renderer.js +552 -53
  40. package/src/renderer/style.css +328 -1
  41. package/src/agent/android.js +0 -265
@@ -1,4 +1,4 @@
1
- /* Beast Agent — monokrom tema: siyah + füme + beyaz */
1
+ /* Beast Agent — monokrom tema: siyah + füme + beyaz */
2
2
 
3
3
  :root {
4
4
  --bg: #f7f7f8;
@@ -4192,3 +4192,330 @@ body.sandbox-mode #sandboxRow { display: flex; flex-direction: row; }
4192
4192
  font-weight: 700;
4193
4193
  pointer-events: none;
4194
4194
  }
4195
+
4196
+ /* ================= BEAST FINANCE MODU =================
4197
+ Studio/Sandbox'tan farklı düzen: SOL sidebar (sohbet geçmişi + izleyiciler)
4198
+ ve ORTA chat AYNI kalır; SAĞDA MT5 işlem paneli açılır. İki ajan:
4199
+ chat copilot'ı (aktif sohbet) + otonom TRADER (farklı API/model). */
4200
+
4201
+ #finPanel { display: none; }
4202
+
4203
+ body.finance-mode #main { position: relative; }
4204
+
4205
+ body.finance-mode #finPanel {
4206
+ display: flex;
4207
+ flex-direction: column;
4208
+ position: absolute;
4209
+ top: 46px;
4210
+ right: 0;
4211
+ bottom: 0;
4212
+ width: var(--finW, 400px);
4213
+ min-width: 320px;
4214
+ background: var(--bg);
4215
+ border-left: 1px solid var(--border);
4216
+ z-index: 40;
4217
+ }
4218
+
4219
+ /* chat alanı sağda panel payı kadar kırpılır */
4220
+ body.finance-mode #chatScroll,
4221
+ body.finance-mode #composerWrap { margin-right: var(--finW, 400px); }
4222
+
4223
+ #finBtn.on { background: var(--accent-dim); color: var(--accent); }
4224
+
4225
+ #finHead {
4226
+ flex: none;
4227
+ display: flex;
4228
+ align-items: center;
4229
+ gap: 7px;
4230
+ padding: 9px 12px;
4231
+ border-bottom: 1px solid var(--border);
4232
+ background: var(--panel);
4233
+ }
4234
+ #finTitle { font-size: 11px; font-weight: 800; letter-spacing: 1px; color: var(--accent); }
4235
+ #finTerm {
4236
+ font-size: 11px;
4237
+ color: var(--muted);
4238
+ max-width: 150px;
4239
+ white-space: nowrap;
4240
+ overflow: hidden;
4241
+ text-overflow: ellipsis;
4242
+ }
4243
+ #finHead button {
4244
+ background: none;
4245
+ border: none;
4246
+ color: var(--muted);
4247
+ font-size: 13px;
4248
+ cursor: pointer;
4249
+ padding: 3px 7px;
4250
+ border-radius: 6px;
4251
+ }
4252
+ #finHead button:hover { background: var(--panel2); color: var(--text); }
4253
+
4254
+ .fin-dot {
4255
+ flex: none;
4256
+ width: 9px;
4257
+ height: 9px;
4258
+ border-radius: 50%;
4259
+ background: var(--muted);
4260
+ }
4261
+ .fin-dot.on { background: #22c55e; box-shadow: 0 0 6px rgba(34, 197, 94, 0.7); }
4262
+ .fin-dot.off { background: #ef4444; }
4263
+ .fin-dot.busy { background: #eab308; box-shadow: 0 0 6px rgba(234, 179, 8, 0.7); }
4264
+
4265
+ #finBody {
4266
+ flex: 1;
4267
+ min-height: 0;
4268
+ overflow-y: auto;
4269
+ display: flex;
4270
+ flex-direction: column;
4271
+ gap: 8px;
4272
+ padding: 10px;
4273
+ }
4274
+
4275
+ .fin-card {
4276
+ flex: none;
4277
+ border: 1px solid var(--border);
4278
+ border-radius: 10px;
4279
+ background: var(--panel);
4280
+ padding: 9px 10px;
4281
+ }
4282
+ .fin-card.fin-grow { flex: 1 1 auto; min-height: 90px; display: flex; flex-direction: column; }
4283
+ .fin-card-title {
4284
+ font-size: 10px;
4285
+ font-weight: 800;
4286
+ letter-spacing: 1px;
4287
+ color: var(--muted);
4288
+ margin-bottom: 7px;
4289
+ display: flex;
4290
+ align-items: center;
4291
+ gap: 6px;
4292
+ }
4293
+
4294
+ .fin-grid {
4295
+ display: grid;
4296
+ grid-template-columns: 1fr 1fr;
4297
+ gap: 6px 10px;
4298
+ }
4299
+ .fin-kv { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
4300
+ .fin-kv span { font-size: 10px; color: var(--muted); }
4301
+ .fin-kv b { font-size: 13px; font-variant-numeric: tabular-nums; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
4302
+
4303
+ .fin-row { display: flex; align-items: center; gap: 8px; margin-bottom: 7px; }
4304
+ .fin-row:last-child { margin-bottom: 0; }
4305
+ .fin-row.fin-mini { gap: 6px; }
4306
+ .fin-row.fin-mini label { flex: none; font-size: 10px; color: var(--muted); min-width: 44px; }
4307
+ .fin-row.fin-mini input { width: 60px; }
4308
+ .fin-row input, .fin-row select {
4309
+ flex: 1;
4310
+ min-width: 0;
4311
+ background: var(--panel2);
4312
+ border: 1px solid var(--border);
4313
+ color: var(--text);
4314
+ font-size: 12px;
4315
+ padding: 4px 7px;
4316
+ border-radius: 6px;
4317
+ }
4318
+ .fin-row input:focus, .fin-row select:focus { border-color: var(--accent); outline: none; }
4319
+
4320
+ /* TRADE AJANI sembol seçme düğmesi — tıklayınca MT5 sembol seçici açılır */
4321
+ .fin-sym-select {
4322
+ flex: 1;
4323
+ min-width: 0;
4324
+ text-align: left;
4325
+ background: var(--panel2);
4326
+ border: 1px solid var(--border);
4327
+ color: var(--text);
4328
+ font-size: 12px;
4329
+ padding: 4px 8px;
4330
+ border-radius: 6px;
4331
+ cursor: pointer;
4332
+ white-space: nowrap;
4333
+ overflow: hidden;
4334
+ text-overflow: ellipsis;
4335
+ }
4336
+ .fin-sym-select:hover { border-color: var(--accent); color: var(--accent); }
4337
+ .fin-check {
4338
+ flex: 1;
4339
+ display: flex;
4340
+ align-items: center;
4341
+ gap: 6px;
4342
+ font-size: 11px;
4343
+ color: var(--text);
4344
+ cursor: pointer;
4345
+ min-width: 0;
4346
+ white-space: nowrap;
4347
+ overflow: hidden;
4348
+ text-overflow: ellipsis;
4349
+ }
4350
+ .fin-check input { flex: none; width: auto; }
4351
+ .fin-start {
4352
+ flex: none;
4353
+ background: var(--accent);
4354
+ border: none;
4355
+ color: #fff;
4356
+ font-size: 11px;
4357
+ font-weight: 700;
4358
+ padding: 6px 12px;
4359
+ border-radius: 7px;
4360
+ cursor: pointer;
4361
+ }
4362
+ .fin-start:hover { filter: brightness(1.1); }
4363
+ .fin-start.stop { background: #ef4444; }
4364
+
4365
+ #finTraderStatus {
4366
+ margin-top: 7px;
4367
+ font-size: 11px;
4368
+ color: var(--muted);
4369
+ white-space: nowrap;
4370
+ overflow: hidden;
4371
+ text-overflow: ellipsis;
4372
+ }
4373
+
4374
+ #finPosList, #finSymList { display: flex; flex-direction: column; gap: 5px; }
4375
+ .fin-empty { font-size: 11px; color: var(--muted); padding: 2px 0 4px; }
4376
+
4377
+ .fin-pos {
4378
+ display: flex;
4379
+ align-items: center;
4380
+ gap: 7px;
4381
+ font-size: 11px;
4382
+ background: var(--panel2);
4383
+ border: 1px solid var(--border);
4384
+ border-radius: 7px;
4385
+ padding: 5px 8px;
4386
+ }
4387
+ .fin-pos .fp-sym { font-weight: 700; min-width: 54px; }
4388
+ .fin-pos .fp-dir {
4389
+ flex: none;
4390
+ font-weight: 800;
4391
+ font-size: 9px;
4392
+ padding: 2px 5px;
4393
+ border-radius: 5px;
4394
+ letter-spacing: 0.5px;
4395
+ }
4396
+ .fin-pos .fp-dir.buy { background: rgba(34, 197, 94, 0.16); color: #22c55e; }
4397
+ .fin-pos .fp-dir.sell { background: rgba(239, 68, 68, 0.16); color: #ef4444; }
4398
+ .fin-pos .fp-meta { flex: 1; min-width: 0; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
4399
+ .fin-pos .fp-pl { font-weight: 800; font-variant-numeric: tabular-nums; white-space: nowrap; }
4400
+ .fin-pos .fp-pl.pos { color: #22c55e; }
4401
+ .fin-pos .fp-pl.neg { color: #ef4444; }
4402
+ .fin-pos .fp-close {
4403
+ flex: none;
4404
+ background: none;
4405
+ border: none;
4406
+ color: var(--muted);
4407
+ font-size: 12px;
4408
+ cursor: pointer;
4409
+ padding: 2px 5px;
4410
+ border-radius: 5px;
4411
+ }
4412
+ .fin-pos .fp-close:hover { background: rgba(239, 68, 68, 0.15); color: #ef4444; }
4413
+
4414
+ .fin-sym {
4415
+ display: flex;
4416
+ align-items: center;
4417
+ justify-content: space-between;
4418
+ gap: 7px;
4419
+ font-size: 11px;
4420
+ padding: 4px 6px;
4421
+ border-radius: 6px;
4422
+ background: var(--panel2);
4423
+ border: 1px solid var(--border);
4424
+ }
4425
+ .fin-sym .fs-name { font-weight: 700; }
4426
+ .fin-sym .fs-price { font-variant-numeric: tabular-nums; }
4427
+ .fin-sym .fs-up { color: #22c55e; }
4428
+ .fin-sym .fs-down { color: #ef4444; }
4429
+ .fin-sym .fs-spread { color: var(--muted); font-size: 10px; }
4430
+
4431
+ #finLog {
4432
+ flex: 1;
4433
+ min-height: 0;
4434
+ overflow-y: auto;
4435
+ font-family: Consolas, monospace;
4436
+ font-size: 10.5px;
4437
+ line-height: 1.55;
4438
+ color: var(--muted);
4439
+ white-space: pre-wrap;
4440
+ word-break: break-word;
4441
+ }
4442
+ #finLog .fin-log-new { color: var(--text); }
4443
+
4444
+ /* PİYASA başlığındaki sembol ekleme ikonu */
4445
+ .fin-card-title button {
4446
+ background: none;
4447
+ border: none;
4448
+ color: var(--muted);
4449
+ font-size: 13px;
4450
+ line-height: 1;
4451
+ cursor: pointer;
4452
+ padding: 1px 6px;
4453
+ border-radius: 5px;
4454
+ }
4455
+ .fin-card-title button:hover { background: var(--accent-dim); color: var(--accent); }
4456
+
4457
+ /* MT5 sembol seçici modalı */
4458
+ .fin-sym-dialog { width: 460px; max-width: calc(100vw - 40px); }
4459
+ .fin-sym-count { font-size: 11px; color: var(--muted); margin-left: auto; margin-right: 6px; }
4460
+ #finSymSearch {
4461
+ width: 100%;
4462
+ box-sizing: border-box;
4463
+ background: var(--panel2);
4464
+ border: 1px solid var(--border);
4465
+ color: var(--text);
4466
+ font-size: 12px;
4467
+ padding: 6px 9px;
4468
+ border-radius: 7px;
4469
+ margin-bottom: 8px;
4470
+ }
4471
+ #finSymSearch:focus { border-color: var(--accent); outline: none; }
4472
+ .fin-sym-dialog .mini-body {
4473
+ flex: 1;
4474
+ min-height: 0;
4475
+ display: flex;
4476
+ flex-direction: column;
4477
+ overflow: hidden;
4478
+ }
4479
+ #finSymPick {
4480
+ flex: 1;
4481
+ min-height: 0;
4482
+ overflow-y: auto;
4483
+ display: flex;
4484
+ flex-direction: column;
4485
+ gap: 3px;
4486
+ }
4487
+ .fin-sym-row {
4488
+ display: flex;
4489
+ align-items: center;
4490
+ gap: 8px;
4491
+ font-size: 12px;
4492
+ padding: 5px 8px;
4493
+ border-radius: 6px;
4494
+ cursor: pointer;
4495
+ border: 1px solid transparent;
4496
+ }
4497
+ .fin-sym-row:hover { background: var(--panel2); }
4498
+ .fin-sym-row.on { background: var(--accent-dim); border-color: var(--accent); }
4499
+ .fin-sym-row .fsr-check {
4500
+ flex: none;
4501
+ width: 14px;
4502
+ text-align: center;
4503
+ color: var(--accent);
4504
+ font-weight: 800;
4505
+ }
4506
+ .fin-sym-row .fsr-name { flex: none; min-width: 84px; font-weight: 700; color: var(--text); }
4507
+ .fin-sym-row .fsr-desc {
4508
+ flex: 1;
4509
+ min-width: 0;
4510
+ color: var(--muted);
4511
+ white-space: nowrap;
4512
+ overflow: hidden;
4513
+ text-overflow: ellipsis;
4514
+ }
4515
+ .fin-sym-hint {
4516
+ flex: none;
4517
+ margin-top: 8px;
4518
+ font-size: 10.5px;
4519
+ color: var(--muted);
4520
+ text-align: center;
4521
+ }
@@ -1,265 +0,0 @@
1
- 'use strict';
2
-
3
- /* ---------- ANDROID EMÜLATÖR (İSTEĞE BAĞLI — Beast Code mobil önizleme) ----------
4
- Ayarlardan (Kurulum) görünür; KUR denince arka planda HAFİF set kurar:
5
- JDK 17 (winget, java yoksa) + Android cmdline-tools (~150MB) +
6
- platform-tools/emulator + 1 sistem imajı + 1 AVD ("Beast").
7
- SDK'yı standarda yerleştirir: %LOCALAPPDATA%\Android\Sdk (Android Studio ile
8
- paylaşımlı konum — kullanıcıda zaten varsa KURULUM YAPMAZ, onu kullanır).
9
- Beast Code preview akışı: AVD varsa ajan `npx expo start --android` ile
10
- uygulamayı emülatörde açar (expo AVD'yi adb üzerinden kendisi bulur). */
11
-
12
- const { spawn, execSync } = require('child_process');
13
- const fs = require('fs');
14
- const path = require('path');
15
- const os = require('os');
16
- const { emitInstallProgress } = require('./progressbus');
17
-
18
- const SDK_DIR = path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'), 'Android', 'Sdk');
19
- const CMDTOOLS_URL = 'https://dl.google.com/android/repository/commandlinetools-win-11076708_latest.zip';
20
- const SYS_IMAGE = 'system-images;android-34;google_apis;x86_64';
21
- const AVD_NAME = 'Beast';
22
-
23
- const state = {
24
- installing: false,
25
- installFailed: false,
26
- installError: '',
27
- stage: '',
28
- booting: false,
29
- };
30
-
31
- function sdkCandidates() {
32
- const list = [];
33
- for (const v of [process.env.ANDROID_HOME, process.env.ANDROID_SDK_ROOT]) {
34
- if (v && fs.existsSync(v)) list.push(path.resolve(String(v)));
35
- }
36
- list.push(SDK_DIR);
37
- return list.filter((p) => fs.existsSync(p));
38
- }
39
-
40
- function findSdk(rel, name) {
41
- for (const sdk of sdkCandidates()) {
42
- const p = path.join(sdk, ...rel.split('/'), name);
43
- if (fs.existsSync(p)) return p;
44
- }
45
- /* PATH'te ara (kullanıcı elle kurduysa) */
46
- try {
47
- const out = String(execSync(`where ${name}`, { timeout: 5000, stdio: 'pipe', encoding: 'utf8', windowsHide: true }));
48
- const first = out.split(/\r?\n/).find(Boolean);
49
- if (first) return String(first).trim();
50
- } catch {}
51
- return '';
52
- }
53
-
54
- function findAdb() { return findSdk('platform-tools', 'adb.exe'); }
55
-
56
- function findEmulator() { return findSdk('emulator', 'emulator.exe'); }
57
-
58
- function javaOk() {
59
- try {
60
- const out = String(execSync('java -version 2>&1', { timeout: 6000, stdio: 'pipe', encoding: 'utf8', windowsHide: true }));
61
- const m = /version "?(\d+)/.exec(out);
62
- return m ? Number(m[1]) >= 17 : false;
63
- } catch {
64
- return false;
65
- }
66
- }
67
-
68
- async function listAvds() {
69
- const emu = findEmulator();
70
- if (!emu) return [];
71
- try {
72
- const out = String(execSync(`"${emu}" -list-avds`, { timeout: 10000, stdio: 'pipe', encoding: 'utf8', windowsHide: true }));
73
- return out.split(/\r?\n/).map((s) => s.trim()).filter(Boolean);
74
- } catch {
75
- return [];
76
- }
77
- }
78
-
79
- function probe() {
80
- const adb = findAdb();
81
- const emu = findEmulator();
82
- return {
83
- sdk: !!(adb && emu),
84
- sdkPath: sdkCandidates()[0] || '',
85
- adb,
86
- emulator: emu,
87
- installing: state.installing,
88
- failed: state.installFailed,
89
- error: state.installError,
90
- booting: state.booting,
91
- };
92
- }
93
-
94
- /* ---------- ARKA PLAN KURULUM ----------
95
- Aşamalar (progress bus 'android'): 1 java → 2 cmdline-tools → 3 sdk paketleri
96
- (en ağır: ~1.4GB) → 4 AVD. Yarıda kalırsa idempotent: yeniden KUR kaldığından değil
97
- baştan ama varolan parçaları atlar (licenses/paket kontrolü). */
98
- function autoInstall() {
99
- if (state.installing) return { ok: true, installing: true };
100
- if (probe().adb && probe().emu) return { ok: true, already: true };
101
- state.installing = true;
102
- state.installFailed = false;
103
- state.installError = '';
104
- runInstall().catch((e) => {
105
- state.installing = false;
106
- state.installFailed = true;
107
- state.installError = String((e && e.message) || e).slice(0, 160);
108
- });
109
- return { ok: true, installing: true };
110
- }
111
-
112
- /* .bat çağırıcı: Node güvenlik yaması (CVE-2024-27980) .bat/.cmd'yi shell'siz
113
- spawn etmeyi EINVAL'le reddeder — cmd.exe /c üzerinden güvenli çağrı */
114
- function spawnBat(bat, args, opts = {}) {
115
- const cmdline = ['"' + bat + '"']
116
- .concat(args.map((a) => (/[\s;&|^<>(),!%]/.test(a) ? '"' + a + '"' : a)))
117
- .join(' ');
118
- return spawn('cmd.exe', ['/d', '/s', '/c', cmdline], { windowsHide: true, ...opts.spawnOpts });
119
- }
120
-
121
- /* .bat çağırıcı (GÜVENLİ PATİKA): Node .bat'i shell'siz spawn etmeyi EINVAL'le
122
- reddeder; cmd.exe'ye args dizisiyle geçmek de tırnak/redirect kaçışında kırılır.
123
- ÇÖZÜM: geçici wrapper .bat YAZ (redirect + tırnaklar cmd'nin içine kalır),
124
- wrapper'ı tek tırnaklı /c ile çalıştır. */
125
- let _batSeq = 0;
126
- function runBat(bat, args, opts = {}) {
127
- return new Promise((resolve, reject) => {
128
- const inner = ['"' + bat + '"']
129
- .concat(args.map((a) => (/[\s;&|^<>(),!%]/.test(a) ? '"' + a + '"' : a)))
130
- .join(' ') + (opts.redirect ? ' < "' + opts.redirect + '"' : '');
131
- const wrapper = path.join(os.tmpdir(), 'beast-sdk-' + Date.now() + '-' + (++_batSeq) + '.bat');
132
- fs.writeFileSync(wrapper, '@echo off\r\n' + inner + '\r\nexit /b %ERRORLEVEL%\r\n', 'utf8');
133
- /* wrapper'a tüm tırnak/redirect işi bırakılır — .bat spawn için Node shell:true ister */
134
- const proc = spawn(wrapper, [], { windowsHide: true, shell: true, ...opts.spawnOpts });
135
- let out = '';
136
- proc.stdout.on('data', (c) => { out += String(c); opts.onData && opts.onData(String(c)); });
137
- proc.stderr.on('data', (c) => { out += String(c); });
138
- proc.on('close', (code) => {
139
- try { fs.unlinkSync(wrapper); } catch {}
140
- (code === 0 ? resolve : reject)(code === 0 ? out : new Error(opts.tag + ' exit ' + code + (out ? ' :: ' + out.slice(-300) : '')));
141
- });
142
- proc.on('error', (e) => {
143
- try { fs.unlinkSync(wrapper); } catch {}
144
- reject(e);
145
- });
146
- });
147
- }
148
-
149
- async function runInstall() {
150
- const stage = (pct) => { try { emitInstallProgress('android', { pct }); } catch {} };
151
-
152
- /* 1) JDK 17 (sdkmanager şartı) — java yoksa winget ile */
153
- stage(3);
154
- if (!javaOk()) {
155
- state.stage = 'JDK 17 kuruluyor (winget)';
156
- await new Promise((resolve, reject) => {
157
- const proc = spawn('winget', ['install', '--id', 'Microsoft.OpenJDK.17', '--accept-source-agreements', '--accept-package-agreements', '--silent'], { windowsHide: true, shell: true });
158
- proc.on('close', (code) => (code === 0 || javaOk() ? resolve() : reject(new Error('JDK kurulumu başarısız (winget)'))));
159
- proc.on('error', () => reject(new Error('winget bulunamadı — JDK 17 elle kur')));
160
- });
161
- }
162
- stage(15);
163
-
164
- /* 2) cmdline-tools indir + aç */
165
- const toolsBin = path.join(SDK_DIR, 'cmdline-tools', 'latest', 'bin', 'sdkmanager.bat');
166
- if (!fs.existsSync(toolsBin)) {
167
- state.stage = 'cmdline-tools indiriliyor (~150MB)';
168
- const zipPath = path.join(os.tmpdir(), 'commandlinetools.zip');
169
- const res = await fetch(CMDTOOLS_URL);
170
- if (!res.ok) throw new Error('cmdline-tools indirilemedi: HTTP ' + res.status);
171
- const buf = Buffer.from(await res.arrayBuffer());
172
- fs.mkdirSync(SDK_DIR, { recursive: true });
173
- fs.writeFileSync(zipPath, buf);
174
- stage(35);
175
- execSync(
176
- `powershell -NoProfile -Command "Expand-Archive -LiteralPath '${zipPath}' -DestinationPath '${SDK_DIR}' -Force"`,
177
- { timeout: 300000, windowsHide: true, stdio: 'pipe' }
178
- );
179
- /* zip içi: cmdline-tools/ → cmdline-tools/latest/ olmalı */
180
- const inner = path.join(SDK_DIR, 'cmdline-tools');
181
- if (fs.existsSync(inner) && !fs.existsSync(toolsBin)) {
182
- fs.renameSync(inner, inner + '_tmp');
183
- fs.mkdirSync(path.join(SDK_DIR, 'cmdline-tools'), { recursive: true });
184
- fs.renameSync(inner + '_tmp', path.join(SDK_DIR, 'cmdline-tools', 'latest'));
185
- }
186
- try { fs.unlinkSync(zipPath); } catch {}
187
- }
188
- stage(45);
189
-
190
- /* 3) lisanslar + paketler (platform-tools + emulator + sistem imajı ~1.3GB)
191
- Cevaplar stdin spam'iyle DEĞİL dosya redirect'iyle verilir — sdkmanager
192
- interaktif stdin'i cmd borularında düzgün okumuyor */
193
- state.stage = 'SDK paketleri iniyor (platform-tools · emulator · sistem imajı)';
194
- const sysEnv = { ...process.env };
195
- const yesFile = path.join(os.tmpdir(), 'beast-sdk-yes.txt');
196
- fs.writeFileSync(yesFile, 'y\n'.repeat(80), 'utf8');
197
- await runBat(toolsBin, ['--licenses'], {
198
- tag: 'lisans adımı',
199
- spawnOpts: { env: sysEnv },
200
- redirect: yesFile,
201
- });
202
- stage(50);
203
- {
204
- const pkgs = ['platform-tools', 'emulator', SYS_IMAGE];
205
- /* GERÇEK PROGRESS: sdkmanager çıktısı "[==== ] 17% Downloading x" biçiminde
206
- GLOBAL yüzde basar (monotonik artar) — %50-95 aralığına doğrudan eşle */
207
- let last = 50;
208
- const lastPercent = /(\d{1,3})%[^%]*$/;
209
- /* sdkmanager ağ ortasında patlarsa: yarım .temp'i temizle + 3 kere yeniden
210
- dene (indirilenler korunur — kaldığı yeri hızla atlar) */
211
- for (let attempt = 1; attempt <= 3; attempt++) {
212
- try {
213
- try { fs.rmSync(path.join(SDK_DIR, '.temp'), { recursive: true, force: true }); } catch {}
214
- await runBat(toolsBin, pkgs, {
215
- tag: 'sdkmanager',
216
- spawnOpts: { env: sysEnv },
217
- redirect: yesFile,
218
- onData: (s) => {
219
- const m = lastPercent.exec(s);
220
- if (m) {
221
- const sdkPct = Math.max(0, Math.min(100, Number(m[1])));
222
- const pct = 50 + sdkPct * 0.45; /* %0→50, %100→95 */
223
- if (pct > last + 0.4) { last = pct; stage(pct); }
224
- }
225
- },
226
- });
227
- break;
228
- } catch (e) {
229
- if (attempt < 3 && !findAdb()) continue;
230
- throw e;
231
- }
232
- }
233
- }
234
-
235
- /* 4) AVD yarat */
236
- stage(96);
237
- state.stage = 'AVD (emülatör cihazı) oluşturuluyor';
238
- if (!(await listAvds()).includes(AVD_NAME)) {
239
- const avdm = path.join(SDK_DIR, 'cmdline-tools', 'latest', 'bin', 'avdmanager.bat');
240
- const noFile = path.join(os.tmpdir(), 'beast-sdk-no.txt');
241
- fs.writeFileSync(noFile, 'no\n', 'utf8');
242
- await runBat(avdm, ['create', 'avd', '-n', AVD_NAME, '-k', SYS_IMAGE, '-d', 'pixel'], {
243
- tag: 'avdmanager',
244
- spawnOpts: { env: sysEnv },
245
- redirect: noFile,
246
- });
247
- }
248
- emitInstallProgress('android', { pct: 100 });
249
- state.installing = false;
250
- }
251
-
252
- /* ---------- EMÜLATÖR BAŞLATMA (uzun ömürlü — kendi penceresi açılır) ---------- */
253
- async function startAvd(name) {
254
- const emu = findEmulator();
255
- if (!emu) return { ok: false, error: 'emulator.exe yok — önce Kurulum sekmesinden kur' };
256
- const target = name || (await listAvds())[0];
257
- if (!target) return { ok: false, error: 'AVD yok — önce kur' };
258
- state.booting = true;
259
- const proc = spawn(emu, ['-avd', target, '-no-snapshot'], { windowsHide: false, detached: false });
260
- proc.on('exit', () => { state.booting = false; });
261
- /* boot tamamlanmasını bekleme — arka planda; expo/adb kendisi bekler */
262
- return { ok: true, avd: target, note: 'emülatör başlatıldı — ilk açılış 30-90 sn sürer' };
263
- }
264
-
265
- module.exports = { probe, autoInstall, listAvds, startAvd };