total-recall-brain 3.6.1 → 3.6.3

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.
@@ -735,3 +735,237 @@ body {
735
735
  ::-webkit-scrollbar-thumb:hover {
736
736
  background: #45475a;
737
737
  }
738
+
739
+ /* ==================== PANEL 5: COLLABORATION ==================== */
740
+
741
+ .collab-section {
742
+ display: flex;
743
+ flex-direction: column;
744
+ padding: 16px;
745
+ height: 100%;
746
+ }
747
+
748
+ .collab-subtext {
749
+ font-size: 12px;
750
+ color: #a6adc8;
751
+ margin-bottom: 16px;
752
+ line-height: 1.4;
753
+ }
754
+
755
+ .collab-form {
756
+ display: flex;
757
+ flex-direction: column;
758
+ gap: 10px;
759
+ }
760
+
761
+ .collab-form input {
762
+ width: 100%;
763
+ padding: 10px 14px;
764
+ background: #1e1e2e;
765
+ border: 1px solid #313244;
766
+ border-radius: 10px;
767
+ color: #cdd6f4;
768
+ font-size: 14px;
769
+ outline: none;
770
+ transition: border-color 0.2s ease;
771
+ }
772
+
773
+ .collab-form input:focus {
774
+ border-color: #cba6f7;
775
+ }
776
+
777
+ .collab-user-bar {
778
+ display: flex;
779
+ justify-content: space-between;
780
+ align-items: center;
781
+ padding: 8px 10px;
782
+ background: #181825;
783
+ border: 1px solid #313244;
784
+ border-radius: 10px;
785
+ margin-bottom: 12px;
786
+ font-size: 13px;
787
+ color: #cdd6f4;
788
+ flex-shrink: 0;
789
+ }
790
+
791
+ .collab-logout-btn {
792
+ background: transparent;
793
+ border: none;
794
+ color: #f38ba8;
795
+ font-size: 11px;
796
+ font-weight: 600;
797
+ cursor: pointer;
798
+ padding: 2px 6px;
799
+ }
800
+
801
+ .collab-logout-btn:hover {
802
+ text-decoration: underline;
803
+ }
804
+
805
+ .collab-control-group {
806
+ display: flex;
807
+ flex-direction: column;
808
+ gap: 6px;
809
+ margin-bottom: 8px;
810
+ flex-shrink: 0;
811
+ }
812
+
813
+ .collab-control-group label {
814
+ font-size: 10px;
815
+ font-weight: 700;
816
+ text-transform: uppercase;
817
+ color: #6c7086;
818
+ letter-spacing: 0.5px;
819
+ }
820
+
821
+ .collab-panel-header {
822
+ display: flex;
823
+ justify-content: space-between;
824
+ align-items: center;
825
+ margin-top: 12px;
826
+ margin-bottom: 6px;
827
+ flex-shrink: 0;
828
+ }
829
+
830
+ .collab-panel-header h4 {
831
+ font-size: 11px;
832
+ font-weight: 700;
833
+ text-transform: uppercase;
834
+ color: #6c7086;
835
+ letter-spacing: 0.5px;
836
+ }
837
+
838
+ .collab-list {
839
+ flex: 1;
840
+ min-height: 80px;
841
+ max-height: 140px;
842
+ overflow-y: auto;
843
+ border: 1px solid #313244;
844
+ background: #181825;
845
+ border-radius: 10px;
846
+ padding: 8px 10px;
847
+ display: flex;
848
+ flex-direction: column;
849
+ gap: 6px;
850
+ margin-bottom: 8px;
851
+ }
852
+
853
+ .collab-annotation-item {
854
+ background: #1e1e2e;
855
+ border: 1px solid #313244;
856
+ border-radius: 8px;
857
+ padding: 8px 10px;
858
+ font-size: 12px;
859
+ line-height: 1.4;
860
+ }
861
+
862
+ .collab-annotation-item .author {
863
+ font-size: 10px;
864
+ color: #89b4fa;
865
+ font-weight: 600;
866
+ margin-bottom: 2px;
867
+ }
868
+
869
+ .collab-annotation-item .text {
870
+ color: #cdd6f4;
871
+ word-break: break-word;
872
+ }
873
+
874
+ .collab-annotation-item .excerpt {
875
+ font-size: 11px;
876
+ color: #a6adc8;
877
+ font-style: italic;
878
+ border-left: 2px solid #89b4fa;
879
+ padding-left: 6px;
880
+ margin-bottom: 4px;
881
+ }
882
+
883
+ .collab-annotation-item .time {
884
+ font-size: 9px;
885
+ color: #585b70;
886
+ text-align: right;
887
+ margin-top: 4px;
888
+ }
889
+
890
+ .collab-annotation-form {
891
+ display: flex;
892
+ flex-direction: column;
893
+ flex-shrink: 0;
894
+ }
895
+
896
+ .collab-annotation-form textarea {
897
+ width: 100%;
898
+ height: 48px;
899
+ min-height: 48px;
900
+ padding: 6px 8px;
901
+ background: #1e1e2e;
902
+ border: 1px solid #313244;
903
+ border-radius: 8px;
904
+ color: #cdd6f4;
905
+ font-size: 12px;
906
+ resize: none;
907
+ outline: none;
908
+ font-family: inherit;
909
+ transition: border-color 0.2s ease;
910
+ }
911
+
912
+ .collab-annotation-form textarea:focus {
913
+ border-color: #cba6f7;
914
+ }
915
+
916
+ .collab-chat-box {
917
+ flex: 2;
918
+ min-height: 100px;
919
+ max-height: 200px;
920
+ border: 1px solid #313244;
921
+ background: #181825;
922
+ border-radius: 10px;
923
+ padding: 10px;
924
+ display: flex;
925
+ flex-direction: column;
926
+ gap: 8px;
927
+ overflow-y: auto;
928
+ margin-bottom: 6px;
929
+ }
930
+
931
+ .collab-input-bar {
932
+ padding: 0 !important;
933
+ border-top: none !important;
934
+ margin-bottom: 0 !important;
935
+ }
936
+
937
+ .collab-input-bar textarea {
938
+ height: 34px !important;
939
+ max-height: 34px !important;
940
+ font-size: 12px !important;
941
+ border-radius: 8px !important;
942
+ padding: 8px !important;
943
+ }
944
+
945
+ .collab-input-bar button {
946
+ height: 34px !important;
947
+ width: 50px !important;
948
+ border-radius: 8px !important;
949
+ font-size: 11px !important;
950
+ }
951
+
952
+ /* Pulse animation for presence badge */
953
+ .pulse {
954
+ animation: pulse-glow 2s infinite;
955
+ }
956
+
957
+ @keyframes pulse-glow {
958
+ 0% {
959
+ transform: scale(0.95);
960
+ box-shadow: 0 0 0 0 rgba(166, 227, 161, 0.7);
961
+ }
962
+ 70% {
963
+ transform: scale(1);
964
+ box-shadow: 0 0 0 5px rgba(166, 227, 161, 0);
965
+ }
966
+ 100% {
967
+ transform: scale(0.95);
968
+ box-shadow: 0 0 0 0 rgba(166, 227, 161, 0);
969
+ }
970
+ }
971
+
@@ -22,6 +22,7 @@
22
22
  <button class="tab-btn" data-tab="chat">💬 Chat</button>
23
23
  <button class="tab-btn" data-tab="research">🔬 Research</button>
24
24
  <button class="tab-btn" data-tab="settings">⚙️ Settings</button>
25
+ <button class="tab-btn" data-tab="collab">👥 Collab</button>
25
26
  </nav>
26
27
 
27
28
  <!-- Tab Panels Container -->
@@ -128,6 +129,77 @@
128
129
  </div>
129
130
  </section>
130
131
 
132
+ <!-- Panel 5: Collaboration -->
133
+ <section id="tab-collab" class="tab-panel">
134
+ <!-- Auth View -->
135
+ <div id="collab-auth-view" class="collab-section">
136
+ <h3>👥 Join Collab Network</h3>
137
+ <p class="collab-subtext">Register or log in to sync annotations and chat on websites live.</p>
138
+ <div class="collab-form">
139
+ <input type="text" id="collab-username" placeholder="Username" />
140
+ <input type="password" id="collab-password" placeholder="Password" />
141
+ <div style="display: flex; gap: 8px; margin-top: 8px;">
142
+ <button id="btn-collab-login" class="action-btn accent" style="flex: 1;">Login</button>
143
+ <button id="btn-collab-register" class="action-btn" style="flex: 1;">Register</button>
144
+ </div>
145
+ </div>
146
+ </div>
147
+
148
+ <!-- Main Collaboration View -->
149
+ <div id="collab-main-view" class="collab-section hidden" style="display: flex; flex-direction: column; height: 100%;">
150
+ <div class="collab-user-bar">
151
+ <span>👤 <strong id="collab-user-display"></strong></span>
152
+ <button id="btn-collab-logout" class="collab-logout-btn">Logout</button>
153
+ </div>
154
+
155
+ <div class="collab-control-group">
156
+ <label for="collab-group-select">Workspace Group</label>
157
+ <select id="collab-group-select" class="settings-select"></select>
158
+ </div>
159
+
160
+ <div class="collab-action-grid" style="display: flex; gap: 8px; margin-bottom: 8px; margin-top: 4px;">
161
+ <button id="btn-collab-show-join" class="action-btn" style="padding: 6px; font-size: 11px;">Join Group</button>
162
+ <button id="btn-collab-show-create" class="action-btn" style="padding: 6px; font-size: 11px;">Create Group</button>
163
+ </div>
164
+
165
+ <div id="collab-join-group-row" class="collab-action-row hidden" style="display: flex; gap: 6px; margin-bottom: 8px;">
166
+ <input type="text" id="collab-join-code" placeholder="Invite Code" style="flex: 1; padding: 6px; font-size: 12px; background: #1e1e2e; border: 1px solid #313244; color: #cdd6f4; border-radius: 6px;" />
167
+ <button id="btn-collab-submit-join" class="action-btn accent" style="flex-shrink: 0; padding: 6px 12px; font-size: 11px; height: 28px;">Join</button>
168
+ </div>
169
+
170
+ <div id="collab-create-group-row" class="collab-action-row hidden" style="display: flex; gap: 6px; margin-bottom: 8px;">
171
+ <input type="text" id="collab-create-name" placeholder="Group Name" style="flex: 1; padding: 6px; font-size: 12px; background: #1e1e2e; border: 1px solid #313244; color: #cdd6f4; border-radius: 6px;" />
172
+ <button id="btn-collab-submit-create" class="action-btn accent" style="flex-shrink: 0; padding: 6px 12px; font-size: 11px; height: 28px;">Create</button>
173
+ </div>
174
+
175
+ <!-- Pinned Annotations -->
176
+ <div class="collab-panel-header">
177
+ <h4>📌 Notes on Page</h4>
178
+ </div>
179
+ <div id="collab-annotations-list" class="collab-list"></div>
180
+
181
+ <!-- Add Annotation Form -->
182
+ <div class="collab-annotation-form">
183
+ <textarea id="collab-note-input" placeholder="Pin a note to this page..."></textarea>
184
+ <button id="btn-collab-save-note" class="action-btn accent" style="width: 100%; margin-top: 4px; padding: 6px; font-size: 12px;">Pin Note</button>
185
+ </div>
186
+
187
+ <!-- Real-time Site Chat -->
188
+ <div class="collab-panel-header" style="margin-top: 8px;">
189
+ <h4>💬 Site Chat Room</h4>
190
+ <div style="display: flex; align-items: center; gap: 4px;">
191
+ <span class="pulse" style="width: 6px; height: 6px; background: #a6e3a1; box-shadow: 0 0 4px #a6e3a1; border-radius: 50%;"></span>
192
+ <span id="collab-presence-count" style="font-size: 10px; color: #a6e3a1;">1 online</span>
193
+ </div>
194
+ </div>
195
+ <div id="collab-chat-messages" class="chat-messages collab-chat-box"></div>
196
+ <div class="chat-input-bar collab-input-bar">
197
+ <textarea id="collab-chat-input" placeholder="Send a message to team..."></textarea>
198
+ <button id="btn-collab-send-chat" class="chat-send-btn">Send</button>
199
+ </div>
200
+ </div>
201
+ </section>
202
+
131
203
  </div>
132
204
  </div>
133
205
  <script src="../lib/preconfigured.js"></script>
@@ -676,6 +676,413 @@ Provide helpful analysis grounded on both the user's brain memory and this page
676
676
  }
