clay-server 2.10.0 → 2.11.0-beta.10

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.
package/lib/public/app.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { showToast, copyToClipboard, escapeHtml } from './modules/utils.js';
2
2
  import { refreshIcons, iconHtml, randomThinkingVerb } from './modules/icons.js';
3
3
  import { renderMarkdown, highlightCodeBlocks, renderMermaidBlocks, closeMermaidModal, parseEmojis } from './modules/markdown.js';
4
- import { initSidebar, renderSessionList, handleSearchResults, updateSessionPresence, updatePageTitle, getActiveSearchQuery, buildSearchTimeline, removeSearchTimeline, populateCliSessionList, renderIconStrip, renderSidebarPresence, initIconStrip, getEmojiCategories } from './modules/sidebar.js';
4
+ import { initSidebar, renderSessionList, handleSearchResults, updateSessionPresence, updatePageTitle, getActiveSearchQuery, buildSearchTimeline, removeSearchTimeline, populateCliSessionList, renderIconStrip, renderSidebarPresence, initIconStrip, getEmojiCategories, renderUserStrip, setCurrentDmUser, updateDmBadge } from './modules/sidebar.js';
5
5
  import { initRewind, setRewindMode, showRewindModal, clearPendingRewindUuid, addRewindButton } from './modules/rewind.js';
6
6
  import { initNotifications, showDoneNotification, playDoneSound, isNotifAlertEnabled, isNotifSoundEnabled } from './modules/notifications.js';
7
7
  import { initInput, clearPendingImages, handleInputSync, autoResize, builtinCommands, sendMessage } from './modules/input.js';
@@ -10,9 +10,9 @@ import { initFileBrowser, loadRootDirectory, refreshTree, handleFsList, handleFs
10
10
  import { initTerminal, openTerminal, closeTerminal, resetTerminals, handleTermList, handleTermCreated, handleTermOutput, handleTermExited, handleTermClosed, sendTerminalCommand } from './modules/terminal.js';
11
11
  import { initStickyNotes, handleNotesList, handleNoteCreated, handleNoteUpdated, handleNoteDeleted, openArchive, closeArchive, isArchiveOpen } from './modules/sticky-notes.js';
12
12
  import { initTheme, getThemeColor, getComputedVar, onThemeChange, getCurrentTheme } from './modules/theme.js';
13
- import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUserQuestion, markAskUserAnswered, renderPermissionRequest, markPermissionResolved, markPermissionCancelled, renderPlanBanner, renderPlanCard, handleTodoWrite, handleTaskCreate, handleTaskUpdate, startThinking, appendThinking, stopThinking, resetThinkingGroup, createToolItem, updateToolExecuting, updateToolResult, markAllToolsDone, addTurnMeta, enableMainInput, getTools, getPlanContent, setPlanContent, isPlanFilePath, getTodoTools, updateSubagentActivity, addSubagentToolEntry, markSubagentDone, updateSubagentProgress, initSubagentStop, closeToolGroup, removeToolFromGroup } from './modules/tools.js';
13
+ import { initTools, resetToolState, saveToolState, restoreToolState, renderAskUserQuestion, markAskUserAnswered, renderPermissionRequest, markPermissionResolved, markPermissionCancelled, renderElicitationRequest, markElicitationResolved, renderPlanBanner, renderPlanCard, handleTodoWrite, handleTaskCreate, handleTaskUpdate, startThinking, appendThinking, stopThinking, resetThinkingGroup, createToolItem, updateToolExecuting, updateToolResult, markAllToolsDone, addTurnMeta, enableMainInput, getTools, getPlanContent, setPlanContent, isPlanFilePath, getTodoTools, updateSubagentActivity, addSubagentToolEntry, markSubagentDone, updateSubagentProgress, initSubagentStop, closeToolGroup, removeToolFromGroup } from './modules/tools.js';
14
14
  import { initServerSettings, updateSettingsStats, updateSettingsModels, updateDaemonConfig, handleSetPinResult, handleKeepAwakeChanged, handleRestartResult, handleShutdownResult, handleSharedEnv, handleSharedEnvSaved, handleGlobalClaudeMdRead, handleGlobalClaudeMdWrite } from './modules/server-settings.js';
15
- import { initProjectSettings, handleInstructionsRead, handleInstructionsWrite, handleProjectEnv, handleProjectEnvSaved, isProjectSettingsOpen, handleProjectSharedEnv, handleProjectSharedEnvSaved } from './modules/project-settings.js';
15
+ import { initProjectSettings, handleInstructionsRead, handleInstructionsWrite, handleProjectEnv, handleProjectEnvSaved, isProjectSettingsOpen, handleProjectSharedEnv, handleProjectSharedEnvSaved, handleProjectOwnerChanged } from './modules/project-settings.js';
16
16
  import { initSkills, handleSkillInstalled, handleSkillUninstalled } from './modules/skills.js';
17
17
  import { initScheduler, resetScheduler, handleLoopRegistryUpdated, handleScheduleRunStarted, handleScheduleRunFinished, handleLoopScheduled, openSchedulerToTab, isSchedulerOpen, closeScheduler, enterCraftingMode, exitCraftingMode, handleLoopRegistryFiles, getUpcomingSchedules } from './modules/scheduler.js';
18
18
  import { initAsciiLogo, startLogoAnimation, stopLogoAnimation } from './modules/ascii-logo.js';
@@ -49,6 +49,13 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
49
49
  var imagePreviewBar = $("image-preview-bar");
50
50
  var connectOverlay = $("connect-overlay");
51
51
 
52
+ // --- DM Mode ---
53
+ var dmMode = false;
54
+ var dmKey = null;
55
+ var dmTargetUser = null;
56
+ var dmUnread = {}; // { otherUserId: count }
57
+ var cachedAllUsers = [];
58
+
52
59
  // --- Home Hub ---
53
60
  var homeHub = $("home-hub");
54
61
  var homeHubVisible = false;
@@ -524,9 +531,232 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
524
531
  }
525
532
  }
526
533
 
