beast-agent 0.15.0 → 0.16.1

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/.env.example CHANGED
@@ -3,5 +3,6 @@
3
3
  # Bu dosya config.yaml'daki key_env alanlarıyla eşleşir;
4
4
  # key_env yoksa <PROVIDER_ID>_API_KEY denenir (örn. zhipu -> ZHIPU_API_KEY).
5
5
 
6
+ OPENCODE_API_KEY=
6
7
  ZHIPU_API_KEY=
7
8
  OPENAI_API_KEY=
package/README.md CHANGED
@@ -25,30 +25,47 @@ Beast Agent is a personal AI agent that runs on your machine and connects to **a
25
25
  - **Full TR/EN interface** — everything switches language instantly
26
26
  - **/health** — `http://127.0.0.1:8788/health` liveness endpoint from the moment the app boots
27
27
 
28
- ## 📦 Download
28
+ ## 📦 Download & Install — 2 commands, ready
29
29
 
30
- ### Installer (recommended)
31
- Grab `BeastAgent-Setup-x.x.x.exe` from the [Releases](../../releases) page. After install, Beast starts automatically with Windows (lives in the tray).
32
-
33
- ### npm
34
30
  ```bash
35
31
  npm install -g beast-agent
36
32
  ```
37
- Then run `beast-agent` from any terminal. On first launch Beast creates a desktop shortcut and registers itself for auto-start.
38
33
 
39
- ### From source
34
+ Then start it:
35
+
40
36
  ```bash
41
- git clone https://github.com/algokodcom/beast-agent.git
42
- cd beast-agent
43
- npm install
44
- npm start
37
+ beast-agent
45
38
  ```
46
39
 
47
- > Requirements: Node.js 18+, Windows 10/11. Python and ffmpeg are optional Beast installs missing tooling itself.
40
+ That's it the app window opens. On first launch Beast also creates a **desktop shortcut** and registers itself to **start with Windows** (lives in the tray). Later updates: close the app and run `beast-agent update`.
48
41
 
49
42
  ## ⚙️ Configuration
50
43
 
