clay-server 2.7.2 → 2.8.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 (58) hide show
  1. package/bin/cli.js +2 -1
  2. package/lib/config.js +7 -4
  3. package/lib/project.js +343 -15
  4. package/lib/public/app.js +1043 -135
  5. package/lib/public/apple-touch-icon-dark.png +0 -0
  6. package/lib/public/apple-touch-icon.png +0 -0
  7. package/lib/public/clay-logo.png +0 -0
  8. package/lib/public/css/base.css +10 -0
  9. package/lib/public/css/filebrowser.css +1 -0
  10. package/lib/public/css/home-hub.css +455 -0
  11. package/lib/public/css/icon-strip.css +6 -5
  12. package/lib/public/css/loop.css +141 -23
  13. package/lib/public/css/messages.css +2 -0
  14. package/lib/public/css/mobile-nav.css +38 -12
  15. package/lib/public/css/overlays.css +205 -169
  16. package/lib/public/css/playbook.css +264 -0
  17. package/lib/public/css/profile.css +268 -0
  18. package/lib/public/css/scheduler-modal.css +1429 -0
  19. package/lib/public/css/scheduler.css +1305 -0
  20. package/lib/public/css/sidebar.css +305 -11
  21. package/lib/public/css/sticky-notes.css +23 -19
  22. package/lib/public/css/stt.css +155 -0
  23. package/lib/public/css/title-bar.css +14 -6
  24. package/lib/public/favicon-banded-32.png +0 -0
  25. package/lib/public/favicon-banded.png +0 -0
  26. package/lib/public/icon-192-dark.png +0 -0
  27. package/lib/public/icon-192.png +0 -0
  28. package/lib/public/icon-512-dark.png +0 -0
  29. package/lib/public/icon-512.png +0 -0
  30. package/lib/public/icon-banded-76.png +0 -0
  31. package/lib/public/icon-banded-96.png +0 -0
  32. package/lib/public/index.html +335 -42
  33. package/lib/public/modules/ascii-logo.js +389 -0
  34. package/lib/public/modules/filebrowser.js +2 -1
  35. package/lib/public/modules/markdown.js +118 -0
  36. package/lib/public/modules/notifications.js +50 -63
  37. package/lib/public/modules/playbook.js +578 -0
  38. package/lib/public/modules/profile.js +357 -0
  39. package/lib/public/modules/project-settings.js +4 -9
  40. package/lib/public/modules/scheduler.js +2826 -0
  41. package/lib/public/modules/server-settings.js +1 -1
  42. package/lib/public/modules/sidebar.js +378 -31
  43. package/lib/public/modules/sticky-notes.js +2 -0
  44. package/lib/public/modules/stt.js +272 -0
  45. package/lib/public/modules/terminal.js +32 -0
  46. package/lib/public/modules/theme.js +3 -10
  47. package/lib/public/modules/tools.js +2 -1
  48. package/lib/public/style.css +6 -0
  49. package/lib/public/sw.js +82 -3
  50. package/lib/public/wordmark-banded-20.png +0 -0
  51. package/lib/public/wordmark-banded-32.png +0 -0
  52. package/lib/public/wordmark-banded-64.png +0 -0
  53. package/lib/public/wordmark-banded-80.png +0 -0
  54. package/lib/scheduler.js +402 -0
  55. package/lib/sdk-bridge.js +3 -2
  56. package/lib/server.js +124 -3
  57. package/lib/sessions.js +35 -2
  58. package/package.json +1 -1