534
+ // --- DM Mode Functions ---
535
+ function openDm(targetUserId) {
536
+ if (!ws || ws.readyState !== 1) return;
537
+ ws.send(JSON.stringify({ type: "dm_open", targetUserId: targetUserId }));
538
+ }
539
+
540
+ function enterDmMode(key, targetUser, messages) {
541
+ dmMode = true;
542
+ dmKey = key;
543
+ dmTargetUser = targetUser;
544
+
545
+ // Clear unread for this user
546
+ if (targetUser) {
547
+ dmUnread[targetUser.id] = 0;
548
+ updateDmBadge(targetUser.id, 0);
549
+ }
550
+
551
+ // Update icon strip active state
552
+ setCurrentDmUser(targetUser ? targetUser.id : null);
553
+ var activeProj = document.querySelector("#icon-strip-projects .icon-strip-item.active");
554
+ if (activeProj) activeProj.classList.remove("active");
555
+ var homeIcon = document.querySelector(".icon-strip-home");
556
+ if (homeIcon) homeIcon.classList.remove("active");
557
+ // Re-render user strip to show active state
558
+ if (cachedProjects && cachedProjects.length > 0) {
559
+ renderProjectList();
560
+ }
561
+
562
+ // Hide home hub if visible
563
+ hideHomeHub();
564
+
565
+ // Hide project UI + sidebar, show DM UI
566
+ var mainCol = document.getElementById("main-column");
567
+ if (mainCol) mainCol.classList.add("dm-mode");
568
+ var sidebarCol = document.getElementById("sidebar-column");
569
+ if (sidebarCol) sidebarCol.classList.add("dm-mode");
570
+ var resizeHandle = document.getElementById("sidebar-resize-handle");
571
+ if (resizeHandle) resizeHandle.classList.add("dm-mode");
572
+
573
+ // Hide user-island (my avatar behind it becomes visible)
574
+ var userIsland = document.getElementById("user-island");
575
+ if (userIsland) userIsland.classList.add("dm-hidden");
576
+
577
+ // Render DM messages
578
+ messagesEl.innerHTML = "";
579
+ if (messages && messages.length > 0) {
580
+ for (var i = 0; i < messages.length; i++) {
581
+ appendDmMessage(messages[i]);
582
+ }
583
+ }
584
+ scrollToBottom();
585
+
586
+ // Focus input
587
+ if (inputEl) {
588
+ inputEl.placeholder = "Message " + (targetUser ? targetUser.displayName : "");
589
+ inputEl.focus();
590
+ }
591
+
592
+ // Populate DM header bar with user avatar, name, and personal color
593
+ if (targetUser) {
594
+ var dmHeaderBar = document.getElementById("dm-header-bar");
595
+ var dmAvatar = document.getElementById("dm-header-avatar");
596
+ var dmName = document.getElementById("dm-header-name");
597
+ if (dmAvatar) {
598
+ dmAvatar.src = "https://api.dicebear.com/9.x/" + (targetUser.avatarStyle || "thumbs") + "/svg?seed=" + encodeURIComponent(targetUser.avatarSeed || targetUser.username) + "&size=28";
599
+ }
600
+ if (dmName) dmName.textContent = targetUser.displayName;
601
+ if (dmHeaderBar && targetUser.avatarColor) {
602
+ dmHeaderBar.style.background = targetUser.avatarColor;
603
+ }
604
+ }
605
+ }
606
+
607
+ function exitDmMode() {
608
+ if (!dmMode) return;
609
+ dmMode = false;
610
+ dmKey = null;
611
+ dmTargetUser = null;
612
+ setCurrentDmUser(null);
613
+
614
+ var mainCol = document.getElementById("main-column");
615
+ if (mainCol) mainCol.classList.remove("dm-mode");
616
+ var sidebarCol = document.getElementById("sidebar-column");
617
+ if (sidebarCol) sidebarCol.classList.remove("dm-mode");
618
+ var resizeHandle = document.getElementById("sidebar-resize-handle");
619
+ if (resizeHandle) resizeHandle.classList.remove("dm-mode");
620
+
621
+ // Reset DM header
622
+ var dmHeaderBar = document.getElementById("dm-header-bar");
623
+ if (dmHeaderBar) dmHeaderBar.style.background = "";
624
+
625
+ // Restore user-island (covers my avatar again)
626
+ var userIsland = document.getElementById("user-island");
627
+ if (userIsland) userIsland.classList.remove("dm-hidden");
628
+
629
+ // Restore project UI
630
+ if (inputEl) inputEl.placeholder = "";
631
+ renderProjectList();
632
+ }
633
+
634
+ function appendDmMessage(msg) {
635
+ var isMe = msg.from === myUserId;
636
+ var d = new Date(msg.ts);
637
+ var timeStr = d.getHours().toString().padStart(2, "0") + ":" + d.getMinutes().toString().padStart(2, "0");
638
+
639
+ // Check if we can compact (same sender as previous, within 5 min)
640
+ var prev = messagesEl.lastElementChild;
641
+ var compact = false;
642
+ if (prev && prev.dataset.from === msg.from) {
643
+ var prevTs = parseInt(prev.dataset.ts || "0", 10);
644
+ if (msg.ts - prevTs < 300000) compact = true;
645
+ }
646
+
647
+ var div = document.createElement("div");
648
+ div.className = "dm-msg" + (compact ? " dm-msg-compact" : "");
649
+ div.dataset.from = msg.from;
650
+ div.dataset.ts = msg.ts;
651
+
652
+ if (compact) {
653
+ // Compact: just hover-time + text, no avatar/name
654
+ var hoverTime = document.createElement("span");
655
+ hoverTime.className = "dm-msg-hover-time";
656
+ hoverTime.textContent = timeStr;
657
+ div.appendChild(hoverTime);
658
+
659
+ var body = document.createElement("div");
660
+ body.className = "dm-msg-body";
661
+ body.textContent = msg.text;
662
+ div.appendChild(body);
663
+ } else {
664
+ // Full: avatar + header(name, time) + text
665
+ var avatar = document.createElement("img");
666
+ avatar.className = "dm-msg-avatar";
667
+ if (isMe) {
668
+ var myUser = cachedAllUsers.find(function (u) { return u.id === myUserId; });
669
+ var myStyle = myUser ? myUser.avatarStyle : "thumbs";
670
+ var mySeed = myUser ? (myUser.avatarSeed || myUser.username) : myUserId;
671
+ avatar.src = "https://api.dicebear.com/9.x/" + (myStyle || "thumbs") + "/svg?seed=" + encodeURIComponent(mySeed) + "&size=36";
672
+ } else if (dmTargetUser) {
673
+ avatar.src = "https://api.dicebear.com/9.x/" + (dmTargetUser.avatarStyle || "thumbs") + "/svg?seed=" + encodeURIComponent(dmTargetUser.avatarSeed || dmTargetUser.username) + "&size=36";
674
+ }
675
+ div.appendChild(avatar);
676
+
677
+ var content = document.createElement("div");
678
+ content.className = "dm-msg-content";
679
+
680
+ var header = document.createElement("div");
681
+ header.className = "dm-msg-header";
682
+
683
+ var name = document.createElement("span");
684
+ name.className = "dm-msg-name";
685
+ if (isMe) {
686
+ var mu = cachedAllUsers.find(function (u) { return u.id === myUserId; });
687
+ name.textContent = mu ? mu.displayName : "Me";
688
+ } else {
689
+ name.textContent = dmTargetUser ? dmTargetUser.displayName : "User";
690
+ }
691
+ header.appendChild(name);
692
+
693
+ var time = document.createElement("span");
694
+ time.className = "dm-msg-time";
695
+ time.textContent = timeStr;
696
+ header.appendChild(time);
697
+
698
+ content.appendChild(header);
699
+
700
+ var body = document.createElement("div");
701
+ body.className = "dm-msg-body";
702
+ body.textContent = msg.text;
703
+ content.appendChild(body);
704
+
705
+ div.appendChild(content);
706
+ }
707
+
708
+ messagesEl.appendChild(div);
709
+ }
710
+
711
+ var dmTypingTimer = null;
712
+
713
+ function showDmTypingIndicator(typing) {
714
+ var existing = document.getElementById("dm-typing-indicator");
715
+ if (!typing) {
716
+ if (existing) existing.remove();
717
+ return;
718
+ }
719
+ if (existing) return; // already showing
720
+ if (!dmTargetUser) return;
721
+
722
+ var div = document.createElement("div");
723
+ div.id = "dm-typing-indicator";
724
+ div.className = "dm-msg dm-typing-indicator";
725
+
726
+ var avatar = document.createElement("img");
727
+ avatar.className = "dm-msg-avatar";
728
+ avatar.src = "https://api.dicebear.com/9.x/" + (dmTargetUser.avatarStyle || "thumbs") + "/svg?seed=" + encodeURIComponent(dmTargetUser.avatarSeed || dmTargetUser.username) + "&size=36";
729
+ div.appendChild(avatar);
730
+
731
+ var dots = document.createElement("div");
732
+ dots.className = "dm-typing-dots";
733
+ dots.innerHTML = "<span></span><span></span><span></span>";
734
+ div.appendChild(dots);
735
+
736
+ messagesEl.appendChild(div);
737
+ scrollToBottom();
738
+
739
+ // Auto-hide after 5s in case stop signal is missed
740
+ clearTimeout(dmTypingTimer);
741
+ dmTypingTimer = setTimeout(function () {
742
+ showDmTypingIndicator(false);
743
+ }, 5000);
744
+ }
745
+
746
+ function handleDmSend() {
747
+ if (!dmMode || !dmKey || !inputEl) return false;
748
+ var text = inputEl.value.trim();
749
+ if (!text) return false;
750
+ ws.send(JSON.stringify({ type: "dm_send", dmKey: dmKey, text: text }));
751
+ inputEl.value = "";
752
+ autoResize();
753
+ return true;
754
+ }
755
+
527
756
  var hubCloseBtn = document.getElementById("home-hub-close");
