clay-server 2.27.0-beta.8 → 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.
Files changed (72) hide show
  1. package/README.md +10 -0
  2. package/lib/daemon-projects.js +164 -0
  3. package/lib/daemon.js +13 -126
  4. package/lib/mates-identity.js +132 -0
  5. package/lib/mates-knowledge.js +113 -0
  6. package/lib/mates-prompts.js +398 -0
  7. package/lib/mates.js +40 -599
  8. package/lib/project-connection.js +2 -0
  9. package/lib/project-debate.js +19 -12
  10. package/lib/project-http.js +4 -2
  11. package/lib/project-loop.js +110 -48
  12. package/lib/project-mate-interaction.js +4 -0
  13. package/lib/project-notifications.js +210 -0
  14. package/lib/project-sessions.js +5 -2
  15. package/lib/project-user-message.js +2 -1
  16. package/lib/project.js +26 -2
  17. package/lib/public/app.js +1193 -8521
  18. package/lib/public/css/command-palette.css +14 -0
  19. package/lib/public/css/loop.css +301 -0
  20. package/lib/public/css/notifications-center.css +190 -0
  21. package/lib/public/css/rewind.css +6 -0
  22. package/lib/public/index.html +89 -35
  23. package/lib/public/modules/app-connection.js +160 -0
  24. package/lib/public/modules/app-cursors.js +473 -0
  25. package/lib/public/modules/app-debate-ui.js +389 -0
  26. package/lib/public/modules/app-dm.js +627 -0
  27. package/lib/public/modules/app-favicon.js +212 -0
  28. package/lib/public/modules/app-header.js +229 -0
  29. package/lib/public/modules/app-home-hub.js +600 -0
  30. package/lib/public/modules/app-loop-ui.js +589 -0
  31. package/lib/public/modules/app-loop-wizard.js +439 -0
  32. package/lib/public/modules/app-messages.js +1560 -0
  33. package/lib/public/modules/app-misc.js +299 -0
  34. package/lib/public/modules/app-notifications.js +372 -0
  35. package/lib/public/modules/app-panels.js +888 -0
  36. package/lib/public/modules/app-projects.js +798 -0
  37. package/lib/public/modules/app-rate-limit.js +451 -0
  38. package/lib/public/modules/app-rendering.js +597 -0
  39. package/lib/public/modules/app-skills-install.js +234 -0
  40. package/lib/public/modules/command-palette.js +27 -4
  41. package/lib/public/modules/input.js +31 -20
  42. package/lib/public/modules/scheduler-config.js +1532 -0
  43. package/lib/public/modules/scheduler-history.js +79 -0
  44. package/lib/public/modules/scheduler.js +33 -1554
  45. package/lib/public/modules/session-search.js +13 -1
  46. package/lib/public/modules/sidebar-mates.js +812 -0
  47. package/lib/public/modules/sidebar-mobile.js +1269 -0
  48. package/lib/public/modules/sidebar-projects.js +1449 -0
  49. package/lib/public/modules/sidebar-sessions.js +986 -0
  50. package/lib/public/modules/sidebar.js +232 -4591
  51. package/lib/public/modules/store.js +27 -0
  52. package/lib/public/modules/ws-ref.js +7 -0
  53. package/lib/public/style.css +1 -0
  54. package/lib/sdk-bridge.js +96 -717
  55. package/lib/sdk-message-processor.js +587 -0
  56. package/lib/sdk-message-queue.js +42 -0
  57. package/lib/sdk-skill-discovery.js +131 -0
  58. package/lib/server-admin.js +712 -0
  59. package/lib/server-auth.js +737 -0
  60. package/lib/server-dm.js +221 -0
  61. package/lib/server-mates.js +281 -0
  62. package/lib/server-palette.js +110 -0
  63. package/lib/server-settings.js +479 -0
  64. package/lib/server-skills.js +280 -0
  65. package/lib/server.js +246 -2755
  66. package/lib/sessions.js +11 -4
  67. package/lib/users-auth.js +146 -0
  68. package/lib/users-permissions.js +118 -0
  69. package/lib/users-preferences.js +210 -0
  70. package/lib/users.js +48 -398
  71. package/lib/ws-schema.js +498 -0
  72. 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 };