total-recall-brain 3.1.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 (170) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +173 -0
  3. package/bin/antigravity.mjs +212 -0
  4. package/bin/init.mjs +36 -0
  5. package/bin/oci-sniper.sh +69 -0
  6. package/bin/total-recall.mjs +229 -0
  7. package/docs/ARCHITECTURE.md +251 -0
  8. package/docs/IDE_INTEGRATION.md +112 -0
  9. package/package.json +71 -0
  10. package/scaffold/.agent/skills/total-recall/SKILL.md +195 -0
  11. package/scaffold/.agent/skills/total-recall/evals/evals.json +20 -0
  12. package/scaffold/.agent/skills/total-recall/memory-vault/concepts/cli-help-reference.md +145 -0
  13. package/scaffold/.agent/skills/total-recall/memory-vault/invariants/operating-instructions.md +75 -0
  14. package/scaffold/.agent/skills/total-recall/memory-vault/preferences/always-websearch-gap.md +16 -0
  15. package/scaffold/.agent/skills/total-recall/memory-vault/preferences/topic-research-sop.md +17 -0
  16. package/scaffold/.agent/skills/total-recall/references/api-reference.md +187 -0
  17. package/scaffold/.agent/skills/total-recall/references/architecture-reference.md +81 -0
  18. package/scaffold/.agent/skills/total-recall/references/cli-reference.md +103 -0
  19. package/scaffold/.agent/skills/total-recall/references/ssss-reference.md +88 -0
  20. package/scaffold/.agent/skills/total-recall/references/troubleshooting.md +68 -0
  21. package/scaffold/.agent/skills/total-recall/scripts/sync-repo.mjs +83 -0
  22. package/scaffold/.agent/skills/total-recall/skills/cli-agents/SKILL.md +122 -0
  23. package/scaffold/.agent/skills/total-recall/skills/cli-agents/agents.yml +27 -0
  24. package/scaffold/.agent/skills/total-recall/skills/cli-agents/references/antigravity.txt +20 -0
  25. package/scaffold/.agent/skills/total-recall/skills/cli-agents/references/claude.txt +58 -0
  26. package/scaffold/.agent/skills/total-recall/skills/cli-agents/references/codex.txt +81 -0
  27. package/scaffold/.agent/skills/total-recall/skills/cli-agents/references/gemini.txt +44 -0
  28. package/scaffold/.agent/skills/total-recall/skills/research/SKILL.md +337 -0
  29. package/scaffold/.agent/skills/total-recall/skills/skill/SKILL.md +281 -0
  30. package/scaffold/.agent/skills/total-recall/skills/skill/assets/skill-template/SKILL.md +20 -0
  31. package/scaffold/.agent/skills/total-recall/skills/skill/evals/evals.json +20 -0
  32. package/scaffold/.agent/skills/total-recall/skills/skill/hooks/on-skill-add.sh +14 -0
  33. package/scaffold/.agent/skills/total-recall/skills/skill/hooks/on-skill-delete.sh +14 -0
  34. package/scaffold/.agent/skills/total-recall/skills/skill/learnings/.gitkeep +0 -0
  35. package/scaffold/.agent/skills/total-recall/skills/skill/references/.gitkeep +0 -0
  36. package/scaffold/.agent/skills/total-recall/skills/skill/scripts/.gitkeep +0 -0
  37. package/scaffold/.agent/skills/total-recall/skills/skill/scripts/create-skill.sh +65 -0
  38. package/scaffold/.agent/skills/total-recall/skills/skill/scripts/enforce-skill-optimization.mjs +135 -0
  39. package/scaffold/.agent/skills/total-recall/skills/skill/scripts/find-skills.mjs +124 -0
  40. package/scaffold/.agent/skills/total-recall/skills/skill/scripts/scan-skill.mjs +207 -0
  41. package/scaffold/.agent/skills/total-recall/skills/skill/scripts/skill-watcher.mjs +64 -0
  42. package/scaffold/.agent/skills/total-recall/skills/skill/subagents/audit-all-skills.md +62 -0
  43. package/scaffold/.agent/skills/total-recall/skills/ssss/SKILL.md +27 -0
  44. package/scaffold/.agent/skills/total-recall/skills/ssss/evals/evals.json +17 -0
  45. package/scaffold/.agent/skills/total-recall/skills/ssss/references/ssss-spec.md +117 -0
  46. package/scaffold/.agent/skills/total-recall/skills/ssss/scripts/validate-schema.mjs +54 -0
  47. package/scaffold/.agent/skills/total-recall/skills/ssss/subagents/audit-ssss-node.md +9 -0
  48. package/scaffold/.agent/skills/total-recall/subagents/total-recall-diagnostician.md +13 -0
  49. package/src/cli/agent-dir.mjs +105 -0
  50. package/src/cli/backup.mjs +313 -0
  51. package/src/cli/brain.mjs +224 -0
  52. package/src/cli/chat.mjs +74 -0
  53. package/src/cli/config.mjs +247 -0
  54. package/src/cli/connect.mjs +872 -0
  55. package/src/cli/daemon.mjs +228 -0
  56. package/src/cli/deploy-ui.mjs +1793 -0
  57. package/src/cli/deploy.mjs +1726 -0
  58. package/src/cli/doctor.mjs +254 -0
  59. package/src/cli/dream.mjs +80 -0
  60. package/src/cli/forget.mjs +92 -0
  61. package/src/cli/friction.mjs +21 -0
  62. package/src/cli/generate-pat.mjs +65 -0
  63. package/src/cli/hash-password.mjs +22 -0
  64. package/src/cli/help.mjs +153 -0
  65. package/src/cli/import-rules.mjs +102 -0
  66. package/src/cli/ingest/index.mjs +318 -0
  67. package/src/cli/ingest/parsers/chrome-bookmarks.mjs +238 -0
  68. package/src/cli/ingest/parsers/google-keep.mjs +147 -0
  69. package/src/cli/ingest/parsers/search-history.mjs +142 -0
  70. package/src/cli/ingest/parsers/youtube-history.mjs +181 -0
  71. package/src/cli/ingest/utils/dedup.mjs +69 -0
  72. package/src/cli/ingest/utils/takeout-walker.mjs +101 -0
  73. package/src/cli/ingest.mjs +102 -0
  74. package/src/cli/init.mjs +736 -0
  75. package/src/cli/integration-dispatcher.mjs +128 -0
  76. package/src/cli/lint.mjs +166 -0
  77. package/src/cli/map.mjs +194 -0
  78. package/src/cli/migrate.mjs +87 -0
  79. package/src/cli/rebuild.mjs +88 -0
  80. package/src/cli/recall.mjs +219 -0
  81. package/src/cli/relay.mjs +518 -0
  82. package/src/cli/remember.mjs +306 -0
  83. package/src/cli/research.mjs +381 -0
  84. package/src/cli/reset-password.mjs +75 -0
  85. package/src/cli/restore.mjs +135 -0
  86. package/src/cli/setup.mjs +616 -0
  87. package/src/cli/share.mjs +180 -0
  88. package/src/cli/skill.mjs +313 -0
  89. package/src/cli/snapshot.mjs +108 -0
  90. package/src/cli/start.mjs +63 -0
  91. package/src/cli/status.mjs +296 -0
  92. package/src/cli/sync.mjs +383 -0
  93. package/src/cli/uninstall.mjs +613 -0
  94. package/src/cli/upgrade.mjs +141 -0
  95. package/src/cli/wizard.html +3682 -0
  96. package/src/core/blackboard.mjs +54 -0
  97. package/src/core/clarity-rewriter.mjs +770 -0
  98. package/src/core/conclusion-writer.mjs +259 -0
  99. package/src/core/config.mjs +277 -0
  100. package/src/core/conflict-detector.mjs +536 -0
  101. package/src/core/crypto.mjs +107 -0
  102. package/src/core/daemon-control.mjs +158 -0
  103. package/src/core/daemon-loop.mjs +364 -0
  104. package/src/core/dream.mjs +347 -0
  105. package/src/core/drift-detector.mjs +143 -0
  106. package/src/core/embeddings.mjs +632 -0
  107. package/src/core/emergency-alerts.mjs +137 -0
  108. package/src/core/evolution.mjs +107 -0
  109. package/src/core/fact-seeker.mjs +1602 -0
  110. package/src/core/friction.mjs +95 -0
  111. package/src/core/import-rules.mjs +308 -0
  112. package/src/core/inference-engine.mjs +367 -0
  113. package/src/core/logger.mjs +75 -0
  114. package/src/core/memory-layers.mjs +96 -0
  115. package/src/core/migrate.mjs +116 -0
  116. package/src/core/notifications.mjs +77 -0
  117. package/src/core/operation-validator.mjs +272 -0
  118. package/src/core/optimizer.mjs +330 -0
  119. package/src/core/pattern_detector.mjs +113 -0
  120. package/src/core/post-mortem.mjs +413 -0
  121. package/src/core/protect-instructions.mjs +148 -0
  122. package/src/core/quick-capture.mjs +145 -0
  123. package/src/core/research-queue.mjs +431 -0
  124. package/src/core/research.mjs +461 -0
  125. package/src/core/runtime.mjs +515 -0
  126. package/src/core/sandbox.mjs +163 -0
  127. package/src/core/scheduler.mjs +569 -0
  128. package/src/core/schema.mjs +333 -0
  129. package/src/core/search.mjs +219 -0
  130. package/src/core/semantic-index.mjs +276 -0
  131. package/src/core/session-watcher.mjs +850 -0
  132. package/src/core/snapshot.mjs +133 -0
  133. package/src/core/source-adapters.mjs +1038 -0
  134. package/src/core/steering.mjs +137 -0
  135. package/src/core/surface.mjs +444 -0
  136. package/src/core/task_runner.mjs +125 -0
  137. package/src/core/tts.mjs +142 -0
  138. package/src/core/usage-tracker.mjs +349 -0
  139. package/src/core/vault-cache.mjs +76 -0
  140. package/src/core/vault.mjs +273 -0
  141. package/src/core/watchdog.mjs +236 -0
  142. package/src/server/api.mjs +944 -0
  143. package/src/server/auth.mjs +410 -0
  144. package/src/server/index.mjs +868 -0
  145. package/src/server/keys.mjs +181 -0
  146. package/src/server/rest.mjs +1652 -0
  147. package/src/server/route-manifest.json +77 -0
  148. package/src/server/routes/_shared.mjs +93 -0
  149. package/src/server/routes/keys.mjs +55 -0
  150. package/src/server/routes/memory.mjs +249 -0
  151. package/src/server/routes/sessions.mjs +336 -0
  152. package/src/server/routes/share.mjs +138 -0
  153. package/src/server/tools.mjs +618 -0
  154. package/templates/Caddyfile +37 -0
  155. package/templates/com.totalrecall.backup.plist +42 -0
  156. package/templates/com.totalrecall.daemon.plist +40 -0
  157. package/templates/com.totalrecall.duckdns.plist +23 -0
  158. package/templates/com.totalrecall.server.plist +46 -0
  159. package/templates/default-config/budget.yml +6 -0
  160. package/templates/default-config/runtime.yml +11 -0
  161. package/templates/default-config/security.yml +53 -0
  162. package/templates/default-config/voice.yml +34 -0
  163. package/templates/obsidian-queries/conflicts.md +26 -0
  164. package/templates/obsidian-queries/daily.md +17 -0
  165. package/templates/obsidian-queries/index.md +38 -0
  166. package/templates/obsidian-queries/skills.md +14 -0
  167. package/templates/onboarding-interview.md +72 -0
  168. package/templates/skill_engineering.md +40 -0
  169. package/templates/total-recall-daemon.service +21 -0
  170. package/templates/total-recall-server.service +20 -0
