clay-server 4.9.0-beta.1 → 5.0.0-beta.2
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/lib/mcp-browser-proxy.js +37 -0
- package/lib/mcp-connection-store.js +39 -0
- package/lib/mcp-network.js +46 -0
- package/lib/mcp-oauth.js +65 -0
- package/lib/mcp-proxy.js +32 -0
- package/lib/mcp-remote-connections.js +215 -0
- package/lib/mcp-session-access.js +15 -0
- package/lib/project-mcp.js +130 -357
- package/lib/project-session-document.js +9 -3
- package/lib/project-user-message.js +2 -0
- package/lib/project.js +28 -45
- package/lib/public/app.js +1 -0
- package/lib/public/css/mcp-connections.css +86 -0
- package/lib/public/css/skills-workbench.css +74 -0
- package/lib/public/index.html +16 -15
- package/lib/public/modules/app-messages.js +2 -1
- package/lib/public/modules/mcp-connection-view.js +136 -0
- package/lib/public/modules/mcp-permissions.js +122 -0
- package/lib/public/modules/mcp-ui.js +85 -461
- package/lib/public/modules/skills-actions.js +47 -79
- package/lib/public/modules/skills-detail.js +32 -145
- package/lib/public/modules/skills-list-view.js +29 -0
- package/lib/public/modules/skills-state.js +4 -2
- package/lib/public/modules/skills.js +104 -399
- package/lib/public/modules/vendor-login-browser.js +16 -5
- package/lib/public/style.css +2 -0
- package/lib/sdk-bridge.js +24 -30
- package/lib/sdk-mcp-descriptors.js +2 -1
- package/lib/vendor-login-browser-worker.js +18 -8
- package/lib/vendor-login-browser.js +10 -3
- package/lib/yoke/adapters/codex.js +3 -20
- package/package.json +6 -2
|
@@ -1,414 +1,119 @@
|
|
|
1
|
-
import { skillState,
|
|
1
|
+
import { skillState, patchSkills, basePath, content } from './skills-state.js';
|
|
2
2
|
import { renderDetail } from './skills-detail.js';
|
|
3
|
-
import {
|
|
3
|
+
import { renderSkillList, renderSkillFeedback } from './skills-list-view.js';
|
|
4
4
|
export { handleSkillInstalled, handleSkillUninstalled } from './skills-actions.js';
|
|
5
5
|
import { openMcpSkillsWorkbench, closeMcpSkillsWorkbench } from './mcp-skills-workbench.js';
|
|
6
|
-
import { iconHtml, refreshIcons } from './icons.js';
|
|
7
|
-
import { escapeHtml } from './utils.js';
|
|
8
6
|
import { store } from './store.js';
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
var btn = document.getElementById("skills-btn");
|
|
17
|
-
var closeBtn = document.getElementById("skills-modal-close");
|
|
18
|
-
var backBtn = document.getElementById("skills-back-btn");
|
|
19
|
-
var backdrop = modal ? modal.querySelector(".confirm-backdrop") : null;
|
|
20
|
-
|
|
21
|
-
if (btn) {
|
|
22
|
-
btn.addEventListener("click", function () {
|
|
23
|
-
openSkillsModal();
|
|
24
|
-
});
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (closeBtn) {
|
|
28
|
-
closeBtn.addEventListener("click", closeSkillsModal);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (backBtn) {
|
|
32
|
-
backBtn.setAttribute("aria-label", "Back to skills");
|
|
33
|
-
backBtn.addEventListener("click", showListView);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (backdrop) {
|
|
37
|
-
backdrop.addEventListener("click", closeSkillsModal);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
store.subscribe(function (state, previous) {
|
|
41
|
-
var next = state.mcpSkillsWorkbench || {}; var old = previous.mcpSkillsWorkbench || {};
|
|
42
|
-
if (state.skillsRefreshVersion !== previous.skillsRefreshVersion && skillState().activeTab === 'installed' && skillState().currentView === 'list') loadInstalledSkills();
|
|
43
|
-
if (next.open && next.tab === 'skills' && (!old.open || old.tab !== 'skills')) activateSkills();
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
// Search input
|
|
47
|
-
var searchInput = document.getElementById("skills-search-input");
|
|
48
|
-
var searchHint = document.getElementById("skills-search-hint");
|
|
49
|
-
var searchClear = document.getElementById("skills-search-clear");
|
|
50
|
-
|
|
51
|
-
function updateSearchControls() {
|
|
52
|
-
var hasValue = searchInput && searchInput.value.length > 0;
|
|
53
|
-
if (searchHint) searchHint.style.display = hasValue ? "none" : "";
|
|
54
|
-
if (searchClear) {
|
|
55
|
-
if (hasValue) { searchClear.classList.remove("hidden"); }
|
|
56
|
-
else { searchClear.classList.add("hidden"); }
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (searchInput) {
|
|
61
|
-
searchInput.setAttribute("aria-label", "Search skills");
|
|
62
|
-
searchInput.addEventListener("input", function () {
|
|
63
|
-
var q = searchInput.value.trim();
|
|
64
|
-
skillState().searchQuery = q;
|
|
65
|
-
updateSearchControls();
|
|
66
|
-
if (skillState().searchTimer) clearTimeout(skillState().searchTimer);
|
|
67
|
-
if (!q) {
|
|
68
|
-
// Clear search — restore tab view
|
|
69
|
-
var tabsEl = modal.querySelector(".skills-tabs");
|
|
70
|
-
if (tabsEl) tabsEl.style.display = "";
|
|
71
|
-
loadSkills(skillState().activeTab);
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
// Hide tabs during search
|
|
75
|
-
var tabsEl2 = modal.querySelector(".skills-tabs");
|
|
76
|
-
if (tabsEl2) tabsEl2.style.display = "none";
|
|
77
|
-
skillState().searchTimer = setTimeout(function () {
|
|
78
|
-
loadSearchResults(q);
|
|
79
|
-
}, 300);
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (searchClear) {
|
|
84
|
-
searchClear.addEventListener("click", function () {
|
|
85
|
-
if (searchInput) { searchInput.value = ""; }
|
|
86
|
-
skillState().searchQuery = "";
|
|
87
|
-
updateSearchControls();
|
|
88
|
-
if (skillState().searchTimer) clearTimeout(skillState().searchTimer);
|
|
89
|
-
var tabsEl = modal.querySelector(".skills-tabs");
|
|
90
|
-
if (tabsEl) tabsEl.style.display = "";
|
|
91
|
-
loadSkills(skillState().activeTab);
|
|
92
|
-
if (searchInput) searchInput.focus();
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// "/" key focuses search when modal is open
|
|
97
|
-
document.addEventListener("keydown", function (e) {
|
|
98
|
-
if (e.target && (e.target.matches("input, textarea") || e.target.isContentEditable)) return;
|
|
99
|
-
if (e.key === "/" && modal && !modal.classList.contains("hidden") && skillState().currentView === "list") {
|
|
100
|
-
if (document.activeElement !== searchInput) {
|
|
101
|
-
e.preventDefault();
|
|
102
|
-
if (searchInput) searchInput.focus();
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
// Tab clicks
|
|
108
|
-
var tabs = modal ? modal.querySelectorAll(".skills-tab") : [];
|
|
109
|
-
for (var i = 0; i < tabs.length; i++) {
|
|
110
|
-
(function (tab) {
|
|
111
|
-
tab.addEventListener("click", function () {
|
|
112
|
-
var tabName = tab.dataset.tab;
|
|
113
|
-
if (tabName === skillState().activeTab && skillState().currentView === "list" && !skillState().searchQuery) return;
|
|
114
|
-
skillState().activeTab = tabName;
|
|
115
|
-
skillState().currentView = "list";
|
|
116
|
-
// Clear search when switching tabs
|
|
117
|
-
skillState().searchQuery = "";
|
|
118
|
-
var si = document.getElementById("skills-search-input");
|
|
119
|
-
if (si) si.value = "";
|
|
120
|
-
updateSearchControls();
|
|
121
|
-
updateTabUI();
|
|
122
|
-
loadSkills(tabName);
|
|
123
|
-
});
|
|
124
|
-
})(tabs[i]);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Esc key
|
|
128
|
-
document.addEventListener("keydown", function (e) {
|
|
129
|
-
if (e.key === "Escape" && modal && !modal.classList.contains("hidden")) {
|
|
130
|
-
if (skillState().currentView === "detail") {
|
|
131
|
-
showListView();
|
|
132
|
-
} else {
|
|
133
|
-
closeSkillsModal();
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
});
|
|
8
|
+
function target() { return String(store.get('currentSlug')) + ':' + String(store.get('myUserId')); }
|
|
9
|
+
function modal() { return document.getElementById('skills-modal'); }
|
|
10
|
+
function requestGuard() {
|
|
11
|
+
var version = (skillState().requestVersion || 0) + 1, owner = target();
|
|
12
|
+
patchSkills({ requestVersion: version });
|
|
13
|
+
return function () { return target() === owner && skillState().requestVersion === version; };
|
|
137
14
|
}
|
|
138
|
-
|
|
139
|
-
function
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
refreshIcons(modal);
|
|
159
|
-
skillState().currentView = "list";
|
|
160
|
-
var backBtn = document.getElementById("skills-back-btn");
|
|
161
|
-
if (backBtn) backBtn.classList.add("hidden");
|
|
162
|
-
var tabsEl = modal.querySelector(".skills-tabs");
|
|
163
|
-
if (tabsEl) tabsEl.style.display = "";
|
|
164
|
-
var searchEl = document.getElementById("skills-search");
|
|
165
|
-
if (searchEl) searchEl.style.display = "";
|
|
166
|
-
var searchInput = document.getElementById("skills-search-input");
|
|
167
|
-
if (searchInput) { searchInput.value = ""; skillState().searchQuery = ""; }
|
|
168
|
-
// Load the current project inventory once; installation events refresh it.
|
|
169
|
-
fetchInstalledSkills().then(function () {
|
|
170
|
-
loadSkills(skillState().activeTab);
|
|
15
|
+
function json(url) { return fetch(url).then(function (res) { if (!res.ok) throw new Error('Request failed'); return res.json(); }); }
|
|
16
|
+
function chrome() {
|
|
17
|
+
var state = skillState(), detail = state.currentView === 'detail', root = modal();
|
|
18
|
+
root.querySelector('.skills-library-heading').hidden = detail;
|
|
19
|
+
document.getElementById('skills-back-btn').classList.toggle('hidden', !detail);
|
|
20
|
+
root.querySelector('.skills-tabs').hidden = detail;
|
|
21
|
+
root.querySelector('.skills-toolbar').hidden = detail;
|
|
22
|
+
root.querySelector('.skills-discovery-views').hidden = detail || state.activeTab === 'installed' || !!state.searchQuery;
|
|
23
|
+
root.querySelectorAll('.skills-tab').forEach(function (button) {
|
|
24
|
+
var active = button.dataset.tab === state.activeTab;
|
|
25
|
+
button.classList.toggle('active', active); button.setAttribute('aria-selected', String(active)); button.tabIndex = active ? 0 : -1;
|
|
26
|
+
});
|
|
27
|
+
var input = document.getElementById('skills-search-input');
|
|
28
|
+
input.placeholder = state.activeTab === 'installed' ? 'Search installed skills' : 'Search community skills';
|
|
29
|
+
input.setAttribute('aria-label', input.placeholder);
|
|
30
|
+
if (input.value !== state.searchQuery) input.value = state.searchQuery;
|
|
31
|
+
document.getElementById('skills-search-clear').classList.toggle('hidden', !state.searchQuery);
|
|
32
|
+
document.getElementById('skills-installed-count').textContent = Object.keys(state.installedSkills).length || '';
|
|
33
|
+
root.querySelectorAll('[data-discovery]').forEach(function (button) {
|
|
34
|
+
button.setAttribute('aria-pressed', String(button.dataset.discovery === (state.discoverySort || 'all')));
|
|
171
35
|
});
|
|
172
36
|
}
|
|
173
|
-
|
|
174
|
-
function
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
} else {
|
|
185
|
-
tabs[i].classList.remove("active");
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function loadSkills(tab) {
|
|
191
|
-
// Always ensure tabs and search are visible when loading list view
|
|
192
|
-
var tabsEl = modal.querySelector(".skills-tabs");
|
|
193
|
-
if (tabsEl && !skillState().searchQuery) tabsEl.style.display = "";
|
|
194
|
-
var searchEl = document.getElementById("skills-search");
|
|
195
|
-
if (searchEl) searchEl.style.display = "";
|
|
196
|
-
|
|
197
|
-
// Installed tab — uses local data, not skills.sh
|
|
198
|
-
if (tab === "installed") {
|
|
199
|
-
loadInstalledSkills();
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// Check cache
|
|
204
|
-
if (skillState().skillsData[tab]) {
|
|
205
|
-
renderList(skillState().skillsData[tab], tab);
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
contentEl.innerHTML = '<div class="skills-loading"><div class="skills-spinner"></div> Loading skills...</div>';
|
|
210
|
-
|
|
211
|
-
fetch("/api/skills?tab=" + encodeURIComponent(tab))
|
|
212
|
-
.then(function (res) { return res.json(); })
|
|
213
|
-
.then(function (data) {
|
|
214
|
-
var skills = data.skills || [];
|
|
215
|
-
skillState().skillsData[tab] = skills;
|
|
216
|
-
renderList(skills, tab);
|
|
217
|
-
})
|
|
218
|
-
.catch(function (err) {
|
|
219
|
-
contentEl.innerHTML = '<div class="skills-empty">Failed to load skills</div>';
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
function loadInstalledSkills() {
|
|
224
|
-
contentEl.innerHTML = '<div class="skills-loading"><div class="skills-spinner"></div> Loading installed skills...</div>';
|
|
225
|
-
|
|
226
|
-
fetch(basePath() + "api/installed-skills")
|
|
227
|
-
.then(function (res) { return res.json(); })
|
|
228
|
-
.then(function (data) {
|
|
229
|
-
skillState().installedSkills = data.installed || {};
|
|
230
|
-
renderInstalledList(skillState().installedSkills);
|
|
231
|
-
})
|
|
232
|
-
.catch(function () {
|
|
233
|
-
contentEl.innerHTML = '<div class="skills-empty">Failed to load installed skills</div>';
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function renderInstalledList(installed) {
|
|
238
|
-
var names = Object.keys(installed);
|
|
239
|
-
if (!names.length) {
|
|
240
|
-
contentEl.innerHTML = '<div class="skills-empty">No skills installed<div class="skills-empty-hint">Browse the other tabs to discover and install skills</div></div>';
|
|
37
|
+
function renderList(rows) { chrome(); renderSkillList(rows || [], openSkill, function () { selectTab('discover'); }); }
|
|
38
|
+
function loadList(refresh) {
|
|
39
|
+
chrome();
|
|
40
|
+
var state = skillState(), valid = requestGuard();
|
|
41
|
+
if (state.activeTab === 'installed') {
|
|
42
|
+
if (!refresh && state.inventoryLoaded) { renderList(); return; }
|
|
43
|
+
renderSkillFeedback('Loading your skills…');
|
|
44
|
+
json(basePath() + 'api/installed-skills').then(function (data) {
|
|
45
|
+
if (!valid()) return;
|
|
46
|
+
patchSkills({ installedSkills: data.installed || {}, inventoryLoaded: true }); renderList();
|
|
47
|
+
}).catch(function () { if (valid()) renderSkillFeedback('Could not load your skills.', function () { loadList(true); }); });
|
|
241
48
|
return;
|
|
242
49
|
}
|
|
243
|
-
|
|
244
|
-
var
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
var
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
'<span class="skills-installed-item-name">' + escapeHtml(name) + '</span>' +
|
|
255
|
-
'<span class="skills-installed-badge">' + iconHtml("check") + ' ' + (info.source === "clay-builtin" ? "Built-in" : scopeLabel(info.scope)) + '</span>' +
|
|
256
|
-
'</div>' +
|
|
257
|
-
(desc ? '<div class="skills-installed-item-desc">' + escapeHtml(desc) + '</div>' : '') +
|
|
258
|
-
'</div>' +
|
|
259
|
-
'<div class="skills-installed-item-actions">' +
|
|
260
|
-
(info.source === "clay-builtin" ? "" : buildUninstallButtons(name, info.scope)) +
|
|
261
|
-
'</div>' +
|
|
262
|
-
'</div>';
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
html += '</div>';
|
|
266
|
-
contentEl.innerHTML = html;
|
|
267
|
-
refreshIcons(contentEl);
|
|
268
|
-
|
|
269
|
-
// Attach uninstall handlers
|
|
270
|
-
var unBtns = contentEl.querySelectorAll(".skills-uninstall-btn");
|
|
271
|
-
for (var j = 0; j < unBtns.length; j++) {
|
|
272
|
-
(function (btn) {
|
|
273
|
-
btn.addEventListener("click", function () {
|
|
274
|
-
uninstallSkill(btn.dataset.skill, btn.dataset.scope);
|
|
275
|
-
});
|
|
276
|
-
})(unBtns[j]);
|
|
277
|
-
}
|
|
50
|
+
var query = state.searchQuery, sort = state.discoverySort || 'all';
|
|
51
|
+
var cache = query ? state.searchCache[query] : state.skillsData[sort];
|
|
52
|
+
if (cache && !refresh) { renderList(cache); return; }
|
|
53
|
+
renderSkillFeedback(query ? 'Searching community skills…' : 'Loading community skills…');
|
|
54
|
+
json(query ? '/api/skills/search?q=' + encodeURIComponent(query) : '/api/skills?tab=' + sort).then(function (data) {
|
|
55
|
+
if (!valid()) return;
|
|
56
|
+
var rows = data.skills || [], update = {};
|
|
57
|
+
if (query) update.searchCache = Object.assign({}, skillState().searchCache, { [query]: rows });
|
|
58
|
+
else update.skillsData = Object.assign({}, skillState().skillsData, { [sort]: rows });
|
|
59
|
+
patchSkills(update); renderList(rows);
|
|
60
|
+
}).catch(function () { if (valid()) renderSkillFeedback('Could not load community skills.', function () { loadList(true); }); });
|
|
278
61
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
if (scope === "both") {
|
|
283
|
-
html += '<button class="skills-uninstall-btn" data-skill="' + escapeHtml(skillName) + '" data-scope="project" title="Uninstall (Project)">' + iconHtml("x") + '</button>';
|
|
284
|
-
html += '<button class="skills-uninstall-btn" data-skill="' + escapeHtml(skillName) + '" data-scope="global" title="Uninstall (Global)">' + iconHtml("x") + '</button>';
|
|
285
|
-
} else {
|
|
286
|
-
html += '<button class="skills-uninstall-btn" data-skill="' + escapeHtml(skillName) + '" data-scope="' + escapeHtml(scope) + '" title="Uninstall">' + iconHtml("x") + '</button>';
|
|
287
|
-
}
|
|
288
|
-
return html;
|
|
62
|
+
function selectTab(tab) {
|
|
63
|
+
clearTimeout(skillState().searchTimer);
|
|
64
|
+
patchSkills({ activeTab: tab, currentView: 'list', searchQuery: '', selectedSkill: null }); loadList();
|
|
289
65
|
}
|
|
290
|
-
|
|
291
|
-
function
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
fetch("/api/skills/search?q=" + encodeURIComponent(q))
|
|
304
|
-
.then(function (res) { return res.json(); })
|
|
305
|
-
.then(function (data) {
|
|
306
|
-
if (q !== skillState().searchQuery) return; // stale
|
|
307
|
-
var skills = data.skills || [];
|
|
308
|
-
skillState().searchCache[q] = skills;
|
|
309
|
-
renderList(skills, "search");
|
|
310
|
-
})
|
|
311
|
-
.catch(function (err) {
|
|
312
|
-
if (q !== skillState().searchQuery) return;
|
|
313
|
-
contentEl.innerHTML = '<div class="skills-empty">Search failed</div>';
|
|
314
|
-
});
|
|
66
|
+
function showList() { patchSkills({ currentView: 'list', selectedSkill: null }); loadList(); document.getElementById('skills-search-input').focus(); }
|
|
67
|
+
function openSkill(row, installed) {
|
|
68
|
+
clearTimeout(skillState().searchTimer);
|
|
69
|
+
patchSkills({ currentView: 'detail', selectedSkill: row.skillId || row.name }); chrome();
|
|
70
|
+
var valid = requestGuard(); content().scrollTop = 0;
|
|
71
|
+
if (installed) {
|
|
72
|
+
renderDetail(Object.assign({}, row, { _skill: row.name, _installed: true })); return;
|
|
73
|
+
}
|
|
74
|
+
renderSkillFeedback('Loading skill details…');
|
|
75
|
+
json('/api/skills/detail?source=' + encodeURIComponent(row.source) + '&skill=' + encodeURIComponent(row.skillId || row.name)).then(function (data) {
|
|
76
|
+
if (valid()) renderDetail(Object.assign({}, data, { _source: row.source, _skill: row.skillId || row.name }));
|
|
77
|
+
}).catch(function () { if (valid()) renderSkillFeedback('Could not load this skill.', function () { openSkill(row, false); }); });
|
|
315
78
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
return n.toString();
|
|
79
|
+
function activate() {
|
|
80
|
+
if (skillState().loadedFor === target()) return;
|
|
81
|
+
clearTimeout(skillState().searchTimer);
|
|
82
|
+
patchSkills({ loadedFor: target(), activeTab: 'installed', currentView: 'list', searchQuery: '', installedSkills: {}, inventoryLoaded: false, selectedSkill: null });
|
|
83
|
+
loadList(true);
|
|
322
84
|
}
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
'</div>' +
|
|
358
|
-
'</div>';
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
html += '</div>';
|
|
362
|
-
contentEl.innerHTML = html;
|
|
363
|
-
refreshIcons(contentEl);
|
|
364
|
-
|
|
365
|
-
// Attach click handlers
|
|
366
|
-
var items = contentEl.querySelectorAll(".skills-item");
|
|
367
|
-
for (var j = 0; j < items.length; j++) {
|
|
368
|
-
(function (item) {
|
|
369
|
-
item.addEventListener("click", function () {
|
|
370
|
-
var source = item.dataset.source;
|
|
371
|
-
var skill = item.dataset.skill;
|
|
372
|
-
loadDetail(source, skill);
|
|
373
|
-
});
|
|
374
|
-
})(items[j]);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
function loadDetail(source, skill) {
|
|
379
|
-
skillState().currentView = "detail";
|
|
380
|
-
|
|
381
|
-
// Hide tabs and search
|
|
382
|
-
var tabsEl = modal.querySelector(".skills-tabs");
|
|
383
|
-
if (tabsEl) tabsEl.style.display = "none";
|
|
384
|
-
var searchEl = document.getElementById("skills-search");
|
|
385
|
-
if (searchEl) searchEl.style.display = "none";
|
|
386
|
-
|
|
387
|
-
contentEl.innerHTML = '<div class="skills-loading"><div class="skills-spinner"></div> Loading skill details...</div>';
|
|
388
|
-
|
|
389
|
-
fetch("/api/skills/detail?source=" + encodeURIComponent(source) + "&skill=" + encodeURIComponent(skill))
|
|
390
|
-
.then(function (res) { return res.json(); })
|
|
391
|
-
.then(function (data) {
|
|
392
|
-
data._source = source;
|
|
393
|
-
data._skill = skill;
|
|
394
|
-
renderDetail(data);
|
|
395
|
-
})
|
|
396
|
-
.catch(function (err) {
|
|
397
|
-
contentEl.innerHTML = '<div class="skills-empty">Failed to load skill details</div>';
|
|
398
|
-
});
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
function showListView() {
|
|
402
|
-
skillState().currentView = "list";
|
|
403
|
-
var tabsEl = modal.querySelector(".skills-tabs");
|
|
404
|
-
if (tabsEl) tabsEl.style.display = skillState().searchQuery ? "none" : "";
|
|
405
|
-
var searchEl = document.getElementById("skills-search");
|
|
406
|
-
if (searchEl) searchEl.style.display = "";
|
|
407
|
-
var backBtn = document.getElementById("skills-back-btn");
|
|
408
|
-
if (backBtn) backBtn.classList.add("hidden");
|
|
409
|
-
if (skillState().searchQuery) {
|
|
410
|
-
loadSearchResults(skillState().searchQuery);
|
|
411
|
-
} else {
|
|
412
|
-
loadSkills(skillState().activeTab);
|
|
413
|
-
}
|
|
85
|
+
export function initSkills() {
|
|
86
|
+
if (!modal()) return;
|
|
87
|
+
var input = document.getElementById('skills-search-input');
|
|
88
|
+
document.getElementById('skills-back-btn').onclick = showList;
|
|
89
|
+
var legacy = document.getElementById('skills-btn'); if (legacy) legacy.onclick = function () { openMcpSkillsWorkbench('skills'); };
|
|
90
|
+
modal().querySelectorAll('.skills-tab').forEach(function (button) {
|
|
91
|
+
button.onclick = function () { selectTab(button.dataset.tab); };
|
|
92
|
+
button.onkeydown = function (event) {
|
|
93
|
+
if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) return;
|
|
94
|
+
event.preventDefault(); var tab = event.key === 'Home' ? 'installed' : event.key === 'End' ? 'discover' : skillState().activeTab === 'installed' ? 'discover' : 'installed';
|
|
95
|
+
selectTab(tab); modal().querySelector('[data-tab="' + tab + '"]').focus();
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
input.oninput = function () {
|
|
99
|
+
clearTimeout(skillState().searchTimer); requestGuard(); patchSkills({ searchQuery: input.value }); chrome();
|
|
100
|
+
if (skillState().activeTab === 'installed') loadList();
|
|
101
|
+
else patchSkills({ searchTimer: setTimeout(function () { loadList(); }, 250) });
|
|
102
|
+
};
|
|
103
|
+
document.getElementById('skills-search-clear').onclick = function () { input.value = ''; input.oninput(); input.focus(); };
|
|
104
|
+
modal().querySelectorAll('[data-discovery]').forEach(function (button) {
|
|
105
|
+
button.onclick = function () { patchSkills({ discoverySort: button.dataset.discovery }); loadList(); };
|
|
106
|
+
});
|
|
107
|
+
document.addEventListener('keydown', function (event) {
|
|
108
|
+
var panel = store.get('mcpSkillsWorkbench') || {};
|
|
109
|
+
if (!panel.open || panel.tab !== 'skills' || (event.target && (event.target.matches('input,textarea,select') || event.target.isContentEditable))) return;
|
|
110
|
+
if (event.key === '/' && skillState().currentView === 'list') { event.preventDefault(); input.focus(); }
|
|
111
|
+
if (event.key === 'Escape') { if (skillState().currentView === 'detail') showList(); else closeMcpSkillsWorkbench(); }
|
|
112
|
+
});
|
|
113
|
+
store.subscribe(function (state, previous) {
|
|
114
|
+
var next = state.mcpSkillsWorkbench || {}, old = previous.mcpSkillsWorkbench || {};
|
|
115
|
+
if (state.currentSlug !== previous.currentSlug || state.myUserId !== previous.myUserId) { clearTimeout(skillState().searchTimer); requestGuard(); }
|
|
116
|
+
if (state.skillsRefreshVersion !== previous.skillsRefreshVersion && skillState().currentView === 'list') loadList(true);
|
|
117
|
+
if (next.open && next.tab === 'skills' && (!old.open || old.tab !== 'skills')) activate();
|
|
118
|
+
});
|
|
414
119
|
}
|
|
@@ -9,7 +9,8 @@ function send(type, extra) {
|
|
|
9
9
|
var view = current();
|
|
10
10
|
var ws = getWs();
|
|
11
11
|
if (!view || view.slug !== store.get('currentSlug') || !ws || ws.readyState !== 1) return;
|
|
12
|
-
|
|
12
|
+
if (view.kind === 'mcp') type = type.replace('vendor_login_', 'mcp_login_');
|
|
13
|
+
ws.send(JSON.stringify(Object.assign({ type: type, vendor: view.vendor, browserId: view.browserId, connectionId: view.connectionId }, extra || {})));
|
|
13
14
|
}
|
|
14
15
|
|
|
15
16
|
function input(event) { send("vendor_login_browser_input", { event: event }); }
|
|
@@ -31,9 +32,10 @@ export function openLoginBrowser(msg) {
|
|
|
31
32
|
return;
|
|
32
33
|
}
|
|
33
34
|
closeLoginBrowser();
|
|
34
|
-
var
|
|
35
|
+
var isMcp = msg.kind === 'mcp';
|
|
36
|
+
var title = isMcp ? 'Sign in to ' + (msg.label || 'MCP server') : (msg.vendor === "codex" ? "Sign in to Codex" : "Sign in to Claude");
|
|
35
37
|
store.set({ vendorLoginBrowser: {
|
|
36
|
-
browserId: msg.browserId, vendor: msg.vendor, slug: msg.slug || store.get('currentSlug'),
|
|
38
|
+
browserId: msg.browserId, vendor: msg.vendor, kind: msg.kind, connectionId: msg.connectionId, slug: msg.slug || store.get('currentSlug'),
|
|
37
39
|
previousFocus: document.activeElement, decoding: false, pointerY: null, pointerId: null, mouseDown: false, moveTimer: null, pendingMove: null, resizeObserver: null, requestedSize: null, phase: "loading",
|
|
38
40
|
} });
|
|
39
41
|
var dialog = document.createElement("dialog");
|
|
@@ -60,7 +62,15 @@ export function openLoginBrowser(msg) {
|
|
|
60
62
|
document.body.appendChild(dialog);
|
|
61
63
|
var keyboard = dialog.querySelector("textarea");
|
|
62
64
|
var canvas = dialog.querySelector("canvas");
|
|
63
|
-
|
|
65
|
+
if (isMcp) {
|
|
66
|
+
dialog.querySelector('.login-browser-vendor').hidden = true;
|
|
67
|
+
dialog.querySelector('[data-action="terminal"]').hidden = true;
|
|
68
|
+
dialog.querySelector('.login-browser-privacy').textContent = 'This private browser is controlled only by you. Cookies are discarded when sign-in ends.';
|
|
69
|
+
}
|
|
70
|
+
function cancel() {
|
|
71
|
+
if (isMcp) { send('vendor_login_cancel'); closeLoginBrowser(); }
|
|
72
|
+
else cancelVendorLogin(msg.vendor);
|
|
73
|
+
}
|
|
64
74
|
function restart(mode) {
|
|
65
75
|
var ws = getWs();
|
|
66
76
|
if (!ws || ws.readyState !== 1) {
|
|
@@ -68,7 +78,8 @@ export function openLoginBrowser(msg) {
|
|
|
68
78
|
return;
|
|
69
79
|
}
|
|
70
80
|
cancel();
|
|
71
|
-
|
|
81
|
+
if (isMcp) ws.send(JSON.stringify({ type: 'mcp_connection_action', action: 'signin', id: msg.connectionId }));
|
|
82
|
+
else requestVendorLogin(msg.vendor, { mode: mode });
|
|
72
83
|
}
|
|
73
84
|
dialog.querySelector('[data-action="cancel"]').onclick = cancel;
|
|
74
85
|
dialog.querySelector('[data-action="retry"]').onclick = function () { restart("browser"); };
|
package/lib/public/style.css
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
@import url("css/wireframe.css");
|
|
2
|
+
@import url("css/mcp-connections.css");
|
|
2
3
|
@import url("css/session-github.css");
|
|
3
4
|
@import url("css/vendor-login-browser.css");
|
|
4
5
|
@import url("css/issues.css");
|
|
@@ -69,5 +70,6 @@
|
|
|
69
70
|
@import url("./css/shared-browser.css");
|
|
70
71
|
|
|
71
72
|
@import url("./css/mcp-skills-workbench.css");
|
|
73
|
+
@import url("css/skills-workbench.css");
|
|
72
74
|
|
|
73
75
|
@import url("css/right-workbench.css");
|