528
757
 
529
758
  function showHomeHub() {
759
+ if (dmMode) exitDmMode();
530
760
  homeHubVisible = true;
531
761
  homeHub.classList.remove("hidden");
532
762
  // Show close button only if there's a project to return to
@@ -591,6 +821,7 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
591
821
  var projectHintDismiss = $("project-hint-dismiss");
592
822
  var cachedProjects = [];
593
823
  var cachedProjectCount = 0;
824
+ var currentProjectOwnerId = null;
594
825
  var currentSlug = slugMatch ? slugMatch[1] : null;
595
826
 
596
827
  function updateProjectList(msg) {
@@ -611,6 +842,23 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
611
842
  if (msg.serverUsers) {
612
843
  renderTopbarPresence(msg.serverUsers);
613
844
  }
845
+ // Update user strip (DM targets) in icon strip
846
+ if (msg.allUsers) {
847
+ cachedAllUsers = msg.allUsers;
848
+ var onlineIds = (msg.serverUsers || []).map(function (u) { return u.id; });
849
+ renderUserStrip(msg.allUsers, onlineIds, myUserId);
850
+ // Render my avatar (always present, hidden behind user-island)
851
+ var meEl = document.getElementById("icon-strip-me");
852
+ if (meEl && !meEl.hasChildNodes()) {
853
+ var myUser = cachedAllUsers.find(function (u) { return u.id === myUserId; });
854
+ if (myUser) {
855
+ var meAvatar = document.createElement("img");
856
+ meAvatar.className = "icon-strip-me-avatar";
857
+ meAvatar.src = "https://api.dicebear.com/9.x/" + (myUser.avatarStyle || "thumbs") + "/svg?seed=" + encodeURIComponent(myUser.avatarSeed || myUser.username) + "&size=34";
858
+ meEl.appendChild(meAvatar);
859
+ }
860
+ }
861
+ }
614
862
  }
615
863
 
616
864
  function renderTopbarPresence(serverUsers) {
@@ -915,9 +1163,11 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
915
1163
 
916
1164
  var confirmCallback = null;
917
1165
 
918
- function showConfirm(text, onConfirm) {
1166
+ function showConfirm(text, onConfirm, okLabel, destructive) {
919
1167
  confirmText.textContent = text;
920
1168
  confirmCallback = onConfirm;
1169
+ confirmOk.textContent = okLabel || "Delete";
1170
+ confirmOk.className = "confirm-btn " + (destructive === false ? "confirm-ok" : "confirm-delete");
921
1171
  confirmModal.classList.remove("hidden");
922
1172
  }
923
1173
 
@@ -972,6 +1222,9 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
972
1222
  getUpcomingSchedules: getUpcomingSchedules,
973
1223
  get multiUser() { return isMultiUserMode; },
974
1224
  get myUserId() { return myUserId; },
1225
+ get projectOwnerId() { return currentProjectOwnerId; },
1226
+ openDm: function (userId) { openDm(userId); },
1227
+ openAddProjectModal: function () { openAddProjectModal(); },
975
1228
  };
976
1229
  initSidebar(sidebarCtx);
977
1230
  initIconStrip(sidebarCtx);
@@ -1215,12 +1468,20 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
1215
1468
  var configBetaSection = $("config-beta-section");
1216
1469
  var configBeta1mBtn = $("config-beta-1m");
1217
1470
 
1471
+ var configThinkingSection = $("config-thinking-section");
1472
+ var configThinkingBar = $("config-thinking-bar");
1473
+ var configThinkingBudgetRow = $("config-thinking-budget-row");
1474
+ var configThinkingBudgetInput = $("config-thinking-budget");
1475
+
1218
1476
  var currentModels = [];
1219
1477
  var currentModel = "";
1220
1478
  var currentMode = "default";
1221
1479
  var currentEffort = "medium";
1222
1480
  var currentBetas = [];
1481
+ var currentThinking = "adaptive";
1482
+ var currentThinkingBudget = 10000;
1223
1483
  var skipPermsEnabled = false;
1484
+ var isOsUsers = false;
1224
1485
 
1225
1486
  var MODE_OPTIONS = [
1226
1487
  { value: "default", label: "Default" },
@@ -1230,6 +1491,7 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
1230
1491
  var MODE_FULL_AUTO = { value: "bypassPermissions", label: "Full auto" };
1231
1492
 
1232
1493
  var EFFORT_LEVELS = ["low", "medium", "high", "max"];
1494
+ var THINKING_OPTIONS = ["disabled", "adaptive", "budget"];
1233
1495
 
1234
1496
  function modelDisplayName(value, models) {
1235
1497
  if (!value) return "";
@@ -1255,6 +1517,13 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
1255
1517
  return value.charAt(0).toUpperCase() + value.slice(1);
1256
1518
  }
1257
1519
 
1520
+ function thinkingDisplayName(value) {
1521
+ if (value === "disabled") return "Off";
1522
+ if (value === "adaptive") return "Adaptive";
1523
+ if (value === "budget") return "Budget";
1524
+ return value || "Adaptive";
1525
+ }
1526
+
1258
1527
  function isSonnetModel(model) {
1259
1528
  if (!model) return false;
1260
1529
  var lower = model.toLowerCase();
@@ -1278,6 +1547,9 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
1278
1547
  if (modelSupportsEffort) {
1279
1548
  parts.push(effortDisplayName(currentEffort));
1280
1549
  }
1550
+ if (currentThinking && currentThinking !== "adaptive") {
1551
+ parts.push(thinkingDisplayName(currentThinking));
1552
+ }
1281
1553
  if (hasBeta("context-1m")) {
1282
1554
  parts.push("1M");
1283
1555
  }
@@ -1285,6 +1557,7 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
1285
1557
  rebuildModelList();
1286
1558
  rebuildModeList();
1287
1559
  rebuildEffortBar();
1560
+ rebuildThinkingSection();
1288
1561
  rebuildBetaSection();
1289
1562
  }
1290
1563
 
@@ -1405,6 +1678,51 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
1405
1678
  configBeta1mBtn.setAttribute("aria-checked", active ? "true" : "false");
1406
1679
  }
1407
1680
 
1681
+ function rebuildThinkingSection() {
1682
+ if (!configThinkingBar || !configThinkingSection) return;
1683
+ configThinkingSection.style.display = "";
1684
+ configThinkingBar.innerHTML = "";
1685
+ for (var i = 0; i < THINKING_OPTIONS.length; i++) {
1686
+ var opt = THINKING_OPTIONS[i];
1687
+ var btn = document.createElement("button");
1688
+ btn.className = "config-segment-btn";
1689
+ if (opt === currentThinking) btn.classList.add("active");
1690
+ btn.dataset.thinking = opt;
1691
+ btn.textContent = thinkingDisplayName(opt);
1692
+ btn.addEventListener("click", function () {
1693
+ var thinking = this.dataset.thinking;
1694
+ var msg = { type: "set_thinking", thinking: thinking };
1695
+ if (thinking === "budget") {
1696
+ msg.budgetTokens = currentThinkingBudget;
1697
+ }
1698
+ if (ws && ws.readyState === 1) {
1699
+ ws.send(JSON.stringify(msg));
1700
+ }
1701
+ });
1702
+ configThinkingBar.appendChild(btn);
1703
+ }
1704
+ // Show/hide budget input
1705
+ if (configThinkingBudgetRow) {
1706
+ configThinkingBudgetRow.style.display = currentThinking === "budget" ? "" : "none";
1707
+ }
1708
+ if (configThinkingBudgetInput) {
1709
+ configThinkingBudgetInput.value = currentThinkingBudget;
1710
+ }
1711
+ }
1712
+
1713
+ if (configThinkingBudgetInput) {
1714
+ configThinkingBudgetInput.addEventListener("change", function () {
1715
+ var val = parseInt(this.value, 10);
1716
+ if (isNaN(val) || val < 1024) val = 1024;
1717
+ if (val > 128000) val = 128000;
1718
+ currentThinkingBudget = val;
1719
+ this.value = val;
1720
+ if (ws && ws.readyState === 1) {
1721
+ ws.send(JSON.stringify({ type: "set_thinking", thinking: "budget", budgetTokens: val }));
1722
+ }
1723
+ });
1724
+ }
1725
+
1408
1726
  configBeta1mBtn.addEventListener("click", function (e) {
1409
1727
  e.stopPropagation();
1410
1728
  var active = hasBeta("context-1m");
@@ -1790,6 +2108,20 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
1790
2108
  forceScrollToBottom();
1791
2109
  });
1792
2110
 
2111
+ // Fork session from a user message
2112
+ messagesEl.addEventListener("click", function(e) {
2113
+ var btn = e.target.closest(".msg-action-fork");
2114
+ if (!btn) return;
2115
+ var msgEl = btn.closest("[data-uuid]");
2116
+ if (!msgEl || !msgEl.dataset.uuid) return;
2117
+ var forkUuid = msgEl.dataset.uuid;
2118
+ showConfirm("Fork session from this message?", function() {
2119
+ if (ws && ws.readyState === 1) {
2120
+ ws.send(JSON.stringify({ type: "fork_session", uuid: forkUuid }));
2121
+ }
2122
+ }, "Fork", false);
2123
+ });
2124
+
1793
2125
  function scrollToBottom() {
1794
2126
  if (prependAnchor) return;
1795
2127
  if (isUserScrolledUp) {
@@ -1897,7 +2229,7 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
1897
2229
  actions.innerHTML =
1898
2230
  '<span class="msg-action-time">' + timeStr + '</span>' +
1899
2231
  '<button class="msg-action-btn msg-action-copy" type="button" title="Copy">' + iconHtml("copy") + '</button>' +
1900
- '<button class="msg-action-btn msg-action-hidden msg-action-fork" type="button" title="Fork">' + iconHtml("git-branch") + '</button>' +
2232
+ '<button class="msg-action-btn msg-action-fork" type="button" title="Fork">' + iconHtml("git-branch") + '</button>' +
1901
2233
  '<button class="msg-action-btn msg-action-rewind msg-user-rewind-btn" type="button" title="Rewind">' + iconHtml("rotate-ccw") + '</button>' +
1902
2234
  '<button class="msg-action-btn msg-action-hidden msg-action-edit" type="button" title="Edit">' + iconHtml("pencil") + '</button>';
1903
2235
  div.appendChild(actions);
@@ -2137,6 +2469,50 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
2137
2469
  scrollToBottom();
2138
2470
  }
2139
2471
 
2472
+ // Pending command to run in the next created terminal
2473
+ var pendingTermCommand = null;
2474
+
2475
+ function addAuthRequiredMessage(msg) {
2476
+ var div = document.createElement("div");
2477
+ div.className = "auth-required-msg";
2478
+
2479
+ var header = document.createElement("div");
2480
+ header.className = "auth-required-header";
2481
+ header.textContent = msg.text || "Claude Code is not logged in.";
2482
+ div.appendChild(header);
2483
+
2484
+ var hint = document.createElement("div");
2485
+ hint.className = "auth-required-hint";
2486
+
2487
+ if (msg.canAutoLogin) {
2488
+ // Auto-open terminal and run claude
2489
+ if (msg.linuxUser) {
2490
+ hint.textContent = "Opening a terminal as " + msg.linuxUser + " to log in...";
2491
+ } else {
2492
+ hint.textContent = "Opening a terminal to log in...";
2493
+ }
2494
+ div.appendChild(hint);
2495
+
2496
+ var guide = document.createElement("div");
2497
+ guide.className = "auth-required-guide";
2498
+ guide.textContent = "When a login URL appears in the terminal, click it to open in your browser. Do not press 'c' as it will try to open the browser on the server.";
2499
+ div.appendChild(guide);
2500
+
2501
+ addToMessages(div);
2502
+ scrollToBottom();
2503
+
2504
+ pendingTermCommand = "claude\n";
2505
+ ws.send(JSON.stringify({ type: "term_create", cols: 80, rows: 24 }));
2506
+ openTerminal();
2507
+ } else {
2508
+ // Multi-user regular user: show message only, no auto-login
2509
+ hint.textContent = "Please ask an administrator to log in to Claude Code.";
2510
+ div.appendChild(hint);
2511
+ addToMessages(div);
2512
+ scrollToBottom();
2513
+ }
2514
+ }
2515
+
2140
2516
  // --- Rate Limit ---
2141
2517
 
2142
2518
  var rateLimitCountdownTimer = null;
@@ -2358,6 +2734,7 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
2358
2734
  // --- Project switching (no full reload) ---
2359
2735
  function switchProject(slug) {
2360
2736
  if (!slug) return;
2737
+ if (dmMode) exitDmMode();
2361
2738
  if (homeHubVisible) {
2362
2739
  hideHomeHub();
2363
2740
  if (slug === currentSlug) return;
@@ -2573,6 +2950,8 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
2573
2950
  var vEl = $("footer-version");
2574
2951
  if (vEl) vEl.textContent = "v" + msg.version;
2575
2952
  }
2953
+ if (msg.projectOwnerId !== undefined) currentProjectOwnerId = msg.projectOwnerId;
2954
+ if (msg.osUsers !== undefined) isOsUsers = !!msg.osUsers;
2576
2955
  if (msg.lanHost) window.__lanHost = msg.lanHost;
2577
2956
  if (msg.dangerouslySkipPermissions) {
2578
2957
  skipPermsEnabled = true;
@@ -2594,6 +2973,12 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
2594
2973
  updResetBtn.innerHTML = '<i data-lucide="download"></i> Update now';
2595
2974
  updResetBtn.disabled = false;
2596
2975
  }
2976
+ // Update manual command based on version (beta vs stable)
2977
+ var updManualCmd = $("update-manual-cmd");
2978
+ if (updManualCmd) {
2979
+ var updTag = msg.version.indexOf("-beta") !== -1 ? "beta" : "latest";
2980
+ updManualCmd.textContent = "npx clay-server@" + updTag;
2981
+ }
2597
2982
  refreshIcons();
2598
2983
  }
2599
2984
  // Update the settings check-for-updates button
@@ -2644,6 +3029,8 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
2644
3029
  if (msg.mode) currentMode = msg.mode;
2645
3030
  if (msg.effort) currentEffort = msg.effort;
2646
3031
  if (msg.betas) currentBetas = msg.betas;
3032
+ if (msg.thinking) currentThinking = msg.thinking;
3033
+ if (msg.thinkingBudget) currentThinkingBudget = msg.thinkingBudget;
2647
3034
  // Validate effort against current model's supported levels
2648
3035
  if (currentModels.length > 0) {
2649
3036
  var levels = getModelEffortLevels();
@@ -2724,7 +3111,7 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
2724
3111
  break;
2725
3112
 
2726
3113
  case "input_sync":
2727
- handleInputSync(msg.text);
3114
+ if (!dmMode) handleInputSync(msg.text);
2728
3115
  break;
2729
3116
 
2730
3117
  case "session_list":
@@ -2932,6 +3319,16 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
2932
3319
  startUrgentBlink();
2933
3320
  break;
2934
3321
 
3322
+ case "elicitation_request":
3323
+ renderElicitationRequest(msg);
3324
+ startUrgentBlink();
3325
+ break;
3326
+
3327
+ case "elicitation_resolved":
3328
+ markElicitationResolved(msg.requestId, msg.action);
3329
+ stopUrgentBlink();
3330
+ break;
3331
+
2935
3332
  case "slash_command_result":
2936
3333
  finalizeAssistantBlock();
2937
3334
  var cmdBlock = document.createElement("div");
@@ -2964,7 +3361,7 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
2964
3361
  break;
2965
3362
 
2966
3363
  case "task_progress":
2967
- updateSubagentProgress(msg.parentToolId, msg.usage, msg.lastToolName);
3364
+ updateSubagentProgress(msg.parentToolId, msg.usage, msg.lastToolName, msg.summary);
2968
3365
  break;
2969
3366
 
2970
3367
  case "result":
@@ -3014,6 +3411,11 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
3014
3411
  addContextOverflowMessage(msg);
3015
3412
  break;
3016
3413
 
3414
+ case "auth_required":
3415
+ setActivity(null);
3416
+ addAuthRequiredMessage(msg);
3417
+ break;
3418
+
3017
3419
  case "rate_limit":
3018
3420
  handleRateLimitEvent(msg);
3019
3421
  break;
@@ -3047,6 +3449,10 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
3047
3449
  addSystemMessage(msg.text || "Rewind failed.", true);
3048
3450
  break;
3049
3451
 
3452
+ case "fork_complete":
3453
+ addSystemMessage("Session forked successfully.");
3454
+ break;
3455
+
3050
3456
  case "fs_list_result":
3051
3457
  handleFsList(msg);
3052
3458
  break;
@@ -3115,6 +3521,14 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
3115
3521
 
3116
3522
  case "term_created":
3117
3523
  handleTermCreated(msg);
3524
+ if (pendingTermCommand) {
3525
+ var cmd = pendingTermCommand;
3526
+ pendingTermCommand = null;
3527
+ // Small delay to let terminal initialize
3528
+ setTimeout(function() {
3529
+ sendTerminalCommand(cmd);
3530
+ }, 300);
3531
+ }
3118
3532
  break;
3119
3533
 
3120
3534
  case "term_output":
@@ -3158,6 +3572,10 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
3158
3572
  handleAddProjectResult(msg);
3159
3573
  break;
3160
3574
 
3575
+ case "clone_project_progress":
3576
+ handleCloneProgress(msg);
3577
+ break;
3578
+
3161
3579
  case "remove_project_result":
3162
3580
  handleRemoveProjectResult(msg);
3163
3581
  break;
@@ -3184,6 +3602,41 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
3184
3602
  updateProjectList(msg);
3185
3603
  break;
3186
3604
 
3605
+ case "project_owner_changed":
3606
+ currentProjectOwnerId = msg.ownerId;
3607
+ handleProjectOwnerChanged(msg);
3608
+ break;
3609
+
3610
+ // --- DM ---
3611
+ case "dm_history":
3612
+ enterDmMode(msg.dmKey, msg.targetUser, msg.messages);
3613
+ break;
3614
+
3615
+ case "dm_message":
3616
+ if (dmMode && msg.dmKey === dmKey) {
3617
+ showDmTypingIndicator(false); // hide typing when message arrives
3618
+ appendDmMessage(msg.message);
3619
+ scrollToBottom();
3620
+ } else if (msg.message) {
3621
+ // DM notification when not in that DM
3622
+ var fromId = msg.message.from;
3623
+ if (fromId && fromId !== myUserId) {
3624
+ dmUnread[fromId] = (dmUnread[fromId] || 0) + 1;
3625
+ updateDmBadge(fromId, dmUnread[fromId]);
3626
+ }
3627
+ }
3628
+ break;
3629
+
3630
+ case "dm_typing":
3631
+ if (dmMode && msg.dmKey === dmKey) {
3632
+ showDmTypingIndicator(msg.typing);
3633
+ }
3634
+ break;
3635
+
3636
+ case "dm_list":
3637
+ // Could be used for DM list view later
3638
+ break;
3639
+
3187
3640
  case "daemon_config":
3188
3641
  updateDaemonConfig(msg.config);
3189
3642
  break;
@@ -3488,6 +3941,9 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
3488
3941
  showImageModal: showImageModal,
3489
3942
  hideSuggestionChips: hideSuggestionChips,
3490
3943
  setSendBtnMode: setSendBtnMode,
3944
+ isDmMode: function () { return dmMode; },
3945
+ getDmKey: function () { return dmKey; },
3946
+ handleDmSend: function () { handleDmSend(); },
3491
3947
  });
3492
3948
 
3493
3949
  // --- STT module (voice input via Web Speech API) ---
@@ -3501,6 +3957,105 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
3501
3957
  basePath: basePath,
3502
3958
  });
