clay-server 2.5.1 → 2.7.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 (41) hide show
  1. package/bin/claude-relay.js +6 -0
  2. package/bin/cli.js +77 -11
  3. package/lib/cli-sessions.js +2 -7
  4. package/lib/config.js +86 -7
  5. package/lib/daemon.js +279 -6
  6. package/lib/ipc.js +12 -0
  7. package/lib/notes.js +4 -3
  8. package/lib/project.js +1174 -28
  9. package/lib/public/app.js +879 -31
  10. package/lib/public/css/diff.css +15 -4
  11. package/lib/public/css/filebrowser.css +363 -3
  12. package/lib/public/css/icon-strip.css +317 -1
  13. package/lib/public/css/input.css +127 -50
  14. package/lib/public/css/loop.css +780 -0
  15. package/lib/public/css/messages.css +1 -1
  16. package/lib/public/css/mobile-nav.css +6 -2
  17. package/lib/public/css/rewind.css +23 -0
  18. package/lib/public/css/server-settings.css +67 -20
  19. package/lib/public/css/sidebar.css +10 -4
  20. package/lib/public/css/skills.css +789 -0
  21. package/lib/public/css/sticky-notes.css +486 -0
  22. package/lib/public/css/title-bar.css +157 -7
  23. package/lib/public/index.html +366 -55
  24. package/lib/public/modules/diff.js +3 -3
  25. package/lib/public/modules/filebrowser.js +169 -45
  26. package/lib/public/modules/input.js +123 -56
  27. package/lib/public/modules/project-settings.js +906 -0
  28. package/lib/public/modules/qrcode.js +23 -26
  29. package/lib/public/modules/server-settings.js +449 -53
  30. package/lib/public/modules/sidebar.js +732 -1
  31. package/lib/public/modules/skills.js +794 -0
  32. package/lib/public/modules/sticky-notes.js +617 -52
  33. package/lib/public/modules/terminal.js +7 -0
  34. package/lib/public/modules/theme.js +9 -19
  35. package/lib/public/modules/tools.js +16 -2
  36. package/lib/public/style.css +2 -0
  37. package/lib/sdk-bridge.js +46 -7
  38. package/lib/server.js +305 -1
  39. package/lib/sessions.js +11 -5
  40. package/lib/utils.js +18 -0
  41. package/package.json +3 -2
