total-recall-brain 3.28.2 → 3.30.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 (132) hide show
  1. package/bin/total-recall.mjs +24 -1
  2. package/docs/ARCHITECTURE.md +26 -18
  3. package/extension/background.js +259 -84
  4. package/extension/content-script.css +7 -13
  5. package/extension/content-script.js +252 -477
  6. package/extension/icons/icon-128.png +0 -0
  7. package/extension/icons/icon-16.png +0 -0
  8. package/extension/icons/icon-32.png +0 -0
  9. package/extension/icons/icon-48.png +0 -0
  10. package/extension/icons/mark.svg +6 -0
  11. package/extension/lib/brain-client.js +160 -73
  12. package/extension/lib/tokens.css +187 -0
  13. package/extension/lib/ui-helpers.js +229 -0
  14. package/extension/lib/ui-helpers.spec.mjs +94 -0
  15. package/extension/manifest.json +20 -5
  16. package/extension/options/options.css +64 -218
  17. package/extension/options/options.html +91 -43
  18. package/extension/options/options.js +208 -85
  19. package/extension/sidepanel/sidepanel.css +268 -970
  20. package/extension/sidepanel/sidepanel.html +200 -166
  21. package/extension/sidepanel/sidepanel.js +788 -942
  22. package/frontend/dist/assets/index-DGMNZi_1.js +5742 -0
  23. package/frontend/dist/index.html +1 -1
  24. package/frontend/src/api/plugins.ts +124 -92
  25. package/frontend/src/components/ResearchAgendaTab.spec.tsx +11 -0
  26. package/frontend/src/components/ResearchAgendaTab.tsx +19 -14
  27. package/frontend/src/pages/PluginsPage.spec.tsx +87 -16
  28. package/frontend/src/pages/PluginsPage.tsx +454 -1218
  29. package/package.json +2 -1
  30. package/plugins/code-quality/README.md +25 -0
  31. package/plugins/code-quality/cli.mjs +25 -0
  32. package/plugins/code-quality/plugin.json +17 -0
  33. package/plugins/git-sentinel/README.md +27 -0
  34. package/{scaffold/.agent/plugins → plugins}/git-sentinel/cli.mjs +8 -5
  35. package/plugins/git-sentinel/plugin.json +18 -0
  36. package/plugins/system-monitor/README.md +26 -0
  37. package/{scaffold/.agent/plugins → plugins}/system-monitor/cli.mjs +5 -4
  38. package/{scaffold/.agent/plugins → plugins}/system-monitor/generator.mjs +4 -5
  39. package/plugins/system-monitor/plugin.json +29 -0
  40. package/scaffold/.agent/skills/meta-harness/SKILL.md +4 -4
  41. package/scaffold/.agent/skills/plugins/SKILL.md +53 -0
  42. package/scaffold/.agent/skills/total-recall/scripts/brain-state.json +1 -0
  43. package/src/cli/agent.mjs +1 -1
  44. package/src/cli/app/index.mjs +493 -0
  45. package/src/cli/brain.mjs +33 -3
  46. package/src/cli/command.mjs +80 -41
  47. package/src/cli/harness.mjs +1 -1
  48. package/src/cli/init.mjs +3 -1
  49. package/src/cli/mesh.mjs +118 -12
  50. package/src/cli/plugin/create.mjs +144 -25
  51. package/src/cli/plugin/index.mjs +83 -53
  52. package/src/cli/plugin/install.mjs +10 -127
  53. package/src/cli/plugin/list.mjs +44 -44
  54. package/src/cli/plugin/remove.mjs +23 -30
  55. package/src/cli/plugin/scaffold-cmd.mjs +30 -0
  56. package/src/cli/plugin/search.mjs +108 -23
  57. package/src/cli/rebuild.mjs +37 -1
  58. package/src/cli/recall.mjs +17 -2
  59. package/src/cli/research.mjs +4 -6
  60. package/src/cli/share.mjs +2 -0
  61. package/src/core/app-deploy/apply.mjs +386 -0
  62. package/src/core/app-deploy/design-tokens.mjs +217 -0
  63. package/src/core/app-deploy/plan.mjs +249 -0
  64. package/src/core/app-deploy/resolve.mjs +295 -0
  65. package/src/core/app-deploy/source.mjs +337 -0
  66. package/src/core/app-deploy/standalone.mjs +248 -0
  67. package/src/core/app-deploy/upgrade.mjs +256 -0
  68. package/src/core/app-deploy/verify.mjs +213 -0
  69. package/src/core/bound-hosts.mjs +1 -1
  70. package/src/core/brain-registry.mjs +146 -0
  71. package/src/core/brain-state.json +1 -0
  72. package/src/core/daemon-loop.mjs +30 -26
  73. package/src/core/dream.mjs +5 -17
  74. package/src/core/fact-seeker.mjs +53 -368
  75. package/src/core/lan-discovery.mjs +3 -3
  76. package/src/core/mesh-access-sync.mjs +134 -0
  77. package/src/core/mesh-access.mjs +18 -3
  78. package/src/core/mesh-discover.mjs +173 -0
  79. package/src/core/mesh.mjs +131 -32
  80. package/src/core/optimizer.mjs +4 -41
  81. package/src/core/plugin-bundle.mjs +167 -0
  82. package/src/core/plugin-context.mjs +8 -13
  83. package/src/core/plugin-loader.mjs +272 -68
  84. package/src/core/plugin-peers.mjs +163 -0
  85. package/src/core/plugin-public.mjs +85 -0
  86. package/src/core/plugin-runner-child.mjs +24 -0
  87. package/src/core/plugin-runner.mjs +102 -0
  88. package/src/core/plugin-store.mjs +384 -0
  89. package/src/core/plugin-tasks.mjs +212 -0
  90. package/src/core/post-mortem.mjs +40 -9
  91. package/src/core/research-gate.mjs +182 -0
  92. package/src/core/research-queue.mjs +47 -7
  93. package/src/core/research-surface.mjs +211 -0
  94. package/src/core/research.mjs +25 -32
  95. package/src/core/scheduler.mjs +67 -153
  96. package/src/core/schema.mjs +129 -0
  97. package/src/core/search.mjs +12 -1
  98. package/src/core/secret-integration-research.mjs +12 -8
  99. package/src/core/secrets-store.mjs +32 -2
  100. package/src/core/session-watcher.mjs +6 -0
  101. package/src/core/surface.mjs +64 -3
  102. package/src/core/task-executors.mjs +8 -45
  103. package/src/core/vault-cache.mjs +70 -4
  104. package/src/server/api.mjs +21 -0
  105. package/src/server/rest.mjs +2 -0
  106. package/src/server/route-manifest.json +21 -5
  107. package/src/server/routes/collab.mjs +38 -11
  108. package/src/server/routes/extension.mjs +3 -2
  109. package/src/server/routes/memory.mjs +24 -2
  110. package/src/server/routes/mesh.mjs +7 -7
  111. package/src/server/routes/plugins-mesh.mjs +49 -0
  112. package/src/server/routes/plugins.mjs +91 -426
  113. package/src/server/routes/research.mjs +5 -2
  114. package/src/server/routes/share.mjs +2 -0
  115. package/src/server/tools.mjs +29 -2
  116. package/extension/icons/README.md +0 -27
  117. package/extension/lib/preconfigured.js +0 -5
  118. package/extension/popup/popup.css +0 -227
  119. package/extension/popup/popup.html +0 -50
  120. package/extension/popup/popup.js +0 -175
  121. package/frontend/dist/assets/index-U7o-qU99.js +0 -5742
  122. package/scaffold/.agent/plugins/.gitkeep +0 -0
  123. package/scaffold/.agent/plugins/code-quality/README.md +0 -16
  124. package/scaffold/.agent/plugins/code-quality/cli.mjs +0 -15
  125. package/scaffold/.agent/plugins/code-quality/plugin.json +0 -34
  126. package/scaffold/.agent/plugins/git-sentinel/README.md +0 -16
  127. package/scaffold/.agent/plugins/git-sentinel/plugin.json +0 -34
  128. package/scaffold/.agent/plugins/meta-harness/README.md +0 -16
  129. package/scaffold/.agent/plugins/meta-harness/cli.mjs +0 -34
  130. package/scaffold/.agent/plugins/meta-harness/plugin.json +0 -34
  131. package/scaffold/.agent/plugins/system-monitor/README.md +0 -16
  132. package/scaffold/.agent/plugins/system-monitor/plugin.json +0 -38
