clay-server 2.27.0-beta.9 → 2.27.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/README.md +10 -0
- package/lib/daemon-projects.js +164 -0
- package/lib/daemon.js +13 -126
- package/lib/mates-identity.js +132 -0
- package/lib/mates-knowledge.js +113 -0
- package/lib/mates-prompts.js +398 -0
- package/lib/mates.js +40 -599
- package/lib/project-connection.js +2 -0
- package/lib/project-http.js +4 -2
- package/lib/project-loop.js +110 -48
- package/lib/project-mate-interaction.js +4 -0
- package/lib/project-notifications.js +210 -0
- package/lib/project-sessions.js +5 -2
- package/lib/project-user-message.js +2 -1
- package/lib/project.js +26 -2
- package/lib/public/app.js +1193 -8517
- package/lib/public/css/command-palette.css +14 -0
- package/lib/public/css/loop.css +301 -0
- package/lib/public/css/notifications-center.css +190 -0
- package/lib/public/css/rewind.css +6 -0
- package/lib/public/index.html +89 -35
- package/lib/public/modules/app-connection.js +160 -0
- package/lib/public/modules/app-cursors.js +473 -0
- package/lib/public/modules/app-debate-ui.js +389 -0
- package/lib/public/modules/app-dm.js +627 -0
- package/lib/public/modules/app-favicon.js +212 -0
- package/lib/public/modules/app-header.js +229 -0
- package/lib/public/modules/app-home-hub.js +600 -0
- package/lib/public/modules/app-loop-ui.js +589 -0
- package/lib/public/modules/app-loop-wizard.js +439 -0
- package/lib/public/modules/app-messages.js +1560 -0
- package/lib/public/modules/app-misc.js +299 -0
- package/lib/public/modules/app-notifications.js +372 -0
- package/lib/public/modules/app-panels.js +888 -0
- package/lib/public/modules/app-projects.js +798 -0
- package/lib/public/modules/app-rate-limit.js +451 -0
- package/lib/public/modules/app-rendering.js +597 -0
- package/lib/public/modules/app-skills-install.js +234 -0
- package/lib/public/modules/command-palette.js +27 -4
- package/lib/public/modules/input.js +31 -20
- package/lib/public/modules/scheduler-config.js +1532 -0
- package/lib/public/modules/scheduler-history.js +79 -0
- package/lib/public/modules/scheduler.js +33 -1554
- package/lib/public/modules/session-search.js +13 -1
- package/lib/public/modules/sidebar-mates.js +812 -0
- package/lib/public/modules/sidebar-mobile.js +1269 -0
- package/lib/public/modules/sidebar-projects.js +1449 -0
- package/lib/public/modules/sidebar-sessions.js +986 -0
- package/lib/public/modules/sidebar.js +232 -4591
- package/lib/public/modules/store.js +27 -0
- package/lib/public/modules/ws-ref.js +7 -0
- package/lib/public/style.css +1 -0
- package/lib/sdk-bridge.js +96 -717
- package/lib/sdk-message-processor.js +587 -0
- package/lib/sdk-message-queue.js +42 -0
- package/lib/sdk-skill-discovery.js +131 -0
- package/lib/server-admin.js +712 -0
- package/lib/server-auth.js +737 -0
- package/lib/server-dm.js +221 -0
- package/lib/server-mates.js +281 -0
- package/lib/server-palette.js +110 -0
- package/lib/server-settings.js +479 -0
- package/lib/server-skills.js +280 -0
- package/lib/server.js +246 -2755
- package/lib/sessions.js +11 -4
- package/lib/users-auth.js +146 -0
- package/lib/users-permissions.js +118 -0
- package/lib/users-preferences.js +210 -0
- package/lib/users.js +48 -398
- package/lib/ws-schema.js +498 -0
- package/package.json +1 -1
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
var http = require("http");
|
|
2
|
+
var https = require("https");
|
|
3
|
+
|
|
4
|
+
// --- Skills proxy cache & helpers ---
|
|
5
|
+
|
|
6
|
+
var skillsCache = {};
|
|
7
|
+
|
|
8
|
+
function httpGet(url) {
|
|
9
|
+
return new Promise(function (resolve, reject) {
|
|
10
|
+
var mod = url.startsWith("https") ? https : http;
|
|
11
|
+
mod.get(url, { headers: { "User-Agent": "Clay/1.0" } }, function (resp) {
|
|
12
|
+
if (resp.statusCode >= 300 && resp.statusCode < 400 && resp.headers.location) {
|
|
13
|
+
return httpGet(resp.headers.location).then(resolve, reject);
|
|
14
|
+
}
|
|
15
|
+
var chunks = [];
|
|
16
|
+
resp.on("data", function (c) { chunks.push(c); });
|
|
17
|
+
resp.on("end", function () { resolve(Buffer.concat(chunks).toString("utf8")); });
|
|
18
|
+
resp.on("error", reject);
|
|
19
|
+
}).on("error", reject);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function fetchSkillsPage(url) {
|
|
24
|
+
return httpGet(url).then(function (html) {
|
|
25
|
+
// Data is inside self.__next_f.push() with escaped quotes: \"initialSkills\":[{\"source\":...}]
|
|
26
|
+
var marker = 'initialSkills';
|
|
27
|
+
var idx = html.indexOf(marker);
|
|
28
|
+
if (idx < 0) return { skills: [] };
|
|
29
|
+
|
|
30
|
+
// Find the start of the array: look for \\\":[
|
|
31
|
+
var arrStart = html.indexOf(':[', idx);
|
|
32
|
+
if (arrStart < 0) return { skills: [] };
|
|
33
|
+
arrStart += 1; // point to '['
|
|
34
|
+
|
|
35
|
+
// Find matching ']' -- track bracket depth
|
|
36
|
+
var depth = 0;
|
|
37
|
+
var arrEnd = -1;
|
|
38
|
+
for (var i = arrStart; i < html.length; i++) {
|
|
39
|
+
var ch = html[i];
|
|
40
|
+
if (ch === '[') depth++;
|
|
41
|
+
else if (ch === ']') {
|
|
42
|
+
depth--;
|
|
43
|
+
if (depth === 0) { arrEnd = i + 1; break; }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (arrEnd < 0) return { skills: [] };
|
|
47
|
+
|
|
48
|
+
var raw = html.substring(arrStart, arrEnd);
|
|
49
|
+
// Unescape: \\\" -> " and \\\\ -> backslash
|
|
50
|
+
var unescaped = raw.replace(/\\\\"/g, '__BSLASH_QUOTE__').replace(/\\"/g, '"').replace(/__BSLASH_QUOTE__/g, '\\"');
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
return { skills: JSON.parse(unescaped) };
|
|
54
|
+
} catch (e) {
|
|
55
|
+
return { skills: [] };
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function fetchSkillDetail(url) {
|
|
61
|
+
return httpGet(url).then(function (html) {
|
|
62
|
+
var result = {};
|
|
63
|
+
|
|
64
|
+
// Title: "skill-name by owner/repo"
|
|
65
|
+
var titleMatch = html.match(/<title>([^<]+)<\/title>/);
|
|
66
|
+
if (titleMatch) {
|
|
67
|
+
var parts = titleMatch[1].split(" by ");
|
|
68
|
+
result.name = parts[0].trim();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Description from meta
|
|
72
|
+
var descMatch = html.match(/meta name="description" content="([^"]+)"/);
|
|
73
|
+
if (descMatch) result.description = descMatch[1];
|
|
74
|
+
|
|
75
|
+
// Install command
|
|
76
|
+
var cmdMatch = html.match(/npx skills add [^ ]+ --skill [^ "<]+/);
|
|
77
|
+
if (cmdMatch) result.command = cmdMatch[0];
|
|
78
|
+
|
|
79
|
+
// Weekly installs: "Weekly Installs</span></div><div ...>VALUE</div>"
|
|
80
|
+
var wiMatch = html.match(/Weekly Installs<\/span><\/div><div[^>]*>([\d,.]+K?)<\/div>/);
|
|
81
|
+
if (wiMatch) result.weeklyInstalls = wiMatch[1];
|
|
82
|
+
|
|
83
|
+
// GitHub Stars: after SVG icon, inside <span>X.XK</span>
|
|
84
|
+
var gsIdx = html.indexOf("GitHub Stars");
|
|
85
|
+
if (gsIdx > 0) {
|
|
86
|
+
var gsRegion = html.substring(gsIdx, gsIdx + 1000);
|
|
87
|
+
var gsVal = gsRegion.match(/<span>(\d[\d,.]*K?)<\/span>/);
|
|
88
|
+
if (gsVal) result.githubStars = gsVal[1];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// First Seen
|
|
92
|
+
var fsMatch = html.match(/First Seen<\/span><\/div><div[^>]*>([^<]+)<\/div>/);
|
|
93
|
+
if (fsMatch) result.firstSeen = fsMatch[1].trim();
|
|
94
|
+
|
|
95
|
+
// Repository: from title "by owner/repo"
|
|
96
|
+
if (titleMatch) {
|
|
97
|
+
var byParts = titleMatch[1].split(" by ");
|
|
98
|
+
if (byParts[1]) result.repository = byParts[1].trim();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Security audits: "text-foreground truncate">NAME</span><span ...>STATUS</span>"
|
|
102
|
+
var audits = [];
|
|
103
|
+
var auditRegex = /class="text-sm font-medium text-foreground truncate">([^<]+)<\/span><span class="[^"]*">(\w+)<\/span>/g;
|
|
104
|
+
var am;
|
|
105
|
+
while ((am = auditRegex.exec(html)) !== null) {
|
|
106
|
+
audits.push({ name: am[1], status: am[2].toLowerCase() });
|
|
107
|
+
}
|
|
108
|
+
if (audits.length) result.audits = audits;
|
|
109
|
+
|
|
110
|
+
// Installed on: "text-foreground">NAME</span><span class="text-muted-foreground font-mono">COUNT</span>
|
|
111
|
+
var ioIdx = html.indexOf("Installed On");
|
|
112
|
+
if (ioIdx > 0) {
|
|
113
|
+
var ioRegion = html.substring(ioIdx, ioIdx + 3000);
|
|
114
|
+
var platforms = [];
|
|
115
|
+
var platRegex = /text-foreground">([^<]+)<\/span><span class="text-muted-foreground font-mono">([\d,.]+K?)<\/span>/g;
|
|
116
|
+
var pm;
|
|
117
|
+
while ((pm = platRegex.exec(ioRegion)) !== null) {
|
|
118
|
+
platforms.push({ name: pm[1], installs: pm[2] });
|
|
119
|
+
}
|
|
120
|
+
if (platforms.length) result.installedOn = platforms;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// SKILL.md content: rendered HTML inside the main content area
|
|
124
|
+
var skillMdIdx = html.indexOf("SKILL.md");
|
|
125
|
+
if (skillMdIdx > 0) {
|
|
126
|
+
// Find the prose content div after SKILL.md marker
|
|
127
|
+
var proseIdx = html.indexOf("prose", skillMdIdx);
|
|
128
|
+
if (proseIdx > 0) {
|
|
129
|
+
var proseStart = html.indexOf(">", proseIdx) + 1;
|
|
130
|
+
// Find the closing of the prose div (heuristic: next major section boundary)
|
|
131
|
+
var endMarkers = ["<div class=\"bg-background", "<div class=\"sticky"];
|
|
132
|
+
var proseEnd = html.length;
|
|
133
|
+
for (var em = 0; em < endMarkers.length; em++) {
|
|
134
|
+
var endIdx = html.indexOf(endMarkers[em], proseStart);
|
|
135
|
+
if (endIdx > 0 && endIdx < proseEnd) proseEnd = endIdx;
|
|
136
|
+
}
|
|
137
|
+
var rawMd = html.substring(proseStart, proseEnd);
|
|
138
|
+
// Rebase relative URLs to absolute (skills.sh base)
|
|
139
|
+
result.skillMd = rawMd
|
|
140
|
+
.replace(/src="(?!https?:\/\/|data:)([^"]+)"/g, function (m, p) {
|
|
141
|
+
return 'src="' + new URL(p, url).href + '"';
|
|
142
|
+
})
|
|
143
|
+
.replace(/href="(?!https?:\/\/|mailto:|#)([^"]+)"/g, function (m, p) {
|
|
144
|
+
return 'href="' + new URL(p, url).href + '"';
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return result;
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function attachSkills(ctx) {
|
|
154
|
+
var users = ctx.users;
|
|
155
|
+
var osUsers = ctx.osUsers;
|
|
156
|
+
var getMultiUserFromReq = ctx.getMultiUserFromReq;
|
|
157
|
+
|
|
158
|
+
function handleRequest(req, res, fullUrl) {
|
|
159
|
+
// Skills proxy: permission gate + routes
|
|
160
|
+
if (fullUrl !== "/api/skills" && !fullUrl.startsWith("/api/skills/") && !fullUrl.startsWith("/api/skills?")) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Permission gate
|
|
165
|
+
if (users.isMultiUser()) {
|
|
166
|
+
var skMu = getMultiUserFromReq(req);
|
|
167
|
+
if (skMu) {
|
|
168
|
+
var skPerms = users.getEffectivePermissions(skMu, osUsers);
|
|
169
|
+
if (!skPerms.skills) {
|
|
170
|
+
res.writeHead(403, { "Content-Type": "application/json" });
|
|
171
|
+
res.end('{"error":"Skills access is not permitted"}');
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Skills proxy: leaderboard list
|
|
178
|
+
if (req.method === "GET" && fullUrl === "/api/skills") {
|
|
179
|
+
var qs = req.url.indexOf("?") >= 0 ? req.url.substring(req.url.indexOf("?")) : "";
|
|
180
|
+
var tabParam = new URLSearchParams(qs).get("tab") || "all";
|
|
181
|
+
var tabPath = tabParam === "trending" ? "/trending" : tabParam === "hot" ? "/hot" : "/";
|
|
182
|
+
var cacheKey = "skills_" + tabParam;
|
|
183
|
+
var cached = skillsCache[cacheKey];
|
|
184
|
+
if (cached && Date.now() - cached.ts < 300000) {
|
|
185
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
186
|
+
res.end(cached.data);
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
fetchSkillsPage("https://skills.sh" + tabPath).then(function (data) {
|
|
190
|
+
var json = JSON.stringify(data);
|
|
191
|
+
skillsCache[cacheKey] = { ts: Date.now(), data: json };
|
|
192
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
193
|
+
res.end(json);
|
|
194
|
+
}).catch(function (err) {
|
|
195
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
196
|
+
res.end(JSON.stringify({ error: "Failed to fetch skills: " + (err.message || err) }));
|
|
197
|
+
});
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Skills proxy: search
|
|
202
|
+
if (req.method === "GET" && fullUrl.startsWith("/api/skills/search")) {
|
|
203
|
+
var sqsRaw = req.url.indexOf("?") >= 0 ? req.url.substring(req.url.indexOf("?")) : "";
|
|
204
|
+
var searchQ = new URLSearchParams(sqsRaw).get("q") || "";
|
|
205
|
+
if (!searchQ) {
|
|
206
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
207
|
+
res.end('{"error":"missing q param"}');
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
var searchCacheKey = "search_" + searchQ.toLowerCase();
|
|
211
|
+
var searchCached = skillsCache[searchCacheKey];
|
|
212
|
+
if (searchCached && Date.now() - searchCached.ts < 300000) {
|
|
213
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
214
|
+
res.end(searchCached.data);
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
// Reuse cached "all" tab data if available, otherwise fetch
|
|
218
|
+
var allCached = skillsCache["skills_all"];
|
|
219
|
+
var allPromise = (allCached && Date.now() - allCached.ts < 300000)
|
|
220
|
+
? Promise.resolve(JSON.parse(allCached.data))
|
|
221
|
+
: fetchSkillsPage("https://skills.sh/");
|
|
222
|
+
allPromise.then(function (data) {
|
|
223
|
+
var q = searchQ.toLowerCase();
|
|
224
|
+
var filtered = (data.skills || []).filter(function (s) {
|
|
225
|
+
var name = (s.name || "").toLowerCase();
|
|
226
|
+
var source = (s.source || "").toLowerCase();
|
|
227
|
+
var skillId = (s.skillId || "").toLowerCase();
|
|
228
|
+
return name.indexOf(q) >= 0 || source.indexOf(q) >= 0 || skillId.indexOf(q) >= 0;
|
|
229
|
+
});
|
|
230
|
+
var json = JSON.stringify({ skills: filtered });
|
|
231
|
+
skillsCache[searchCacheKey] = { ts: Date.now(), data: json };
|
|
232
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
233
|
+
res.end(json);
|
|
234
|
+
}).catch(function (err) {
|
|
235
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
236
|
+
res.end(JSON.stringify({ error: "Failed to search skills: " + (err.message || err) }));
|
|
237
|
+
});
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Skills proxy: skill detail
|
|
242
|
+
if (req.method === "GET" && fullUrl.startsWith("/api/skills/detail")) {
|
|
243
|
+
var qs2 = req.url.indexOf("?") >= 0 ? req.url.substring(req.url.indexOf("?")) : "";
|
|
244
|
+
var params2 = new URLSearchParams(qs2);
|
|
245
|
+
var detailSource = params2.get("source");
|
|
246
|
+
var detailSkill = params2.get("skill");
|
|
247
|
+
if (!detailSource || !detailSkill) {
|
|
248
|
+
res.writeHead(400, { "Content-Type": "application/json" });
|
|
249
|
+
res.end('{"error":"missing source or skill param"}');
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
var detailCacheKey = "detail_" + detailSource + "_" + detailSkill;
|
|
253
|
+
var detailCached = skillsCache[detailCacheKey];
|
|
254
|
+
if (detailCached && Date.now() - detailCached.ts < 300000) {
|
|
255
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
256
|
+
res.end(detailCached.data);
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
var detailUrl = "https://skills.sh/" + encodeURIComponent(detailSource).replace(/%2F/g, "/") + "/" + encodeURIComponent(detailSkill);
|
|
260
|
+
fetchSkillDetail(detailUrl).then(function (data) {
|
|
261
|
+
var json = JSON.stringify(data);
|
|
262
|
+
skillsCache[detailCacheKey] = { ts: Date.now(), data: json };
|
|
263
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
264
|
+
res.end(json);
|
|
265
|
+
}).catch(function (err) {
|
|
266
|
+
res.writeHead(502, { "Content-Type": "application/json" });
|
|
267
|
+
res.end(JSON.stringify({ error: "Failed to fetch skill detail: " + (err.message || err) }));
|
|
268
|
+
});
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
handleRequest: handleRequest,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
module.exports = { attachSkills: attachSkills };
|