3503
3959
 
3960
+ // --- Force PIN change overlay (for admin-created accounts with temp PIN) ---
3961
+ function showForceChangePinOverlay() {
3962
+ var ov = document.createElement("div");
3963
+ ov.id = "force-change-pin-overlay";
3964
+ ov.style.cssText = "position:fixed;inset:0;background:var(--bg,#0e0e10);z-index:99999;display:flex;align-items:center;justify-content:center;flex-direction:column";
3965
+ ov.innerHTML = '<div style="width:100%;max-width:380px;padding:24px;text-align:center">' +
3966
+ '<h2 style="margin:0 0 8px;color:var(--text,#fff);font-size:22px">Set your new PIN</h2>' +
3967
+ '<p style="margin:0 0 24px;color:var(--text-secondary,#aaa);font-size:14px">Your temporary PIN has expired. Please set a new 6-digit PIN to continue.</p>' +
3968
+ '<div style="display:flex;gap:8px;justify-content:center;margin-bottom:16px" id="fcp-boxes">' +
3969
+ '<input class="fcp-digit" type="tel" maxlength="1" inputmode="numeric" autocomplete="off" style="width:44px;height:52px;text-align:center;font-size:22px;font-weight:600;border:2px solid var(--border,#333);border-radius:10px;background:var(--bg-secondary,#1a1a1e);color:var(--text,#fff);outline:none">' +
3970
+ '<input class="fcp-digit" type="tel" maxlength="1" inputmode="numeric" autocomplete="off" style="width:44px;height:52px;text-align:center;font-size:22px;font-weight:600;border:2px solid var(--border,#333);border-radius:10px;background:var(--bg-secondary,#1a1a1e);color:var(--text,#fff);outline:none">' +
3971
+ '<input class="fcp-digit" type="tel" maxlength="1" inputmode="numeric" autocomplete="off" style="width:44px;height:52px;text-align:center;font-size:22px;font-weight:600;border:2px solid var(--border,#333);border-radius:10px;background:var(--bg-secondary,#1a1a1e);color:var(--text,#fff);outline:none">' +
3972
+ '<input class="fcp-digit" type="tel" maxlength="1" inputmode="numeric" autocomplete="off" style="width:44px;height:52px;text-align:center;font-size:22px;font-weight:600;border:2px solid var(--border,#333);border-radius:10px;background:var(--bg-secondary,#1a1a1e);color:var(--text,#fff);outline:none">' +
3973
+ '<input class="fcp-digit" type="tel" maxlength="1" inputmode="numeric" autocomplete="off" style="width:44px;height:52px;text-align:center;font-size:22px;font-weight:600;border:2px solid var(--border,#333);border-radius:10px;background:var(--bg-secondary,#1a1a1e);color:var(--text,#fff);outline:none">' +
3974
+ '<input class="fcp-digit" type="tel" maxlength="1" inputmode="numeric" autocomplete="off" style="width:44px;height:52px;text-align:center;font-size:22px;font-weight:600;border:2px solid var(--border,#333);border-radius:10px;background:var(--bg-secondary,#1a1a1e);color:var(--text,#fff);outline:none">' +
3975
+ '</div>' +
3976
+ '<button id="fcp-save" disabled style="width:100%;padding:12px;border:none;border-radius:10px;background:var(--accent,#7c3aed);color:#fff;font-size:15px;font-weight:600;cursor:pointer;opacity:0.5">Save PIN</button>' +
3977
+ '<div id="fcp-err" style="margin-top:12px;color:#ef4444;font-size:13px"></div>' +
3978
+ '</div>';
3979
+ document.body.appendChild(ov);
3980
+
3981
+ var digits = ov.querySelectorAll(".fcp-digit");
3982
+ var saveBtn = ov.querySelector("#fcp-save");
3983
+ var errEl = ov.querySelector("#fcp-err");
3984
+
3985
+ function getPin() {
3986
+ var pin = "";
3987
+ for (var i = 0; i < digits.length; i++) pin += digits[i].value;
3988
+ return pin;
3989
+ }
3990
+
3991
+ function updateBtn() {
3992
+ var ready = getPin().length === 6;
3993
+ saveBtn.disabled = !ready;
3994
+ saveBtn.style.opacity = ready ? "1" : "0.5";
3995
+ }
3996
+
3997
+ for (var i = 0; i < digits.length; i++) {
3998
+ (function (idx) {
3999
+ digits[idx].addEventListener("input", function () {
4000
+ var val = this.value.replace(/\D/g, "");
4001
+ this.value = val.substring(0, 1);
4002
+ if (val && idx < digits.length - 1) digits[idx + 1].focus();
4003
+ updateBtn();
4004
+ });
4005
+ digits[idx].addEventListener("keydown", function (e) {
4006
+ if (e.key === "Backspace" && !this.value && idx > 0) {
4007
+ digits[idx - 1].focus();
4008
+ digits[idx - 1].value = "";
4009
+ updateBtn();
4010
+ }
4011
+ if (e.key === "Enter" && !saveBtn.disabled) doSave();
4012
+ e.stopPropagation();
4013
+ });
4014
+ digits[idx].addEventListener("keyup", function (e) { e.stopPropagation(); });
4015
+ digits[idx].addEventListener("keypress", function (e) { e.stopPropagation(); });
4016
+ digits[idx].addEventListener("paste", function (e) {
4017
+ e.preventDefault();
4018
+ var text = (e.clipboardData || window.clipboardData).getData("text").replace(/\D/g, "").substring(0, 6);
4019
+ for (var j = 0; j < text.length && (idx + j) < digits.length; j++) {
4020
+ digits[idx + j].value = text[j];
4021
+ }
4022
+ if (text.length > 0) {
4023
+ var focusIdx = Math.min(idx + text.length, digits.length - 1);
4024
+ digits[focusIdx].focus();
4025
+ }
4026
+ updateBtn();
4027
+ });
4028
+ })(i);
4029
+ }
4030
+ digits[0].focus();
4031
+
4032
+ function doSave() {
4033
+ var pin = getPin();
4034
+ if (pin.length !== 6) return;
4035
+ saveBtn.disabled = true;
4036
+ saveBtn.style.opacity = "0.5";
4037
+ errEl.textContent = "";
4038
+ fetch("/api/user/pin", {
4039
+ method: "PUT",
4040
+ headers: { "Content-Type": "application/json" },
4041
+ body: JSON.stringify({ newPin: pin }),
4042
+ }).then(function (r) { return r.json(); }).then(function (d) {
4043
+ if (d.ok) {
4044
+ ov.remove();
4045
+ return;
4046
+ }
4047
+ errEl.textContent = d.error || "Failed to save PIN";
4048
+ saveBtn.disabled = false;
4049
+ saveBtn.style.opacity = "1";
4050
+ }).catch(function () {
4051
+ errEl.textContent = "Connection error";
4052
+ saveBtn.disabled = false;
4053
+ saveBtn.style.opacity = "1";
4054
+ });
4055
+ }
4056
+ saveBtn.addEventListener("click", doSave);
4057
+ }
4058
+
3504
4059
  // --- Admin (multi-user mode) ---
3505
4060
  var isMultiUserMode = false;
3506
4061
  var myUserId = null;
@@ -3510,6 +4065,7 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
3510
4065
  fetch("/api/me").then(function (r) { return r.json(); }).then(function (d) {
3511
4066
  if (d.multiUser) isMultiUserMode = true;
3512
4067
  if (d.user && d.user.id) myUserId = d.user.id;
4068
+ if (d.mustChangePin) showForceChangePinOverlay();
3513
4069
  }).catch(function () {});
3514
4070
  // Hide server settings and update controls for non-admin users in multi-user mode
3515
4071
  checkAdminAccess().then(function (isAdmin) {
@@ -3546,6 +4102,8 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
3546
4102
  get currentMode() { return currentMode; },
3547
4103
  get currentEffort() { return currentEffort; },
3548
4104
  get currentBetas() { return currentBetas; },
4105
+ get currentThinking() { return currentThinking; },
4106
+ get currentThinkingBudget() { return currentThinkingBudget; },
3549
4107
  setContextView: setContextView,
3550
4108
  applyContextView: applyContextView,
3551
4109
  });
@@ -3559,6 +4117,8 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
3559
4117
  get currentMode() { return currentMode; },
3560
4118
  get currentEffort() { return currentEffort; },
3561
4119
  get currentBetas() { return currentBetas; },
4120
+ get currentThinking() { return currentThinking; },
4121
+ get currentThinkingBudget() { return currentThinkingBudget; },
3562
4122
  }, getEmojiCategories());
3563
4123
 
3564
4124
  // --- QR code ---
@@ -4544,34 +5104,98 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
4544
5104
  // --- Add project modal ---
4545
5105
  var addProjectModal = document.getElementById("add-project-modal");
4546
5106
  var addProjectInput = document.getElementById("add-project-input");
5107
+ var addProjectCreateInput = document.getElementById("add-project-create-input");
5108
+ var addProjectCloneInput = document.getElementById("add-project-clone-input");
5109
+ var addProjectCloneProgress = document.getElementById("add-project-clone-progress");
4547
5110
  var addProjectSuggestions = document.getElementById("add-project-suggestions");
4548
5111
  var addProjectError = document.getElementById("add-project-error");
4549
5112
  var addProjectOk = document.getElementById("add-project-ok");
4550
5113
  var addProjectCancel = document.getElementById("add-project-cancel");
5114
+ var addProjectModeBtns = addProjectModal.querySelectorAll(".add-project-mode-btn");
5115
+ var addProjectPanels = addProjectModal.querySelectorAll(".add-project-panel");
4551
5116
  var addProjectDebounce = null;