677
677
  }
678
678
 
679
+ // ==================== COLLABORATION MODULE ====================
680
+ const collabAuthView = document.getElementById('collab-auth-view');
681
+ const collabMainView = document.getElementById('collab-main-view');
682
+ const collabUsernameInput = document.getElementById('collab-username');
683
+ const collabPasswordInput = document.getElementById('collab-password');
684
+ const btnCollabLogin = document.getElementById('btn-collab-login');
685
+ const btnCollabRegister = document.getElementById('btn-collab-register');
686
+
687
+ const collabUserDisplay = document.getElementById('collab-user-display');
688
+ const btnCollabLogout = document.getElementById('btn-collab-logout');
689
+ const collabGroupSelect = document.getElementById('collab-group-select');
690
+
691
+ const btnCollabShowJoin = document.getElementById('btn-collab-show-join');
692
+ const btnCollabShowCreate = document.getElementById('btn-collab-show-create');
693
+ const collabJoinGroupRow = document.getElementById('collab-join-group-row');
694
+ const collabJoinCode = document.getElementById('collab-join-code');
695
+ const btnCollabSubmitJoin = document.getElementById('btn-collab-submit-join');
696
+
697
+ const collabCreateGroupRow = document.getElementById('collab-create-group-row');
698
+ const collabCreateName = document.getElementById('collab-create-name');
699
+ const btnCollabSubmitCreate = document.getElementById('btn-collab-submit-create');
700
+
701
+ const collabAnnotationsList = document.getElementById('collab-annotations-list');
702
+ const collabNoteInput = document.getElementById('collab-note-input');
703
+ const btnCollabSaveNote = document.getElementById('btn-collab-save-note');
704
+
705
+ const collabPresenceCount = document.getElementById('collab-presence-count');
706
+ const collabChatMessages = document.getElementById('collab-chat-messages');
707
+ const collabChatInput = document.getElementById('collab-chat-input');
708
+ const btnCollabSendChat = document.getElementById('btn-collab-send-chat');
709
+
710
+ let collabToken = '';
711
+ let collabUsername = '';
712
+ let collabActiveUrl = '';
713
+ let collabSocket = null;
714
+ let collabGroups = [];
715
+ let collabSelectedGroup = null;
716
+
717
+ async function initCollab() {
718
+ const data = await chrome.storage.local.get(['collabToken', 'collabUsername']);
719
+ if (data.collabToken && data.collabUsername) {
720
+ collabToken = data.collabToken;
721
+ collabUsername = data.collabUsername;
722
+ showCollabMain();
723
+ } else {
724
+ showCollabAuth();
725
+ }
726
+ }
727
+
728
+ function showCollabAuth() {
729
+ collabAuthView.classList.remove('hidden');
730
+ collabMainView.classList.add('hidden');
731
+ closeCollabSocket();
732
+ }
733
+
734
+ async function showCollabMain() {
735
+ collabAuthView.classList.add('hidden');
736
+ collabMainView.classList.remove('hidden');
737
+ collabUserDisplay.textContent = collabUsername;
738
+ await fetchCollabGroups();
739
+ updateCollabActiveUrl();
740
+ }
741
+
742
+ async function fetchCollabGroups() {
743
+ try {
744
+ const config = await self.BrainClient.getConfig();
745
+ const res = await fetch(`${config.brainUrl}/api/collab/groups`, {
746
+ headers: { 'Authorization': `Bearer ${collabToken}` }
747
+ });
748
+ if (res.ok) {
749
+ collabGroups = await res.json();
750
+ renderCollabGroupsDropdown();
751
+ } else if (res.status === 401) {
752
+ logoutCollab();
753
+ }
754
+ } catch (err) {
755
+ console.error('Failed to fetch collab groups:', err);
756
+ }
757
+ }
758
+
759
+ function renderCollabGroupsDropdown() {
760
+ collabGroupSelect.innerHTML = collabGroups.map(g =>
761
+ `<option value="${g.code}">${escapeHtml(g.name)} (${g.code})</option>`
762
+ ).join('');
763
+ if (collabGroups.length > 0) {
764
+ const activeCode = collabGroupSelect.value;
765
+ collabSelectedGroup = collabGroups.find(g => g.code === activeCode) || collabGroups[0];
766
+ } else {
767
+ collabSelectedGroup = null;
768
+ }
769
+ }
770
+
771
+ async function updateCollabActiveUrl() {
772
+ try {
773
+ const tab = await getCurrentTab();
774
+ if (tab && tab.url && !tab.url.startsWith('chrome://') && !tab.url.startsWith('about:') && !tab.url.startsWith('chrome-extension://')) {
775
+ const cleanUrl = tab.url.split('#')[0];
776
+ if (cleanUrl !== collabActiveUrl) {
777
+ collabActiveUrl = cleanUrl;
778
+ if (collabToken) {
779
+ await fetchCollabAnnotations();
780
+ connectCollabSocket();
781
+ }
782
+ }
783
+ } else {
784
+ collabActiveUrl = '';
785
+ collabAnnotationsList.innerHTML = '<div class="memories-empty">No active web tab</div>';
786
+ closeCollabSocket();
787
+ }
788
+ } catch (err) {
789
+ console.error(err);
790
+ }
791
+ }
792
+
793
+ async function fetchCollabAnnotations() {
794
+ if (!collabActiveUrl || !collabToken) return;
795
+ try {
796
+ const config = await self.BrainClient.getConfig();
797
+ const res = await fetch(`${config.brainUrl}/api/collab/annotations?url=${encodeURIComponent(collabActiveUrl)}`, {
798
+ headers: { 'Authorization': `Bearer ${collabToken}` }
799
+ });
800
+ if (res.ok) {
801
+ const annotations = await res.json();
802
+ renderCollabAnnotations(annotations);
803
+ }
804
+ } catch (err) {
805
+ console.error('Failed to fetch annotations:', err);
806
+ }
807
+ }
808
+
809
+ function renderCollabAnnotations(list) {
810
+ if (!list.length) {
811
+ collabAnnotationsList.innerHTML = '<div class="memories-empty">No notes on this page</div>';
812
+ return;
813
+ }
814
+ collabAnnotationsList.innerHTML = list.map(a => `
815
+ <div class="collab-annotation-item">
816
+ <div class="author">👤 ${escapeHtml(a.author)}</div>
817
+ ${a.excerpt ? `<div class="excerpt">${escapeHtml(a.excerpt)}</div>` : ''}
818
+ <div class="text">${escapeHtml(a.text)}</div>
819
+ <div class="time">${formatTime(a.created_at)}</div>
820
+ </div>
821
+ `).join('');
822
+ }
823
+
824
+ function connectCollabSocket() {
825
+ if (!collabToken || !collabActiveUrl) return;
826
+ closeCollabSocket();
827
+
828
+ self.BrainClient.getConfig().then(config => {
829
+ const serverUrl = new URL(config.brainUrl);
830
+ const wsProto = serverUrl.protocol === 'https:' ? 'wss:' : 'ws:';
831
+ const wsUrl = `${wsProto}//${serverUrl.host}/collab-ws?token=${collabToken}`;
832
+
833
+ const ws = new WebSocket(wsUrl);
834
+ collabSocket = ws;
835
+
836
+ ws.onopen = () => {
837
+ ws.send(JSON.stringify({ type: 'SUBSCRIBE', url: collabActiveUrl }));
838
+ collabPresenceCount.textContent = 'Connected';
839
+ };
840
+
841
+ ws.onmessage = (event) => {
842
+ try {
843
+ const data = JSON.parse(event.data);
844
+ if (data.type === 'CHAT_MESSAGE') {
845
+ appendCollabChatBubble(data.username, data.text, data.created_at);
846
+ } else if (data.type === 'USER_JOINED') {
847
+ appendCollabChatBubble('System', `${data.username} joined this page`, new Date().toISOString(), true);
848
+ } else if (data.type === 'USER_LEFT') {
849
+ appendCollabChatBubble('System', `${data.username} left`, new Date().toISOString(), true);
850
+ } else if (data.type === 'ANNOTATION_ADDED') {
851
+ fetchCollabAnnotations();
852
+ }
853
+ } catch (err) {
854
+ console.error(err);
855
+ }
856
+ };
857
+
858
+ ws.onclose = () => {
859
+ collabPresenceCount.textContent = 'Disconnected';
860
+ };
861
+
862
+ ws.onerror = () => {
863
+ collabPresenceCount.textContent = 'Error';
864
+ };
865
+ });
866
+ }
867
+
868
+ function closeCollabSocket() {
869
+ if (collabSocket) {
870
+ try {
871
+ collabSocket.close();
872
+ } catch {}
873
+ collabSocket = null;
874
+ }
875
+ }
876
+
877
+ function appendCollabChatBubble(sender, text, timestamp, isSystem = false) {
878
+ const bubble = document.createElement('div');
879
+ if (isSystem) {
880
+ bubble.className = 'chat-bubble system';
881
+ bubble.textContent = text;
882
+ } else {
883
+ const isMe = sender.toLowerCase() === collabUsername.toLowerCase();
884
+ bubble.className = `chat-bubble ${isMe ? 'user' : 'assistant'}`;
885
+ bubble.innerHTML = `<span style="font-size: 10px; opacity: 0.8; font-weight: bold; display: block; margin-bottom: 2px;">${escapeHtml(sender)}</span>${escapeHtml(text)}`;
886
+ }
887
+ collabChatMessages.appendChild(bubble);
888
+ collabChatMessages.scrollTop = collabChatMessages.scrollHeight;
889
+ }
890
+
891
+ async function registerCollab(username, password) {
892
+ try {
893
+ const config = await self.BrainClient.getConfig();
894
+ const res = await fetch(`${config.brainUrl}/api/collab/auth/register`, {
895
+ method: 'POST',
896
+ headers: { 'Content-Type': 'application/json' },
897
+ body: JSON.stringify({ username, password })
898
+ });
899
+ const data = await res.json();
900
+ if (res.ok) {
901
+ collabToken = data.token;
902
+ collabUsername = data.username;
903
+ await chrome.storage.local.set({ collabToken: data.token, collabUsername: data.username });
904
+ showToast('Registered successfully!');
905
+ showCollabMain();
906
+ } else {
907
+ showToast('Registration failed: ' + (data.error || 'Unknown error'), true);
908
+ }
909
+ } catch (err) {
910
+ showToast('Registration error: ' + err.message, true);
911
+ }
912
+ }
913
+
914
+ async function loginCollab(username, password) {
915
+ try {
916
+ const config = await self.BrainClient.getConfig();
917
+ const res = await fetch(`${config.brainUrl}/api/collab/auth/login`, {
918
+ method: 'POST',
919
+ headers: { 'Content-Type': 'application/json' },
920
+ body: JSON.stringify({ username, password })
921
+ });
922
+ const data = await res.json();
923
+ if (res.ok) {
924
+ collabToken = data.token;
925
+ collabUsername = data.username;
926
+ await chrome.storage.local.set({ collabToken: data.token, collabUsername: data.username });
927
+ showToast('Logged in successfully!');
928
+ showCollabMain();
929
+ } else {
930
+ showToast('Login failed: ' + (data.error || 'Unknown error'), true);
931
+ }
932
+ } catch (err) {
933
+ showToast('Login error: ' + err.message, true);
934
+ }
935
+ }
936
+
937
+ async function logoutCollab() {
938
+ collabToken = '';
939
+ collabUsername = '';
940
+ await chrome.storage.local.remove(['collabToken', 'collabUsername']);
941
+ showCollabAuth();
942
+ showToast('Logged out');
943
+ }
944
+
945
+ // --- Buttons / Forms bindings ---
946
+ btnCollabLogin.addEventListener('click', () => {
947
+ const u = collabUsernameInput.value.trim();
948
+ const p = collabPasswordInput.value.trim();
949
+ if (u && p) loginCollab(u, p);
950
+ });
951
+
952
+ btnCollabRegister.addEventListener('click', () => {
953
+ const u = collabUsernameInput.value.trim();
954
+ const p = collabPasswordInput.value.trim();
955
+ if (u && p) registerCollab(u, p);
956
+ });
957
+
958
+ btnCollabLogout.addEventListener('click', logoutCollab);
959
+
960
+ btnCollabShowJoin.addEventListener('click', () => {
961
+ collabJoinGroupRow.classList.toggle('hidden');
962
+ collabCreateGroupRow.classList.add('hidden');
963
+ });
964
+
965
+ btnCollabShowCreate.addEventListener('click', () => {
966
+ collabCreateGroupRow.classList.toggle('hidden');
967
+ collabJoinGroupRow.classList.add('hidden');
968
+ });
969
+
970
+ btnCollabSubmitJoin.addEventListener('click', async () => {
971
+ const code = collabJoinCode.value.trim();
972
+ if (!code) return;
973
+ try {
974
+ const config = await self.BrainClient.getConfig();
975
+ const res = await fetch(`${config.brainUrl}/api/collab/groups/join`, {
976
+ method: 'POST',
977
+ headers: {
978
+ 'Content-Type': 'application/json',
979
+ 'Authorization': `Bearer ${collabToken}`
980
+ },
981
+ body: JSON.stringify({ code })
982
+ });
983
+ const data = await res.json();
984
+ if (res.ok) {
985
+ showToast('Joined group!');
986
+ collabJoinCode.value = '';
987
+ collabJoinGroupRow.classList.add('hidden');
988
+ await fetchCollabGroups();
989
+ } else {
990
+ showToast('Join error: ' + data.error, true);
991
+ }
992
+ } catch (err) {
993
+ showToast('Failed to join: ' + err.message, true);
994
+ }
995
+ });
996
+
997
+ btnCollabSubmitCreate.addEventListener('click', async () => {
998
+ const name = collabCreateName.value.trim();
999
+ if (!name) return;
1000
+ try {
1001
+ const config = await self.BrainClient.getConfig();
1002
+ const res = await fetch(`${config.brainUrl}/api/collab/groups`, {
1003
+ method: 'POST',
1004
+ headers: {
1005
+ 'Content-Type': 'application/json',
1006
+ 'Authorization': `Bearer ${collabToken}`
1007
+ },
1008
+ body: JSON.stringify({ name })
1009
+ });
1010
+ const data = await res.json();
1011
+ if (res.ok) {
1012
+ showToast('Group created!');
1013
+ collabCreateName.value = '';
1014
+ collabCreateGroupRow.classList.add('hidden');
1015
+ await fetchCollabGroups();
1016
+ } else {
1017
+ showToast('Create error: ' + data.error, true);
1018
+ }
1019
+ } catch (err) {
1020
+ showToast('Failed to create group: ' + err.message, true);
1021
+ }
1022
+ });
1023
+
1024
+ collabGroupSelect.addEventListener('change', () => {
1025
+ const activeCode = collabGroupSelect.value;
1026
+ collabSelectedGroup = collabGroups.find(g => g.code === activeCode) || null;
1027
+ fetchCollabAnnotations();
1028
+ });
1029
+
1030
+ btnCollabSaveNote.addEventListener('click', async () => {
1031
+ const text = collabNoteInput.value.trim();
1032
+ if (!text || !collabActiveUrl || !collabSelectedGroup) return;
1033
+ try {
1034
+ const config = await self.BrainClient.getConfig();
1035
+ const res = await fetch(`${config.brainUrl}/api/collab/annotations`, {
1036
+ method: 'POST',
1037
+ headers: {
1038
+ 'Content-Type': 'application/json',
1039
+ 'Authorization': `Bearer ${collabToken}`
1040
+ },
1041
+ body: JSON.stringify({
1042
+ url: collabActiveUrl,
1043
+ groupCode: collabSelectedGroup.code,
1044
+ text
1045
+ })
1046
+ });
1047
+ if (res.ok) {
1048
+ showToast('Note pinned!');
1049
+ collabNoteInput.value = '';
1050
+ fetchCollabAnnotations();
1051
+ } else {
1052
+ const errData = await res.json();
1053
+ showToast('Pin note error: ' + errData.error, true);
1054
+ }
1055
+ } catch (err) {
1056
+ showToast('Failed to pin note: ' + err.message, true);
1057
+ }
1058
+ });
1059
+
1060
+ btnCollabSendChat.addEventListener('click', () => {
1061
+ const text = collabChatInput.value.trim();
1062
+ if (text && collabSocket && collabSocket.readyState === WebSocket.OPEN) {
1063
+ collabSocket.send(JSON.stringify({ type: 'CHAT_MESSAGE', text }));
1064
+ collabChatInput.value = '';
1065
+ }
1066
+ });
1067
+
1068
+ collabChatInput.addEventListener('keydown', (e) => {
1069
+ if (e.key === 'Enter' && !e.shiftKey) {
1070
+ e.preventDefault();
1071
+ btnCollabSendChat.click();
1072
+ }
1073
+ });
1074
+
1075
+ // Listen for tab active changes
1076
+ chrome.tabs.onActivated.addListener(() => {
1077
+ updateCollabActiveUrl();
1078
+ });
1079
+
1080
+ chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
1081
+ if (changeInfo.url) {
1082
+ updateCollabActiveUrl();
1083
+ }
1084
+ });
1085
+
679
1086
  // ---- Event listeners ----
680
1087
  searchInput.addEventListener('input', (e) => debounceSearch(e.target.value));
681
1088
 
@@ -684,6 +1091,7 @@ Provide helpful analysis grounded on both the user's brain memory and this page
684
1091
  loadSettingsState().then(() => {
685
1092
  loadRecentCaptures();
686
1093
  recordRememberInvariants();
1094
+ initCollab();
687
1095
  });
688
1096
 
689
1097
  // Set intervals
@@ -696,3 +1104,5 @@ Provide helpful analysis grounded on both the user's brain memory and this page
696
1104
  }
697
1105
  }, 30000);
698
1106
  })();
1107
+
1108
+
@@ -21,6 +21,26 @@ export default function HealthPage() {
21
21
  const [isUpdating, setIsUpdating] = useState(false)
22
22
  const [showConfirmModal, setShowConfirmModal] = useState(false)
23
23
  const [updateError, setUpdateError] = useState('')
24
+ const [isCheckingUpdate, setIsCheckingUpdate] = useState(false)
25
+ const [checkMessage, setCheckMessage] = useState('')
26
+
27
+ const handleManualCheckUpdate = async () => {
28
+ setIsCheckingUpdate(true)
29
+ setCheckMessage('')
30
+ setUpdateError('')
31
+ try {
32
+ const data = await checkUpdate()
33
+ setUpdateInfo(data)
34
+ if (!data.updateAvailable) {
35
+ setCheckMessage('System is up to date!')
36
+ setTimeout(() => setCheckMessage(''), 3000)
37
+ }
38
+ } catch (e) {
39
+ setUpdateError(`Failed to check updates: ${(e as Error).message}`)
40
+ } finally {
41
+ setIsCheckingUpdate(false)
42
+ }
43
+ }
24
44
 
25
45
  useEffect(() => {
26
46
  let active = true
@@ -168,7 +188,7 @@ export default function HealthPage() {
168
188
  <h1>System Health</h1>
169
189
  <p>Live monitoring of your sovereign brain {lastPoll && <span>· Last poll: {lastPoll.toLocaleTimeString()}</span>}</p>
170
190
  </div>
171
- {updateInfo?.updateAvailable && (
191
+ {updateInfo?.updateAvailable ? (
172
192
  <div className="badge badge-success" style={{
173
193
  display: 'flex',
174
194
  alignItems: 'center',
@@ -199,6 +219,30 @@ export default function HealthPage() {
199
219
  Update Now
200
220
  </button>
201
221
  </div>
222
+ ) : (
223
+ <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
224
+ {checkMessage && (
225
+ <span style={{ color: '#3fb950', fontSize: 14, fontWeight: 500, animation: 'fadeIn 0.3s ease' }}>
226
+ {checkMessage}
227
+ </span>
228
+ )}
229
+ <button
230
+ className="btn btn-secondary"
231
+ disabled={isCheckingUpdate}
232
+ onClick={handleManualCheckUpdate}
233
+ style={{
234
+ fontSize: 13,
235
+ padding: '8px 16px',
236
+ borderRadius: 8,
237
+ cursor: 'pointer',
238
+ display: 'flex',
239
+ alignItems: 'center',
240
+ gap: 6
241
+ }}
242
+ >
243
+ {isCheckingUpdate ? 'Checking...' : 'Check for Updates'}
244
+ </button>
245
+ </div>
202
246
  )}
203
247
  </div>
204
248
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "total-recall-brain",
3
- "version": "3.6.1",
3
+ "version": "3.6.3",
4
4
  "description": "Sovereign AI system — database-free, SSSS-powered personal intelligence engine deployable to any POSIX host.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,7 +8,7 @@ agents:
8
8
 
9
9
  - name: claude
10
10
  binary: claude
11
- flags: --output-format json --permission-mode bypassPermissions
11
+ flags: --output-format json --permission-mode bypassPermissions --setting-sources local --tools ""
12
12
  priority: 3
13
13
  enabled: true
14
14
  exec: flag
@@ -43,7 +43,7 @@ export function findBinaryInPath(binaryName) {
43
43
 
44
44
  const DEFAULT_AGENTS = [
45
45
  { name: 'antigravity', binary: 'antigravity', flags: '--sandbox=false --yolo -o json', priority: 1, enabled: true, exec: 'flag' },
46
- { name: 'claude', binary: 'claude', flags: '--output-format json --permission-mode bypassPermissions', priority: 3, enabled: true, exec: 'flag' },
46
+ { name: 'claude', binary: 'claude', flags: '--output-format json --permission-mode bypassPermissions --setting-sources local --tools ""', priority: 3, enabled: true, exec: 'flag' },
47
47
  { name: 'codex', binary: 'codex', flags: '--full-auto --json --skip-git-repo-check', priority: 4, enabled: true, exec: 'subcommand' },
48
48
  ];
49
49