total-recall-brain 3.28.2 → 3.30.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.
- package/bin/total-recall.mjs +24 -1
- package/docs/ARCHITECTURE.md +26 -18
- package/extension/background.js +259 -84
- package/extension/content-script.css +7 -13
- package/extension/content-script.js +252 -477
- package/extension/icons/icon-128.png +0 -0
- package/extension/icons/icon-16.png +0 -0
- package/extension/icons/icon-32.png +0 -0
- package/extension/icons/icon-48.png +0 -0
- package/extension/icons/mark.svg +6 -0
- package/extension/lib/brain-client.js +160 -73
- package/extension/lib/tokens.css +187 -0
- package/extension/lib/ui-helpers.js +229 -0
- package/extension/lib/ui-helpers.spec.mjs +94 -0
- package/extension/manifest.json +20 -5
- package/extension/options/options.css +64 -218
- package/extension/options/options.html +91 -43
- package/extension/options/options.js +208 -85
- package/extension/sidepanel/sidepanel.css +268 -970
- package/extension/sidepanel/sidepanel.html +200 -166
- package/extension/sidepanel/sidepanel.js +788 -942
- package/frontend/dist/assets/index-DGMNZi_1.js +5742 -0
- package/frontend/dist/index.html +1 -1
- package/frontend/src/api/plugins.ts +124 -92
- package/frontend/src/components/ResearchAgendaTab.spec.tsx +11 -0
- package/frontend/src/components/ResearchAgendaTab.tsx +19 -14
- package/frontend/src/pages/PluginsPage.spec.tsx +87 -16
- package/frontend/src/pages/PluginsPage.tsx +454 -1218
- package/package.json +2 -1
- package/plugins/code-quality/README.md +25 -0
- package/plugins/code-quality/cli.mjs +25 -0
- package/plugins/code-quality/plugin.json +17 -0
- package/plugins/git-sentinel/README.md +27 -0
- package/{scaffold/.agent/plugins → plugins}/git-sentinel/cli.mjs +8 -5
- package/plugins/git-sentinel/plugin.json +18 -0
- package/plugins/system-monitor/README.md +26 -0
- package/{scaffold/.agent/plugins → plugins}/system-monitor/cli.mjs +5 -4
- package/{scaffold/.agent/plugins → plugins}/system-monitor/generator.mjs +4 -5
- package/plugins/system-monitor/plugin.json +29 -0
- package/scaffold/.agent/skills/meta-harness/SKILL.md +4 -4
- package/scaffold/.agent/skills/plugins/SKILL.md +53 -0
- package/scaffold/.agent/skills/total-recall/SKILL.md +22 -6
- package/scaffold/.agent/skills/total-recall/scripts/brain-state.json +1 -0
- package/src/cli/agent.mjs +1 -1
- package/src/cli/app/index.mjs +493 -0
- package/src/cli/brain.mjs +33 -3
- package/src/cli/command.mjs +80 -41
- package/src/cli/connect.mjs +10 -1
- package/src/cli/harness.mjs +1 -1
- package/src/cli/init.mjs +3 -1
- package/src/cli/mesh.mjs +118 -12
- package/src/cli/plugin/create.mjs +144 -25
- package/src/cli/plugin/index.mjs +83 -53
- package/src/cli/plugin/install.mjs +10 -127
- package/src/cli/plugin/list.mjs +44 -44
- package/src/cli/plugin/remove.mjs +23 -30
- package/src/cli/plugin/scaffold-cmd.mjs +30 -0
- package/src/cli/plugin/search.mjs +108 -23
- package/src/cli/rebuild.mjs +37 -1
- package/src/cli/recall.mjs +17 -2
- package/src/cli/research.mjs +4 -6
- package/src/cli/share.mjs +2 -0
- package/src/core/app-deploy/apply.mjs +386 -0
- package/src/core/app-deploy/design-tokens.mjs +217 -0
- package/src/core/app-deploy/plan.mjs +249 -0
- package/src/core/app-deploy/resolve.mjs +295 -0
- package/src/core/app-deploy/source.mjs +337 -0
- package/src/core/app-deploy/standalone.mjs +248 -0
- package/src/core/app-deploy/upgrade.mjs +256 -0
- package/src/core/app-deploy/verify.mjs +213 -0
- package/src/core/bound-hosts.mjs +1 -1
- package/src/core/brain-registry.mjs +146 -0
- package/src/core/brain-state.json +1 -0
- package/src/core/daemon-loop.mjs +30 -26
- package/src/core/dream.mjs +5 -17
- package/src/core/fact-seeker.mjs +53 -368
- package/src/core/lan-discovery.mjs +3 -3
- package/src/core/mesh-access-sync.mjs +134 -0
- package/src/core/mesh-access.mjs +18 -3
- package/src/core/mesh-discover.mjs +173 -0
- package/src/core/mesh.mjs +131 -32
- package/src/core/optimizer.mjs +4 -41
- package/src/core/plugin-bundle.mjs +167 -0
- package/src/core/plugin-context.mjs +8 -13
- package/src/core/plugin-loader.mjs +272 -68
- package/src/core/plugin-peers.mjs +163 -0
- package/src/core/plugin-public.mjs +85 -0
- package/src/core/plugin-runner-child.mjs +24 -0
- package/src/core/plugin-runner.mjs +102 -0
- package/src/core/plugin-store.mjs +384 -0
- package/src/core/plugin-tasks.mjs +212 -0
- package/src/core/post-mortem.mjs +40 -9
- package/src/core/research-gate.mjs +182 -0
- package/src/core/research-queue.mjs +47 -7
- package/src/core/research-surface.mjs +211 -0
- package/src/core/research.mjs +25 -32
- package/src/core/scheduler.mjs +67 -153
- package/src/core/schema.mjs +129 -0
- package/src/core/search.mjs +12 -1
- package/src/core/secret-integration-research.mjs +12 -8
- package/src/core/secrets-store.mjs +76 -2
- package/src/core/session-watcher.mjs +6 -0
- package/src/core/surface.mjs +64 -3
- package/src/core/task-executors.mjs +8 -45
- package/src/core/vault-cache.mjs +70 -4
- package/src/server/api.mjs +21 -0
- package/src/server/rest.mjs +2 -0
- package/src/server/route-manifest.json +21 -5
- package/src/server/routes/collab.mjs +38 -11
- package/src/server/routes/extension.mjs +3 -2
- package/src/server/routes/memory.mjs +24 -2
- package/src/server/routes/mesh.mjs +7 -7
- package/src/server/routes/plugins-mesh.mjs +49 -0
- package/src/server/routes/plugins.mjs +91 -426
- package/src/server/routes/research.mjs +5 -2
- package/src/server/routes/share.mjs +2 -0
- package/src/server/tools.mjs +29 -2
- package/extension/icons/README.md +0 -27
- package/extension/lib/preconfigured.js +0 -5
- package/extension/popup/popup.css +0 -227
- package/extension/popup/popup.html +0 -50
- package/extension/popup/popup.js +0 -175
- package/frontend/dist/assets/index-U7o-qU99.js +0 -5742
- package/scaffold/.agent/plugins/.gitkeep +0 -0
- package/scaffold/.agent/plugins/code-quality/README.md +0 -16
- package/scaffold/.agent/plugins/code-quality/cli.mjs +0 -15
- package/scaffold/.agent/plugins/code-quality/plugin.json +0 -34
- package/scaffold/.agent/plugins/git-sentinel/README.md +0 -16
- package/scaffold/.agent/plugins/git-sentinel/plugin.json +0 -34
- package/scaffold/.agent/plugins/meta-harness/README.md +0 -16
- package/scaffold/.agent/plugins/meta-harness/cli.mjs +0 -34
- package/scaffold/.agent/plugins/meta-harness/plugin.json +0 -34
- package/scaffold/.agent/plugins/system-monitor/README.md +0 -16
- package/scaffold/.agent/plugins/system-monitor/plugin.json +0 -38
|
@@ -2,72 +2,120 @@
|
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
|
-
<
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>Total Recall — Extension settings</title>
|
|
7
|
+
<link rel="stylesheet" href="../lib/tokens.css">
|
|
6
8
|
<link rel="stylesheet" href="options.css">
|
|
7
9
|
</head>
|
|
8
10
|
<body>
|
|
9
|
-
<
|
|
10
|
-
<
|
|
11
|
-
|
|
11
|
+
<svg width="0" height="0" style="position:absolute" aria-hidden="true">
|
|
12
|
+
<symbol id="i-plug" viewBox="0 0 24 24"><path d="M9 3v5M15 3v5"/><path d="M6 8h12v3a6 6 0 0 1-12 0z"/><path d="M12 17v4"/></symbol>
|
|
13
|
+
<symbol id="i-shield" viewBox="0 0 24 24"><path d="M12 3 4.5 6v5.5c0 4.5 3.2 8.2 7.5 9.5 4.3-1.3 7.5-5 7.5-9.5V6z"/><path d="m9 12 2 2 4-4"/></symbol>
|
|
14
|
+
<symbol id="i-keyboard" viewBox="0 0 24 24"><rect x="2.5" y="6" width="19" height="12" rx="2"/><path d="M6.5 10h.01M10 10h.01M14 10h.01M17.5 10h.01M7 14h10"/></symbol>
|
|
15
|
+
<symbol id="i-eye" viewBox="0 0 24 24"><path d="M2.5 12S6 5 12 5s9.5 7 9.5 7-3.5 7-9.5 7-9.5-7-9.5-7z"/><circle cx="12" cy="12" r="3"/></symbol>
|
|
16
|
+
<symbol id="i-external" viewBox="0 0 24 24"><path d="M14 4h6v6"/><path d="M20 4 11 13"/><path d="M19 14v4a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V7a2 2 0 0 1 2-2h4"/></symbol>
|
|
17
|
+
</svg>
|
|
18
|
+
|
|
19
|
+
<div class="page">
|
|
20
|
+
<header class="hero">
|
|
21
|
+
<img src="../icons/icon-128.png" alt="" width="44" height="44" class="hero-mark">
|
|
22
|
+
<div>
|
|
23
|
+
<h1>Total Recall for Chrome</h1>
|
|
24
|
+
<p>Capture what you read, recall what you know, and chat with your brain beside any tab.</p>
|
|
25
|
+
</div>
|
|
12
26
|
</header>
|
|
13
27
|
|
|
14
|
-
<section class="
|
|
15
|
-
<
|
|
28
|
+
<section class="card" aria-labelledby="h-connection">
|
|
29
|
+
<div class="card-head">
|
|
30
|
+
<span class="card-icon"><svg class="icon"><use href="#i-plug"/></svg></span>
|
|
31
|
+
<div>
|
|
32
|
+
<h2 id="h-connection">Connection</h2>
|
|
33
|
+
<p>Where your brain runs and the token the extension uses to reach it.</p>
|
|
34
|
+
</div>
|
|
35
|
+
<span class="chip" id="connection-chip">Not tested</span>
|
|
36
|
+
</div>
|
|
16
37
|
|
|
17
|
-
<div class="field
|
|
18
|
-
<label for="brain-url">Brain
|
|
19
|
-
<input
|
|
38
|
+
<div class="field">
|
|
39
|
+
<label for="brain-url">Brain address</label>
|
|
40
|
+
<input class="input" id="brain-url" type="url" placeholder="http://127.0.0.1:3000" spellcheck="false" autocomplete="off">
|
|
41
|
+
<p class="help">Usually <code>http://127.0.0.1:3000</code>. For a brain on another machine, Chrome asks once for permission to reach it.</p>
|
|
20
42
|
</div>
|
|
21
43
|
|
|
22
|
-
<div class="field
|
|
23
|
-
<label for="pat-token">
|
|
24
|
-
<
|
|
44
|
+
<div class="field">
|
|
45
|
+
<label for="pat-token">Access token</label>
|
|
46
|
+
<div class="input-row">
|
|
47
|
+
<input class="input mono" id="pat-token" type="password" placeholder="tr_…" spellcheck="false" autocomplete="off">
|
|
48
|
+
<button class="icon-btn" id="btn-reveal" type="button" aria-label="Show token" aria-pressed="false" title="Show token"><svg class="icon"><use href="#i-eye"/></svg></button>
|
|
49
|
+
</div>
|
|
50
|
+
<p class="help">Create one in the dashboard under <a href="#" id="link-integrations">Integrations<svg class="icon sm"><use href="#i-external"/></svg></a>. It stays on this device and is never synced.</p>
|
|
25
51
|
</div>
|
|
26
52
|
|
|
27
|
-
<div class="connection-
|
|
28
|
-
|
|
29
|
-
|
|
53
|
+
<div class="result" id="connection-result" hidden></div>
|
|
54
|
+
|
|
55
|
+
<div class="actions">
|
|
56
|
+
<button class="btn" id="btn-test" type="button">Test connection</button>
|
|
57
|
+
<button class="btn primary" id="btn-save-connection" type="button">Save connection</button>
|
|
30
58
|
</div>
|
|
31
59
|
</section>
|
|
32
60
|
|
|
33
|
-
<section class="
|
|
34
|
-
<
|
|
61
|
+
<section class="card" aria-labelledby="h-privacy">
|
|
62
|
+
<div class="card-head">
|
|
63
|
+
<span class="card-icon"><svg class="icon"><use href="#i-shield"/></svg></span>
|
|
64
|
+
<div>
|
|
65
|
+
<h2 id="h-privacy">Page recall & privacy</h2>
|
|
66
|
+
<p>Nothing leaves this browser unless you capture it — or you turn on related memories below.</p>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<div class="setting">
|
|
71
|
+
<div>
|
|
72
|
+
<div class="setting-title">Related memories on pages</div>
|
|
73
|
+
<p class="help">When a page loads, send its title to your brain and badge the toolbar icon if you already know something related. Off by default.</p>
|
|
74
|
+
</div>
|
|
75
|
+
<label class="switch"><input type="checkbox" id="opt-page-recall"><span class="track"></span></label>
|
|
76
|
+
</div>
|
|
77
|
+
<div class="setting sub" id="pill-setting">
|
|
78
|
+
<div>
|
|
79
|
+
<div class="setting-title">Floating card on the page</div>
|
|
80
|
+
<p class="help">Also show a small “Related” pill in the corner of the page.</p>
|
|
81
|
+
</div>
|
|
82
|
+
<label class="switch"><input type="checkbox" id="opt-show-pill"><span class="track"></span></label>
|
|
83
|
+
</div>
|
|
35
84
|
|
|
36
|
-
<div class="field
|
|
37
|
-
<label for="blocklist">
|
|
38
|
-
<textarea id="blocklist" rows="5" placeholder="
|
|
85
|
+
<div class="field">
|
|
86
|
+
<label for="blocklist">Never check these sites <span class="muted">— one domain per line, subdomains included</span></label>
|
|
87
|
+
<textarea class="textarea mono" id="blocklist" rows="5" placeholder="mail.google.com bank.example.com"></textarea>
|
|
39
88
|
</div>
|
|
40
89
|
|
|
41
|
-
<div class="
|
|
42
|
-
<
|
|
43
|
-
<
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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>
|
|
90
|
+
<div class="actions">
|
|
91
|
+
<span class="saved" id="privacy-saved" hidden>Saved</span>
|
|
92
|
+
<button class="btn primary" id="btn-save-privacy" type="button">Save privacy settings</button>
|
|
93
|
+
</div>
|
|
94
|
+
</section>
|
|
95
|
+
|
|
96
|
+
<section class="card" aria-labelledby="h-shortcuts">
|
|
97
|
+
<div class="card-head">
|
|
98
|
+
<span class="card-icon"><svg class="icon"><use href="#i-keyboard"/></svg></span>
|
|
99
|
+
<div>
|
|
100
|
+
<h2 id="h-shortcuts">Shortcuts</h2>
|
|
101
|
+
<p>Capture without reaching for the mouse.</p>
|
|
59
102
|
</div>
|
|
60
103
|
</div>
|
|
104
|
+
<dl class="shortcuts" id="shortcut-list"></dl>
|
|
105
|
+
<div class="tips">
|
|
106
|
+
<div><span class="kbd">tr</span> then <span class="kbd">Space</span> in the address bar searches your brain.</div>
|
|
107
|
+
<div>Right-click a selection to remember it, research it, or ask your brain about it.</div>
|
|
108
|
+
</div>
|
|
109
|
+
<div class="actions">
|
|
110
|
+
<button class="btn" id="btn-shortcuts" type="button">Change shortcuts<svg class="icon sm"><use href="#i-external"/></svg></button>
|
|
111
|
+
</div>
|
|
61
112
|
</section>
|
|
62
113
|
|
|
63
|
-
<
|
|
64
|
-
<button id="btn-save" class="btn primary">Save Settings</button>
|
|
65
|
-
<span id="save-status" class="save-status"></span>
|
|
66
|
-
</div>
|
|
114
|
+
<footer class="foot" id="foot"></footer>
|
|
67
115
|
</div>
|
|
68
116
|
|
|
69
|
-
<script src="../lib/preconfigured.js"></script>
|
|
70
117
|
<script src="../lib/brain-client.js"></script>
|
|
118
|
+
<script src="../lib/ui-helpers.js"></script>
|
|
71
119
|
<script src="options.js"></script>
|
|
72
120
|
</body>
|
|
73
121
|
</html>
|
|
@@ -1,111 +1,234 @@
|
|
|
1
|
-
// Total Recall —
|
|
1
|
+
// Total Recall — options page
|
|
2
2
|
|
|
3
3
|
(function () {
|
|
4
4
|
'use strict';
|
|
5
5
|
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
6
|
+
const Brain = self.BrainClient;
|
|
7
|
+
const $ = (id) => document.getElementById(id);
|
|
8
|
+
|
|
9
|
+
function el(tag, className, text) {
|
|
10
|
+
const node = document.createElement(tag);
|
|
11
|
+
if (className) node.className = className;
|
|
12
|
+
if (text !== undefined) node.textContent = text;
|
|
13
|
+
return node;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function setBusy(button, busy) {
|
|
17
|
+
button.disabled = busy;
|
|
18
|
+
button.classList.toggle('is-busy', busy);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function isLocal(url) {
|
|
22
|
+
try {
|
|
23
|
+
const host = new URL(url).hostname;
|
|
24
|
+
return host === '127.0.0.1' || host === 'localhost';
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function originPattern(url) {
|
|
31
|
+
const u = new URL(url);
|
|
32
|
+
return `${u.protocol}//${u.hostname}/*`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Remote brains need a runtime host grant; must be requested from a click. */
|
|
36
|
+
async function ensureHostPermission(url) {
|
|
37
|
+
if (isLocal(url)) return true;
|
|
38
|
+
const origins = [originPattern(url)];
|
|
39
|
+
if (await chrome.permissions.contains({ origins })) return true;
|
|
40
|
+
return chrome.permissions.request({ origins });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function readConnectionForm() {
|
|
44
|
+
const raw = $('brain-url').value.trim() || Brain.DEFAULT_BRAIN_URL;
|
|
45
|
+
let brainUrl;
|
|
46
|
+
try {
|
|
47
|
+
const u = new URL(raw);
|
|
48
|
+
if (!/^https?:$/.test(u.protocol)) throw new Error();
|
|
49
|
+
brainUrl = Brain.normalizeUrl(u.origin + u.pathname);
|
|
50
|
+
} catch {
|
|
51
|
+
throw new Error('Enter a full address, like http://127.0.0.1:3000');
|
|
52
|
+
}
|
|
53
|
+
return { brainUrl, pat: $('pat-token').value.trim() };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function setChip(text, tone) {
|
|
57
|
+
const chip = $('connection-chip');
|
|
58
|
+
chip.textContent = text;
|
|
59
|
+
chip.className = `chip ${tone || ''}`;
|
|
60
|
+
}
|
|
25
61
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
62
|
+
function showResult(ok, rows, message) {
|
|
63
|
+
const box = $('connection-result');
|
|
64
|
+
box.className = `result ${ok ? 'ok' : 'fail'}`;
|
|
65
|
+
if (ok) {
|
|
66
|
+
box.replaceChildren(...rows.flatMap(([k, v]) => [el('dt', '', k), el('dd', '', v)]));
|
|
67
|
+
} else {
|
|
68
|
+
box.replaceChildren(el('div', '', message));
|
|
69
|
+
}
|
|
70
|
+
box.hidden = false;
|
|
71
|
+
}
|
|
29
72
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
73
|
+
async function testConnection() {
|
|
74
|
+
const { brainUrl, pat } = readConnectionForm();
|
|
75
|
+
if (!(await ensureHostPermission(brainUrl))) {
|
|
76
|
+
throw new Error(`Chrome permission to reach ${new URL(brainUrl).host} was declined.`);
|
|
77
|
+
}
|
|
78
|
+
const config = { brainUrl, pat, activeBrainId: 'global' };
|
|
79
|
+
const health = await Brain.request('/health', { config, brainScoped: false });
|
|
80
|
+
if (!pat) {
|
|
81
|
+
const err = new Error('The brain is reachable. Add an access token to finish connecting.');
|
|
82
|
+
err.code = 'unconfigured';
|
|
83
|
+
err.health = health;
|
|
84
|
+
throw err;
|
|
33
85
|
}
|
|
86
|
+
const list = await Brain.request('/api/memory?limit=1', { config });
|
|
87
|
+
let brains = null;
|
|
88
|
+
try { brains = await Brain.request('/api/brains', { config, brainScoped: false }); } catch { /* optional scope */ }
|
|
89
|
+
return { health, total: list && list.total, brains: brains && brains.brains };
|
|
90
|
+
}
|
|
34
91
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
92
|
+
function describeTest(result) {
|
|
93
|
+
const rows = [
|
|
94
|
+
['Brain', `v${result.health.version || '?'} · daemon ${result.health.daemon || 'unknown'}`],
|
|
95
|
+
['Memories', typeof result.total === 'number' ? result.total.toLocaleString() : '—'],
|
|
96
|
+
];
|
|
97
|
+
if (Array.isArray(result.brains) && result.brains.length > 1) {
|
|
98
|
+
rows.push(['Project brains', String(result.brains.length - 1)]);
|
|
39
99
|
}
|
|
40
|
-
|
|
41
|
-
if (radio) radio.checked = true;
|
|
100
|
+
return rows;
|
|
42
101
|
}
|
|
43
102
|
|
|
44
|
-
|
|
45
|
-
|
|
103
|
+
$('btn-test').addEventListener('click', async () => {
|
|
104
|
+
const btn = $('btn-test');
|
|
105
|
+
setBusy(btn, true);
|
|
106
|
+
setChip('Testing…');
|
|
46
107
|
try {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
? blocklistRaw.split('\n').map((d) => d.trim()).filter(Boolean)
|
|
51
|
-
: [];
|
|
52
|
-
|
|
53
|
-
// Get selected granularity
|
|
54
|
-
const granularityRadio = document.querySelector('input[name="granularity"]:checked');
|
|
55
|
-
const captureGranularity = granularityRadio ? granularityRadio.value : 'minimal';
|
|
56
|
-
const passiveTracking = captureGranularity !== 'off';
|
|
57
|
-
|
|
58
|
-
await chrome.storage.local.set({
|
|
59
|
-
brainUrl: brainUrlInput.value.trim() || 'http://127.0.0.1:3000',
|
|
60
|
-
pat: patInput.value.trim(),
|
|
61
|
-
});
|
|
62
|
-
await chrome.storage.sync.remove('pat');
|
|
63
|
-
await chrome.storage.sync.set({
|
|
64
|
-
blocklist,
|
|
65
|
-
captureGranularity,
|
|
66
|
-
passiveTracking
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
showSaveStatus('Settings saved!', false);
|
|
108
|
+
const result = await testConnection();
|
|
109
|
+
setChip('Connected', 'success');
|
|
110
|
+
showResult(true, describeTest(result));
|
|
70
111
|
} catch (err) {
|
|
71
|
-
|
|
112
|
+
setChip(err.code === 'unconfigured' ? 'Needs token' : 'Not connected', err.code === 'unconfigured' ? 'warning' : 'error');
|
|
113
|
+
showResult(false, null, err.message);
|
|
114
|
+
} finally {
|
|
115
|
+
setBusy(btn, false);
|
|
72
116
|
}
|
|
73
117
|
});
|
|
74
118
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
connectionStatus.className = 'connection-status';
|
|
79
|
-
|
|
119
|
+
$('btn-save-connection').addEventListener('click', async () => {
|
|
120
|
+
const btn = $('btn-save-connection');
|
|
121
|
+
setBusy(btn, true);
|
|
80
122
|
try {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
123
|
+
const { brainUrl, pat } = readConnectionForm();
|
|
124
|
+
if (!(await ensureHostPermission(brainUrl))) {
|
|
125
|
+
throw new Error(`Without permission to reach ${new URL(brainUrl).host}, the extension can't use that brain.`);
|
|
126
|
+
}
|
|
127
|
+
await chrome.storage.local.set({ brainUrl, pat });
|
|
128
|
+
await chrome.storage.sync.remove(['pat', 'brainUrl']);
|
|
129
|
+
$('brain-url').value = brainUrl;
|
|
130
|
+
try {
|
|
131
|
+
const result = await testConnection();
|
|
132
|
+
setChip('Connected', 'success');
|
|
133
|
+
showResult(true, [['Saved', 'Connection works'], ...describeTest(result)]);
|
|
134
|
+
} catch (err) {
|
|
135
|
+
setChip('Saved · not connected', 'warning');
|
|
136
|
+
showResult(false, null, `Saved, but the test failed: ${err.message}`);
|
|
137
|
+
}
|
|
138
|
+
} catch (err) {
|
|
139
|
+
showResult(false, null, err.message);
|
|
140
|
+
} finally {
|
|
141
|
+
setBusy(btn, false);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
$('btn-reveal').addEventListener('click', () => {
|
|
146
|
+
const input = $('pat-token');
|
|
147
|
+
const show = input.type === 'password';
|
|
148
|
+
input.type = show ? 'text' : 'password';
|
|
149
|
+
$('btn-reveal').setAttribute('aria-pressed', String(show));
|
|
150
|
+
$('btn-reveal').setAttribute('aria-label', show ? 'Hide token' : 'Show token');
|
|
151
|
+
});
|
|
84
152
|
|
|
85
|
-
|
|
86
|
-
|
|
153
|
+
$('link-integrations').addEventListener('click', async (e) => {
|
|
154
|
+
e.preventDefault();
|
|
155
|
+
let base;
|
|
156
|
+
try { base = readConnectionForm().brainUrl; } catch { base = Brain.DEFAULT_BRAIN_URL; }
|
|
157
|
+
chrome.tabs.create({ url: `${base}/integrations` });
|
|
158
|
+
});
|
|
87
159
|
|
|
88
|
-
|
|
89
|
-
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
160
|
+
// ---- Privacy ----
|
|
90
161
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
162
|
+
function syncPillSetting() {
|
|
163
|
+
const on = $('opt-page-recall').checked;
|
|
164
|
+
$('opt-show-pill').disabled = !on;
|
|
165
|
+
$('pill-setting').classList.toggle('is-disabled', !on);
|
|
166
|
+
}
|
|
167
|
+
$('opt-page-recall').addEventListener('change', syncPillSetting);
|
|
168
|
+
|
|
169
|
+
$('btn-save-privacy').addEventListener('click', async () => {
|
|
170
|
+
const blocklist = $('blocklist').value
|
|
171
|
+
.split('\n')
|
|
172
|
+
.map((line) => line.trim().toLowerCase().replace(/^[a-z]+:\/\//, '').replace(/\/.*$/, ''))
|
|
173
|
+
.filter(Boolean);
|
|
174
|
+
const unique = [...new Set(blocklist)];
|
|
175
|
+
await chrome.storage.sync.set({
|
|
176
|
+
pageRecall: $('opt-page-recall').checked,
|
|
177
|
+
showPill: $('opt-show-pill').checked,
|
|
178
|
+
blocklist: unique,
|
|
179
|
+
});
|
|
180
|
+
$('blocklist').value = unique.join('\n');
|
|
181
|
+
const saved = $('privacy-saved');
|
|
182
|
+
saved.hidden = false;
|
|
183
|
+
setTimeout(() => { saved.hidden = true; }, 2200);
|
|
97
184
|
});
|
|
98
185
|
|
|
99
|
-
// ----
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
186
|
+
// ---- Shortcuts ----
|
|
187
|
+
|
|
188
|
+
async function renderShortcuts() {
|
|
189
|
+
const labels = {
|
|
190
|
+
_execute_action: 'Open the side panel',
|
|
191
|
+
'remember-page': 'Remember page (or selection)',
|
|
192
|
+
};
|
|
193
|
+
const commands = await chrome.commands.getAll();
|
|
194
|
+
const list = $('shortcut-list');
|
|
195
|
+
list.replaceChildren(...commands.filter((c) => labels[c.name]).map((c) => {
|
|
196
|
+
const row = el('div');
|
|
197
|
+
row.append(el('dt', '', labels[c.name]));
|
|
198
|
+
const dd = el('dd');
|
|
199
|
+
if (c.shortcut) {
|
|
200
|
+
for (const key of c.shortcut.split(/\+/)) dd.append(el('span', 'kbd', key));
|
|
201
|
+
} else {
|
|
202
|
+
dd.append(el('span', 'unset', 'Not set'));
|
|
203
|
+
}
|
|
204
|
+
row.append(dd);
|
|
205
|
+
return row;
|
|
206
|
+
}));
|
|
107
207
|
}
|
|
108
208
|
|
|
209
|
+
$('btn-shortcuts').addEventListener('click', () => chrome.tabs.create({ url: 'chrome://extensions/shortcuts' }));
|
|
210
|
+
|
|
109
211
|
// ---- Init ----
|
|
110
|
-
|
|
212
|
+
|
|
213
|
+
async function init() {
|
|
214
|
+
const [local, sync] = await Promise.all([
|
|
215
|
+
chrome.storage.local.get(['brainUrl', 'pat']),
|
|
216
|
+
chrome.storage.sync.get({ pageRecall: false, showPill: true, blocklist: [] }),
|
|
217
|
+
]);
|
|
218
|
+
const config = await Brain.getConfig();
|
|
219
|
+
$('brain-url').value = local.brainUrl || config.brainUrl;
|
|
220
|
+
$('pat-token').value = config.pat || '';
|
|
221
|
+
$('opt-page-recall').checked = !!sync.pageRecall;
|
|
222
|
+
$('opt-show-pill').checked = sync.showPill !== false;
|
|
223
|
+
$('blocklist').value = (sync.blocklist || []).join('\n');
|
|
224
|
+
syncPillSetting();
|
|
225
|
+
renderShortcuts();
|
|
226
|
+
|
|
227
|
+
const foot = $('foot');
|
|
228
|
+
foot.replaceChildren(el('span', '', `Total Recall extension v${chrome.runtime.getManifest().version}`), el('span', '', 'Your data stays on your brain.'));
|
|
229
|
+
|
|
230
|
+
if (config.pat) $('btn-test').click();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
init();
|
|
111
234
|
})();
|