total-recall-brain 3.5.0 → 3.6.1

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.
@@ -78,6 +78,7 @@ const COMMANDS = {
78
78
  config: 'config.mjs',
79
79
  skill: 'skill.mjs',
80
80
  help: 'help.mjs',
81
+ collab: 'collab.mjs',
81
82
  };
82
83
  function printHelp() {
83
84
  console.log(`
@@ -126,6 +127,8 @@ function printHelp() {
126
127
  Commands: start | stop | status | once | install | uninstall
127
128
  uninstall Completely uninstall all services, launchd agents, VFS, and shims
128
129
  help <topic> Interactive offline documentation, SSSS reference, and system help
130
+ collab Start Express/WebSockets backend & React collab sandbox dev server
131
+
129
132
 
130
133
 
131
134
 
@@ -1,6 +1,8 @@
1
1
  // Total Recall — Content Script
2
2
  // Injected into every page. Queries the brain for related memories and shows
3
3
  // a floating pill in the bottom-right corner when matches are found.
4
+ // Clicking the pill opens an interactive overlay listing the related memories
5
+ // and exposing quick actions (Remember page, Research page).
4
6
  // Uses Shadow DOM to isolate styles from the host page.
5
7
 
6
8
  (function () {
@@ -21,8 +23,9 @@
21
23
  }
22
24
 
23
25
  let pillHost = null;
26
+ let currentMemories = [];
24
27
 
25
- // ---- Create floating pill with Shadow DOM ----
28
+ // ---- Create floating pill and interactive overlay with Shadow DOM ----
26
29
  function createPill(count) {
27
30
  if (pillHost) pillHost.remove();
28
31
 
@@ -42,20 +45,27 @@
42
45
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
43
46
  }
44
47
 
48
+ .container {
49
+ position: relative;
50
+ display: flex;
51
+ flex-direction: column;
52
+ align-items: flex-end;
53
+ }
54
+
45
55
  .pill {
46
56
  display: flex;
47
57
  align-items: center;
48
- gap: 6px;
49
- padding: 8px 14px;
50
- background: #1e1e2e;
58
+ gap: 8px;
59
+ padding: 10px 16px;
60
+ background: #11111b;
51
61
  color: #cdd6f4;
52
62
  border: 1px solid #313244;
53
63
  border-radius: 999px;
54
64
  font-size: 13px;
55
65
  font-weight: 600;
56
66
  cursor: pointer;
57
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
58
- transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
67
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);
68
+ transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1);
59
69
  user-select: none;
60
70
  opacity: 0;
61
71
  transform: translateY(10px);
@@ -65,7 +75,13 @@
65
75
  .pill:hover {
66
76
  border-color: #89b4fa;
67
77
  transform: translateY(-2px);
68
- box-shadow: 0 6px 24px rgba(137, 180, 250, 0.15);
78
+ box-shadow: 0 6px 24px rgba(137, 180, 250, 0.2);
79
+ background: #1e1e2e;
80
+ }
81
+
82
+ .pill.active {
83
+ border-color: #89b4fa;
84
+ background: #1e1e2e;
69
85
  }
70
86
 
71
87
  .pill .brain-icon {
@@ -87,6 +103,195 @@
87
103
  font-weight: 700;
88
104
  }
89
105
 
106
+ /* Interactive Overlay Card */
107
+ .card {
108
+ display: none;
109
+ flex-direction: column;
110
+ width: 320px;
111
+ max-height: 420px;
112
+ background: rgba(17, 17, 27, 0.95);
113
+ backdrop-filter: blur(12px);
114
+ -webkit-backdrop-filter: blur(12px);
115
+ border: 1px solid #313244;
116
+ border-radius: 16px;
117
+ box-shadow: 0 12px 40px rgba(0, 0, 0, 0.6);
118
+ position: absolute;
119
+ bottom: 50px;
120
+ right: 0;
121
+ z-index: 2147483647;
122
+ overflow: hidden;
123
+ transform: scale(0.95) translateY(10px);
124
+ opacity: 0;
125
+ transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1);
126
+ pointer-events: none;
127
+ }
128
+
129
+ .card.visible {
130
+ display: flex;
131
+ transform: scale(1) translateY(0);
132
+ opacity: 1;
133
+ pointer-events: auto;
134
+ }
135
+
136
+ .card-header {
137
+ display: flex;
138
+ align-items: center;
139
+ justify-content: space-between;
140
+ padding: 14px 16px;
141
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
142
+ }
143
+
144
+ .card-header h3 {
145
+ margin: 0;
146
+ font-size: 14px;
147
+ font-weight: 700;
148
+ color: #cdd6f4;
149
+ display: flex;
150
+ align-items: center;
151
+ gap: 6px;
152
+ }
153
+
154
+ .card-header .close-btn {
155
+ background: none;
156
+ border: none;
157
+ color: #a6adc8;
158
+ font-size: 18px;
159
+ cursor: pointer;
160
+ padding: 0;
161
+ line-height: 1;
162
+ display: flex;
163
+ align-items: center;
164
+ justify-content: center;
165
+ width: 20px;
166
+ height: 20px;
167
+ border-radius: 50%;
168
+ transition: background 0.2s;
169
+ }
170
+
171
+ .card-header .close-btn:hover {
172
+ color: #f38ba8;
173
+ background: rgba(255, 255, 255, 0.05);
174
+ }
175
+
176
+ .memory-list {
177
+ flex: 1;
178
+ overflow-y: auto;
179
+ padding: 12px 16px;
180
+ display: flex;
181
+ flex-direction: column;
182
+ gap: 10px;
183
+ }
184
+
185
+ .memory-item {
186
+ background: rgba(255, 255, 255, 0.02);
187
+ border: 1px solid rgba(255, 255, 255, 0.04);
188
+ border-radius: 10px;
189
+ padding: 12px;
190
+ cursor: pointer;
191
+ transition: all 0.2s ease;
192
+ }
193
+
194
+ .memory-item:hover {
195
+ background: rgba(255, 255, 255, 0.05);
196
+ border-color: #89b4fa;
197
+ }
198
+
199
+ .memory-title {
200
+ font-size: 12px;
201
+ font-weight: 600;
202
+ color: #89b4fa;
203
+ margin-bottom: 4px;
204
+ line-height: 1.4;
205
+ }
206
+
207
+ .memory-excerpt {
208
+ font-size: 11px;
209
+ color: #bac2de;
210
+ line-height: 1.45;
211
+ display: -webkit-box;
212
+ -webkit-line-clamp: 3;
213
+ -webkit-box-orient: vertical;
214
+ overflow: hidden;
215
+ word-break: break-word;
216
+ }
217
+
218
+ .memory-excerpt.expanded {
219
+ display: block;
220
+ -webkit-line-clamp: unset;
221
+ }
222
+
223
+ .memory-meta {
224
+ display: flex;
225
+ justify-content: space-between;
226
+ font-size: 9px;
227
+ color: #6c7086;
228
+ margin-top: 8px;
229
+ border-top: 1px solid rgba(255, 255, 255, 0.03);
230
+ padding-top: 6px;
231
+ }
232
+
233
+ .card-actions {
234
+ display: flex;
235
+ gap: 8px;
236
+ padding: 12px 16px;
237
+ border-top: 1px solid rgba(255, 255, 255, 0.05);
238
+ background: rgba(0, 0, 0, 0.2);
239
+ }
240
+
241
+ .action-btn {
242
+ flex: 1;
243
+ padding: 8px 12px;
244
+ border: 1px solid rgba(255, 255, 255, 0.05);
245
+ border-radius: 8px;
246
+ background: rgba(255, 255, 255, 0.04);
247
+ color: #cdd6f4;
248
+ font-size: 11px;
249
+ font-weight: 600;
250
+ cursor: pointer;
251
+ display: flex;
252
+ align-items: center;
253
+ justify-content: center;
254
+ gap: 4px;
255
+ transition: all 0.2s ease;
256
+ }
257
+
258
+ .action-btn:hover {
259
+ background: #89b4fa;
260
+ color: #11111b;
261
+ border-color: #89b4fa;
262
+ }
263
+
264
+ .action-btn:disabled {
265
+ opacity: 0.5;
266
+ cursor: not-allowed;
267
+ background: rgba(255, 255, 255, 0.02) !important;
268
+ color: #6c7086 !important;
269
+ border-color: transparent !important;
270
+ }
271
+
272
+ .toast {
273
+ position: absolute;
274
+ bottom: 60px;
275
+ left: 50%;
276
+ transform: translateX(-50%);
277
+ background: #2ec471;
278
+ color: #11111b;
279
+ padding: 8px 16px;
280
+ border-radius: 8px;
281
+ font-size: 12px;
282
+ font-weight: 700;
283
+ z-index: 2147483647;
284
+ opacity: 0;
285
+ box-shadow: 0 4px 12px rgba(46, 196, 113, 0.3);
286
+ transition: opacity 0.3s ease, transform 0.3s ease;
287
+ pointer-events: none;
288
+ }
289
+
290
+ .toast.visible {
291
+ opacity: 1;
292
+ transform: translateX(-50%) translateY(-5px);
293
+ }
294
+
90
295
  @keyframes pill-enter {
91
296
  to {
92
297
  opacity: 1;
@@ -95,20 +300,157 @@
95
300
  }
96
301
  `;
97
302
 
303
+ const container = document.createElement('div');
304
+ container.className = 'container';
305
+
306
+ // The Floating Pill
98
307
  const pill = document.createElement('div');
99
308
  pill.className = 'pill';
100
309
  pill.innerHTML = `
101
310
  <span class="brain-icon">🧠</span>
102
- <span>${count} related</span>
311
+ <span>Related Memories</span>
103
312
  <span class="count">${count}</span>
104
313
  `;
105
314
 
106
- pill.addEventListener('click', () => {
107
- chrome.runtime.sendMessage({ type: 'OPEN_SIDE_PANEL' });
315
+ // The Overlay Card
316
+ const card = document.createElement('div');
317
+ card.className = 'card';
318
+
319
+ // Header
320
+ const cardHeader = document.createElement('div');
321
+ cardHeader.className = 'card-header';
322
+ cardHeader.innerHTML = `
323
+ <h3>🧠 Related Memories</h3>
324
+ <button class="close-btn" title="Close Panel">×</button>
325
+ `;
326
+ card.appendChild(cardHeader);
327
+
328
+ // List of Memories
329
+ const memoryList = document.createElement('div');
330
+ memoryList.className = 'memory-list';
331
+
332
+ currentMemories.forEach(mem => {
333
+ const item = document.createElement('div');
334
+ item.className = 'memory-item';
335
+
336
+ const relevancePercent = mem.confidence ? Math.round(mem.confidence * 100) : null;
337
+ const relevanceStr = relevancePercent ? `${relevancePercent}% match` : 'Related';
338
+
339
+ item.innerHTML = `
340
+ <div class="memory-title">${mem.title}</div>
341
+ <div class="memory-excerpt">${mem.content}</div>
342
+ <div class="memory-meta">
343
+ <span style="text-transform: capitalize; font-weight: 600;">${mem.category}</span>
344
+ <span>${relevanceStr}</span>
345
+ </div>
346
+ `;
347
+
348
+ // Click to toggle full content expansion
349
+ item.addEventListener('click', () => {
350
+ const excerpt = item.querySelector('.memory-excerpt');
351
+ excerpt.classList.toggle('expanded');
352
+ });
353
+
354
+ memoryList.appendChild(item);
108
355
  });
356
+ card.appendChild(memoryList);
357
+
358
+ // Quick Actions
359
+ const cardActions = document.createElement('div');
360
+ cardActions.className = 'card-actions';
361
+
362
+ const rememberBtn = document.createElement('button');
363
+ rememberBtn.className = 'action-btn';
364
+ rememberBtn.innerHTML = '📌 Remember Page';
365
+
366
+ const researchBtn = document.createElement('button');
367
+ researchBtn.className = 'action-btn';
368
+ researchBtn.innerHTML = '🔬 Research Page';
369
+
370
+ cardActions.appendChild(rememberBtn);
371
+ cardActions.appendChild(researchBtn);
372
+ card.appendChild(cardActions);
373
+
374
+ // Tiny Toast message indicator
375
+ const toast = document.createElement('div');
376
+ toast.className = 'toast';
377
+ container.appendChild(toast);
378
+
379
+ // Action button listeners
380
+ const triggerAction = async (action, btn) => {
381
+ btn.disabled = true;
382
+ const originalText = btn.innerHTML;
383
+ btn.innerHTML = '⏳ Processing...';
384
+
385
+ chrome.runtime.sendMessage({
386
+ type: 'SHARE',
387
+ data: {
388
+ url: pageContext.url,
389
+ title: pageContext.title,
390
+ action: action,
391
+ source: 'chrome-extension-overlay'
392
+ }
393
+ }, (res) => {
394
+ btn.disabled = false;
395
+ btn.innerHTML = originalText;
396
+ if (res && res.success) {
397
+ toast.textContent = action === 'remember' ? '📌 Remembered page!' : '🔬 Research queued!';
398
+ toast.classList.add('visible');
399
+ setTimeout(() => toast.classList.remove('visible'), 2500);
400
+ } else {
401
+ toast.textContent = '❌ Action failed';
402
+ toast.style.background = '#e74c3c';
403
+ toast.style.boxShadow = '0 4px 12px rgba(231, 76, 60, 0.3)';
404
+ toast.classList.add('visible');
405
+ setTimeout(() => {
406
+ toast.classList.remove('visible');
407
+ toast.style.background = '#2ec471';
408
+ toast.style.boxShadow = '0 4px 12px rgba(46, 196, 113, 0.3)';
409
+ }, 2500);
410
+ }
411
+ });
412
+ };
413
+
414
+ rememberBtn.addEventListener('click', (e) => {
415
+ e.stopPropagation();
416
+ triggerAction('remember', rememberBtn);
417
+ });
418
+
419
+ researchBtn.addEventListener('click', (e) => {
420
+ e.stopPropagation();
421
+ triggerAction('research', researchBtn);
422
+ });
423
+
424
+ // Toggle overlay visibility on pill click
425
+ const toggleOverlay = (e) => {
426
+ e.stopPropagation();
427
+ const isVisible = card.classList.contains('visible');
428
+ if (isVisible) {
429
+ card.classList.remove('visible');
430
+ pill.classList.remove('active');
431
+ } else {
432
+ card.classList.add('visible');
433
+ pill.classList.add('active');
434
+ }
435
+ };
436
+
437
+ pill.addEventListener('click', toggleOverlay);
438
+ cardHeader.querySelector('.close-btn').addEventListener('click', toggleOverlay);
439
+
440
+ // Prevent clicks inside card from closing it
441
+ card.addEventListener('click', (e) => e.stopPropagation());
442
+
443
+ // Close overlay if clicking outside the host
444
+ document.addEventListener('click', () => {
445
+ card.classList.remove('visible');
446
+ pill.classList.remove('active');
447
+ });
448
+
449
+ container.appendChild(card);
450
+ container.appendChild(pill);
109
451
 
110
452
  shadow.appendChild(style);
111
- shadow.appendChild(pill);
453
+ shadow.appendChild(container);
112
454
  document.body.appendChild(pillHost);
113
455
  }
114
456
 
@@ -140,6 +482,7 @@
140
482
  if (chrome.runtime.lastError) return;
141
483
  const memories = response?.memories || [];
142
484
  if (memories.length > 0) {
485
+ currentMemories = memories;
143
486
  createPill(memories.length);
144
487
  }
145
488
  }
@@ -36,6 +36,11 @@
36
36
  </div>
37
37
 
38
38
  <div id="status-msg" class="status-msg hidden"></div>
39
+
40
+ <footer class="popup-footer" style="margin-top: 16px; padding-top: 8px; border-top: 1px solid rgba(255, 255, 255, 0.05); display: flex; justify-content: space-between; font-size: 10px; color: #6c7086;">
41
+ <span>Total Recall Brain Connection</span>
42
+ <span id="version-label">v0.1.0</span>
43
+ </footer>
39
44
  </div>
40
45
 
41
46
  <script src="../lib/preconfigured.js"></script>
@@ -26,6 +26,25 @@
26
26
  try {
27
27
  await self.BrainClient.healthCheck();
28
28
  dot.className = 'dot connected';
29
+
30
+ // Check for extension updates
31
+ try {
32
+ const status = await self.BrainClient.brainFetch('/api/extension/status');
33
+ const clientVersion = chrome.runtime.getManifest().version;
34
+ if (status && status.version && status.version !== clientVersion) {
35
+ const config = await self.BrainClient.getConfig();
36
+ const brainUrl = config.brainUrl || 'http://127.0.0.1:3000';
37
+ const footer = document.querySelector('.popup-footer');
38
+ if (footer) {
39
+ footer.innerHTML = `
40
+ <span style="color: #f38ba8; font-weight: bold; display: flex; align-items: center; gap: 4px;">⚠️ Update Available (v${status.version})</span>
41
+ <a href="${brainUrl}/health" target="_blank" style="color: #89b4fa; text-decoration: none; font-weight: bold;">Download ZIP</a>
42
+ `;
43
+ }
44
+ }
45
+ } catch (err) {
46
+ console.error('Failed to check extension update status:', err);
47
+ }
29
48
  } catch {
30
49
  dot.className = 'dot disconnected';
31
50
  }
@@ -147,4 +166,10 @@
147
166
  fillCurrentTab();
148
167
  checkConnection();
149
168
  loadTrackingPref();
169
+
170
+ // Populate version label
171
+ const versionLabel = document.getElementById('version-label');
172
+ if (versionLabel) {
173
+ versionLabel.textContent = 'v' + chrome.runtime.getManifest().version;
174
+ }
150
175
  })();
