beast-agent 2.35.1 → 2.36.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "beast-agent",
3
3
  "productName": "Beast Agent",
4
- "version": "2.35.1",
4
+ "version": "2.36.0",
5
5
  "description": "Ultra-fast local agent shell for Windows.",
6
6
  "author": "algokodcom (AlgoKod)",
7
7
  "license": "MIT",
@@ -40,6 +40,8 @@
40
40
  "dist": "electron-builder --win nsis portable",
41
41
  "test": "node --test \"tests/**/*.test.js\"",
42
42
  "release": "node scripts/release.js",
43
+ "skillsweb:sync": "node skillsweb/sync-builtins.js",
44
+ "skillsweb:deploy": "node skillsweb/sync-builtins.js && cd skillsweb && firebase deploy --only hosting:skills",
43
45
  "postinstall": "node scripts/fix-electron.js --soft",
44
46
  "prepublishOnly": "node scripts/swap-electron.js deps",
45
47
  "postpublish": "node scripts/swap-electron.js devdeps"
@@ -3,7 +3,8 @@
3
3
  /* SKILLS STORE: topluluk skill mağazası.
4
4
  - Yerel DB : %APPDATA%\beast\store\skills.json (yüklenen skill'ler, dosyalar gömülü)
5
5
  - İstatistik DB : %APPDATA%\beast\store\stats.json (installs/likes — makine başına)
6
- - Topluluk indeksi: GitHub repo /store/skills.json (raw fetch + offline cache)
6
+ - Veri kaynağı : beastagent-skills.web.app (builtins.json + community.json)
7
+ erişilemezse GitHub raw /store/skills.json, o da yoksa offline cache
7
8
  - Kurulum hedefi : %APPDATA%\beast\skills\<id>\ (skills.scan() otomatik görür)
8
9
  Trending/Stars sıralaması renderer'da skorla yapılır; burada ham veri döner.
9
10
  Beast Kodu ASLA paylaşılmaz — yalnız sha256 türevi parmak izi (beastId) kullanılır. */
@@ -14,6 +15,9 @@ const crypto = require('crypto');
14
15
  const { beastRoot } = require('./memory');
15
16
  const skills = require('./skills');
16
17
 
18
+ const SITE_URL = 'https://beastagent-skills.web.app';
19
+ const SITE_COMMUNITY_URL = SITE_URL + '/community.json';
20
+ const SITE_BUILTINS_URL = SITE_URL + '/builtins.json';
17
21
  const COMMUNITY_URL = 'https://raw.githubusercontent.com/algokodcom/beast-agent/main/store/skills.json';
18
22
  const MAX_FILES = 20;
19
23
  const MAX_FILE_BYTES = 200 * 1024;
