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
package/src/main.js CHANGED
@@ -3474,6 +3474,15 @@ function reloadBackend() {
3474
3474
  /* PANEL RUN köprüsü: ajanın panel_run aracı → ÇALIŞTIR panelindeki yönetilen
3475
3475
  süreç koşucusu (sandbox:run IPC ile aynı makine). */
3476
3476
  engine.sbRunHook = sbRunStartManaged;
3477
+ /* SANDBOX KÖPRÜSÜ: ana sohbetteki ajanın sandbox_repo aracı → panel ile aynı
3478
+ makine (indir/kur/başlat/durdur); kullanıcı chatte repo linki bırakınca
3479
+ ajan kendisi indirip kurabilir. */
3480
+ engine.sbCloneHook = sbCloneRepo;
3481
+ engine.sbFolderHook = sbResolveFolder;
3482
+ engine.sbDetectHook = sbProjectInfoMerged;
3483
+ engine.sbInstallHook = sbInstallRepo;
3484
+ engine.sbStartHook = sbStartRepo;
3485
+ engine.sbStopHook = sbRunStopFolder;
3477
3486
 
3478
3487
  /* SQUEEZE (opsiyonel token sıkıştırma): varsayılan KAPALI; ayar açıksa
3479
3488
  her LLM isteğinin kopyası gönderimden önce sıkıştırılır. */
@@ -3634,7 +3643,7 @@ app.whenReady().then(() => {
3634
3643
  reloadBackend();
3635
3644
  syncWhitelist(); // bot sistemi: whitelist.json aynası ilk açılışta garanti
3636
3645
  try { bots.ensureBotCodes(); } catch {} // her bota benzersiz 5 haneli kod garanti
3637
- try { customtools.seedIfEmpty(); } catch {} // kişisel toollar klasörü boşsa örnek tool kur
3646
+ try { customtools.seedDefaults(); customtools.seedIfEmpty(); } catch {} // varsayılan MT5 toolları + kişisel toollar klasörü boşsa örnek tool kur
3638
3647
  createSplash();
3639
3648
  createWindow();
3640
3649
  log.info('main', 'Beast Agent başlatıldı');
@@ -10814,6 +10823,7 @@ ipcMain.handle('studio:delete', async (_e, rel) => {
10814
10823
  run_command/read/write araçları repo klasöründe çalışır). Ana sohbet VE
10815
10824
  Code/Studio panellerinden tamamen ayrıdır; ana chat solda yerinde kalır. */
10816
10825
  const sbSessions = new Map(); /* klasör → sessionId */
10826
+ let sbLastFolder = ''; /* panelde seçilen / son indirilen repo — ajan araçları boş repo alanında bunu kullanır */
10817
10827
  const SB_DEBOUNCE_MS = 900;
10818
10828
  const sbQueue = new Map(); /* klasör → { timer, msgs[] } */
10819
10829
 
@@ -10935,6 +10945,29 @@ function sbRepoFromInput(input) {
10935
10945
  return null;
10936
10946
  }
10937
10947
 
10948
+ /* repo adres/klasör çözümleyici: hem panel hem ajan araçları (sandbox_repo)
10949
+ kullanır — tam yol, klasör adı, owner/repo ya da boş ("son repo") kabul eder */
10950
+ function sbResolveFolder(input) {
10951
+ const root = sandboxRoot();
10952
+ const s = String(input || '').trim();
10953
+ if (!s) return sbLastFolder;
10954
+ try {
10955
+ if (fs.existsSync(s) && fs.statSync(s).isDirectory()) return path.resolve(s);
10956
+ const direct = path.join(root, s);
10957
+ if (fs.existsSync(direct) && fs.statSync(direct).isDirectory()) return direct;
10958
+ const name = s.toLowerCase().replace(/[\\/]+$/, '').split(/[\\/]/).pop().replace(/\.git$/i, '');
10959
+ let partial = '';
10960
+ for (const e of fs.readdirSync(root, { withFileTypes: true })) {
10961
+ if (!e.isDirectory()) continue;
10962
+ const n = e.name.toLowerCase();
10963
+ if (n === name) return path.join(root, e.name);
10964
+ if (!partial && n.startsWith(name + '-')) partial = path.join(root, e.name);
10965
+ }
10966
+ if (partial) return partial;
10967
+ } catch {}
10968
+ return sbLastFolder;
10969
+ }
10970
+
10938
10971
  ipcMain.handle('sandbox:list', async () => {
10939
10972
  const root = sandboxRoot();
10940
10973
  const items = [];
@@ -10950,15 +10983,18 @@ ipcMain.handle('sandbox:list', async () => {
10950
10983
  sid = String(known);
10951
10984
  try { busy = engine.isBusy(known); } catch {}
10952
10985
  }
10953
- items.push({ name: e.name, folder, sid, busy });
10986
+ const slot = sbProcs.get(folder);
10987
+ items.push({ name: e.name, folder, sid, busy, running: !!(slot && slot.proc), kind: slot ? slot.kind : '' });
10954
10988
  }
10955
10989
  } catch {}
10956
10990
  return { ok: true, root, items };
10957
10991
  });
10958
10992
 
10959
- ipcMain.handle('sandbox:clone', async (_e, input) => {
10993
+ /* klonlama çekirdeği: hem Sandbox paneli IPC'si hem de ajanın sandbox_repo
10994
+ aracı buradan geçer (engine.sbCloneHook) */
10995
+ function sbCloneRepo(input) {
10960
10996
  const repo = sbRepoFromInput(input);
10961
- if (!repo) return { ok: false, error: 'github.com/owner/repo, owner/repo ya da tam .git adresi gir' };
10997
+ if (!repo) return Promise.resolve({ ok: false, error: 'github.com/owner/repo, owner/repo ya da tam .git adresi gir' });
10962
10998
  const root = sandboxRoot();
10963
10999
  const base = String(repo.name || 'repo').replace(/[\\/:*?"<>|]/g, '_').slice(0, 60) || 'repo';
10964
11000
  let dest = path.join(root, base);
@@ -11019,6 +11055,7 @@ ipcMain.handle('sandbox:clone', async (_e, input) => {
11019
11055
  return;
11020
11056
  }
11021
11057
  log.info('main', 'sandbox: klonlandı ' + repo.url + ' → ' + finalName);
11058
+ sbLastFolder = dest;
11022
11059
  resolve({ ok: true, name: finalName, folder: dest });
11023
11060
  };
11024
11061
  proc.on('error', done);
@@ -11031,7 +11068,9 @@ ipcMain.handle('sandbox:clone', async (_e, input) => {
11031
11068
  }
11032
11069
  }, 600000);
11033
11070
  });
11034
- });
11071
+ }
11072
+
11073
+ ipcMain.handle('sandbox:clone', async (_e, input) => sbCloneRepo(input));
11035
11074
 
11036
11075
  ipcMain.handle('sandbox:send', async (_e, payload) => {
11037
11076
  const text = String((payload && payload.msg) || '').trim();
@@ -11097,6 +11136,8 @@ ipcMain.handle('sandbox:remove', async (_e, payload) => {
11097
11136
  sbLoadMap();
11098
11137
  const sid = sbSessions.get(folder);
11099
11138
  if (sid && engine.isBusy(sid)) return { ok: false, error: 'repo çalışıyor — önce ■ ile durdur' };
11139
+ const slot = sbProcs.get(folder);
11140
+ if (slot && slot.proc) return { ok: false, error: 'repoda süreç çalışıyor — önce ■ Durdur' };
11100
11141
  try { fs.rmSync(folder, { recursive: true, force: true }); } catch (e) {
11101
11142
  return { ok: false, error: String((e && e.message) || e) };
11102
11143
  }
@@ -11105,6 +11146,12 @@ ipcMain.handle('sandbox:remove', async (_e, payload) => {
11105
11146
  sbSessions.delete(folder);
11106
11147
  sbSaveMap();
11107
11148
  }
11149
+ sbProcs.delete(folder);
11150
+ sbLoadCfg();
11151
+ if (sbCfg[folder]) {
11152
+ delete sbCfg[folder];
11153
+ sbSaveCfg();
11154
+ }
11108
11155
  return { ok: true };
11109
11156
  });
11110
11157
 
@@ -11121,6 +11168,7 @@ ipcMain.handle('sandbox:open', async (_e, payload) => {
11121
11168
  const folder = String((payload && payload.folder) || '');
11122
11169
  if (!folder.startsWith(sandboxRoot() + path.sep)) return { ok: false, error: 'geçersiz klasör' };
11123
11170
  if (!engine) return { ok: false, error: 'ajan hazır değil' };
11171
+ sbLastFolder = folder;
11124
11172
  const s = sandboxGetSession(folder);
11125
11173
  s.workspace = folder;
11126
11174
  engine.cache.set(s.id, s);
@@ -11162,25 +11210,154 @@ ipcMain.handle('sandbox:tree', (_e, payload) => {
11162
11210
  }
11163
11211
  });
11164
11212
 
11165
- /* Sandbox ÇALIŞTIRICI: repo klasöründe uzun süreli komut (npm start/dev vb.)
11166
- YÖNETİLEN SÜREÇ — çıktı satır satır panele akar; ■ ile ağaç-kullanı durdurulur.
11167
- Dev server adresi çıktıdan yakalanır → 'Tarayıcıda aç' aktifleşir.
11168
- Aynı fonksiyon engine.sbRunHook olarak da bağlanır — ajanın panel_run
11169
- aracı turu KİLİTLEMEDEN süreç başlatır (Kur/Başlat butonları bunu kullanır). */
11170
- const sbRunProc = { proc: null, folder: '', url: '' };
11213
+ /* Sandbox ÇALIŞTIRICI v2: repo başına YÖNETİLEN SÜREÇLER.
11214
+ Kur/Başlat/Durdur butonları artık deterministik: proje tipi algılanır
11215
+ (package.json, requirements.txt, pyproject.toml, Cargo.toml, go.mod,
11216
+ index.html) ve doğru komut DOĞRUDAN çalıştırılır. Her repo kendi süreç
11217
+ yuvasında koşar — birden çok repo aynı anda çalışabilir; çıktı tamponlanır,
11218
+ repo değiştirilince panel geçmişi yeniden basılır. Dev server adresi
11219
+ çıktıdan yakalanır → sağdaki dahili tarayıcıda açılır. Aynı fonksiyon
11220
+ engine.sbRunHook'tur: ajanın panel_run aracı da buradan geçer. */
11221
+
11222
+ const sbProcs = new Map(); /* klasör → { proc, kind, cmd, url, lines[], startedAt } */
11223
+ const SB_LOG_CAP = 500; /* repo başına tamponlanan çıktı satırı */
11224
+ const SB_CFG_FILE = 'sandbox-config.json';
11171
11225
 
11172
- function sbRunEmit(data) {
11226
+ function sbCfgPath() {
11227
+ return path.join(beastDir(), SB_CFG_FILE);
11228
+ }
11229
+
11230
+ let sbCfgLoaded = false;
11231
+ let sbCfg = {}; /* klasör → { install, run, dev } — kullanıcının kaydettiği özel komutlar */
11232
+
11233
+ function sbLoadCfg() {
11234
+ if (sbCfgLoaded) return sbCfg;
11235
+ sbCfgLoaded = true;
11173
11236
  try {
11174
- if (win && !win.isDestroyed()) win.webContents.send('agent:event', { type: 'sb-run', data: String(data || '') });
11237
+ const raw = JSON.parse(fs.readFileSync(sbCfgPath(), 'utf8'));
11238
+ if (raw && typeof raw === 'object') sbCfg = raw;
11175
11239
  } catch {}
11240
+ return sbCfg;
11241
+ }
11242
+
11243
+ function sbSaveCfg() {
11244
+ try { fs.writeFileSync(sbCfgPath(), JSON.stringify(sbCfg, null, 2)); } catch {}
11245
+ }
11246
+
11247
+ /* embedded python varsa onu kullan — PATH'te python olmasa bile çalışsın */
11248
+ function sbPythonCmd() {
11249
+ const cands = [process.env.BEAST_PYTHON, path.join(beastDir(), 'py', 'python.exe')];
11250
+ for (const c of cands) {
11251
+ try { if (c && fs.existsSync(c)) return '"' + c + '"'; } catch {}
11252
+ }
11253
+ return 'python';
11254
+ }
11255
+
11256
+ /* repo köküne bakıp proje tipini + kur/çalıştır komutlarını çıkarır */
11257
+ function sbProjectInfo(folder) {
11258
+ const out = { kind: 'unknown', label: 'bilinmiyor', name: path.basename(folder), install: '', run: '', dev: '', build: '' };
11259
+ try {
11260
+ const pkgPath = path.join(folder, 'package.json');
11261
+ if (fs.existsSync(pkgPath)) {
11262
+ let pkg = {};
11263
+ try { pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8')); } catch {}
11264
+ out.name = String(pkg.name || out.name);
11265
+ let pm = 'npm';
11266
+ if (fs.existsSync(path.join(folder, 'pnpm-lock.yaml'))) pm = 'pnpm';
11267
+ else if (fs.existsSync(path.join(folder, 'yarn.lock'))) pm = 'yarn';
11268
+ else if (fs.existsSync(path.join(folder, 'bun.lockb')) || fs.existsSync(path.join(folder, 'bun.lock'))) pm = 'bun';
11269
+ const s = pkg.scripts && typeof pkg.scripts === 'object' ? pkg.scripts : {};
11270
+ out.kind = 'node';
11271
+ out.label = pm;
11272
+ out.install = pm + ' install';
11273
+ out.dev = s.dev ? pm + ' run dev' : '';
11274
+ out.run = s.start ? pm + ' run start' : s.dev ? pm + ' run dev' : s.serve ? pm + ' run serve' : '';
11275
+ out.build = s.build ? pm + ' run build' : '';
11276
+ return out;
11277
+ }
11278
+ const hasPy = ['pyproject.toml', 'requirements.txt', 'setup.py'].some((f) => fs.existsSync(path.join(folder, f)));
11279
+ if (hasPy) {
11280
+ const py = sbPythonCmd();
11281
+ out.kind = 'python';
11282
+ out.label = 'python';
11283
+ if (fs.existsSync(path.join(folder, 'uv.lock'))) out.install = 'uv sync';
11284
+ else if (fs.existsSync(path.join(folder, 'requirements.txt'))) out.install = py + ' -m pip install -r requirements.txt';
11285
+ else out.install = py + ' -m pip install -e .';
11286
+ if (fs.existsSync(path.join(folder, 'manage.py'))) out.run = py + ' manage.py runserver';
11287
+ else if (fs.existsSync(path.join(folder, 'main.py'))) out.run = py + ' main.py';
11288
+ else if (fs.existsSync(path.join(folder, 'app.py'))) out.run = py + ' app.py';
11289
+ else if (fs.existsSync(path.join(folder, 'run.py'))) out.run = py + ' run.py';
11290
+ else if (fs.existsSync(path.join(folder, 'bot.py'))) out.run = py + ' bot.py';
11291
+ out.dev = out.run;
11292
+ return out;
11293
+ }
11294
+ if (fs.existsSync(path.join(folder, 'Cargo.toml'))) {
11295
+ out.kind = 'rust';
11296
+ out.label = 'cargo';
11297
+ out.install = 'cargo fetch';
11298
+ out.run = 'cargo run';
11299
+ out.dev = 'cargo run';
11300
+ return out;
11301
+ }
11302
+ if (fs.existsSync(path.join(folder, 'go.mod'))) {
11303
+ out.kind = 'go';
11304
+ out.label = 'go';
11305
+ out.install = 'go mod download';
11306
+ out.run = 'go run .';
11307
+ out.dev = 'go run .';
11308
+ return out;
11309
+ }
11310
+ if (fs.existsSync(path.join(folder, 'index.html'))) {
11311
+ out.kind = 'static';
11312
+ out.label = 'statik';
11313
+ out.run = sbPythonCmd() + ' -m http.server 8123 --bind 127.0.0.1';
11314
+ out.dev = out.run;
11315
+ return out;
11316
+ }
11317
+ } catch {}
11318
+ return out;
11176
11319
  }
11177
11320
 
11178
- function sbRunStartManaged(folder, cmd) {
11321
+ /* algılanan komutlar + kullanıcının kaydettiği özel komutlar birleşir */
11322
+ function sbProjectInfoMerged(folder) {
11323
+ const info = sbProjectInfo(folder);
11324
+ const c = sbLoadCfg()[folder];
11325
+ if (c && typeof c === 'object') {
11326
+ if (c.install) info.install = String(c.install);
11327
+ if (c.run) info.run = String(c.run);
11328
+ if (c.dev) info.dev = String(c.dev);
11329
+ if (c.install || c.run || c.dev) info.custom = true;
11330
+ }
11331
+ return info;
11332
+ }
11333
+
11334
+ function sbRunEmit(folder, data, kind) {
11335
+ try {
11336
+ if (win && !win.isDestroyed()) {
11337
+ win.webContents.send('agent:event', {
11338
+ type: 'sb-run',
11339
+ folder: String(folder || ''),
11340
+ kind: kind || 'run',
11341
+ data: String(data || ''),
11342
+ });
11343
+ }
11344
+ } catch {}
11345
+ }
11346
+
11347
+ function sbSlotPush(slot, folder, text) {
11348
+ slot.lines.push(String(text || ''));
11349
+ if (slot.lines.length > SB_LOG_CAP) slot.lines.splice(0, slot.lines.length - SB_LOG_CAP);
11350
+ sbRunEmit(folder, text, slot.kind);
11351
+ }
11352
+
11353
+ function sbRunStartManaged(folder, cmd, kind) {
11179
11354
  folder = String(folder || '');
11180
11355
  cmd = String(cmd || '').trim();
11356
+ kind = kind === 'install' ? 'install' : 'run';
11181
11357
  if (!folder.startsWith(sandboxRoot() + path.sep)) return { ok: false, error: 'geçersiz klasör' };
11182
11358
  if (!cmd) return { ok: false, error: 'komut boş' };
11183
- if (sbRunProc.proc) return { ok: false, error: 'ÇALIŞTIR panelinde zaten bir süreç çalışıyor — önce ■ ile durdur' };
11359
+ const cur = sbProcs.get(folder);
11360
+ if (cur && cur.proc) return { ok: false, error: 'Bu repoda zaten bir süreç çalışıyor — önce ■ Durdur' };
11184
11361
  const { spawn } = require('child_process');
11185
11362
  try {
11186
11363
  const proc = spawn('cmd.exe', ['/d', '/s', '/c', cmd], {
@@ -11188,19 +11365,19 @@ function sbRunStartManaged(folder, cmd) {
11188
11365
  windowsHide: true,
11189
11366
  env: { ...process.env, FORCE_COLOR: '0', NO_COLOR: '1' },
11190
11367
  });
11191
- sbRunProc.proc = proc;
11192
- sbRunProc.folder = folder;
11193
- sbRunProc.url = '';
11368
+ const slot = { proc, kind, cmd, url: '', lines: [], startedAt: Date.now() };
11369
+ sbProcs.set(folder, slot);
11194
11370
  const onLine = (buf) => {
11195
11371
  for (const piece of String(buf).split(/\r?\n/)) {
11196
11372
  if (!piece.trim()) continue;
11197
- sbRunEmit(piece);
11373
+ sbSlotPush(slot, folder, piece);
11374
+ if (kind !== 'run') continue;
11198
11375
  /* dev server adresi yakala (sadece localhost) */
11199
11376
  const m = /https?:\/\/(?:localhost|127\.0\.0\.1)(?::\d{2,5})?[^\s'"<>]*/i.exec(piece);
11200
- if (m && !sbRunProc.url) {
11201
- sbRunProc.url = m[0].replace(/[)\].,;:'"]+$/, '');
11377
+ if (m && !slot.url) {
11378
+ slot.url = m[0].replace(/[)\].,;:'"]+$/, '');
11202
11379
  try {
11203
- if (win && !win.isDestroyed()) win.webContents.send('agent:event', { type: 'sb-run-url', url: sbRunProc.url });
11380
+ if (win && !win.isDestroyed()) win.webContents.send('agent:event', { type: 'sb-run-url', folder, url: slot.url });
11204
11381
  } catch {}
11205
11382
  }
11206
11383
  }
@@ -11208,40 +11385,131 @@ function sbRunStartManaged(folder, cmd) {
11208
11385
  proc.stdout.on('data', onLine);
11209
11386
  proc.stderr.on('data', onLine);
11210
11387
  proc.on('close', (code) => {
11211
- sbRunEmit('[süreç bitti' + (code != null ? ' — kod ' + code : '') + ']');
11212
- sbRunProc.proc = null;
11388
+ sbSlotPush(slot, folder, '[süreç bitti' + (code != null ? ' — kod ' + code : '') + ']');
11389
+ slot.proc = null;
11213
11390
  try {
11214
- if (win && !win.isDestroyed()) win.webContents.send('agent:event', { type: 'sb-run-end' });
11391
+ if (win && !win.isDestroyed()) {
11392
+ win.webContents.send('agent:event', { type: 'sb-run-end', folder, code: code == null ? null : Number(code) });
11393
+ }
11215
11394
  } catch {}
11216
11395
  });
11217
- sbRunEmit('▶ ' + cmd + ' (klasör: ' + path.basename(folder) + ')');
11396
+ sbSlotPush(slot, folder, '▶ ' + cmd + ' (klasör: ' + path.basename(folder) + ')');
11218
11397
  return {
11219
11398
  ok: true,
11399
+ cmd,
11400
+ kind,
11220
11401
  note: 'süreç ÇALIŞTIR panelinde başlatıldı — çıktı orada canlı akar; durdurmak için paneldeki ■. Kullanıcıya adres varsa bildir.',
11221
11402
  };
11222
11403
  } catch (e) {
11223
- sbRunProc.proc = null;
11224
11404
  return { ok: false, error: String((e && e.message) || e) };
11225
11405
  }
11226
11406
  }
11227
11407
 
11408
+ function sbRunStopFolder(folder) {
11409
+ const slot = sbProcs.get(String(folder || ''));
11410
+ if (!slot || !slot.proc) return false;
11411
+ const p = slot.proc;
11412
+ slot.proc = null;
11413
+ try {
11414
+ /* cmd.exe /c zinciriyle çocuklar (node/vite) geride kalmasın — ağaç kesimi */
11415
+ if (process.platform === 'win32') {
11416
+ const { exec } = require('child_process');
11417
+ exec('taskkill /pid ' + p.pid + '/T /F', { windowsHide: true });
11418
+ } else p.kill('SIGTERM');
11419
+ } catch {}
11420
+ return true;
11421
+ }
11422
+
11228
11423
  ipcMain.handle('sandbox:run', async (_e, payload) => {
11229
11424
  const folder = String((payload && payload.folder) || '');
11230
11425
  const cmd = String((payload && payload.cmd) || '').trim();
11231
- return sbRunStartManaged(folder, cmd);
11426
+ return sbRunStartManaged(folder, cmd, (payload && payload.kind) || 'run');
11232
11427
  });
11233
11428
 
11234
- ipcMain.handle('sandbox:runstop', async () => {
11235
- const p = sbRunProc.proc;
11236
- if (!p) return { ok: true, wasRunning: false };
11237
- sbRunProc.proc = null;
11238
- try {
11239
- /* cmd.exe /c zinciriyle çocuklar (node/vite) geride kalmasın — ağaç kesimi */
11240
- const { exec } = require('child_process');
11241
- if (process.platform === 'win32') exec('taskkill /pid ' + p.pid + ' /T /F', { windowsHide: true });
11242
- else p.kill('SIGTERM');
11243
- } catch {}
11244
- return { ok: true, wasRunning: true };
11429
+ /* proje tipi + önerilen komutlar (kullanıcı özel komutlarıyla birleşik) */
11430
+ ipcMain.handle('sandbox:detect', async (_e, payload) => {
11431
+ const folder = String((payload && payload.folder) || '');
11432
+ if (!folder.startsWith(sandboxRoot() + path.sep)) return { ok: false, error: 'geçersiz klasör' };
11433
+ return { ok: true, info: sbProjectInfoMerged(folder) };
11434
+ });
11435
+
11436
+ /* Kur: algılanan/kayıtlı kurulum komutunu doğrudan çalıştırır (panel + ajan ortak) */
11437
+ function sbInstallRepo(folder) {
11438
+ folder = String(folder || '');
11439
+ if (!folder.startsWith(sandboxRoot() + path.sep)) return { ok: false, error: 'geçersiz klasör' };
11440
+ const info = sbProjectInfoMerged(folder);
11441
+ if (!info.install) return { ok: false, error: 'Bu repoda kurulum komutu algılanamadı — özel komutu kutuya yazıp ▶ ile çalıştır' };
11442
+ const r = sbRunStartManaged(folder, info.install, 'install');
11443
+ if (r.ok) r.cmd = info.install;
11444
+ return r;
11445
+ }
11446
+
11447
+ ipcMain.handle('sandbox:install', async (_e, payload) => sbInstallRepo(String((payload && payload.folder) || '')));
11448
+
11449
+ /* Başlat/Dev: algılanan/kayıtlı çalıştırma komutunu başlatır (panel + ajan ortak) */
11450
+ function sbStartRepo(folder, mode) {
11451
+ folder = String(folder || '');
11452
+ mode = String(mode || 'run') === 'dev' ? 'dev' : 'run';
11453
+ if (!folder.startsWith(sandboxRoot() + path.sep)) return { ok: false, error: 'geçersiz klasör' };
11454
+ const info = sbProjectInfoMerged(folder);
11455
+ const cmd = mode === 'dev' ? info.dev || info.run : info.run || info.dev;
11456
+ if (!cmd) return { ok: false, error: 'Bu repo için çalıştırma komutu algılanamadı — özel komutu kutuya yazıp ▶ ile çalıştır' };
11457
+ const r = sbRunStartManaged(folder, cmd, 'run');
11458
+ if (r.ok) r.cmd = cmd;
11459
+ return r;
11460
+ }
11461
+
11462
+ ipcMain.handle('sandbox:start', async (_e, payload) => {
11463
+ const folder = String((payload && payload.folder) || '');
11464
+ return sbStartRepo(folder, String((payload && payload.mode) || 'run'));
11465
+ });
11466
+
11467
+ /* seçili repo paneli için: çalışıyor mu + tamponlanmış çıktı */
11468
+ ipcMain.handle('sandbox:procstate', async (_e, payload) => {
11469
+ const folder = String((payload && payload.folder) || '');
11470
+ if (!folder.startsWith(sandboxRoot() + path.sep)) return { ok: false, error: 'geçersiz klasör' };
11471
+ const slot = sbProcs.get(folder);
11472
+ return {
11473
+ ok: true,
11474
+ running: !!(slot && slot.proc),
11475
+ kind: slot ? slot.kind : '',
11476
+ cmd: slot ? slot.cmd : '',
11477
+ url: slot ? slot.url : '',
11478
+ lines: slot ? slot.lines.slice() : [],
11479
+ };
11480
+ });
11481
+
11482
+ /* özel komutları kalıcı kaydet (repo klasörü → install/run/dev) */
11483
+ ipcMain.handle('sandbox:cfg:set', async (_e, payload) => {
11484
+ const folder = String((payload && payload.folder) || '');
11485
+ if (!folder.startsWith(sandboxRoot() + path.sep)) return { ok: false, error: 'geçersiz klasör' };
11486
+ sbLoadCfg();
11487
+ const cur = sbCfg[folder] && typeof sbCfg[folder] === 'object' ? sbCfg[folder] : {};
11488
+ const next = { ...cur };
11489
+ for (const k of ['install', 'run', 'dev']) {
11490
+ if (payload && payload[k] !== undefined) {
11491
+ const v = String(payload[k] || '').trim().slice(0, 500);
11492
+ if (v) next[k] = v;
11493
+ else delete next[k];
11494
+ }
11495
+ }
11496
+ if (Object.keys(next).length) sbCfg[folder] = next;
11497
+ else delete sbCfg[folder];
11498
+ sbSaveCfg();
11499
+ return { ok: true, cfg: sbCfg[folder] || {} };
11500
+ });
11501
+
11502
+ ipcMain.handle('sandbox:runstop', async (_e, payload) => {
11503
+ const folder = String((payload && payload.folder) || '');
11504
+ if (folder) {
11505
+ if (!folder.startsWith(sandboxRoot() + path.sep)) return { ok: false, error: 'geçersiz klasör' };
11506
+ return { ok: true, wasRunning: sbRunStopFolder(folder) };
11507
+ }
11508
+ let any = false;
11509
+ for (const f of Array.from(sbProcs.keys())) {
11510
+ if (sbRunStopFolder(f)) any = true;
11511
+ }
11512
+ return { ok: true, wasRunning: any };
11245
11513
  });
11246
11514
 
11247
11515
  ipcMain.handle('sandbox:openurl', async (_e, url) => {
package/src/preload.js CHANGED
@@ -185,7 +185,12 @@ contextBridge.exposeInMainWorld('beast', {
185
185
  sandboxOpen: (payload) => ipcRenderer.invoke('sandbox:open', payload),
186
186
  sandboxTree: (payload) => ipcRenderer.invoke('sandbox:tree', payload),
187
187
  sandboxRun: (payload) => ipcRenderer.invoke('sandbox:run', payload),
188
- sandboxRunStop: () => ipcRenderer.invoke('sandbox:runstop'),
188
+ sandboxRunStop: (payload) => ipcRenderer.invoke('sandbox:runstop', payload),
189
+ sandboxDetect: (payload) => ipcRenderer.invoke('sandbox:detect', payload),
190
+ sandboxInstall: (payload) => ipcRenderer.invoke('sandbox:install', payload),
191
+ sandboxStart: (payload) => ipcRenderer.invoke('sandbox:start', payload),
192
+ sandboxProcState: (payload) => ipcRenderer.invoke('sandbox:procstate', payload),
193
+ sandboxCfgSet: (payload) => ipcRenderer.invoke('sandbox:cfg:set', payload),
189
194
  sandboxOpenUrl: (url) => ipcRenderer.invoke('sandbox:openurl', url),
190
195
  waStart: () => ipcRenderer.invoke('wa:start'),
191
196
  waStop: () => ipcRenderer.invoke('wa:stop'),
@@ -426,21 +426,23 @@
426
426
  <section id="sbRun">
427
427
  <div id="sbRunHead">
428
428
  <span id="sbRunTitle">ÇALIŞTIR</span>
429
+ <span id="sbRunKind"></span>
429
430
  <span id="sbRunCwd" title="">—</span>
430
431
  </div>
431
432
  <div id="sbRunBtns">
432
- <button id="sbRunInstall" title="Ajana talimat: projeyi kur — doğru komutu ajan bulur"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:3px"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>Kur</button>
433
- <button id="sbRunStart" title="Ajana talimat: projeyi çalıştır — uzun süreli süreç ÇALIŞTIR panelinde başlar"><svg viewBox="0 0 24 24" width="12" height="12" fill="currentColor" stroke="none" style="vertical-align:-2px;margin-right:3px"><polygon points="6 3 20 12 6 21 6 3"/></svg>Başlat</button>
434
- <button id="sbRunDev" title="Ajana talimat: geliştirme modunda başlat (dev)"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:3px"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>Dev</button>
433
+ <button id="sbRunInstall" title="Kur — algılanan paket yöneticisiyle bağımlılıkları yükle (npm/pip/cargo/go)"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:3px"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>Kur</button>
434
+ <button id="sbRunStart" title="Başlat — algılanan komutla çalıştır (sunucu adresi sağdaki tarayıcıda açılır)"><svg viewBox="0 0 24 24" width="12" height="12" fill="currentColor" stroke="none" style="vertical-align:-2px;margin-right:3px"><polygon points="6 3 20 12 6 21 6 3"/></svg>Başlat</button>
435
+ <button id="sbRunDev" title="Geliştirme modunda başlat (npm run dev, vite, expo…)"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:3px"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>Dev</button>
435
436
  <button id="sbRunStop" title="Süreci durdur" hidden><svg viewBox="0 0 24 24" width="12" height="12" fill="currentColor" stroke="none" style="vertical-align:-2px;margin-right:3px"><rect x="6" y="6" width="12" height="12" rx="2"/></svg>Durdur</button>
436
437
  </div>
437
438
  <div id="sbRunCustom">
438
439
  <input id="sbRunCmd" placeholder="özel komut: python main.py, cargo run, node index.js…" spellcheck="false" autocomplete="off" />
439
440
  <button id="sbRunGo" title="Çalıştır"><svg viewBox="0 0 24 24" width="12" height="12" fill="currentColor" stroke="none"><polygon points="6 3 20 12 6 21 6 3"/></svg></button>
441
+ <button id="sbRunSave" title="Yazılan komutu bu reponun başlatma komutu olarak kaydet"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg></button>
440
442
  </div>
441
443
  <div id="sbRunOut"></div>
442
444
  <div id="sbRunFoot">
443
- <button id="sbRunOpen" title="Ajana talimat: çalışma adresini bulup tarayıcıda açar" disabled><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:3px"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg><span id="sbRunOpenLabel">Tarayıcıda aç</span></button>
445
+ <button id="sbRunOpen" title="Önizlemeyi sağdaki dahili tarayıcıda aç" disabled><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:3px"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg><span id="sbRunOpenLabel">Tarayıcıda aç</span></button>
444
446
  <span id="sbRunUrl" title=""></span>
445
447
  </div>
446
448
  </section>