beast-agent 0.20.0 → 0.22.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/src/main.js CHANGED
@@ -193,6 +193,7 @@ const BUILTIN_PROVIDERS = [
193
193
  { id: 'gemini', name: 'Google Gemini', baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai', hint: 'aistudio.google.com/apikey' },
194
194
  { id: 'zhipu', name: 'Zhipu AI', baseUrl: 'https://api.z.ai/api/paas/v4', hint: 'z.ai model konsolu' },
195
195
  { id: 'groq', name: 'Groq', baseUrl: 'https://api.groq.com/openai/v1', hint: 'console.groq.com/keys' },
196
+ { id: 'nvidia', name: 'NVIDIA NIM', baseUrl: 'https://integrate.api.nvidia.com/v1', hint: 'build.nvidia.com — ücretsiz kredi veriyor, talep yüksek' },
196
197
  ];
197
198
  const SESSIONS_DIR = path.join(APP_DIR, 'sessions');
198
199
  const SETTINGS_FILE = path.join(APP_DIR, 'settings.json');
@@ -1333,13 +1334,37 @@ function scheduleReminder({ when, message, sessionId, repeat }) {
1333
1334
  /* ---------------- BOT SİSTEMİ yardımcıları ----------------
1334
1335
  Bot eşleştirme, izolasyon, whitelist.json aynası ve bot istatistikleri. */
1335
1336
 
1336
- const PERM_RANK = { all: 3, web: 2, read: 1, chat: 0 }; // küçük = kısıtlı
1337
+ /* İzin değerini araç kümesine çevirir; null = tüm araçlar ('all').
1338
+ 'web' / ['web','read'] / 'web,read' biçimlerini kabul eder. */
1339
+ function permToToolSet(p) {
1340
+ const { PERM_TOOL_SETS } = require('./agent/engine');
1341
+ const set = new Set();
1342
+ for (const raw of Array.isArray(p) ? p : String(p == null ? 'all' : p).split(',')) {
1343
+ const k = String(raw).trim() || 'all';
1344
+ if (k === 'all' || !PERM_TOOL_SETS[k]) return null;
1345
+ for (const t of PERM_TOOL_SETS[k]) set.add(t);
1346
+ }
1347
+ return set;
1348
+ }
1337
1349
 
1350
+ /* Hangi izin daha kısıtlıysa o kazanır. Dizi (çoklu bot izni) destekler:
1351
+ araç kümesi diğerinin alt kümesiyse o geçer; ikisi de alt küme değilse
1352
+ daha küçük küme kazanır (eşitse kişi yetkisi). */
1338
1353
  function moreRestrictivePerm(a, b) {
1339
- const ra = PERM_RANK[a] ?? 3;
1340
- const rb = PERM_RANK[b] ?? 3;
1341
- const keys = Object.keys(PERM_RANK);
1342
- return keys.find((k) => PERM_RANK[k] === Math.min(ra, rb)) || 'all';
1354
+ const sa = permToToolSet(a);
1355
+ const sb = permToToolSet(b);
1356
+ if (sa === null && sb === null) return 'all';
1357
+ if (sa === null) return b; // b kısıtlı
1358
+ if (sb === null) return a; // a kısıtlı
1359
+ const aSubB = [...sa].every((t) => sb.has(t));
1360
+ const bSubA = [...sb].every((t) => sa.has(t));
1361
+ if (aSubB && !bSubA) return a;
1362
+ if (bSubA && !aSubB) return b;
1363
+ return sa.size <= sb.size ? a : b;
1364
+ }
1365
+
1366
+ function fmtPerm(p) {
1367
+ return Array.isArray(p) ? '[' + p.join('+') + ']' : String(p);
1343
1368
  }
1344
1369
 
1345
1370
  /* Bot skill checkbox'ları → oturumun görebileceği araç adları.
@@ -1623,7 +1648,7 @@ async function processWaMessage(jid, payload, senderNum, requeues = 0) {
1623
1648
  } else {
1624
1649
  engine.setSessionTools(sid, null);
1625
1650
  }
1626
- waLog(`perm=${perm} bot=${botId} sid=${sid}`);
1651
+ waLog(`perm=${fmtPerm(perm)} bot=${botId} sid=${sid}`);
1627
1652
 
1628
1653
  const participantName = payload.participant ? '+' + String(payload.participant).split('@')[0].split(':')[0] : '';
1629
1654
  /* #v13.1 rol: SAHİP vs MİSAFİR — ajan kime konuştuğunu net bilsin */
@@ -2193,9 +2218,9 @@ function setBrowserOpen(v, forceVisible) {
2193
2218
  }
2194
2219
 
2195
2220
  // AÇMA — görünürlük: kullanıcı kendi açtıysa (forceVisible) MUTLAKA görünür;
2196
- // ajan açtıysa göz ikonu tercihine bak (varsayılan: GİZLİ/headless)
2221
+ // ajan açtıysa DA VARSAYILAN GÖRÜNÜR (göz ikonuyla gizli mod seçilmedikçe)
2197
2222
  browser.open = true;
2198
- browser.visible = forceVisible === true ? true : settings.browserHeadless === false;
2223
+ browser.visible = forceVisible === true ? true : settings.browserHeadless !== true;
2199
2224
  ensureBrowser();
2200
2225
  if (!browser.started) {
2201
2226
  browser.started = true;
@@ -2770,7 +2795,8 @@ function createWindow() {
2770
2795
  icon: path.join(__dirname, '..', 'assets', 'app.ico'),
2771
2796
  titleBarStyle: 'hidden',
2772
2797
  titleBarOverlay: {
2773
- color: 'transparent',
2798
+ /* transparent Windows'ta beyaz buton arka planı veriyor — tema rengiyle başlat */
2799
+ color: dark ? '#0d0d0f' : '#f7f7f8',
2774
2800
  symbolColor: dark ? '#9a9aa2' : '#707078',
2775
2801
  height: 46,
2776
2802
  },
@@ -3584,24 +3610,82 @@ ipcMain.handle('update:check', async () => {
3584
3610
  });
3585
3611
 
3586
3612
  /* npm kurulumunda KENDİ KENDİNİ GÜNCELLEME:
3587
- detached helper bırakır (uygulama çıkınca npm install + yeniden başlatma), sonra app.quit() */
3613
+ helper script %APPDATA%\beast'a yazılır (paket dizini değişse de yaşar) ve
3614
+ detached çalışır: 1) uygulama PID'i tamamen çıkana kadar bekler (en çok 30 sn,
3615
+ sonra zorla kapatır — EBUSY dosya kilidinin numarası), 2) npm install -g
3616
+ beast-agent@latest — kilide karşı 5 deneme, 3) tüm çıktı update.log'a yazılır,
3617
+ 4) beast-agent komut shim'i üzerinden yeniden başlatır (electron sürümü
3618
+ değişse de yol bozulmaz). Sonra app.quit(). */
3588
3619
  function npmSelfUpdate() {
3589
3620
  try {
3590
- const electronExe = process.execPath;
3591
- const appPath = app.getAppPath();
3621
+ fs.mkdirSync(APP_DIR, { recursive: true });
3622
+ const pid = String(process.pid);
3592
3623
  if (process.platform === 'win32') {
3593
- const ps =
3594
- 'Start-Sleep -Seconds 3;' +
3595
- 'npm install -g beast-agent@latest;' +
3596
- 'Start-Sleep -Seconds 1;' +
3597
- `Start-Process -FilePath '${electronExe}' -ArgumentList '\"${appPath}\"'`;
3598
- spawn('powershell.exe', ['-NoProfile', '-Command', ps], { detached: true, stdio: 'ignore', windowsHide: true }).unref();
3624
+ const ps = [
3625
+ "param([int]$ProcId = 0)",
3626
+ "$ErrorActionPreference = 'Continue'",
3627
+ "$Log = Join-Path $env:APPDATA 'beast\\update.log'",
3628
+ "function L([string]$m) { try { Add-Content -LiteralPath $Log -Value (\"[\" + (Get-Date -Format 'yyyy-MM-dd HH:mm:ss') + \"] \" + $m) } catch {} }",
3629
+ "L \"=== self-update basladi (app pid: $ProcId) ===\"",
3630
+ "if ($ProcId -gt 0) {",
3631
+ " $deadline = (Get-Date).AddSeconds(30)",
3632
+ " while ((Get-Date) -lt $deadline) {",
3633
+ " if (-not (Get-Process -Id $ProcId -ErrorAction SilentlyContinue)) { break }",
3634
+ " Start-Sleep -Milliseconds 500",
3635
+ " }",
3636
+ " if (Get-Process -Id $ProcId -ErrorAction SilentlyContinue) {",
3637
+ " L 'uygulama hala acik - zorla kapatiliyor'",
3638
+ " try { Stop-Process -Id $ProcId -Force } catch {}",
3639
+ " Start-Sleep -Seconds 2",
3640
+ " }",
3641
+ "}",
3642
+ "L 'uygulama kapandi, npm install basliyor'",
3643
+ "$npmCmd = (Get-Command 'npm.cmd' -ErrorAction SilentlyContinue).Source",
3644
+ "if (-not $npmCmd) { $npmCmd = Join-Path $env:APPDATA 'npm\\npm.cmd' }",
3645
+ "$ok = $false",
3646
+ "for ($i = 1; $i -le 5; $i++) {",
3647
+ " if ($ok) { break }",
3648
+ " L \"npm install -g beast-agent@latest (deneme $i)\"",
3649
+ " $out = & $npmCmd install -g beast-agent@latest 2>&1",
3650
+ " $code = $LASTEXITCODE",
3651
+ " foreach ($line in @($out)) { L \" npm: $line\" }",
3652
+ " if ($code -eq 0) { $ok = $true } else { Start-Sleep -Seconds 3 }",
3653
+ "}",
3654
+ "if (-not $ok) {",
3655
+ " L 'HATA: npm install 5 denemede basarisiz - uygulama yeniden baslatilmiyor'",
3656
+ " exit 1",
3657
+ "}",
3658
+ "L 'npm install tamam, yeniden baslatma'",
3659
+ "$shim = Get-Command 'beast-agent.cmd' -ErrorAction SilentlyContinue",
3660
+ "if ($shim) {",
3661
+ " L \"shim uzerinden: $($shim.Source)\"",
3662
+ " Start-Process -FilePath $shim.Source -WindowStyle Hidden",
3663
+ "} else {",
3664
+ " $prefix = (& $npmCmd prefix -g 2>$null)",
3665
+ " if (-not $prefix) { $prefix = Join-Path $env:APPDATA 'npm' }",
3666
+ " $exe = Join-Path $prefix 'node_modules\\electron\\dist\\electron.exe'",
3667
+ " if (-not (Test-Path $exe)) { $exe = Join-Path $prefix 'node_modules\\beast-agent\\node_modules\\electron\\dist\\electron.exe' }",
3668
+ " $appDir = Join-Path $prefix 'node_modules\\beast-agent'",
3669
+ " L \"shim bulunamadi - dogrudan: $exe\"",
3670
+ " Start-Process -FilePath $exe -ArgumentList \"`\"$appDir`\"\" -WindowStyle Hidden",
3671
+ "}",
3672
+ "L '=== self-update bitti ==='",
3673
+ ].join('\r\n');
3674
+ const psFile = path.join(APP_DIR, 'update-helper.ps1');
3675
+ fs.writeFileSync(psFile, ps, 'utf8');
3676
+ spawn('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', psFile, '-ProcId', pid],
3677
+ { detached: true, stdio: 'ignore', windowsHide: true }).unref();
3599
3678
  } else {
3600
- const sh = `sleep 3; npm install -g beast-agent@latest; sleep 1; '${electronExe}' '${appPath}' &`;
3679
+ const sh =
3680
+ `i=0; while [ $i -lt 60 ] && kill -0 ${pid} 2>/dev/null; do i=$((i+1)); sleep 0.5; done; ` +
3681
+ 'ok=0; for n in 1 2 3 4 5; do npm install -g beast-agent@latest && ok=1 && break; sleep 3; done; ' +
3682
+ 'if [ $ok -eq 1 ]; then nohup beast-agent >/dev/null 2>&1 & fi';
3601
3683
  spawn('sh', ['-c', sh], { detached: true, stdio: 'ignore' }).unref();
3602
3684
  }
3603
3685
  log.info('main', 'npm self-update: helper bırakıldı, uygulama kapatılıyor');
3604
- } catch {}
3686
+ } catch (e) {
3687
+ log.error('main', 'npm self-update hatası: ' + String((e && e.message) || e));
3688
+ }
3605
3689
  setTimeout(() => { try { app.quit(); } catch {} }, 400);