51
- 1. Copy `config.example.yaml` `config.yaml` and add your providers:
44
+ ### Recommended: OpenCode Zen (free tier, no credit card)
45
+
46
+ Get a key at [opencode.ai/auth](https://opencode.ai/auth), then copy `config.example.yaml` → `config.yaml`:
47
+
48
+ ```yaml
49
+ defaultSelection: opencode::glm-5.2
50
+ providers:
51
+ - id: opencode
52
+ name: OpenCode Zen
53
+ baseUrl: https://opencode.ai/zen/v1
54
+ apiKey: <your-key>
55
+ models:
56
+ - glm-5.2
57
+ - kimi-k2.7-code
58
+ - deepseek-v4-flash-free
59
+ - big-pickle
60
+ - minimax-m3
61
+ ```
62
+
63
+ > Free models include `deepseek-v4-flash-free`, `big-pickle`, `mimo-v2.5-free`, `nemotron-3-ultra-free`. If you have an **OpenCode Go** subscription, use the same config with `baseUrl: https://opencode.ai/zen/go/v1`.
64
+
65
+ ### Any other OpenAI-compatible provider
66
+
67
+ OpenRouter, Zhipu, Ollama, OpenAI — anything that speaks `/v1/chat/completions` works:
68
+
52
69
  ```yaml
53
70
  defaultSelection: openrouter::anthropic/claude-3.5-sonnet
54
71
  providers:
@@ -60,8 +77,8 @@ providers:
60
77
  - anthropic/claude-3.5-sonnet
61
78
  - gpt-4o
62
79
  ```
63
- 2. You can also provide API keys via `.env`: copy `.env.example` → `.env`
64
- 3. On first launch, Settings → Provider pulls your models automatically.
80
+
81
+ API keys can also go into `.env` (copy `.env.example` → `.env`). On first launch, Settings → Provider pulls your models automatically.
65
82
 
66
83
  All app data lives under `%APPDATA%\beast` (sessions, memory, WhatsApp pairing, encrypted settings).
67
84
 
@@ -2,11 +2,21 @@
2
2
  'use strict';
3
3
 
4
4
  /* Beast Agent global npm başlatıcısı:
5
- `beast-agent` komutu uygulamayı detached başlatır ve terminali hemen serbest bırakır. */
5
+ `beast-agent` uygulamayı detached başlatır, terminali hemen serbest bırakır
6
+ `beast-agent update` → npm'den en son sürümü yükler (uygulama kapalıyken çalıştır) */
6
7
 
7
- const { spawn } = require('child_process');
8
+ const { spawn, spawnSync } = require('child_process');
8
9
  const path = require('path');
9
10
 
11
+ /* güncelleme modu: uygulama kapalıyken dosyalar kilitli olmaz */
12
+ if (process.argv[2] === 'update') {
13
+ const r = spawnSync('npm', ['install', '-g', 'beast-agent@latest'], { stdio: 'inherit', shell: true });
14
+ console.log(r.status === 0
15
+ ? '\n✓ beast-agent güncellendi — "beast-agent" ile başlatabilirsin.'
16
+ : '\n✗ güncelleme başarısız — elle: npm install -g beast-agent@latest');
17
+ process.exit(r.status || 0);
18
+ }
19
+
10
20
  const electron = require('electron');
11
21
  if (typeof electron !== 'string') {
12
22
  /* electron runtime içindeyiz — bu script için anlamsız */
@@ -17,7 +27,7 @@ const appPath = path.resolve(__dirname, '..');
17
27
  const child = spawn(electron, [appPath, ...process.argv.slice(2)], {
18
28
  stdio: 'ignore',
19
29
  detached: true,
20
- windowsHide: true,
30
+ /* windowsHide KULLANMA: Chromium ilk pencereyi gizli başlatıyor (tray-only bug) */
21
31
  });
22
32
  child.unref();
23
33
  process.exit(0);
@@ -2,7 +2,20 @@
2
2
  # Kullanım: bu dosyayı %APPDATA%\beast\config.yaml olarak kopyala ve düzenle.
3
3
  # (Taşınabilir kullanım için BEAST_DATA ortam değişkeni ile başka klasöre alabilirsin.)
4
4
 
5
+ # ÖNERİLEN: OpenCode Zen — ücretsiz modeller, kredi kartı gerekmez (opencode.ai/auth)
5
6
  providers:
7
+ opencode:
8
+ name: OpenCode Zen
9
+ base_url: https://opencode.ai/zen/v1
10
+ key_env: OPENCODE_API_KEY
11
+ models:
12
+ glm-5.2: {}
13
+ kimi-k2.7-code: {}
14
+ deepseek-v4-flash-free: {}
15
+ big-pickle: {}
16
+ minimax-m3: {}
17
+ # OpenCode Go aboneliğin varsa base_url: https://opencode.ai/zen/go/v1 kullan.
18
+
6
19
  zhipu:
7
20
  name: Zhipu AI
8
21
  base_url: https://api.z.ai/api/paas/v4
@@ -20,7 +33,7 @@ providers:
20
33
  gpt-4o: {}
21
34
 
22
35
  model:
23
- provider: zhipu # varsayılan provider id (yoksa ilk provider kullanılır)
24
- default: glm-4.6 # varsayılan model
36
+ provider: opencode # varsayılan provider id (yoksa ilk provider kullanılır)
37
+ default: glm-5.2 # varsayılan model
25
38
  # base_url: ... # istersen aktif provider'ın URL'ini override et
26
39
  # key_env: ... # istersen anahtarı farklı env'den oku
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "beast-agent",
3
3
  "productName": "Beast Agent",
4
- "version": "0.15.0",
4
+ "version": "0.16.1",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -31,6 +31,7 @@
31
31
  "start": "electron .",
32
32
  "dist": "electron-builder --win nsis portable",
33
33
  "test": "node --test \"tests/**/*.test.js\"",
34
+ "release": "node scripts/release.js",
34
35
  "prepublishOnly": "node scripts/swap-electron.js deps",
35
36
  "postpublish": "node scripts/swap-electron.js devdeps"
36
37
  },
@@ -0,0 +1,134 @@
1
+ 'use strict';
2
+
3
+ /* Beast Agent — Dağıtım / Senkron Sistemi
4
+ Tek komutla tam sürüm akışı:
5
+ npm run release → otomatik patch bump (0.15.0 → 0.15.1)
6
+ npm run release -- minor → 0.16.0
7
+ npm release -- 0.17.0 → belirli sürüm
8
+
9
+ Adımlar:
10
+ 1) package.json sürümü yükselt
11
+ 2) commit + tag + push (main + tag)
12
+ 3) npm run dist (NSIS setup + portable)
13
+ 4) GitHub Release + exe upload (gh CLI)
14
+ 5) npm publish (NPM_TOKEN env var ise; yoksa atlar ve uyarır)
15
+ 6) OneDrive yedek klasörüne kaynak kopyası (beast-v< sürüm >)
16
+ */
17
+
18
+ const fs = require('fs');
19
+ const path = require('path');
20
+ const { execSync } = require('child_process');
21
+
22
+ const ROOT = path.join(__dirname, '..');
23
+ const ONE_DRIVE_DIR = process.env.BEAST_BACKUP_DIR
24
+ || 'C:\\Users\\batuh\\OneDrive\\Masaüstü\\Beast Agent';
25
+
26
+ const step = (msg) => console.log('\n\x1b[1m▶ ' + msg + '\x1b[0m');
27
+ const ok = (msg) => console.log(' \x1b[32m✓\x1b[0m ' + msg);
28
+ const warn = (msg) => console.log(' \x1b[33m!\x1b[0m ' + msg);
29
+ const fail = (msg) => { console.error(' \x1b[31m✗ ' + msg + '\x1b[0m'); process.exit(1); };
30
+
31
+ function run(cmd, opts = {}) {
32
+ return execSync(cmd, { cwd: ROOT, stdio: opts.inherit ? 'inherit' : 'pipe', encoding: 'utf8', ...opts }).trim();
33
+ }
34
+
35
+ function bump(version, kind) {
36
+ const [ma, mi, pa] = version.split('.').map(Number);
37
+ if (kind === 'major') return `${ma + 1}.0.0`;
38
+ if (kind === 'minor') return `${ma}.${mi + 1}.0`;
39
+ return `${ma}.${mi}.${pa + 1}`;
40
+ }
41
+
42
+ /* ---------- argümanlar ---------- */
43
+ const arg = process.argv[2] || 'patch';
44
+ const pkgPath = path.join(ROOT, 'package.json');
45
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
46
+ const current = pkg.version;
47
+ const next = ['major', 'minor', 'patch'].includes(arg) ? bump(current, arg) : arg.replace(/^v/, '');
48
+ if (!/^\d+\.\d+\.\d+$/.test(next)) fail('geçersiz sürüm: ' + next);
49
+ const tag = 'v' + next;
50
+
51
+ console.log(`\x1b[1mBeast Agent release: ${current} → ${next}\x1b[0m`);
52
+
53
+ /* ---------- 1) sürüm bump ---------- */
54
+ step('1/6 sürüm yükselt: ' + current + ' → ' + next);
55
+ pkg.version = next;
56
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 4) + '\n');
57
+ ok('package.json → ' + next);
58
+
59
+ /* ---------- 2) commit + push ---------- */
60
+ step('2/6 git: commit + tag + push');
61
+ try {
62
+ run('git add -A');
63
+ run(`git commit -m "v${next}"`);
64
+ } catch { warn('commit edilecek değişiklik yoktu'); }
65
+ try { run(`git tag ${tag} -f`); } catch {}
66
+ run('git push origin main');
67
+ run(`git push origin ${tag}`);
68
+ ok('pushed: main + ' + tag);
69
+
70
+ /* ---------- 3) build ---------- */
71
+ step('3/6 build: npm run dist (birkaç dakika)');
72
+ run('npm run dist', { inherit: true });
73
+ const setupExe = path.join(ROOT, 'dist', `BeastAgent-Setup-${next}.exe`);
74
+ const portableExe = path.join(ROOT, 'dist', 'BeastAgent.exe');
75
+ if (!fs.existsSync(setupExe)) fail('setup exe bulunamadı: ' + setupExe);
76
+ if (!fs.existsSync(portableExe)) fail('portable exe bulunamadı: ' + portableExe);
77
+ ok('dist hazır: Setup + Portable');
78
+
79
+ /* ---------- 4) GitHub release ---------- */
80
+ step('4/6 GitHub Release ' + tag);
81
+ const gh = process.env.GH || 'gh';
82
+ try {
83
+ const notes = [
84
+ `## Beast Agent ${tag}`,
85
+ '',
86
+ '- `BeastAgent-Setup-${next}.exe` — kurulumlu (önerilen)',
87
+ '- \`BeastAgent.exe\` — portable',
88
+ '- \`npm i -g beast-agent\` — npm üzerinden',
89
+ '',
90
+ 'Tam değişiklik listesi: commit geçmişi.',
91
+ ].join('\n');
92
+ const notesFile = path.join(ROOT, 'dist', 'release-notes.md');
93
+ fs.writeFileSync(notesFile, notes);
94
+ const assets = [setupExe, setupExe + '.blockmap', portableExe, path.join(ROOT, 'dist', 'latest.yml')]
95
+ .filter((f) => fs.existsSync(f))
96
+ .map((f) => `"${f}"`)
97
+ .join(' ');
98
+ try { run(`${gh} release delete ${tag} --yes --cleanup-tag`); } catch {}
99
+ run(`${gh} release create ${tag} ${assets} --title "Beast Agent ${tag}" --notes-file "${notesFile}"`, { inherit: true });
100
+ ok('release yayında: https://github.com/algokodcom/beast-agent/releases/tag/' + tag);
101
+ } catch (e) {
102
+ fail('GitHub release: ' + String(e));
103
+ }
104
+
105
+ /* ---------- 5) npm publish ---------- */
106
+ step('5/6 npm publish');
107
+ if (process.env.NPM_TOKEN) {
108
+ try {
109
+ run(`npm publish --//registry.npmjs.org/:_authToken=${process.env.NPM_TOKEN}`, { inherit: true });
110
+ ok('npm: beast-agent@' + next);
111
+ } catch (e) {
112
+ warn('npm publish başarısız (sürüm zaten var olabilir): ' + String(e).slice(0, 200));
113
+ }
114
+ } else {
115
+ warn('NPM_TOKEN env yok — npm adımı atlandı.');
116
+ warn('Elle: set NPM_TOKEN=<token> && npm publish');
117
+ }
118
+
119
+ /* ---------- 6) OneDrive kaynak yedeği ---------- */
120
+ step('6/6 OneDrive yedek (beast-v' + next + ')');
121
+ try {
122
+ const dest = path.join(ONE_DRIVE_DIR, 'beast-v' + next);
123
+ fs.mkdirSync(dest, { recursive: true });
124
+ run(`robocopy "${ROOT}" "${dest}" /E /XD node_modules dist "beast agent web" .git /NFL /NDL /NJH`);
125
+ const info = path.join(dest, `YEDEK-BILGI-v${next}.txt`);
126
+ fs.writeFileSync(info, `BEAST AGENT v${next} — ${new Date().toLocaleString('tr-TR')}\nKaynak: ${ROOT}\nGitHub: https://github.com/algokodcom/beast-agent/releases/tag/${tag}\nnpm: https://www.npmjs.com/package/beast-agent\n`);
127
+ ok(dest);
128
+ } catch (e) {
129
+ warn('OneDrive yedeği atlandı: ' + String(e).slice(0, 160));
130
+ }
131
+
132
+ console.log(`\n\x1b[1m\x1b[32m✓ v${next} dağıtımı tamam.\x1b[0m`);
133
+ console.log(' GitHub : https://github.com/algokodcom/beast-agent/releases/tag/' + tag);
134
+ console.log(' npm : https://www.npmjs.com/package/beast-agent');
package/src/main.js CHANGED
@@ -19,27 +19,84 @@ const computeruse = require('./agent/computeruse');
19
19
  const log = require('./agent/logger');