@@ -79,6 +79,8 @@ router.post('/api/share', requireAuth, requireScope('memory:write'), async (req,
79
79
  topic,
80
80
  priority: 'medium',
81
81
  notes: excerpt || '',
82
+ origin: 'user',
83
+ requested_via: String(source || '').includes('extension') ? 'extension' : 'share',
82
84
  });
83
85
 
84
86
  return res.status(201).json({
@@ -524,9 +524,35 @@ export async function meshMintPreAuthKey(options = {}) {
524
524
  }
525
525
  }
526
526
 
527
+ /**
528
+ * The user asked, in chat, for background research. Recorded as a user request
529
+ * (runs ahead of autonomous research, no budget applies).
530
+ */
531
+ export async function queueResearch(topic, notes) {
532
+ const { requestResearch } = await import('../core/research-gate.mjs');
533
+ const item = requestResearch({ topic, notes, via: 'chat' });
534
+ const state = item.status === 'done' ? 'already researched' : item.status === 'pending' ? 'queued' : item.status;
535
+ return JSON.stringify({ id: item.id, topic: item.topic, status: state, note: 'Runs in the background; findings will appear in memory and future context.' });
536
+ }
537
+
527
538
  // ─── Tool Definitions ─────────────────────────────────────────────────────────
528
539
 
529
540
  export const AVAILABLE_TOOLS = [
541
+ {
542
+ type: 'function',
543
+ function: {
544
+ name: 'queue_research',
545
+ description: 'Queue deep background research on a topic. Use ONLY when the user explicitly asks you to research something in depth or to look into it later. Returns immediately; the findings land in memory and show up in future context. For an answer right now, use search_web instead.',
546
+ parameters: {
547
+ type: 'object',
548
+ properties: {
549
+ topic: { type: 'string', description: 'Specific, searchable topic, e.g. "Stripe Node SDK v17 webhook signature verification".' },
550
+ notes: { type: 'string', description: 'Optional: what the user wants to learn and why.' },
551
+ },
552
+ required: ['topic'],
553
+ },
554
+ },
555
+ },
530
556
  {
531
557
  type: 'function',
532
558
  function: {
@@ -787,7 +813,7 @@ export const AVAILABLE_TOOLS = [
787
813
  parameters: {
788
814
  type: 'object',
789
815
  properties: {
790
- node: { type: 'string', description: 'Target node hostname, slug, or mesh IP (e.g. "macmini", "cloud", "100.64.0.2").' },
816
+ node: { type: 'string', description: 'Target node hostname, slug, or mesh IP (e.g. "build-box", "cloud", "100.64.0.10").' },
791
817
  command: { type: 'string', description: 'Shell command to execute on the remote node (e.g. "uptime", "git status", "docker ps").' },
792
818
  timeoutMs: { type: 'integer', description: 'Timeout in milliseconds (default 60000).' },
793
819
  },
@@ -804,7 +830,7 @@ export const AVAILABLE_TOOLS = [
804
830
  type: 'object',
805
831
  properties: {
806
832
  node: { type: 'string', description: 'Target node hostname, slug, or mesh IP.' },
807
- ssh_user: { type: 'string', description: 'Username to log in as (e.g. "greg", "root", "gregoryiteen").' },
833
+ ssh_user: { type: 'string', description: 'Username to log in as (e.g. "admin", "root", "deploy").' },
808
834
  ssh_port: { type: 'integer', description: 'SSH port (default 22).' },
809
835
  identity_file: { type: 'string', description: 'Path to private SSH key file (e.g. "~/.ssh/id_ed25519").' },
810
836
  ssh_host: { type: 'string', description: 'Custom host or IP override if not using the mesh IP.' },
@@ -851,6 +877,7 @@ export async function handleToolCall(toolCall) {
851
877
  try {
852
878
  const args = JSON.parse(argsString);
853
879
  switch (name) {
880
+ case 'queue_research': return await queueResearch(args.topic, args.notes);
854
881
  case 'search_web': return await executeWebSearch(args.query);
855
882
  case 'browser_navigate': return await browserNavigate(args.url);
856
883
  case 'browser_click': return await browserClick(args.selector);
@@ -1,27 +0,0 @@
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.
@@ -1,5 +0,0 @@
1
- // Exposes default configuration dynamically injected by the server
2
- self.PreConfigured = {
3
- brainUrl: '',
4
- pat: ''
5
- };
@@ -1,227 +0,0 @@
1
- /* Total Recall — Popup 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
- width: 350px;
14
- overflow: hidden;
15
- }
16
-
17
- .popup {
18
- padding: 16px;
19
- }
20
-
21
- /* ---- Header ---- */
22
- .popup-header {
23
- display: flex;
24
- align-items: center;
25
- justify-content: space-between;
26
- margin-bottom: 16px;
27
- }
28
-
29
- .popup-header h1 {
30
- font-size: 15px;
31
- font-weight: 700;
32
- }
33
-
34
- .dot {
35
- width: 10px;
36
- height: 10px;
37
- border-radius: 50%;
38
- background: #6c7086;
39
- transition: background 0.3s ease;
40
- }
41
-
42
- .dot.connected {
43
- background: #a6e3a1;
44
- box-shadow: 0 0 6px #a6e3a1;
45
- }
46
-
47
- .dot.disconnected {
48
- background: #f38ba8;
49
- box-shadow: 0 0 6px #f38ba8;
50
- }
51
-
52
- /* ---- Fields ---- */
53
- .field-group {
54
- margin-bottom: 12px;
55
- }
56
-
57
- .field-group label {
58
- display: block;
59
- font-size: 11px;
60
- font-weight: 600;
61
- text-transform: uppercase;
62
- letter-spacing: 0.5px;
63
- color: #585b70;
64
- margin-bottom: 6px;
65
- }
66
-
67
- .field-group input,
68
- .field-group textarea {
69
- width: 100%;
70
- padding: 8px 12px;
71
- background: #1e1e2e;
72
- border: 1px solid #313244;
73
- border-radius: 8px;
74
- color: #cdd6f4;
75
- font-size: 13px;
76
- outline: none;
77
- font-family: inherit;
78
- transition: border-color 0.2s ease;
79
- }
80
-
81
- .field-group input:focus,
82
- .field-group textarea:focus {
83
- border-color: #89b4fa;
84
- }
85
-
86
- .field-group textarea {
87
- resize: vertical;
88
- min-height: 60px;
89
- }
90
-
91
- .field-group input[readonly] {
92
- color: #a6adc8;
93
- cursor: default;
94
- }
95
-
96
- /* ---- Buttons ---- */
97
- .button-row {
98
- display: flex;
99
- gap: 8px;
100
- margin-bottom: 12px;
101
- }
102
-
103
- .btn {
104
- flex: 1;
105
- padding: 9px 10px;
106
- font-size: 13px;
107
- font-weight: 600;
108
- border: 1px solid #313244;
109
- border-radius: 10px;
110
- cursor: pointer;
111
- transition: background 0.2s ease, border-color 0.2s ease, transform 0.1s ease;
112
- }
113
-
114
- .btn:active {
115
- transform: scale(0.97);
116
- }
117
-
118
- .btn:disabled, .btn.loading {
119
- opacity: 0.6;
120
- cursor: not-allowed;
121
- transform: none !important;
122
- }
123
-
124
- .btn.primary {
125
- background: #89b4fa;
126
- color: #11111b;
127
- border-color: #89b4fa;
128
- }
129
-
130
- .btn.primary:hover {
131
- background: #74c7ec;
132
- border-color: #74c7ec;
133
- }
134
-
135
- .btn.secondary {
136
- background: #1e1e2e;
137
- color: #cdd6f4;
138
- }
139
-
140
- .btn.secondary:hover {
141
- background: #313244;
142
- border-color: #89b4fa;
143
- }
144
-
145
- /* ---- Toggle ---- */
146
- .toggle-row {
147
- display: flex;
148
- align-items: center;
149
- justify-content: space-between;
150
- padding: 10px 0;
151
- border-top: 1px solid #313244;
152
- }
153
-
154
- .toggle-label {
155
- font-size: 12px;
156
- color: #a6adc8;
157
- }
158
-
159
- .switch {
160
- position: relative;
161
- display: inline-block;
162
- width: 36px;
163
- height: 20px;
164
- }
165
-
166
- .switch input {
167
- opacity: 0;
168
- width: 0;
169
- height: 0;
170
- }
171
-
172
- .slider {
173
- position: absolute;
174
- cursor: pointer;
175
- inset: 0;
176
- background: #313244;
177
- border-radius: 20px;
178
- transition: background 0.3s ease;
179
- }
180
-
181
- .slider::before {
182
- content: '';
183
- position: absolute;
184
- height: 14px;
185
- width: 14px;
186
- left: 3px;
187
- bottom: 3px;
188
- background: #6c7086;
189
- border-radius: 50%;
190
- transition: transform 0.3s ease, background 0.3s ease;
191
- }
192
-
193
- .switch input:checked + .slider {
194
- background: rgba(137, 180, 250, 0.3);
195
- }
196
-
197
- .switch input:checked + .slider::before {
198
- transform: translateX(16px);
199
- background: #89b4fa;
200
- }
201
-
202
- /* ---- Status Message ---- */
203
- .status-msg {
204
- margin-top: 10px;
205
- padding: 8px 12px;
206
- border-radius: 8px;
207
- font-size: 12px;
208
- font-weight: 600;
209
- text-align: center;
210
- transition: opacity 0.3s ease;
211
- }
212
-
213
- .status-msg.success {
214
- background: rgba(166, 227, 161, 0.1);
215
- color: #a6e3a1;
216
- border: 1px solid rgba(166, 227, 161, 0.3);
217
- }
218
-
219
- .status-msg.error {
220
- background: rgba(243, 139, 168, 0.1);
221
- color: #f38ba8;
222
- border: 1px solid rgba(243, 139, 168, 0.3);
223
- }
224
-
225
- .hidden {
226
- display: none !important;
227
- }
@@ -1,50 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <title>Total Recall</title>
6
- <link rel="stylesheet" href="popup.css">
7
- </head>
8
- <body>
9
- <div class="popup">
10
- <header class="popup-header">
11
- <h1>🧠 Total Recall</h1>
12
- <div id="connection-dot" class="dot"></div>
13
- </header>
14
-
15
- <div class="field-group">
16
- <label for="url-input">Page URL</label>
17
- <input type="text" id="url-input" readonly />
18
- </div>
19
-
20
- <div class="field-group">
21
- <label for="note-input">Quick Note</label>
22
- <textarea id="note-input" placeholder="Add a note about this page…" rows="3"></textarea>
23
- </div>
24
-
25
- <div class="button-row">
26
- <button id="btn-remember" class="btn primary">📌 Remember</button>
27
- <button id="btn-research" class="btn secondary">🔬 Research</button>
28
- </div>
29
-
30
- <div class="toggle-row">
31
- <span class="toggle-label">Passive tracking</span>
32
- <label class="switch">
33
- <input type="checkbox" id="tracking-toggle" />
34
- <span class="slider"></span>
35
- </label>
36
- </div>
37
-
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>
44
- </div>
45
-
46
- <script src="../lib/preconfigured.js"></script>
47
- <script src="../lib/brain-client.js"></script>
48
- <script src="popup.js"></script>
49
- </body>
50
- </html>
@@ -1,175 +0,0 @@
1
- // Total Recall — Popup Logic
2
-
3
- (function () {
4
- 'use strict';
5
-
6
- const dot = document.getElementById('connection-dot');
7
- const urlInput = document.getElementById('url-input');
8
- const noteInput = document.getElementById('note-input');
9
- const btnRemember = document.getElementById('btn-remember');
10
- const btnResearch = document.getElementById('btn-research');
11
- const trackingToggle = document.getElementById('tracking-toggle');
12
- const statusMsg = document.getElementById('status-msg');
13
-
14
- // ---- Auto-fill current tab URL ----
15
- async function fillCurrentTab() {
16
- try {
17
- const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
18
- if (tab) urlInput.value = tab.url || '';
19
- } catch {
20
- // ignore — might not have activeTab permission yet
21
- }
22
- }
23
-
24
- // ---- Connection check ----
25
- async function checkConnection() {
26
- try {
27
- await self.BrainClient.healthCheck();
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
- }
48
- } catch {
49
- dot.className = 'dot disconnected';
50
- }
51
- }
52
-
53
- // ---- Send share message to background ----
54
- function sendShare(data) {
55
- return new Promise((resolve, reject) => {
56
- chrome.runtime.sendMessage({ type: 'SHARE', data }, (res) => {
57
- if (chrome.runtime.lastError) return reject(new Error(chrome.runtime.lastError.message));
58
- if (res && res.success) return resolve(res);
59
- reject(new Error(res?.error || 'Unknown error'));
60
- });
61
- });
62
- }
63
-
64
- // ---- Show status ----
65
- function showStatus(message, isError = false) {
66
- statusMsg.textContent = message;
67
- statusMsg.className = `status-msg ${isError ? 'error' : 'success'}`;
68
- statusMsg.classList.remove('hidden');
69
- setTimeout(() => statusMsg.classList.add('hidden'), 3000);
70
- }
71
-
72
- // ---- Remember ----
73
- btnRemember.addEventListener('click', async () => {
74
- if (btnRemember.disabled) return;
75
- const originalText = btnRemember.innerHTML;
76
- btnRemember.innerHTML = '⏳ Remembering...';
77
- btnRemember.disabled = true;
78
- btnRemember.classList.add('loading');
79
- try {
80
- const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
81
- const note = noteInput.value.trim();
82
- const targetUrl = tab?.url || urlInput.value || '';
83
-
84
- // If we don't have a URL, we must have a note/excerpt
85
- if (!targetUrl && !note) {
86
- throw new Error('Please enter a note or browse to a webpage to remember.');
87
- }
88
- if (targetUrl && (targetUrl.startsWith('chrome://') || targetUrl.startsWith('about:') || targetUrl.startsWith('chrome-extension://'))) {
89
- if (!note) {
90
- throw new Error('Cannot capture system pages. Please enter a note to save.');
91
- }
92
- }
93
-
94
- const payload = {
95
- url: targetUrl && !targetUrl.startsWith('chrome://') && !targetUrl.startsWith('about:') && !targetUrl.startsWith('chrome-extension://') ? targetUrl : undefined,
96
- title: tab?.title || '',
97
- action: 'remember',
98
- source: 'chrome-extension-popup'
99
- };
100
- if (note) payload.excerpt = note;
101
-
102
- await sendShare(payload);
103
- showStatus('Page remembered!');
104
- noteInput.value = '';
105
- } catch (err) {
106
- showStatus('Error: ' + err.message, true);
107
- } finally {
108
- btnRemember.innerHTML = originalText;
109
- btnRemember.disabled = false;
110
- btnRemember.classList.remove('loading');
111
- }
112
- });
113
-
114
- // ---- Research ----
115
- btnResearch.addEventListener('click', async () => {
116
- if (btnResearch.disabled) return;
117
- const originalText = btnResearch.innerHTML;
118
- btnResearch.innerHTML = '⏳ Researching...';
119
- btnResearch.disabled = true;
120
- btnResearch.classList.add('loading');
121
- try {
122
- const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
123
- const note = noteInput.value.trim();
124
- const targetUrl = tab?.url || urlInput.value || '';
125
-
126
- if (!targetUrl && !note) {
127
- throw new Error('Please enter research notes or browse to a webpage to queue research.');
128
- }
129
- if (targetUrl && (targetUrl.startsWith('chrome://') || targetUrl.startsWith('about:') || targetUrl.startsWith('chrome-extension://'))) {
130
- if (!note) {
131
- throw new Error('Cannot research system pages. Please enter research notes.');
132
- }
133
- }
134
-
135
- const payload = {
136
- url: targetUrl && !targetUrl.startsWith('chrome://') && !targetUrl.startsWith('about:') && !targetUrl.startsWith('chrome-extension://') ? targetUrl : undefined,
137
- title: tab?.title || '',
138
- action: 'research',
139
- source: 'chrome-extension-popup'
140
- };
141
- if (note) payload.excerpt = note;
142
-
143
- await sendShare(payload);
144
- showStatus('Research queued!');
145
- noteInput.value = '';
146
- } catch (err) {
147
- showStatus('Error: ' + err.message, true);
148
- } finally {
149
- btnResearch.innerHTML = originalText;
150
- btnResearch.disabled = false;
151
- btnResearch.classList.remove('loading');
152
- }
153
- });
154
-
155
- // ---- Tracking toggle ----
156
- async function loadTrackingPref() {
157
- const { passiveTracking } = await chrome.storage.sync.get('passiveTracking');
158
- trackingToggle.checked = passiveTracking || false;
159
- }
160
-
161
- trackingToggle.addEventListener('change', () => {
162
- chrome.storage.sync.set({ passiveTracking: trackingToggle.checked });
163
- });
164
-
165
- // ---- Init ----
166
- fillCurrentTab();
167
- checkConnection();
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
- }
175
- })();