@@ -0,0 +1,906 @@
1
+ // project-settings.js — Project settings panel (profile, defaults, instructions, env)
2
+ import { refreshIcons } from './icons.js';
3
+ import { showToast } from './utils.js';
4
+
5
+ var ctx = null;
6
+ var panelEl = null;
7
+ var navItems = null;
8
+ var sections = null;
9
+ var currentSlug = null;
10
+ var currentProject = null; // { slug, name, icon }
11
+
12
+ // Emoji categories (reuse from sidebar)
13
+ var EMOJI_CATEGORIES = null;
14
+
15
+ var MODE_OPTIONS = [
16
+ { value: "default", label: "Default", desc: "Claude asks for permission before running tools and editing files." },
17
+ { value: "plan", label: "Plan", desc: "Claude creates a plan first and asks for approval before making changes." },
18
+ { value: "acceptEdits", label: "Auto-accept edits", desc: "File edits are applied automatically. Claude still asks before running commands." },
19
+ ];
20
+
21
+ var EFFORT_LEVELS = [
22
+ { value: "low", desc: "Quick, concise responses. Best for simple questions." },
23
+ { value: "medium", desc: "Balanced responses with moderate reasoning. Good for most tasks." },
24
+ { value: "high", desc: "Thorough responses with deeper analysis. Good for complex tasks." },
25
+ { value: "max", desc: "Maximum reasoning depth. Best for the most difficult problems." },
26
+ ];
27
+
28
+ var MODEL_DESCRIPTIONS = {
29
+ "default": "Automatically selects the best model for the task.",
30
+ "sonnet": "Fast and capable. Great balance of speed and intelligence.",
31
+ "haiku": "Fastest model. Best for quick tasks and simple questions.",
32
+ "opus": "Most powerful model. Best for complex reasoning and analysis.",
33
+ };
34
+
35
+ // ===== Init =====
36
+ export function initProjectSettings(appCtx, emojiCategories) {
37
+ ctx = appCtx;
38
+ EMOJI_CATEGORIES = emojiCategories;
39
+ panelEl = document.getElementById("project-settings");
40
+ if (!panelEl) return;
41
+
42
+ navItems = panelEl.querySelectorAll(".settings-nav-item");
43
+ sections = panelEl.querySelectorAll(".ps-section");
44
+
45
+ // Nav clicks
46
+ for (var i = 0; i < navItems.length; i++) {
47
+ navItems[i].addEventListener("click", function () {
48
+ switchSection(this.dataset.section);
49
+ });
50
+ }
51
+
52
+ // Close button
53
+ var closeBtn = document.getElementById("project-settings-close");
54
+ if (closeBtn) {
55
+ closeBtn.addEventListener("click", function () {
56
+ closeProjectSettings();
57
+ });
58
+ }
59
+
60
+ // ESC key
61
+ document.addEventListener("keydown", function (e) {
62
+ if (e.key === "Escape" && panelEl && !panelEl.classList.contains("hidden")) {
63
+ closeProjectSettings();
64
+ }
65
+ });
66
+
67
+ // Profile: rename
68
+ var renameBtn = document.getElementById("ps-rename-btn");
69
+ var renameForm = document.getElementById("ps-rename-form");
70
+ var renameInput = document.getElementById("ps-rename-input");
71
+ var renameSave = document.getElementById("ps-rename-save");
72
+ var renameCancel = document.getElementById("ps-rename-cancel");
73
+
74
+ if (renameBtn) {
75
+ renameBtn.addEventListener("click", function () {
76
+ renameForm.classList.remove("hidden");
77
+ renameInput.value = currentProject ? currentProject.name || "" : "";
78
+ renameBtn.classList.add("hidden");
79
+ renameInput.focus();
80
+ renameInput.select();
81
+ });
82
+ }
83
+ if (renameSave) {
84
+ renameSave.addEventListener("click", function () { commitRename(); });
85
+ }
86
+ if (renameCancel) {
87
+ renameCancel.addEventListener("click", function () { cancelRename(); });
88
+ }
89
+ if (renameInput) {
90
+ renameInput.addEventListener("keydown", function (e) {
91
+ e.stopPropagation();
92
+ if (e.key === "Enter") { e.preventDefault(); commitRename(); }
93
+ if (e.key === "Escape") { e.preventDefault(); cancelRename(); }
94
+ });
95
+ }
96
+
97
+ // Profile: icon
98
+ var iconBtn = document.getElementById("ps-icon-btn");
99
+ var iconRemoveBtn = document.getElementById("ps-icon-remove-btn");
100
+ if (iconBtn) {
101
+ iconBtn.addEventListener("click", function () {
102
+ showPsEmojiPicker();
103
+ });
104
+ }
105
+ if (iconRemoveBtn) {
106
+ iconRemoveBtn.addEventListener("click", function () {
107
+ if (ctx.ws && ctx.connected) {
108
+ ctx.ws.send(JSON.stringify({ type: "set_project_icon", slug: currentSlug, icon: null }));
109
+ }
110
+ updateIconPreview(null);
111
+ });
112
+ }
113
+
114
+ // Instructions: save
115
+ var instrSave = document.getElementById("ps-instructions-save");
116
+ if (instrSave) {
117
+ instrSave.addEventListener("click", function () { saveInstructions(); });
118
+ }
119
+
120
+ // Environment: add button
121
+ var envAddBtn = document.getElementById("ps-env-add-btn");
122
+ if (envAddBtn) {
123
+ envAddBtn.addEventListener("click", function () {
124
+ addEnvRow("", "", true);
125
+ autoSaveEnv();
126
+ });
127
+ }
128
+
129
+ // Environment: tab switching
130
+ var envTabs = panelEl.querySelectorAll(".ps-env-tab");
131
+ var envTabContents = panelEl.querySelectorAll(".ps-env-tab-content");
132
+ for (var ti = 0; ti < envTabs.length; ti++) {
133
+ envTabs[ti].addEventListener("click", function () {
134
+ var tab = this.dataset.tab;
135
+ for (var a = 0; a < envTabs.length; a++) {
136
+ envTabs[a].classList.toggle("active", envTabs[a].dataset.tab === tab);
137
+ }
138
+ for (var b = 0; b < envTabContents.length; b++) {
139
+ envTabContents[b].classList.toggle("active", envTabContents[b].dataset.tab === tab);
140
+ }
141
+ if (tab === "shared") loadSharedEnv();
142
+ });
143
+ }
144
+
145
+ // Environment: shared env add button
146
+ var sharedEnvAddBtn = document.getElementById("ps-shared-env-add-btn");
147
+ if (sharedEnvAddBtn) {
148
+ sharedEnvAddBtn.addEventListener("click", function () {
149
+ addSharedEnvRow("", "", true);
150
+ autoSaveSharedEnv();
151
+ });
152
+ }
153
+ }
154
+
155
+ // ===== Open / Close =====
156
+ export function openProjectSettings(slug, project) {
157
+ if (!panelEl) return;
158
+ currentSlug = slug;
159
+ currentProject = project;
160
+
161
+ // Set nav title
162
+ var navTitle = document.getElementById("ps-nav-title");
163
+ if (navTitle) navTitle.textContent = project.name || slug;
164
+
165
+ // Reset to first section
166
+ switchSection("profile");
167
+
168
+ // Populate profile
169
+ populateProfile();
170
+
171
+ // Show panel
172
+ panelEl.classList.remove("hidden");
173
+ refreshIcons();
174
+ }
175
+
176
+ export function closeProjectSettings() {
177
+ if (!panelEl) return;
178
+ panelEl.classList.add("hidden");
179
+ closePsEmojiPicker();
180
+ }
181
+
182
+ export function isProjectSettingsOpen() {
183
+ return panelEl && !panelEl.classList.contains("hidden");
184
+ }
185
+
186
+ // ===== Section switching =====
187
+ function switchSection(name) {
188
+ for (var i = 0; i < navItems.length; i++) {
189
+ var active = navItems[i].dataset.section === name;
190
+ navItems[i].classList.toggle("active", active);
191
+ }
192
+ for (var j = 0; j < sections.length; j++) {
193
+ var active2 = sections[j].dataset.section === name;
194
+ sections[j].classList.toggle("active", active2);
195
+ }
196
+
197
+ // Lazy-load section data
198
+ if (name === "defaults") populateDefaults();
199
+ if (name === "instructions") loadInstructions();
200
+ if (name === "environment") {
201
+ // Reset tabs to "project" tab
202
+ var envTabs = panelEl.querySelectorAll(".ps-env-tab");
203
+ var envTabContents = panelEl.querySelectorAll(".ps-env-tab-content");
204
+ for (var t = 0; t < envTabs.length; t++) {
205
+ envTabs[t].classList.toggle("active", envTabs[t].dataset.tab === "project");
206
+ }
207
+ for (var u = 0; u < envTabContents.length; u++) {
208
+ envTabContents[u].classList.toggle("active", envTabContents[u].dataset.tab === "project");
209
+ }
210
+ loadEnvironment();
211
+ }
212
+ }
213
+
214
+ // ===== Profile =====
215
+ function populateProfile() {
216
+ var nameEl = document.getElementById("ps-project-name");
217
+ if (nameEl) nameEl.textContent = currentProject ? currentProject.name || "-" : "-";
218
+
219
+ // Reset rename form
220
+ var renameForm = document.getElementById("ps-rename-form");
221
+ var renameBtn = document.getElementById("ps-rename-btn");
222
+ if (renameForm) renameForm.classList.add("hidden");
223
+ if (renameBtn) renameBtn.classList.remove("hidden");
224
+
225
+ // Icon
226
+ updateIconPreview(currentProject ? currentProject.icon : null);
227
+ }
228
+
229
+ function commitRename() {
230
+ var renameInput = document.getElementById("ps-rename-input");
231
+ var nameEl = document.getElementById("ps-project-name");
232
+ var newName = renameInput ? renameInput.value.trim() : "";
233
+ if (newName && ctx.ws && ctx.connected) {
234
+ ctx.ws.send(JSON.stringify({ type: "set_project_title", slug: currentSlug, title: newName }));
235
+ if (nameEl) nameEl.textContent = newName;
236
+ if (currentProject) currentProject.name = newName;
237
+ var navTitle = document.getElementById("ps-nav-title");
238
+ if (navTitle) navTitle.textContent = newName;
239
+ }
240
+ cancelRename();
241
+ }
242
+
243
+ function cancelRename() {
244
+ var renameForm = document.getElementById("ps-rename-form");
245
+ var renameBtn = document.getElementById("ps-rename-btn");
246
+ if (renameForm) renameForm.classList.add("hidden");
247
+ if (renameBtn) renameBtn.classList.remove("hidden");
248
+ }
249
+
250
+ function updateIconPreview(icon) {
251
+ var preview = document.getElementById("ps-icon-preview");
252
+ var removeBtn = document.getElementById("ps-icon-remove-btn");
253
+ if (preview) {
254
+ preview.textContent = icon || "";
255
+ if (typeof twemoji !== "undefined" && icon) {
256
+ twemoji.parse(preview, { folder: "svg", ext: ".svg" });
257
+ }
258
+ }
259
+ if (removeBtn) {
260
+ removeBtn.classList.toggle("hidden", !icon);
261
+ }
262
+ }
263
+
264
+ // ===== Emoji picker (inline in settings) =====
265
+ var psEmojiPickerEl = null;
266
+
267
+ function closePsEmojiPicker() {
268
+ if (psEmojiPickerEl) {
269
+ psEmojiPickerEl.remove();
270
+ psEmojiPickerEl = null;
271
+ }
272
+ }
273
+
274
+ function showPsEmojiPicker() {
275
+ closePsEmojiPicker();
276
+ if (!EMOJI_CATEGORIES) return;
277
+
278
+ var anchor = document.getElementById("ps-emoji-picker-anchor");
279
+ if (!anchor) return;
280
+
281
+ var picker = document.createElement("div");
282
+ picker.className = "emoji-picker";
283
+ picker.style.position = "relative";
284
+ picker.style.left = "0";
285
+ picker.style.top = "0";
286
+ picker.style.marginTop = "8px";
287
+ picker.addEventListener("click", function (e) { e.stopPropagation(); });
288
+
289
+ // Header
290
+ var header = document.createElement("div");
291
+ header.className = "emoji-picker-header";
292
+ header.textContent = "Choose Icon";
293
+ picker.appendChild(header);
294
+
295
+ // Category tabs
296
+ var tabBar = document.createElement("div");
297
+ tabBar.className = "emoji-picker-tabs";
298
+ var tabBtns = [];
299
+
300
+ for (var t = 0; t < EMOJI_CATEGORIES.length; t++) {
301
+ (function (cat, idx) {
302
+ var tab = document.createElement("button");
303
+ tab.className = "emoji-picker-tab" + (idx === 0 ? " active" : "");
304
+ tab.textContent = cat.icon;
305
+ tab.title = cat.label;
306
+ tab.addEventListener("click", function (e) {
307
+ e.stopPropagation();
308
+ switchCategory(idx);
309
+ });
310
+ tabBar.appendChild(tab);
311
+ tabBtns.push(tab);
312
+ })(EMOJI_CATEGORIES[t], t);
313
+ }
314
+ picker.appendChild(tabBar);
315
+
316
+ // Grid
317
+ var scrollArea = document.createElement("div");
318
+ scrollArea.className = "emoji-picker-scroll";
319
+ var grid = document.createElement("div");
320
+ grid.className = "emoji-picker-grid";
321
+ scrollArea.appendChild(grid);
322
+ picker.appendChild(scrollArea);
323
+
324
+ function buildGrid(emojis) {
325
+ grid.innerHTML = "";
326
+ for (var i = 0; i < emojis.length; i++) {
327
+ (function (emoji) {
328
+ var btn = document.createElement("button");
329
+ btn.className = "emoji-picker-item";
330
+ btn.textContent = emoji;
331
+ btn.addEventListener("click", function (e) {
332
+ e.stopPropagation();
333
+ closePsEmojiPicker();
334
+ if (ctx.ws && ctx.connected) {
335
+ ctx.ws.send(JSON.stringify({ type: "set_project_icon", slug: currentSlug, icon: emoji }));
336
+ }
337
+ updateIconPreview(emoji);
338
+ });
339
+ grid.appendChild(btn);
340
+ })(emojis[i]);
341
+ }
342
+ if (typeof twemoji !== "undefined") {
343
+ twemoji.parse(grid, { folder: "svg", ext: ".svg" });
344
+ }
345
+ scrollArea.scrollTop = 0;
346
+ }
347
+
348
+ function switchCategory(idx) {
349
+ for (var j = 0; j < tabBtns.length; j++) {
350
+ tabBtns[j].classList.toggle("active", j === idx);
351
+ }
352
+ buildGrid(EMOJI_CATEGORIES[idx].emojis);
353
+ }
354
+
355
+ buildGrid(EMOJI_CATEGORIES[0].emojis);
356
+ if (typeof twemoji !== "undefined") {
357
+ twemoji.parse(tabBar, { folder: "svg", ext: ".svg" });
358
+ }
359
+
360
+ anchor.innerHTML = "";
361
+ anchor.appendChild(picker);
362
+ psEmojiPickerEl = picker;
363
+ }
364
+
365
+ // ===== Defaults =====
366
+ function getModelDesc(model) {
367
+ if (!model) return "";
368
+ var lower = (model.value || model).toLowerCase();
369
+ for (var key in MODEL_DESCRIPTIONS) {
370
+ if (lower.indexOf(key) !== -1) return MODEL_DESCRIPTIONS[key];
371
+ }
372
+ return "";
373
+ }
374
+
375
+ function isSonnetModel(model) {
376
+ if (!model) return false;
377
+ return model.toLowerCase().indexOf("sonnet") !== -1;
378
+ }
379
+
380
+ function populateDefaults() {
381
+ var models = ctx.currentModels || [];
382
+ var model = ctx.currentModel || "";
383
+ var mode = ctx.currentMode || "default";
384
+ var effort = ctx.currentEffort || "medium";
385
+ var betas = ctx.currentBetas || [];
386
+
387
+ // Model list
388
+ var modelList = document.getElementById("ps-model-list");
389
+ if (modelList) {
390
+ modelList.innerHTML = "";
391
+ for (var i = 0; i < models.length; i++) {
392
+ (function (m) {
393
+ var item = document.createElement("div");
394
+ item.className = "settings-model-item" + (m.value === model ? " active" : "");
395
+
396
+ var nameSpan = document.createElement("span");
397
+ nameSpan.className = "settings-model-name";
398
+ nameSpan.textContent = m.displayName || m.value;
399
+ item.appendChild(nameSpan);
400
+
401
+ var desc = getModelDesc(m.value);
402
+ if (desc) {
403
+ var descSpan = document.createElement("span");
404
+ descSpan.className = "settings-model-desc";
405
+ descSpan.textContent = desc;
406
+ item.appendChild(descSpan);
407
+ }
408
+
409
+ item.addEventListener("click", function () {
410
+ if (ctx.ws && ctx.connected) {
411
+ ctx.ws.send(JSON.stringify({ type: "set_project_default_model", model: m.value }));
412
+ }
413
+ var items = modelList.querySelectorAll(".settings-model-item");
414
+ for (var j = 0; j < items.length; j++) items[j].classList.remove("active");
415
+ item.classList.add("active");
416
+ // Show/hide beta card based on Sonnet
417
+ updateBetaCard("ps", m.value);
418
+ });
419
+ modelList.appendChild(item);
420
+ })(models[i]);
421
+ }
422
+ }
423
+
424
+ // Beta 1M toggle
425
+ updateBetaCard("ps", model);
426
+ var beta1m = document.getElementById("ps-beta-1m");
427
+ if (beta1m) {
428
+ var hasBeta = false;
429
+ for (var bi = 0; bi < betas.length; bi++) {
430
+ if (betas[bi].indexOf("context-1m") !== -1) { hasBeta = true; break; }
431
+ }
432
+ beta1m.checked = hasBeta;
433
+ beta1m.onchange = function () {
434
+ toggleBeta1m(this.checked);
435
+ };
436
+ }
437
+
438
+ // Mode list
439
+ var modeList = document.getElementById("ps-mode-list");
440
+ if (modeList) {
441
+ modeList.innerHTML = "";
442
+ for (var k = 0; k < MODE_OPTIONS.length; k++) {
443
+ (function (opt) {
444
+ var item = document.createElement("div");
445
+ item.className = "settings-model-item" + (opt.value === mode ? " active" : "");
446
+
447
+ var nameSpan = document.createElement("span");
448
+ nameSpan.className = "settings-model-name";
449
+ nameSpan.textContent = opt.label;
450
+ item.appendChild(nameSpan);
451
+
452
+ var descSpan = document.createElement("span");
453
+ descSpan.className = "settings-model-desc";
454
+ descSpan.textContent = opt.desc;
455
+ item.appendChild(descSpan);
456
+
457
+ item.addEventListener("click", function () {
458
+ if (ctx.ws && ctx.connected) {
459
+ ctx.ws.send(JSON.stringify({ type: "set_project_default_mode", mode: opt.value }));
460
+ }
461
+ var items = modeList.querySelectorAll(".settings-model-item");
462
+ for (var j = 0; j < items.length; j++) items[j].classList.remove("active");
463
+ item.classList.add("active");
464
+ });
465
+ modeList.appendChild(item);
466
+ })(MODE_OPTIONS[k]);
467
+ }
468
+ }
469
+
470
+ // Effort bar
471
+ var effortBar = document.getElementById("ps-effort-bar");
472
+ if (effortBar) {
473
+ effortBar.innerHTML = "";
474
+ for (var e = 0; e < EFFORT_LEVELS.length; e++) {
475
+ (function (lvl) {
476
+ var btn = document.createElement("button");
477
+ btn.className = "settings-btn-option" + (lvl.value === effort ? " active" : "");
478
+ btn.textContent = lvl.value.charAt(0).toUpperCase() + lvl.value.slice(1);
479
+ btn.title = lvl.desc;
480
+ btn.addEventListener("click", function () {
481
+ if (ctx.ws && ctx.connected) {
482
+ ctx.ws.send(JSON.stringify({ type: "set_project_default_effort", effort: lvl.value }));
483
+ }
484
+ var btns = effortBar.querySelectorAll(".settings-btn-option");
485
+ for (var j = 0; j < btns.length; j++) btns[j].classList.remove("active");
486
+ btn.classList.add("active");
487
+ });
488
+ effortBar.appendChild(btn);
489
+ })(EFFORT_LEVELS[e]);
490
+ }
491
+ }
492
+ }
493
+
494
+ function updateBetaCard(prefix, model) {
495
+ var card = document.getElementById(prefix + "-beta-card");
496
+ if (card) {
497
+ card.style.display = isSonnetModel(model) ? "" : "none";
498
+ }
499
+ }
500
+
501
+ function toggleBeta1m(enable) {
502
+ var betas = ctx.currentBetas || [];
503
+ var newBetas;
504
+ if (enable) {
505
+ newBetas = betas.slice();
506
+ newBetas.push("context-1m-2025-08-07");
507
+ } else {
508
+ newBetas = [];
509
+ for (var i = 0; i < betas.length; i++) {
510
+ if (betas[i].indexOf("context-1m") === -1) {
511
+ newBetas.push(betas[i]);
512
+ }
513
+ }
514
+ }
515
+ if (ctx.ws && ctx.connected) {
516
+ ctx.ws.send(JSON.stringify({ type: "set_betas", betas: newBetas }));
517
+ }
518
+ }
519
+
520
+ // ===== Instructions (CLAUDE.md) =====
521
+ function loadInstructions() {
522
+ var editor = document.getElementById("ps-instructions-editor");
523
+ var status = document.getElementById("ps-instructions-status");
524
+ var saveStatus = document.getElementById("ps-instructions-save-status");
525
+ if (saveStatus) saveStatus.textContent = "";
526
+
527
+ if (status) status.textContent = "Loading...";
528
+
529
+ if (ctx.ws && ctx.connected) {
530
+ ctx.ws.send(JSON.stringify({ type: "fs_read", path: "CLAUDE.md" }));
531
+ }
532
+ }
533
+
534
+ export function handleInstructionsRead(msg) {
535
+ var editor = document.getElementById("ps-instructions-editor");
536
+ var status = document.getElementById("ps-instructions-status");
537
+ if (!editor) return;
538
+
539
+ if (msg.error) {
540
+ editor.value = "";
541
+ if (status) status.textContent = "No CLAUDE.md file found. Save to create one.";
542
+ } else {
543
+ editor.value = msg.content || "";
544
+ if (status) status.textContent = "";
545
+ }
546
+ }
547
+
548
+ function saveInstructions() {
549
+ var editor = document.getElementById("ps-instructions-editor");
550
+ var saveStatus = document.getElementById("ps-instructions-save-status");
551
+ if (!editor) return;
552
+
553
+ if (ctx.ws && ctx.connected) {
554
+ ctx.ws.send(JSON.stringify({ type: "fs_write", path: "CLAUDE.md", content: editor.value }));
555
+ if (saveStatus) saveStatus.textContent = "Saving...";
556
+ }
557
+ }
558
+
559
+ export function handleInstructionsWrite(msg) {
560
+ var saveStatus = document.getElementById("ps-instructions-save-status");
561
+ if (!saveStatus) return;
562
+ if (msg.ok) {
563
+ saveStatus.textContent = "Saved";
564
+ setTimeout(function () { saveStatus.textContent = ""; }, 2000);
565
+ } else {
566
+ saveStatus.textContent = "Error: " + (msg.error || "Failed to save");
567
+ }
568
+ }
569
+
570
+ // ===== Environment (key-value list) =====
571
+ var envSaveTimer = null;
572
+
573
+ function loadEnvironment() {
574
+ var saveStatus = document.getElementById("ps-env-save-status");
575
+ if (saveStatus) saveStatus.textContent = "";
576
+
577
+ if (ctx.ws && ctx.connected) {
578
+ ctx.ws.send(JSON.stringify({ type: "get_project_env", slug: currentSlug }));
579
+ }
580
+ }
581
+
582
+ export function handleProjectEnv(msg) {
583
+ var notice = document.getElementById("ps-env-override-notice");
584
+ if (notice) notice.classList.toggle("hidden", !msg.hasEnvrc);
585
+
586
+ // Parse envrc string into key-value pairs
587
+ var list = document.getElementById("ps-env-list");
588
+ if (!list) return;
589
+ list.innerHTML = "";
590
+
591
+ var pairs = parseEnvString(msg.envrc || "");
592
+ for (var i = 0; i < pairs.length; i++) {
593
+ addEnvRow(pairs[i].key, pairs[i].value, false);
594
+ }
595
+ refreshIcons();
596
+ }
597
+
598
+ // Check if text looks like env format: first line starts with a valid VAR_NAME=
599
+ export function looksLikeEnv(text) {
600
+ var first = text.split("\n")[0].trim();
601
+ if (first.indexOf("export ") === 0) first = first.substring(7);
602
+ return /^[A-Za-z_][A-Za-z0-9_]*=/.test(first);
603
+ }
604
+
605
+ export function parseEnvString(str) {
606
+ var pairs = [];
607
+ if (!str) return pairs;
608
+ var lines = str.split("\n");
609
+ for (var i = 0; i < lines.length; i++) {
610
+ var line = lines[i].trim();
611
+ if (!line || line.charAt(0) === "#") continue;
612
+ // Strip leading "export "
613
+ if (line.indexOf("export ") === 0) line = line.substring(7);
614
+ var eq = line.indexOf("=");
615
+ if (eq === -1) continue;
616
+ var key = line.substring(0, eq).trim();
617
+ var val = line.substring(eq + 1).trim();
618
+ // Strip surrounding quotes
619
+ if ((val.charAt(0) === '"' && val.charAt(val.length - 1) === '"') ||
620
+ (val.charAt(0) === "'" && val.charAt(val.length - 1) === "'")) {
621
+ val = val.substring(1, val.length - 1);
622
+ }
623
+ if (key) pairs.push({ key: key, value: val });
624
+ }
625
+ return pairs;
626
+ }
627
+
628
+ function buildEnvString() {
629
+ var list = document.getElementById("ps-env-list");
630
+ if (!list) return "";
631
+ var rows = list.querySelectorAll(".ps-env-row");
632
+ var lines = [];
633
+ for (var i = 0; i < rows.length; i++) {
634
+ var keyInput = rows[i].querySelector(".ps-env-key");
635
+ var valInput = rows[i].querySelector(".ps-env-val");
636
+ var key = keyInput ? keyInput.value.trim() : "";
637
+ var val = valInput ? valInput.value : "";
638
+ if (key) lines.push("export " + key + "=" + val);
639
+ }
640
+ return lines.join("\n");
641
+ }
642
+
643
+ function addEnvRow(key, value, focus) {
644
+ var list = document.getElementById("ps-env-list");
645
+ if (!list) return;
646
+
647
+ var row = document.createElement("div");
648
+ row.className = "ps-env-row";
649
+
650
+ var keyInput = document.createElement("input");
651
+ keyInput.type = "text";
652
+ keyInput.className = "ps-env-key";
653
+ keyInput.placeholder = "KEY";
654
+ keyInput.value = key;
655
+ keyInput.spellcheck = false;
656
+ keyInput.autocomplete = "off";
657
+
658
+ var valInput = document.createElement("input");
659
+ valInput.type = "text";
660
+ valInput.className = "ps-env-val";
661
+ valInput.placeholder = "value";
662
+ valInput.value = value;
663
+ valInput.spellcheck = false;
664
+ valInput.autocomplete = "off";
665
+
666
+ var delBtn = document.createElement("button");
667
+ delBtn.className = "ps-env-del";
668
+ delBtn.title = "Remove";
669
+ delBtn.innerHTML = '<i data-lucide="x"></i>';
670
+
671
+ delBtn.addEventListener("click", function () {
672
+ row.remove();
673
+ autoSaveEnv();
674
+ });
675
+
676
+ // Auto-save on change
677
+ keyInput.addEventListener("input", function () { autoSaveEnv(); });
678
+ valInput.addEventListener("input", function () { autoSaveEnv(); });
679
+
680
+ // Paste detection: if pasting KEY=VALUE content into key field, parse it
681
+ keyInput.addEventListener("paste", function (e) {
682
+ var text = (e.clipboardData || window.clipboardData).getData("text");
683
+ if (text && looksLikeEnv(text)) {
684
+ e.preventDefault();
685
+ var pairs = parseEnvString(text);
686
+ if (pairs.length > 0) {
687
+ // Fill current row with first pair
688
+ keyInput.value = pairs[0].key;
689
+ valInput.value = pairs[0].value;
690
+ // Add remaining as new rows
691
+ for (var p = 1; p < pairs.length; p++) {
692
+ addEnvRow(pairs[p].key, pairs[p].value, false);
693
+ }
694
+ autoSaveEnv();
695
+ }
696
+ }
697
+ });
698
+
699
+ // Also handle paste into value field
700
+ valInput.addEventListener("paste", function (e) {
701
+ var text = (e.clipboardData || window.clipboardData).getData("text");
702
+ if (text && text.indexOf("\n") !== -1 && text.indexOf("=") !== -1) {
703
+ e.preventDefault();
704
+ var pairs = parseEnvString(text);
705
+ if (pairs.length > 0) {
706
+ keyInput.value = pairs[0].key;
707
+ valInput.value = pairs[0].value;
708
+ for (var p = 1; p < pairs.length; p++) {
709
+ addEnvRow(pairs[p].key, pairs[p].value, false);
710
+ }
711
+ autoSaveEnv();
712
+ }
713
+ }
714
+ });
715
+
716
+ row.appendChild(keyInput);
717
+ row.appendChild(valInput);
718
+ row.appendChild(delBtn);
719
+ list.appendChild(row);
720
+ refreshIcons();
721
+
722
+ if (focus) keyInput.focus();
723
+ }
724
+
725
+ function autoSaveEnv() {
726
+ if (envSaveTimer) clearTimeout(envSaveTimer);
727
+ envSaveTimer = setTimeout(function () {
728
+ var envrc = buildEnvString();
729
+ if (ctx.ws && ctx.connected) {
730
+ ctx.ws.send(JSON.stringify({ type: "set_project_env", slug: currentSlug, envrc: envrc }));
731
+ var saveStatus = document.getElementById("ps-env-save-status");
732
+ if (saveStatus) {
733
+ saveStatus.textContent = "Saved";
734
+ setTimeout(function () { saveStatus.textContent = ""; }, 2000);
735
+ }
736
+ }
737
+ }, 800);
738
+ }
739
+
740
+ export function handleProjectEnvSaved(msg) {
741
+ var saveStatus = document.getElementById("ps-env-save-status");
742
+ if (!saveStatus) return;
743
+ if (msg.ok) {
744
+ saveStatus.textContent = "Saved";
745
+ setTimeout(function () { saveStatus.textContent = ""; }, 2000);
746
+ } else {
747
+ saveStatus.textContent = "Error: " + (msg.error || "Failed to save");
748
+ }
749
+ }
750
+
751
+ // ===== Shared Environment (via tabs) =====
752
+ var sharedEnvSaveTimer = null;
753
+
754
+ function loadSharedEnv() {
755
+ var saveStatus = document.getElementById("ps-shared-env-save-status");
756
+ if (saveStatus) saveStatus.textContent = "";
757
+
758
+ if (ctx.ws && ctx.connected) {
759
+ ctx.ws.send(JSON.stringify({ type: "get_shared_env" }));
760
+ }
761
+ }
762
+
763
+ export function handleProjectSharedEnv(msg) {
764
+ var list = document.getElementById("ps-shared-env-list");
765
+ if (!list) return;
766
+ list.innerHTML = "";
767
+
768
+ var pairs = parseEnvString(msg.envrc || "");
769
+ for (var i = 0; i < pairs.length; i++) {
770
+ addSharedEnvRow(pairs[i].key, pairs[i].value, false);
771
+ }
772
+ refreshIcons();
773
+ }
774
+
775
+ export function handleProjectSharedEnvSaved(msg) {
776
+ var saveStatus = document.getElementById("ps-shared-env-save-status");
777
+ if (!saveStatus) return;
778
+ if (msg.ok) {
779
+ saveStatus.textContent = "Saved";
780
+ setTimeout(function () { saveStatus.textContent = ""; }, 2000);
781
+ } else {
782
+ saveStatus.textContent = "Error: " + (msg.error || "Failed to save");
783
+ }
784
+ }
785
+
786
+ function buildSharedEnvString() {
787
+ var list = document.getElementById("ps-shared-env-list");
788
+ if (!list) return "";
789
+ var rows = list.querySelectorAll(".ps-env-row");
790
+ var lines = [];
791
+ for (var i = 0; i < rows.length; i++) {
792
+ var keyInput = rows[i].querySelector(".ps-env-key");
793
+ var valInput = rows[i].querySelector(".ps-env-val");
794
+ var key = keyInput ? keyInput.value.trim() : "";
795
+ var val = valInput ? valInput.value : "";
796
+ if (key) lines.push("export " + key + "=" + val);
797
+ }
798
+ return lines.join("\n");
799
+ }
800
+
801
+ function addSharedEnvRow(key, value, focus) {
802
+ var list = document.getElementById("ps-shared-env-list");
803
+ if (!list) return;
804
+
805
+ var row = document.createElement("div");
806
+ row.className = "ps-env-row";
807
+
808
+ var keyInput = document.createElement("input");
809
+ keyInput.type = "text";
810
+ keyInput.className = "ps-env-key";
811
+ keyInput.placeholder = "KEY";
812
+ keyInput.value = key;
813
+ keyInput.spellcheck = false;
814
+ keyInput.autocomplete = "off";
815
+
816
+ var valInput = document.createElement("input");
817
+ valInput.type = "text";
818
+ valInput.className = "ps-env-val";
819
+ valInput.placeholder = "value";
820
+ valInput.value = value;
821
+ valInput.spellcheck = false;
822
+ valInput.autocomplete = "off";
823
+
824
+ var delBtn = document.createElement("button");
825
+ delBtn.className = "ps-env-del";
826
+ delBtn.title = "Remove";
827
+ delBtn.innerHTML = '<i data-lucide="x"></i>';
828
+
829
+ delBtn.addEventListener("click", function () {
830
+ row.remove();
831
+ autoSaveSharedEnv();
832
+ });
833
+
834
+ keyInput.addEventListener("input", function () { autoSaveSharedEnv(); });
835
+ valInput.addEventListener("input", function () { autoSaveSharedEnv(); });
836
+
837
+ // Paste detection
838
+ keyInput.addEventListener("paste", function (e) {
839
+ var text = (e.clipboardData || window.clipboardData).getData("text");
840
+ if (text && looksLikeEnv(text)) {
841
+ e.preventDefault();
842
+ var pairs = parseEnvString(text);
843
+ if (pairs.length > 0) {
844
+ keyInput.value = pairs[0].key;
845
+ valInput.value = pairs[0].value;
846
+ for (var p = 1; p < pairs.length; p++) {
847
+ addSharedEnvRow(pairs[p].key, pairs[p].value, false);
848
+ }
849
+ autoSaveSharedEnv();
850
+ }
851
+ }
852
+ });
853
+
854
+ valInput.addEventListener("paste", function (e) {
855
+ var text = (e.clipboardData || window.clipboardData).getData("text");
856
+ if (text && text.indexOf("\n") !== -1 && text.indexOf("=") !== -1) {
857
+ e.preventDefault();
858
+ var pairs = parseEnvString(text);
859
+ if (pairs.length > 0) {
860
+ keyInput.value = pairs[0].key;
861
+ valInput.value = pairs[0].value;
862
+ for (var p = 1; p < pairs.length; p++) {
863
+ addSharedEnvRow(pairs[p].key, pairs[p].value, false);
864
+ }
865
+ autoSaveSharedEnv();
866
+ }
867
+ }
868
+ });
869
+
870
+ row.appendChild(keyInput);
871
+ row.appendChild(valInput);
872
+ row.appendChild(delBtn);
873
+ list.appendChild(row);
874
+ refreshIcons();
875
+
876
+ if (focus) keyInput.focus();
877
+ }
878
+
879
+ function autoSaveSharedEnv() {
880
+ if (sharedEnvSaveTimer) clearTimeout(sharedEnvSaveTimer);
881
+ sharedEnvSaveTimer = setTimeout(function () {
882
+ var envrc = buildSharedEnvString();
883
+ if (ctx.ws && ctx.connected) {
884
+ ctx.ws.send(JSON.stringify({ type: "set_shared_env", envrc: envrc }));
885
+ var saveStatus = document.getElementById("ps-shared-env-save-status");
886
+ if (saveStatus) {
887
+ saveStatus.textContent = "Saved";
888
+ setTimeout(function () { saveStatus.textContent = ""; }, 2000);
889
+ }
890
+ }
891
+ }, 800);
892
+ }
893
+
894
+ // ===== Update from external events =====
895
+ export function updateProjectSettingsIcon(icon) {
896
+ if (currentProject) currentProject.icon = icon;
897
+ updateIconPreview(icon);
898
+ }
899
+
900
+ export function updateProjectSettingsName(name) {
901
+ if (currentProject) currentProject.name = name;
902
+ var nameEl = document.getElementById("ps-project-name");
903
+ if (nameEl) nameEl.textContent = name || "-";
904
+ var navTitle = document.getElementById("ps-nav-title");
905
+ if (navTitle) navTitle.textContent = name || "-";
906
+ }