@@ -33,6 +37,9 @@ function statsFile() {
33
37
  function communityCacheFile() {
34
38
  return path.join(storeDir(), 'community.json');
35
39
  }
40
+ function builtinsCacheFile() {
41
+ return path.join(storeDir(), 'builtins.json');
42
+ }
36
43
 
37
44
  function readJson(p, fallback) {
38
45
  try {
@@ -83,32 +90,64 @@ function slugify(s) {
83
90
  );
84
91
  }
85
92
 
86
- /* ---------- topluluk indeksi ---------- */
93
+ /* ---------- veri kaynağı (site → GitHub → cache) ---------- */
87
94
 
88
- async function fetchCommunity(timeoutMs = 6000) {
95
+ const MEMO_MS = 5 * 60 * 1000;
96
+ const memo = { community: null, communityAt: 0, builtins: null, builtinsAt: 0 };
97
+
98
+ async function fetchJson(url, timeoutMs = 6000) {
99
+ const ctl = typeof AbortController !== 'undefined' ? new AbortController() : null;
100
+ const t = setTimeout(() => {
101
+ try {
102
+ ctl && ctl.abort();
103
+ } catch {}
104
+ }, timeoutMs);
89
105
  try {
90
- const ctl = typeof AbortController !== 'undefined' ? new AbortController() : null;
91
- const t = setTimeout(() => {
92
- try {
93
- ctl && ctl.abort();
94
- } catch {}
95
- }, timeoutMs);
96
- const r = await fetch(COMMUNITY_URL, { signal: ctl ? ctl.signal : undefined });
97
- clearTimeout(t);
106
+ const r = await fetch(url, { signal: ctl ? ctl.signal : undefined });
98
107
  if (!r.ok) throw new Error('HTTP ' + r.status);
99
- const j = await r.json();
108
+ return await r.json();
109
+ } finally {
110
+ clearTimeout(t);
111
+ }
112
+ }
113
+
114
+ /* Topluluk indeksi: önce SİTE (beastagent-skills.web.app), sonra GitHub raw */
115
+ async function fetchCommunity(timeoutMs = 6000) {
116
+ if (memo.community && Date.now() - memo.communityAt < MEMO_MS) return memo.community;
117
+ for (const url of [SITE_COMMUNITY_URL, COMMUNITY_URL]) {
118
+ try {
119
+ const j = await fetchJson(url, timeoutMs);
120
+ if (j && Array.isArray(j.skills)) {
121
+ try {
122
+ writeJson(communityCacheFile(), j);
123
+ } catch {}
124
+ memo.community = j.skills;
125
+ memo.communityAt = Date.now();
126
+ return j.skills;
127
+ }
128
+ } catch {}
129
+ }
130
+ /* offline: son iyi kopyadan devam */
131
+ const cached = readJson(communityCacheFile(), { skills: [] });
132
+ return Array.isArray(cached.skills) ? cached.skills : [];
133
+ }
134
+
135
+ /* Yerleşik skill vitrini: sitedeki builtins.json (offline'da diske cache) */
136
+ async function fetchBuiltins(timeoutMs = 6000) {
137
+ if (memo.builtins && Date.now() - memo.builtinsAt < MEMO_MS) return memo.builtins;
138
+ try {
139
+ const j = await fetchJson(SITE_BUILTINS_URL, timeoutMs);
100
140
  if (j && Array.isArray(j.skills)) {
101
141
  try {
102
- writeJson(communityCacheFile(), j);
142
+ writeJson(builtinsCacheFile(), j);
103
143
  } catch {}
144
+ memo.builtins = j.skills;
145
+ memo.builtinsAt = Date.now();
104
146
  return j.skills;
105
147
  }
106
- return [];
107
- } catch {
108
- /* offline: son iyi kopyadan devam */
109
- const cached = readJson(communityCacheFile(), { skills: [] });
110
- return Array.isArray(cached.skills) ? cached.skills : [];
111
- }
148
+ } catch {}
149
+ const cached = readJson(builtinsCacheFile(), { skills: [] });
150
+ return Array.isArray(cached.skills) ? cached.skills : [];
112
151
  }
113
152
 
114
153
  function mergeStats(entry, stats) {
@@ -122,18 +161,24 @@ function mergeStats(entry, stats) {
122
161
  };
123
162
  }
124
163
 
125
- /* Birleşik liste: topluluk + yerel (aynı id'de yerel kazanır) */
164
+ /* Birleşik liste: site (yerleşikler + topluluk) + yerel; aynı id'de yerel kazanır.
165
+ Yerleşikler `builtin:true` taşır — kart "Kurulu" gösterir, Trending/Stars'ta görünür. */
126
166
  async function list(beastCode) {
127
- const [community] = await Promise.all([fetchCommunity()]);
167
+ const [community, builtins] = await Promise.all([fetchCommunity(), fetchBuiltins()]);
128
168
  const db = loadLocal();
129
169
  const stats = loadStats();
130
170
  const byId = new Map();
131
171
  for (const e of community) {
132
172
  if (e && e.id && e.files && e.files['SKILL.md']) byId.set(e.id, e);
133
173
  }
174
+ for (const b of builtins) {
175
+ if (!b || !b.id || !b.files || !b.files['SKILL.md']) continue;
176
+ byId.set(b.id, { ...b, builtin: true });
177
+ }
134
178
  for (const e of db.skills) if (e && e.id) byId.set(e.id, e);
135
179
  const entries = [...byId.values()].map((e) => mergeStats(e, stats));
136
180
  const installed = new Set(skills.scan().map((s) => path.basename(path.dirname(s.path))));
181
+ for (const e of entries) if (e.builtin) installed.add(e.id);
137
182
  return {
138
183
  ok: true,
139
184
  beastId: beastFingerprint(beastCode),
@@ -620,6 +620,8 @@
620
620
  store_update: 'Güncelle',
621
621
  store_installs: 'kurulum',
622
622
  store_likes: 'beğeni',
623
+ store_builtin: 'Yerleşik',
624
+ store_builtin_note: 'Ajanla birlikte gelir',
623
625
  store_empty_trending: 'Mağaza henüz boş — ilk skill\u2019i sen yükle!',
624
626
  store_empty_stars: 'Henüz yıldız skill yok — uzun süre popüler olanlar buraya yükselir.',
625
627
  store_offline: 'Topluluk indeksine ulaşılamadı (offline) — yerel kayıtlar gösteriliyor.',
@@ -1411,6 +1413,8 @@
1411
1413
  store_update: 'Update',
1412
1414
  store_installs: 'installs',
1413
1415
  store_likes: 'likes',
1416
+ store_builtin: 'Built-in',
1417
+ store_builtin_note: 'Ships with the agent',
1414
1418
  store_empty_trending: 'The store is empty — be the first to upload a skill!',
1415
1419
  store_empty_stars: 'No star skills yet — long-time popular ones rise here.',
1416
1420
  store_offline: 'Community index unreachable (offline) — showing local entries.',
@@ -615,7 +615,6 @@
615
615
  <nav id="storeTabs">
616
616
  <button class="stab active" data-stab="trending" data-i18n="store_tab_trending">Trending</button>
617
617
  <button class="stab" data-stab="stars" data-i18n="store_tab_stars">Stars</button>
618
- <button class="stab" data-stab="upload" data-i18n="store_tab_upload">Upload</button>
619
618
  </nav>
620
619
  <div id="storePane"></div>
621
620
  </div>
@@ -7676,33 +7676,73 @@ function closeStore() {
7676
7676
  $('#storeOverlay').hidden = true;
7677
7677
  }
7678
7678
 
7679
+ /* Mağaza kart ikonları — kapağı olmayan skill'lere otomatik SVG ikon atanır
7680
+ (site builtins.json / community kayıtlarındaki `icon` alanı kullanılır). */
7681
+ const STORE_ICONS = {
7682
+ grid: '<rect x="3.5" y="3.5" width="7" height="7" rx="1.5"/><rect x="13.5" y="3.5" width="7" height="7" rx="1.5"/><rect x="3.5" y="13.5" width="7" height="7" rx="1.5"/><rect x="13.5" y="13.5" width="7" height="7" rx="1.5"/>',
7683
+ 'file-text': '<path d="M13 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V9z"/><path d="M13 3v6h6M9 13h6M9 17h4"/>',
7684
+ wrench: '<path d="M15.5 3.5a5.5 5.5 0 0 0-4.6 8.5L4 18.9a2 2 0 0 0 2.8 2.8l6.9-6.9a5.5 5.5 0 0 0 6.9-7.3l-3.2 3.2-2.8-.7-.7-2.8z"/>',
7685
+ zap: '<path d="M13 2 4.5 13.5H11L10 22l8.5-11.5H12z"/>',
7686
+ bar: '<path d="M5 20V11M12 20V5M19 20v-6"/>',
7687
+ shield: '<path d="M12 3l7.5 3v5.5c0 4.6-3.1 7.9-7.5 9.5-4.4-1.6-7.5-4.9-7.5-9.5V6z"/><path d="m9 12 2 2 4-4"/>',
7688
+ book: '<path d="M4 4.5A2.5 2.5 0 0 1 6.5 2H20v16H6.5A2.5 2.5 0 0 0 4 20.5z"/><path d="M4 20.5A2.5 2.5 0 0 1 6.5 18H20v4H6.5A2.5 2.5 0 0 1 4 19.5z"/>',
7689
+ cpu: '<rect x="6.5" y="6.5" width="11" height="11" rx="2"/><rect x="10" y="10" width="4" height="4" rx="1"/><path d="M9 3v2.5M15 3v2.5M9 18.5V21M15 18.5V21M3 9h2.5M3 15h2.5M18.5 9H21M18.5 15H21"/>',
7690
+ flask: '<path d="M9.5 3h5M10.5 3v6.2L5 18.5A2 2 0 0 0 6.8 21h10.4a2 2 0 0 0 1.8-2.5L13.5 9.2V3"/><path d="M7.5 15h9"/>',
7691
+ news: '<path d="M4 5h13a2 2 0 0 1 2 2v11a2 2 0 0 0 2 2H6a2 2 0 0 1-2-2z"/><path d="M7 9h7M7 13h7M7 17h4"/>',
7692
+ compass: '<circle cx="12" cy="12" r="8.5"/><path d="m15.5 8.5-2 5-5 2 2-5z"/>',
7693
+ mail: '<rect x="3" y="5.5" width="18" height="13" rx="2"/><path d="m3.5 7 8.5 6 8.5-6"/>',
7694
+ activity: '<path d="M3 12h4l2.5-6 4 12L16 12h5"/>',
7695
+ bulb: '<path d="M9 18h6M10 21h4"/><path d="M12 3a6 6 0 0 1 3.5 10.9c-.6.5-1 1.3-1 2.1h-5c0-.8-.4-1.6-1-2.1A6 6 0 0 1 12 3z"/>',
7696
+ clipboard: '<rect x="5" y="4.5" width="14" height="16" rx="2"/><path d="M9 4.5V3.8A1.8 1.8 0 0 1 10.8 2h2.4A1.8 1.8 0 0 1 15 3.8v.7M9 10h6M9 14h6M9 18h3"/>',
7697
+ play: '<path d="M7 4.5v15l12-7.5z"/>',
7698
+ bug: '<rect x="8" y="7" width="8" height="11" rx="4"/><path d="M9.5 7a2.5 2.5 0 0 1 5 0M3.5 11H8M16 11h4.5M4.5 17H8M16 17h3.5M6 6.5 8 9M18 6.5 16 9"/>',
7699
+ 'check-circle': '<circle cx="12" cy="12" r="8.5"/><path d="m8.5 12.2 2.4 2.4 4.6-4.8"/>',
7700
+ search: '<circle cx="11" cy="11" r="6.5"/><path d="m16.2 16.2 4.3 4.3"/>',
7701
+ trending: '<path d="m3.5 17 5.5-5.5 3.5 3.5 7-7"/><path d="M15 8h5v5"/>',
7702
+ download: '<path d="M12 3v11M7.5 10.5 12 15l4.5-4.5"/><path d="M4 18v1.5A1.5 1.5 0 0 0 5.5 21h13A1.5 1.5 0 0 0 20 19.5V18"/>',
7703
+ heart: '<path d="M12 20s-7-4.4-7-9.4A4.1 4.1 0 0 1 12 7.6a4.1 4.1 0 0 1 7 3c0 5-7 9.4-7 9.4z"/>',
7704
+ box: '<path d="M3.5 7.5 12 3l8.5 4.5v9L12 21l-8.5-4.5z"/><path d="M3.5 7.5 12 12l8.5-4.5M12 12v9"/>',
7705
+ user: '<circle cx="12" cy="8" r="3.6"/><path d="M5 20c.6-3.4 3.5-5.4 7-5.4s6.4 2 7 5.4"/>',
7706
+ paw: '<circle cx="7" cy="9" r="2.2"/><circle cx="12" cy="7" r="2.2"/><circle cx="17" cy="9" r="2.2"/><path d="M12 12c-3.2 0-5.5 2.4-5.5 4.6 0 1.6 1.3 2.4 2.9 2.4 1 0 1.8-.4 2.6-.4s1.6.4 2.6.4c1.6 0 2.9-.8 2.9-2.4C17.5 14.4 15.2 12 12 12Z"/>',
7707
+ };
7708
+
7709
+ function storeIconSvg(name, size) {
7710
+ const s = Number(size) || 20;
7711
+ const body = STORE_ICONS[name] || STORE_ICONS.grid;
7712
+ const filled = name === 'paw';
7713
+ return `<svg viewBox="0 0 24 24" width="${s}" height="${s}" fill="${filled ? 'currentColor' : 'none'}" stroke="${filled ? 'none' : 'currentColor'}" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">${body}</svg>`;
7714
+ }
7715
+
7679
7716
  function storeCard(e, opts = {}) {
7680
- const installed = storeState.installed.has(e.id);
7681
- const hasUpdate = installed && e.updatedAt && e.installedAt && String(e.updatedAt) > String(e.installedAt);
7717
+ const builtin = !!e.builtin;
7718
+ const installed = builtin || storeState.installed.has(e.id);
7719
+ const hasUpdate = !builtin && installed && e.updatedAt && e.installedAt && String(e.updatedAt) > String(e.installedAt);
7682
7720
  const img = e.image
7683
7721
  ? `<span class="store-avatar"><img src="${e.image}" alt="" style="width:100%;height:100%;object-fit:cover;border-radius:8px"/></span>`
7684
- : `<span class="store-avatar">${e.author && e.author.avatar ? escapeHtml(e.author.avatar) : '🧩'}</span>`;
7722
+ : `<span class="store-avatar store-avatar-ic">${storeIconSvg(e.icon, 20)}</span>`;
7685
7723
  const author = e.author || {};
7686
- const btnLabel = !installed ? _t('store_install') : hasUpdate ? _t('store_update') : _t('store_installed');
7724
+ const btnLabel = builtin ? _t('store_installed') : !installed ? _t('store_install') : hasUpdate ? _t('store_update') : _t('store_installed');
7687
7725
  const btnDone = installed && !hasUpdate;
7688
7726
  return `
7689
7727
  <div class="store-card" data-id="${escapeHtml(e.id)}">
7690
7728
  <div class="store-card-top">
7691
7729
  ${img}
7692
7730
  <div style="min-width:0;flex:1">
7693
- <div class="store-name" title="${escapeHtml(e.name)}">${escapeHtml(e.name)} <span class="store-ver">v${escapeHtml(e.version || '1.0.0')}</span></div>
7731
+ <div class="store-name" title="${escapeHtml(e.name)}">${escapeHtml(e.name)} <span class="store-ver">v${escapeHtml(e.version || '1.0.0')}</span>${builtin ? ` <span class="store-builtin">${_t('store_builtin')}</span>` : ''}</div>
7694
7732
  <div class="store-author">
7695
- <span class="sa-avatar">${author.avatar ? escapeHtml(author.avatar) : '👤'}</span>
7696
- <span class="sa-name">${escapeHtml(author.username || '—')}</span>
7697
- ${author.beastId ? `<span class="sa-beast">${escapeHtml(author.beastId)}</span>` : ''}
7733
+ <span class="sa-avatar">${builtin ? storeIconSvg('paw', 13) : (author.avatar ? escapeHtml(author.avatar) : storeIconSvg('user', 13))}</span>
7734
+ <span class="sa-name">${escapeHtml(builtin ? 'beast' : (author.username || '—'))}</span>
7735
+ ${!builtin && author.beastId ? `<span class="sa-beast">${escapeHtml(author.beastId)}</span>` : ''}
7698
7736
  </div>
7699
7737
  </div>
7700
7738
  </div>
7701
7739
  <div class="store-desc">${escapeHtml(e.description || '')}</div>
7702
7740
  ${(e.tags || []).length ? `<div class="store-tags">${e.tags.map((t) => `<span class="store-tag">${escapeHtml(t)}</span>`).join('')}</div>` : ''}
7703
7741
  <div class="store-card-foot">
7704
- <span class="store-stats"><span>⬇ ${e.installs || 0} ${_t('store_installs')}</span><span>♥ ${e.likes || 0} ${_t('store_likes')}</span></span>
7705
- <button class="store-like ${e.liked ? 'liked' : ''}" data-like="${escapeHtml(e.id)}">♥</button>
7742
+ ${builtin
7743
+ ? `<span class="store-stats"><span>${storeIconSvg('box', 13)} ${_t('store_builtin_note')}</span></span>`
7744
+ : `<span class="store-stats"><span>${storeIconSvg('download', 13)} ${e.installs || 0} ${_t('store_installs')}</span><span>${storeIconSvg('heart', 13)} ${e.likes || 0} ${_t('store_likes')}</span></span>
7745
+ <button class="store-like ${e.liked ? 'liked' : ''}" data-like="${escapeHtml(e.id)}">${storeIconSvg('heart', 14)}</button>`}
7706
7746
  <button class="store-install ${btnDone ? 'done' : ''}" data-install="${escapeHtml(e.id)}">${btnLabel}</button>
7707
7747
  </div>
7708
7748
  </div>`;
@@ -7711,6 +7751,7 @@ function storeCard(e, opts = {}) {
7711
7751
  function bindStoreCardActions(pane) {
7712
7752
  pane.querySelectorAll('[data-install]').forEach((btn) =>
7713
7753
  btn.addEventListener('click', async () => {
7754
+ if (btn.classList.contains('done')) return; /* kurulu/yerleşik — tıklama yok */
7714
7755
  const id = btn.dataset.install;
7715
7756
  btn.disabled = true;
7716
7757
  const r = await beast.storeInstall(id).catch(() => null);
@@ -7739,7 +7780,7 @@ function bindStoreCardActions(pane) {
7739
7780
  function renderStoreList(mode) {
7740
7781
  const pane = $('#storePane');
7741
7782
  const list = storeState.entries
7742
- .filter((e) => (mode === 'stars' ? storeAgeDays(e.createdAt) >= 14 && storeStarsScore(e) > 0 : true))
7783
+ .filter((e) => (mode === 'stars' ? (e.builtin || (storeAgeDays(e.createdAt) >= 14 && storeStarsScore(e) > 0)) : true))
7743
7784
  .sort((a, b) => (mode === 'stars' ? storeStarsScore(b) - storeStarsScore(a) : storeTrendingScore(b) - storeTrendingScore(a)))
7744
7785
  .slice(0, 30);
7745
7786
  pane.innerHTML =
@@ -7957,8 +7998,7 @@ function renderStoreUpload() {
7957
7998
  }
7958
7999
 
7959
8000
  function renderStorePane() {
7960
- if (storeState.tab === 'upload') renderStoreUpload();
7961
- else renderStoreList(storeState.tab);
8001
+ renderStoreList(storeState.tab);
7962
8002
  const bid = $('#storeBeastId');
7963
8003
  if (bid) bid.textContent = storeState.beastId || '';
7964
8004
  }
@@ -3852,6 +3852,22 @@ body.browser-open #storeOverlay { right: var(--bw, 480px); }
3852
3852
  }
3853
3853
  .store-name { font-weight: 700; font-size: 13.5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
3854
3854
  .store-ver { font-size: 10.5px; color: var(--muted); }
3855
+ .store-builtin {
3856
+ font-size: 9.5px;
3857
+ font-weight: 700;
3858
+ letter-spacing: 0.5px;
3859
+ color: var(--muted);
3860
+ border: 1px solid var(--border);
3861
+ border-radius: 20px;
3862
+ padding: 1px 7px;
3863
+ background: var(--panel2);
3864
+ vertical-align: 1px;
3865
+ }
3866
+ .store-avatar-ic { color: var(--text); }
3867
+ .store-avatar-ic svg { display: block; }
3868
+ .store-author .sa-avatar svg { display: inline-block; vertical-align: -2px; }
3869
+ .store-stats svg { vertical-align: -2px; }
3870
+ .store-like svg { display: block; }
3855
3871
  .store-author { display: flex; align-items: center; gap: 5px; font-size: 11.5px; color: var(--muted); min-width: 0; }
3856
3872
  .store-author .sa-avatar { font-size: 13px; }
3857
3873
  .store-author .sa-name { font-weight: 700; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }