clay-server 5.2.0-beta.9 → 5.2.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 (104) hide show
  1. package/README.md +0 -4
  2. package/lib/clay-product-control-tools.js +1 -0
  3. package/lib/clay-product-control.js +14 -11
  4. package/lib/clay-vendor-usage.js +44 -0
  5. package/lib/github-star-count.js +147 -0
  6. package/lib/notes-checklist.js +43 -1
  7. package/lib/notes.js +2 -1
  8. package/lib/project-session-notes.js +33 -8
  9. package/lib/project-sessions.js +63 -24
  10. package/lib/project-start-protocol.js +29 -0
  11. package/lib/project-vendor-login.js +2 -0
  12. package/lib/project.js +5 -1
  13. package/lib/public/app.js +27 -18
  14. package/lib/public/css/contextual-clay.css +1 -1
  15. package/lib/public/css/filebrowser.css +1 -1
  16. package/lib/public/css/mate-loading-scene.css +21 -0
  17. package/lib/public/css/overlays.css +64 -256
  18. package/lib/public/css/project-mate-creation.css +5 -6
  19. package/lib/public/css/project-start.css +108 -0
  20. package/lib/public/css/projects-hub.css +53 -14
  21. package/lib/public/css/settings-shell.css +103 -0
  22. package/lib/public/css/sidebar.css +7 -0
  23. package/lib/public/css/title-bar.css +83 -0
  24. package/lib/public/index.html +50 -63
  25. package/lib/public/modules/account-transport-settings.js +8 -2
  26. package/lib/public/modules/admin-settings-email.js +7 -2
  27. package/lib/public/modules/admin-settings-invitations.js +2 -2
  28. package/lib/public/modules/admin-settings-projects.js +2 -2
  29. package/lib/public/modules/admin-settings-ui.js +20 -4
  30. package/lib/public/modules/admin-settings-users.js +4 -4
  31. package/lib/public/modules/app-home-hub.js +2 -1
  32. package/lib/public/modules/app-messages.js +10 -2
  33. package/lib/public/modules/app-projects.js +1 -407
  34. package/lib/public/modules/app-rate-limit.js +12 -1
  35. package/lib/public/modules/clay-tool-presentation.js +1 -0
  36. package/lib/public/modules/contextual-clay.js +3 -1
  37. package/lib/public/modules/github-star.js +66 -0
  38. package/lib/public/modules/home-surface-boot.js +0 -1
  39. package/lib/public/modules/mate-loading-scene.js +17 -0
  40. package/lib/public/modules/pane-appearance-bridge.js +59 -0
  41. package/lib/public/modules/profile.js +11 -3
  42. package/lib/public/modules/project-mate-creation-workspace.js +13 -4
  43. package/lib/public/modules/project-session-visibility-settings.js +19 -16
  44. package/lib/public/modules/project-settings-environment.js +12 -1
  45. package/lib/public/modules/project-settings.js +119 -129
  46. package/lib/public/modules/project-start-dialog.js +130 -0
  47. package/lib/public/modules/project-start-folder-state.js +78 -0
  48. package/lib/public/modules/project-start-folder-view.js +190 -0
  49. package/lib/public/modules/project-start-forms.js +66 -0
  50. package/lib/public/modules/project-start-path.js +60 -0
  51. package/lib/public/modules/project-start-state.js +54 -0
  52. package/lib/public/modules/project-start-submit.js +67 -0
  53. package/lib/public/modules/projects-hub-view.js +32 -44
  54. package/lib/public/modules/projects-hub.js +39 -28
  55. package/lib/public/modules/server-settings.js +36 -91
  56. package/lib/public/modules/settings-project-target.js +56 -0
  57. package/lib/public/modules/settings-select.js +1 -1
  58. package/lib/public/modules/settings-shell-registry.js +88 -0
  59. package/lib/public/modules/settings-shell-view.js +104 -0
  60. package/lib/public/modules/settings-shell.js +342 -0
  61. package/lib/public/modules/sidebar-mobile.js +8 -30
  62. package/lib/public/modules/sidebar-projects.js +2 -1
  63. package/lib/public/modules/theme.js +31 -3
  64. package/lib/public/modules/user-settings-integrations.js +2 -1
  65. package/lib/public/modules/user-settings-layout.js +12 -34
  66. package/lib/public/modules/user-settings-profile.js +1 -1
  67. package/lib/public/modules/user-settings.js +18 -85
  68. package/lib/public/modules/vendor-usage-format.js +93 -0
  69. package/lib/public/modules/vendor-usage-list.js +45 -0
  70. package/lib/public/modules/vendor-usage-model.js +215 -0
  71. package/lib/public/modules/vendor-usage-overview.js +323 -0
  72. package/lib/public/style.css +10 -7
  73. package/lib/public/welcome-art/conversation-desk.png +0 -0
  74. package/lib/public/welcome-art/mate-clay.png +0 -0
  75. package/lib/public/welcome-art/project-clay.png +0 -0
  76. package/lib/sdk-bridge.js +6 -1
  77. package/lib/sdk-message-processor.js +9 -1
  78. package/lib/sdk-quota-snapshot.js +20 -2
  79. package/lib/server-github-stars.js +39 -0
  80. package/lib/server-global-ws.js +21 -17
  81. package/lib/server.js +11 -0
  82. package/lib/session-notes-mcp-server.js +26 -4
  83. package/lib/session-notes-writes.js +88 -0
  84. package/lib/session-visibility.js +20 -8
  85. package/lib/sessions.js +12 -2
  86. package/lib/vendor-usage-acp-readers.js +97 -0
  87. package/lib/vendor-usage-capabilities.js +88 -0
  88. package/lib/vendor-usage-http-readers.js +214 -0
  89. package/lib/vendor-usage-metrics.js +47 -0
  90. package/lib/vendor-usage-native-readers.js +106 -0
  91. package/lib/vendor-usage-overview.js +406 -0
  92. package/lib/vendor-usage-service.js +281 -0
  93. package/lib/ws-schema.js +4 -2
  94. package/lib/yoke/adapters/claude.js +12 -0
  95. package/lib/yoke/adapters/codex-query-contract.js +1 -0
  96. package/lib/yoke/adapters/codex.js +45 -12
  97. package/lib/yoke/claude-hook-installer.js +1 -0
  98. package/lib/yoke/codex-dynamic-tool-names.js +80 -0
  99. package/lib/yoke/codex-model-catalog.js +37 -3
  100. package/lib/yoke/muse-services.js +16 -3
  101. package/lib/yoke/providers/claude-model-descriptions.js +22 -2
  102. package/lib/yoke/vendor-registry.js +6 -2
  103. package/package.json +2 -1
  104. package/lib/public/modules/server-settings-navigation.js +0 -39
@@ -65,7 +65,7 @@ import { showDoneNotification, playDoneSound, isNotifAlertEnabled, isNotifSoundE
65
65
  import { handleFsList, handleFsRead, handleFileChanged, handleDirChanged, handleFileHistory, handleGitDiff, handleFileAt, refreshIfOpen, getPendingNavigate, handleFsSearch, presentMarkdownEdit } from './filebrowser.js';
66
66
  import { beginMarkdownTurn, finishMarkdownTurn, markdownPathFromToolInput } from './markdown-live-edit.js';
67
67
  import { forwardPaneMarkdownPresentation } from './pane-bridge.js';
68
- import { handleProjectOwnerChanged } from './project-settings.js';
68
+ import { handleProjectOwnerChanged, handleProjectSettingsEditResult } from './project-settings.js';
69
69
  import { handleProjectEnv, handleProjectEnvSaved } from './project-settings-environment.js';
70
70
  import { handleProjectSessionVisibilityDefault, refreshProjectSessionVisibilitySettings } from './project-session-visibility-settings.js';
71
71
  import { updateSettingsStats, updateDaemonConfig, handleSetPinResult, handleKeepAwakeChanged, handleAutoContinueChanged, handleRestartResult, handleShutdownResult, handleSharedEnv, handleSharedEnvSaved, handleServerSettingResult } from './server-settings.js';
@@ -100,13 +100,15 @@ import { markProjectSessionListHydrated, applyProjectSplitGroups, clearProjectSp
100
100
  import { selectDefaultVendorForBlankSession } from './vendor-selection.js';
101
101
  import { getModelInfoUpdate, modelEntryValue, modelEntryMatches, handleModelSelectionResult, requestVendorModels } from './model-picker.js';
102
102
  import { getModelEffortLevels, accumulateUsage, updateUsagePanel, accumulateContext, updateContextPanel, renderCtxPopover, updateStatusPanel, markContextUnavailable } from './app-panels.js';
103
- import { updateProjectList, resetClientState, showUpdateAvailable, handleRemoveProjectCheckResult, handleRemoveProjectResult, handleBrowseDirResult, handleAddProjectResult, handleCloneProgress, finishProjectSessionActivation } from './app-projects.js';
103
+ import { updateProjectList, resetClientState, showUpdateAvailable, handleRemoveProjectCheckResult, handleRemoveProjectResult, finishProjectSessionActivation } from './app-projects.js';
104
+ import { handleBrowseDirResult, handleAddProjectResult, handleCloneProgress } from './project-start-dialog.js';
104
105
  import { updateHistorySentinel, prependOlderHistory } from './app-header.js';
105
106
  import { handleToolsState, handleToolInstalled, handleToolRemoved, handleToolStorageResult, handleToolLlmResult, handleToolsError, handleToolControlRequest, handleToolServerState, handleToolServerEvent, handleCapsuleGameSession } from './home-tools.js';
106
107
  import { handleHomeDockState } from './home-dock.js';
107
108
  import { handleToolFrameUrlState } from './home-tool-frame.js';
108
109
  import { enterDmMode, exitDmMode, handleMateCreatedInApp, updateMateIconStatus, appendDmMessage, showDmTypingIndicator, buildMateInterviewPrompt } from './app-dm.js';
109
110
  import { handleRateLimitEvent, updateRateLimitUsage, handleFastModeState } from './app-rate-limit.js';
111
+ import { handleVendorUsageOverview } from './vendor-usage-overview.js';
110
112
  import { handleRemoteCursorMove, handleRemoteCursorLeave, handleRemoteSelection, clearRemoteCursors } from './app-cursors.js';
111
113
  import { showLoopBanner, updateLoopBanner, updateLoopInputVisibility, showRalphApprovalBar, updateRalphApprovalStatus, openRalphPreviewModal, showExecModal, updateExecModalStatus } from './app-loop-ui.js';
112
114
  import { handleAutonomousRunMessage, syncAutonomousRunForSession } from './autonomous-run.js';
@@ -1615,6 +1617,10 @@ export function processMessage(msg) {
1615
1617
  updateRateLimitUsage(msg);
1616
1618
  break;
1617
1619
 
1620
+ case "vendor_usage_overview":
1621
+ handleVendorUsageOverview(msg);
1622
+ break;
1623
+
1618
1624
  case "auto_continue_scheduled":
1619
1625
  // Scheduler auto-continue, just show info
1620
1626
  break;
@@ -1922,12 +1928,14 @@ export function processMessage(msg) {
1922
1928
  break;
1923
1929
 
1924
1930
  case "set_project_title_result":
1931
+ handleProjectSettingsEditResult(msg);
1925
1932
  if (!msg.ok) {
1926
1933
  showToast(msg.error || "Failed to rename project", "error");
1927
1934
  }
1928
1935
  break;
1929
1936
 
1930
1937
  case "set_project_icon_result":
1938
+ handleProjectSettingsEditResult(msg);
1931
1939
  if (!msg.ok) {
1932
1940
  showToast(msg.error || "Failed to set icon", "error");
1933
1941
  }
@@ -4,9 +4,8 @@ import { checkComposerDeparture, saveSessionRichDraft } from './session-rich-dra
4
4
  // Extracted from app.js (PR-29)
5
5
 
6
6
  import { showProjectsHub, hideProjectsHub, completeProjectsOnboarding } from './projects-hub.js';
7
- import { escapeHtml, showToast } from './utils.js';
7
+ import { showToast } from './utils.js';
8
8
  import { refreshIcons } from './icons.js';
9
- import { parseEmojis } from './markdown.js';
10
9
  import { store } from './store.js';
11
10
  import { resetModelPickerState } from './model-picker.js';
12
11
  import { getWs, setWs } from './ws-ref.js';
@@ -50,157 +49,6 @@ var cachedRemovedProjects = [];
50
49
  var pendingRemoveSlug = null;
51
50
  var pendingRemoveName = null;
52
51
 
53
- // Add-project modal state
54
- var addProjectModal = null;
55
- var addProjectInput = null;
56
- var addProjectCreateInput = null;
57
- var addProjectCloneInput = null;
58
- var addProjectCloneProgress = null;
59
- var addProjectSuggestions = null;
60
- var addProjectError = null;
61
- var addProjectOk = null;
62
- var addProjectCancel = null;
63
- var addProjectModeBtns = null;
64
- var addProjectPanels = null;
65
- var addProjectRemoved = null;
66
- var addProjectPrefixEl = null;
67
- var addProjectPrefixValue = "";
68
- var addProjectDebounce = null;
69
- var addProjectActiveIdx = -1;
70
- var addProjectMode = "existing";
71
-
72
- export function initProjects() {
73
- // Init add-project modal DOM refs
74
- addProjectModal = document.getElementById("add-project-modal");
75
- addProjectInput = document.getElementById("add-project-input");
76
- addProjectCreateInput = document.getElementById("add-project-create-input");
77
- addProjectCloneInput = document.getElementById("add-project-clone-input");
78
- addProjectCloneProgress = document.getElementById("add-project-clone-progress");
79
- addProjectSuggestions = document.getElementById("add-project-suggestions");
80
- addProjectError = document.getElementById("add-project-error");
81
- addProjectOk = document.getElementById("add-project-ok");
82
- addProjectCancel = document.getElementById("add-project-cancel");
83
- addProjectModeBtns = addProjectModal.querySelectorAll(".add-project-mode-btn");
84
- addProjectPanels = addProjectModal.querySelectorAll(".add-project-panel");
85
- addProjectRemoved = document.getElementById("add-project-removed");
86
- addProjectPrefixEl = document.getElementById("add-project-prefix");
87
-
88
- // Mode button click listeners
89
- for (var mbi = 0; mbi < addProjectModeBtns.length; mbi++) {
90
- addProjectModeBtns[mbi].addEventListener("click", function () {
91
- if (this.disabled) return;
92
- switchAddProjectMode(this.dataset.mode);
93
- });
94
- }
95
-
96
- // Existing project input listeners
97
- addProjectInput.addEventListener("focus", function () {
98
- var val = addProjectInput.value;
99
- if ((val || addProjectPrefixValue) && addProjectSuggestions.children.length === 0) {
100
- requestBrowseDir(val);
101
- } else if (addProjectSuggestions.children.length > 0) {
102
- addProjectSuggestions.classList.remove("hidden");
103
- }
104
- });
105
-
106
- addProjectModal.querySelector(".confirm-dialog").addEventListener("click", function (e) {
107
- if (e.target === addProjectInput || addProjectInput.contains(e.target)) return;
108
- if (e.target === addProjectSuggestions || addProjectSuggestions.contains(e.target)) return;
109
- addProjectSuggestions.classList.add("hidden");
110
- addProjectActiveIdx = -1;
111
- });
112
-
113
- addProjectInput.addEventListener("input", function () {
114
- var val = addProjectInput.value;
115
- addProjectError.classList.add("hidden");
116
- if (addProjectDebounce) clearTimeout(addProjectDebounce);
117
- addProjectDebounce = setTimeout(function () {
118
- requestBrowseDir(val);
119
- }, 200);
120
- });
121
-
122
- addProjectInput.addEventListener("keydown", function (e) {
123
- var items = addProjectSuggestions.querySelectorAll(".add-project-suggestion-item");
124
-
125
- if (e.key === "ArrowDown") {
126
- e.preventDefault();
127
- if (items.length > 0) {
128
- var next = addProjectActiveIdx < items.length - 1 ? addProjectActiveIdx + 1 : 0;
129
- setActiveIdx(next);
130
- }
131
- return;
132
- }
133
-
134
- if (e.key === "ArrowUp") {
135
- e.preventDefault();
136
- if (items.length > 0) {
137
- var prev = addProjectActiveIdx > 0 ? addProjectActiveIdx - 1 : items.length - 1;
138
- setActiveIdx(prev);
139
- }
140
- return;
141
- }
142
-
143
- if (e.key === "Tab") {
144
- e.preventDefault();
145
- var target = addProjectActiveIdx >= 0 && addProjectActiveIdx < items.length
146
- ? items[addProjectActiveIdx]
147
- : items.length > 0 ? items[0] : null;
148
- if (target) {
149
- var fullP = target.dataset.path + "/";
150
- addProjectInput.value = stripPrefix(fullP);
151
- addProjectError.classList.add("hidden");
152
- requestBrowseDir(addProjectInput.value);
153
- }
154
- return;
155
- }
156
-
157
- if (e.key === "Enter") {
158
- e.preventDefault();
159
- if (addProjectActiveIdx >= 0 && addProjectActiveIdx < items.length) {
160
- var fullPicked = items[addProjectActiveIdx].dataset.path + "/";
161
- addProjectInput.value = stripPrefix(fullPicked);
162
- addProjectError.classList.add("hidden");
163
- requestBrowseDir(addProjectInput.value);
164
- return;
165
- }
166
- submitAddProject();
167
- return;
168
- }
169
-
170
- if (e.key === "Escape") {
171
- e.preventDefault();
172
- closeAddProjectModal();
173
- return;
174
- }
175
- });
176
-
177
- // Enter key on create/clone inputs
178
- addProjectCreateInput.addEventListener("keydown", function (e) {
179
- if (e.key === "Enter") { e.preventDefault(); submitAddProject(); }
180
- if (e.key === "Escape") { e.preventDefault(); closeAddProjectModal(); }
181
- });
182
-
183
- addProjectCloneInput.addEventListener("keydown", function (e) {
184
- if (e.key === "Enter") { e.preventDefault(); submitAddProject(); }
185
- if (e.key === "Escape") { e.preventDefault(); closeAddProjectModal(); }
186
- });
187
-
188
- addProjectOk.addEventListener("click", function () { submitAddProject(); });
189
- addProjectCancel.addEventListener("click", function () { closeAddProjectModal(); });
190
-
191
- // Close on backdrop click
192
- addProjectModal.querySelector(".confirm-backdrop").addEventListener("click", function () {
193
- closeAddProjectModal();
194
- });
195
-
196
- // Project list add button
197
- var projectListAddBtn = document.getElementById("project-list-add");
198
- if (projectListAddBtn) {
199
- projectListAddBtn.addEventListener("click", function () {
200
- openAddProjectModal();
201
- });
202
- }
203
- }
204
52
 
205
53
  // --- State accessors ---
206
54
 
@@ -643,257 +491,3 @@ export function handleRemoveProjectResult(msg) {
643
491
  showToast(msg.error || "Failed to remove project", "error");
644
492
  }
645
493
  }
646
-
647
- // --- Add project modal ---
648
-
649
- function switchAddProjectMode(mode) {
650
- addProjectMode = mode;
651
- for (var mi = 0; mi < addProjectModeBtns.length; mi++) {
652
- var btn = addProjectModeBtns[mi];
653
- if (btn.dataset.mode === mode) {
654
- btn.classList.add("active");
655
- } else {
656
- btn.classList.remove("active");
657
- }
658
- }
659
- for (var pi = 0; pi < addProjectPanels.length; pi++) {
660
- var panel = addProjectPanels[pi];
661
- if (panel.dataset.panel === mode) {
662
- panel.classList.add("active");
663
- } else {
664
- panel.classList.remove("active");
665
- }
666
- }
667
- addProjectError.classList.add("hidden");
668
- addProjectCloneProgress.classList.add("hidden");
669
- if (mode === "existing") {
670
- addProjectOk.textContent = "Add";
671
- } else if (mode === "create") {
672
- addProjectOk.textContent = "Create";
673
- } else if (mode === "clone") {
674
- addProjectOk.textContent = "Clone";
675
- }
676
- setTimeout(function () {
677
- if (mode === "existing") {
678
- addProjectInput.focus();
679
- } else if (mode === "create") {
680
- addProjectCreateInput.focus();
681
- } else if (mode === "clone") {
682
- addProjectCloneInput.focus();
683
- }
684
- }, 50);
685
- }
686
-
687
- export function openAddProjectModal(mode) {
688
- addProjectModal.classList.remove("hidden");
689
- addProjectCreateInput.value = "";
690
- addProjectCloneInput.value = "";
691
- addProjectError.classList.add("hidden");
692
- addProjectError.textContent = "";
693
- addProjectCloneProgress.classList.add("hidden");
694
- addProjectSuggestions.classList.add("hidden");
695
- addProjectSuggestions.innerHTML = "";
696
- addProjectActiveIdx = -1;
697
- addProjectOk.disabled = false;
698
- var existingBtn = addProjectModal.querySelector('.add-project-mode-btn[data-mode="existing"]');
699
- var st = store.snap();
700
- if (st.isOsUsers) {
701
- existingBtn.disabled = false;
702
- var myUser = st.cachedAllUsers.find(function (u) { return u.id === st.myUserId; });
703
- var isAdmin = myUser && myUser.role === "admin";
704
- if (!isAdmin && myUser && myUser.linuxUser) {
705
- // Non-admin: lock prefix to home directory
706
- addProjectPrefixValue = "/home/" + myUser.linuxUser + "/";
707
- addProjectPrefixEl.textContent = addProjectPrefixValue;
708
- addProjectPrefixEl.classList.remove("hidden");
709
- addProjectInput.value = "";
710
- addProjectInput.placeholder = "subdirectory";
711
- } else {
712
- // Admin: no prefix restriction
713
- addProjectPrefixValue = "";
714
- addProjectPrefixEl.classList.add("hidden");
715
- addProjectInput.value = "/";
716
- addProjectInput.placeholder = "/";
717
- }
718
- switchAddProjectMode("existing");
719
- } else {
720
- addProjectPrefixValue = "";
721
- addProjectPrefixEl.classList.add("hidden");
722
- addProjectInput.value = "/";
723
- addProjectInput.placeholder = "/";
724
- existingBtn.disabled = false;
725
- switchAddProjectMode("existing");
726
- }
727
- if (mode === "create" || mode === "clone") switchAddProjectMode(mode);
728
- renderRemovedProjectsList();
729
- }
730
-
731
- function renderRemovedProjectsList() {
732
- if (!addProjectRemoved) return;
733
- addProjectRemoved.innerHTML = "";
734
- if (!cachedRemovedProjects || cachedRemovedProjects.length === 0) {
735
- addProjectRemoved.classList.add("hidden");
736
- return;
737
- }
738
- addProjectRemoved.classList.remove("hidden");
739
- for (var ri = 0; ri < cachedRemovedProjects.length; ri++) {
740
- var rp = cachedRemovedProjects[ri];
741
- var item = document.createElement("div");
742
- item.className = "add-project-removed-item";
743
- item.dataset.path = rp.path;
744
- item.addEventListener("click", function () {
745
- var p = this.dataset.path;
746
- var ws = getWs();
747
- if (ws && ws.readyState === 1) {
748
- ws.send(JSON.stringify({ type: "add_project", path: p }));
749
- }
750
- closeAddProjectModal();
751
- });
752
- var iconEl = document.createElement("span");
753
- iconEl.className = "add-project-removed-icon";
754
- iconEl.textContent = rp.icon || "\uD83D\uDCC1";
755
- item.appendChild(iconEl);
756
- var info = document.createElement("div");
757
- info.className = "add-project-removed-info";
758
- var nameEl = document.createElement("div");
759
- nameEl.className = "add-project-removed-name";
760
- nameEl.textContent = rp.title || rp.path.split("/").pop() || rp.path;
761
- info.appendChild(nameEl);
762
- var pathEl = document.createElement("div");
763
- pathEl.className = "add-project-removed-path";
764
- pathEl.textContent = rp.path;
765
- info.appendChild(pathEl);
766
- item.appendChild(info);
767
- addProjectRemoved.appendChild(item);
768
- }
769
- try { parseEmojis(addProjectRemoved); } catch (e) {}
770
- }
771
-
772
- export function closeAddProjectModal() {
773
- addProjectModal.classList.add("hidden");
774
- addProjectInput.value = "";
775
- addProjectCreateInput.value = "";
776
- addProjectCloneInput.value = "";
777
- addProjectSuggestions.classList.add("hidden");
778
- addProjectSuggestions.innerHTML = "";
779
- addProjectError.classList.add("hidden");
780
- addProjectCloneProgress.classList.add("hidden");
781
- addProjectActiveIdx = -1;
782
- addProjectPrefixValue = "";
783
- addProjectPrefixEl.classList.add("hidden");
784
- if (addProjectDebounce) { clearTimeout(addProjectDebounce); addProjectDebounce = null; }
785
- }
786
-
787
- function getFullPath(inputVal) {
788
- return addProjectPrefixValue + inputVal;
789
- }
790
-
791
- function stripPrefix(fullPath) {
792
- if (addProjectPrefixValue && fullPath.indexOf(addProjectPrefixValue) === 0) {
793
- return fullPath.slice(addProjectPrefixValue.length);
794
- }
795
- return fullPath;
796
- }
797
-
798
- function requestBrowseDir(val) {
799
- var ws = getWs();
800
- if (!ws || ws.readyState !== 1) return;
801
- ws.send(JSON.stringify({ type: "browse_dir", path: getFullPath(val) }));
802
- }
803
-
804
- export function handleBrowseDirResult(msg) {
805
- addProjectSuggestions.innerHTML = "";
806
- addProjectActiveIdx = -1;
807
- if (msg.error) {
808
- addProjectSuggestions.classList.add("hidden");
809
- return;
810
- }
811
- var entries = msg.entries || [];
812
- if (entries.length === 0) {
813
- addProjectSuggestions.classList.add("hidden");
814
- return;
815
- }
816
- for (var si = 0; si < entries.length; si++) {
817
- var entry = entries[si];
818
- var item = document.createElement("div");
819
- item.className = "add-project-suggestion-item";
820
- item.dataset.path = entry.path;
821
- item.innerHTML = '<i data-lucide="folder"></i><span class="add-project-suggestion-name">' +
822
- escapeHtml(entry.name) + '</span>';
823
- item.addEventListener("click", function (e) {
824
- var fullP = this.dataset.path + "/";
825
- addProjectInput.value = stripPrefix(fullP);
826
- addProjectInput.focus();
827
- addProjectError.classList.add("hidden");
828
- requestBrowseDir(addProjectInput.value);
829
- });
830
- addProjectSuggestions.appendChild(item);
831
- }
832
- addProjectSuggestions.classList.remove("hidden");
833
- refreshIcons();
834
- }
835
-
836
- export function handleAddProjectResult(msg) {
837
- addProjectCloneProgress.classList.add("hidden");
838
- if (msg.ok) {
839
- closeAddProjectModal();
840
- if (msg.existing) {
841
- showToast("Project already registered", "info");
842
- } else {
843
- var toastMsg = addProjectMode === "create" ? "Project created" : addProjectMode === "clone" ? "Project cloned" : "Project added";
844
- showToast(toastMsg, "success");
845
- }
846
- if (msg.slug) switchProject(msg.slug);
847
- } else {
848
- addProjectError.textContent = msg.error || "Failed to add project";
849
- addProjectError.classList.remove("hidden");
850
- addProjectOk.disabled = false;
851
- }
852
- }
853
-
854
- export function handleCloneProgress(msg) {
855
- if (msg.status === "cloning") {
856
- addProjectCloneProgress.classList.remove("hidden");
857
- }
858
- }
859
-
860
- function setActiveIdx(idx) {
861
- var items = addProjectSuggestions.querySelectorAll(".add-project-suggestion-item");
862
- addProjectActiveIdx = idx;
863
- for (var ai = 0; ai < items.length; ai++) {
864
- if (ai === idx) {
865
- items[ai].classList.add("active");
866
- items[ai].scrollIntoView({ block: "nearest" });
867
- } else {
868
- items[ai].classList.remove("active");
869
- }
870
- }
871
- }
872
-
873
- function submitAddProject() {
874
- addProjectError.classList.add("hidden");
875
- addProjectOk.disabled = true;
876
-
877
- if (addProjectMode === "existing") {
878
- var val = getFullPath(addProjectInput.value).replace(/\/+$/, "");
879
- if (!val) { addProjectOk.disabled = false; return; }
880
- var ws = getWs();
881
- if (ws && ws.readyState === 1) {
882
- ws.send(JSON.stringify({ type: "add_project", path: val }));
883
- }
884
- } else if (addProjectMode === "create") {
885
- var name = addProjectCreateInput.value.trim();
886
- if (!name) { addProjectOk.disabled = false; return; }
887
- var ws2 = getWs();
888
- if (ws2 && ws2.readyState === 1) {
889
- ws2.send(JSON.stringify({ type: "create_project", name: name }));
890
- }
891
- } else if (addProjectMode === "clone") {
892
- var url = addProjectCloneInput.value.trim();
893
- if (!url) { addProjectOk.disabled = false; return; }
894
- var ws3 = getWs();
895
- if (ws3 && ws3.readyState === 1) {
896
- ws3.send(JSON.stringify({ type: "clone_project", url: url }));
897
- }
898
- }
899
- }
@@ -238,9 +238,16 @@ function tickRateLimitUsage() {
238
238
  renderRateLimitUsage();
239
239
  }
240
240
 
241
+ // Once the server's vendor overview has answered, every vendor including the
242
+ // displayed one is shown by the overview chips, so this session-scoped link
243
+ // stands down. Rejection, warning and scheduling state is untouched.
244
+ function overviewOwnsUsageDisplay() {
245
+ return !!store.get('vendorUsageOverview');
246
+ }
247
+
241
248
  function renderRateLimitUsage() {
242
249
  var activeVendor = store.get('currentVendor') || "claude";
243
- if (!vendorTracksRateLimits(activeVendor)) {
250
+ if (!vendorTracksRateLimits(activeVendor) || overviewOwnsUsageDisplay()) {
244
251
  if (rateLimitUsageEl) {
245
252
  rateLimitUsageEl.remove();
246
253
  rateLimitUsageEl = null;
@@ -320,6 +327,10 @@ export function initRateLimit() {
320
327
  || state.currentSlug !== prev.currentSlug || state.myUserId !== prev.myUserId) {
321
328
  resetRateLimitState();
322
329
  }
330
+ if (state.vendorUsageOverview !== prev.vendorUsageOverview && state.currentVendor === prev.currentVendor && state.activeSessionId === prev.activeSessionId
331
+ && state.currentSlug === prev.currentSlug && state.myUserId === prev.myUserId) {
332
+ renderRateLimitUsage();
333
+ }
323
334
  if (state.vendorInfo !== prev.vendorInfo && state.currentVendor === prev.currentVendor && state.activeSessionId === prev.activeSessionId
324
335
  && state.currentSlug === prev.currentSlug && state.myUserId === prev.myUserId) {
325
336
  renderRateLimitUsage();
@@ -172,6 +172,7 @@ rows('clay-workspace', [
172
172
  ['inspect_mate_chat_change', 'Inspecting Mate conversation change', 'Mate conversation change inspected', 'Mate conversation'],
173
173
  ['control_mate_chat', 'Requesting Mate conversation change', 'Mate conversation request returned', 'Mate conversation', 'why', 'mate-chat'],
174
174
  ['control_schedules', 'Requesting schedule change', 'Schedule change request returned', 'Scheduling', 'why', 'schedule-control'],
175
+ ['read_vendor_usage', 'Reading vendor usage', 'Vendor usage read', 'Vendor usage', 'projectSlug'],
175
176
  ['read_project_records', 'Reading project records', 'Project records read', 'Project records', 'projectSlug'],
176
177
  ['inspect_project_record_change', 'Inspecting record change', 'Record change inspected', 'Project records', 'projectSlug'],
177
178
  ['control_project_records', 'Requesting record change', 'Record change request returned', 'Project records', 'why'],
@@ -1,3 +1,4 @@
1
+ import { mateLoadingMarkup, updateMateLoadingScene } from './mate-loading-scene.js';
1
2
  import { attachContextualSessionCitations } from './contextual-session-citations.js';
2
3
  import { attachContextualConversations } from './contextual-mate-sessions.js';
3
4
  import { getCachedSessions } from './sidebar-sessions.js';
@@ -67,6 +68,7 @@ function render() {
67
68
  var mate = contextualMate(state().mateId);
68
69
  if (mate) {
69
70
  var name = getHomeMateName(mate);
71
+ updateMateLoadingScene(surface.querySelector('[data-clay-loading]'), name, mateAvatarUrl(mate, 96));
70
72
  surface.setAttribute('aria-label', name + ' conversation');
71
73
  surface.querySelector('strong').textContent = name;
72
74
  var header = surface.querySelector('header');
@@ -130,7 +132,7 @@ function ensureSurface() {
130
132
  if (!host()) {
131
133
  var surface = document.createElement('section');
132
134
  surface.id = 'contextual-clay'; surface.setAttribute('role', 'dialog');
133
- surface.innerHTML = '<header><img class="contextual-clay-avatar" alt=""><div class="contextual-clay-identity"><strong></strong><div class="contextual-context-line"><span data-clay-context></span><details class="contextual-clay-context"><summary aria-label="What this Mate can see" title="What this Mate can see">' + iconHtml('circle-help') + '</summary><p></p></details></div></div><div class="contextual-clay-actions"><button type="button" data-clay-expand>' + iconHtml('arrow-up-right') + '<span></span></button><button type="button" data-clay-close title="Close">' + iconHtml('x') + '</button></div></header><p class="contextual-capabilities" role="note" aria-label="Conversation capabilities"></p><div class="contextual-clay-body"><p data-clay-loading role="status">Opening conversation…</p></div>';
135
+ surface.innerHTML = '<header><img class="contextual-clay-avatar" alt=""><div class="contextual-clay-identity"><strong></strong><div class="contextual-context-line"><span data-clay-context></span><details class="contextual-clay-context"><summary aria-label="What this Mate can see" title="What this Mate can see">' + iconHtml('circle-help') + '</summary><p></p></details></div></div><div class="contextual-clay-actions"><button type="button" data-clay-expand>' + iconHtml('arrow-up-right') + '<span></span></button><button type="button" data-clay-close title="Close">' + iconHtml('x') + '</button></div></header><p class="contextual-capabilities" role="note" aria-label="Conversation capabilities"></p><div class="contextual-clay-body">' + mateLoadingMarkup() + '</div>';
134
136
  surface.addEventListener('contextual-clay-keydown', function (event) {
135
137
  var detail = event.detail; var frame = contextualFrame();
136
138
  if (!detail || !frame || detail.source !== frame.contentWindow) return;
@@ -0,0 +1,66 @@
1
+ // github-star.js - Public star count beside the top-bar GitHub Star link.
2
+ //
3
+ // The link itself is static markup. This module only fills in the count read
4
+ // from the server's cached GET /api/github-stars. An unknown count leaves the
5
+ // count segment hidden; nothing is invented, and the module never writes to
6
+ // GitHub or sends anything about the user.
7
+
8
+ import { store } from './store.js';
9
+
10
+ var REFRESH_MS = 60 * 60 * 1000;
11
+ var MOBILE_QUERY = "(max-width: 768px)";
12
+
13
+ var timer = null;
14
+ var inflight = false;
15
+
16
+ // 999 -> "999", 1234 -> "1.2k", 12500 -> "12.5k", 1200000 -> "1.2m"
17
+ export function formatStarCount(n) {
18
+ if (typeof n !== "number" || !isFinite(n) || n < 0) return "";
19
+ n = Math.floor(n);
20
+ if (n < 1000) return String(n);
21
+ var units = [[1000000, "m"], [1000, "k"]];
22
+ for (var i = 0; i < units.length; i++) {
23
+ if (n >= units[i][0]) {
24
+ var v = Math.floor(n / units[i][0] * 10) / 10;
25
+ return (v % 1 === 0 ? String(v) : v.toFixed(1)) + units[i][1];
26
+ }
27
+ }
28
+ return String(n);
29
+ }
30
+
31
+ function render() {
32
+ var count = store.get("githubStarCount");
33
+ var countEl = document.getElementById("github-star-count");
34
+ var linkEl = document.getElementById("github-star-link");
35
+ if (!countEl || !linkEl) return;
36
+ var text = typeof count === "number" ? formatStarCount(count) : "";
37
+ countEl.textContent = text;
38
+ countEl.classList.toggle("hidden", !text);
39
+ linkEl.setAttribute("aria-label", text ? "Star Clay on GitHub, " + count + " stars" : "Star Clay on GitHub");
40
+ }
41
+
42
+ function isMobile() {
43
+ return !!(window.matchMedia && window.matchMedia(MOBILE_QUERY).matches);
44
+ }
45
+
46
+ function load() {
47
+ if (inflight || isMobile()) return Promise.resolve();
48
+ inflight = true;
49
+ return fetch("/api/github-stars", { credentials: "same-origin" }).then(function (res) {
50
+ return res.ok ? res.json() : null;
51
+ }).then(function (data) {
52
+ // Keep the last known count when the server has none or the call failed.
53
+ if (data && typeof data.stars === "number") {
54
+ store.set({ githubStarCount: data.stars });
55
+ render();
56
+ }
57
+ }).catch(function () {}).then(function () { inflight = false; });
58
+ }
59
+
60
+ export function initGithubStar() {
61
+ if (!document.getElementById("github-star")) return;
62
+ render();
63
+ load();
64
+ if (timer) clearInterval(timer);
65
+ timer = setInterval(load, REFRESH_MS);
66
+ }
@@ -11,7 +11,6 @@ function explicitProjectPath(pathname) {
11
11
  export function resolveHomeBootDestination(options) {
12
12
  if (!options) return "wait";
13
13
  if (options.paneMode || explicitProjectPath(options.pathname)) return "project";
14
- if (/^\/projects\/?$/.test(options.pathname || "")) return "projects";
15
14
  // The server-selected project embedded in the root shell is authoritative.
16
15
  // Home is entered only through an explicit in-app action; reloading `/`
17
16
  // returns to the project workspace even if Home was the last open surface.
@@ -0,0 +1,17 @@
1
+ // Decorative opening state; conversation readiness remains with its host.
2
+ export function mateLoadingMarkup() {
3
+ return '<div data-clay-loading class="mate-loading" role="status" aria-live="polite">' +
4
+ '<div class="mate-loading-scene" aria-hidden="true">' +
5
+ '<img class="mate-loading-avatar" alt="">' +
6
+ '<img class="mate-loading-desk" src="/welcome-art/conversation-desk.png" alt="" decoding="async">' +
7
+ '</div><p class="mate-loading-label"></p>' +
8
+ '<div class="mate-loading-dots" aria-hidden="true"><span></span><span></span><span></span></div></div>';
9
+ }
10
+
11
+ export function updateMateLoadingScene(element, name, avatar) {
12
+ var image = element.querySelector('.mate-loading-avatar');
13
+ if (image.getAttribute('src') !== avatar) image.setAttribute('src', avatar);
14
+ var label = element.querySelector('.mate-loading-label');
15
+ var text = 'Getting ready with ' + name;
16
+ if (label.textContent !== text) label.textContent = text;
17
+ }