@kevin5251984/guild 0.2.12
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/LICENSE +21 -0
- package/bin/guildd.mjs +20 -0
- package/cordis.yml +24 -0
- package/package.json +52 -0
- package/src/agent-file.ts +125 -0
- package/src/browser.ts +668 -0
- package/src/catalog/default-bots.ts +263 -0
- package/src/catalog/skills.ts +128 -0
- package/src/catalog/subagents.ts +70 -0
- package/src/chat-parts.ts +71 -0
- package/src/cli-args.ts +75 -0
- package/src/cli.ts +60 -0
- package/src/compact.ts +355 -0
- package/src/cordis.d.ts +40 -0
- package/src/db.ts +653 -0
- package/src/generate.ts +673 -0
- package/src/handlers.ts +1623 -0
- package/src/harness.ts +326 -0
- package/src/host-agents.ts +137 -0
- package/src/host-browse.ts +199 -0
- package/src/host-skills.ts +150 -0
- package/src/image-gen.ts +270 -0
- package/src/index.ts +12 -0
- package/src/llm.ts +993 -0
- package/src/mcp.ts +563 -0
- package/src/memory.ts +159 -0
- package/src/mention.ts +176 -0
- package/src/oauth.ts +1474 -0
- package/src/plugins/api.ts +8 -0
- package/src/plugins/chat.ts +31 -0
- package/src/plugins/harness.ts +77 -0
- package/src/plugins/llm.ts +50 -0
- package/src/plugins/mcp.ts +58 -0
- package/src/plugins/memory.ts +42 -0
- package/src/plugins/oauth.ts +47 -0
- package/src/plugins/server.ts +126 -0
- package/src/plugins/store.ts +29 -0
- package/src/plugins/tools.ts +79 -0
- package/src/public/buddy.js +432 -0
- package/src/public/chat.css +3045 -0
- package/src/public/chat.html +5834 -0
- package/src/public/favicon-16.png +0 -0
- package/src/public/favicon-16.svg +10 -0
- package/src/public/favicon-32.png +0 -0
- package/src/public/favicon.ico +0 -0
- package/src/public/favicon.svg +13 -0
- package/src/public/i18n.js +663 -0
- package/src/public/index.html +143 -0
- package/src/public/library.html +678 -0
- package/src/public/mcp-add.html +126 -0
- package/src/public/md.js +332 -0
- package/src/public/rpg/inn-street.jpg +0 -0
- package/src/public/settings.html +795 -0
- package/src/public/skills-add.html +212 -0
- package/src/public/studio.html +1181 -0
- package/src/public/style.css +1678 -0
- package/src/public/subagents-add.html +152 -0
- package/src/router.ts +978 -0
- package/src/send-budget.ts +52 -0
- package/src/server.ts +1 -0
- package/src/skill-import.ts +250 -0
- package/src/slash.ts +15 -0
- package/src/start.ts +103 -0
- package/src/store.ts +1208 -0
- package/src/subagent.ts +355 -0
- package/src/tools.ts +818 -0
- package/src/trajectory.ts +339 -0
- package/src/usage.ts +111 -0
- package/vendor/protocol/package.json +19 -0
- package/vendor/protocol/src/index.ts +159 -0
|
@@ -0,0 +1,678 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-Hant">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<link rel="icon" href="/favicon.ico" sizes="32x32" />
|
|
7
|
+
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
|
8
|
+
<link rel="icon" href="/favicon-32.png" type="image/png" sizes="32x32" />
|
|
9
|
+
<link rel="icon" href="/favicon-16.png" type="image/png" sizes="16x16" />
|
|
10
|
+
<title>Guild — 技能庫</title>
|
|
11
|
+
<link rel="stylesheet" href="/style.css" />
|
|
12
|
+
<script src="/i18n.js"></script>
|
|
13
|
+
</head>
|
|
14
|
+
<body class="app library-page" data-i18n-page="library" data-lib="skills">
|
|
15
|
+
<script>
|
|
16
|
+
(function () {
|
|
17
|
+
var p = location.pathname.replace(/\/+$/, "") || "/";
|
|
18
|
+
var lib =
|
|
19
|
+
p === "/subagents" ? "subagents" : p === "/mcp" ? "mcp" : "skills";
|
|
20
|
+
document.body.setAttribute("data-lib", lib);
|
|
21
|
+
document.body.setAttribute(
|
|
22
|
+
"data-i18n-page",
|
|
23
|
+
lib === "skills" ? "library" : lib,
|
|
24
|
+
);
|
|
25
|
+
})();
|
|
26
|
+
</script>
|
|
27
|
+
<aside class="sidebar">
|
|
28
|
+
<div class="side-brand"><a class="logo" href="/">Guild</a></div>
|
|
29
|
+
<nav class="side-nav">
|
|
30
|
+
<a href="/" data-i18n="nav.chat">大廳</a>
|
|
31
|
+
<a href="/studio" data-i18n="nav.studio">編制</a>
|
|
32
|
+
<a class="on" href="/library" data-i18n="nav.library">工坊</a>
|
|
33
|
+
<a href="/settings" data-i18n="nav.settings">模型</a>
|
|
34
|
+
</nav>
|
|
35
|
+
<div class="sidebar-resizer" data-i18n-title="sidebar.resize" title="拖曳調整寬度"></div>
|
|
36
|
+
</aside>
|
|
37
|
+
<section class="pane">
|
|
38
|
+
<header class="pane-head">
|
|
39
|
+
<nav class="tabs">
|
|
40
|
+
<a class="tab" href="/library" data-lib="skills" data-i18n="library.sub.skills">技能</a>
|
|
41
|
+
<a class="tab" href="/subagents" data-lib="subagents" data-i18n="nav.subagents">子代理</a>
|
|
42
|
+
<a class="tab" href="/mcp" data-lib="mcp" data-i18n="nav.mcp">MCP</a>
|
|
43
|
+
</nav>
|
|
44
|
+
<script>
|
|
45
|
+
(function () {
|
|
46
|
+
var lib = document.body.getAttribute("data-lib");
|
|
47
|
+
document.querySelectorAll(".pane-head .tab").forEach(function (a) {
|
|
48
|
+
a.classList.toggle("on", a.getAttribute("data-lib") === lib);
|
|
49
|
+
});
|
|
50
|
+
})();
|
|
51
|
+
</script>
|
|
52
|
+
<div class="meta" id="lib-summary"></div>
|
|
53
|
+
<div class="head-actions">
|
|
54
|
+
<a class="btn" data-lib-action="skills" href="/skills/add" data-i18n="library.install">安裝新技能</a>
|
|
55
|
+
<a class="btn" data-lib-action="subagents" href="/subagents/add" data-i18n="subagents.install">新增子代理</a>
|
|
56
|
+
<a class="btn" data-lib-action="mcp" href="/mcp/add" data-i18n="mcp.install">連接伺服器</a>
|
|
57
|
+
</div>
|
|
58
|
+
</header>
|
|
59
|
+
<div class="page-body">
|
|
60
|
+
<p id="flash" class="muted"></p>
|
|
61
|
+
|
|
62
|
+
<section data-lib-panel="skills">
|
|
63
|
+
<p class="lede" data-i18n-html="library.lede">
|
|
64
|
+
Guild 自己的技能,加上本機 Claude、Codex、Pi、Grok、Cursor 留下的 SKILL.md。
|
|
65
|
+
安裝走 <a href="/skills/add">追加技能</a>;編制可以直接勾本機 CLI 技能。
|
|
66
|
+
</p>
|
|
67
|
+
<div class="toolbar">
|
|
68
|
+
<div class="search">
|
|
69
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
|
70
|
+
<circle cx="7" cy="7" r="4.5" stroke="currentColor"/>
|
|
71
|
+
<path d="M10.5 10.5 14 14" stroke="currentColor"/>
|
|
72
|
+
</svg>
|
|
73
|
+
<input id="skill-search" type="search" placeholder="搜尋技能" data-i18n-placeholder="search.skills" />
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
<div id="skill-tags" class="chips tag-row"></div>
|
|
77
|
+
<div id="skill-grid" class="skill-grid"></div>
|
|
78
|
+
</section>
|
|
79
|
+
|
|
80
|
+
<section data-lib-panel="subagents">
|
|
81
|
+
<p class="lede" data-i18n-html="subagents.lede">
|
|
82
|
+
Guild 內建 explorer / worker / reviewer,加上本機 Codex、Grok、Claude 留下的 agent 檔。
|
|
83
|
+
對話裡用 spawn 叫它們;自己建的走 <a href="/subagents/add">新增子代理</a>。
|
|
84
|
+
</p>
|
|
85
|
+
<div class="toolbar">
|
|
86
|
+
<div class="search">
|
|
87
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
|
88
|
+
<circle cx="7" cy="7" r="4.5" stroke="currentColor"/>
|
|
89
|
+
<path d="M10.5 10.5 14 14" stroke="currentColor"/>
|
|
90
|
+
</svg>
|
|
91
|
+
<input id="agent-search" type="search" placeholder="搜尋子代理" data-i18n-placeholder="search.subagents" />
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
<div id="agent-tags" class="chips tag-row"></div>
|
|
95
|
+
<div id="agent-grid" class="skill-grid"></div>
|
|
96
|
+
</section>
|
|
97
|
+
|
|
98
|
+
<section data-lib-panel="mcp">
|
|
99
|
+
<p class="lede" data-i18n-html="mcp.lede">
|
|
100
|
+
連接器,不是技能。Skill 是說明書;MCP 是工具伺服器。本機 Codex / Claude / Cursor 的 MCP 會直接進對話,不必匯入。
|
|
101
|
+
</p>
|
|
102
|
+
<div class="toolbar">
|
|
103
|
+
<div class="search">
|
|
104
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
|
105
|
+
<circle cx="7" cy="7" r="4.5" stroke="currentColor"/>
|
|
106
|
+
<path d="M10.5 10.5 14 14" stroke="currentColor"/>
|
|
107
|
+
</svg>
|
|
108
|
+
<input id="mcp-search" type="search" placeholder="搜尋 MCP" data-i18n-placeholder="search.mcp" />
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
<div id="mcp-tags" class="chips tag-row"></div>
|
|
112
|
+
<div id="mcp-grid" class="skill-grid"></div>
|
|
113
|
+
</section>
|
|
114
|
+
</div>
|
|
115
|
+
</section>
|
|
116
|
+
<script>
|
|
117
|
+
const flash = document.getElementById("flash");
|
|
118
|
+
const COLORS = ["#7c6af7", "#5b8def", "#2ea887", "#e07a3d", "#d4537e"];
|
|
119
|
+
const summaryEl = document.getElementById("lib-summary");
|
|
120
|
+
|
|
121
|
+
function libSection() {
|
|
122
|
+
const p = location.pathname.replace(/\/+$/, "") || "/";
|
|
123
|
+
if (p === "/subagents") return "subagents";
|
|
124
|
+
if (p === "/mcp") return "mcp";
|
|
125
|
+
return "skills";
|
|
126
|
+
}
|
|
127
|
+
function showLib() {
|
|
128
|
+
const lib = libSection();
|
|
129
|
+
document.body.setAttribute("data-lib", lib);
|
|
130
|
+
document.body.setAttribute(
|
|
131
|
+
"data-i18n-page",
|
|
132
|
+
lib === "skills" ? "library" : lib,
|
|
133
|
+
);
|
|
134
|
+
document.querySelectorAll(".pane-head .tab").forEach((a) => {
|
|
135
|
+
a.classList.toggle("on", a.getAttribute("data-lib") === lib);
|
|
136
|
+
});
|
|
137
|
+
document.title = t("title." + (lib === "skills" ? "library" : lib));
|
|
138
|
+
}
|
|
139
|
+
function escapeHtml(value) {
|
|
140
|
+
return String(value)
|
|
141
|
+
.replace(/&/g, "&")
|
|
142
|
+
.replace(/</g, "<")
|
|
143
|
+
.replace(/>/g, ">")
|
|
144
|
+
.replace(/"/g, """);
|
|
145
|
+
}
|
|
146
|
+
function hue(key) {
|
|
147
|
+
let n = 0;
|
|
148
|
+
for (const ch of String(key)) n = (n + ch.charCodeAt(0) * 13) % COLORS.length;
|
|
149
|
+
return COLORS[n];
|
|
150
|
+
}
|
|
151
|
+
function initials(name) {
|
|
152
|
+
const parts = String(name).trim().split(/[\s-]+/);
|
|
153
|
+
const letters = parts.map((p) => p[0]).join("").slice(0, 2);
|
|
154
|
+
return letters.toUpperCase() || "?";
|
|
155
|
+
}
|
|
156
|
+
function chipRow(counts, activeTags) {
|
|
157
|
+
const allOn = activeTags.size === 0;
|
|
158
|
+
return (
|
|
159
|
+
'<button type="button" class="chip' +
|
|
160
|
+
(allOn ? " on" : "") +
|
|
161
|
+
'" data-tag="">' +
|
|
162
|
+
t("library.all") +
|
|
163
|
+
"</button>" +
|
|
164
|
+
counts
|
|
165
|
+
.map(([tag, n]) => {
|
|
166
|
+
const on = activeTags.has(tag) ? " on" : "";
|
|
167
|
+
return (
|
|
168
|
+
'<button type="button" class="chip' +
|
|
169
|
+
on +
|
|
170
|
+
'" data-tag="' +
|
|
171
|
+
escapeHtml(tag) +
|
|
172
|
+
'">' +
|
|
173
|
+
escapeHtml(tagLabel(tag)) +
|
|
174
|
+
' <span class="chip-n">' +
|
|
175
|
+
n +
|
|
176
|
+
"</span></button>"
|
|
177
|
+
);
|
|
178
|
+
})
|
|
179
|
+
.join("")
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
function toggleTag(activeTags, tag) {
|
|
183
|
+
if (!tag || (activeTags.has(tag) && activeTags.size === 1)) {
|
|
184
|
+
activeTags.clear();
|
|
185
|
+
} else {
|
|
186
|
+
activeTags.clear();
|
|
187
|
+
if (tag) activeTags.add(tag);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function fail(err) {
|
|
191
|
+
flash.className = "error";
|
|
192
|
+
flash.textContent = err && err.message ? err.message : String(err);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function initSkills() {
|
|
196
|
+
let allSkills = [];
|
|
197
|
+
let bots = [];
|
|
198
|
+
let openId = "";
|
|
199
|
+
const activeTags = new Set();
|
|
200
|
+
function tagPills(skill) {
|
|
201
|
+
return (skill.tags || [])
|
|
202
|
+
.map((tag) => {
|
|
203
|
+
const on = activeTags.has(tag) ? " on" : "";
|
|
204
|
+
return (
|
|
205
|
+
'<span class="mini-tag' +
|
|
206
|
+
on +
|
|
207
|
+
'" data-tag="' +
|
|
208
|
+
escapeHtml(tag) +
|
|
209
|
+
'">' +
|
|
210
|
+
escapeHtml(tagLabel(tag)) +
|
|
211
|
+
"</span>"
|
|
212
|
+
);
|
|
213
|
+
})
|
|
214
|
+
.join("");
|
|
215
|
+
}
|
|
216
|
+
function tagCounts() {
|
|
217
|
+
const counts = new Map();
|
|
218
|
+
for (const skill of allSkills) {
|
|
219
|
+
for (const tag of skill.tags || []) {
|
|
220
|
+
counts.set(tag, (counts.get(tag) || 0) + 1);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return [...counts.entries()].sort(
|
|
224
|
+
(a, b) => b[1] - a[1] || a[0].localeCompare(b[0]),
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
function usedBy(skillId) {
|
|
228
|
+
return bots.filter((bot) => (bot.skillIds || []).includes(skillId));
|
|
229
|
+
}
|
|
230
|
+
function cardHtml(skill) {
|
|
231
|
+
const desc = skill.description || "";
|
|
232
|
+
const users = usedBy(skill.id);
|
|
233
|
+
const source =
|
|
234
|
+
skill.source === "host"
|
|
235
|
+
? skill.hostName || skill.host || t("library.local")
|
|
236
|
+
: skill.source === "catalog"
|
|
237
|
+
? t("library.catalog")
|
|
238
|
+
: t("library.user");
|
|
239
|
+
const sourceTitle = skill.source === "host" && skill.path ? skill.path : "";
|
|
240
|
+
const who =
|
|
241
|
+
skill.source === "host"
|
|
242
|
+
? '<span class="muted">' + t("library.host") + "</span>"
|
|
243
|
+
: users.length === 0
|
|
244
|
+
? '<span class="muted">' + t("library.unused") + "</span>"
|
|
245
|
+
: users
|
|
246
|
+
.map(
|
|
247
|
+
(bot) =>
|
|
248
|
+
'<a href="/edit/' +
|
|
249
|
+
encodeURIComponent(bot.id) +
|
|
250
|
+
'">' +
|
|
251
|
+
escapeHtml(bot.name) +
|
|
252
|
+
"</a>",
|
|
253
|
+
)
|
|
254
|
+
.join("、");
|
|
255
|
+
const open = openId === skill.id;
|
|
256
|
+
return (
|
|
257
|
+
'<article class="lib-card' +
|
|
258
|
+
(open ? " open" : "") +
|
|
259
|
+
'" data-id="' +
|
|
260
|
+
escapeHtml(skill.id) +
|
|
261
|
+
'">' +
|
|
262
|
+
'<div class="skill-ico" style="background:' +
|
|
263
|
+
hue(skill.slug || skill.id) +
|
|
264
|
+
'">' +
|
|
265
|
+
initials(skill.name) +
|
|
266
|
+
"</div>" +
|
|
267
|
+
'<div class="skill-meta"><strong>' +
|
|
268
|
+
escapeHtml(skill.name) +
|
|
269
|
+
'</strong><em class="lib-desc">' +
|
|
270
|
+
escapeHtml(desc) +
|
|
271
|
+
"</em>" +
|
|
272
|
+
(skill.tags && skill.tags.length
|
|
273
|
+
? '<p class="tag-list">' + tagPills(skill) + "</p>"
|
|
274
|
+
: "") +
|
|
275
|
+
'<p class="used-by">' +
|
|
276
|
+
who +
|
|
277
|
+
"</p>" +
|
|
278
|
+
'<p class="hint"' +
|
|
279
|
+
(sourceTitle ? ' title="' + escapeHtml(sourceTitle) + '"' : "") +
|
|
280
|
+
">" +
|
|
281
|
+
escapeHtml(source) +
|
|
282
|
+
"</p></div>" +
|
|
283
|
+
(open
|
|
284
|
+
? '<pre class="lib-body">' + escapeHtml(skill.body || "") + "</pre>"
|
|
285
|
+
: "") +
|
|
286
|
+
"</article>"
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
function render() {
|
|
290
|
+
document.getElementById("skill-tags").innerHTML = chipRow(
|
|
291
|
+
tagCounts(),
|
|
292
|
+
activeTags,
|
|
293
|
+
);
|
|
294
|
+
const q = document.getElementById("skill-search").value.toLowerCase();
|
|
295
|
+
const pool = allSkills.filter((skill) => {
|
|
296
|
+
const tags = skill.tags || [];
|
|
297
|
+
const tagOk =
|
|
298
|
+
activeTags.size === 0 || tags.some((tag) => activeTags.has(tag));
|
|
299
|
+
const hay = (
|
|
300
|
+
skill.name +
|
|
301
|
+
" " +
|
|
302
|
+
(skill.description || "") +
|
|
303
|
+
" " +
|
|
304
|
+
(skill.hostName || "") +
|
|
305
|
+
" " +
|
|
306
|
+
(skill.path || "") +
|
|
307
|
+
" " +
|
|
308
|
+
tags.map(tagLabel).join(" ") +
|
|
309
|
+
" " +
|
|
310
|
+
tags.join(" ")
|
|
311
|
+
).toLowerCase();
|
|
312
|
+
return tagOk && (!q || hay.includes(q));
|
|
313
|
+
});
|
|
314
|
+
const filtering = Boolean(q) || activeTags.size > 0;
|
|
315
|
+
summaryEl.textContent = filtering
|
|
316
|
+
? t("library.found", { n: pool.length, total: allSkills.length })
|
|
317
|
+
: t("library.count", { n: allSkills.length });
|
|
318
|
+
const grid = document.getElementById("skill-grid");
|
|
319
|
+
if (pool.length === 0) {
|
|
320
|
+
grid.innerHTML =
|
|
321
|
+
'<section class="empty">' + t("library.emptyHtml") + "</section>";
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
grid.innerHTML = pool.map(cardHtml).join("");
|
|
325
|
+
}
|
|
326
|
+
document.getElementById("skill-search").addEventListener("input", render);
|
|
327
|
+
document.getElementById("skill-tags").addEventListener("click", (event) => {
|
|
328
|
+
const chip = event.target.closest("[data-tag]");
|
|
329
|
+
if (!chip) return;
|
|
330
|
+
toggleTag(activeTags, chip.getAttribute("data-tag"));
|
|
331
|
+
render();
|
|
332
|
+
});
|
|
333
|
+
document.getElementById("skill-grid").addEventListener("click", (event) => {
|
|
334
|
+
const mini = event.target.closest(".mini-tag");
|
|
335
|
+
if (mini) {
|
|
336
|
+
toggleTag(activeTags, mini.getAttribute("data-tag"));
|
|
337
|
+
render();
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
if (event.target.closest("a")) return;
|
|
341
|
+
const card = event.target.closest(".lib-card");
|
|
342
|
+
if (!card) return;
|
|
343
|
+
const id = card.getAttribute("data-id");
|
|
344
|
+
openId = openId === id ? "" : id;
|
|
345
|
+
render();
|
|
346
|
+
});
|
|
347
|
+
window.addEventListener("guild-locale", render);
|
|
348
|
+
Promise.all([
|
|
349
|
+
fetch("/library/skills").then((r) => r.json()),
|
|
350
|
+
fetch("/library/skills/host").then((r) => r.json()).catch(() => []),
|
|
351
|
+
fetch("/bots").then((r) => r.json()),
|
|
352
|
+
])
|
|
353
|
+
.then(([skills, host, roster]) => {
|
|
354
|
+
allSkills = (Array.isArray(skills) ? skills : []).concat(
|
|
355
|
+
Array.isArray(host) ? host : [],
|
|
356
|
+
);
|
|
357
|
+
bots = Array.isArray(roster) ? roster : [];
|
|
358
|
+
render();
|
|
359
|
+
})
|
|
360
|
+
.catch(fail);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function initSubagents() {
|
|
364
|
+
let allAgents = [];
|
|
365
|
+
let openId = "";
|
|
366
|
+
const activeTags = new Set();
|
|
367
|
+
function tagPills(agent) {
|
|
368
|
+
return (agent.tags || [])
|
|
369
|
+
.map((tag) => {
|
|
370
|
+
const on = activeTags.has(tag) ? " on" : "";
|
|
371
|
+
return (
|
|
372
|
+
'<span class="mini-tag' +
|
|
373
|
+
on +
|
|
374
|
+
'" data-tag="' +
|
|
375
|
+
escapeHtml(tag) +
|
|
376
|
+
'">' +
|
|
377
|
+
escapeHtml(tagLabel(tag)) +
|
|
378
|
+
"</span>"
|
|
379
|
+
);
|
|
380
|
+
})
|
|
381
|
+
.join("");
|
|
382
|
+
}
|
|
383
|
+
function tagCounts() {
|
|
384
|
+
const counts = new Map();
|
|
385
|
+
for (const agent of allAgents) {
|
|
386
|
+
for (const tag of agent.tags || []) {
|
|
387
|
+
counts.set(tag, (counts.get(tag) || 0) + 1);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return [...counts.entries()].sort(
|
|
391
|
+
(a, b) => b[1] - a[1] || a[0].localeCompare(b[0]),
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
function cardHtml(agent) {
|
|
395
|
+
const desc = agent.description || "";
|
|
396
|
+
const source =
|
|
397
|
+
agent.source === "host"
|
|
398
|
+
? agent.hostName || agent.host || t("library.local")
|
|
399
|
+
: agent.source === "catalog"
|
|
400
|
+
? t("library.catalog")
|
|
401
|
+
: t("library.user");
|
|
402
|
+
const sourceTitle = agent.source === "host" && agent.path ? agent.path : "";
|
|
403
|
+
const who =
|
|
404
|
+
agent.source === "host"
|
|
405
|
+
? '<span class="muted">' + t("subagents.host") + "</span>"
|
|
406
|
+
: '<span class="muted">' + t("subagents.spawnHint") + "</span>";
|
|
407
|
+
const open = openId === agent.id;
|
|
408
|
+
return (
|
|
409
|
+
'<article class="lib-card' +
|
|
410
|
+
(open ? " open" : "") +
|
|
411
|
+
'" data-id="' +
|
|
412
|
+
escapeHtml(agent.id) +
|
|
413
|
+
'">' +
|
|
414
|
+
'<div class="skill-ico" style="background:' +
|
|
415
|
+
hue(agent.slug || agent.id) +
|
|
416
|
+
'">' +
|
|
417
|
+
initials(agent.name) +
|
|
418
|
+
"</div>" +
|
|
419
|
+
'<div class="skill-meta"><strong>' +
|
|
420
|
+
escapeHtml(agent.name) +
|
|
421
|
+
'</strong><em class="lib-desc">' +
|
|
422
|
+
escapeHtml(desc) +
|
|
423
|
+
"</em>" +
|
|
424
|
+
(agent.tags && agent.tags.length
|
|
425
|
+
? '<p class="tag-list">' + tagPills(agent) + "</p>"
|
|
426
|
+
: "") +
|
|
427
|
+
'<p class="used-by">' +
|
|
428
|
+
who +
|
|
429
|
+
"</p>" +
|
|
430
|
+
'<p class="hint"' +
|
|
431
|
+
(sourceTitle ? ' title="' + escapeHtml(sourceTitle) + '"' : "") +
|
|
432
|
+
">" +
|
|
433
|
+
escapeHtml(source) +
|
|
434
|
+
"</p></div>" +
|
|
435
|
+
(open
|
|
436
|
+
? '<pre class="lib-body">' +
|
|
437
|
+
escapeHtml(agent.body || agent.instructions || "") +
|
|
438
|
+
"</pre>"
|
|
439
|
+
: "") +
|
|
440
|
+
"</article>"
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
function render() {
|
|
444
|
+
document.getElementById("agent-tags").innerHTML = chipRow(
|
|
445
|
+
tagCounts(),
|
|
446
|
+
activeTags,
|
|
447
|
+
);
|
|
448
|
+
const q = document.getElementById("agent-search").value.toLowerCase();
|
|
449
|
+
const pool = allAgents.filter((agent) => {
|
|
450
|
+
const tags = agent.tags || [];
|
|
451
|
+
const tagOk =
|
|
452
|
+
activeTags.size === 0 || tags.some((tag) => activeTags.has(tag));
|
|
453
|
+
const hay = (
|
|
454
|
+
agent.name +
|
|
455
|
+
" " +
|
|
456
|
+
(agent.description || "") +
|
|
457
|
+
" " +
|
|
458
|
+
(agent.hostName || "") +
|
|
459
|
+
" " +
|
|
460
|
+
(agent.path || "") +
|
|
461
|
+
" " +
|
|
462
|
+
tags.map(tagLabel).join(" ") +
|
|
463
|
+
" " +
|
|
464
|
+
tags.join(" ")
|
|
465
|
+
).toLowerCase();
|
|
466
|
+
return tagOk && (!q || hay.includes(q));
|
|
467
|
+
});
|
|
468
|
+
const filtering = Boolean(q) || activeTags.size > 0;
|
|
469
|
+
summaryEl.textContent = filtering
|
|
470
|
+
? t("library.found", { n: pool.length, total: allAgents.length })
|
|
471
|
+
: t("subagents.count", { n: allAgents.length });
|
|
472
|
+
const grid = document.getElementById("agent-grid");
|
|
473
|
+
if (pool.length === 0) {
|
|
474
|
+
grid.innerHTML =
|
|
475
|
+
'<section class="empty">' + t("subagents.emptyHtml") + "</section>";
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
grid.innerHTML = pool.map(cardHtml).join("");
|
|
479
|
+
}
|
|
480
|
+
document.getElementById("agent-search").addEventListener("input", render);
|
|
481
|
+
document.getElementById("agent-tags").addEventListener("click", (event) => {
|
|
482
|
+
const chip = event.target.closest("[data-tag]");
|
|
483
|
+
if (!chip) return;
|
|
484
|
+
toggleTag(activeTags, chip.getAttribute("data-tag"));
|
|
485
|
+
render();
|
|
486
|
+
});
|
|
487
|
+
document.getElementById("agent-grid").addEventListener("click", (event) => {
|
|
488
|
+
const mini = event.target.closest(".mini-tag");
|
|
489
|
+
if (mini) {
|
|
490
|
+
toggleTag(activeTags, mini.getAttribute("data-tag"));
|
|
491
|
+
render();
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
if (event.target.closest("a")) return;
|
|
495
|
+
const card = event.target.closest(".lib-card");
|
|
496
|
+
if (!card) return;
|
|
497
|
+
const id = card.getAttribute("data-id");
|
|
498
|
+
openId = openId === id ? "" : id;
|
|
499
|
+
render();
|
|
500
|
+
});
|
|
501
|
+
window.addEventListener("guild-locale", render);
|
|
502
|
+
Promise.all([
|
|
503
|
+
fetch("/library/subagents").then((r) => r.json()),
|
|
504
|
+
fetch("/library/subagents/host").then((r) => r.json()).catch(() => []),
|
|
505
|
+
])
|
|
506
|
+
.then(([guild, host]) => {
|
|
507
|
+
allAgents = (Array.isArray(guild) ? guild : []).concat(
|
|
508
|
+
Array.isArray(host) ? host : [],
|
|
509
|
+
);
|
|
510
|
+
render();
|
|
511
|
+
})
|
|
512
|
+
.catch(fail);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function initMcp() {
|
|
516
|
+
let allServers = [];
|
|
517
|
+
let openId = "";
|
|
518
|
+
const activeTags = new Set();
|
|
519
|
+
function launchLine(server) {
|
|
520
|
+
const launch = server.launch || {};
|
|
521
|
+
if (launch.command) {
|
|
522
|
+
return [launch.command].concat(launch.args || []).join(" ");
|
|
523
|
+
}
|
|
524
|
+
return launch.url || "";
|
|
525
|
+
}
|
|
526
|
+
function tagsOf(server) {
|
|
527
|
+
if (server.source === "user") return ["Guild"];
|
|
528
|
+
return [server.host || "host"];
|
|
529
|
+
}
|
|
530
|
+
function cardHtml(server) {
|
|
531
|
+
const open = openId === server.id;
|
|
532
|
+
const tags = tagsOf(server);
|
|
533
|
+
const cmd = launchLine(server);
|
|
534
|
+
const who =
|
|
535
|
+
server.source === "host" ? t("mcp.host") : t("mcp.guildHint");
|
|
536
|
+
const extra = open
|
|
537
|
+
? '<pre class="lib-body">' +
|
|
538
|
+
escapeHtml(cmd) +
|
|
539
|
+
"</pre>" +
|
|
540
|
+
(server.source === "host"
|
|
541
|
+
? ""
|
|
542
|
+
: '<p class="lib-act"><button type="button" class="secondary danger" data-del="' +
|
|
543
|
+
escapeHtml(server.name) +
|
|
544
|
+
'">' +
|
|
545
|
+
t("mcp.delete") +
|
|
546
|
+
"</button></p>")
|
|
547
|
+
: "";
|
|
548
|
+
return (
|
|
549
|
+
'<article class="lib-card' +
|
|
550
|
+
(open ? " open" : "") +
|
|
551
|
+
'" data-id="' +
|
|
552
|
+
escapeHtml(server.id) +
|
|
553
|
+
'"><div class="skill-ico" style="background:' +
|
|
554
|
+
hue(server.name) +
|
|
555
|
+
'">M</div><div class="skill-meta"><strong>' +
|
|
556
|
+
escapeHtml(server.name) +
|
|
557
|
+
'</strong><em class="lib-desc">' +
|
|
558
|
+
escapeHtml(cmd) +
|
|
559
|
+
'</em><p class="tag-list">' +
|
|
560
|
+
tags
|
|
561
|
+
.map(function (tag) {
|
|
562
|
+
const on = activeTags.has(tag) ? " on" : "";
|
|
563
|
+
return (
|
|
564
|
+
'<span class="mini-tag' +
|
|
565
|
+
on +
|
|
566
|
+
'" data-tag="' +
|
|
567
|
+
escapeHtml(tag) +
|
|
568
|
+
'">' +
|
|
569
|
+
escapeHtml(tag) +
|
|
570
|
+
"</span>"
|
|
571
|
+
);
|
|
572
|
+
})
|
|
573
|
+
.join("") +
|
|
574
|
+
'</p><p class="used-by">' +
|
|
575
|
+
who +
|
|
576
|
+
"</p></div>" +
|
|
577
|
+
extra +
|
|
578
|
+
"</article>"
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
function render() {
|
|
582
|
+
const q = document.getElementById("mcp-search").value.toLowerCase();
|
|
583
|
+
const pool = allServers.filter((server) => {
|
|
584
|
+
const tags = tagsOf(server);
|
|
585
|
+
const tagOk =
|
|
586
|
+
activeTags.size === 0 || tags.some((tag) => activeTags.has(tag));
|
|
587
|
+
const hay = (
|
|
588
|
+
server.name +
|
|
589
|
+
" " +
|
|
590
|
+
launchLine(server) +
|
|
591
|
+
" " +
|
|
592
|
+
tags.join(" ")
|
|
593
|
+
).toLowerCase();
|
|
594
|
+
return tagOk && (!q || hay.includes(q));
|
|
595
|
+
});
|
|
596
|
+
const counts = new Map();
|
|
597
|
+
for (const server of allServers) {
|
|
598
|
+
for (const tag of tagsOf(server)) {
|
|
599
|
+
counts.set(tag, (counts.get(tag) || 0) + 1);
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
const chips = [...counts.entries()].sort(
|
|
603
|
+
(a, b) => b[1] - a[1] || a[0].localeCompare(b[0]),
|
|
604
|
+
);
|
|
605
|
+
document.getElementById("mcp-tags").innerHTML = chipRow(chips, activeTags);
|
|
606
|
+
summaryEl.textContent = t("mcp.count", { n: allServers.length });
|
|
607
|
+
const grid = document.getElementById("mcp-grid");
|
|
608
|
+
if (!pool.length) {
|
|
609
|
+
grid.innerHTML =
|
|
610
|
+
'<section class="empty">' + t("mcp.emptyHtml") + "</section>";
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
grid.innerHTML = pool.map(cardHtml).join("");
|
|
614
|
+
}
|
|
615
|
+
document.getElementById("mcp-search").addEventListener("input", render);
|
|
616
|
+
document.getElementById("mcp-tags").addEventListener("click", (event) => {
|
|
617
|
+
const chip = event.target.closest("[data-tag]");
|
|
618
|
+
if (!chip) return;
|
|
619
|
+
toggleTag(activeTags, chip.getAttribute("data-tag"));
|
|
620
|
+
render();
|
|
621
|
+
});
|
|
622
|
+
document.getElementById("mcp-grid").addEventListener("click", async (event) => {
|
|
623
|
+
const mini = event.target.closest(".mini-tag");
|
|
624
|
+
if (mini) {
|
|
625
|
+
toggleTag(activeTags, mini.getAttribute("data-tag"));
|
|
626
|
+
render();
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
const del = event.target.closest("[data-del]");
|
|
630
|
+
if (del) {
|
|
631
|
+
event.stopPropagation();
|
|
632
|
+
const name = del.getAttribute("data-del");
|
|
633
|
+
const res = await fetch("/mcp/servers/" + encodeURIComponent(name), {
|
|
634
|
+
method: "DELETE",
|
|
635
|
+
});
|
|
636
|
+
if (!res.ok) {
|
|
637
|
+
const body = await res.json().catch(() => ({}));
|
|
638
|
+
flash.className = "error";
|
|
639
|
+
flash.textContent = body.error || t("deleteFailed");
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
location.reload();
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
const card = event.target.closest(".lib-card");
|
|
646
|
+
if (!card) return;
|
|
647
|
+
const id = card.getAttribute("data-id");
|
|
648
|
+
openId = openId === id ? "" : id;
|
|
649
|
+
render();
|
|
650
|
+
});
|
|
651
|
+
window.addEventListener("guild-locale", render);
|
|
652
|
+
Promise.all([
|
|
653
|
+
fetch("/mcp/servers").then((r) => r.json()),
|
|
654
|
+
fetch("/mcp/host").then((r) => r.json()).catch(() => []),
|
|
655
|
+
])
|
|
656
|
+
.then(([guild, host]) => {
|
|
657
|
+
const guildRows = Array.isArray(guild) ? guild : [];
|
|
658
|
+
const taken = new Set(
|
|
659
|
+
guildRows.map((row) => String(row.name || "").toLowerCase()),
|
|
660
|
+
);
|
|
661
|
+
const hostRows = (Array.isArray(host) ? host : []).filter(
|
|
662
|
+
(row) => !taken.has(String(row.name || "").toLowerCase()),
|
|
663
|
+
);
|
|
664
|
+
allServers = guildRows.concat(hostRows);
|
|
665
|
+
render();
|
|
666
|
+
})
|
|
667
|
+
.catch(fail);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
showLib();
|
|
671
|
+
const section = libSection();
|
|
672
|
+
if (section === "subagents") initSubagents();
|
|
673
|
+
else if (section === "mcp") initMcp();
|
|
674
|
+
else initSkills();
|
|
675
|
+
window.addEventListener("guild-locale", showLib);
|
|
676
|
+
</script>
|
|
677
|
+
</body>
|
|
678
|
+
</html>
|