clay-server 2.5.0 → 2.6.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.
@@ -1,7 +1,7 @@
1
1
  // server-settings.js — Full-screen server settings overlay
2
2
  import { refreshIcons } from './icons.js';
3
- import { getCurrentTheme, openSettingsThemePicker } from './theme.js';
4
- import { showToast } from './utils.js';
3
+ import { showToast, copyToClipboard } from './utils.js';
4
+ import { parseEnvString, looksLikeEnv } from './project-settings.js';
5
5
 
6
6
  var ctx = null;
7
7
  var settingsEl = null;
@@ -11,6 +11,26 @@ var navItems = null;
11
11
  var sections = null;
12
12
  var statsTimer = null;
13
13
 
14
+ var SS_MODE_OPTIONS = [
15
+ { value: "default", label: "Default", desc: "Claude asks for permission before running tools and editing files." },
16
+ { value: "plan", label: "Plan", desc: "Claude creates a plan first and asks for approval before making changes." },
17
+ { value: "acceptEdits", label: "Auto-accept edits", desc: "File edits are applied automatically. Claude still asks before running commands." },
18
+ ];
19
+
20
+ var SS_EFFORT_LEVELS = [
21
+ { value: "low", desc: "Quick, concise responses. Best for simple questions." },
22
+ { value: "medium", desc: "Balanced responses with moderate reasoning. Good for most tasks." },
23
+ { value: "high", desc: "Thorough responses with deeper analysis. Good for complex tasks." },
24
+ { value: "max", desc: "Maximum reasoning depth. Best for the most difficult problems." },
25
+ ];
26
+
27
+ var SS_MODEL_DESCRIPTIONS = {
28
+ "default": "Automatically selects the best model for the task.",
29
+ "sonnet": "Fast and capable. Great balance of speed and intelligence.",
30
+ "haiku": "Fastest model. Best for quick tasks and simple questions.",
31
+ "opus": "Most powerful model. Best for complex reasoning and analysis.",
32
+ };
33
+
14
34
  export function initServerSettings(appCtx) {
15
35
  ctx = appCtx;
16
36
  settingsEl = document.getElementById("server-settings");
@@ -47,18 +67,22 @@ export function initServerSettings(appCtx) {
47
67
  });
48
68
  }
49
69
 
50
- // Context view buttons
51
- var contextViewEl = document.getElementById("settings-context-view");
52
- if (contextViewEl) {
53
- var btns = contextViewEl.querySelectorAll(".settings-btn-option");
54
- for (var b = 0; b < btns.length; b++) {
55
- btns[b].addEventListener("click", function () {
56
- var view = this.dataset.view;
57
- if (ctx.setContextView) ctx.setContextView(view);
58
- if (ctx.applyContextView) ctx.applyContextView(view);
59
- updateContextViewButtons();
70
+ // Copyable command blocks
71
+ var copyables = settingsEl.querySelectorAll(".settings-copyable");
72
+ for (var c = 0; c < copyables.length; c++) {
73
+ copyables[c].addEventListener("click", function () {
74
+ var text = this.dataset.copy;
75
+ if (!text) return;
76
+ var btn = this.querySelector(".settings-copy-btn");
77
+ copyToClipboard(text).then(function () {
78
+ if (btn) {
79
+ var orig = btn.textContent;
80
+ btn.textContent = "✓";
81
+ setTimeout(function () { btn.textContent = orig; }, 1500);
82
+ }
83
+ showToast("Copied to clipboard");
60
84
  });
61
- }
85
+ });
62
86
  }
63
87
 
64
88
  // Notification toggles
@@ -104,7 +128,7 @@ export function initServerSettings(appCtx) {
104
128
  if (!model) return;
105
129
  var ws = ctx.ws;
106
130
  if (ws && ws.readyState === 1) {
107
- ws.send(JSON.stringify({ type: "set_model", model: model }));
131
+ ws.send(JSON.stringify({ type: "set_server_default_model", model: model }));
108
132
  }
109
133
  });
110
134
 
@@ -140,6 +164,21 @@ export function initServerSettings(appCtx) {
140
164
  });
141
165
  }
