total-recall-brain 3.1.2 → 3.2.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.
Files changed (60) hide show
  1. package/extension/background.js +99 -0
  2. package/extension/content-script.css +16 -0
  3. package/extension/content-script.js +134 -0
  4. package/extension/icons/README.md +27 -0
  5. package/extension/icons/icon-128.png +0 -0
  6. package/extension/icons/icon-16.png +0 -0
  7. package/extension/icons/icon-48.png +0 -0
  8. package/extension/lib/brain-client.js +47 -0
  9. package/extension/manifest.json +39 -0
  10. package/extension/options/options.css +218 -0
  11. package/extension/options/options.html +72 -0
  12. package/extension/options/options.js +99 -0
  13. package/extension/popup/popup.css +221 -0
  14. package/extension/popup/popup.html +44 -0
  15. package/extension/popup/popup.js +107 -0
  16. package/extension/sidepanel/sidepanel.css +340 -0
  17. package/extension/sidepanel/sidepanel.html +38 -0
  18. package/extension/sidepanel/sidepanel.js +229 -0
  19. package/frontend/README.md +73 -0
  20. package/frontend/index.html +17 -0
  21. package/frontend/package.json +31 -0
  22. package/frontend/src/App.css +2 -0
  23. package/frontend/src/App.tsx +390 -0
  24. package/frontend/src/api.ts +688 -0
  25. package/frontend/src/assets/hero.png +0 -0
  26. package/frontend/src/assets/react.svg +1 -0
  27. package/frontend/src/assets/vite.svg +1 -0
  28. package/frontend/src/components/BrainSelector.tsx +272 -0
  29. package/frontend/src/components/DaemonLogsTab.tsx +54 -0
  30. package/frontend/src/components/Graph3D.tsx +850 -0
  31. package/frontend/src/components/MarkdownUtils.tsx +99 -0
  32. package/frontend/src/components/ResearchAgendaTab.tsx +707 -0
  33. package/frontend/src/components/TaskQueueTab.tsx +185 -0
  34. package/frontend/src/index.css +1589 -0
  35. package/frontend/src/main.tsx +10 -0
  36. package/frontend/src/pages/ApiKeysPage.tsx +288 -0
  37. package/frontend/src/pages/AutomationsPage.tsx +354 -0
  38. package/frontend/src/pages/ChatPage.tsx +806 -0
  39. package/frontend/src/pages/DeploymentsPage.tsx +275 -0
  40. package/frontend/src/pages/FilesPage.tsx +384 -0
  41. package/frontend/src/pages/HealthPage.tsx +91 -0
  42. package/frontend/src/pages/IntegrationsPage.tsx +382 -0
  43. package/frontend/src/pages/LoginPage.tsx +237 -0
  44. package/frontend/src/pages/MemoryPage.tsx +1192 -0
  45. package/frontend/src/pages/SandboxPage.tsx +113 -0
  46. package/frontend/src/pages/SettingsPage.tsx +662 -0
  47. package/frontend/src/pages/SkillsPage.tsx +618 -0
  48. package/frontend/src/pages/TasksPage.tsx +392 -0
  49. package/frontend/src/pages/UsagePage.tsx +260 -0
  50. package/frontend/src/types.ts +163 -0
  51. package/frontend/tsconfig.app.json +25 -0
  52. package/frontend/tsconfig.json +7 -0
  53. package/frontend/tsconfig.node.json +24 -0
  54. package/frontend/vite.config.ts +24 -0
  55. package/package.json +8 -1
  56. package/scaffold/.agent/skills/total-recall/memory-vault/concepts/cli-help-reference.md +35 -8
  57. package/scaffold/.agent/skills/total-recall/memory-vault/invariants/operating-instructions.md +13 -8
  58. package/scaffold/.agent/skills/total-recall/memory-vault/preferences/always-websearch-gap.md +28 -1
  59. package/scaffold/.agent/skills/total-recall/memory-vault/preferences/topic-research-sop.md +28 -1
  60. package/scaffold/.agent/skills/total-recall/skills/ssss/references/ssss-spec.md +830 -81