@@ -18,6 +18,9 @@ import AutomationsPage from './pages/AutomationsPage'
18
18
  import DeploymentsPage from './pages/DeploymentsPage'
19
19
  import SkillsPage from './pages/SkillsPage'
20
20
  import UsagePage from './pages/UsagePage'
21
+ import CollabPage from './pages/CollabPage'
22
+ import HelpPage from './pages/HelpPage'
23
+ import GraphPage from './pages/GraphPage'
21
24
 
22
25
  // ─── Auth state type ──────────────────────────────────────────────────────────
23
26
  type AuthState = 'loading' | 'authed' | 'unauthed'
@@ -143,6 +146,29 @@ function Sidebar({ onLogout, health, activeBrainId, onBrainChange }: SidebarProp
143
146
  </svg>
144
147
  Skills Manager
145
148
  </NavLink>
149
+ <NavLink to="/collab" className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`} id="nav-collab">
150
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
151
+ <path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2" />
152
+ <circle cx="9" cy="7" r="4" />
153
+ <path d="M23 21v-2a4 4 0 00-3-3.87" />
154
+ <path d="M16 3.13a4 4 0 010 7.75" />
155
+ </svg>
156
+ Collaboration
157
+ </NavLink>
158
+ <NavLink to="/help" className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`} id="nav-help">
159
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
160
+ <circle cx="12" cy="12" r="10" />
161
+ <path d="M9.09 9a3 3 0 015.83 1c0 2-3 3-3 3" />
162
+ <line x1="12" y1="17" x2="12.01" y2="17" />
163
+ </svg>
164
+ Documentation
165
+ </NavLink>
166
+ <NavLink to="/graph" className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`} id="nav-graph">
167
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
168
+ <polygon points="12 2 2 22 22 22" />
169
+ </svg>
170
+ Sovereign Graph
171
+ </NavLink>
146
172
  </nav>
147
173
  <div className="sidebar-footer">
148
174
  <div style={{ marginBottom: 8 }}>
@@ -293,6 +319,9 @@ function MainContent({ activeBrainId, onBrainChange }: { activeBrainId: string;
293
319
  <Route path="/keys" element={<ApiKeysPage />} />
294
320
  <Route path="/integrations" element={<IntegrationsPage />} />
295
321
  <Route path="/skills" element={<SkillsPage />} />
322
+ <Route path="/collab" element={<CollabPage />} />
323
+ <Route path="/help" element={<HelpPage />} />
324
+ <Route path="/graph" element={<GraphPage activeBrainId={activeBrainId} />} />
296
325
  </Routes>
297
326
  </div>
298
327
  )}
@@ -705,3 +705,23 @@ export async function runUpdate(): Promise<{ success: boolean; message: string }
705
705
  if (!res.ok) throw new Error(`Run update API error: ${res.status}`)
706
706
  return res.json()
707
707
  }
708
+
709
+ // ─── Help API ──────────────────────────────────────────────────────────────────
710
+
711
+ export interface HelpTopic {
712
+ id: string
713
+ title: string
714
+ description: string
715
+ }
716
+
717
+ export async function fetchHelpTopics(): Promise<{ topics: HelpTopic[] }> {
718
+ const res = await apiFetch(`${API_BASE}/api/help`)
719
+ if (!res.ok) throw new Error('Failed to load help topics')
720
+ return res.json()
721
+ }
722
+
723
+ export async function fetchHelpContent(topicId: string): Promise<{ topic: string; content: string }> {
724
+ const res = await apiFetch(`${API_BASE}/api/help?topic=${encodeURIComponent(topicId)}`)
725
+ if (!res.ok) throw new Error('Failed to load help content')
726
+ return res.json()
727
+ }