beast-agent 2.41.0 → 2.43.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/assets/app.ico CHANGED
Binary file
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "beast-agent",
3
3
  "productName": "Beast Agent",
4
- "version": "2.41.0",
4
+ "version": "2.43.0",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -5030,7 +5030,7 @@ const skills = require('./skills');
5030
5030
  folder: r.folder,
5031
5031
  detected: info ? { kind: info.kind, label: info.label, install: info.install, run: info.run } : undefined,
5032
5032
  note:
5033
- 'Repo "' + r.name + '" → ' + r.folder + ' klasörüne indirildi. Kurmak için sandbox_repo action=kur, çalıştırmak için action=baslat; kullanıcı Sandbox panelindeki Kur/Başlat butonlarını da kullanabilir.',
5033
+ 'Repo "' + r.name + '" → ' + r.folder + ' klasörüne indirildi. Kurmak için sandbox_repo action=kur, çalıştırmak için action=baslat (bağımlılıklar eksikse önce kurar); kullanıcı Sandbox panelindeki Başlat/Dev butonlarını da kullanabilir.',
5034
5034
  };
5035
5035
  }
5036
5036
  if (action !== 'kur' && action !== 'baslat' && action !== 'durdur') {
@@ -131,9 +131,9 @@ function build(opts = {}) {
131
131
  ),
132
132
  prompt: [
133
133
  'BEAST SANDBOX MODUNDASIN: GitHub\u2019dan indirilmi\u015f A\u00c7IK KAYNAK bir repo klas\u00f6r\u00fcndesin (Beast-Sandbox). Bu kodun sahibi de\u011filsin \u2014 haz\u0131r bir d\u0131\u015f proje; repo sahibinin konvansiyonlar\u0131na uy, gereksiz yeniden bi\u00e7imlendirme yapma.',
134
- 'PANEL: \u00c7ALI\u015eTIR b\u00f6l\u00fcm\u00fcndeki Kur/Ba\u015flat/Dev butonlar\u0131 proje tipini OTOMAT\u0130K alg\u0131lar (package.json scripts + npm/pnpm/yarn/bun kilidi; requirements.txt/pyproject + uv; Cargo.toml; go.mod; statik index.html) ve do\u011fru komutu do\u011frudan \u00e7al\u0131\u015ft\u0131r\u0131r. \u00d6zel komut kutusu ad-hoc komut \u00e7al\u0131\u015ft\u0131r\u0131r; kaydet butonu o komutu repo i\u00e7in kal\u0131c\u0131 ba\u015flatma komutu yapar. Kullan\u0131c\u0131 butonla ba\u015flatt\u0131ysa \u00e7\u0131kt\u0131 panelde canl\u0131 akar, localhost adresi yakalan\u0131nca sa\u011fdaki dahili taray\u0131c\u0131da otomatik a\u00e7\u0131l\u0131r.',
135
- '\u0130\u015fe README + paket bildirim dosyalar\u0131yla ba\u015fla (list/glob); kullan\u0131c\u0131 Kur demediyse ba\u011f\u0131ml\u0131l\u0131klar\u0131 sen kurabilirsin (npm install, pip install -r requirements.txt vb.). Kur/Ba\u015flat ba\u015far\u0131s\u0131z olursa ger\u00e7ek hatay\u0131 g\u00f6rmek i\u00e7in ayn\u0131 komutu bash ile \u00e7al\u0131\u015ft\u0131r, nedenini bul, gerekiyorsa d\u00fczelt ve kullan\u0131c\u0131ya do\u011fru \u00f6zel komutu s\u00f6yle (kutuya yaz\u0131p kaydetsin).',
136
- 'UZUN S\u00dcREL\u0130 sunucu/s\u00fcre\u00e7 (npm start, npm run dev, python app.py vb.) bash ile ASLA ba\u015flatma \u2014 tur kilitlenir; kullan\u0131c\u0131 senden isterse panel_run ile ba\u015flat (\u00e7\u0131kt\u0131 panelde akar, adres sa\u011fda a\u00e7\u0131l\u0131r). Repo ba\u015f\u0131na TEK s\u00fcre\u00e7: kullan\u0131c\u0131 zaten ba\u015flatt\u0131ysa panel_run hata verir \u2014 kullan\u0131c\u0131ya \u00f6nce Durdur demesini s\u00f6yle.',
134
+ 'PANEL: \u00c7ALI\u015eTIR b\u00f6l\u00fcm\u00fcndeki Ba\u015flat/Dev butonlar\u0131 proje tipini OTOMAT\u0130K alg\u0131lar (package.json scripts + npm/pnpm/yarn/bun kilidi; requirements.txt/pyproject + uv; hugo/jekyll/mkdocs; Cargo.toml; go.mod; statik index.html) ve gerekirse \u00d6NCE ba\u011f\u0131ml\u0131l\u0131klar\u0131 kurup sonra do\u011fru komutu \u00e7al\u0131\u015ft\u0131r\u0131r. Alg\u0131lama komut bulamazsa ya da komut 10 sn i\u00e7inde hata verirse sistem sana OTOMAT\u0130K mesaj d\u00fc\u015f\u00fcr\u00fcr: repo yap\u0131s\u0131na g\u00f6re (README, betikler, site ayarlar\u0131, ana dosyalar) DO\u011eRU komutu bul ve panel_run ile ba\u015flat. \u00d6zel komut kutusu ad-hoc komut \u00e7al\u0131\u015ft\u0131r\u0131r; kaydet butonu o komutu repo i\u00e7in kal\u0131c\u0131 ba\u015flatma komutu yapar. Kullan\u0131c\u0131 butonla ba\u015flatt\u0131ysa \u00e7\u0131kt\u0131 panelde canl\u0131 akar, localhost adresi yakalan\u0131nca \u00c7ALI\u015eTIR panelinde CANLI \u00d6N\u0130ZLEME a\u00e7\u0131l\u0131r (kullan\u0131c\u0131 G\u00f6rsel/\u00c7\u0131kt\u0131 d\u00fc\u011fmesiyle ge\u00e7i\u015f yapar).',
135
+ '\u0130\u015fe README + paket bildirim dosyalar\u0131yla ba\u015fla (list/glob); ba\u011f\u0131ml\u0131l\u0131klar kurulu de\u011filse kurulumu sen yapabilirsin (npm install, pip install -r requirements.txt vb.) \u2014 Ba\u015flat/Dev butonlar\u0131 da gerekirse \u00f6nce kurar. Komut ba\u015far\u0131s\u0131z olursa ger\u00e7ek hatay\u0131 g\u00f6rmek i\u00e7in ayn\u0131 komutu bash ile \u00e7al\u0131\u015ft\u0131r, nedenini bul, gerekiyorsa d\u00fczelt ve kullan\u0131c\u0131ya do\u011fru \u00f6zel komutu s\u00f6yle (kutuya yaz\u0131p kaydetsin).',
136
+ 'UZUN S\u00dcREL\u0130 sunucu/s\u00fcre\u00e7 (npm start, npm run dev, hugo server, python app.py vb.) bash ile ASLA ba\u015flatma \u2014 tur kilitlenir; kullan\u0131c\u0131 senden isterse panel_run ile ba\u015flat (\u00e7\u0131kt\u0131 panelde akar, adres \u00c7ALI\u015eTIR panelinde canl\u0131 \u00f6nizlemede a\u00e7\u0131l\u0131r). Repo ba\u015f\u0131na TEK s\u00fcre\u00e7: kullan\u0131c\u0131 zaten ba\u015flatt\u0131ysa panel_run hata verir \u2014 kullan\u0131c\u0131ya \u00f6nce Durdur demesini s\u00f6yle.',
137
137
  ].join('\n'),
138
138
  mode: 'all',
139
139
  native: false,
@@ -1618,7 +1618,7 @@ const definitions = [
1618
1618
  function: {
1619
1619
  name: 'sandbox_repo',
1620
1620
  description:
1621
- 'Beast Sandbox yönetimi — kullanıcı GitHub repo adresi/linki verdiğinde ya da indirilmiş bir repoyu kurmak/çalıştırmak istediğinde kullan. action=indir: repoyu ~/Beast-Sandbox klasörüne klonlar (owner/repo ya da tam URL); action=kur: algılanan kurulum komutunu (npm install, pip install -r requirements.txt, uv sync, cargo fetch, go mod download) ÇALIŞTIR panelinde başlatır; action=baslat: algılanan çalıştırma komutunu başlatır (npm run dev, python app.py vb.) — localhost adresi yakalanınca sağdaki dahili tarayıcıda otomatik açılır; action=durdur: repodaki çalışan süreci durdurur. Kur/Başlat turu KİLİTLEMEZ, çıktı panelde canlı akar. repo alanı boşsa panelde son kullanılan repo kullanılır.',
1621
+ 'Beast Sandbox yönetimi — kullanıcı GitHub repo adresi/linki verdiğinde ya da indirilmiş bir repoyu kurmak/çalıştırmak istediğinde kullan. action=indir: repoyu ~/Beast-Sandbox klasörüne klonlar (owner/repo ya da tam URL); action=kur: algılanan kurulum komutunu (npm install, pip install -r requirements.txt, uv sync, cargo fetch, go mod download) ÇALIŞTIR panelinde başlatır; action=baslat: çalıştırma komutunu başlatır — bağımlılıklar eksikse önce kurar (npm install && npm run dev, python app.py vb.); localhost adresi yakalanınca ÇALIŞTIR panelinde canlı önizlemede açılır; action=durdur: repodaki çalışan süreci durdurur. Kur/Başlat/Dev turu KİLİTLEMEZ, çıktı panelde canlı akar. repo alanı boşsa panelde son kullanılan repo kullanılır.',
1622
1622
  parameters: {
1623
1623
  type: 'object',
1624
1624
  properties: {
package/src/main.js CHANGED
@@ -272,8 +272,15 @@ try {
272
272
  try { nodemailer = require('nodemailer'); } catch {}
273
273
 
274
274
  const APP_DIR = path.join(app.getPath('appData'), 'beast');
275
- /* Windows toast bildirimleri için AppUserModelID erken ayarlanmalı */
276
- try { app.setAppUserModelId('com.quantumalgo.beastagent'); } catch {}
275
+ /* Windows toast bildirimleri için AppUserModelID erken ayarlanmalı. ANCAK özel
276
+ AUMID, Windows'ta kayıtlı kısayol yoksa taskbar ikonunu electron.exe logosuna
277
+ düşürür. Bu yüzden yalnız paketli kurulumda ya da npm modunda masaüstü
278
+ kısayolu (aynı AUMID ile oluşturulur) mevcutsa atanır. */
279
+ try {
280
+ let aumidOk = app.isPackaged;
281
+ if (!aumidOk) aumidOk = fs.existsSync(path.join(app.getPath('desktop'), 'Beast Agent.lnk'));
282
+ if (aumidOk) app.setAppUserModelId('com.quantumalgo.beastagent');
283
+ } catch {}
277
284
  /* mem0-native embedding modeli whisper ile AYNI cache'i kullanır (src/agent/mem0.js okur) */
278
285
  process.env.BEAST_MODELS_DIR = path.join(APP_DIR, 'models');
279
286
 
@@ -3603,6 +3610,7 @@ function ensureDesktopShortcut() {
3603
3610
  description: 'Beast Agent — hızlı, hafif ve becerikli',
3604
3611
  icon: path.join(__dirname, '..', 'assets', 'app.ico'),
3605
3612
  iconIndex: 0,
3613
+ appUserModelId: 'com.quantumalgo.beastagent',
3606
3614
  });
3607
3615
  log.info('main', ok ? 'Masaüstü kısayolu oluşturuldu (npm modu)' : 'Masaüstü kısayolu oluşturulamadı');
3608
3616
  } catch (e) {
@@ -11152,6 +11160,7 @@ ipcMain.handle('sandbox:remove', async (_e, payload) => {
11152
11160
  delete sbCfg[folder];
11153
11161
  sbSaveCfg();
11154
11162
  }
11163
+ try { fs.rmSync(path.join(beastDir(), 'sandbox-installed', path.basename(folder)), { force: true }); } catch {}
11155
11164
  return { ok: true };
11156
11165
  });
11157
11166
 
@@ -11211,7 +11220,7 @@ ipcMain.handle('sandbox:tree', (_e, payload) => {
11211
11220
  });
11212
11221
 
11213
11222
  /* 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
11223
+ Başlat/Dev/Durdur butonları artık deterministik: proje tipi algılanır
11215
11224
  (package.json, requirements.txt, pyproject.toml, Cargo.toml, go.mod,
11216
11225
  index.html) ve doğru komut DOĞRUDAN çalıştırılır. Her repo kendi süreç
11217
11226
  yuvasında koşar — birden çok repo aynı anda çalışabilir; çıktı tamponlanır,
@@ -11253,6 +11262,25 @@ function sbPythonCmd() {
11253
11262
  return 'python';
11254
11263
  }
11255
11264
 
11265
+ /* statik site üreticileri: hugo / jekyll / mkdocs — doküman repoları sık böyle;
11266
+ Başlat/Dev butonları bunları da tanısın (ör. docs/ altında hugo.yaml) */
11267
+ function sbSiteGenInfo(folder) {
11268
+ for (const d of ['', 'docs', 'site', 'website']) {
11269
+ const base = d ? path.join(folder, d) : folder;
11270
+ const has = (f) => { try { return fs.existsSync(path.join(base, f)); } catch { return false; } };
11271
+ if (has('hugo.toml') || has('hugo.yaml') || has('hugo.json') || (has('config.toml') && (has('content') || has('themes')))) {
11272
+ return { kind: 'static', label: 'hugo', install: '', run: 'hugo server' + (d ? ' -s ' + d : '') };
11273
+ }
11274
+ if (has('_config.yml') && (has('Gemfile') || has('Gemfile.lock'))) {
11275
+ return { kind: 'ruby', label: 'jekyll', install: 'bundle install', run: 'bundle exec jekyll serve' + (d ? ' -s ' + d : '') };
11276
+ }
11277
+ if (has('mkdocs.yml') || has('mkdocs.yaml')) {
11278
+ return { kind: 'python', label: 'mkdocs', install: sbPythonCmd() + ' -m pip install mkdocs', run: sbPythonCmd() + ' -m mkdocs serve' };
11279
+ }
11280
+ }
11281
+ return null;
11282
+ }
11283
+
11256
11284
  /* repo köküne bakıp proje tipini + kur/çalıştır komutlarını çıkarır */
11257
11285
  function sbProjectInfo(folder) {
11258
11286
  const out = { kind: 'unknown', label: 'bilinmiyor', name: path.basename(folder), install: '', run: '', dev: '', build: '' };
@@ -11275,6 +11303,21 @@ function sbProjectInfo(folder) {
11275
11303
  out.build = s.build ? pm + ' run build' : '';
11276
11304
  return out;
11277
11305
  }
11306
+ /* statik site üreticisi (hugo/jekyll/mkdocs) python paketinden ÖNCE gelir:
11307
+ docs/ altındaki hugo.yaml, kökteki requirements.txt'i gölgeleyebilsin */
11308
+ const site = sbSiteGenInfo(folder);
11309
+ if (site) {
11310
+ out.kind = site.kind;
11311
+ out.label = site.label;
11312
+ if (site.install) out.install = site.install;
11313
+ if (site.run) { out.run = site.run; out.dev = site.run; }
11314
+ if (!out.install) {
11315
+ const py = sbPythonCmd();
11316
+ if (fs.existsSync(path.join(folder, 'requirements.txt'))) out.install = py + ' -m pip install -r requirements.txt';
11317
+ else if (fs.existsSync(path.join(folder, 'pyproject.toml'))) out.install = py + ' -m pip install -e .';
11318
+ }
11319
+ return out;
11320
+ }
11278
11321
  const hasPy = ['pyproject.toml', 'requirements.txt', 'setup.py'].some((f) => fs.existsSync(path.join(folder, f)));
11279
11322
  if (hasPy) {
11280
11323
  const py = sbPythonCmd();
@@ -11350,7 +11393,7 @@ function sbSlotPush(slot, folder, text) {
11350
11393
  sbRunEmit(folder, text, slot.kind);
11351
11394
  }
11352
11395
 
11353
- function sbRunStartManaged(folder, cmd, kind) {
11396
+ function sbRunStartManaged(folder, cmd, kind, opts) {
11354
11397
  folder = String(folder || '');
11355
11398
  cmd = String(cmd || '').trim();
11356
11399
  kind = kind === 'install' ? 'install' : 'run';
@@ -11365,7 +11408,7 @@ function sbRunStartManaged(folder, cmd, kind) {
11365
11408
  windowsHide: true,
11366
11409
  env: { ...process.env, FORCE_COLOR: '0', NO_COLOR: '1' },
11367
11410
  });
11368
- const slot = { proc, kind, cmd, url: '', lines: [], startedAt: Date.now() };
11411
+ const slot = { proc, kind, cmd, url: '', lines: [], startedAt: Date.now(), installCmd: (opts && opts.installCmd) || '' };
11369
11412
  sbProcs.set(folder, slot);
11370
11413
  const onLine = (buf) => {
11371
11414
  for (const piece of String(buf).split(/\r?\n/)) {
@@ -11387,9 +11430,20 @@ function sbRunStartManaged(folder, cmd, kind) {
11387
11430
  proc.on('close', (code) => {
11388
11431
  sbSlotPush(slot, folder, '[süreç bitti' + (code != null ? ' — kod ' + code : '') + ']');
11389
11432
  slot.proc = null;
11433
+ /* başarılı kurulum damgalanır: sonraki Başlat/Dev kurulumu tekrarlamaz */
11434
+ if (code === 0 && (slot.kind === 'install' || slot.installCmd)) sbMarkInstalled(folder);
11435
+ /* hızlı çöküş (10 sn içinde sıfırdan farklı çıkış): renderer bunu ajan
11436
+ devralması için işaret sayar — yanlış algılanan komut ajanla düzeltilir */
11437
+ const quickFail = code != null && Number(code) !== 0 && Date.now() - slot.startedAt < 10000;
11390
11438
  try {
11391
11439
  if (win && !win.isDestroyed()) {
11392
- win.webContents.send('agent:event', { type: 'sb-run-end', folder, code: code == null ? null : Number(code) });
11440
+ win.webContents.send('agent:event', {
11441
+ type: 'sb-run-end',
11442
+ folder,
11443
+ code: code == null ? null : Number(code),
11444
+ kind: slot.kind,
11445
+ quickFail,
11446
+ });
11393
11447
  }
11394
11448
  } catch {}
11395
11449
  });
@@ -11446,7 +11500,35 @@ function sbInstallRepo(folder) {
11446
11500
 
11447
11501
  ipcMain.handle('sandbox:install', async (_e, payload) => sbInstallRepo(String((payload && payload.folder) || '')));
11448
11502
 
11449
- /* Başlat/Dev: algılanan/kayıtlı çalıştırma komutunu başlatır (panel + ajan ortak) */
11503
+ /* bağımlılıklar eksik mi? — Başlat/Dev gerekirse ÖNCE kurar; kurulum bir kez
11504
+ yapıldıktan sonra damga dosyasıyla (node_modules var / %APPDATA% damgası)
11505
+ tekrarlanmaz */
11506
+ function sbInstallNeeded(folder, info) {
11507
+ try {
11508
+ if (!info.install) return false;
11509
+ if (info.kind === 'node') {
11510
+ const nm = path.join(folder, 'node_modules');
11511
+ return !fs.existsSync(nm) || !fs.readdirSync(nm).length;
11512
+ }
11513
+ if (info.label === 'jekyll') return !fs.existsSync(path.join(folder, 'Gemfile.lock'));
11514
+ if (info.kind === 'python' || info.label === 'mkdocs') {
11515
+ if (fs.existsSync(path.join(folder, '.venv'))) return false;
11516
+ return !fs.existsSync(path.join(beastDir(), 'sandbox-installed', path.basename(folder)));
11517
+ }
11518
+ } catch {}
11519
+ return false;
11520
+ }
11521
+
11522
+ function sbMarkInstalled(folder) {
11523
+ try {
11524
+ const dir = path.join(beastDir(), 'sandbox-installed');
11525
+ fs.mkdirSync(dir, { recursive: true });
11526
+ fs.writeFileSync(path.join(dir, path.basename(folder)), new Date().toISOString());
11527
+ } catch {}
11528
+ }
11529
+
11530
+ /* Başlat/Dev: algılanan çalıştırma komutunu başlatır; bağımlılıklar eksikse
11531
+ önce kurulum komutunu zincirler (npm install && npm run dev gibi) */
11450
11532
  function sbStartRepo(folder, mode) {
11451
11533
  folder = String(folder || '');
11452
11534
  mode = String(mode || 'run') === 'dev' ? 'dev' : 'run';
@@ -11454,8 +11536,10 @@ function sbStartRepo(folder, mode) {
11454
11536
  const info = sbProjectInfoMerged(folder);
11455
11537
  const cmd = mode === 'dev' ? info.dev || info.run : info.run || info.dev;
11456
11538
  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;
11539
+ const pre = sbInstallNeeded(folder, info) ? info.install : '';
11540
+ const full = pre ? pre + ' && ' + cmd : cmd;
11541
+ const r = sbRunStartManaged(folder, full, 'run', pre ? { installCmd: pre } : null);
11542
+ if (r.ok) r.cmd = full;
11459
11543
  return r;
11460
11544
  }
11461
11545
 
@@ -11524,6 +11608,98 @@ ipcMain.handle('sandbox:openurl', async (_e, url) => {
11524
11608
  }
11525
11609
  });
11526
11610
 
11611
+ /* ---------- SANDBOX PANEL İÇİ CANLI ÖNİZLEME (native WebContentsView) ----------
11612
+ ÇALIŞTIR bölümünde, çıktı günlüğünün yerine repo'nun çalışan hâlini gösterir.
11613
+ Tarayıcı dock'undan bağımsızdır: renderer host alanının DOM dikdörtgenini
11614
+ gönderir, view tam o alana oturur. Yalnız localhost adresleri yüklenir. */
11615
+ const sbPreview = { view: null, attached: false, visible: false, url: '', bounds: null };
11616
+
11617
+ function ensureSbPreview() {
11618
+ if (sbPreview.view) return sbPreview.view;
11619
+ const view = new WebContentsView({
11620
+ webPreferences: {
11621
+ partition: 'persist:sandbox-preview',
11622
+ contextIsolation: true,
11623
+ nodeIntegration: false,
11624
+ sandbox: true,
11625
+ spellcheck: false,
11626
+ backgroundThrottling: false,
11627
+ },
11628
+ });
11629
+ view.setBackgroundColor(settings.theme === 'dark' ? '#0d0d0f' : '#ffffff');
11630
+ const wc = view.webContents;
11631
+ try {
11632
+ const chromeUA = `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/${process.versions.chrome} Safari/537.36`;
11633
+ wc.setUserAgent(chromeUA);
11634
+ } catch {}
11635
+ wc.setWindowOpenHandler(({ url }) => {
11636
+ if (/^https?:\/\//i.test(url)) wc.loadURL(url).catch(() => {});
11637
+ return { action: 'deny' };
11638
+ });
11639
+ wc.on('render-process-gone', () => {
11640
+ if (win && !win.isDestroyed() && sbPreview.view) {
11641
+ try { win.contentView.removeChildView(sbPreview.view); } catch {}
11642
+ }
11643
+ sbPreview.view = null;
11644
+ sbPreview.attached = false;
11645
+ sbPreview.visible = false;
11646
+ });
11647
+ sbPreview.view = view;
11648
+ return view;
11649
+ }
11650
+
11651
+ function sbPreviewDetach() {
11652
+ if (sbPreview.view && win && !win.isDestroyed()) {
11653
+ try { sbPreview.view.setVisible(false); } catch {}
11654
+ try { win.contentView.removeChildView(sbPreview.view); } catch {}
11655
+ }
11656
+ sbPreview.attached = false;
11657
+ sbPreview.visible = false;
11658
+ }
11659
+
11660
+ function sbPreviewSet(payload) {
11661
+ if (!win || win.isDestroyed()) return { ok: false, error: 'pencere yok' };
11662
+ const p = payload || {};
11663
+ if (p.bounds && typeof p.bounds === 'object') {
11664
+ sbPreview.bounds = {
11665
+ x: Math.max(0, Math.round(Number(p.bounds.x) || 0)),
11666
+ y: Math.max(0, Math.round(Number(p.bounds.y) || 0)),
11667
+ width: Math.max(0, Math.round(Number(p.bounds.width) || 0)),
11668
+ height: Math.max(0, Math.round(Number(p.bounds.height) || 0)),
11669
+ };
11670
+ }
11671
+ if (p.url) {
11672
+ const u = String(p.url);
11673
+ if (/^https?:\/\/(?:localhost|127\.0\.0\.1)(?::\d{2,5})?(?:[/?#]|$)/i.test(u)) sbPreview.url = u;
11674
+ }
11675
+ const b = sbPreview.bounds;
11676
+ const want = p.visible !== false && !!sbPreview.url && !!b && b.width > 0 && b.height > 0;
11677
+ if (!want) {
11678
+ if (sbPreview.view) {
11679
+ try { sbPreview.view.setVisible(false); } catch {}
11680
+ }
11681
+ sbPreview.visible = false;
11682
+ return { ok: true, visible: false };
11683
+ }
11684
+ const view = ensureSbPreview();
11685
+ if (sbPreview.url) {
11686
+ let cur = '';
11687
+ try { cur = view.webContents.getURL(); } catch {}
11688
+ if (cur !== sbPreview.url) view.webContents.loadURL(sbPreview.url).catch(() => {});
11689
+ }
11690
+ if (!sbPreview.attached) {
11691
+ try { win.contentView.addChildView(view); sbPreview.attached = true; } catch {}
11692
+ }
11693
+ try {
11694
+ view.setBounds({ x: b.x, y: b.y, width: b.width, height: b.height });
11695
+ view.setVisible(true);
11696
+ sbPreview.visible = true;
11697
+ } catch {}
11698
+ return { ok: true, visible: true, url: sbPreview.url };
11699
+ }
11700
+
11701
+ ipcMain.handle('sbpreview:set', async (_e, payload) => sbPreviewSet(payload || {}));
11702
+
11527
11703
  /* Sağ tık menüsü: HTML dosyasını dahili tarayıcıda GÖRÜNÜR aç */
11528
11704
  /* ---------- BC DAHİLİ STATİK SUNUCU ----------
11529
11705
  Beast Code çıktısı ASLA file:// ile açılmaz: ES modülleri, fetch, ServiceWorker
package/src/preload.js CHANGED
@@ -192,6 +192,7 @@ contextBridge.exposeInMainWorld('beast', {
192
192
  sandboxProcState: (payload) => ipcRenderer.invoke('sandbox:procstate', payload),
193
193
  sandboxCfgSet: (payload) => ipcRenderer.invoke('sandbox:cfg:set', payload),
194
194
  sandboxOpenUrl: (url) => ipcRenderer.invoke('sandbox:openurl', url),
195
+ sbPreviewSet: (payload) => ipcRenderer.invoke('sbpreview:set', payload),
195
196
  waStart: () => ipcRenderer.invoke('wa:start'),
196
197
  waStop: () => ipcRenderer.invoke('wa:stop'),
197
198
  waReset: () => ipcRenderer.invoke('wa:reset'),
@@ -594,6 +594,7 @@
594
594
  tipIde: 'IDE Modu — dosyalar + chat + preview',
595
595
  tipStudio: 'Beast Studio — video yapma/düzenleme modu',
596
596
  tipSandbox: 'Beast Sandbox — GitHub reposu indir & içinde çalış',
597
+ tipMcp: 'Beast MCP — dış araç sunucuları (Model Context Protocol)',
597
598
  tipCodeHide: 'Editörü kapat',
598
599
  tipCodeShow: 'Editörü göster',
599
600
  ide_files: 'DOSYALAR',
@@ -1387,6 +1388,7 @@
1387
1388
  tipIde: 'IDE Mode — files + chat + preview',
1388
1389
  tipStudio: 'Beast Studio — video making/editing mode',
1389
1390
  tipSandbox: 'Beast Sandbox — download a GitHub repo & work inside it',
1391
+ tipMcp: 'Beast MCP — external tool servers (Model Context Protocol)',
1390
1392
  tipCodeHide: 'Hide editor',
1391
1393
  tipCodeShow: 'Show editor',
1392
1394
  ide_files: 'FILES',
@@ -103,6 +103,7 @@
103
103
  <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>
104
104
  <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>
105
105
  <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>
106
+ <button id="mcpBtn" class="dd-btn dd-icon-btn" title="Beast MCP — dış araç sunucuları (Model Context Protocol)" data-i18n-title="tipMcp"><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="3" y="4" width="18" height="7" rx="2"/><rect x="3" y="13" width="18" height="7" rx="2"/><line x1="7" y1="7.5" x2="7.01" y2="7.5"/><line x1="7" y1="16.5" x2="7.01" y2="16.5"/></svg></button>
106
107
  <button id="railBtn" class="dd-btn dd-icon-btn" title="Paralel Ajan Konsolu" data-i18n-title="tipRail">&#x25A4;</button>
107
108
  <button id="dmBtn" class="dd-btn dd-icon-btn" title="Ajan DM — ajanlar arası konuşma" data-i18n-title="tipAgentDm">&#x2709;&#xFE0E;</button>
108
109
  <button id="watchBtn" class="dd-btn dd-icon-btn" title="İzleyiciler (Watchers)" data-i18n-title="tipWatch">&#x25CE;</button>
@@ -419,20 +420,24 @@
419
420
  <div id="sbStatus" hidden></div>
420
421
  <div id="sbInputRow">
421
422
  <textarea id="sbInput" rows="1" spellcheck="false" autocomplete="off" placeholder="Sandbox mesajı — ajan bu repo klasöründe çalışır…"></textarea>
423
+ <button id="sbSend" title="Gönder (Enter)"><svg viewBox="0 0 24 24" width="13" height="13" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg></button>
422
424
  <button id="sbStop" title="Çalışan mesajı durdur" hidden data-i18n-title="bc_stop"><svg viewBox="0 0 24 24" width="13" height="13" fill="currentColor" stroke="none"><rect x="6" y="6" width="12" height="12" rx="2"/></svg></button>
423
425
  </div>
424
426
  </section>
425
427
 
428
+ <div id="sbSplit" title="Sürükleyerek boyutlandır"></div>
429
+
426
430
  <section id="sbRun">
427
431
  <div id="sbRunHead">
428
432
  <span id="sbRunTitle">ÇALIŞTIR</span>
429
433
  <span id="sbRunKind"></span>
430
434
  <span id="sbRunCwd" title="">—</span>
435
+ <span class="file-spacer"></span>
436
+ <button id="sbRunView" class="on" title="Görsel önizleme açık — çıktıya dön">Görsel</button>
431
437
  </div>
432
438
  <div id="sbRunBtns">
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>
439
+ <button id="sbRunStart" title="Başlat — gerekirse bağımlılıkları kurar, sonra çalıştırır (adres panelde canlı önizlemede 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>
440
+ <button id="sbRunDev" title="Geliştirme modunda başlat (gerekirse önce kurar)"><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>
436
441
  <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>
437
442
  </div>
438
443
  <div id="sbRunCustom">
@@ -440,6 +445,7 @@
440
445
  <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
446
  <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>
442
447
  </div>
448
+ <div id="sbPreviewHost" hidden><div id="sbPreviewHint">🌐 canlı önizleme — Başlat/Dev ile sunucu açılınca burada görünür</div></div>
443
449
  <div id="sbRunOut"></div>
444
450
  <div id="sbRunFoot">
445
451
  <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>
@@ -447,6 +453,37 @@
447
453
  </div>
448
454
  </section>
449
455
  </div>
456
+
457
+ <div id="mcpRow">
458
+ <section id="mcpPanel">
459
+ <div id="mcpHead">
460
+ <span id="mcpTitle">BEAST MCP</span>
461
+ <span class="file-spacer"></span>
462
+ <button id="mcpReload" title="Durumu yenile">&#x27F3;&#xFE0E;</button>
463
+ </div>
464
+ <div id="mcpList"></div>
465
+ <div id="mcpFoot">
466
+ <button id="mcpAddBtn" title="mcp.json'a örnek bir server bloğu ekler">+ Yeni Server</button>
467
+ </div>
468
+ </section>
469
+ <section id="mcpDetail">
470
+ <div id="mcpDetailHead">
471
+ <span id="mcpDetailTitle">Detay</span>
472
+ <span id="mcpDetailState" class="mcp-badge"></span>
473
+ </div>
474
+ <div id="mcpDetailBody"></div>
475
+ <div id="mcpJsonWrap">
476
+ <div id="mcpJsonHead">
477
+ <span>mcp.json</span>
478
+ <span id="mcpJsonPath" title=""></span>
479
+ <span class="file-spacer"></span>
480
+ <span id="mcpMsg"></span>
481
+ <button id="mcpSave">Kaydet</button>
482
+ </div>
483
+ <textarea id="mcpJson" spellcheck="false"></textarea>
484
+ </div>
485
+ </section>
486
+ </div>
450
487
  </main>
451
488
 
452
489
  <aside id="rail">
@@ -135,9 +135,12 @@ const els = {
135
135
  sbStatus: $('#sbStatus'),
136
136
  sbInput: $('#sbInput'),
137
137
  sbStop: $('#sbStop'),
138
+ sbSend: $('#sbSend'),
139
+ sbSplit: $('#sbSplit'),
140
+ sbRun: $('#sbRun'),
138
141
  sbRunCwd: $('#sbRunCwd'),
142
+ sbRunView: $('#sbRunView'),
139
143
  sbRunKind: $('#sbRunKind'),
140
- sbRunInstall: $('#sbRunInstall'),
141
144
  sbRunStart: $('#sbRunStart'),
142
145
  sbRunDev: $('#sbRunDev'),
143
146
  sbRunStop: $('#sbRunStop'),
@@ -147,6 +150,18 @@ const els = {
147
150
  sbRunOut: $('#sbRunOut'),
148
151
  sbRunOpen: $('#sbRunOpen'),
149
152
  sbRunUrl: $('#sbRunUrl'),
153
+ mcpBtn: $('#mcpBtn'),
154
+ mcpRow: $('#mcpRow'),
155
+ mcpList: $('#mcpList'),
156
+ mcpReload: $('#mcpReload'),
157
+ mcpAddBtn: $('#mcpAddBtn'),
158
+ mcpDetailTitle: $('#mcpDetailTitle'),
159
+ mcpDetailState: $('#mcpDetailState'),
160
+ mcpDetailBody: $('#mcpDetailBody'),
161
+ mcpJson: $('#mcpJson'),
162
+ mcpJsonPath: $('#mcpJsonPath'),
163
+ mcpSave: $('#mcpSave'),
164
+ mcpMsg: $('#mcpMsg'),
150
165
  stPanel: $('#stPanel'),
151
166
  stTitle: $('#stTitle'),
152
167
  stCwd: $('#stCwd'),
@@ -6000,6 +6015,7 @@ function onEvent(ev) {
6000
6015
  /* IDE modunda tarayıcı açılır/kapanır/genişlik değişirse row yeniden bölünür —
6001
6016
  editör + Beast Code ORTAK kırpılıp ORTAK açılır (kullanıcı payı korunur) */
6002
6017
  if (ideModeOn()) ideSplitApplyFrac();
6018
+ if (sbModeOn()) sbSplitApplyFrac();
6003
6019
  if (shown) {
6004
6020
  els.browserBtn.classList.add('on');
6005
6021
  if (ev.url && document.activeElement !== els.bbUrl) {
@@ -8354,6 +8370,7 @@ async function setIdeMode(on) {
8354
8370
  if (on && studioModeOn()) await setStudioMode(false); /* Studio açıkken IDE'ye geçiş — Studio kapanır */
8355
8371
  if (on && sbModeOn()) setSandboxMode(false); /* Sandbox açıkken IDE'ye geçiş — Sandbox kapanır */
8356
8372
  if (on && financeModeOn()) setFinanceMode(false); /* Finance açıkken IDE'ye geçiş — Finance kapanır */
8373
+ if (on && mcpModeOn()) setMcpMode(false); /* MCP açıkken IDE'ye geçiş — MCP kapanır */
8357
8374
  document.body.classList.toggle('ide-mode', !!on);
8358
8375
  if (els.ideBtn) els.ideBtn.classList.toggle('on', !!on);
8359
8376
  /* soldaki marka: chat modunda BEAST Agent, IDE modunda BEAST Code, Studio modunda BEAST Studio */
@@ -8429,6 +8446,7 @@ async function setStudioMode(on) {
8429
8446
  if (on && ideModeOn()) await setIdeMode(false); /* IDE açıkken Studio'ya geçiş — IDE kapanır */
8430
8447
  if (on && sbModeOn()) setSandboxMode(false); /* Sandbox açıkken Studio'ya geçiş — Sandbox kapanır */
8431
8448
  if (on && financeModeOn()) setFinanceMode(false); /* Finance açıkken Studio'ya geçiş — Finance kapanır */
8449
+ if (on && mcpModeOn()) setMcpMode(false); /* MCP açıkken Studio'ya geçiş — MCP kapanır */
8432
8450
  document.body.classList.toggle('studio-mode', !!on);
8433
8451
  if (els.studioBtn) els.studioBtn.classList.toggle('on', !!on);
8434
8452
  /* ALT TERMINAL: yalnız Beast Code'a özgü — Studio'ya geçişte kapanır */
@@ -9725,6 +9743,7 @@ async function setFinanceMode(on) {
9725
9743
  if (on && ideModeOn()) await setIdeMode(false);
9726
9744
  if (on && studioModeOn()) await setStudioMode(false);
9727
9745
  if (on && sbModeOn()) setSandboxMode(false);
9746
+ if (on && mcpModeOn()) setMcpMode(false); /* MCP açıkken Finance'a geçiş — MCP kapanır */
9728
9747
  document.body.classList.toggle('finance-mode', !!on);
9729
9748
  if (els.finBtn) els.finBtn.classList.toggle('on', !!on);
9730
9749
  const brandSub = document.querySelector('#brand .brand-sub');
@@ -11967,6 +11986,10 @@ const sbTreeState = { cache: new Map(), open: new Set(['']), path: '' };
11967
11986
  let sbRunUrl = '';
11968
11987
  let sbRunRunning = false;
11969
11988
  let sbRunInfo = null;
11989
+ let sbAutoAssist = null; /* butonla başlatılan süreç hızlı çökerse: 'install' | 'run' */
11990
+ let sbPreviewOn = true; /* ÇALIŞTIR içi görsel önizleme tercihi */
11991
+ let sbPreviewShown = false;
11992
+ let sbPreviewSuspend = false; /* ayırıcı sürüklenirken native view gizli tutulur */
11970
11993
 
11971
11994
  function sbModeOn() {
11972
11995
  return document.body.classList.contains('sandbox-mode');
@@ -12168,6 +12191,7 @@ async function sbSelectCurrent() {
12168
12191
  sbRunLine('ℹ ' + (det.info.label || 'proje') + ' · kur: ' + (det.info.install || '—') + ' · çalıştır: ' + (det.info.run || '—'), 't-dim');
12169
12192
  }
12170
12193
  }
12194
+ sbPreviewApply();
12171
12195
  /* sol konsol: seçilen reponun klasörü — oturum açıldıktan sonra da garanti tazele */
12172
12196
  sbTreeState.cache.clear();
12173
12197
  if (sbModeOn()) renderFileTree().catch(() => {});
@@ -12183,7 +12207,8 @@ function sbInputResize() {
12183
12207
 
12184
12208
  function sbRunCurrent() {
12185
12209
  const msg = els.sbInput.value.trim();
12186
- if (!msg || !sbFolder || sbCloning) return;
12210
+ if (!msg || sbCloning) return;
12211
+ if (!sbFolder) { toast('Önce repo indir ya da seç'); return; }
12187
12212
  els.sbInput.value = '';
12188
12213
  sbInputResize();
12189
12214
  sbLine('t-cmd', 'sen> ' + msg);
@@ -12252,8 +12277,10 @@ function setSandboxMode(on) {
12252
12277
  if (on && ideModeOn()) { setIdeMode(false); }
12253
12278
  if (on && studioModeOn()) { setStudioMode(false); }
12254
12279
  if (on && financeModeOn()) { setFinanceMode(false); }
12280
+ if (on && mcpModeOn()) setMcpMode(false); /* MCP açıkken Sandbox'a geçiş — MCP kapanır */
12255
12281
  document.body.classList.toggle('sandbox-mode', !!on);
12256
12282
  if (els.sbBtn) els.sbBtn.classList.toggle('on', !!on);
12283
+ sbPreviewApply();
12257
12284
  /* marka + sol konsol başlığı: moduna göre BEAST Agent/Code/Studio/Sandbox */
12258
12285
  const brandSub = document.querySelector('#brand .brand-sub');
12259
12286
  if (brandSub) brandSub.textContent = on ? 'Sandbox' : 'Agent';
@@ -12261,6 +12288,7 @@ function setSandboxMode(on) {
12261
12288
  if (ftp && on) ftp.textContent = 'REPO DOSYALARI';
12262
12289
  if (on) {
12263
12290
  loadSandboxList();
12291
+ sbSplitRestore();
12264
12292
  sbTreeState.cache.clear();
12265
12293
  renderFileTree().catch(() => {});
12266
12294
  } else if (!ideModeOn() && !studioModeOn()) {
@@ -12270,19 +12298,176 @@ function setSandboxMode(on) {
12270
12298
  }
12271
12299
  }
12272
12300
 
12301
+ /* ================= BEAST MCP MODU =================
12302
+ Model Context Protocol sunucuları: tek panelde durum, araç listesi,
12303
+ aç/kapat, yeniden bağlan ve mcp.json düzenleme. Ana sohbeti bozmaz —
12304
+ diğer modlar gibi body.mcp-mode ile ayrı satır. */
12305
+ function mcpModeOn() {
12306
+ return document.body.classList.contains('mcp-mode');
12307
+ }
12308
+
12309
+ const MCP_STATE_TR = { up: 'BAĞLI', starting: 'BAĞLANIYOR', down: 'HATA', idle: 'BEKLEMEDE', disabled: 'KAPALI' };
12310
+ let mcpSel = '';
12311
+ let mcpTimer = null;
12312
+
12313
+ async function setMcpMode(on) {
12314
+ if (on && ideModeOn()) await setIdeMode(false);
12315
+ if (on && studioModeOn()) await setStudioMode(false);
12316
+ if (on && sbModeOn()) setSandboxMode(false);
12317
+ if (on && financeModeOn()) setFinanceMode(false);
12318
+ document.body.classList.toggle('mcp-mode', !!on);
12319
+ if (els.mcpBtn) els.mcpBtn.classList.toggle('on', !!on);
12320
+ const brandSub = document.querySelector('#brand .brand-sub');
12321
+ if (brandSub) brandSub.textContent = on ? 'MCP' : 'Agent';
12322
+ if (on) {
12323
+ await renderMcpPanel();
12324
+ if (!mcpTimer) {
12325
+ mcpTimer = setInterval(() => { if (mcpModeOn()) renderMcpPanel(); }, 5000);
12326
+ }
12327
+ } else if (mcpTimer) {
12328
+ clearInterval(mcpTimer);
12329
+ mcpTimer = null;
12330
+ }
12331
+ }
12332
+
12333
+ async function renderMcpPanel() {
12334
+ if (!els.mcpList || !mcpModeOn()) return;
12335
+ const st = await beast.mcpStatus().catch(() => ({ path: '', servers: [] }));
12336
+ const cfg = await beast.mcpConfigGet().catch(() => ({ path: '', raw: '' }));
12337
+ const servers = st.servers || [];
12338
+ if (!servers.length) {
12339
+ els.mcpList.innerHTML = '<div class="file-empty">Henüz MCP server yok — aşağıdaki mcp.json\'a ekle ya da “Yeni Server”e bas.</div>';
12340
+ } else {
12341
+ if (!servers.some((s) => s.name === mcpSel)) mcpSel = servers[0].name;
12342
+ els.mcpList.innerHTML = servers.map((s) => {
12343
+ const badge = MCP_STATE_TR[s.state] || s.state;
12344
+ return '<div class="mcp-card' + (s.name === mcpSel ? ' sel' : '') + '" data-n="' + escapeHtml(s.name) + '">' +
12345
+ '<div class="mc-top"><span class="mc-name">' + escapeHtml(s.name) + '</span>' +
12346
+ '<span class="mcp-badge ' + escapeHtml(s.state) + '">' + escapeHtml(badge) + '</span>' +
12347
+ '<span class="file-spacer"></span>' +
12348
+ (s.enabled && s.toolCount ? '<span class="mcp-badge">' + s.toolCount + ' araç</span>' : '') + '</div>' +
12349
+ '<div class="mc-cmd">' + escapeHtml((s.command || '') + ' ' + (s.args || []).join(' ')) + '</div>' +
12350
+ (s.lastError && s.state === 'down' ? '<div class="mc-err">' + escapeHtml(String(s.lastError).slice(0, 140)) + '</div>' : '') +
12351
+ '<div class="mc-actions">' +
12352
+ '<button class="mcp-tgl" data-n="' + escapeHtml(s.name) + '">' + (s.enabled ? '⏸ Durdur' : '▶ Başlat') + '</button>' +
12353
+ '<button class="mcp-ref" data-n="' + escapeHtml(s.name) + '">⟳ Yenile</button>' +
12354
+ '</div></div>';
12355
+ }).join('');
12356
+ }
12357
+ renderMcpDetail(servers.find((s) => s.name === mcpSel) || null);
12358
+ if (els.mcpJson && document.activeElement !== els.mcpJson) {
12359
+ els.mcpJson.value = cfg.raw || JSON.stringify({ servers: {} }, null, 2);
12360
+ }
12361
+ if (els.mcpJsonPath) {
12362
+ els.mcpJsonPath.textContent = cfg.path || '';
12363
+ els.mcpJsonPath.title = cfg.path || '';
12364
+ }
12365
+
12366
+ els.mcpList.querySelectorAll('.mcp-card').forEach((c) => c.addEventListener('click', (ev) => {
12367
+ if (ev.target.closest('button')) return;
12368
+ mcpSel = c.dataset.n;
12369
+ renderMcpPanel();
12370
+ }));
12371
+ els.mcpList.querySelectorAll('.mcp-ref').forEach((b) => b.addEventListener('click', async () => {
12372
+ b.disabled = true;
12373
+ mcpSel = b.dataset.n;
12374
+ await beast.mcpRefresh(b.dataset.n).catch(() => {});
12375
+ renderMcpPanel();
12376
+ }));
12377
+ els.mcpList.querySelectorAll('.mcp-tgl').forEach((b) => b.addEventListener('click', async () => {
12378
+ b.disabled = true;
12379
+ try {
12380
+ const obj = JSON.parse(els.mcpJson.value || '{}');
12381
+ const name = b.dataset.n;
12382
+ if (obj.servers && obj.servers[name]) {
12383
+ obj.servers[name].enabled = obj.servers[name].enabled === false;
12384
+ await beast.mcpConfigSet(JSON.stringify(obj)).catch(() => {});
12385
+ mcpSel = name;
12386
+ }
12387
+ } catch (e) {
12388
+ toast('JSON hatası: ' + String((e && e.message) || e));
12389
+ }
12390
+ renderMcpPanel();
12391
+ }));
12392
+ }
12393
+
12394
+ function renderMcpDetail(s) {
12395
+ if (els.mcpDetailTitle) els.mcpDetailTitle.textContent = s ? s.name : 'Detay';
12396
+ if (els.mcpDetailState) {
12397
+ els.mcpDetailState.className = 'mcp-badge' + (s ? ' ' + s.state : '');
12398
+ els.mcpDetailState.textContent = s ? (MCP_STATE_TR[s.state] || s.state) : '';
12399
+ }
12400
+ if (!els.mcpDetailBody) return;
12401
+ if (!s) {
12402
+ els.mcpDetailBody.innerHTML = '<div class="file-empty">Soldan bir server seç.</div>';
12403
+ return;
12404
+ }
12405
+ const rows = [
12406
+ ['Komut', (s.command || '') + ' ' + (s.args || []).join(' ')],
12407
+ ['Durum', (MCP_STATE_TR[s.state] || s.state) + (s.enabled ? '' : ' (kapalı)')],
12408
+ ['Araç', s.enabled ? String(s.toolCount || 0) + ' araç' : '—'],
12409
+ s.timeoutMs ? ['Zaman aşımı', s.timeoutMs + ' ms'] : null,
12410
+ s.tools && s.tools.length ? ['İzinli liste', s.tools.join(', ')] : null,
12411
+ ].filter(Boolean);
12412
+ let html = rows.map(([k, v]) =>
12413
+ '<div class="mcp-drow"><span class="mcp-dk">' + escapeHtml(k) + '</span><span>' + escapeHtml(String(v)) + '</span></div>'
12414
+ ).join('');
12415
+ if (s.lastError && s.state === 'down') {
12416
+ html += '<div class="mcp-drow"><span class="mcp-dk">Hata</span><span style="color:var(--err)">' + escapeHtml(s.lastError) + '</span></div>';
12417
+ }
12418
+ if (s.toolNames && s.toolNames.length) {
12419
+ html += '<div class="mcp-dk" style="margin-top:10px">Araçlar</div><div class="mcp-tools">' +
12420
+ s.toolNames.map((t) => '<span class="mcp-tool">' + escapeHtml(t) + '</span>').join('') + '</div>';
12421
+ } else if (s.enabled) {
12422
+ html += '<div class="file-empty">Araç listesi için ⟳ Yenile (server ilk kullanımda bağlanır).</div>';
12423
+ }
12424
+ els.mcpDetailBody.innerHTML = html;
12425
+ }
12426
+
12427
+ if (els.mcpBtn) els.mcpBtn.addEventListener('click', () => setMcpMode(!mcpModeOn()));
12428
+ if (els.mcpReload) els.mcpReload.addEventListener('click', () => renderMcpPanel());
12429
+ if (els.mcpSave) els.mcpSave.addEventListener('click', async () => {
12430
+ const r = await beast.mcpConfigSet(els.mcpJson.value || '').catch(() => ({ ok: false, error: 'IPC' }));
12431
+ if (els.mcpMsg) {
12432
+ els.mcpMsg.textContent = r && r.ok ? 'Kaydedildi' : ('Hata: ' + ((r && r.error) || '?'));
12433
+ els.mcpMsg.style.color = r && r.ok ? '' : 'var(--err)';
12434
+ }
12435
+ if (r && r.ok) renderMcpPanel();
12436
+ });
12437
+ if (els.mcpAddBtn) els.mcpAddBtn.addEventListener('click', () => {
12438
+ if (!els.mcpJson) return;
12439
+ try {
12440
+ const obj = JSON.parse(els.mcpJson.value || '{}') || {};
12441
+ obj.servers = obj.servers || {};
12442
+ let i = 1;
12443
+ let name = 'yeni-server';
12444
+ while (obj.servers[name]) name = 'yeni-server' + (++i);
12445
+ obj.servers[name] = { command: 'npx', args: ['-y', '@modelcontextprotocol/server-filesystem', 'C:/'], enabled: false };
12446
+ els.mcpJson.value = JSON.stringify(obj, null, 2);
12447
+ els.mcpJson.focus();
12448
+ if (els.mcpMsg) {
12449
+ els.mcpMsg.textContent = 'örnek eklendi — komutu düzenleyip Kaydet';
12450
+ els.mcpMsg.style.color = '';
12451
+ }
12452
+ } catch {}
12453
+ });
12454
+
12273
12455
  /* ---------- ÇALIŞTIRICI v2: butonlar DETERMİNİSTİK ----------
12274
12456
  Proje tipi algılanır (package.json/requirements.txt/pyproject/Cargo.toml/
12275
- go.mod/index.html) ve Kur/Başlat/Dev butonları algılanan komutu DOĞRUDAN
12276
- çalıştırır; süreç panelde canlı akar, repo başına ayrı yuvada koşar. */
12457
+ go.mod/hugo/jekyll/mkdocs/index.html); Başlat/Dev gerekirse bağımlılıkları
12458
+ ÖNCE kurar (npm install && npm run dev gibi), süreç panelde canlı akar. */
12277
12459
  async function sbRunLaunch(mode) {
12278
12460
  if (!sbFolder) { toast('Önce repo indir ya da seç'); return; }
12279
12461
  if (sbRunRunning) { toast('Bu repoda süreç çalışıyor — önce ■ Durdur'); return; }
12280
- const call = mode === 'install'
12281
- ? beast.sandboxInstall({ folder: sbFolder })
12282
- : beast.sandboxStart({ folder: sbFolder, mode: mode === 'dev' ? 'dev' : 'run' });
12283
- const r = await call.catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
12462
+ sbAutoAssist = mode;
12463
+ const r = await beast.sandboxStart({ folder: sbFolder, mode: mode === 'dev' ? 'dev' : 'run' })
12464
+ .catch((e) => ({ ok: false, error: String((e && e.message) || e) }));
12284
12465
  if (!r || !r.ok) {
12285
- sbRunLine('[hata] ' + ((r && r.error) || 'başlatılamadı'), 't-err');
12466
+ sbAutoAssist = null;
12467
+ const err = (r && r.error) || 'başlatılamadı';
12468
+ sbRunLine('[hata] ' + err, 't-err');
12469
+ /* algılama komut bulamadıysa ajan devralır: repo yapısından doğru komutu bulur */
12470
+ if (/algılanamadı/.test(err)) sbAskAgent(mode);
12286
12471
  return;
12287
12472
  }
12288
12473
  sbRunRunning = true;
@@ -12332,24 +12517,97 @@ function sbRunIngest(ev) {
12332
12517
  if (els.sbRunOpen) els.sbRunOpen.disabled = !sbRunUrl;
12333
12518
  if (els.sbRunUrl) els.sbRunUrl.textContent = sbRunUrl;
12334
12519
  sbBadge(folder || sbFolder, true);
12335
- /* sağdaki dahili tarayıcıda otomatik açılır — sandbox modundayken */
12336
- if (sbModeOn() && sbRunUrl) beast.sandboxOpenUrl(sbRunUrl).catch(() => {});
12520
+ /* ÇALIŞTIR panelinde canlı önizleme otomatik açılır */
12521
+ sbPreviewApply();
12337
12522
  return;
12338
12523
  }
12339
12524
  if (ev.type === 'sb-run-end') {
12340
12525
  sbRunRunning = false;
12341
12526
  if (els.sbRunStop) els.sbRunStop.hidden = true;
12342
12527
  sbBadge(folder || sbFolder, false);
12528
+ const assist = sbAutoAssist;
12529
+ sbAutoAssist = null;
12530
+ if (ev.quickFail && assist) {
12531
+ const kind = ev.kind === 'install' ? 'install' : 'run';
12532
+ sbRunLine('[hata] komut başarısız bitti — ajan doğru komutu arıyor…', 't-err');
12533
+ sbAskAgent(kind);
12534
+ }
12535
+ }
12536
+ }
12537
+
12538
+ /* ---------- ÇALIŞTIR içi CANLI ÖNİZLEME: DOM dikdörtgenini main'e bildir ---------- */
12539
+ function sbPreviewLayout() {
12540
+ if (!sbPreviewShown || sbPreviewSuspend) return;
12541
+ const host = document.getElementById('sbPreviewHost');
12542
+ if (!host) return;
12543
+ const r = host.getBoundingClientRect();
12544
+ if (!r.width || !r.height) return;
12545
+ beast.sbPreviewSet({
12546
+ url: sbRunUrl,
12547
+ visible: true,
12548
+ bounds: { x: r.left, y: r.top, width: r.width, height: r.height },
12549
+ }).catch(() => {});
12550
+ }
12551
+
12552
+ function sbPreviewApply() {
12553
+ const show = sbPreviewOn && sbModeOn() && !!sbFolder && !!sbRunUrl && !sbPreviewSuspend;
12554
+ sbPreviewShown = show;
12555
+ const host = document.getElementById('sbPreviewHost');
12556
+ if (host) host.hidden = !show;
12557
+ if (els.sbRunOut) els.sbRunOut.hidden = show;
12558
+ if (show) {
12559
+ sbPreviewLayout();
12560
+ /* görünür olunca yerleşim oturana kadar birkaç kez tazele */
12561
+ setTimeout(sbPreviewLayout, 50);
12562
+ setTimeout(sbPreviewLayout, 250);
12563
+ } else {
12564
+ beast.sbPreviewSet({ visible: false }).catch(() => {});
12565
+ }
12566
+ }
12567
+
12568
+ function sbPreviewToggle() {
12569
+ sbPreviewOn = !sbPreviewOn;
12570
+ if (els.sbRunView) {
12571
+ els.sbRunView.classList.toggle('on', sbPreviewOn);
12572
+ els.sbRunView.textContent = sbPreviewOn ? 'Görsel' : 'Çıktı';
12573
+ els.sbRunView.title = sbPreviewOn ? 'Görsel önizleme açık — çıktıya dön' : 'Çıktı görünümü — görsele dön';
12343
12574
  }
12575
+ sbPreviewApply();
12576
+ }
12577
+
12578
+ /* Başlat/Dev başarısız olunca ajanı devreye sokar: repo yapısına göre
12579
+ DOĞRU komutu bulup panel_run ile başlatmasını ister (otomatik devralma). */
12580
+ function sbAskAgent(kind) {
12581
+ if (!sbFolder) return;
12582
+ const repoName = (sbItems.find((x) => x.folder === sbFolder) || {}).name || 'repo';
12583
+ const msg = kind === 'install'
12584
+ ? `Kur butonu bu repoda başarısız oldu. ${repoName} reposunun dosyalarını incele (README, requirements.txt, package.json, pyproject.toml, Gemfile, mkdocs/hugo ayarları, alt klasörler) ve DOĞRU kurulum komutunu bulup panel_run ile ÇALIŞTIR panelinde çalıştır. Sonucu tek cümleyle bildir.`
12585
+ : `Başlat/Dev butonu bu repoda başarısız oldu. ${repoName} reposunun yapısını incele (README, package.json scripts, hugo/mkdocs/jekyll ayarları, main/app.py, Cargo.toml, go.mod…) ve GERÇEK çalıştırma komutunu bulup panel_run ile ÇALIŞTIR panelinde başlat. Web adresi çıkarsa kullanıcıya bildir.`;
12586
+ sbLine('t-sys', '⚙ ajan devrede — doğru komutu bulup başlatacak…');
12587
+ sbSetBusy(true);
12588
+ beast.sandboxSend({ folder: sbFolder, msg }).then((r) => {
12589
+ if (r && r.ok) {
12590
+ if (r.sessionId) sbSessionId = r.sessionId;
12591
+ if (r.queued) sbLine('t-dim', '⏳ kuyrukta (' + r.count + ') — iş bitince gönderilir');
12592
+ } else {
12593
+ sbSetBusy(false);
12594
+ sbLine('t-err', (r && r.error) || 'gönderilemedi');
12595
+ }
12596
+ }).catch((e) => {
12597
+ sbSetBusy(false);
12598
+ sbLine('t-err', String((e && e.message) || e));
12599
+ });
12344
12600
  }
12345
12601
 
12346
12602
  function sbRunExec(cmd) {
12347
12603
  if (!sbFolder) { toast('Önce repo indir ya da seç'); return; }
12348
12604
  if (sbRunRunning) { toast('Zaten çalışıyor — önce ■ ile durdur'); return; }
12349
12605
  sbRunRunning = true;
12606
+ sbAutoAssist = 'run';
12350
12607
  if (els.sbRunStop) els.sbRunStop.hidden = false;
12351
12608
  beast.sandboxRun({ folder: sbFolder, cmd }).then((r) => {
12352
12609
  if (!r || !r.ok) {
12610
+ sbAutoAssist = null;
12353
12611
  sbRunRunning = false;
12354
12612
  if (els.sbRunStop) els.sbRunStop.hidden = true;
12355
12613
  sbRunLine('[hata] ' + ((r && r.error) || 'çalıştırılamadı'), 't-err');
@@ -12357,6 +12615,7 @@ function sbRunExec(cmd) {
12357
12615
  sbBadge(sbFolder, true);
12358
12616
  }
12359
12617
  }).catch((e) => {
12618
+ sbAutoAssist = null;
12360
12619
  sbRunRunning = false;
12361
12620
  if (els.sbRunStop) els.sbRunStop.hidden = true;
12362
12621
  sbRunLine('[hata] ' + String((e && e.message) || e), 't-err');
@@ -12412,6 +12671,10 @@ if (els.sbInput) {
12412
12671
  }
12413
12672
  });
12414
12673
  }
12674
+ if (els.sbSend) els.sbSend.addEventListener('click', () => {
12675
+ sbRunCurrent();
12676
+ if (els.sbInput) els.sbInput.focus();
12677
+ });
12415
12678
  if (els.sbStop) {
12416
12679
  els.sbStop.addEventListener('click', () => {
12417
12680
  beast.sandboxStop({ folder: sbFolder }).then((r) => {
@@ -12422,8 +12685,7 @@ if (els.sbStop) {
12422
12685
  }).catch(() => {});
12423
12686
  });
12424
12687
  }
12425
- /* ÇALIŞTIRICI butonları: algılanan komutu doğrudan çalıştırır */
12426
- if (els.sbRunInstall) els.sbRunInstall.addEventListener('click', () => sbRunLaunch('install'));
12688
+ /* ÇALIŞTIRICI butonları: Başlat/Dev algılanan komutu (gerekirse kurup) çalıştırır */
12427
12689
  if (els.sbRunStart) els.sbRunStart.addEventListener('click', () => sbRunLaunch('run'));
12428
12690
  if (els.sbRunDev) els.sbRunDev.addEventListener('click', () => sbRunLaunch('dev'));
12429
12691
  if (els.sbRunGo) els.sbRunGo.addEventListener('click', () => {
@@ -12475,3 +12737,75 @@ if (els.sbRunOpen) {
12475
12737
  if (!r || !r.ok) toast('Açılamadı: ' + ((r && r.error) || 'bilinmeyen hata'));
12476
12738
  });
12477
12739
  }
12740
+ if (els.sbRunView) els.sbRunView.addEventListener('click', sbPreviewToggle);
12741
+
12742
+ /* görsel önizleme boyutu her yerleşim değişiminde main'e bildirilir
12743
+ (ayırıcı, tarayıcı dock'u, pencere boyutu, mod geçişi) */
12744
+ if (window.ResizeObserver) {
12745
+ const host = document.getElementById('sbPreviewHost');
12746
+ if (host) new ResizeObserver(() => sbPreviewLayout()).observe(host);
12747
+ }
12748
+
12749
+ /* ---------- sohbet/çalıştır ayırıcı (Beast Code ideSplit ile aynı desen) ----------
12750
+ Orantılı model: kaydedilen değer sohbet panelinin sandboxRow içindeki PAYI (0-1);
12751
+ sürükle-bırak anında --sbSplit yazar, bırakınca localStorage'a kaydeder. */
12752
+ let sbSplitFrac = 0;
12753
+
12754
+ function sbSplitApplyFrac() {
12755
+ const rowW = els.sandboxRow && els.sandboxRow.clientWidth ? els.sandboxRow.clientWidth : window.innerWidth - 250;
12756
+ /* kayıt yoksa eski varsayılan görünüm korunur: sohbet 480px, çalıştır kalanı */
12757
+ if (!sbSplitFrac) {
12758
+ const w = Math.max(280, Math.min(rowW - 480, Math.max(320, rowW - 300)));
12759
+ document.body.style.setProperty('--sbSplit', Math.round(w) + 'px');
12760
+ return;
12761
+ }
12762
+ sbSplitFrac = Math.max(0.2, Math.min(sbSplitFrac, 0.8));
12763
+ const w = Math.max(280, Math.min(Math.round(rowW * sbSplitFrac), Math.max(320, rowW - 300)));
12764
+ document.body.style.setProperty('--sbSplit', w + 'px');
12765
+ }
12766
+
12767
+ function sbSplitSave() {
12768
+ try { localStorage.setItem('beast.sbSplit', String(sbSplitFrac)); } catch {}
12769
+ }
12770
+
12771
+ function sbSplitRestore() {
12772
+ let saved = 0;
12773
+ try { saved = parseFloat(localStorage.getItem('beast.sbSplit')) || 0; } catch {}
12774
+ sbSplitFrac = saved > 0.05 && saved < 0.95 ? saved : 0;
12775
+ sbSplitApplyFrac();
12776
+ }
12777
+
12778
+ if (els.sbSplit) {
12779
+ els.sbSplit.addEventListener('mousedown', (e) => {
12780
+ e.preventDefault();
12781
+ els.sbSplit.classList.add('dragging');
12782
+ /* native önizleme view'i fare olaylarını yutabilir — sürükleme boyunca gizle,
12783
+ bırakınca geri getir (IDE'deki ayırıcı da aynı nedenle DOM üstünde çizilir) */
12784
+ sbPreviewSuspend = true;
12785
+ if (sbPreviewShown) beast.sbPreviewSet({ visible: false }).catch(() => {});
12786
+ const rightEdge = els.sbRun ? els.sbRun.getBoundingClientRect().right : window.innerWidth;
12787
+ const rowW = Math.max(600, els.sandboxRow && els.sandboxRow.clientWidth ? els.sandboxRow.clientWidth : window.innerWidth - 250);
12788
+ const move = (ev) => {
12789
+ const w = Math.max(280, Math.min(rightEdge - ev.clientX, rowW - 300));
12790
+ sbSplitFrac = Math.max(0.2, Math.min(w / rowW, 0.8));
12791
+ document.body.style.setProperty('--sbSplit', Math.round(w) + 'px');
12792
+ sbPreviewLayout();
12793
+ };
12794
+ const up = () => {
12795
+ document.removeEventListener('mousemove', move);
12796
+ document.removeEventListener('mouseup', up);
12797
+ els.sbSplit.classList.remove('dragging');
12798
+ sbSplitSave();
12799
+ sbPreviewSuspend = false;
12800
+ sbPreviewApply();
12801
+ };
12802
+ document.addEventListener('mousemove', move);
12803
+ document.addEventListener('mouseup', up);
12804
+ });
12805
+ window.addEventListener('resize', () => {
12806
+ if (sbModeOn()) {
12807
+ sbSplitApplyFrac();
12808
+ sbPreviewLayout();
12809
+ }
12810
+ });
12811
+ }
@@ -4271,7 +4271,7 @@ body.sandbox-mode #sandboxRow { display: flex; flex-direction: row; }
4271
4271
  flex-direction: column;
4272
4272
  min-height: 0;
4273
4273
  min-width: 300px;
4274
- flex: 0 0 var(--sbSplit, 480px);
4274
+ flex: 1 1 auto;
4275
4275
  background: var(--bg);
4276
4276
  overflow: hidden;
4277
4277
  }
@@ -4280,10 +4280,20 @@ body.sandbox-mode #sandboxRow { display: flex; flex-direction: row; }
4280
4280
  flex-direction: column;
4281
4281
  min-height: 0;
4282
4282
  min-width: 240px;
4283
- flex: 1;
4283
+ flex: 0 0 var(--sbSplit, 480px);
4284
4284
  background: var(--bg);
4285
4285
  overflow: hidden;
4286
- border-left: 1px solid var(--border);
4286
+ }
4287
+ #sbSplit {
4288
+ flex: none;
4289
+ width: 6px;
4290
+ cursor: col-resize;
4291
+ background: var(--border);
4292
+ opacity: 0.55;
4293
+ transition: opacity 0.15s;
4294
+ }
4295
+ #sbSplit:hover {
4296
+ opacity: 1;
4287
4297
  }
4288
4298
 
4289
4299
  #sbHead {
@@ -4394,9 +4404,31 @@ body.sandbox-mode #sandboxRow { display: flex; flex-direction: row; }
4394
4404
  padding: 7px 9px;
4395
4405
  border-radius: 8px;
4396
4406
  font-family: inherit;
4407
+ overflow-y: hidden;
4408
+ max-height: 140px;
4409
+ line-height: 1.45;
4410
+ display: block;
4397
4411
  }
4398
4412
  #sbInput:focus { border-color: var(--accent); outline: none; }
4399
4413
  #sbInput.expand { overflow-y: auto; }
4414
+ #sbInputRow button {
4415
+ flex: none;
4416
+ background: var(--panel2);
4417
+ border: 1px solid var(--border);
4418
+ color: var(--text);
4419
+ border-radius: 7px;
4420
+ cursor: pointer;
4421
+ font-size: 11px;
4422
+ padding: 6px 9px;
4423
+ line-height: 1;
4424
+ margin-bottom: 1px;
4425
+ }
4426
+ #sbInputRow button:hover { border-color: var(--accent); }
4427
+ #sbSend { color: var(--accent); }
4428
+ #sbSend:hover { background: var(--accent-dim); }
4429
+ #sbStop { color: var(--err); }
4430
+ #sbStop[hidden] { display: none; }
4431
+ #sbStop:hover { border-color: var(--err); }
4400
4432
 
4401
4433
  /* ---------- ÇALIŞTIR bölmesi ---------- */
4402
4434
  #sbRunHead {
@@ -4430,6 +4462,233 @@ body.sandbox-mode #sandboxRow { display: flex; flex-direction: row; }
4430
4462
  text-overflow: ellipsis;
4431
4463
  white-space: nowrap;
4432
4464
  }
4465
+ #sbRunHead button {
4466
+ flex: none;
4467
+ background: var(--panel2);
4468
+ border: 1px solid var(--border);
4469
+ color: var(--muted);
4470
+ font-size: 11px;
4471
+ padding: 3px 9px;
4472
+ border-radius: 6px;
4473
+ cursor: pointer;
4474
+ }
4475
+ #sbRunHead button:hover { color: var(--text); border-color: var(--accent); }
4476
+ #sbRunHead button.on { color: var(--accent); border-color: var(--accent); }
4477
+ #sbPreviewHost {
4478
+ flex: 1;
4479
+ min-height: 0;
4480
+ display: flex;
4481
+ align-items: center;
4482
+ justify-content: center;
4483
+ background: var(--bg);
4484
+ }
4485
+ #sbPreviewHost[hidden] { display: none; }
4486
+ #sbPreviewHint {
4487
+ color: var(--muted);
4488
+ font-size: 12px;
4489
+ padding: 0 16px;
4490
+ text-align: center;
4491
+ }
4492
+
4493
+ /* ---------- MCP MODU ---------- */
4494
+ body.mcp-mode #chatScroll,
4495
+ body.mcp-mode #composerWrap,
4496
+ body.mcp-mode #ideRow,
4497
+ body.mcp-mode #studioRow,
4498
+ body.mcp-mode #sandboxRow { display: none; }
4499
+
4500
+ #mcpRow { display: none; flex: 1; min-height: 0; }
4501
+ body.mcp-mode #mcpRow { display: flex; flex-direction: row; }
4502
+
4503
+ #mcpPanel {
4504
+ display: flex;
4505
+ flex-direction: column;
4506
+ min-height: 0;
4507
+ min-width: 280px;
4508
+ flex: 0 0 380px;
4509
+ background: var(--bg);
4510
+ overflow: hidden;
4511
+ }
4512
+ #mcpDetail {
4513
+ display: flex;
4514
+ flex-direction: column;
4515
+ flex: 1;
4516
+ min-width: 0;
4517
+ min-height: 0;
4518
+ background: var(--bg);
4519
+ border-left: 1px solid var(--border);
4520
+ }
4521
+ #mcpHead {
4522
+ flex: none;
4523
+ display: flex;
4524
+ align-items: center;
4525
+ gap: 8px;
4526
+ padding: 9px 12px;
4527
+ border-bottom: 1px solid var(--border);
4528
+ background: var(--panel);
4529
+ }
4530
+ #mcpTitle { font-size: 11px; font-weight: 800; letter-spacing: 1px; color: var(--accent); flex: none; }
4531
+ #mcpHead button {
4532
+ background: none;
4533
+ border: none;
4534
+ color: var(--muted);
4535
+ font-size: 14px;
4536
+ cursor: pointer;
4537
+ padding: 2px 7px;
4538
+ border-radius: 6px;
4539
+ }
4540
+ #mcpHead button:hover { background: var(--panel2); color: var(--text); }
4541
+ #mcpList {
4542
+ flex: 1;
4543
+ min-height: 0;
4544
+ overflow-y: auto;
4545
+ padding: 10px 12px;
4546
+ }
4547
+ #mcpFoot {
4548
+ flex: none;
4549
+ padding: 8px 12px;
4550
+ border-top: 1px solid var(--border);
4551
+ background: var(--panel);
4552
+ }
4553
+ #mcpFoot button {
4554
+ width: 100%;
4555
+ background: var(--panel2);
4556
+ border: 1px solid var(--border);
4557
+ color: var(--text);
4558
+ font-size: 12px;
4559
+ padding: 6px 10px;
4560
+ border-radius: 7px;
4561
+ cursor: pointer;
4562
+ }
4563
+ #mcpFoot button:hover { border-color: var(--accent); }
4564
+
4565
+ .mcp-card {
4566
+ border: 1px solid var(--border);
4567
+ border-radius: 9px;
4568
+ padding: 9px 10px;
4569
+ margin-bottom: 8px;
4570
+ background: var(--panel);
4571
+ cursor: pointer;
4572
+ }
4573
+ .mcp-card:hover { border-color: var(--accent); }
4574
+ .mcp-card.sel { border-color: var(--accent); background: var(--panel2); }
4575
+ .mcp-card .mc-top { display: flex; align-items: center; gap: 7px; }
4576
+ .mcp-card .mc-name { font-weight: 700; font-size: 13px; }
4577
+ .mcp-card .mc-cmd {
4578
+ font-family: Consolas, monospace;
4579
+ font-size: 11px;
4580
+ color: var(--muted);
4581
+ margin-top: 4px;
4582
+ overflow: hidden;
4583
+ text-overflow: ellipsis;
4584
+ white-space: nowrap;
4585
+ }
4586
+ .mcp-card .mc-err {
4587
+ color: var(--err);
4588
+ font-size: 11px;
4589
+ margin-top: 4px;
4590
+ overflow: hidden;
4591
+ text-overflow: ellipsis;
4592
+ white-space: nowrap;
4593
+ }
4594
+ .mcp-card .mc-actions { display: flex; gap: 6px; margin-top: 7px; }
4595
+ .mcp-card .mc-actions button {
4596
+ background: var(--panel2);
4597
+ border: 1px solid var(--border);
4598
+ color: var(--text);
4599
+ font-size: 11px;
4600
+ padding: 3px 9px;
4601
+ border-radius: 6px;
4602
+ cursor: pointer;
4603
+ }
4604
+ .mcp-card .mc-actions button:hover { border-color: var(--accent); }
4605
+
4606
+ .mcp-badge {
4607
+ flex: none;
4608
+ font-size: 9.5px;
4609
+ font-weight: 800;
4610
+ letter-spacing: 0.5px;
4611
+ padding: 1px 6px;
4612
+ border-radius: 4px;
4613
+ border: 1px solid var(--border);
4614
+ color: var(--muted);
4615
+ text-transform: uppercase;
4616
+ }
4617
+ .mcp-badge.up { color: #22c55e; border-color: #22c55e; }
4618
+ .mcp-badge.down { color: var(--err); border-color: var(--err); }
4619
+ .mcp-badge.starting { color: var(--accent); border-color: var(--accent); }
4620
+
4621
+ #mcpDetailHead {
4622
+ flex: none;
4623
+ display: flex;
4624
+ align-items: center;
4625
+ gap: 10px;
4626
+ padding: 9px 14px;
4627
+ border-bottom: 1px solid var(--border);
4628
+ background: var(--panel);
4629
+ }
4630
+ #mcpDetailTitle { font-weight: 700; font-size: 13px; }
4631
+ #mcpDetailState { margin-left: auto; }
4632
+ #mcpDetailBody { flex: 1; min-height: 0; overflow-y: auto; padding: 12px 14px; }
4633
+ .mcp-drow { display: flex; gap: 8px; margin-bottom: 6px; font-size: 12.5px; }
4634
+ .mcp-dk { color: var(--muted); min-width: 76px; flex: none; }
4635
+ .mcp-tools { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
4636
+ .mcp-tool {
4637
+ font-family: Consolas, monospace;
4638
+ font-size: 11px;
4639
+ border: 1px solid var(--border);
4640
+ background: var(--panel2);
4641
+ padding: 2px 7px;
4642
+ border-radius: 6px;
4643
+ }
4644
+ #mcpJsonWrap {
4645
+ flex: none;
4646
+ display: flex;
4647
+ flex-direction: column;
4648
+ max-height: 40%;
4649
+ border-top: 1px solid var(--border);
4650
+ background: var(--panel);
4651
+ }
4652
+ #mcpJsonHead {
4653
+ flex: none;
4654
+ display: flex;
4655
+ align-items: center;
4656
+ gap: 8px;
4657
+ padding: 7px 12px;
4658
+ font-size: 11px;
4659
+ color: var(--muted);
4660
+ }
4661
+ #mcpJsonPath {
4662
+ max-width: 45%;
4663
+ overflow: hidden;
4664
+ text-overflow: ellipsis;
4665
+ white-space: nowrap;
4666
+ font-family: Consolas, monospace;
4667
+ }
4668
+ #mcpSave {
4669
+ background: var(--panel2);
4670
+ border: 1px solid var(--border);
4671
+ color: var(--accent);
4672
+ font-size: 11px;
4673
+ padding: 3px 10px;
4674
+ border-radius: 6px;
4675
+ cursor: pointer;
4676
+ }
4677
+ #mcpSave:hover { border-color: var(--accent); }
4678
+ #mcpJson {
4679
+ flex: 1;
4680
+ min-height: 110px;
4681
+ resize: none;
4682
+ background: var(--bg);
4683
+ border: none;
4684
+ border-top: 1px solid var(--border);
4685
+ color: var(--text);
4686
+ font-family: Consolas, monospace;
4687
+ font-size: 12px;
4688
+ padding: 8px 12px;
4689
+ outline: none;
4690
+ white-space: pre;
4691
+ }
4433
4692
  #sbRunBtns {
4434
4693
  flex: none;
4435
4694
  display: flex;