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.
Files changed (71) 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-http.js +4 -2
  10. package/lib/project-loop.js +110 -48
  11. package/lib/project-mate-interaction.js +4 -0
  12. package/lib/project-notifications.js +210 -0
  13. package/lib/project-sessions.js +5 -2
  14. package/lib/project-user-message.js +2 -1
  15. package/lib/project.js +26 -2
  16. package/lib/public/app.js +1193 -8517
  17. package/lib/public/css/command-palette.css +14 -0
  18. package/lib/public/css/loop.css +301 -0
  19. package/lib/public/css/notifications-center.css +190 -0
  20. package/lib/public/css/rewind.css +6 -0
  21. package/lib/public/index.html +89 -35
  22. package/lib/public/modules/app-connection.js +160 -0
  23. package/lib/public/modules/app-cursors.js +473 -0
  24. package/lib/public/modules/app-debate-ui.js +389 -0
  25. package/lib/public/modules/app-dm.js +627 -0
  26. package/lib/public/modules/app-favicon.js +212 -0
  27. package/lib/public/modules/app-header.js +229 -0
  28. package/lib/public/modules/app-home-hub.js +600 -0
  29. package/lib/public/modules/app-loop-ui.js +589 -0
  30. package/lib/public/modules/app-loop-wizard.js +439 -0
  31. package/lib/public/modules/app-messages.js +1560 -0
  32. package/lib/public/modules/app-misc.js +299 -0
  33. package/lib/public/modules/app-notifications.js +372 -0
  34. package/lib/public/modules/app-panels.js +888 -0
  35. package/lib/public/modules/app-projects.js +798 -0
  36. package/lib/public/modules/app-rate-limit.js +451 -0
  37. package/lib/public/modules/app-rendering.js +597 -0
  38. package/lib/public/modules/app-skills-install.js +234 -0
  39. package/lib/public/modules/command-palette.js +27 -4
  40. package/lib/public/modules/input.js +31 -20
  41. package/lib/public/modules/scheduler-config.js +1532 -0
  42. package/lib/public/modules/scheduler-history.js +79 -0
  43. package/lib/public/modules/scheduler.js +33 -1554
  44. package/lib/public/modules/session-search.js +13 -1
  45. package/lib/public/modules/sidebar-mates.js +812 -0
  46. package/lib/public/modules/sidebar-mobile.js +1269 -0
  47. package/lib/public/modules/sidebar-projects.js +1449 -0
  48. package/lib/public/modules/sidebar-sessions.js +986 -0
  49. package/lib/public/modules/sidebar.js +232 -4591
  50. package/lib/public/modules/store.js +27 -0
  51. package/lib/public/modules/ws-ref.js +7 -0
  52. package/lib/public/style.css +1 -0
  53. package/lib/sdk-bridge.js +96 -717
  54. package/lib/sdk-message-processor.js +587 -0
  55. package/lib/sdk-message-queue.js +42 -0
  56. package/lib/sdk-skill-discovery.js +131 -0
  57. package/lib/server-admin.js +712 -0
  58. package/lib/server-auth.js +737 -0
  59. package/lib/server-dm.js +221 -0
  60. package/lib/server-mates.js +281 -0
  61. package/lib/server-palette.js +110 -0
  62. package/lib/server-settings.js +479 -0
  63. package/lib/server-skills.js +280 -0
  64. package/lib/server.js +246 -2755
  65. package/lib/sessions.js +11 -4
  66. package/lib/users-auth.js +146 -0
  67. package/lib/users-permissions.js +118 -0
  68. package/lib/users-preferences.js +210 -0
  69. package/lib/users.js +48 -398
  70. package/lib/ws-schema.js +498 -0
  71. package/package.json +1 -1