@@ -0,0 +1,357 @@
1
+ // User profile module — Discord-style popover for name, language, avatar
2
+ // Stores profile server-side in ~/.clay/profile.json
3
+ // Avatar generated via DiceBear API (deterministic SVG from seed)
4
+
5
+ import { iconHtml, refreshIcons } from './icons.js';
6
+ import { setSTTLang, getSTTLang } from './stt.js';
7
+
8
+ var ctx;
9
+ var profile = { name: '', lang: 'en-US', avatarStyle: 'thumbs', avatarSeed: '', avatarColor: '#7c3aed' };
10
+ var popoverEl = null;
11
+ var saveTimer = null;
12
+ var previewSeed = '';
13
+
14
+ var LANGUAGES = [
15
+ { code: 'en-US', name: 'English' },
16
+ { code: 'ko-KR', name: 'Korean' },
17
+ { code: 'ja-JP', name: 'Japanese' },
18
+ { code: 'zh-CN', name: 'Chinese' },
19
+ { code: 'es-ES', name: 'Spanish' },
20
+ { code: 'fr-FR', name: 'French' },
21
+ { code: 'de-DE', name: 'German' },
22
+ ];
23
+
24
+ var AVATAR_STYLES = [
25
+ { id: 'thumbs', name: 'Thumbs' },
26
+ { id: 'bottts', name: 'Bots' },
27
+ { id: 'pixel-art', name: 'Pixel' },
28
+ { id: 'adventurer', name: 'Adventurer' },
29
+ { id: 'micah', name: 'Micah' },
30
+ { id: 'lorelei', name: 'Lorelei' },
31
+ { id: 'fun-emoji', name: 'Emoji' },
32
+ { id: 'icons', name: 'Icons' },
33
+ ];
34
+
35
+ var COLORS = [
36
+ '#7c3aed', '#4f46e5', '#2563eb', '#0891b2',
37
+ '#059669', '#65a30d', '#d97706', '#dc2626',
38
+ '#db2777', '#6366f1', '#0d9488', '#ea580c',
39
+ '#475569', '#1e293b', '#be123c', '#a21caf',
40
+ '#0369a1', '#15803d',
41
+ ];
42
+
43
+ // --- DiceBear URL builder ---
44
+ function avatarUrl(style, seed, size) {
45
+ var s = encodeURIComponent(seed || 'anonymous');
46
+ return 'https://api.dicebear.com/9.x/' + style + '/svg?seed=' + s + '&size=' + (size || 64);
47
+ }
48
+
49
+ function getAvatarSeed() {
50
+ return profile.avatarSeed || 'anonymous';
51
+ }
52
+
53
+ // --- API ---
54
+ function fetchProfile() {
55
+ return fetch('/api/profile').then(function(r) { return r.json(); });
56
+ }
57
+
58
+ function saveProfile() {
59
+ return fetch('/api/profile', {
60
+ method: 'PUT',
61
+ headers: { 'Content-Type': 'application/json' },
62
+ body: JSON.stringify(profile),
63
+ }).then(function(r) { return r.json(); });
64
+ }
65
+
66
+ function debouncedSave() {
67
+ if (saveTimer) clearTimeout(saveTimer);
68
+ saveTimer = setTimeout(function() {
69
+ saveProfile();
70
+ saveTimer = null;
71
+ }, 400);
72
+ }
73
+
74
+ // --- DOM updates ---
75
+ function applyToIsland() {
76
+ var avatarWrap = document.querySelector('.user-island-avatar');
77
+ var nameEl = document.querySelector('.user-island-name');
78
+ if (!avatarWrap || !nameEl) return;
79
+
80
+ var displayName = profile.name || 'Awesome Clay User';
81
+
82
+ // Replace letter fallback with DiceBear img
83
+ var existingImg = avatarWrap.querySelector('img');
84
+ var existingLetter = avatarWrap.querySelector('.user-island-avatar-letter');
85
+ var url = avatarUrl(profile.avatarStyle || 'thumbs', getAvatarSeed(), 32);
86
+
87
+ if (existingImg) {
88
+ existingImg.src = url;
89
+ } else {
90
+ if (existingLetter) existingLetter.style.display = 'none';
91
+ var img = document.createElement('img');
92
+ img.src = url;
93
+ img.alt = displayName;
94
+ avatarWrap.appendChild(img);
95
+ }
96
+
97
+ nameEl.textContent = displayName;
98
+ }
99
+
100
+ // --- Popover ---
101
+ function showPopover() {
102
+ if (popoverEl) {
103
+ hidePopover();
104
+ return;
105
+ }
106
+
107
+ popoverEl = document.createElement('div');
108
+ popoverEl.className = 'profile-popover';
109
+
110
+ var displayName = profile.name || '';
111
+ var currentLang = profile.lang || 'en-US';
112
+ var currentColor = profile.avatarColor || '#7c3aed';
113
+ var currentStyle = profile.avatarStyle || 'thumbs';
114
+ var seed = getAvatarSeed();
115
+ previewSeed = seed;
116
+
117
+ var html = '';
118
+
119
+ // Banner + close
120
+ html += '<div class="profile-banner" style="background:' + currentColor + '">';
121
+ html += '<button class="profile-close-btn">&times;</button>';
122
+ html += '</div>';
123
+
124
+ // Avatar row (overlapping banner)
125
+ html += '<div class="profile-avatar-row">';
126
+ html += '<div class="profile-popover-avatar">';
127
+ html += '<img class="profile-popover-avatar-img" src="' + avatarUrl(currentStyle, seed, 80) + '" alt="avatar">';
128
+ html += '</div>';
129
+ html += '<div class="profile-name-display">' + escapeAttr(displayName || 'Awesome Clay User') + '</div>';
130
+ html += '</div>';
131
+
132
+ // Body
133
+ html += '<div class="profile-popover-body">';
134
+
135
+ // Name
136
+ html += '<div class="profile-field">';
137
+ html += '<label class="profile-field-label">Display Name</label>';
138
+ html += '<input type="text" class="profile-field-input" id="profile-name-input" value="' + escapeAttr(displayName) + '" placeholder="Enter your name..." maxlength="50" spellcheck="false" autocomplete="off">';
139
+ html += '</div>';
140
+
141
+ // Language dropdown
142
+ html += '<div class="profile-field">';
143
+ html += '<label class="profile-field-label">Language <span class="profile-field-hint">for voice input</span></label>';
144
+ html += '<select class="profile-field-select" id="profile-lang-select">';
145
+ for (var i = 0; i < LANGUAGES.length; i++) {
146
+ var l = LANGUAGES[i];
147
+ var sel = (currentLang === l.code) ? ' selected' : '';
148
+ html += '<option value="' + l.code + '"' + sel + '>' + l.name + '</option>';
149
+ }
150
+ html += '</select>';
151
+ html += '</div>';
152
+
153
+ // Avatar picker
154
+ html += '<div class="profile-field">';
155
+ html += '<label class="profile-field-label">Avatar <button class="profile-shuffle-btn" title="Shuffle">' + iconHtml('shuffle') + '</button></label>';
156
+ html += '<div class="profile-avatar-grid">';
157
+ for (var j = 0; j < AVATAR_STYLES.length; j++) {
158
+ var st = AVATAR_STYLES[j];
159
+ var activeS = (currentStyle === st.id) ? ' profile-avatar-option-active' : '';
160
+ html += '<button class="profile-avatar-option' + activeS + '" data-style="' + st.id + '" title="' + st.name + '">';
161
+ html += '<img src="' + avatarUrl(st.id, seed, 40) + '" alt="' + st.name + '">';
162
+ html += '</button>';
163
+ }
164
+ html += '</div>';
165
+ html += '</div>';
166
+
167
+ // Color
168
+ html += '<div class="profile-field">';
169
+ html += '<label class="profile-field-label">Color</label>';
170
+ html += '<div class="profile-color-grid">';
171
+ for (var k = 0; k < COLORS.length; k++) {
172
+ var c = COLORS[k];
173
+ var activeC = (currentColor === c) ? ' profile-color-active' : '';
174
+ html += '<button class="profile-color-swatch' + activeC + '" data-color="' + c + '" style="background:' + c + '"></button>';
175
+ }
176
+ html += '</div>';
177
+ html += '</div>';
178
+
179
+ html += '</div>'; // close body
180
+
181
+ popoverEl.innerHTML = html;
182
+
183
+ // --- Events ---
184
+ popoverEl.querySelector('.profile-close-btn').addEventListener('click', function(e) {
185
+ e.stopPropagation();
186
+ hidePopover();
187
+ });
188
+
189
+ var nameInput = popoverEl.querySelector('#profile-name-input');
190
+ nameInput.addEventListener('input', function() {
191
+ profile.name = nameInput.value.trim();
192
+ applyToIsland();
193
+ updatePopoverHeader();
194
+ debouncedSave();
195
+ });
196
+
197
+ nameInput.addEventListener('keydown', function(e) {
198
+ if (e.key === 'Enter') {
199
+ e.preventDefault();
200
+ hidePopover();
201
+ }
202
+ e.stopPropagation();
203
+ });
204
+ nameInput.addEventListener('keyup', function(e) { e.stopPropagation(); });
205
+ nameInput.addEventListener('keypress', function(e) { e.stopPropagation(); });
206
+
207
+ // Language dropdown
208
+ popoverEl.querySelector('#profile-lang-select').addEventListener('change', function(e) {
209
+ profile.lang = e.target.value;
210
+ setSTTLang(profile.lang);
211
+ debouncedSave();
212
+ });
213
+
214
+ // Avatar style — clicking confirms both the style and the current previewSeed
215
+ popoverEl.querySelectorAll('.profile-avatar-option[data-style]').forEach(function(btn) {
216
+ btn.addEventListener('click', function() {
217
+ profile.avatarStyle = btn.dataset.style;
218
+ profile.avatarSeed = previewSeed;
219
+ applyToIsland();
220
+ updatePopoverHeader();
221
+ popoverEl.querySelectorAll('.profile-avatar-option').forEach(function(b) {
222
+ b.classList.remove('profile-avatar-option-active');
223
+ });
224
+ btn.classList.add('profile-avatar-option-active');
225
+ debouncedSave();
226
+ });
227
+ });
228
+
229
+ // Shuffle button — only changes preview candidates, not the actual profile
230
+ popoverEl.querySelector('.profile-shuffle-btn').addEventListener('click', function(e) {
231
+ e.stopPropagation();
232
+ previewSeed = Math.random().toString(36).substring(2, 10);
233
+ refreshAvatarPreviews();
234
+ });
235
+
236
+ // Color swatches
237
+ popoverEl.querySelectorAll('.profile-color-swatch').forEach(function(btn) {
238
+ btn.addEventListener('click', function() {
239
+ profile.avatarColor = btn.dataset.color;
240
+ applyToIsland();
241
+ var bannerEl = popoverEl.querySelector('.profile-banner');
242
+ if (bannerEl) bannerEl.style.background = profile.avatarColor;
243
+ popoverEl.querySelectorAll('.profile-color-swatch').forEach(function(b) {
244
+ b.classList.remove('profile-color-active');
245
+ });
246
+ btn.classList.add('profile-color-active');
247
+ debouncedSave();
248
+ });
249
+ });
250
+
251
+ // Prevent clicks inside popover from closing it
252
+ popoverEl.addEventListener('click', function(e) {
253
+ e.stopPropagation();
254
+ });
255
+
256
+ var island = document.getElementById('user-island');
257
+ island.appendChild(popoverEl);
258
+ refreshIcons();
259
+
260
+ if (!profile.name) {
261
+ nameInput.focus();
262
+ }
263
+
264
+ setTimeout(function() {
265
+ document.addEventListener('click', closeOnOutside);
266
+ document.addEventListener('keydown', closeOnEscape);
267
+ }, 0);
268
+ }
269
+
270
+ function updatePopoverHeader() {
271
+ if (!popoverEl) return;
272
+ var img = popoverEl.querySelector('.profile-popover-avatar-img');
273
+ var nd = popoverEl.querySelector('.profile-name-display');
274
+ if (img) img.src = avatarUrl(profile.avatarStyle || 'thumbs', getAvatarSeed(), 80);
275
+ if (nd) nd.textContent = profile.name || 'Awesome Clay User';
276
+ }
277
+
278
+
279
+ function refreshAvatarPreviews() {
280
+ if (!popoverEl) return;
281
+ popoverEl.querySelectorAll('.profile-avatar-option[data-style] img').forEach(function(img) {
282
+ var style = img.closest('.profile-avatar-option').dataset.style;
283
+ img.src = avatarUrl(style, previewSeed, 40);
284
+ });
285
+ }
286
+
287
+ function closeOnOutside(e) {
288
+ var island = document.getElementById('user-island');
289
+ if (popoverEl && !popoverEl.contains(e.target) && !island.contains(e.target)) {
290
+ hidePopover();
291
+ }
292
+ }
293
+
294
+ function closeOnEscape(e) {
295
+ if (e.key === 'Escape' && popoverEl) {
296
+ hidePopover();
297
+ }
298
+ }
299
+
300
+ function hidePopover() {
301
+ if (popoverEl) {
302
+ popoverEl.remove();
303
+ popoverEl = null;
304
+ }
305
+ document.removeEventListener('click', closeOnOutside);
306
+ document.removeEventListener('keydown', closeOnEscape);
307
+ }
308
+
309
+ function escapeAttr(str) {
310
+ return str.replace(/&/g, '&amp;').replace(/"/g, '&quot;').replace(/</g, '&lt;');
311
+ }
312
+
313
+ // --- Init ---
314
+ export function initProfile(_ctx) {
315
+ ctx = _ctx;
316
+
317
+ var island = document.getElementById('user-island');
318
+ if (!island) return;
319
+
320
+ var profileArea = island.querySelector('.user-island-profile');
321
+ if (profileArea) {
322
+ profileArea.addEventListener('click', function(e) {
323
+ e.stopPropagation();
324
+ showPopover();
325
+ });
326
+ }
327
+
328
+ fetchProfile().then(function(data) {
329
+ if (data.name !== undefined) profile.name = data.name;
330
+ if (data.lang) profile.lang = data.lang;
331
+ if (data.avatarColor) profile.avatarColor = data.avatarColor;
332
+ if (data.avatarStyle) profile.avatarStyle = data.avatarStyle;
333
+ if (data.avatarSeed) profile.avatarSeed = data.avatarSeed;
334
+
335
+ // Auto-generate seed if none exists
336
+ if (!profile.avatarSeed) {
337
+ profile.avatarSeed = Math.random().toString(36).substring(2, 10);
338
+ saveProfile();
339
+ }
340
+
341
+ applyToIsland();
342
+
343
+ if (profile.lang) {
344
+ setSTTLang(profile.lang);
345
+ }
346
+ }).catch(function(err) {
347
+ console.warn('[Profile] Failed to load:', err);
348
+ });
349
+ }
350
+
351
+ export function getProfile() {
352
+ return profile;
353
+ }
354
+
355
+ export function getProfileLang() {
356
+ return profile.lang;
357
+ }
@@ -1,6 +1,7 @@
1
1
  // project-settings.js — Project settings panel (profile, defaults, instructions, env)
2
2
  import { refreshIcons } from './icons.js';
3
3
  import { showToast } from './utils.js';
4
+ import { parseEmojis } from './markdown.js';
4
5
 
5
6
  var ctx = null;
6
7
  var panelEl = null;
@@ -252,9 +253,7 @@ function updateIconPreview(icon) {
252
253
  var removeBtn = document.getElementById("ps-icon-remove-btn");
253
254
  if (preview) {
254
255
  preview.textContent = icon || "";
255
- if (typeof twemoji !== "undefined" && icon) {
256
- twemoji.parse(preview, { folder: "svg", ext: ".svg" });
257
- }
256
+ if (icon) parseEmojis(preview);
258
257
  }
259
258
  if (removeBtn) {
260
259
  removeBtn.classList.toggle("hidden", !icon);
@@ -339,9 +338,7 @@ function showPsEmojiPicker() {
339
338
  grid.appendChild(btn);
340
339
  })(emojis[i]);
341
340
  }
342
- if (typeof twemoji !== "undefined") {
343
- twemoji.parse(grid, { folder: "svg", ext: ".svg" });
344
- }
341
+ parseEmojis(grid);
345
342
  scrollArea.scrollTop = 0;
346
343
  }
347
344
 
@@ -353,9 +350,7 @@ function showPsEmojiPicker() {
353
350
  }
354
351
 
355
352
  buildGrid(EMOJI_CATEGORIES[0].emojis);
356
- if (typeof twemoji !== "undefined") {
357
- twemoji.parse(tabBar, { folder: "svg", ext: ".svg" });
358
- }
353
+ parseEmojis(tabBar);
359
354
 
360
355
  anchor.innerHTML = "";
361
356
  anchor.appendChild(picker);