clay-server 4.8.0-beta.1 → 4.8.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/bundled-skills/clay-agent-browser/SKILL.md +36 -0
- package/lib/clay-skill-install.js +33 -0
- package/lib/clay-skill-storage.js +49 -0
- package/lib/project-http.js +14 -5
- package/lib/project-shared-browser.js +256 -0
- package/lib/project.js +35 -1
- package/lib/public/app.js +7 -0
- package/lib/public/css/filebrowser.css +2 -25
- package/lib/public/css/issues.css +1 -14
- package/lib/public/css/mcp-skills-workbench.css +51 -0
- package/lib/public/css/project-logs.css +0 -11
- package/lib/public/css/right-workbench.css +28 -0
- package/lib/public/css/scheduled-tasks.css +1 -1
- package/lib/public/css/shared-browser-skins.css +28 -0
- package/lib/public/css/shared-browser.css +91 -0
- package/lib/public/css/sticky-notes.css +0 -11
- package/lib/public/index.html +30 -22
- package/lib/public/modules/app-messages.js +2 -0
- package/lib/public/modules/extension-settings.js +110 -0
- package/lib/public/modules/mcp-skills-workbench.js +67 -0
- package/lib/public/modules/mcp-ui.js +9 -12
- package/lib/public/modules/notifications.js +2 -107
- package/lib/public/modules/shared-browser-activity.js +66 -0
- package/lib/public/modules/shared-browser-input.js +73 -0
- package/lib/public/modules/shared-browser-tabs.js +33 -0
- package/lib/public/modules/shared-browser-viewport-menu.js +76 -0
- package/lib/public/modules/shared-browser.js +214 -0
- package/lib/public/modules/sidebar-mobile.js +3 -3
- package/lib/public/modules/skills-actions.js +247 -0
- package/lib/public/modules/skills-detail.js +148 -0
- package/lib/public/modules/skills-state.js +10 -0
- package/lib/public/modules/skills.js +64 -459
- package/lib/public/modules/tool-palette-order.js +11 -13
- package/lib/public/style.css +6 -0
- package/lib/sdk-bridge.js +14 -1
- package/lib/sessions.js +2 -0
- package/lib/shared-browser-activity-history.js +17 -0
- package/lib/shared-browser-policy.js +26 -0
- package/lib/shared-browser-runtime.js +82 -0
- package/lib/shared-browser-tools.js +21 -0
- package/lib/shared-browser-worker.js +160 -0
- package/lib/ws-schema.js +10 -0
- package/lib/yoke/skill-discovery.js +22 -2
- package/package.json +1 -1
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { iconHtml, refreshIcons } from './icons.js';
|
|
2
|
+
import { escapeHtml, copyToClipboard, showToast } from './utils.js';
|
|
3
|
+
import { store } from './store.js';
|
|
4
|
+
import { skillState, content, basePath, getInstalledInfo, scopeLabel } from './skills-state.js';
|
|
5
|
+
import { getWs } from './ws-ref.js';
|
|
6
|
+
function reloadActiveSessionSkills() { var ws = getWs(); if (ws && ws.readyState === 1) ws.send(JSON.stringify({ type: 'reload_skills' })); }
|
|
7
|
+
function installSkill(source, skill, scope) {
|
|
8
|
+
var url = "https://github.com/" + source;
|
|
9
|
+
|
|
10
|
+
// Set all install buttons in the detail view to installing state
|
|
11
|
+
var btns = content().querySelectorAll(".skills-install-btn:not(.installed-state)");
|
|
12
|
+
for (var i = 0; i < btns.length; i++) {
|
|
13
|
+
btns[i].disabled = true;
|
|
14
|
+
btns[i].classList.add("installing");
|
|
15
|
+
btns[i].innerHTML = '<div class="skills-btn-spinner"></div> Installing...';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
fetch(basePath() + "api/install-skill", {
|
|
19
|
+
method: "POST",
|
|
20
|
+
headers: { "Content-Type": "application/json" },
|
|
21
|
+
body: JSON.stringify({ url: url, skill: skill, scope: scope }),
|
|
22
|
+
}).catch(function () {
|
|
23
|
+
// Re-enable buttons on fetch error
|
|
24
|
+
for (var j = 0; j < btns.length; j++) {
|
|
25
|
+
btns[j].disabled = false;
|
|
26
|
+
btns[j].classList.remove("installing");
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function handleSkillInstalled(msg) {
|
|
32
|
+
var skill = msg.skill;
|
|
33
|
+
var scope = msg.scope;
|
|
34
|
+
var success = msg.success;
|
|
35
|
+
|
|
36
|
+
if (success) {
|
|
37
|
+
// Update skillState().installedSkills cache
|
|
38
|
+
var existing = skillState().installedSkills[skill];
|
|
39
|
+
if (existing) {
|
|
40
|
+
if (existing.scope !== scope && existing.scope !== "both") {
|
|
41
|
+
existing.scope = "both";
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
skillState().installedSkills[skill] = { scope: scope };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Invalidate skills data cache so list refreshes with updated badges
|
|
48
|
+
skillState().skillsData = {};
|
|
49
|
+
|
|
50
|
+
// If we're on the detail view for this skill, re-render it
|
|
51
|
+
if (skillState().currentView === "detail") {
|
|
52
|
+
var detailEl = content().querySelector(".skills-detail");
|
|
53
|
+
if (detailEl) {
|
|
54
|
+
// Re-render the install section in the sidebar
|
|
55
|
+
updateDetailInstallButtons(skill);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// If we're on the installed tab, refresh it
|
|
60
|
+
if (skillState().activeTab === "installed" && skillState().currentView === "list") {
|
|
61
|
+
store.set({ skillsRefreshVersion: (store.get('skillsRefreshVersion') || 0) + 1 });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Hot-reload skills in the active session so it's usable immediately
|
|
65
|
+
reloadActiveSessionSkills();
|
|
66
|
+
} else {
|
|
67
|
+
// Show error toast
|
|
68
|
+
showToast("Failed to install " + skill + (msg.error ? ": " + msg.error : ""), "error");
|
|
69
|
+
// Re-enable buttons
|
|
70
|
+
var btns = content().querySelectorAll(".skills-install-btn.installing");
|
|
71
|
+
for (var i = 0; i < btns.length; i++) {
|
|
72
|
+
btns[i].disabled = false;
|
|
73
|
+
btns[i].classList.remove("installing");
|
|
74
|
+
// We can't easily restore the original text, so just set generic labels
|
|
75
|
+
btns[i].innerHTML = iconHtml("download") + " Install";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function uninstallSkill(skill, scope) {
|
|
81
|
+
// Set the uninstall button to loading state
|
|
82
|
+
var unBtn = content().querySelector('.skills-uninstall-btn[data-scope="' + scope + '"]');
|
|
83
|
+
if (unBtn) {
|
|
84
|
+
unBtn.disabled = true;
|
|
85
|
+
unBtn.classList.add("uninstalling");
|
|
86
|
+
unBtn.innerHTML = '<div class="skills-btn-spinner small"></div>';
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
fetch(basePath() + "api/uninstall-skill", {
|
|
90
|
+
method: "POST",
|
|
91
|
+
headers: { "Content-Type": "application/json" },
|
|
92
|
+
body: JSON.stringify({ skill: skill, scope: scope }),
|
|
93
|
+
}).catch(function () {
|
|
94
|
+
if (unBtn) {
|
|
95
|
+
unBtn.disabled = false;
|
|
96
|
+
unBtn.classList.remove("uninstalling");
|
|
97
|
+
unBtn.innerHTML = iconHtml("x");
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function handleSkillUninstalled(msg) {
|
|
103
|
+
var skill = msg.skill;
|
|
104
|
+
var scope = msg.scope;
|
|
105
|
+
var success = msg.success;
|
|
106
|
+
|
|
107
|
+
if (success) {
|
|
108
|
+
// Update skillState().installedSkills cache
|
|
109
|
+
var existing = skillState().installedSkills[skill];
|
|
110
|
+
if (existing) {
|
|
111
|
+
if (existing.scope === "both") {
|
|
112
|
+
// Downgrade from "both" to whichever scope remains
|
|
113
|
+
existing.scope = scope === "global" ? "project" : "global";
|
|
114
|
+
} else {
|
|
115
|
+
// Fully uninstalled
|
|
116
|
+
delete skillState().installedSkills[skill];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Invalidate skills data cache so list refreshes
|
|
121
|
+
skillState().skillsData = {};
|
|
122
|
+
|
|
123
|
+
// If we're on the detail view, re-render buttons
|
|
124
|
+
if (skillState().currentView === "detail") {
|
|
125
|
+
var detailEl = content().querySelector(".skills-detail");
|
|
126
|
+
if (detailEl) {
|
|
127
|
+
updateDetailInstallButtons(skill);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// If we're on the installed tab, refresh it
|
|
132
|
+
if (skillState().activeTab === "installed" && skillState().currentView === "list") {
|
|
133
|
+
store.set({ skillsRefreshVersion: (store.get('skillsRefreshVersion') || 0) + 1 });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Hot-reload skills in the active session so the change applies immediately
|
|
137
|
+
reloadActiveSessionSkills();
|
|
138
|
+
} else {
|
|
139
|
+
showToast("Failed to uninstall " + skill + (msg.error ? ": " + msg.error : ""), "error");
|
|
140
|
+
// Re-enable the uninstall button
|
|
141
|
+
var unBtn = content().querySelector('.skills-uninstall-btn[data-scope="' + scope + '"]');
|
|
142
|
+
if (unBtn) {
|
|
143
|
+
unBtn.disabled = false;
|
|
144
|
+
unBtn.classList.remove("uninstalling");
|
|
145
|
+
unBtn.innerHTML = iconHtml("x");
|
|
146
|
+
refreshIcons(unBtn);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function updateDetailInstallButtons(skillId) {
|
|
152
|
+
var info = getInstalledInfo(skillId);
|
|
153
|
+
var sidebar = content().querySelector(".skills-detail-sidebar");
|
|
154
|
+
if (!sidebar) return;
|
|
155
|
+
|
|
156
|
+
// Find the install actions or installed status block (last .skills-meta-block)
|
|
157
|
+
var blocks = sidebar.querySelectorAll(".skills-meta-block");
|
|
158
|
+
var lastBlock = blocks[blocks.length - 1];
|
|
159
|
+
if (!lastBlock) return;
|
|
160
|
+
|
|
161
|
+
// Get source/skill from the current detail view data attributes
|
|
162
|
+
var sourceEl = sidebar.querySelector("[data-detail-source]");
|
|
163
|
+
if (!sourceEl) sourceEl = content().querySelector("[data-detail-source]");
|
|
164
|
+
var source = sourceEl ? sourceEl.dataset.detailSource : "";
|
|
165
|
+
var skill = sourceEl ? sourceEl.dataset.detailSkill : skillId;
|
|
166
|
+
|
|
167
|
+
lastBlock.innerHTML = buildInstallButtonsHtml(skillId, source, skill);
|
|
168
|
+
refreshIcons(lastBlock);
|
|
169
|
+
attachInstallHandlers(lastBlock, source, skill);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function buildInstallButtonsHtml(skillId, source, skill) {
|
|
173
|
+
var info = getInstalledInfo(skillId);
|
|
174
|
+
if (info && info.source === "clay-builtin") return '<div class="skills-installed-status">' + iconHtml("circle-check") + ' Built in to Clay</div>';
|
|
175
|
+
|
|
176
|
+
var html = '<div class="skills-install-actions" data-detail-source="' + escapeHtml(source) + '" data-detail-skill="' + escapeHtml(skill) + '">';
|
|
177
|
+
|
|
178
|
+
if (info && info.scope === "both") {
|
|
179
|
+
// Both scopes installed — show status + uninstall for each
|
|
180
|
+
html += '<div class="skills-installed-row">' +
|
|
181
|
+
'<div class="skills-installed-status compact">' +
|
|
182
|
+
iconHtml("circle-check") + ' Installed (Project)' +
|
|
183
|
+
'</div>' +
|
|
184
|
+
'<button class="skills-uninstall-btn" data-scope="project" title="Uninstall (Project)">' +
|
|
185
|
+
iconHtml("x") +
|
|
186
|
+
'</button>' +
|
|
187
|
+
'</div>';
|
|
188
|
+
html += '<div class="skills-installed-row">' +
|
|
189
|
+
'<div class="skills-installed-status compact">' +
|
|
190
|
+
iconHtml("circle-check") + ' Installed (Global)' +
|
|
191
|
+
'</div>' +
|
|
192
|
+
'<button class="skills-uninstall-btn" data-scope="global" title="Uninstall (Global)">' +
|
|
193
|
+
iconHtml("x") +
|
|
194
|
+
'</button>' +
|
|
195
|
+
'</div>';
|
|
196
|
+
} else if (info) {
|
|
197
|
+
// One scope installed — show status with uninstall + install button for other scope
|
|
198
|
+
html += '<div class="skills-installed-row">' +
|
|
199
|
+
'<div class="skills-installed-status compact">' +
|
|
200
|
+
iconHtml("circle-check") + ' Installed (' + scopeLabel(info.scope) + ')' +
|
|
201
|
+
'</div>' +
|
|
202
|
+
'<button class="skills-uninstall-btn" data-scope="' + escapeHtml(info.scope) + '" title="Uninstall (' + scopeLabel(info.scope) + ')">' +
|
|
203
|
+
iconHtml("x") +
|
|
204
|
+
'</button>' +
|
|
205
|
+
'</div>';
|
|
206
|
+
|
|
207
|
+
// Show install button for the other scope
|
|
208
|
+
var otherScope = info.scope === "global" ? "project" : "global";
|
|
209
|
+
var otherLabel = info.scope === "global" ? "Project" : "Global";
|
|
210
|
+
html += '<button class="skills-install-btn secondary" data-scope="' + otherScope + '">' +
|
|
211
|
+
iconHtml("download") + ' Install (' + otherLabel + ')' +
|
|
212
|
+
'</button>';
|
|
213
|
+
} else {
|
|
214
|
+
// Not installed — show both install buttons
|
|
215
|
+
html += '<button class="skills-install-btn" data-scope="project">' +
|
|
216
|
+
iconHtml("download") + ' Install (Project)' +
|
|
217
|
+
'</button>';
|
|
218
|
+
html += '<button class="skills-install-btn secondary" data-scope="global">' +
|
|
219
|
+
iconHtml("download") + ' Install (Global)' +
|
|
220
|
+
'</button>';
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
html += '</div>';
|
|
224
|
+
return html;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function attachInstallHandlers(container, source, skill) {
|
|
228
|
+
var btns = container.querySelectorAll(".skills-install-btn");
|
|
229
|
+
for (var i = 0; i < btns.length; i++) {
|
|
230
|
+
(function (btn) {
|
|
231
|
+
btn.addEventListener("click", function () {
|
|
232
|
+
var scope = btn.dataset.scope;
|
|
233
|
+
installSkill(source, skill, scope);
|
|
234
|
+
});
|
|
235
|
+
})(btns[i]);
|
|
236
|
+
}
|
|
237
|
+
var unBtns = container.querySelectorAll(".skills-uninstall-btn");
|
|
238
|
+
for (var j = 0; j < unBtns.length; j++) {
|
|
239
|
+
(function (btn) {
|
|
240
|
+
btn.addEventListener("click", function () {
|
|
241
|
+
var scope = btn.dataset.scope;
|
|
242
|
+
uninstallSkill(skill, scope);
|
|
243
|
+
});
|
|
244
|
+
})(unBtns[j]);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { iconHtml, refreshIcons } from './icons.js';
|
|
2
|
+
import { escapeHtml, copyToClipboard, showToast } from './utils.js';
|
|
3
|
+
import { store } from './store.js';
|
|
4
|
+
import { skillState, content, basePath, getInstalledInfo, scopeLabel } from './skills-state.js';
|
|
5
|
+
import { buildInstallButtonsHtml, attachInstallHandlers } from './skills-actions.js';
|
|
6
|
+
export function renderDetail(data) {
|
|
7
|
+
var name = data.name || data._skill || "Unknown";
|
|
8
|
+
var desc = data.description || "";
|
|
9
|
+
var cmd = data.command || "npx skills add https://github.com/" + data._source + " --skill " + data._skill;
|
|
10
|
+
var skillId = data._skill || name;
|
|
11
|
+
var info = getInstalledInfo(skillId);
|
|
12
|
+
|
|
13
|
+
// Show back button in header
|
|
14
|
+
var backBtn = document.getElementById("skills-back-btn");
|
|
15
|
+
if (backBtn) backBtn.classList.remove("hidden");
|
|
16
|
+
|
|
17
|
+
var html = '<div class="skills-detail">';
|
|
18
|
+
|
|
19
|
+
// --- Main content (left) ---
|
|
20
|
+
html += '<div class="skills-detail-main">';
|
|
21
|
+
html += '<div class="skills-detail-name">' + escapeHtml(name) + '</div>';
|
|
22
|
+
|
|
23
|
+
if (desc) {
|
|
24
|
+
html += '<div class="skills-detail-desc">' + escapeHtml(desc) + '</div>';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
html += '<div class="skills-detail-cmd">' +
|
|
28
|
+
'<code>' + escapeHtml(cmd) + '</code>' +
|
|
29
|
+
'<button class="skills-copy-btn" data-cmd="' + escapeHtml(cmd) + '">' + iconHtml("copy") + '</button>' +
|
|
30
|
+
'</div>';
|
|
31
|
+
|
|
32
|
+
// SKILL.md content (already rendered HTML from skills.sh)
|
|
33
|
+
if (data.skillMd) {
|
|
34
|
+
html += '<div class="skills-detail-md-wrap">' +
|
|
35
|
+
'<div class="skills-detail-section-title">SKILL.md</div>' +
|
|
36
|
+
'<div class="skills-detail-md">' + sanitizeSkillHtml(data.skillMd) + '</div>' +
|
|
37
|
+
'</div>';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
html += '</div>'; // end main
|
|
41
|
+
|
|
42
|
+
// --- Sidebar (right) ---
|
|
43
|
+
html += '<div class="skills-detail-sidebar">';
|
|
44
|
+
|
|
45
|
+
// Weekly installs
|
|
46
|
+
if (data.weeklyInstalls) {
|
|
47
|
+
html += '<div class="skills-meta-block">' +
|
|
48
|
+
'<div class="skills-meta-label">Weekly Installs</div>' +
|
|
49
|
+
'<div class="skills-meta-value">' + escapeHtml(data.weeklyInstalls) + '</div>' +
|
|
50
|
+
'</div>';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Repository
|
|
54
|
+
if (data.repository || data._source) {
|
|
55
|
+
var repo = data.repository || data._source;
|
|
56
|
+
html += '<div class="skills-meta-block">' +
|
|
57
|
+
'<div class="skills-meta-label">Repository</div>' +
|
|
58
|
+
'<div class="skills-meta-value small">' +
|
|
59
|
+
'<div class="skills-meta-repo">' +
|
|
60
|
+
iconHtml("external-link") +
|
|
61
|
+
'<a href="https://github.com/' + escapeHtml(repo) + '" target="_blank" rel="noopener">' + escapeHtml(repo) + '</a>' +
|
|
62
|
+
'</div>' +
|
|
63
|
+
'</div>' +
|
|
64
|
+
'</div>';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// GitHub stars
|
|
68
|
+
if (data.githubStars) {
|
|
69
|
+
html += '<div class="skills-meta-block">' +
|
|
70
|
+
'<div class="skills-meta-label">GitHub Stars</div>' +
|
|
71
|
+
'<div class="skills-meta-value">' + escapeHtml(data.githubStars) + '</div>' +
|
|
72
|
+
'</div>';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// First seen
|
|
76
|
+
if (data.firstSeen) {
|
|
77
|
+
html += '<div class="skills-meta-block">' +
|
|
78
|
+
'<div class="skills-meta-label">First Seen</div>' +
|
|
79
|
+
'<div class="skills-meta-value small">' + escapeHtml(data.firstSeen) + '</div>' +
|
|
80
|
+
'</div>';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Security audits
|
|
84
|
+
if (data.audits && data.audits.length) {
|
|
85
|
+
html += '<div class="skills-meta-block">' +
|
|
86
|
+
'<div class="skills-meta-label">Security Audits</div>' +
|
|
87
|
+
'<div class="skills-audit-list">';
|
|
88
|
+
for (var a = 0; a < data.audits.length; a++) {
|
|
89
|
+
var audit = data.audits[a];
|
|
90
|
+
html += '<div class="skills-audit-item">' +
|
|
91
|
+
'<span>' + escapeHtml(audit.name) + '</span>' +
|
|
92
|
+
'<span class="skills-audit-badge ' + escapeHtml(audit.status) + '">' + escapeHtml(audit.status.toUpperCase()) + '</span>' +
|
|
93
|
+
'</div>';
|
|
94
|
+
}
|
|
95
|
+
html += '</div></div>';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Installed on
|
|
99
|
+
if (data.installedOn && data.installedOn.length) {
|
|
100
|
+
html += '<div class="skills-meta-block">' +
|
|
101
|
+
'<div class="skills-meta-label">Installed On</div>' +
|
|
102
|
+
'<div class="skills-platform-list">';
|
|
103
|
+
for (var p = 0; p < data.installedOn.length; p++) {
|
|
104
|
+
var plat = data.installedOn[p];
|
|
105
|
+
html += '<div class="skills-platform-item">' +
|
|
106
|
+
'<span>' + escapeHtml(plat.name) + '</span>' +
|
|
107
|
+
'<span class="skills-platform-count">' + escapeHtml(plat.installs) + '</span>' +
|
|
108
|
+
'</div>';
|
|
109
|
+
}
|
|
110
|
+
html += '</div></div>';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Install buttons or installed status
|
|
114
|
+
html += '<div class="skills-meta-block">' +
|
|
115
|
+
buildInstallButtonsHtml(skillId, data._source, data._skill) +
|
|
116
|
+
'</div>';
|
|
117
|
+
|
|
118
|
+
html += '</div>'; // end sidebar
|
|
119
|
+
html += '</div>'; // end detail
|
|
120
|
+
|
|
121
|
+
content().innerHTML = html;
|
|
122
|
+
refreshIcons(content());
|
|
123
|
+
|
|
124
|
+
// Copy button handler
|
|
125
|
+
var copyBtn = content().querySelector(".skills-copy-btn");
|
|
126
|
+
if (copyBtn) {
|
|
127
|
+
copyBtn.addEventListener("click", function (e) {
|
|
128
|
+
e.stopPropagation();
|
|
129
|
+
var cmdText = copyBtn.dataset.cmd;
|
|
130
|
+
copyToClipboard(cmdText);
|
|
131
|
+
copyBtn.classList.add("copied");
|
|
132
|
+
setTimeout(function () { copyBtn.classList.remove("copied"); }, 1500);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Install button handlers
|
|
137
|
+
var installActions = content().querySelector(".skills-install-actions");
|
|
138
|
+
if (installActions) {
|
|
139
|
+
attachInstallHandlers(installActions, data._source, data._skill);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function sanitizeSkillHtml(rawHtml) {
|
|
144
|
+
if (typeof DOMPurify !== "undefined") {
|
|
145
|
+
return DOMPurify.sanitize(rawHtml);
|
|
146
|
+
}
|
|
147
|
+
return rawHtml;
|
|
148
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
export function skillState() {
|
|
3
|
+
var value = store.get('skillsUi');
|
|
4
|
+
if (!value) { value = { activeTab: 'installed', currentView: 'list', skillsData: {}, installedSkills: {}, searchQuery: '', searchTimer: null, searchCache: {} }; store.set({ skillsUi: value }); }
|
|
5
|
+
return value;
|
|
6
|
+
}
|
|
7
|
+
export function content() { return document.getElementById('skills-content'); }
|
|
8
|
+
export function basePath() { return store.get('basePath') || ''; }
|
|
9
|
+
export function getInstalledInfo(skillId) { return skillState().installedSkills[skillId] || null; }
|
|
10
|
+
export function scopeLabel(scope) { return scope === 'both' ? 'Global + Project' : scope === 'project' ? 'Project' : 'Global'; }
|