beast-agent 2.35.0 → 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 +3 -1
- package/src/agent/store.js +66 -21
- package/src/main.js +48 -8
- package/src/preload.js +1 -0
- package/src/renderer/i18n.js +4 -0
- package/src/renderer/index.html +0 -1
- package/src/renderer/renderer.js +54 -13
- package/src/renderer/style.css +16 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "beast-agent",
|
|
3
3
|
"productName": "Beast Agent",
|
|
4
|
-
"version": "2.
|
|
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"
|
package/src/agent/store.js
CHANGED
|
@@ -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
|
-
-
|
|
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
|
-
/* ----------
|
|
93
|
+
/* ---------- veri kaynağı (site → GitHub → cache) ---------- */
|
|
87
94
|
|
|
88
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
|
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),
|
package/src/main.js
CHANGED
|
@@ -317,6 +317,7 @@ let waHistory = new Map(); // jid -> [sid,...] bu sohbete ait tüm oturumlar
|
|
|
317
317
|
let waBcMode = new Set(); // jid -> BeastCode modu AKTİF (WhatsApp'tan uzaktan kodlama)
|
|
318
318
|
let waJidPn = new Map(); // jid -> gerçek telefon numarası (LID fallback için)
|
|
319
319
|
let waLastActiveJid = ''; // en son mesaj gelen WA sohbeti — kanal tek oturumunda cevap/dosya hedefi
|
|
320
|
+
let desktopActiveSid = ''; // masaüstü UI'da AÇIK olan oturum — proaktif not buraya işlenir
|
|
320
321
|
const WA_HISTORY_CAP = 20;
|
|
321
322
|
let tg = null;
|
|
322
323
|
let tgChats = new Map(); // telegram chatId -> aktif session id (hepsi KANAL TEK OTURUMUNA bağlı)
|
|
@@ -5081,11 +5082,34 @@ ipcMain.handle('sessions:create', () => {
|
|
|
5081
5082
|
return v;
|
|
5082
5083
|
});
|
|
5083
5084
|
ipcMain.handle('sessions:open', (_e, id) => engine.openSession(id));
|
|
5084
|
-
ipcMain.handle('sessions:delete', (_e, id) =>
|
|
5085
|
+
ipcMain.handle('sessions:delete', (_e, id) => {
|
|
5086
|
+
const sid = String(id || '');
|
|
5087
|
+
if (sid && sid === desktopActiveSid) desktopActiveSid = '';
|
|
5088
|
+
return engine.deleteSession(id);
|
|
5089
|
+
});
|
|
5090
|
+
/* masaüstü UI hangi sohbeti AÇIK tutuyorsa main'e bildirir: empati loop
|
|
5091
|
+
proaktif notu rastgele bir oturuma değil, kullanıcının gördüğü sohbete
|
|
5092
|
+
işler; meşgulse enjeksiyon kuyruğa girer, tur bitince geçmişe düşer */
|
|
5093
|
+
ipcMain.handle('sessions:active', (_e, id) => {
|
|
5094
|
+
const sid = String(id || '');
|
|
5095
|
+
if (!sid) {
|
|
5096
|
+
desktopActiveSid = '';
|
|
5097
|
+
return true;
|
|
5098
|
+
}
|
|
5099
|
+
try {
|
|
5100
|
+
if (sessionFileAlive(sid)) desktopActiveSid = sid;
|
|
5101
|
+
} catch {}
|
|
5102
|
+
return true;
|
|
5103
|
+
});
|
|
5085
5104
|
|
|
5086
5105
|
ipcMain.handle('agent:send', (_e, { sessionId, text }) => {
|
|
5087
5106
|
const raw = text && typeof text === 'object' ? String(text.text || '') : String(text ?? '');
|
|
5088
5107
|
const t = raw.trim();
|
|
5108
|
+
/* yazan sohbet = kullanıcının gördüğü sohbet: proaktif enjeksiyon hedefi */
|
|
5109
|
+
try {
|
|
5110
|
+
const asid = String(sessionId || '');
|
|
5111
|
+
if (asid && sessionFileAlive(asid)) desktopActiveSid = asid;
|
|
5112
|
+
} catch {}
|
|
5089
5113
|
/* BOT HAFIZA GARANTİSİ: aktif bot bir MÜŞTERİ botuysa, botId'siz (eskiden
|
|
5090
5114
|
kalma) oturumlar bu bota bağlanır — bot konuşması asla Beast'in global
|
|
5091
5115
|
hafızasıyla (SOUL/USER/MEMORY) yürümez. Var olan botId asla üstüne yazılmaz. */
|
|
@@ -7210,10 +7234,18 @@ function ensureDcSession(channelId) {
|
|
|
7210
7234
|
return dcSingleSid;
|
|
7211
7235
|
}
|
|
7212
7236
|
|
|
7213
|
-
/* Masaüstü
|
|
7214
|
-
|
|
7237
|
+
/* Masaüstü hedefi: kullanıcının AÇIK sohbeti önceliklidir — proaktif not
|
|
7238
|
+
nereye bakıyorsa oraya işlenir; meşgulse empatiInjectToSession kuyruğa alır.
|
|
7239
|
+
Açık sohbet bilinmiyorsa en güncel meşgul olmayan sohbet (kanal oturumları
|
|
7240
|
+
hariç — onlara kanal yoluyla zaten enjekte edildi); o da yoksa yeni oturum. */
|
|
7241
|
+
function empatiDesktopSid(exclude) {
|
|
7242
|
+
const skip = exclude instanceof Set ? exclude : null;
|
|
7243
|
+
try {
|
|
7244
|
+
if (desktopActiveSid && sessionFileAlive(desktopActiveSid)) return String(desktopActiveSid);
|
|
7245
|
+
} catch {}
|
|
7215
7246
|
try {
|
|
7216
7247
|
for (const v of engine.listSessions()) {
|
|
7248
|
+
if (skip && skip.has(String(v.id))) continue;
|
|
7217
7249
|
if (!engine.isBusy(v.id)) return String(v.id);
|
|
7218
7250
|
}
|
|
7219
7251
|
} catch {}
|
|
@@ -7234,14 +7266,17 @@ function empatiNotify(text, ev) {
|
|
|
7234
7266
|
const outWa = PROACTIVE_MARK + '\n' + text + (srcWa ? '\n' + srcWa : '');
|
|
7235
7267
|
const inject = empatiInjectText(ev, out);
|
|
7236
7268
|
const senders = [];
|
|
7269
|
+
const channelSids = new Set(); /* bu tur enjeksiyon ALAN kanal oturumları */
|
|
7237
7270
|
const tryWa = () => {
|
|
7238
7271
|
try {
|
|
7239
7272
|
const own = waOwnerNum();
|
|
7240
7273
|
if (own && wa && wa.connected) {
|
|
7241
7274
|
const jid = own + '@s.whatsapp.net';
|
|
7275
|
+
const sid = ensureWaSession(jid);
|
|
7276
|
+
channelSids.add(String(sid));
|
|
7242
7277
|
senders.push(() =>
|
|
7243
7278
|
Promise.resolve(sendWaSafe(jid, outWa))
|
|
7244
|
-
.then(() => empatiInjectToSession(
|
|
7279
|
+
.then(() => empatiInjectToSession(sid, inject))
|
|
7245
7280
|
.catch(() => {})
|
|
7246
7281
|
);
|
|
7247
7282
|
}
|
|
@@ -7251,9 +7286,11 @@ function empatiNotify(text, ev) {
|
|
|
7251
7286
|
try {
|
|
7252
7287
|
if (tg && tg.connected) {
|
|
7253
7288
|
for (const id of tgOwnerIds()) {
|
|
7289
|
+
const sid = ensureTgSession(String(id));
|
|
7290
|
+
channelSids.add(String(sid));
|
|
7254
7291
|
senders.push(() =>
|
|
7255
7292
|
Promise.resolve(sendTgSafe(id, outPlain))
|
|
7256
|
-
.then(() => empatiInjectToSession(
|
|
7293
|
+
.then(() => empatiInjectToSession(sid, inject))
|
|
7257
7294
|
.catch(() => {})
|
|
7258
7295
|
);
|
|
7259
7296
|
}
|
|
@@ -7265,9 +7302,11 @@ function empatiNotify(text, ev) {
|
|
|
7265
7302
|
if (dc && dc.connected) {
|
|
7266
7303
|
const outDc = out.replace('🫡 *Beast proaktif:*', '🫡 **Beast proaktif:**');
|
|
7267
7304
|
for (const id of dcOwnerIds()) {
|
|
7305
|
+
const sid = ensureDcSession(String(id));
|
|
7306
|
+
channelSids.add(String(sid));
|
|
7268
7307
|
senders.push(() =>
|
|
7269
7308
|
Promise.resolve(sendDcSafe(id, outDc))
|
|
7270
|
-
.then(() => empatiInjectToSession(
|
|
7309
|
+
.then(() => empatiInjectToSession(sid, inject))
|
|
7271
7310
|
.catch(() => {})
|
|
7272
7311
|
);
|
|
7273
7312
|
}
|
|
@@ -7289,8 +7328,9 @@ function empatiNotify(text, ev) {
|
|
|
7289
7328
|
gider (kanal kullanıcısı ham BAĞLAM bloğunu görmez, ajan görür). */
|
|
7290
7329
|
try {
|
|
7291
7330
|
if (win && !win.isDestroyed()) {
|
|
7292
|
-
const dsid = empatiDesktopSid();
|
|
7293
|
-
|
|
7331
|
+
const dsid = String(empatiDesktopSid(channelSids) || '');
|
|
7332
|
+
/* aynı oturuma kanal yoluyla zaten enjekte edildiyse TEKRAR yazma */
|
|
7333
|
+
if (dsid && !channelSids.has(dsid)) empatiInjectToSession(dsid, out);
|
|
7294
7334
|
win.webContents.send('agent:event', { type: 'proactive', sessionId: dsid, id: ev.id, level: ev.level, title: ev.title, text: out });
|
|
7295
7335
|
}
|
|
7296
7336
|
} catch {}
|
package/src/preload.js
CHANGED
|
@@ -10,6 +10,7 @@ contextBridge.exposeInMainWorld('beast', {
|
|
|
10
10
|
listSessions: () => ipcRenderer.invoke('sessions:list'),
|
|
11
11
|
createSession: () => ipcRenderer.invoke('sessions:create'),
|
|
12
12
|
openSession: (id) => ipcRenderer.invoke('sessions:open', id),
|
|
13
|
+
setActiveSession: (id) => ipcRenderer.invoke('sessions:active', id),
|
|
13
14
|
deleteSession: (id) => ipcRenderer.invoke('sessions:delete', id),
|
|
14
15
|
send: (sessionId, text) => ipcRenderer.invoke('agent:send', { sessionId, text }),
|
|
15
16
|
interrupt: (sessionId, reason) => ipcRenderer.invoke('agent:interrupt', sessionId, reason),
|
package/src/renderer/i18n.js
CHANGED
|
@@ -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.',
|
package/src/renderer/index.html
CHANGED
|
@@ -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>
|
package/src/renderer/renderer.js
CHANGED
|
@@ -1077,6 +1077,7 @@ setInterval(() => {
|
|
|
1077
1077
|
|
|
1078
1078
|
async function openSession(id) {
|
|
1079
1079
|
activeId = id;
|
|
1080
|
+
try { beast.setActiveSession(id); } catch {}
|
|
1080
1081
|
streamEl = null;
|
|
1081
1082
|
const s = await beast.openSession(id);
|
|
1082
1083
|
els.msgs.innerHTML = '';
|
|
@@ -7675,33 +7676,73 @@ function closeStore() {
|
|
|
7675
7676
|
$('#storeOverlay').hidden = true;
|
|
7676
7677
|
}
|
|
7677
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
|
+
|
|
7678
7716
|
function storeCard(e, opts = {}) {
|
|
7679
|
-
const
|
|
7680
|
-
const
|
|
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);
|
|
7681
7720
|
const img = e.image
|
|
7682
7721
|
? `<span class="store-avatar"><img src="${e.image}" alt="" style="width:100%;height:100%;object-fit:cover;border-radius:8px"/></span>`
|
|
7683
|
-
: `<span class="store-avatar">${e.
|
|
7722
|
+
: `<span class="store-avatar store-avatar-ic">${storeIconSvg(e.icon, 20)}</span>`;
|
|
7684
7723
|
const author = e.author || {};
|
|
7685
|
-
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');
|
|
7686
7725
|
const btnDone = installed && !hasUpdate;
|
|
7687
7726
|
return `
|
|
7688
7727
|
<div class="store-card" data-id="${escapeHtml(e.id)}">
|
|
7689
7728
|
<div class="store-card-top">
|
|
7690
7729
|
${img}
|
|
7691
7730
|
<div style="min-width:0;flex:1">
|
|
7692
|
-
<div class="store-name" title="${escapeHtml(e.name)}">${escapeHtml(e.name)} <span class="store-ver">v${escapeHtml(e.version || '1.0.0')}</span
|
|
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>
|
|
7693
7732
|
<div class="store-author">
|
|
7694
|
-
<span class="sa-avatar">${author.avatar ? escapeHtml(author.avatar) : '
|
|
7695
|
-
<span class="sa-name">${escapeHtml(author.username || '—')}</span>
|
|
7696
|
-
${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>` : ''}
|
|
7697
7736
|
</div>
|
|
7698
7737
|
</div>
|
|
7699
7738
|
</div>
|
|
7700
7739
|
<div class="store-desc">${escapeHtml(e.description || '')}</div>
|
|
7701
7740
|
${(e.tags || []).length ? `<div class="store-tags">${e.tags.map((t) => `<span class="store-tag">${escapeHtml(t)}</span>`).join('')}</div>` : ''}
|
|
7702
7741
|
<div class="store-card-foot">
|
|
7703
|
-
|
|
7704
|
-
|
|
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>`}
|
|
7705
7746
|
<button class="store-install ${btnDone ? 'done' : ''}" data-install="${escapeHtml(e.id)}">${btnLabel}</button>
|
|
7706
7747
|
</div>
|
|
7707
7748
|
</div>`;
|
|
@@ -7710,6 +7751,7 @@ function storeCard(e, opts = {}) {
|
|
|
7710
7751
|
function bindStoreCardActions(pane) {
|
|
7711
7752
|
pane.querySelectorAll('[data-install]').forEach((btn) =>
|
|
7712
7753
|
btn.addEventListener('click', async () => {
|
|
7754
|
+
if (btn.classList.contains('done')) return; /* kurulu/yerleşik — tıklama yok */
|
|
7713
7755
|
const id = btn.dataset.install;
|
|
7714
7756
|
btn.disabled = true;
|
|
7715
7757
|
const r = await beast.storeInstall(id).catch(() => null);
|
|
@@ -7738,7 +7780,7 @@ function bindStoreCardActions(pane) {
|
|
|
7738
7780
|
function renderStoreList(mode) {
|
|
7739
7781
|
const pane = $('#storePane');
|
|
7740
7782
|
const list = storeState.entries
|
|
7741
|
-
.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))
|
|
7742
7784
|
.sort((a, b) => (mode === 'stars' ? storeStarsScore(b) - storeStarsScore(a) : storeTrendingScore(b) - storeTrendingScore(a)))
|
|
7743
7785
|
.slice(0, 30);
|
|
7744
7786
|
pane.innerHTML =
|
|
@@ -7956,8 +7998,7 @@ function renderStoreUpload() {
|
|
|
7956
7998
|
}
|
|
7957
7999
|
|
|
7958
8000
|
function renderStorePane() {
|
|
7959
|
-
|
|
7960
|
-
else renderStoreList(storeState.tab);
|
|
8001
|
+
renderStoreList(storeState.tab);
|
|
7961
8002
|
const bid = $('#storeBeastId');
|
|
7962
8003
|
if (bid) bid.textContent = storeState.beastId || '';
|
|
7963
8004
|
}
|
package/src/renderer/style.css
CHANGED
|
@@ -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; }
|