@@ -0,0 +1,99 @@
1
+ // Total Recall — Background Service Worker
2
+ // Handles context menus, message routing, and passive browsing buffer.
3
+
4
+ importScripts('lib/brain-client.js');
5
+
6
+ // Destructure for convenience after importScripts populates self.BrainClient
7
+ const { share, search, healthCheck } = self.BrainClient;
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // Context menu registration
11
+ // ---------------------------------------------------------------------------
12
+ chrome.runtime.onInstalled.addListener(() => {
13
+ chrome.contextMenus.create({
14
+ id: 'send-to-brain',
15
+ title: 'Send to Brain',
16
+ contexts: ['page']
17
+ });
18
+ chrome.contextMenus.create({
19
+ id: 'remember-this',
20
+ title: 'Remember This',
21
+ contexts: ['selection']
22
+ });
23
+ chrome.contextMenus.create({
24
+ id: 'research-this',
25
+ title: 'Research This',
26
+ contexts: ['selection']
27
+ });
28
+ });
29
+
30
+ // ---------------------------------------------------------------------------
31
+ // Context menu click handler
32
+ // ---------------------------------------------------------------------------
33
+ chrome.contextMenus.onClicked.addListener(async (info, tab) => {
34
+ try {
35
+ if (info.menuItemId === 'send-to-brain') {
36
+ await share({
37
+ url: tab.url,
38
+ title: tab.title,
39
+ action: 'research',
40
+ source: 'chrome-extension'
41
+ });
42
+ showBadge('\u2713', '#22c55e');
43
+ } else if (info.menuItemId === 'remember-this') {
44
+ await share({
45
+ url: tab.url,
46
+ title: tab.title,
47
+ excerpt: info.selectionText,
48
+ action: 'remember',
49
+ source: 'chrome-extension'
50
+ });
51
+ showBadge('\u2713', '#22c55e');
52
+ } else if (info.menuItemId === 'research-this') {
53
+ await share({
54
+ excerpt: info.selectionText,
55
+ action: 'research',
56
+ source: 'chrome-extension'
57
+ });
58
+ showBadge('\u2713', '#22c55e');
59
+ }
60
+ } catch (err) {
61
+ console.error('Brain API error:', err);
62
+ showBadge('!', '#ef4444');
63
+ }
64
+ });
65
+
66
+ // ---------------------------------------------------------------------------
67
+ // Badge helper
68
+ // ---------------------------------------------------------------------------
69
+ function showBadge(text, color) {
70
+ chrome.action.setBadgeText({ text });
71
+ chrome.action.setBadgeBackgroundColor({ color });
72
+ setTimeout(() => chrome.action.setBadgeText({ text: '' }), 3000);
73
+ }
74
+
75
+ // ---------------------------------------------------------------------------
76
+ // Message listener (popup, content-script, sidepanel → background)
77
+ // ---------------------------------------------------------------------------
78
+ chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
79
+ if (msg.type === 'QUERY_BRAIN') {
80
+ search(msg.query, msg.topK || 3)
81
+ .then(data => sendResponse({ memories: data.results || [] }))
82
+ .catch(err => sendResponse({ memories: [], error: err.message }));
83
+ return true; // keep channel open for async response
84
+ }
85
+
86
+ if (msg.type === 'SHARE') {
87
+ share(msg.data)
88
+ .then(data => sendResponse({ success: true, ...data }))
89
+ .catch(err => sendResponse({ success: false, error: err.message }));
90
+ return true;
91
+ }
92
+
93
+ if (msg.type === 'HEALTH_CHECK') {
94
+ healthCheck()
95
+ .then(data => sendResponse({ connected: true, ...data }))
96
+ .catch(() => sendResponse({ connected: false }));
97
+ return true;
98
+ }
99
+ });
@@ -0,0 +1,16 @@
1
+ /* Total Recall — Content Script Styles
2
+ * The floating pill uses Shadow DOM for style isolation,
3
+ * so this file only contains the host-level positioning.
4
+ * Keeping it minimal to avoid conflicts with host pages. */
5
+
6
+ #total-recall-pill-host {
7
+ /* Position is handled inline via Shadow DOM :host styles.
8
+ * This rule serves as a fallback and ensures the element
9
+ * doesn't inherit unintended styles from the host page. */
10
+ all: initial;
11
+ position: fixed !important;
12
+ bottom: 20px !important;
13
+ right: 20px !important;
14
+ z-index: 2147483647 !important;
15
+ pointer-events: auto !important;
16
+ }
@@ -0,0 +1,134 @@
1
+ // Total Recall — Content Script
2
+ // Injected into every page. Queries the brain for related memories and shows
3
+ // a floating pill in the bottom-right corner when matches are found.
4
+ // Uses Shadow DOM to isolate styles from the host page.
5
+
6
+ (function () {
7
+ 'use strict';
8
+
9
+ // ---- Capture page context ----
10
+ const pageContext = {
11
+ url: location.href,
12
+ title: document.title,
13
+ description: document.querySelector('meta[name="description"]')?.content || ''
14
+ };
15
+
16
+ // ---- Skip non-content pages ----
17
+ if (pageContext.url.startsWith('chrome://') ||
18
+ pageContext.url.startsWith('chrome-extension://') ||
19
+ pageContext.url.startsWith('about:')) {
20
+ return;
21
+ }
22
+
23
+ let pillHost = null;
24
+
25
+ // ---- Create floating pill with Shadow DOM ----
26
+ function createPill(count) {
27
+ if (pillHost) pillHost.remove();
28
+
29
+ pillHost = document.createElement('div');
30
+ pillHost.id = 'total-recall-pill-host';
31
+
32
+ const shadow = pillHost.attachShadow({ mode: 'closed' });
33
+
34
+ const style = document.createElement('style');
35
+ style.textContent = `
36
+ :host {
37
+ all: initial;
38
+ position: fixed;
39
+ bottom: 20px;
40
+ right: 20px;
41
+ z-index: 2147483647;
42
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
43
+ }
44
+
45
+ .pill {
46
+ display: flex;
47
+ align-items: center;
48
+ gap: 6px;
49
+ padding: 8px 14px;
50
+ background: #1e1e2e;
51
+ color: #cdd6f4;
52
+ border: 1px solid #313244;
53
+ border-radius: 999px;
54
+ font-size: 13px;
55
+ font-weight: 600;
56
+ 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;
59
+ user-select: none;
60
+ opacity: 0;
61
+ transform: translateY(10px);
62
+ animation: pill-enter 0.3s ease forwards;
63
+ }
64
+
65
+ .pill:hover {
66
+ border-color: #89b4fa;
67
+ transform: translateY(-2px);
68
+ box-shadow: 0 6px 24px rgba(137, 180, 250, 0.15);
69
+ }
70
+
71
+ .pill .brain-icon {
72
+ font-size: 15px;
73
+ line-height: 1;
74
+ }
75
+
76
+ .pill .count {
77
+ display: inline-flex;
78
+ align-items: center;
79
+ justify-content: center;
80
+ min-width: 18px;
81
+ height: 18px;
82
+ padding: 0 5px;
83
+ background: #89b4fa;
84
+ color: #11111b;
85
+ border-radius: 999px;
86
+ font-size: 11px;
87
+ font-weight: 700;
88
+ }
89
+
90
+ @keyframes pill-enter {
91
+ to {
92
+ opacity: 1;
93
+ transform: translateY(0);
94
+ }
95
+ }
96
+ `;
97
+
98
+ const pill = document.createElement('div');
99
+ pill.className = 'pill';
100
+ pill.innerHTML = `
101
+ <span class="brain-icon">🧠</span>
102
+ <span>${count} related</span>
103
+ <span class="count">${count}</span>
104
+ `;
105
+
106
+ pill.addEventListener('click', () => {
107
+ chrome.runtime.sendMessage({ type: 'OPEN_SIDE_PANEL' });
108
+ });
109
+
110
+ shadow.appendChild(style);
111
+ shadow.appendChild(pill);
112
+ document.body.appendChild(pillHost);
113
+ }
114
+
115
+ // ---- Query brain after delay ----
116
+ function queryBrain() {
117
+ const query = pageContext.title || pageContext.url;
118
+ if (!query) return;
119
+
120
+ chrome.runtime.sendMessage(
121
+ { type: 'QUERY_BRAIN', query, topK: 5 },
122
+ (response) => {
123
+ if (chrome.runtime.lastError) return;
124
+ const memories = response?.memories || [];
125
+ if (memories.length > 0) {
126
+ createPill(memories.length);
127
+ }
128
+ }
129
+ );
130
+ }
131
+
132
+ // Wait 2 seconds after page load to avoid interfering with page rendering
133
+ setTimeout(queryBrain, 2000);
134
+ })();
@@ -0,0 +1,27 @@
1
+ # Extension Icons
2
+
3
+ This directory should contain the following PNG icon files for the Total Recall Brain Extension:
4
+
5
+ | File | Size | Usage |
6
+ |-----------------|---------|---------------------------------------------|
7
+ | `icon-16.png` | 16×16 | Favicon, toolbar (small) |
8
+ | `icon-48.png` | 48×48 | Extensions management page |
9
+ | `icon-128.png` | 128×128 | Chrome Web Store, install dialog |
10
+
11
+ ## Requirements
12
+
13
+ - Format: PNG with transparency
14
+ - Design: Should match Total Recall branding (brain / recall motif)
15
+ - The manifest references these exact filenames — do not rename without updating `manifest.json`
16
+
17
+ ## Generating placeholder icons
18
+
19
+ You can generate minimal placeholder PNGs with ImageMagick:
20
+
21
+ ```bash
22
+ convert -size 16x16 xc:'#6366f1' icon-16.png
23
+ convert -size 48x48 xc:'#6366f1' icon-48.png
24
+ convert -size 128x128 xc:'#6366f1' icon-128.png
25
+ ```
26
+
27
+ Replace these with proper branded icons before publishing.
Binary file
Binary file
Binary file
@@ -0,0 +1,47 @@
1
+ // Brain API Client for Total Recall Chrome Extension
2
+ // Shared by background.js, popup, sidepanel, and options page.
3
+ // Exposed as `self.BrainClient` namespace (no ES-module bundler required).
4
+
5
+ const DEFAULT_BRAIN_URL = 'http://127.0.0.1:3000';
6
+
7
+ async function getConfig() {
8
+ const config = await chrome.storage.sync.get(['brainUrl', 'pat']);
9
+ return {
10
+ brainUrl: config.brainUrl || DEFAULT_BRAIN_URL,
11
+ pat: config.pat || ''
12
+ };
13
+ }
14
+
15
+ async function brainFetch(path, options = {}) {
16
+ const { brainUrl, pat } = await getConfig();
17
+ const url = `${brainUrl}${path}`;
18
+ const headers = { 'Content-Type': 'application/json', ...options.headers };
19
+ if (pat) headers['Authorization'] = `Bearer ${pat}`;
20
+
21
+ const res = await fetch(url, { ...options, headers });
22
+ if (res.status === 401) {
23
+ chrome.action.setBadgeText({ text: '!' });
24
+ chrome.action.setBadgeBackgroundColor({ color: '#ef4444' });
25
+ throw new Error('Authentication failed — check your PAT token');
26
+ }
27
+ if (!res.ok) throw new Error(`Brain API error: ${res.status}`);
28
+ return res.json();
29
+ }
30
+
31
+ async function share(data) {
32
+ return brainFetch('/api/share', { method: 'POST', body: JSON.stringify(data) });
33
+ }
34
+
35
+ async function search(query, topK = 3) {
36
+ return brainFetch('/api/memory/search/semantic', {
37
+ method: 'POST',
38
+ body: JSON.stringify({ query, top_k: topK })
39
+ });
40
+ }
41
+
42
+ async function healthCheck() {
43
+ return brainFetch('/health');
44
+ }
45
+
46
+ // Expose as a global namespace for non-module contexts (service worker, popup, etc.)
47
+ self.BrainClient = { getConfig, brainFetch, share, search, healthCheck };
@@ -0,0 +1,39 @@
1
+ {
2
+ "manifest_version": 3,
3
+ "name": "Total Recall — Brain Extension",
4
+ "version": "0.1.0",
5
+ "description": "Contextual memory overlay, Share-to-Brain, and passive browsing context for Total Recall",
6
+ "permissions": ["activeTab", "contextMenus", "sidePanel", "storage"],
7
+ "optional_permissions": ["history", "webNavigation"],
8
+ "host_permissions": ["http://127.0.0.1:*/*", "http://localhost:*/*"],
9
+ "background": {
10
+ "service_worker": "background.js"
11
+ },
12
+ "action": {
13
+ "default_popup": "popup/popup.html",
14
+ "default_icon": {
15
+ "16": "icons/icon-16.png",
16
+ "48": "icons/icon-48.png",
17
+ "128": "icons/icon-128.png"
18
+ }
19
+ },
20
+ "side_panel": {
21
+ "default_path": "sidepanel/sidepanel.html"
22
+ },
23
+ "content_scripts": [{
24
+ "matches": ["<all_urls>"],
25
+ "js": ["content-script.js"],
26
+ "css": ["content-script.css"],
27
+ "run_at": "document_idle"
28
+ }],
29
+ "options_ui": {
30
+ "page": "options/options.html",
31
+ "open_in_tab": true
32
+ },
33
+ "incognito": "not_allowed",
34
+ "icons": {
35
+ "16": "icons/icon-16.png",
36
+ "48": "icons/icon-48.png",
37
+ "128": "icons/icon-128.png"
38
+ }
39
+ }
@@ -0,0 +1,218 @@
1
+ /* Total Recall — Options Page Styles (Catppuccin Mocha) */
2
+
3
+ * {
4
+ margin: 0;
5
+ padding: 0;
6
+ box-sizing: border-box;
7
+ }
8
+
9
+ body {
10
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
11
+ background: #11111b;
12
+ color: #cdd6f4;
13
+ min-height: 100vh;
14
+ }
15
+
16
+ .options-page {
17
+ max-width: 560px;
18
+ margin: 0 auto;
19
+ padding: 32px 24px;
20
+ }
21
+
22
+ /* ---- Header ---- */
23
+ .options-header {
24
+ margin-bottom: 32px;
25
+ }
26
+
27
+ .options-header h1 {
28
+ font-size: 20px;
29
+ font-weight: 700;
30
+ }
31
+
32
+ /* ---- Sections ---- */
33
+ .section {
34
+ margin-bottom: 28px;
35
+ padding: 20px;
36
+ background: #181825;
37
+ border-radius: 14px;
38
+ border: 1px solid #313244;
39
+ }
40
+
41
+ .section h2 {
42
+ font-size: 14px;
43
+ font-weight: 700;
44
+ text-transform: uppercase;
45
+ letter-spacing: 0.8px;
46
+ color: #89b4fa;
47
+ margin-bottom: 16px;
48
+ }
49
+
50
+ /* ---- Fields ---- */
51
+ .field-group {
52
+ margin-bottom: 16px;
53
+ }
54
+
55
+ .field-group:last-child {
56
+ margin-bottom: 0;
57
+ }
58
+
59
+ .field-group label {
60
+ display: block;
61
+ font-size: 13px;
62
+ font-weight: 600;
63
+ color: #a6adc8;
64
+ margin-bottom: 6px;
65
+ }
66
+
67
+ .field-group .hint {
68
+ font-weight: 400;
69
+ color: #585b70;
70
+ font-size: 11px;
71
+ }
72
+
73
+ .field-group input[type="text"],
74
+ .field-group input[type="password"],
75
+ .field-group textarea {
76
+ width: 100%;
77
+ padding: 10px 14px;
78
+ background: #1e1e2e;
79
+ border: 1px solid #313244;
80
+ border-radius: 10px;
81
+ color: #cdd6f4;
82
+ font-size: 14px;
83
+ outline: none;
84
+ font-family: inherit;
85
+ transition: border-color 0.2s ease;
86
+ }
87
+
88
+ .field-group input:focus,
89
+ .field-group textarea:focus {
90
+ border-color: #89b4fa;
91
+ }
92
+
93
+ .field-group textarea {
94
+ resize: vertical;
95
+ min-height: 80px;
96
+ font-family: 'SF Mono', Menlo, Monaco, monospace;
97
+ font-size: 13px;
98
+ line-height: 1.6;
99
+ }
100
+
101
+ /* ---- Connection test ---- */
102
+ .connection-row {
103
+ display: flex;
104
+ align-items: center;
105
+ gap: 12px;
106
+ }
107
+
108
+ .connection-status {
109
+ font-size: 13px;
110
+ font-weight: 600;
111
+ transition: color 0.3s ease;
112
+ }
113
+
114
+ .connection-status.ok {
115
+ color: #a6e3a1;
116
+ }
117
+
118
+ .connection-status.fail {
119
+ color: #f38ba8;
120
+ }
121
+
122
+ /* ---- Radio group ---- */
123
+ .radio-group {
124
+ display: flex;
125
+ flex-direction: column;
126
+ gap: 8px;
127
+ }
128
+
129
+ .radio-label {
130
+ display: flex;
131
+ align-items: center;
132
+ gap: 10px;
133
+ padding: 10px 14px;
134
+ background: #1e1e2e;
135
+ border: 1px solid #313244;
136
+ border-radius: 10px;
137
+ cursor: pointer;
138
+ transition: border-color 0.2s ease;
139
+ }
140
+
141
+ .radio-label:hover {
142
+ border-color: #45475a;
143
+ }
144
+
145
+ .radio-label input[type="radio"] {
146
+ accent-color: #89b4fa;
147
+ width: 16px;
148
+ height: 16px;
149
+ }
150
+
151
+ .radio-text {
152
+ font-size: 13px;
153
+ font-weight: 600;
154
+ color: #cdd6f4;
155
+ min-width: 60px;
156
+ }
157
+
158
+ .radio-desc {
159
+ font-size: 12px;
160
+ color: #585b70;
161
+ }
162
+
163
+ /* ---- Buttons ---- */
164
+ .btn {
165
+ padding: 10px 20px;
166
+ font-size: 13px;
167
+ font-weight: 600;
168
+ border: 1px solid #313244;
169
+ border-radius: 10px;
170
+ cursor: pointer;
171
+ transition: background 0.2s ease, border-color 0.2s ease, transform 0.1s ease;
172
+ }
173
+
174
+ .btn:active {
175
+ transform: scale(0.97);
176
+ }
177
+
178
+ .btn.primary {
179
+ background: #89b4fa;
180
+ color: #11111b;
181
+ border-color: #89b4fa;
182
+ }
183
+
184
+ .btn.primary:hover {
185
+ background: #74c7ec;
186
+ border-color: #74c7ec;
187
+ }
188
+
189
+ .btn.secondary {
190
+ background: #1e1e2e;
191
+ color: #cdd6f4;
192
+ }
193
+
194
+ .btn.secondary:hover {
195
+ background: #313244;
196
+ border-color: #89b4fa;
197
+ }
198
+
199
+ /* ---- Actions footer ---- */
200
+ .actions {
201
+ display: flex;
202
+ align-items: center;
203
+ gap: 14px;
204
+ }
205
+
206
+ .save-status {
207
+ font-size: 13px;
208
+ font-weight: 600;
209
+ transition: opacity 0.3s ease;
210
+ }
211
+
212
+ .save-status.saved {
213
+ color: #a6e3a1;
214
+ }
215
+
216
+ .save-status.error {
217
+ color: #f38ba8;
218
+ }
@@ -0,0 +1,72 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Total Recall — Settings</title>
6
+ <link rel="stylesheet" href="options.css">
7
+ </head>
8
+ <body>
9
+ <div class="options-page">
10
+ <header class="options-header">
11
+ <h1>🧠 Total Recall — Settings</h1>
12
+ </header>
13
+
14
+ <section class="section">
15
+ <h2>Connection</h2>
16
+
17
+ <div class="field-group">
18
+ <label for="brain-url">Brain URL</label>
19
+ <input type="text" id="brain-url" placeholder="http://127.0.0.1:3000" />
20
+ </div>
21
+
22
+ <div class="field-group">
23
+ <label for="pat-token">PAT Token</label>
24
+ <input type="password" id="pat-token" placeholder="Enter your personal access token" />
25
+ </div>
26
+
27
+ <div class="connection-row">
28
+ <button id="btn-test" class="btn secondary">Test Connection</button>
29
+ <div id="connection-status" class="connection-status"></div>
30
+ </div>
31
+ </section>
32
+
33
+ <section class="section">
34
+ <h2>Privacy</h2>
35
+
36
+ <div class="field-group">
37
+ <label for="blocklist">Domain Blocklist <span class="hint">(one per line)</span></label>
38
+ <textarea id="blocklist" rows="5" placeholder="example.com&#10;private.internal.io"></textarea>
39
+ </div>
40
+
41
+ <div class="field-group">
42
+ <label>Capture Granularity</label>
43
+ <div class="radio-group">
44
+ <label class="radio-label">
45
+ <input type="radio" name="granularity" value="off" />
46
+ <span class="radio-text">Off</span>
47
+ <span class="radio-desc">No passive tracking</span>
48
+ </label>
49
+ <label class="radio-label">
50
+ <input type="radio" name="granularity" value="minimal" />
51
+ <span class="radio-text">Minimal</span>
52
+ <span class="radio-desc">URL and title only</span>
53
+ </label>
54
+ <label class="radio-label">
55
+ <input type="radio" name="granularity" value="full" />
56
+ <span class="radio-text">Full</span>
57
+ <span class="radio-desc">URL, title, and page metadata</span>
58
+ </label>
59
+ </div>
60
+ </div>
61
+ </section>
62
+
63
+ <div class="actions">
64
+ <button id="btn-save" class="btn primary">Save Settings</button>
65
+ <span id="save-status" class="save-status"></span>
66
+ </div>
67
+ </div>
68
+
69
+ <script src="../lib/brain-client.js"></script>
70
+ <script src="options.js"></script>
71
+ </body>
72
+ </html>