4552
5117
  var addProjectActiveIdx = -1;
5118
+ var addProjectMode = "existing";
5119
+
5120
+ function switchAddProjectMode(mode) {
5121
+ addProjectMode = mode;
5122
+ for (var mi = 0; mi < addProjectModeBtns.length; mi++) {
5123
+ var btn = addProjectModeBtns[mi];
5124
+ if (btn.dataset.mode === mode) {
5125
+ btn.classList.add("active");
5126
+ } else {
5127
+ btn.classList.remove("active");
5128
+ }
5129
+ }
5130
+ for (var pi = 0; pi < addProjectPanels.length; pi++) {
5131
+ var panel = addProjectPanels[pi];
5132
+ if (panel.dataset.panel === mode) {
5133
+ panel.classList.add("active");
5134
+ } else {
5135
+ panel.classList.remove("active");
5136
+ }
5137
+ }
5138
+ addProjectError.classList.add("hidden");
5139
+ addProjectCloneProgress.classList.add("hidden");
5140
+ // Update OK button text
5141
+ if (mode === "existing") {
5142
+ addProjectOk.textContent = "Add";
5143
+ } else if (mode === "create") {
5144
+ addProjectOk.textContent = "Create";
5145
+ } else if (mode === "clone") {
5146
+ addProjectOk.textContent = "Clone";
5147
+ }
5148
+ // Focus the right input
5149
+ setTimeout(function () {
5150
+ if (mode === "existing") {
5151
+ addProjectInput.focus();
5152
+ } else if (mode === "create") {
5153
+ addProjectCreateInput.focus();
5154
+ } else if (mode === "clone") {
5155
+ addProjectCloneInput.focus();
5156
+ }
5157
+ }, 50);
5158
+ }
5159
+
5160
+ for (var mbi = 0; mbi < addProjectModeBtns.length; mbi++) {
5161
+ addProjectModeBtns[mbi].addEventListener("click", function () {
5162
+ if (this.disabled) return;
5163
+ switchAddProjectMode(this.dataset.mode);
5164
+ });
5165
+ }
4553
5166
 