@@ -0,0 +1,451 @@
1
+ // app-rate-limit.js - Rate limit UI, scheduled messages, fast mode indicator
2
+ // Extracted from app.js (PR-26)
3
+
4
+ import { iconHtml, refreshIcons } from './icons.js';
5
+ import { store } from './store.js';
6
+ import { getWs } from './ws-ref.js';
7
+ import { addToMessages, scrollToBottom } from './app-rendering.js';
8
+ import { userAvatarUrl } from './avatar.js';
9
+ import { setScheduleDelayMs, clearScheduleDelay } from './input.js';
10
+
11
+ // --- Module-owned state ---
12
+ var rateLimitCountdownTimer = null;
13
+ var rateLimitIndicatorEl = null;
14
+ var rateLimitResetsAt = null;
15
+ var rateLimitResetTimer = null;
16
+ var rateLimitUsageEl = null;
17
+ var rateLimitResetState = {};
18
+ var rateLimitTickTimer = null;
19
+ var scheduledMsgEl = null;
20
+ var scheduledCountdownTimer = null;
21
+ var fastModeIndicatorEl = null;
22
+
23
+ // --- Internal helpers ---
24
+
25
+ function rateLimitTypeLabel(type) {
26
+ if (!type) return "Usage";
27
+ var labels = {
28
+ "five_hour": "5-hour",
29
+ "seven_day": "7-day",
30
+ "seven_day_opus": "7-day Opus",
31
+ "seven_day_sonnet": "7-day Sonnet",
32
+ "overage": "Overage",
33
+ };
34
+ return labels[type] || type;
35
+ }
36
+
37
+ function startRateLimitCountdown(el, resetsAt, cardEl) {
38
+ if (rateLimitCountdownTimer) clearInterval(rateLimitCountdownTimer);
39
+
40
+ function tick() {
41
+ var remaining = resetsAt - Date.now();
42
+ if (remaining <= 0) {
43
+ clearInterval(rateLimitCountdownTimer);
44
+ rateLimitCountdownTimer = null;
45
+ clearRateLimitIndicator();
46
+ return;
47
+ }
48
+ // Update pill text with countdown
49
+ if (rateLimitIndicatorEl) {
50
+ var pillText = rateLimitIndicatorEl.querySelector(".header-pill-text");
51
+ if (pillText) {
52
+ var mins = Math.floor(remaining / 60000);
53
+ var secs = Math.floor((remaining % 60000) / 1000);
54
+ if (mins >= 60) {
55
+ var hrs = Math.floor(mins / 60);
56
+ mins = mins % 60;
57
+ pillText.textContent = hrs + "h " + mins + "m";
58
+ } else {
59
+ pillText.textContent = mins + "m " + secs + "s";
60
+ }
61
+ }
62
+ }
63
+ }
64
+
65
+ tick();
66
+ rateLimitCountdownTimer = setInterval(tick, 1000);
67
+ }
68
+
69
+ function updateRateLimitIndicator(msg) {
70
+ var statusArea = document.querySelector(".title-bar-content .status");
71
+ if (!statusArea) return;
72
+
73
+ if (!rateLimitIndicatorEl) {
74
+ rateLimitIndicatorEl = document.createElement("span");
75
+ rateLimitIndicatorEl.className = "header-rate-limit-wrap";
76
+ statusArea.insertBefore(rateLimitIndicatorEl, statusArea.firstChild);
77
+ }
78
+
79
+ var isRejected = msg.status === "rejected";
80
+ var pillClass = "header-rate-limit" + (isRejected ? " rejected" : " warning");
81
+ var label = isRejected ? "Rate limited" : "Rate warning";
82
+ rateLimitIndicatorEl.innerHTML =
83
+ '<span class="' + pillClass + '">' +
84
+ iconHtml("alert-triangle") +
85
+ '<span class="header-pill-text">' + label + "</span>" +
86
+ '<a href="https://claude.ai/settings/usage" target="_blank" rel="noopener" class="rate-limit-link">' +
87
+ iconHtml("external-link") +
88
+ "</a>" +
89
+ "</span>";
90
+ refreshIcons();
91
+ }
92
+
93
+ function showRateLimitPopover(text, isRejected) {
94
+ if (!rateLimitIndicatorEl) return;
95
+ // Remove existing popover
96
+ var old = rateLimitIndicatorEl.querySelector(".rate-limit-popover");
97
+ if (old) old.remove();
98
+
99
+ var pop = document.createElement("div");
100
+ pop.className = "rate-limit-popover" + (isRejected ? " rejected" : "");
101
+ pop.textContent = text;
102
+ rateLimitIndicatorEl.appendChild(pop);
103
+
104
+ // Auto-dismiss after 5s
105
+ setTimeout(function () {
106
+ pop.classList.add("fade-out");
107
+ setTimeout(function () { if (pop.parentNode) pop.remove(); }, 300);
108
+ }, 5000);
109
+ }
110
+
111
+ function clearRateLimitIndicator() {
112
+ if (rateLimitIndicatorEl) {
113
+ rateLimitIndicatorEl.remove();
114
+ rateLimitIndicatorEl = null;
115
+ }
116
+ }
117
+
118
+ function formatResetTime(resetsAt) {
119
+ if (!resetsAt) return "";
120
+ var d = new Date(resetsAt);
121
+ var now = new Date();
122
+ var diff = resetsAt - now.getTime();
123
+ if (diff <= 0) return "";
124
+ var hrs = Math.floor(diff / 3600000);
125
+ var mins = Math.floor((diff % 3600000) / 60000);
126
+ if (hrs > 0) return hrs + "h " + mins + "m";
127
+ return mins + "m";
128
+ }
129
+
130
+ function rateLimitTypeShortLabel(type) {
131
+ if (type === "five_hour") return "5h";
132
+ if (type === "seven_day") return "7d";
133
+ if (type === "seven_day_opus") return "7d opus";
134
+ if (type === "seven_day_sonnet") return "7d sonnet";
135
+ return type || "";
136
+ }
137
+
138
+ function tickRateLimitUsage() {
139
+ if (!rateLimitUsageEl) return;
140
+ var parts = [];
141
+ var types = ["five_hour", "seven_day", "seven_day_opus", "seven_day_sonnet"];
142
+ for (var i = 0; i < types.length; i++) {
143
+ var entry = rateLimitResetState[types[i]];
144
+ if (!entry || !entry.resetsAt) continue;
145
+ var timeStr = formatResetTime(entry.resetsAt);
146
+ if (!timeStr) { delete rateLimitResetState[types[i]]; continue; }
147
+ parts.push(rateLimitTypeShortLabel(types[i]) + " resets " + timeStr);
148
+ }
149
+ if (parts.length === 0) {
150
+ rateLimitUsageEl.innerHTML = iconHtml("activity") + '<span>Check usage</span>' + iconHtml("external-link");
151
+ refreshIcons();
152
+ if (rateLimitTickTimer) { clearInterval(rateLimitTickTimer); rateLimitTickTimer = null; }
153
+ return;
154
+ }
155
+ var label = parts.join(" · ");
156
+ rateLimitUsageEl.innerHTML = iconHtml("activity") + '<span>' + label + '</span>' + iconHtml("external-link");
157
+ refreshIcons();
158
+ }
159
+
160
+ // --- Exported functions ---
161
+
162
+ export function initRateLimit() {}
163
+
164
+ export function handleRateLimitEvent(msg) {
165
+ var isRejected = msg.status === "rejected";
166
+ var typeLabel = rateLimitTypeLabel(msg.rateLimitType);
167
+ var popoverText = "";
168
+
169
+ if (isRejected && msg.resetsAt) {
170
+ // Check if already expired (history replay) — skip popover
171
+ if (msg.resetsAt < Date.now()) {
172
+ updateRateLimitIndicator(msg);
173
+ return;
174
+ }
175
+ popoverText = typeLabel + " limit exceeded";
176
+ updateRateLimitIndicator(msg);
177
+ startRateLimitCountdown(null, msg.resetsAt, null);
178
+ // Track rate limit reset time
179
+ rateLimitResetsAt = msg.resetsAt;
180
+ if (rateLimitResetTimer) clearTimeout(rateLimitResetTimer);
181
+ // Auto-switch input to schedule mode: any message typed will be queued for after reset
182
+ var delayUntilReset = msg.resetsAt - Date.now();
183
+ if (delayUntilReset > 0) {
184
+ setScheduleDelayMs(delayUntilReset + 60000); // +1min buffer after reset
185
+ }
186
+ rateLimitResetTimer = setTimeout(function () {
187
+ rateLimitResetsAt = null;
188
+ rateLimitResetTimer = null;
189
+ // Clear schedule mode when rate limit resets
190
+ clearScheduleDelay();
191
+ }, msg.resetsAt - Date.now() + 1000);
192
+ } else {
193
+ var pct = msg.utilization ? Math.round(msg.utilization * 100) : null;
194
+ popoverText = typeLabel + " warning" + (pct ? " (" + pct + "% used)" : "");
195
+ updateRateLimitIndicator(msg);
196
+ }
197
+
198
+ showRateLimitPopover(popoverText, isRejected);
199
+ }
200
+
201
+ export function updateRateLimitUsage(msg) {
202
+ if (msg.rateLimitType && msg.resetsAt) {
203
+ rateLimitResetState[msg.rateLimitType] = { resetsAt: msg.resetsAt, status: msg.status };
204
+ }
205
+
206
+ var topBarActions = document.querySelector("#top-bar .top-bar-actions");
207
+ if (!topBarActions) return;
208
+
209
+ if (!rateLimitUsageEl) {
210
+ rateLimitUsageEl = document.createElement("a");
211
+ rateLimitUsageEl.id = "rate-limit-usage-link";
212
+ rateLimitUsageEl.className = "top-bar-pill pill-dim usage-check-link";
213
+ rateLimitUsageEl.href = "https://claude.ai/settings/usage";
214
+ rateLimitUsageEl.target = "_blank";
215
+ rateLimitUsageEl.rel = "noopener";
216
+ rateLimitUsageEl.title = "Check usage on claude.ai";
217
+ var ref = document.getElementById("skip-perms-pill");
218
+ topBarActions.insertBefore(rateLimitUsageEl, ref);
219
+ }
220
+
221
+ // Build label from available reset times
222
+ var parts = [];
223
+ var types = ["five_hour", "seven_day", "seven_day_opus", "seven_day_sonnet"];
224
+ for (var i = 0; i < types.length; i++) {
225
+ var entry = rateLimitResetState[types[i]];
226
+ if (!entry || !entry.resetsAt) continue;
227
+ var timeStr = formatResetTime(entry.resetsAt);
228
+ if (!timeStr) continue;
229
+ parts.push(rateLimitTypeShortLabel(types[i]) + " resets " + timeStr);
230
+ }
231
+
232
+ var label = parts.length > 0 ? parts.join(" · ") : "Check usage";
233
+ rateLimitUsageEl.innerHTML = iconHtml("activity") + '<span>' + label + '</span>' + iconHtml("external-link");
234
+ refreshIcons();
235
+
236
+ // Start or stop live countdown tick
237
+ if (parts.length > 0 && !rateLimitTickTimer) {
238
+ rateLimitTickTimer = setInterval(tickRateLimitUsage, 30000);
239
+ } else if (parts.length === 0 && rateLimitTickTimer) {
240
+ clearInterval(rateLimitTickTimer);
241
+ rateLimitTickTimer = null;
242
+ }
243
+ }
244
+
245
+ export function addScheduledMessageBubble(text, resetsAt) {
246
+ removeScheduledMessageBubble();
247
+ var isChannel = document.body.classList.contains("wide-view");
248
+ var wrap = document.createElement("div");
249
+ wrap.className = "msg-user scheduled-msg-wrap";
250
+ wrap.id = "scheduled-msg-bubble";
251
+
252
+ var countdownEl;
253
+ var cancelBtn;
254
+
255
+ if (isChannel) {
256
+ // Channel mode: avatar + header with scheduled badge + message
257
+ var _me = store.getState().cachedAllUsers.find(function (u) { return u.id === store.getState().myUserId; });
258
+ if (!_me) { try { _me = JSON.parse(localStorage.getItem("clay_my_user") || "null"); } catch(e) {} }
259
+ var _myName = document.body.dataset.myDisplayName || (_me && (_me.displayName || _me.username)) || "Me";
260
+
261
+ var avi = document.createElement("img");
262
+ avi.className = "dm-bubble-avatar dm-bubble-avatar-me";
263
+ avi.src = document.body.dataset.myAvatarUrl || userAvatarUrl(_me || { id: store.getState().myUserId }, 36);
264
+ wrap.appendChild(avi);
265
+
266
+ var content = document.createElement("div");
267
+ content.className = "dm-bubble-content";
268
+
269
+ var header = document.createElement("div");
270
+ header.className = "dm-bubble-header";
271
+
272
+ var nameSpan = document.createElement("span");
273
+ nameSpan.className = "dm-bubble-name";
274
+ nameSpan.textContent = _myName;
275
+ header.appendChild(nameSpan);
276
+
277
+ var badge = document.createElement("span");
278
+ badge.className = "scheduled-msg-badge";
279
+ badge.innerHTML = iconHtml("clock");
280
+ countdownEl = document.createElement("span");
281
+ countdownEl.className = "scheduled-msg-countdown";
282
+ badge.appendChild(countdownEl);
283
+ header.appendChild(badge);
284
+
285
+ var actions = document.createElement("span");
286
+ actions.className = "scheduled-msg-actions";
287
+
288
+ var sendNowBtn = document.createElement("button");
289
+ sendNowBtn.className = "scheduled-msg-send-now";
290
+ sendNowBtn.textContent = "Send now";
291
+ sendNowBtn.addEventListener("click", function () {
292
+ var ws = getWs();
293
+ if (ws && ws.readyState === 1) {
294
+ ws.send(JSON.stringify({ type: "send_scheduled_now" }));
295
+ }
296
+ });
297
+ actions.appendChild(sendNowBtn);
298
+
299
+ var sep = document.createElement("span");
300
+ sep.className = "scheduled-msg-sep";
301
+ sep.textContent = "\u00b7";
302
+ actions.appendChild(sep);
303
+
304
+ cancelBtn = document.createElement("button");
305
+ cancelBtn.className = "scheduled-msg-cancel";
306
+ cancelBtn.textContent = "Cancel";
307
+ cancelBtn.addEventListener("click", function () {
308
+ var ws = getWs();
309
+ if (ws && ws.readyState === 1) {
310
+ ws.send(JSON.stringify({ type: "cancel_scheduled_message" }));
311
+ }
312
+ });
313
+ actions.appendChild(cancelBtn);
314
+
315
+ header.appendChild(actions);
316
+
317
+ content.appendChild(header);
318
+
319
+ var bubble = document.createElement("div");
320
+ bubble.className = "bubble scheduled-msg-bubble";
321
+ var textEl = document.createElement("span");
322
+ textEl.textContent = text;
323
+ bubble.appendChild(textEl);
324
+ content.appendChild(bubble);
325
+
326
+ wrap.appendChild(content);
327
+ } else {
328
+ // Bubble mode: original layout
329
+ var bubble = document.createElement("div");
330
+ bubble.className = "bubble scheduled-msg-bubble";
331
+
332
+ var textEl = document.createElement("span");
333
+ textEl.textContent = text;
334
+ bubble.appendChild(textEl);
335
+
336
+ var metaEl = document.createElement("div");
337
+ metaEl.className = "scheduled-msg-meta";
338
+
339
+ var clockIcon = document.createElement("span");
340
+ clockIcon.className = "scheduled-msg-icon";
341
+ clockIcon.innerHTML = iconHtml("clock");
342
+ metaEl.appendChild(clockIcon);
343
+
344
+ countdownEl = document.createElement("span");
345
+ countdownEl.className = "scheduled-msg-countdown";
346
+ metaEl.appendChild(countdownEl);
347
+
348
+ var sendNowBtn2 = document.createElement("button");
349
+ sendNowBtn2.className = "scheduled-msg-send-now";
350
+ sendNowBtn2.textContent = "Send now";
351
+ sendNowBtn2.addEventListener("click", function () {
352
+ var ws = getWs();
353
+ if (ws && ws.readyState === 1) {
354
+ ws.send(JSON.stringify({ type: "send_scheduled_now" }));
355
+ }
356
+ });
357
+ metaEl.appendChild(sendNowBtn2);
358
+
359
+ var sep2 = document.createElement("span");
360
+ sep2.className = "scheduled-msg-sep";
361
+ sep2.textContent = "\u00b7";
362
+ metaEl.appendChild(sep2);
363
+
364
+ cancelBtn = document.createElement("button");
365
+ cancelBtn.className = "scheduled-msg-cancel";
366
+ cancelBtn.textContent = "Cancel";
367
+ cancelBtn.addEventListener("click", function () {
368
+ var ws = getWs();
369
+ if (ws && ws.readyState === 1) {
370
+ ws.send(JSON.stringify({ type: "cancel_scheduled_message" }));
371
+ }
372
+ });
373
+ metaEl.appendChild(cancelBtn);
374
+
375
+ wrap.appendChild(bubble);
376
+ wrap.appendChild(metaEl);
377
+ }
378
+
379
+ addToMessages(wrap);
380
+ scheduledMsgEl = wrap;
381
+ scrollToBottom();
382
+
383
+ // Start countdown
384
+ function updateCountdown() {
385
+ var remaining = resetsAt - Date.now();
386
+ if (remaining <= 0) {
387
+ countdownEl.textContent = "Sending...";
388
+ if (scheduledCountdownTimer) { clearInterval(scheduledCountdownTimer); scheduledCountdownTimer = null; }
389
+ return;
390
+ }
391
+ var hrs = Math.floor(remaining / 3600000);
392
+ var mins = Math.floor((remaining % 3600000) / 60000);
393
+ var secs = Math.floor((remaining % 60000) / 1000);
394
+ var timeStr = "";
395
+ if (hrs > 0) timeStr += hrs + "h ";
396
+ if (mins > 0 || hrs > 0) timeStr += mins + "m ";
397
+ timeStr += secs + "s";
398
+
399
+ var sendDate = new Date(resetsAt);
400
+ var absTime = sendDate.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
401
+ countdownEl.textContent = "Sends at " + absTime + " (" + timeStr + ")";
402
+ }
403
+ updateCountdown();
404
+ scheduledCountdownTimer = setInterval(updateCountdown, 1000);
405
+ }
406
+
407
+ export function removeScheduledMessageBubble() {
408
+ if (scheduledMsgEl) {
409
+ scheduledMsgEl.remove();
410
+ scheduledMsgEl = null;
411
+ }
412
+ if (scheduledCountdownTimer) {
413
+ clearInterval(scheduledCountdownTimer);
414
+ scheduledCountdownTimer = null;
415
+ }
416
+ }
417
+
418
+ export function handleFastModeState(state) {
419
+ var statusArea = document.querySelector(".title-bar-content .status");
420
+ if (!statusArea) return;
421
+
422
+ if (state === "off") {
423
+ if (fastModeIndicatorEl) {
424
+ fastModeIndicatorEl.remove();
425
+ fastModeIndicatorEl = null;
426
+ }
427
+ return;
428
+ }
429
+
430
+ if (!fastModeIndicatorEl) {
431
+ fastModeIndicatorEl = document.createElement("span");
432
+ statusArea.insertBefore(fastModeIndicatorEl, statusArea.firstChild);
433
+ }
434
+
435
+ if (state === "cooldown") {
436
+ fastModeIndicatorEl.className = "header-fast-mode cooldown";
437
+ fastModeIndicatorEl.innerHTML = iconHtml("timer") + '<span class="header-pill-text">Cooldown</span>';
438
+ } else if (state === "on") {
439
+ fastModeIndicatorEl.className = "header-fast-mode active";
440
+ fastModeIndicatorEl.innerHTML = iconHtml("zap") + '<span class="header-pill-text">Fast mode</span>';
441
+ }
442
+ refreshIcons();
443
+ }
444
+
445
+ export function getScheduledMsgEl() { return scheduledMsgEl; }
446
+
447
+ export function resetRateLimitState() {
448
+ clearRateLimitIndicator();
449
+ if (rateLimitCountdownTimer) { clearInterval(rateLimitCountdownTimer); rateLimitCountdownTimer = null; }
450
+ if (fastModeIndicatorEl) { fastModeIndicatorEl.remove(); fastModeIndicatorEl = null; }
451
+ }