142
166
 
167
+ // Global CLAUDE.md: save button
168
+ var ssClaudeMdSave = document.getElementById("ss-claudemd-save");
169
+ if (ssClaudeMdSave) {
170
+ ssClaudeMdSave.addEventListener("click", function () { saveGlobalClaudeMd(); });
171
+ }
172
+
173
+ // Shared environment: add button
174
+ var ssEnvAddBtn = document.getElementById("ss-env-add-btn");
175
+ if (ssEnvAddBtn) {
176
+ ssEnvAddBtn.addEventListener("click", function () {
177
+ addSharedEnvRow("", "", true);
178
+ autoSaveSharedEnv();
179
+ });
180
+ }
181
+
143
182
  // Shutdown server
144
183
  var shutdownInput = document.getElementById("settings-shutdown-input");
145
184
  var shutdownBtn = document.getElementById("settings-shutdown-btn");
@@ -184,6 +223,10 @@ function switchSection(sectionName) {
184
223
  var isActive2 = sections[j].dataset.section === sectionName;
185
224
  sections[j].classList.toggle("active", isActive2);
186
225
  }
226
+
227
+ // Lazy-load section data
228
+ if (sectionName === "claudemd") loadGlobalClaudeMd();
229
+ if (sectionName === "environment") loadSharedEnv();
187
230
  }
188
231
 