4554
5167
  function openAddProjectModal() {
4555
5168
  addProjectModal.classList.remove("hidden");
4556
5169
  addProjectInput.value = "/";
5170
+ addProjectCreateInput.value = "";
5171
+ addProjectCloneInput.value = "";
4557
5172
  addProjectError.classList.add("hidden");
4558
5173
  addProjectError.textContent = "";
5174
+ addProjectCloneProgress.classList.add("hidden");
4559
5175
  addProjectSuggestions.classList.add("hidden");
4560
5176
  addProjectSuggestions.innerHTML = "";
4561
5177
  addProjectActiveIdx = -1;
4562
5178
  addProjectOk.disabled = false;
4563
- setTimeout(function () {
4564
- addProjectInput.focus();
4565
- addProjectInput.setSelectionRange(1, 1);
4566
- }, 50);
5179
+ // In osUsers mode, disable "existing" and default to "create"
5180
+ var existingBtn = addProjectModal.querySelector('.add-project-mode-btn[data-mode="existing"]');
5181
+ if (isOsUsers) {
5182
+ existingBtn.disabled = true;
5183
+ switchAddProjectMode("create");
5184
+ } else {
5185
+ existingBtn.disabled = false;
5186
+ switchAddProjectMode("existing");
5187
+ }
4567
5188
  }
4568
5189
 
4569
5190
  function closeAddProjectModal() {
4570
5191
  addProjectModal.classList.add("hidden");
4571
5192
  addProjectInput.value = "";
5193
+ addProjectCreateInput.value = "";
5194
+ addProjectCloneInput.value = "";
4572
5195
  addProjectSuggestions.classList.add("hidden");
4573
5196
  addProjectSuggestions.innerHTML = "";
4574
5197
  addProjectError.classList.add("hidden");
5198
+ addProjectCloneProgress.classList.add("hidden");
4575
5199
  addProjectActiveIdx = -1;
4576
5200
  if (addProjectDebounce) { clearTimeout(addProjectDebounce); addProjectDebounce = null; }
4577
5201
  }
@@ -4614,12 +5238,14 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
4614
5238
  }
4615
5239
 
4616
5240
  function handleAddProjectResult(msg) {
5241
+ addProjectCloneProgress.classList.add("hidden");
4617
5242
  if (msg.ok) {
4618
5243
  closeAddProjectModal();
4619
5244
  if (msg.existing) {
4620
5245
  showToast("Project already registered", "info");
4621
5246
  } else {
4622
- showToast("Project added", "success");
5247
+ var toastMsg = addProjectMode === "create" ? "Project created" : addProjectMode === "clone" ? "Project cloned" : "Project added";
5248
+ showToast(toastMsg, "success");
4623
5249
  // Navigate to the new project
4624
5250
  if (msg.slug) {
4625
5251
  switchProject(msg.slug);
@@ -4632,6 +5258,12 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
4632
5258
  }
4633
5259
  }
4634
5260
 
5261
+ function handleCloneProgress(msg) {
5262
+ if (msg.status === "cloning") {
5263
+ addProjectCloneProgress.classList.remove("hidden");
5264
+ }
5265
+ }
5266
+
4635
5267
  function setActiveIdx(idx) {
4636
5268
  var items = addProjectSuggestions.querySelectorAll(".add-project-suggestion-item");
4637
5269
  addProjectActiveIdx = idx;
@@ -4727,13 +5359,39 @@ import { initAdmin, checkAdminAccess } from './modules/admin.js';
4727
5359
  }
4728
5360
  });
4729
5361
 
5362
+ // Enter key on create/clone inputs
5363
+ addProjectCreateInput.addEventListener("keydown", function (e) {
5364
+ if (e.key === "Enter") { e.preventDefault(); submitAddProject(); }
5365
+ if (e.key === "Escape") { e.preventDefault(); closeAddProjectModal(); }
5366
+ });
5367
+
5368
+ addProjectCloneInput.addEventListener("keydown", function (e) {
5369
+ if (e.key === "Enter") { e.preventDefault(); submitAddProject(); }
5370
+ if (e.key === "Escape") { e.preventDefault(); closeAddProjectModal(); }
5371
+ });
5372
+
4730
5373
  function submitAddProject() {
4731
- var val = addProjectInput.value.replace(/\/+$/, "");
4732
- if (!val) return;
4733
- addProjectOk.disabled = true;
4734
5374
  addProjectError.classList.add("hidden");
4735
- if (ws && ws.readyState === 1) {
4736
- ws.send(JSON.stringify({ type: "add_project", path: val }));
5375
+ addProjectOk.disabled = true;
5376
+
5377
+ if (addProjectMode === "existing") {
5378
+ var val = addProjectInput.value.replace(/\/+$/, "");
5379
+ if (!val) { addProjectOk.disabled = false; return; }
5380
+ if (ws && ws.readyState === 1) {
5381
+ ws.send(JSON.stringify({ type: "add_project", path: val }));
5382
+ }
5383
+ } else if (addProjectMode === "create") {
5384
+ var name = addProjectCreateInput.value.trim();
5385
+ if (!name) { addProjectOk.disabled = false; return; }
5386
+ if (ws && ws.readyState === 1) {
5387
+ ws.send(JSON.stringify({ type: "create_project", name: name }));
5388
+ }
5389
+ } else if (addProjectMode === "clone") {
5390
+ var url = addProjectCloneInput.value.trim();
5391
+ if (!url) { addProjectOk.disabled = false; return; }
5392
+ if (ws && ws.readyState === 1) {
5393
+ ws.send(JSON.stringify({ type: "clone_project", url: url }));
5394
+ }
4737
5395
  }
4738
5396
  }
4739
5397