3606
3690
  }
3607
3691
 
@@ -3993,6 +4077,74 @@ ipcMain.handle('terminal:stop', () => {
3993
4077
  return { ok: true };
3994
4078
  });
3995
4079
 
4080
+ /* ---------------- Beast Code paneli (IDE modu ortası) ----------------
4081
+ IDE modunda ortadaki sohbet yerine Beast'in KENDİ ajanı çalışır:
4082
+ varsayılan model zinciri + soldaki dosya panelindeki klasör.
4083
+ Yazışma SOLDAKİ KLASÖRE BAĞLIDIR: her klasörün kendi gizli engine
4084
+ oturumu vardır (klasör değişince sohbet de değişir); sohbet geçmişi
4085
+ listesine karışmaz. Olayları renderer zaten agent:event ile alır,
4086
+ panele orada akıtılır. */
4087
+ const bcSessions = new Map(); /* klasör yolu → sessionId */
4088
+
4089
+ function bcGetSession(folder) {
4090
+ let sid = bcSessions.get(folder);
4091
+ if (sid) {
4092
+ try {
4093
+ const s = engine.cache.get(sid);
4094
+ if (s) return s;
4095
+ } catch {}
4096
+ bcSessions.delete(folder);
4097
+ }
4098
+ const s = engine._load(engine.createSession().id);
4099
+ s.messages = s.messages || [];
4100
+ s.bgTitle = 'Beast Code'; /* _view.isBg → sohbet geçmişi listesinde gizli */
4101
+ s.bcCode = true; /* engine: her işte todo planı çıkar (BEAST CODE MODU bloğu) */
4102
+ try {
4103
+ fs.appendFileSync(
4104
+ engine._file(s.id),
4105
+ JSON.stringify({ t: 'meta2', bgOf: '', title: 'Beast Code', at: new Date().toISOString() }) + '\n'
4106
+ );
4107
+ } catch {}
4108
+ engine.cache.set(s.id, s);
4109
+ bcSessions.set(folder, s.id);
4110
+ return s;
4111
+ }
4112
+
4113
+ ipcMain.handle('beastcode:send', (_e, payload) => {
4114
+ const text = String((payload && payload.msg) || '').trim();
4115
+ if (!text) return { ok: false, error: 'boş mesaj' };
4116
+ if (!engine) return { ok: false, error: 'ajan hazır değil' };
4117
+ if (!engine.publicState().hasModel) return { ok: false, error: 'model yok — Ayarlar → Provider sekmesinden ekle' };
4118
+ const ws = ideRoot();
4119
+ if (engine.isBusy(bcSessions.get(ws))) return { ok: false, busy: true, error: 'önceki mesaj sürüyor — ■ ile durdurabilirsin' };
4120
+ const s = bcGetSession(ws);
4121
+ s.workspace = ws; /* soldaki klasörde çalış */
4122
+ s.bcCode = true; /* todo disiplini + iş sonu hızlı kapanış (engine) */
4123
+ engine.cache.set(s.id, s);
4124
+ const ok = engine.send(s.id, text);
4125
+ if (!ok) return { ok: false, error: 'mesaj gönderilemedi' };
4126
+ return { ok: true, sessionId: s.id };
4127
+ });
4128
+
4129
+ ipcMain.handle('beastcode:stop', () => {
4130
+ const sid = bcSessions.get(ideRoot());
4131
+ if (!sid) return { ok: false };
4132
+ let r = false;
4133
+ try { r = engine.interrupt(sid); } catch {}
4134
+ return { ok: !!r };
4135
+ });
4136
+
4137
+ ipcMain.handle('beastcode:new', () => {
4138
+ const ws = ideRoot();
4139
+ const sid = bcSessions.get(ws);
4140
+ if (sid && engine.isBusy(sid)) return { ok: false, error: 'mesaj sürüyor — önce ■ ile durdur' };
4141
+ if (sid) {
4142
+ try { engine.deleteSession(sid); } catch {}
4143
+ bcSessions.delete(ws);
4144
+ }
4145
+ return { ok: true };
4146
+ });
4147
+
3996
4148
  /* düşünme (reasoning) seviyesi */