189
232
  function openSettings() {
@@ -262,14 +305,11 @@ function populateSettings() {
262
305
  // Sync notification toggles
263
306
  syncNotifToggles();
264
307
 
265
- // Theme
266
- updateThemeDisplay();
267
-
268
- // Context view
269
- updateContextViewButtons();
270
-
271
- // Models
308
+ // Session defaults
272
309
  updateModelList();
310
+ updateModeList();
311
+ updateEffortBar();
312
+ updateSsBetaCard();
273
313
  }
274
314
 
275
315
  function syncNotifToggles() {
@@ -285,39 +325,26 @@ function syncNotifToggles() {
285
325
  }
286
326
  }
287
327
 
288
- function updateThemeDisplay() {
289
- var container = document.getElementById("settings-theme-picker-container");
290
- if (container) {
291
- openSettingsThemePicker(container);
328
+ function ssGetModelDesc(model) {
329
+ if (!model) return "";
330
+ var lower = model.toLowerCase();
331
+ for (var key in SS_MODEL_DESCRIPTIONS) {
332
+ if (lower.indexOf(key) !== -1) return SS_MODEL_DESCRIPTIONS[key];
292
333
  }
334
+ return "";
293
335
  }
294
336
 
295
- function updateContextViewButtons() {
296
- var view = "off";
297
- try { view = localStorage.getItem("clay-context-view") || "off"; } catch (e) {}
298
- var btns = document.querySelectorAll("#settings-context-view .settings-btn-option");
299
- for (var i = 0; i < btns.length; i++) {
300
- btns[i].classList.toggle("active", btns[i].dataset.view === view);
301
- }
337
+ function ssIsSonnetModel(model) {
338
+ if (!model) return false;
339
+ return model.toLowerCase().indexOf("sonnet") !== -1;
302
340
  }
303
341
 
304
342
  function updateModelList() {
305
343
  var listEl = document.getElementById("settings-model-list");
306
- var currentEl = document.getElementById("settings-current-model");
307
344
  if (!listEl) return;
308
345
 
309
346
  var models = ctx.currentModels || [];
310
- var currentModel = ctx._currentModelValue || "";
311
-
312
- // Look up display name for settings panel
313
- var displayName = currentModel;
314
- for (var j = 0; j < models.length; j++) {
315
- if (models[j].value === currentModel && models[j].displayName) {
316
- displayName = models[j].displayName;
317
- break;
318
- }
319
- }
320
- if (currentEl) currentEl.textContent = displayName || "-";
347
+ var currentModel = ctx.currentModel || ctx._currentModelValue || "";
321
348
 
322
349
  listEl.innerHTML = "";
323
350
  if (models.length === 0) {
@@ -326,15 +353,145 @@ function updateModelList() {
326
353
  }
327
354
 
328
355
  for (var i = 0; i < models.length; i++) {
329
- var m = models[i];
330
- var value = m.value || "";
331
- var label = m.displayName || value;
332
- var item = document.createElement("div");
333
- item.className = "settings-model-item";
334
- if (label === currentModel || value === currentModel) item.classList.add("active");
335
- item.dataset.model = value;
336
- item.textContent = label;
337
- listEl.appendChild(item);
356
+ (function (m) {
357
+ var value = m.value || "";
358
+ var label = m.displayName || value;
359
+ var item = document.createElement("div");
360
+ item.className = "settings-model-item";
361
+ if (value === currentModel) item.classList.add("active");
362
+ item.dataset.model = value;
363
+
364
+ var nameSpan = document.createElement("span");
365
+ nameSpan.className = "settings-model-name";
366
+ nameSpan.textContent = label;
367
+ item.appendChild(nameSpan);
368
+
369
+ var desc = ssGetModelDesc(value);
370
+ if (desc) {
371
+ var descSpan = document.createElement("span");
372
+ descSpan.className = "settings-model-desc";
373
+ descSpan.textContent = desc;
374
+ item.appendChild(descSpan);
375
+ }
376
+
377
+ item.addEventListener("click", function () {
378
+ var ws = ctx.ws;
379
+ if (ws && ws.readyState === 1) {
380
+ ws.send(JSON.stringify({ type: "set_model", model: value }));
381
+ }
382
+ var items = listEl.querySelectorAll(".settings-model-item");
383
+ for (var j = 0; j < items.length; j++) items[j].classList.remove("active");
384
+ item.classList.add("active");
385
+ updateSsBetaCard(value);
386
+ });
387
+
388
+ listEl.appendChild(item);
389
+ })(models[i]);
390
+ }
391
+ }
392
+
393
+ function updateModeList() {
394
+ var listEl = document.getElementById("ss-mode-list");
395
+ if (!listEl) return;
396
+
397
+ var currentMode = ctx.currentMode || "default";
398
+ listEl.innerHTML = "";
399
+
400
+ for (var i = 0; i < SS_MODE_OPTIONS.length; i++) {
401
+ (function (opt) {
402
+ var item = document.createElement("div");
403
+ item.className = "settings-model-item" + (opt.value === currentMode ? " active" : "");
404
+
405
+ var nameSpan = document.createElement("span");
406
+ nameSpan.className = "settings-model-name";
407
+ nameSpan.textContent = opt.label;
408
+ item.appendChild(nameSpan);
409
+
410
+ var descSpan = document.createElement("span");
411
+ descSpan.className = "settings-model-desc";
412
+ descSpan.textContent = opt.desc;
413
+ item.appendChild(descSpan);
414
+
415
+ item.addEventListener("click", function () {
416
+ var ws = ctx.ws;
417
+ if (ws && ws.readyState === 1) {
418
+ ws.send(JSON.stringify({ type: "set_server_default_mode", mode: opt.value }));
419
+ }
420
+ var items = listEl.querySelectorAll(".settings-model-item");
421
+ for (var j = 0; j < items.length; j++) items[j].classList.remove("active");
422
+ item.classList.add("active");
423
+ });
424
+
425
+ listEl.appendChild(item);
426
+ })(SS_MODE_OPTIONS[i]);
427
+ }
428
+ }
429
+
430
+ function updateEffortBar() {
431
+ var bar = document.getElementById("ss-effort-bar");
432
+ if (!bar) return;
433
+
434
+ var currentEffort = ctx.currentEffort || "medium";
435
+ bar.innerHTML = "";
436
+
437
+ for (var i = 0; i < SS_EFFORT_LEVELS.length; i++) {
438
+ (function (lvl) {
439
+ var btn = document.createElement("button");
440
+ btn.className = "settings-btn-option" + (lvl.value === currentEffort ? " active" : "");
441
+ btn.textContent = lvl.value.charAt(0).toUpperCase() + lvl.value.slice(1);
442
+ btn.title = lvl.desc;
443
+ btn.addEventListener("click", function () {
444
+ var ws = ctx.ws;
445
+ if (ws && ws.readyState === 1) {
446
+ ws.send(JSON.stringify({ type: "set_server_default_effort", effort: lvl.value }));
447
+ }
448
+ var btns = bar.querySelectorAll(".settings-btn-option");
449
+ for (var j = 0; j < btns.length; j++) btns[j].classList.remove("active");
450
+ btn.classList.add("active");
451
+ });
452
+ bar.appendChild(btn);
453
+ })(SS_EFFORT_LEVELS[i]);
454
+ }
455
+ }
456
+
457
+ function updateSsBetaCard(overrideModel) {
458
+ var model = overrideModel || ctx.currentModel || ctx._currentModelValue || "";
459
+ var card = document.getElementById("ss-beta-card");
460
+ if (card) {
461
+ card.style.display = ssIsSonnetModel(model) ? "" : "none";
462
+ }
463
+
464
+ var toggle = document.getElementById("ss-beta-1m");
465
+ if (toggle) {
466
+ var betas = ctx.currentBetas || [];
467
+ var hasBeta = false;
468
+ for (var i = 0; i < betas.length; i++) {
469
+ if (betas[i].indexOf("context-1m") !== -1) { hasBeta = true; break; }
470
+ }
471
+ toggle.checked = hasBeta;
472
+ toggle.onchange = function () {
473
+ ssToggleBeta1m(this.checked);
474
+ };
475
+ }
476
+ }
477
+
478
+ function ssToggleBeta1m(enable) {
479
+ var betas = ctx.currentBetas || [];
480
+ var newBetas;
481
+ if (enable) {
482
+ newBetas = betas.slice();
483
+ newBetas.push("context-1m-2025-08-07");
484
+ } else {
485
+ newBetas = [];
486
+ for (var i = 0; i < betas.length; i++) {
487
+ if (betas[i].indexOf("context-1m") === -1) {
488
+ newBetas.push(betas[i]);
489
+ }
490
+ }
491
+ }
492
+ var ws = ctx.ws;
493
+ if (ws && ws.readyState === 1) {
494
+ ws.send(JSON.stringify({ type: "set_betas", betas: newBetas }));
338
495
  }
339
496
  }
340
497
 
@@ -359,6 +516,9 @@ export function updateSettingsModels(current, models) {
359
516
  ctx._currentModelValue = current;
360
517
  if (isSettingsOpen()) {
361
518
  updateModelList();
519
+ updateModeList();
520
+ updateEffortBar();
521
+ updateSsBetaCard();
362
522
  }
363
523
  }
364
524
 
@@ -492,6 +652,202 @@ function updatePinStatus(enabled) {
492
652
  if (actionLabel) actionLabel.textContent = enabled ? "Change PIN" : "Set PIN";
493
653
  }
494
654
 
655
+ // ===== Global CLAUDE.md =====
656
+ function loadGlobalClaudeMd() {
657
+ var editor = document.getElementById("ss-claudemd-editor");
658
+ var status = document.getElementById("ss-claudemd-status");
659
+ var saveStatus = document.getElementById("ss-claudemd-save-status");
660
+ if (saveStatus) saveStatus.textContent = "";
661
+ if (status) status.textContent = "Loading...";
662
+
663
+ var ws = ctx.ws;
664
+ if (ws && ws.readyState === 1) {
665
+ ws.send(JSON.stringify({ type: "read_global_claude_md" }));
666
+ }
667
+ }
668
+
669
+ export function handleGlobalClaudeMdRead(msg) {
670
+ var editor = document.getElementById("ss-claudemd-editor");
671
+ var status = document.getElementById("ss-claudemd-status");
672
+ if (!editor) return;
673
+
674
+ if (msg.error) {
675
+ editor.value = "";
676
+ if (status) status.textContent = "No global CLAUDE.md found. Save to create one.";
677
+ } else {
678
+ editor.value = msg.content || "";
679
+ if (status) status.textContent = "";
680
+ }
681
+ }
682
+
683
+ function saveGlobalClaudeMd() {
684
+ var editor = document.getElementById("ss-claudemd-editor");
685
+ var saveStatus = document.getElementById("ss-claudemd-save-status");
686
+ if (!editor) return;
687
+
688
+ var ws = ctx.ws;
689
+ if (ws && ws.readyState === 1) {
690
+ ws.send(JSON.stringify({ type: "write_global_claude_md", content: editor.value }));
691
+ if (saveStatus) saveStatus.textContent = "Saving...";
692
+ }
693
+ }
694
+
695
+ export function handleGlobalClaudeMdWrite(msg) {
696
+ var saveStatus = document.getElementById("ss-claudemd-save-status");
697
+ if (!saveStatus) return;
698
+ if (msg.ok) {
699
+ saveStatus.textContent = "Saved";
700
+ setTimeout(function () { saveStatus.textContent = ""; }, 2000);
701
+ } else {
702
+ saveStatus.textContent = "Error: " + (msg.error || "Failed to save");
703
+ }
704
+ }
705
+
706
+ // ===== Shared Environment Variables =====
707
+ var sharedEnvSaveTimer = null;
708
+
709
+ function loadSharedEnv() {
710
+ var saveStatus = document.getElementById("ss-env-save-status");
711
+ if (saveStatus) saveStatus.textContent = "";
712
+
713
+ var ws = ctx.ws;
714
+ if (ws && ws.readyState === 1) {
715
+ ws.send(JSON.stringify({ type: "get_shared_env" }));
716
+ }
717
+ }
718
+
719
+ export function handleSharedEnv(msg) {
720
+ var list = document.getElementById("ss-env-list");
721
+ if (!list) return;
722
+ list.innerHTML = "";
723
+
724
+ var pairs = parseEnvString(msg.envrc || "");
725
+ for (var i = 0; i < pairs.length; i++) {
726
+ addSharedEnvRow(pairs[i].key, pairs[i].value, false);
727
+ }
728
+ refreshIcons();
729
+ }
730
+
731
+ export function handleSharedEnvSaved(msg) {
732
+ var saveStatus = document.getElementById("ss-env-save-status");
733
+ if (!saveStatus) return;
734
+ if (msg.ok) {
735
+ saveStatus.textContent = "Saved";
736
+ setTimeout(function () { saveStatus.textContent = ""; }, 2000);
737
+ } else {
738
+ saveStatus.textContent = "Error: " + (msg.error || "Failed to save");
739
+ }
740
+ }
741
+
742
+ function buildSharedEnvString() {
743
+ var list = document.getElementById("ss-env-list");
744
+ if (!list) return "";
745
+ var rows = list.querySelectorAll(".ps-env-row");
746
+ var lines = [];
747
+ for (var i = 0; i < rows.length; i++) {
748
+ var keyInput = rows[i].querySelector(".ps-env-key");
749
+ var valInput = rows[i].querySelector(".ps-env-val");
750
+ var key = keyInput ? keyInput.value.trim() : "";
751
+ var val = valInput ? valInput.value : "";
752
+ if (key) lines.push("export " + key + "=" + val);
753
+ }
754
+ return lines.join("\n");
755
+ }
756
+
757
+ function addSharedEnvRow(key, value, focus) {
758
+ var list = document.getElementById("ss-env-list");
759
+ if (!list) return;
760
+
761
+ var row = document.createElement("div");
762
+ row.className = "ps-env-row";
763
+
764
+ var keyInput = document.createElement("input");
765
+ keyInput.type = "text";
766
+ keyInput.className = "ps-env-key";
767
+ keyInput.placeholder = "KEY";
768
+ keyInput.value = key;
769
+ keyInput.spellcheck = false;
770
+ keyInput.autocomplete = "off";
771
+
772
+ var valInput = document.createElement("input");
773
+ valInput.type = "text";
774
+ valInput.className = "ps-env-val";
775
+ valInput.placeholder = "value";
776
+ valInput.value = value;
777
+ valInput.spellcheck = false;
778
+ valInput.autocomplete = "off";
779
+
780
+ var delBtn = document.createElement("button");
781
+ delBtn.className = "ps-env-del";
782
+ delBtn.title = "Remove";
783
+ delBtn.innerHTML = '<i data-lucide="x"></i>';
784
+
785
+ delBtn.addEventListener("click", function () {
786
+ row.remove();
787
+ autoSaveSharedEnv();
788
+ });
789
+
790
+ keyInput.addEventListener("input", function () { autoSaveSharedEnv(); });
791
+ valInput.addEventListener("input", function () { autoSaveSharedEnv(); });
792
+
793
+ // Paste detection
794
+ keyInput.addEventListener("paste", function (e) {
795
+ var text = (e.clipboardData || window.clipboardData).getData("text");
796
+ if (text && looksLikeEnv(text)) {
797
+ e.preventDefault();
798
+ var pairs = parseEnvString(text);
799
+ if (pairs.length > 0) {
800
+ keyInput.value = pairs[0].key;
801
+ valInput.value = pairs[0].value;
802
+ for (var p = 1; p < pairs.length; p++) {
803
+ addSharedEnvRow(pairs[p].key, pairs[p].value, false);
804
+ }
805
+ autoSaveSharedEnv();
806
+ }
807
+ }
808
+ });
809
+
810
+ valInput.addEventListener("paste", function (e) {
811
+ var text = (e.clipboardData || window.clipboardData).getData("text");
812
+ if (text && text.indexOf("\n") !== -1 && text.indexOf("=") !== -1) {
813
+ e.preventDefault();
814
+ var pairs = parseEnvString(text);
815
+ if (pairs.length > 0) {
816
+ keyInput.value = pairs[0].key;
817
+ valInput.value = pairs[0].value;
818
+ for (var p = 1; p < pairs.length; p++) {
819
+ addSharedEnvRow(pairs[p].key, pairs[p].value, false);
820
+ }
821
+ autoSaveSharedEnv();
822
+ }
823
+ }
824
+ });
825
+
826
+ row.appendChild(keyInput);
827
+ row.appendChild(valInput);
828
+ row.appendChild(delBtn);
829
+ list.appendChild(row);
830
+ refreshIcons();
831
+
832
+ if (focus) keyInput.focus();
833
+ }
834
+
835
+ function autoSaveSharedEnv() {
836
+ if (sharedEnvSaveTimer) clearTimeout(sharedEnvSaveTimer);
837
+ sharedEnvSaveTimer = setTimeout(function () {
838
+ var envrc = buildSharedEnvString();
839
+ var ws = ctx.ws;
840
+ if (ws && ws.readyState === 1) {
841
+ ws.send(JSON.stringify({ type: "set_shared_env", envrc: envrc }));
842
+ var saveStatus = document.getElementById("ss-env-save-status");
843
+ if (saveStatus) {
844
+ saveStatus.textContent = "Saved";
845
+ setTimeout(function () { saveStatus.textContent = ""; }, 2000);
846
+ }
847
+ }
848
+ }, 800);
849
+ }
850
+
495
851
  function formatBytes(n) {
496
852
  if (n >= 1073741824) return (n / 1073741824).toFixed(1) + " GB";
497
853
  if (n >= 1048576) return (n / 1048576).toFixed(1) + " MB";