20
20
 
21
21
  /* #3 otomatik updater: sessiz — indirir, kapanışta kurar, kullanıcıya soru sormaz.
22
- Paketlenmemiş (npm start) modda devre dışı. */
22
+ Paketlenmemiş (npm start) modda devre dışı; Update sekmesi ve /update komutu kontrol eder. */
23
23
  let autoUpdater = null;
24
24
  try { if (app.isPackaged) autoUpdater = require('electron-updater').autoUpdater; } catch {}
25
25
 
26
+ const updateState = { checking: false, available: false, downloaded: false, version: null, progress: null, error: null };
27
+ const updateReplies = { sids: new Set(), jids: new Set() }; // /update isteyen hedefler — sonuç oraya gider
28
+
29
+ function isNpmMode() {
30
+ return !app.isPackaged && /node_modules[\\/]beast-agent/i.test(String(app.getAppPath()));
31
+ }
32
+
33
+ function emitUpdateEvent() {
34
+ try {
35
+ if (win && !win.isDestroyed()) {
36
+ win.webContents.send('agent:event', { type: 'update', ...updateState, current: app.getVersion() });
37
+ }
38
+ } catch {}
39
+ }
40
+
41
+ function replyUpdate(text) {
42
+ try {
43
+ for (const sid of updateReplies.sids) desktopEcho(sid, '/update', text);
44
+ for (const jid of updateReplies.jids) sendWaSafe(jid, text).catch(() => {});
45
+ updateReplies.sids.clear();
46
+ updateReplies.jids.clear();
47
+ } catch {}
48
+ }
49
+
26
50
  function startAutoUpdater() {
27
51
  if (!autoUpdater || !app.isPackaged) return;
28
52
  try {
29
- autoUpdater.autoDownload = true; // sessiz indir
30
- autoUpdater.autoInstallOnAppQuit = true; // kapanışta sessiz kur
53
+ autoUpdater.autoDownload = settings.autoDownloadUpdate !== false; // sessiz indir (toggle'lı)
54
+ autoUpdater.autoInstallOnAppQuit = true; // kapanışta sessiz kur
31
55
  autoUpdater.logger = {
32
56
  info: (m) => waLog('[UPD] ' + m),
33
57
  warn: (m) => waLog('[UPD] ' + m),
34
58
  error: (m) => waLog('[UPD] ' + m),
35
59
  debug: () => {},
36
60
  };
37
- autoUpdater.on('update-downloaded', () => {
38
- try { fs.appendFileSync(path.join(APP_DIR, 'wa.log'), `[${new Date().toISOString()}] [UPD] güncelleme indirildi — kapanışta kurulacak\n`); } catch {}
61
+ autoUpdater.on('checking-for-update', () => {
62
+ updateState.checking = true; updateState.error = null; emitUpdateEvent();
63
+ });
64
+ autoUpdater.on('update-available', (i) => {
65
+ updateState.checking = false; updateState.available = true;
66
+ updateState.version = (i && i.version) || null;
67
+ emitUpdateEvent();
68
+ replyUpdate(`🔄 *Yeni sürüm bulundu:* v${updateState.version} (mevcut v${app.getVersion()}) — indiriliyor…`);
69
+ });
70
+ autoUpdater.on('update-not-available', () => {
71
+ updateState.checking = false; updateState.available = false; updateState.version = null;
72
+ emitUpdateEvent();
73
+ replyUpdate(`✅ *Güncelsin* — v${app.getVersion()} en son sürüm.`);
74
+ });
75
+ autoUpdater.on('download-progress', (p) => {
76
+ updateState.progress = {
77
+ percent: Math.round(Number(p && p.percent) || 0),
78
+ mbps: Math.round((Number(p && p.bytesPerSecond) || 0) / 1048576 * 10) / 10,
79
+ };
80
+ emitUpdateEvent();
81
+ });
82
+ autoUpdater.on('update-downloaded', (i) => {
83
+ updateState.checking = false; updateState.downloaded = true;
84
+ updateState.version = (i && i.version) || updateState.version;
85
+ updateState.progress = null;
86
+ emitUpdateEvent();
87
+ waLog('[UPD] güncelleme indirildi — /update now veya kapanışta kurulacak');
88
+ replyUpdate(`✅ *v${updateState.version} indirildi.* Kurmak için: \`/update now\` — ya da uygulama kapanınca otomatik kurulur.`);
39
89
  });
40
- const check = () => autoUpdater.checkForUpdates().catch(() => {});
41
- check();
42
- setInterval(check, 6 * 60 * 60 * 1000); // 6 saatte bir sessiz kontrol
90
+ autoUpdater.on('error', (e) => {
91
+ updateState.checking = false; updateState.error = String((e && e.message) || e);
92
+ emitUpdateEvent();
93
+ });
94
+ /* otomatik kontrol: açılışta + 6 saatte bir (Update sekmesinden kapatılabilir) */
95
+ if (settings.autoCheckUpdate !== false) {
96
+ const check = () => autoUpdater.checkForUpdates().catch(() => {});
97
+ check();
98
+ setInterval(check, 6 * 60 * 60 * 1000);
99
+ }
43
100
  } catch {}
44
101
  }
45
102
  const { htmlToText, setExaKey, setTinyfishKey } = require('./agent/tools');
@@ -472,6 +529,7 @@ function waSlashHelp() {
472
529
  '• /allow <isim> <numara> – WhatsApp allow listesine kişi ekle (örn: /allow batu 905414178456)',
473
530
  '• /block – allow listesini numaralarıyla listele (/block 3: 3. kişiyi çıkar; 1 = sahip, silinemez)',
474
531
  '• /approve – bekleyen riskli işlemi onayla (/approve always: bir daha sorulmasın · /deny: reddet)',
532
+ '• /update – yeni sürüm kontrolü (/update now: indirileni hemen kur)',
475
533
  '• /model – aktif modeli göster (/model <isim> ile değiştir)',
476
534
  '• /skills – kurulu skill\u2019ler',
477
535
  '• /usage – bugünkü kullanım',
@@ -848,6 +906,19 @@ async function tryWaSlash(jid, rawText, senderNum) {
848
906
  out = r.ok
849
907
  ? `*${cmd === 'deny' ? 'Reddedildi' : 'Onaylandı'}:* ${r.tool}${always ? ' — bu araç için bir daha sorulmayacak' : ''}`
850
908
  : 'Bekleyen onay yok.';
909
+ } else if (cmd === 'update') {
910
+ /* /update — sürüm kontrol; /update now — indirileni kur */
911
+ if (String(arg || '').toLowerCase() === 'now') {
912
+ if (updateState.downloaded && autoUpdater) {
913
+ out = `*v${updateState.version} kuruluyor* — uygulama yeniden başlayacak.`;
914
+ setTimeout(() => { try { autoUpdater.quitAndInstall(); } catch {} }, 1200);
915
+ } else {
916
+ out = 'İndirilmiş sürüm yok — önce `/update` yaz.';
917
+ }
918
+ } else {
919
+ updateReplies.jids.add(jid);
920
+ await runUpdateCommand(async (text) => { out = text; });
921
+ }
851
922
  } else if (cmd === 'think') {
852
923
  const r = arg ? applyThinkLevel(arg) : null;
853
924
  out = r && r.error ? r.error : r ? r.text : thinkStatusText();
@@ -1604,11 +1675,8 @@ if (!gotLock) {
1604
1675
  app.quit();
1605
1676
  } else {
1606
1677
  app.on('second-instance', () => {
1607
- if (win) {
1608
- if (win.isMinimized()) win.restore();
1609
- win.show();
1610
- win.focus();
1611
- }
1678
+ /* ikinci `beast-agent` çağrısı: tepside gizli olsa bile pencereyi öne getir */
1679
+ try { showWin(); } catch {}
1612
1680
  });
1613
1681
 
1614
1682
  /* npm (global) kurulumda masaüstü kısayolu — yoksa bir kez oluşturulur.
@@ -1623,7 +1691,7 @@ function ensureDesktopShortcut() {
1623
1691
  args: app.getAppPath(),
1624
1692
  cwd: app.getAppPath(),
1625
1693
  description: 'Beast Agent — hızlı, hafif ve becerikli',
1626
- icon: process.execPath,
1694
+ icon: path.join(__dirname, '..', 'assets', 'app.ico'),
1627
1695
  iconIndex: 0,
1628
1696
  });
1629
1697
  log.info('main', ok ? 'Masaüstü kısayolu oluşturuldu (npm modu)' : 'Masaüstü kısayolu oluşturulamadı');
@@ -1662,6 +1730,7 @@ app.whenReady().then(() => {
1662
1730
  ensureDesktopShortcut();
1663
1731
  }
1664
1732
  reloadBackend();
1733
+ createSplash();
1665
1734
  createWindow();
1666
1735
  log.info('main', 'Beast Agent başlatıldı');
1667
1736
  createTray();
@@ -2362,6 +2431,49 @@ const startHidden =
2362
2431
  process.argv.includes('--silent') ||
2363
2432
  String(process.env.BEAST_HIDDEN || '') === '1';
2364
2433
 
2434
+ /* Splash: npm/ portable başlangıcında logolu karşılama penceresi — ana pencere
2435
+ hazır olunca kapanır. */
2436
+ let splash = null;
2437
+
2438
+ function createSplash() {
2439
+ try {
2440
+ const dark = settings.theme === 'dark';
2441
+ const bg = dark ? '#0d0d0f' : '#f7f7f8';
2442
+ const fg = dark ? '#f2f2f4' : '#17171a';
2443
+ const muted = dark ? '#9a9aa2' : '#707078';
2444
+ splash = new BrowserWindow({
2445
+ width: 420,
2446
+ height: 250,
2447
+ frame: false,
2448
+ resizable: false,
2449
+ alwaysOnTop: true,
2450
+ skipTaskbar: true,
2451
+ center: true,
2452
+ backgroundColor: bg,
2453
+ icon: path.join(__dirname, '..', 'assets', 'app.ico'),
2454
+ });
2455
+ const html = `<!doctype html><html><head><meta charset="utf-8"><style>
2456
+ body{margin:0;height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;background:${bg};font-family:'Segoe UI',sans-serif;color:${fg}}
2457
+ .logo{width:84px;height:84px;border-radius:20px;background:${fg};color:${bg};display:flex;align-items:center;justify-content:center;font-weight:900;font-size:44px}
2458
+ .t{margin-top:16px;font-size:20px;color:${fg}}.t b{font-weight:900}
2459
+ .s{margin-top:6px;font-size:12px;color:${muted}}
2460
+ .bar{margin-top:18px;width:180px;height:3px;background:${muted}44;border-radius:2px;overflow:hidden}
2461
+ .bar>i{display:block;height:100%;width:40%;background:${fg};border-radius:2px;animation:sw 1.1s ease-in-out infinite}
2462
+ @keyframes sw{0%{transform:translateX(-100%)}100%{transform:translateX(260%)}}
2463
+ </style></head><body>
2464
+ <div class="logo">B</div>
2465
+ <div class="t"><b>BEAST</b> Agent</div>
2466
+ <div class="s">hızlı · hafif · becerikli — v${app.getVersion()}</div>
2467
+ <div class="bar"><i></i></div>
2468
+ </body></html>`;
2469
+ splash.loadURL('data:text/html;charset=utf-8,' + encodeURIComponent(html));
2470
+ } catch {}
2471
+ }
2472
+
2473
+ function closeSplash() {
2474
+ try { if (splash) { splash.close(); splash = null; } } catch {}
2475
+ }
2476
+
2365
2477
  function createWindow() {
2366
2478
  log.info('main', 'Pencere oluşturuluyor…');
2367
2479
  const dark = settings.theme === 'dark';
@@ -2372,6 +2484,7 @@ function createWindow() {
2372
2484
  minHeight: 500,
2373
2485
  show: false,
2374
2486
  backgroundColor: dark ? '#0d0d0f' : '#f7f7f8',
2487
+ icon: path.join(__dirname, '..', 'assets', 'app.ico'),
2375
2488
  titleBarStyle: 'hidden',
2376
2489
  titleBarOverlay: {
2377
2490
  color: 'transparent',
@@ -2390,8 +2503,16 @@ function createWindow() {
2390
2503
  win.setMenuBarVisibility(false);
2391
2504
  win.loadFile(path.join(__dirname, 'renderer', 'index.html'));
2392
2505
  win.once('ready-to-show', () => {
2506
+ closeSplash();
2393
2507
  if (startHidden) win.hide();
2394
- else win.show();
2508
+ else {
2509
+ win.show();
2510
+ win.focus();
2511
+ /* emniyet: bazı başlatma yollarında ilk show yutulur — tekrar dene */
2512
+ setTimeout(() => {
2513
+ try { if (win && !win.isDestroyed() && !win.isVisible()) { win.show(); win.focus(); } } catch {}
2514
+ }, 1200);
2515
+ }
2395
2516
  });
2396
2517
  win.on('resize', layoutBrowser);
2397
2518
  win.on('maximize', layoutBrowser);
@@ -2489,6 +2610,21 @@ ipcMain.handle('agent:send', (_e, { sessionId, text }) => {
2489
2610
  );
2490
2611
  return true;
2491
2612
  }
2613
+ if (t === '/update' || t.startsWith('/update ')) {
2614
+ const a = t.slice(7).trim().toLowerCase();
2615
+ updateReplies.sids.add(String(sessionId || ''));
2616
+ if (a === 'now') {
2617
+ if (updateState.downloaded && autoUpdater) {
2618
+ desktopEcho(sessionId, t, `*v${updateState.version} kuruluyor* — uygulama yeniden başlayacak.`);
2619
+ setTimeout(() => { try { autoUpdater.quitAndInstall(); } catch {} }, 1200);
2620
+ } else {
2621
+ desktopEcho(sessionId, t, 'İndirilmiş sürüm yok — önce `/update` yaz.');
2622
+ }
2623
+ } else {
2624
+ runUpdateCommand((text) => desktopEcho(sessionId, t, text));
2625
+ }
2626
+ return true;
2627
+ }
2492
2628
  resumeServices(); // pause durumunda gerçek mesaj her şeyi canlandırır
2493
2629
  queueDesktopMessage(sessionId, text);
2494
2630
  return true;
@@ -2998,6 +3134,62 @@ ipcMain.handle('sec:set', (_e, cfg) => {
2998
3134
  });
2999
3135
  ipcMain.handle('approval:respond', (_e, { id, ok, always }) => resolveApproval(id, ok, always));
3000
3136
 
3137
+ /* ---------------- #Update IPC ---------------- */
3138
+ ipcMain.handle('update:status', () => ({
3139
+ current: app.getVersion(),
3140
+ packaged: app.isPackaged,
3141
+ npm: isNpmMode(),
3142
+ ...updateState,
3143
+ autoCheck: settings.autoCheckUpdate !== false,
3144
+ autoDownload: settings.autoDownloadUpdate !== false,
3145
+ }));
3146
+
3147
+ ipcMain.handle('update:check', async (_e, viaCommand) => {
3148
+ if (isNpmMode()) return { ok: false, npm: true, error: 'npm kurulumu — güncelleme: beast-agent update' };
3149
+ if (!autoUpdater) return { ok: false, error: 'updater kullanılamıyor (taşınabilir/geliştirme modu)' };
3150
+ try {
3151
+ const r = await autoUpdater.checkForUpdates();
3152
+ const v = r && r.update && r.update.version;
3153
+ return { ok: true, version: v, available: !!v && v !== app.getVersion() };
3154
+ } catch (e) {
3155
+ return { ok: false, error: String((e && e.message) || e) };
3156
+ }
3157
+ });
3158
+
3159
+ ipcMain.handle('update:install', () => {
3160
+ if (isNpmMode() || !autoUpdater) return { ok: false, error: 'updater kullanılamıyor' };
3161
+ if (!updateState.downloaded) return { ok: false, error: 'indirilmiş sürüm yok — önce /update' };
3162
+ try { autoUpdater.quitAndInstall(); return { ok: true }; } catch (e) {
3163
+ return { ok: false, error: String((e && e.message) || e) };
3164
+ }
3165
+ });
3166
+
3167
+ ipcMain.handle('update:setAuto', (_e, cfg) => {
3168
+ if (cfg && typeof cfg.autoCheck === 'boolean') settings.autoCheckUpdate = cfg.autoCheck;
3169
+ if (cfg && typeof cfg.autoDownload === 'boolean') {
3170
+ settings.autoDownloadUpdate = cfg.autoDownload;
3171
+ if (autoUpdater) autoUpdater.autoDownload = cfg.autoDownload;
3172
+ }
3173
+ saveSettings();
3174
+ return { autoCheck: settings.autoCheckUpdate !== false, autoDownload: settings.autoDownloadUpdate !== false };
3175
+ });
3176
+
3177
+ /* /update komutu (masaüstü + WA): hedefi kaydet, kontrol başlat */
3178
+ async function runUpdateCommand(reply /* fn(text) */) {
3179
+ if (isNpmMode()) {
3180
+ reply('npm kurulumu — güncellemek için:\n1) Uygulamayı kapat\n2) Terminalde: `beast-agent update`\n3) Tekrar: `beast-agent`');
3181
+ return;
3182
+ }
3183
+ if (!autoUpdater) {
3184
+ reply('Updater bu modda kullanılamıyor (taşınabilir sürüm). Yeni exe: github.com/algokodcom/beast-agent/releases');
3185
+ return;
3186
+ }
3187
+ reply(`🔍 v${app.getVersion()} — güncellemeler kontrol ediliyor…`);
3188
+ try { await autoUpdater.checkForUpdates(); } catch (e) {
3189
+ reply('Güncelleme kontrolü başarısız: ' + String((e && e.message) || e));
3190
+ }
3191
+ }
3192
+
3001
3193
  /* #STT: sohbet mikrofonu — MediaRecorder sesini (webm/opus) yerel whisper'a çevir */
3002
3194
  ipcMain.handle('stt:transcribe', async (_e, b64) => {
3003
3195
  try {
package/src/preload.js CHANGED
@@ -45,6 +45,10 @@ contextBridge.exposeInMainWorld('beast', {
45
45
  secSet: (cfg) => ipcRenderer.invoke('sec:set', cfg),
46
46
  approvalRespond: (id, ok, always) => ipcRenderer.invoke('approval:respond', { id, ok, always }),
47
47
  sttTranscribe: (b64) => ipcRenderer.invoke('stt:transcribe', b64),
48
+ updateStatus: () => ipcRenderer.invoke('update:status'),
49
+ updateCheck: () => ipcRenderer.invoke('update:check'),
50
+ updateInstall: () => ipcRenderer.invoke('update:install'),
51
+ updateSetAuto: (cfg) => ipcRenderer.invoke('update:setAuto', cfg),
48
52
  setModel: (sel) => ipcRenderer.invoke('model:set', sel),
49
53
  setRoleModels: (map) => ipcRenderer.invoke('model:role', map),
50
54
  deleteModel: (sel) => ipcRenderer.invoke('model:delete', sel),
@@ -61,6 +61,23 @@
61
61
  tab_websearch: 'Web Arama',
62
62
  tab_limits: 'Limit Ayarları',
63
63
  tab_security: 'Güvenlik',
64
+ tab_update: 'Güncelleme',
65
+ up_h2: 'Güncelleme',
66
+ up_sub: 'Sürümler GitHub Releases\u2019ten gelir. Otomatik kontrol açılışta ve 6 saatte bir çalışır.',
67
+ up_current: 'Mevcut sürüm',
68
+ up_latest: 'En son sürüm',
69
+ up_status: 'Durum',
70
+ up_checking: 'Kontrol ediliyor…',
71
+ up_available: 'Yeni sürüm var',
72
+ up_uptodate: 'Güncel',
73
+ up_downloading: 'İndiriliyor…',
74
+ up_downloaded: 'İndirildi — kurulum için hazır',
75
+ up_npm_mode: 'npm kurulumu — güncelleme: beast-agent update',
76
+ up_auto_check: 'Otomatik sürüm kontrolü (açılış + 6 saatte bir)',
77
+ up_auto_dl: 'Yeni sürümü otomatik indir (kapanışta kurulur)',
78
+ up_check_now: 'Şimdi Kontrol Et',
79
+ up_install_now: 'Yeniden Başlat & Kur',
80
+ up_note: 'Kurulumdan sonra uygulama otomatik yeniden başlar. İstersen WhatsApp\u2019tan /update komutunu da kullanabilirsin.',
64
81
  tab_events: 'Olay Merkezi',
65
82
  tab_cron: 'Cron',
66
83
  tab_usage: 'Maliyet',
@@ -394,6 +411,23 @@
394
411
  tab_websearch: 'Web Search',
395
412
  tab_limits: 'Limits',
396
413
  tab_security: 'Security',
414
+ tab_update: 'Update',
415
+ up_h2: 'Update',
416
+ up_sub: 'Versions come from GitHub Releases. Automatic check runs on startup and every 6 hours.',
417
+ up_current: 'Current version',
418
+ up_latest: 'Latest version',
419
+ up_status: 'Status',
420
+ up_checking: 'Checking…',
421
+ up_available: 'New version available',
422
+ up_uptodate: 'Up to date',
423
+ up_downloading: 'Downloading…',
424
+ up_downloaded: 'Downloaded — ready to install',
425
+ up_npm_mode: 'npm install — update via: beast-agent update',
426
+ up_auto_check: 'Automatic version check (on startup + every 6 hours)',
427
+ up_auto_dl: 'Auto-download new versions (installed on quit)',
428
+ up_check_now: 'Check Now',
429
+ up_install_now: 'Restart & Install',
430
+ up_note: 'The app restarts itself after installing. You can also use the /update command from WhatsApp.',
397
431
  tab_events: 'Event Center',
398
432
  tab_cron: 'Cron',
399
433
  tab_usage: 'Costs',
@@ -109,6 +109,7 @@
109
109
  <button class="tab" data-tab="dash" data-i18n="tab_dash">Dashboard</button>
110
110
  <button class="tab" data-tab="limits" data-i18n="tab_limits">Limit</button>
111
111
  <button class="tab" data-tab="sec" data-i18n="tab_security">Güvenlik</button>
112
+ <button class="tab" data-tab="update" data-i18n="tab_update">Güncelleme</button>
112
113
  <div class="set-tabs-foot">
113
114
  <div id="beastCodeBox" class="beast-code" data-i18n-title="bc_label" title="Beast Kodu">
114
115
  <div class="bc-row">
@@ -135,6 +136,7 @@
135
136
  <div id="tab-dash" class="pane" hidden></div>
136
137
  <div id="tab-limits" class="pane" hidden></div>
137
138
  <div id="tab-sec" class="pane" hidden></div>
139
+ <div id="tab-update" class="pane" hidden></div>
138
140
  <div id="tab-cron" class="pane" hidden>
139
141
  <h2 data-i18n="cron_h2">Cron Görevler</h2>
140
142
  <div class="sub" data-i18n="cron_sub">Zamanlanmış görevler — saati gelince agent otomatik çalışır</div>
@@ -508,6 +508,7 @@ async function renderActiveSettingsTab() {
508
508
  case 'dash': await renderDashboardPane(); break;
509
509
  case 'limits': await renderLimitsPane(); break;
510
510
  case 'sec': await renderSecurityPane(); break;
511
+ case 'update': await renderUpdatePane(); break;
511
512
  }
512
513
  }
513
514
 
@@ -516,7 +517,7 @@ function switchTab(name) {
516
517
  document.querySelectorAll('#setTabs .tab').forEach((b) =>
517
518
  b.classList.toggle('active', b.dataset.tab === name)
518
519
  );
519
- for (const p of ['provider', 'fallout', 'memory', 'skills', 'agents', 'tts', 'email', 'integrations', 'websearch', 'events', 'cron', 'usage', 'logs', 'dash', 'limits', 'sec']) {
520
+ for (const p of ['provider', 'fallout', 'memory', 'skills', 'agents', 'tts', 'email', 'integrations', 'websearch', 'events', 'cron', 'usage', 'logs', 'dash', 'limits', 'sec', 'update']) {
520
521
  $('#tab-' + p).hidden = p !== name;
521
522
  }
522
523
  if (name === 'cron') openCron();
@@ -526,6 +527,7 @@ function switchTab(name) {
526
527
  if (name === 'dash') renderDashboardPane();
527
528
  if (name === 'limits') renderLimitsPane();
528
529
  if (name === 'sec') renderSecurityPane();
530
+ if (name === 'update') renderUpdatePane();
529
531
  if (name === 'agents') refreshAgentsPane();
530
532
  if (name === 'websearch') renderWebSearchPane();
531
533
  /* Fallout: her açılışta güncel provider zincirini çek */
@@ -1542,8 +1544,84 @@ async function renderSecurityPane() {
1542
1544
  }
1543
1545
  }
1544
1546
 
1545
- /* Sohbete onay kartı düşürür Onayla / Her zaman / Reddet */
1546
- function showApprovalCard(ev) {
1547
+ /* ---------------- Update: sürüm kontrol + otomatik güncelleme ---------------- */
1548
+
1549
+ let updatePaneTimer = null;
1550
+
1551
+ function renderUpdateStateHtml(st) {
1552
+ let status;
1553
+ if (st.npm) status = _t('up_npm_mode');
1554
+ else if (st.error) status = '⚠ ' + st.error;
1555
+ else if (st.downloaded) status = _t('up_downloaded') + ' (v' + (st.version || '?') + ')';
1556
+ else if (st.progress) status = _t('up_downloading') + ' %' + st.progress.percent;
1557
+ else if (st.checking) status = _t('up_checking');
1558
+ else if (st.available) status = _t('up_available') + ' (v' + (st.version || '?') + ')';
1559
+ else if (st.available === false) status = _t('up_uptodate');
1560
+ else status = '—';
1561
+
1562
+ return `<div class="usage-stat" style="margin-top:10px"><div class="us-label">${_t('up_status')}</div><div class="us-value" style="font-size:14px">${escapeHtml(status)}</div></div>`;
1563
+ }
1564
+
1565
+ async function renderUpdatePane() {
1566
+ const pane = $('#tab-update');
1567
+ if (!pane) return;
1568
+ const st = await beast.updateStatus().catch(() => null);
1569
+ if (!st) return;
1570
+ clearInterval(updatePaneTimer);
1571
+
1572
+ pane.innerHTML =
1573
+ '<h2>' + _t('up_h2') + '</h2>' +
1574
+ '<div class="sub">' + _t('up_sub') + '</div>' +
1575
+ '<div class="usage-cards">' +
1576
+ `<div class="usage-stat"><div class="us-label">${_t('up_current')}</div><div class="us-value">v${escapeHtml(st.current)}</div></div>` +
1577
+ `<div class="usage-stat"><div class="us-label">${_t('up_latest')}</div><div class="us-value">${st.version ? 'v' + escapeHtml(st.version) : '—'}</div></div>` +
1578
+ '</div>' +
1579
+ renderUpdateStateHtml(st) +
1580
+ `<div class="fo-toggles" style="margin-top:12px">
1581
+ <label class="lock-row"><input type="checkbox" id="upAutoCheck" ${st.autoCheck ? 'checked' : ''}/><span>${_t('up_auto_check')}</span></label>
1582
+ <label class="lock-row"><input type="checkbox" id="upAutoDl" ${st.autoDownload ? 'checked' : ''}/><span>${_t('up_auto_dl')}</span></label>
1583
+ </div>` +
1584
+ (st.npm
1585
+ ? `<div class="codeblock npm" style="margin-top:10px"><pre># uygulamayı kapat, sonra:
1586
+ beast-agent update</pre></div>`
1587
+ : `<div class="form-grid" style="grid-template-columns:auto auto;gap:8px;margin-top:12px">
1588
+ <button id="upCheck" class="btn ghost">${_t('up_check_now')}</button>
1589
+ <button id="upInstall" class="btn ghost" ${st.downloaded ? '' : 'disabled style="opacity:.45;cursor:default"'}>${_t('up_install_now')}</button>
1590
+ </div>
1591
+ <div class="sub" style="margin-top:8px">${_t('up_note')}</div>`);
1592
+
1593
+ const chk = pane.querySelector('#upAutoCheck');
1594
+ if (chk) chk.addEventListener('change', async (e) => { await beast.updateSetAuto({ autoCheck: e.target.checked }); });
1595
+ const dl = pane.querySelector('#upAutoDl');
1596
+ if (dl) dl.addEventListener('change', async (e) => { await beast.updateSetAuto({ autoDownload: e.target.checked }); });
1597
+
1598
+ const btnCheck = pane.querySelector('#upCheck');
1599
+ if (btnCheck) btnCheck.addEventListener('click', async () => {
1600
+ btnCheck.disabled = true;
1601
+ const r = await beast.updateCheck().catch(() => ({ ok: false, error: 'ipc' }));
1602
+ if (!r.ok && r.error) toast(r.error);
1603
+ });
1604
+ const btnInstall = pane.querySelector('#upInstall');
1605
+ if (btnInstall) btnInstall.addEventListener('click', async () => {
1606
+ const r = await beast.updateInstall().catch(() => ({ ok: false, error: 'ipc' }));
1607
+ if (!r.ok && r.error) toast(r.error);
1608
+ });
1609
+
1610
+ /* indirme ilerlemesi için sekme açıkken canlı tazele */
1611
+ updatePaneTimer = setInterval(() => {
1612
+ if ($('#tab-update').hidden || els.settingsOverlay.hidden) { clearInterval(updatePaneTimer); return; }
1613
+ beast.updateStatus().then((s) => {
1614
+ if (!s) return;
1615
+ const box = pane.querySelector('.us-value');
1616
+ if (box) {
1617
+ const wrap = pane.querySelector('.usage-stat');
1618
+ if (wrap) wrap.outerHTML = renderUpdateStateHtml(s);
1619
+ }
1620
+ }).catch(() => {});
1621
+ }, 1000);
1622
+ }
1623
+
1624
+ /* Sohbete onay kartı düşürür — Onayla / Her zaman / Reddet */function showApprovalCard(ev) {
1547
1625
  streamEl = null;
1548
1626
  const card = document.createElement('div');
1549
1627
  card.className = 'tool-card';
@@ -2114,6 +2192,11 @@ function onEvent(ev) {
2114
2192
  showApprovalCard(ev);
2115
2193
  return;
2116
2194
  }
2195
+ if (ev.type === 'update') {
2196
+ if (ev.downloaded) toast(_t('up_downloaded') + ' (v' + (ev.version || '?') + ') — /update now');
2197
+ if (!els.settingsOverlay.hidden && setTab === 'update') renderUpdatePane();
2198
+ return;
2199
+ }
2117
2200
  if (ev.type === 'agents') {
2118
2201
  agentState.jobs = ev.jobs || [];
2119
2202
  updateAgentIds();