3997
4149
  ipcMain.handle('think:set', (_e, v) => {
3998
4150
  setThinkLevel(v);
@@ -4110,6 +4262,166 @@ ipcMain.handle('store:remove', (_e, id) =>
4110
4262
 
4111
4263
  ipcMain.handle('store:export', (_e, id) => storeMod.exportEntry(id));
4112
4264
 
4265
+ /* ---------- IDE MODU (sol: dosya gezgini · orta: chat · sağ: preview) ---------- */
4266
+
4267
+ const IDE_TEXT_EXT = new Set([
4268
+ '.html', '.htm', '.css', '.js', '.mjs', '.cjs', '.ts', '.jsx', '.tsx', '.json',
4269
+ '.md', '.txt', '.csv', '.py', '.ps1', '.bat', '.cmd', '.sh', '.yaml', '.yml',
4270
+ '.xml', '.ini', '.svg', '.gitignore',
4271
+ ]);
4272
+ const IDE_MAX_BYTES = 400 * 1024;
4273
+
4274
+ function ideRoot() {
4275
+ /* kullanıcı panelde başka klasör seçtiyse o kök alınır; yoksa agent workspace'i */
4276
+ return path.resolve(settings.ideRoot || settings.workspace || app.getPath('home'));
4277
+ }
4278
+
4279
+ ipcMain.handle('ide:setroot', async () => {
4280
+ try {
4281
+ const r = await dialog.showOpenDialog({
4282
+ title: 'Klasör seç — dosya paneli ve preview bu klasörü kullanır',
4283
+ defaultPath: ideRoot(),
4284
+ properties: ['openDirectory'],
4285
+ });
4286
+ if (r.canceled || !r.filePaths || !r.filePaths[0]) return { ok: false, canceled: true };
4287
+ settings.ideRoot = r.filePaths[0];
4288
+ saveSettings();
4289
+ return { ok: true, root: settings.ideRoot };
4290
+ } catch (e) {
4291
+ return { ok: false, error: String((e && e.message) || e) };
4292
+ }
4293
+ });
4294
+
4295
+ /* rel yol → workspace içinde kal (path traversal kilidi) */
4296
+ function ideSafe(rel) {
4297
+ const root = ideRoot();
4298
+ const p = path.resolve(root, String(rel || ''));
4299
+ if (p !== root && !p.startsWith(root + path.sep)) return null;
4300
+ return p;
4301
+ }
4302
+
4303
+ ipcMain.handle('ide:tree', (_e, rel) => {
4304
+ const p = ideSafe(rel);
4305
+ if (!p) return { ok: false, error: 'geçersiz yol' };
4306
+ try {
4307
+ const entries = fs.readdirSync(p, { withFileTypes: true })
4308
+ .filter((e) => e.name !== 'node_modules' && e.name !== '.git')
4309
+ .map((e) => {
4310
+ let size = 0;
4311
+ try { if (e.isFile()) size = fs.statSync(path.join(p, e.name)).size; } catch {}
4312
+ return { name: e.name, dir: e.isDirectory(), size };
4313
+ })
4314
+ .sort((a, b) => (a.dir === b.dir ? a.name.localeCompare(b.name) : a.dir ? -1 : 1));
4315
+ return { ok: true, workspace: ideRoot(), entries };
4316
+ } catch (e) {
4317
+ return { ok: false, error: String((e && e.message) || e) };
4318
+ }
4319
+ });
4320
+
4321
+ ipcMain.handle('ide:read', (_e, rel) => {
4322
+ const p = ideSafe(rel);
4323
+ if (!p) return { ok: false, error: 'geçersiz yol' };
4324
+ const ext = path.extname(p).toLowerCase();
4325
+ if (ext && !IDE_TEXT_EXT.has(ext)) return { ok: false, error: 'metin dosyası değil — düzenlenemez' };
4326
+ try {
4327
+ const st = fs.statSync(p);
4328
+ if (st.isDirectory()) return { ok: false, error: 'klasör' };
4329
+ if (st.size > IDE_MAX_BYTES) return { ok: false, error: 'dosya çok büyük (max 400KB)' };
4330
+ const buf = fs.readFileSync(p);
4331
+ if (buf.slice(0, 4096).includes(0)) return { ok: false, error: 'ikili (binary) dosya' };
4332
+ return { ok: true, content: buf.toString('utf8') };
4333
+ } catch (e) {
4334
+ return { ok: false, error: String((e && e.message) || e) };
4335
+ }
4336
+ });
4337
+
4338
+ ipcMain.handle('ide:write', (_e, p) => {
4339
+ const target = ideSafe(p && p.rel);
4340
+ if (!target) return { ok: false, error: 'geçersiz yol' };
4341
+ const ext = path.extname(target).toLowerCase();
4342
+ if (ext && !IDE_TEXT_EXT.has(ext)) return { ok: false, error: 'metin dosyası değil — yazılamaz' };
4343
+ try {
4344
+ const body = String((p && p.content) ?? '');
4345
+ if (Buffer.byteLength(body, 'utf8') > IDE_MAX_BYTES) return { ok: false, error: 'içerik çok büyük (max 400KB)' };
4346
+ fs.writeFileSync(target, body, 'utf8');
4347
+ return { ok: true };
4348
+ } catch (e) {
4349
+ return { ok: false, error: String((e && e.message) || e) };
4350
+ }
4351
+ });
4352
+
4353
+ /* Sağ tık menüsü: dosya/klasör sil (onay diyaloglu) */
4354
+ ipcMain.handle('ide:delete', async (_e, rel) => {
4355
+ const p = ideSafe(rel);
4356
+ if (!p || p === ideRoot()) return { ok: false, error: 'geçersiz yol' };
4357
+ try {
4358
+ const st = fs.statSync(p);
4359
+ const isDir = st.isDirectory();
4360
+ const owner = win && !win.isDestroyed() ? win : undefined;
4361
+ const r = owner
4362
+ ? await dialog.showMessageBox(owner, {
4363
+ type: 'warning',
4364
+ title: 'Sil',
4365
+ message: `"${rel}" ${isDir ? 'klasörünü (içi dahil)' : 'dosyasını'} silmek istiyor musun?`,
4366
+ buttons: ['Sil', 'Vazgeç'],
4367
+ defaultId: 1,
4368
+ cancelId: 1,
4369
+ })
4370
+ : { response: 1 };
4371
+ if (r.response !== 0) return { ok: false, canceled: true };
4372
+ fs.rmSync(p, { recursive: true, force: true });
4373
+ return { ok: true };
4374
+ } catch (e) {
4375
+ return { ok: false, error: String((e && e.message) || e) };
4376
+ }
4377
+ });
4378
+
4379
+ /* Sağ tık menüsü: HTML dosyasını dahili tarayıcıda GÖRÜNÜR aç */
4380
+ ipcMain.handle('ide:previewFile', (_e, rel) => {
4381
+ try {
4382
+ const p = ideSafe(rel);
4383
+ if (!p) return { ok: false, error: 'geçersiz yol' };
4384
+ if (!/\.html?$/i.test(p)) return { ok: false, error: 'önizleme yalnız .html/.htm dosyaları için' };
4385
+ setBrowserOpen(true, true);
4386
+ const url = 'file:///' + p.replace(/\\/g, '/');
4387
+ browser.view.webContents.loadURL(url).catch(() => {});
4388
+ browserEmit({ open: true, width: browser.width, url });
4389
+ return { ok: true };
4390
+ } catch (e) {
4391
+ return { ok: false, error: String((e && e.message) || e) };
4392
+ }
4393
+ });
4394
+
4395
+ /* PREVIEW: workspace kökündeki entry sayfayı (index.html → ilk *.html) sağdaki
4396
+ dahili tarayıcıda aç. file:// yükleme browserNavigate'i BYPASS eder — o https
4397
+ olmayan adresi aramaya çevirir. */
4398
+ ipcMain.handle('ide:preview', () => { try {
4399
+ const root = ideRoot();
4400
+ const pick = (name) => {
4401
+ const p = path.join(root, name);
4402
+ try { return fs.existsSync(p) ? p : null; } catch { return null; }
4403
+ };
4404
+ let entry = pick('index.html');
4405
+ if (!entry) {
4406
+ const htmls = fs
4407
+ .readdirSync(root, { withFileTypes: true })
4408
+ .filter((e) => e.isFile() && /\.html?$/i.test(e.name))
4409
+ .map((e) => e.name);
4410
+ entry = htmls.length ? path.join(root, htmls.sort()[0]) : null;
4411
+ }
4412
+ if (!entry) return { ok: false, error: 'workspace kökünde index.html yok — önce agent\'a siteyi yazdır' };
4413
+ /* forceVisible: preview'a basınca tarayıcı ikonuna basmaya gerek kalmasın —
4414
+ dahili tarayıcı otomatik ve GÖRÜNÜR açılır */
4415
+ setBrowserOpen(true, true);
4416
+ const url = 'file:///' + entry.replace(/\\/g, '/');
4417
+ browser.view.webContents.loadURL(url).catch(() => {});
4418
+ browserEmit({ open: true, width: browser.width, url });
4419
+ return { ok: true, url };
4420
+ } catch (e) {
4421
+ return { ok: false, error: String((e && e.message) || e) };
4422
+ }
4423
+ });
4424
+
4113
4425
  ipcMain.handle('custom:set', (_e, list) => {
4114
4426
  settings.customProviders = Array.isArray(list) ? list : [];
4115
4427
  saveSettings();
package/src/preload.js CHANGED
@@ -64,6 +64,9 @@ contextBridge.exposeInMainWorld('beast', {
64
64
  terminalToggle: () => ipcRenderer.invoke('terminal:toggle'),
65
65
  terminalRun: (cmd, shell) => ipcRenderer.invoke('terminal:run', { cmd, shell }),
66
66
  terminalStop: () => ipcRenderer.invoke('terminal:stop'),
67
+ beastcodeSend: (msg) => ipcRenderer.invoke('beastcode:send', { msg }),
68
+ beastcodeStop: () => ipcRenderer.invoke('beastcode:stop'),
69
+ beastcodeNew: () => ipcRenderer.invoke('beastcode:new'),
67
70
  thinkSet: (v) => ipcRenderer.invoke('think:set', v),
68
71
  exaGet: () => ipcRenderer.invoke('exa:get'),
69
72
  exaSet: (key) => ipcRenderer.invoke('exa:set', key),
@@ -144,6 +147,13 @@ contextBridge.exposeInMainWorld('beast', {
144
147
  storeLike: (id) => ipcRenderer.invoke('store:like', id),
145
148
  storeRemove: (id) => ipcRenderer.invoke('store:remove', id),
146
149
  storeExport: (id) => ipcRenderer.invoke('store:export', id),
150
+ ideTree: (rel) => ipcRenderer.invoke('ide:tree', rel),
151
+ ideSetRoot: () => ipcRenderer.invoke('ide:setroot'),
152
+ ideRead: (rel) => ipcRenderer.invoke('ide:read', rel),
153
+ ideWrite: (rel, content) => ipcRenderer.invoke('ide:write', { rel, content }),
154
+ idePreview: () => ipcRenderer.invoke('ide:preview'),
155
+ idePreviewFile: (rel) => ipcRenderer.invoke('ide:previewFile', rel),
156
+ ideDelete: (rel) => ipcRenderer.invoke('ide:delete', rel),
147
157
  onWaEvent: (cb) => ipcRenderer.on('wa:event', (_e, ev) => cb(ev)),
148
158
  onEvent: (cb) => ipcRenderer.on('agent:event', (_e, ev) => cb(ev)),
149
159
  });
@@ -73,7 +73,8 @@
73
73
  up_downloading: 'İndiriliyor…',
74
74
  up_downloaded: 'İndirildi — kurulum için hazır',
75
75
  up_npm_mode: 'npm kurulumu — güncellemeler otomatik kontrol edilir',
76
- up_npm_note: 'Yeni sürüm algılanınca buton aktifleşir: uygulama kapanır, güncellenir ve kendiliğinden yeniden açılır. Terminal komutu: beast-agent update',
76
+ up_npm_note: 'Yeni sürüm algılanınca buton aktifleşir: uygulama kapanır, güncellenir ve kendiliğinden yeniden açılır. Terminal komutu: beast update',
77
+ up_npm_started: 'Güncelleme başladı — uygulama kapanacak, en son sürüm kurulup kendiliğinden açılacak…',
77
78
  up_dev_note: 'Geliştirme modu — güncelleme buradan yapılmaz; dağıtım npm ve GitHub Releases üzerinden. Kaynak kod için: git pull',
78
79
  up_auto_check: 'Otomatik sürüm kontrolü (açılış + 6 saatte bir)',
79
80
  up_auto_dl: 'Yeni sürümü otomatik indir (kapanışta kurulur)',
@@ -374,6 +375,7 @@
374
375
  bot_tab_plugin: 'Plugin',
375
376
  bot_tab_watcher: 'Watcher',
376
377
  bot_tab_stats: 'İstatistik',
378
+ bot_tab_notes: 'Notlar',
377
379
  bot_name: 'Bot adı',
378
380
  bot_icon: 'İkon',
379
381
  bot_prompt: 'System prompt (kişilik/görev)',
@@ -396,6 +398,7 @@
396
398
  bot_see: 'Görebilir (diğer botlar)',
397
399
  bot_no_other: 'Başka bot yok.',
398
400
  bot_perm: 'Yetki seviyesi',
401
+ bot_perm_note: 'all tek başına tüm araçları açar (özel). Web / Okuma / Sohbet birden fazla seçilebilir — seçilenlerin araçları birleşir.',
399
402
  bot_skills: 'Skill erişimi',
400
403
  bot_browser: 'Tarayıcı ayarları',
401
404
  bot_ext_browser: 'Dış tarayıcı yetkisi (kullanıcı isterse)',
@@ -451,6 +454,16 @@
451
454
  p_zen_fail: 'Model kurulumu başarısız',
452
455
  bot_num_ro_hint: 'Numara ekleme/kişi adı Ayarlar → Entegrasyonlar (WhatsApp izin listesi) üzerinden yapılır; burada yalnızca bağlı numaralar görünür.',
453
456
  tipStore: 'Skills Store',
457
+ tipIde: 'IDE Modu — dosyalar + chat + preview',
458
+ ide_files: 'DOSYALAR',
459
+ ide_pick: 'Klasör seç',
460
+ ide_refresh: 'Yenile',
461
+ ide_preview: '▶ Preview',
462
+ ide_saved: 'Kaydedildi — preview\u2019ı yenile',
463
+ bc_ph: 'Beast Code mesajı yaz — Enter ile gönder…',
464
+ bc_new: 'Yeni oturum',
465
+ bc_clear: 'Çıktıyı temizle',
466
+ bc_stop: 'Çalışan mesajı durdur',
454
467
  store_title: 'SKILLS STORE',
455
468
  store_tab_trending: 'Trending',
456
469
  store_tab_stars: 'Stars',
@@ -561,7 +574,8 @@
561
574
  up_downloading: 'Downloading…',
562
575
  up_downloaded: 'Downloaded — ready to install',
563
576
  up_npm_mode: 'npm install — updates are checked automatically',
564
- up_npm_note: 'The button activates when a new version is detected: the app closes, updates and relaunches by itself. Terminal command: beast-agent update',
577
+ up_npm_note: 'The button activates when a new version is detected: the app closes, updates and relaunches by itself. Terminal command: beast update',
578
+ up_npm_started: 'Update started — the app will close, install the latest version and relaunch…',
565
579
  up_dev_note: 'Development mode — updates are distributed via npm and GitHub Releases. For source code: git pull',
566
580
  up_auto_check: 'Automatic version check (on startup + every 6 hours)',
567
581
  up_auto_dl: 'Auto-download new versions (installed on quit)',
@@ -920,6 +934,7 @@
920
934
  bot_tab_plugin: 'Plugin',
921
935
  bot_tab_watcher: 'Watcher',
922
936
  bot_tab_stats: 'Stats',
937
+ bot_tab_notes: 'Notes',
923
938
  bot_name: 'Bot name',
924
939
  bot_icon: 'Icon',
925
940
  bot_prompt: 'System prompt (personality/task)',
@@ -942,6 +957,7 @@
942
957
  bot_see: 'Can see (other bots)',
943
958
  bot_no_other: 'No other bots.',
944
959
  bot_perm: 'Permission level',
960
+ bot_perm_note: 'all alone grants every tool (exclusive). Web / Read / Chat can be multi-selected — their tool sets are combined.',
945
961
  bot_skills: 'Skill access',
946
962
  bot_browser: 'Browser settings',
947
963
  bot_ext_browser: 'External browser permission (when user asks)',
@@ -997,6 +1013,16 @@
997
1013
  p_zen_fail: 'Model setup failed',
998
1014
  bot_num_ro_hint: 'Add numbers / person names via Settings → Integrations (WhatsApp allow list); only linked numbers are shown here.',
999
1015
  tipStore: 'Skills Store',
1016
+ tipIde: 'IDE Mode — files + chat + preview',
1017
+ ide_files: 'FILES',
1018
+ ide_pick: 'Choose folder',
1019
+ ide_refresh: 'Refresh',
1020
+ ide_preview: '▶ Preview',
1021
+ ide_saved: 'Saved — refresh the preview',
1022
+ bc_ph: 'Type a message for Beast Code — press Enter to send…',
1023
+ bc_new: 'New session',
1024
+ bc_clear: 'Clear output',
1025
+ bc_stop: 'Stop running message',
1000
1026
  store_title: 'SKILLS STORE',
1001
1027
  store_tab_trending: 'Trending',
1002
1028
  store_tab_stars: 'Stars',
@@ -23,11 +23,23 @@
23
23
  </div>
24
24
  <div id="botList"></div>
25
25
  </div>
26
+ <aside id="filePanel">
27
+ <div id="filePanelHead">
28
+ <span id="filePanelTitle" data-i18n="ide_files">DOSYALAR</span>
29
+ <span class="file-spacer"></span>
30
+ <button id="filePick" title="Klasör seç" data-i18n-title="ide_pick"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 7a2 2 0 0 1 2-2h4l2 2h8a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7z"/></svg></button>
31
+ <button id="fileRefresh" title="Yenile" data-i18n-title="ide_refresh">&#x27F3;</button>
32
+ <button id="filePreview" title="Preview" data-i18n="ide_preview">&#9654; Preview</button>
33
+ </div>
34
+ <div id="filePanelPath" title="">—</div>
35
+ <div id="fileTree"></div>
36
+ </aside>
26
37
  <div id="sideFoot">
27
38
  <button id="gearBtn" title="Ayarlar" data-i18n-title="tipGear">&#x2699;&#xFE0E;</button>
28
39
  <button id="themeBtn" title="Koyu tema">&#x263E;</button>
29
40
  <button id="langBtn" title="Dil değiştir / Switch language" data-i18n-title="tipLang"><svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="12" r="9"/><path d="M3 12h18"/><path d="M12 3c2.6 2.6 2.6 15.4 0 18M12 3c-2.6 2.6-2.6 15.4 0 18"/></svg></button>
30
41
  <button id="storeBtn" title="Skills Store" data-i18n-title="tipStore"><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="M4 7h16l-1.3 12.1a2 2 0 0 1-2 1.9H7.3a2 2 0 0 1-2-1.9L4 7z"/><path d="M8 10V6a4 4 0 0 1 8 0v4"/></svg></button>
42
+ <button id="ideBtn" title="IDE Modu" data-i18n-title="tipIde"><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="M8 6l-5 6 5 6"/><path d="M16 6l5 6-5 6"/><path d="M13.5 4l-3 16"/></svg></button>
31
43
  </div>
32
44
  </aside>
33
45
 
@@ -65,7 +77,7 @@
65
77
  <button id="termGBtn" class="dd-btn dd-icon-btn" title="Git Bash terminali" data-i18n-title="tipTermG">&#x25C8;</button>
66
78
  <button id="termCBtn" class="dd-btn dd-icon-btn" title="CMD terminali" data-i18n-title="tipTermC">&#x276F;&#xFE0E;</button>
67
79
  <button id="browserBtn" class="dd-btn dd-icon-btn" title="Dahili tarayıcı" data-i18n-title="tipBrowser">&#x29C9;</button>
68
- <button id="eyeBtn" class="dd-btn dd-icon-btn" title="Ajan tarayıcısı görünür/gizli — göz açıkken aramalar panelde izlenir, kapalıyken gizli çalışır" data-i18n-title="tipEye"><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="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7z"/><circle cx="12" cy="12" r="3"/></svg></button>
80
+ <button id="eyeBtn" class="dd-btn dd-icon-btn" title="Ajan tarayıcısı görünür/gizli — göz açıkken aramalar panelde izlenir, kapalıyken gizli çalışır" data-i18n-title="tipEye"><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="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7z"/><circle cx="12" cy="12" r="3"/><line class="eye-slash" x1="4" y1="4" x2="20" y2="20"/></svg></button>
69
81
  <div class="drag-spacer"></div>
70
82
  </header>
71
83
 
@@ -90,6 +102,36 @@
90
102
  <button id="sendBtn" title="Gönder" data-i18n-title="tipSend">&#x27A4;&#xFE0E;</button>
91
103
  </div>
92
104
  </div>
105
+
106
+ <div id="ideRow">
107
+ <section id="codePane">
108
+ <div id="codeTabsBar">
109
+ <div id="codeTabs"></div>
110
+ <code id="codePath" title="">—</code>
111
+ <button id="codeSave" title="Kaydet (Ctrl+S)">Kaydet</button>
112
+ </div>
113
+ <textarea id="codeTa" spellcheck="false"></textarea>
114
+ </section>
115
+
116
+ <div id="ideSplit" title="Sürükleyerek boyutlandır"></div>
117
+
118
+ <div id="bcPanel">
119
+ <div id="bcHead">
120
+ <span id="bcTitle">BEAST CODE</span>
121
+ <span id="bcCwd" title=""></span>
122
+ <span class="file-spacer"></span>
123
+ <button id="bcNew" title="Yeni oturum" data-i18n-title="bc_new">+</button>
124
+ <button id="bcClear" title="Çıktıyı temizle" data-i18n-title="bc_clear">Temizle</button>
125
+ </div>
126
+ <div id="bcOut"></div>
127
+ <div id="bcTodoWrap" hidden></div>
128
+ <div id="bcInputRow">
129
+ <span id="bcPrompt">code&gt;</span>
130
+ <input id="bcInput" spellcheck="false" autocomplete="off" placeholder="Beast Code mesajı yaz — Enter ile gönder…" data-i18n-ph="bc_ph" />
131
+ <button id="bcStop" title="Çalışan mesajı durdur" hidden data-i18n-title="bc_stop">&#x25A0;&#xFE0E;</button>
132
+ </div>
133
+ </div>
134
+ </div>
93
135
  </main>
94
136
 
95
137
  <aside id="rail">
@@ -198,6 +240,7 @@
198
240
  <button class="btab" data-btab="log" data-i18n="bot_tab_log">Log</button>
199
241
  <button class="btab" data-btab="watcher" data-i18n="bot_tab_watcher">Watcher</button>
200
242
  <button class="btab" data-btab="stats" data-i18n="bot_tab_stats">İstatistik</button>
243
+ <button class="btab" data-btab="notes" data-i18n="bot_tab_notes">Notlar</button>
201
244
  </nav>
202
245
  <div id="botPane"></div>
203
246
  </div>
@@ -279,6 +322,8 @@
279
322
 
280
323
  <div id="toast"></div>
281
324
 
325
+ <div id="fileCtxMenu" hidden></div>
326
+
282
327
  <input type="file" id="fileInput" multiple hidden
283
328
  accept="image/*,.txt,.md,.json,.csv,.log,.js,.ts,.py,.ps1,.bat,.cmd,.html,.css,.yaml,.yml,.xml,.ini" />
284
329