@@ -0,0 +1,276 @@
1
+ /**
2
+ * Semantic Index — file-native vector search alongside tf-idf
3
+ *
4
+ * Generates embeddings via a local Ollama embedding model and persists them
5
+ * in memory-derived/embeddings.jsonl — a plain JSONL file, no database.
6
+ *
7
+ * Embedding model: nomic-embed-text (default) or any Ollama embedding model.
8
+ *
9
+ * Usage:
10
+ * - `buildSemanticIndex(nodes, derivedDir, opts)` — called by compileSurface
11
+ * - `semanticSearch(query, derivedDir, opts)` — called alongside tf-idf recall
12
+ */
13
+
14
+ import fs from 'node:fs';
15
+ import path from 'node:path';
16
+ import crypto from 'node:crypto';
17
+ import { getEmbedding } from './embeddings.mjs';
18
+
19
+ const EMBEDDINGS_FILE = 'embeddings.jsonl';
20
+
21
+ // ─── Embedding generation ────────────────────────────────────────────────────────
22
+
23
+ /**
24
+ * Generate an embedding vector for `text` via Google embeddings (with OpenAI fallback).
25
+ * Returns null if the embeddings API is unreachable or fails.
26
+ *
27
+ * @param {string} text
28
+ * @param {object} [opts]
29
+ * @returns {Promise<number[]|null>}
30
+ */
31
+ export async function generateEmbedding(text, opts = {}) {
32
+ try {
33
+ return await getEmbedding(text);
34
+ } catch {
35
+ return null;
36
+ }
37
+ }
38
+
39
+ // ─── Cosine similarity ───────────────────────────────────────────────────────────
40
+
41
+ export function cosineSimilarity(a, b) {
42
+ if (!a || !b || a.length !== b.length) return 0;
43
+ let dot = 0, magA = 0, magB = 0;
44
+ for (let i = 0; i < a.length; i++) {
45
+ dot += a[i] * b[i];
46
+ magA += a[i] * a[i];
47
+ magB += b[i] * b[i];
48
+ }
49
+ const denom = Math.sqrt(magA) * Math.sqrt(magB);
50
+ return denom === 0 ? 0 : dot / denom;
51
+ }
52
+
53
+ // ─── Index I/O ───────────────────────────────────────────────────────────────────
54
+
55
+ /**
56
+ * Load the embeddings index from disk.
57
+ * Returns Map<slug, number[] & { content_sha256: string }>.
58
+ */
59
+ export function loadEmbeddingsIndex(derivedDir) {
60
+ const file = path.join(derivedDir, EMBEDDINGS_FILE);
61
+ const index = new Map();
62
+ if (!fs.existsSync(file)) return index;
63
+ for (const line of fs.readFileSync(file, 'utf8').split('\n')) {
64
+ if (!line.trim()) continue;
65
+ try {
66
+ const parsed = JSON.parse(line);
67
+ const { slug, embedding, content_sha256 } = parsed;
68
+ if (slug && Array.isArray(embedding)) {
69
+ const vec = embedding;
70
+ Object.defineProperty(vec, 'embedding', {
71
+ get() { return this; },
72
+ enumerable: false,
73
+ configurable: true
74
+ });
75
+ Object.defineProperty(vec, 'content_sha256', {
76
+ value: content_sha256 || '',
77
+ writable: true,
78
+ enumerable: false,
79
+ configurable: true
80
+ });
81
+ index.set(slug, vec);
82
+ } else if (slug && parsed.embedding === undefined && Array.isArray(parsed)) {
83
+ // Fallback for very old formats if any
84
+ const vec = parsed;
85
+ Object.defineProperty(vec, 'embedding', {
86
+ get() { return this; },
87
+ enumerable: false,
88
+ configurable: true
89
+ });
90
+ Object.defineProperty(vec, 'content_sha256', {
91
+ value: '',
92
+ writable: true,
93
+ enumerable: false,
94
+ configurable: true
95
+ });
96
+ index.set(slug, vec);
97
+ }
98
+ } catch { /* skip corrupt lines */ }
99
+ }
100
+ return index;
101
+ }
102
+
103
+ function writeEmbeddingsIndex(derivedDir, index) {
104
+ if (!fs.existsSync(derivedDir)) fs.mkdirSync(derivedDir, { recursive: true });
105
+ const lines = [];
106
+ for (const [slug, val] of index) {
107
+ if (val && Array.isArray(val.embedding)) {
108
+ lines.push(JSON.stringify({ slug, embedding: val.embedding, content_sha256: val.content_sha256 || '' }));
109
+ } else if (Array.isArray(val)) {
110
+ lines.push(JSON.stringify({ slug, embedding: val, content_sha256: val.content_sha256 || '' }));
111
+ }
112
+ }
113
+ fs.writeFileSync(path.join(derivedDir, EMBEDDINGS_FILE), lines.join('\n') + '\n', 'utf8');
114
+ }
115
+
116
+ async function throttledMap(items, limit, fn) {
117
+ const results = [];
118
+ const executing = new Set();
119
+ for (const item of items) {
120
+ const p = Promise.resolve().then(() => fn(item));
121
+ results.push(p);
122
+ executing.add(p);
123
+ const clean = () => executing.delete(p);
124
+ p.then(clean, clean);
125
+ if (executing.size >= limit) {
126
+ await Promise.race(executing);
127
+ }
128
+ }
129
+ return Promise.all(results);
130
+ }
131
+
132
+ // ─── Index builder ───────────────────────────────────────────────────────────────
133
+
134
+ /**
135
+ * Build (or incrementally update) the embeddings index from SSSS nodes.
136
+ * Re-embeds nodes if they are missing from the index OR if their content hash has changed.
137
+ * Skips silently if Ollama is unreachable.
138
+ *
139
+ * @param {object[]} nodes SSSS nodes with slug, title, body/content fields
140
+ * @param {string} derivedDir
141
+ * @param {object} [opts]
142
+ * @param {string} [opts.endpoint]
143
+ * @param {string} [opts.model]
144
+ * @returns {Promise<{ indexed: number, skipped: number, unavailable: boolean }>}
145
+ */
146
+ export async function buildSemanticIndex(nodes, derivedDir, opts = {}) {
147
+ if (nodes.length === 0) {
148
+ return { indexed: 0, skipped: 0, unavailable: false };
149
+ }
150
+
151
+ const existing = loadEmbeddingsIndex(derivedDir);
152
+
153
+ // Probe availability and get current model dimension using the first node
154
+ const firstText = [nodes[0].title, nodes[0].body || nodes[0].content].filter(Boolean).join('\n').slice(0, 4096);
155
+ const probe = await generateEmbedding(firstText, opts);
156
+ if (!probe) {
157
+ return { indexed: 0, skipped: nodes.length, unavailable: true };
158
+ }
159
+
160
+ const newDim = probe.length;
161
+
162
+ // Check for dimension mismatch against existing cached embeddings
163
+ if (existing.size > 0) {
164
+ const firstVal = existing.values().next().value;
165
+ const existingDim = Array.isArray(firstVal)
166
+ ? firstVal.length
167
+ : (firstVal && Array.isArray(firstVal.embedding) ? firstVal.embedding.length : 0);
168
+
169
+ if (existingDim > 0 && existingDim !== newDim) {
170
+ // Dimension mismatch detected! Wipe the old index to re-embed everything
171
+ existing.clear();
172
+ try {
173
+ const file = path.join(derivedDir, EMBEDDINGS_FILE);
174
+ if (fs.existsSync(file)) {
175
+ fs.unlinkSync(file);
176
+ }
177
+ } catch {}
178
+ }
179
+ }
180
+
181
+ // Filter nodes that need embedding
182
+ const toEmbed = nodes.filter(n => {
183
+ const cached = existing.get(n.slug);
184
+ if (!cached) return true;
185
+
186
+ const cachedHash = cached.content_sha256 || '';
187
+ const text = [n.title, n.body || n.content].filter(Boolean).join('\n').slice(0, 4096);
188
+ const hash = crypto.createHash('sha256').update(text).digest('hex');
189
+ return !cachedHash || cachedHash !== hash;
190
+ });
191
+
192
+ if (toEmbed.length === 0) {
193
+ return { indexed: 0, skipped: nodes.length, unavailable: false };
194
+ }
195
+
196
+ // Save the probe result we already generated for the first node if it is in toEmbed
197
+ const firstNode = toEmbed.find(n => n.slug === nodes[0].slug);
198
+ if (firstNode) {
199
+ const firstHash = crypto.createHash('sha256').update(firstText).digest('hex');
200
+ const firstVec = probe;
201
+ Object.defineProperty(firstVec, 'embedding', {
202
+ get() { return this; },
203
+ enumerable: false,
204
+ configurable: true
205
+ });
206
+ Object.defineProperty(firstVec, 'content_sha256', {
207
+ value: firstHash,
208
+ writable: true,
209
+ enumerable: false,
210
+ configurable: true
211
+ });
212
+ existing.set(firstNode.slug, firstVec);
213
+ }
214
+
215
+ // Embed the remaining nodes concurrently
216
+ const remainingToEmbed = toEmbed.filter(n => n.slug !== nodes[0].slug);
217
+ await throttledMap(remainingToEmbed, 6, async (node) => {
218
+ const text = [node.title, node.body || node.content].filter(Boolean).join('\n').slice(0, 4096);
219
+ const vec = await generateEmbedding(text, opts);
220
+ if (vec) {
221
+ const hash = crypto.createHash('sha256').update(text).digest('hex');
222
+ const vecWithHash = vec;
223
+ Object.defineProperty(vecWithHash, 'embedding', {
224
+ get() { return this; },
225
+ enumerable: false,
226
+ configurable: true
227
+ });
228
+ Object.defineProperty(vecWithHash, 'content_sha256', {
229
+ value: hash,
230
+ writable: true,
231
+ enumerable: false,
232
+ configurable: true
233
+ });
234
+ existing.set(node.slug, vecWithHash);
235
+ }
236
+ });
237
+
238
+ writeEmbeddingsIndex(derivedDir, existing);
239
+ const indexed = toEmbed.filter(n => existing.has(n.slug)).length;
240
+ return { indexed, skipped: nodes.length - toEmbed.length, unavailable: false };
241
+ }
242
+
243
+ // ─── Semantic search ─────────────────────────────────────────────────────────────
244
+
245
+ /**
246
+ * Search the embeddings index for nodes similar to `query`.
247
+ * Returns an array of { slug, score } sorted by descending cosine similarity.
248
+ *
249
+ * @param {string} query
250
+ * @param {string} derivedDir
251
+ * @param {object} [opts]
252
+ * @param {string} [opts.endpoint]
253
+ * @param {string} [opts.model]
254
+ * @param {number} [opts.topK] Max results (default: 10)
255
+ * @param {number} [opts.threshold] Min cosine similarity (default: 0.5)
256
+ * @returns {Promise<Array<{ slug: string, score: number }>>}
257
+ */
258
+ export async function semanticSearch(query, derivedDir, opts = {}) {
259
+ const topK = opts.topK ?? 10;
260
+ const threshold = opts.threshold ?? 0.5;
261
+ const index = loadEmbeddingsIndex(derivedDir);
262
+ if (index.size === 0) return [];
263
+
264
+ const queryVec = await generateEmbedding(query, opts);
265
+ if (!queryVec) return [];
266
+
267
+ const scored = [];
268
+ for (const [slug, val] of index) {
269
+ const vec = val && Array.isArray(val.embedding) ? val.embedding : (Array.isArray(val) ? val : null);
270
+ if (!vec) continue;
271
+ const score = cosineSimilarity(queryVec, vec);
272
+ if (score >= threshold) scored.push({ slug, score });
273
+ }
274
+
275
+ return scored.sort((a, b) => b.score - a.score).slice(0, topK);
276
+ }