total-recall-brain 3.13.0 → 3.13.2

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 (152) hide show
  1. package/README.md +91 -108
  2. package/bin/antigravity.mjs +81 -52
  3. package/bin/total-recall.mjs +32 -57
  4. package/extension/background.js +1 -1
  5. package/extension/options/options.js +9 -3
  6. package/extension/sidepanel/sidepanel.js +0 -2
  7. package/frontend/index.html +4 -3
  8. package/frontend/package.json +1 -0
  9. package/frontend/src/App.tsx +109 -152
  10. package/frontend/src/api.ts +605 -23
  11. package/frontend/src/components/BrainSelector.tsx +211 -199
  12. package/frontend/src/components/DocumentEditorModal.tsx +57 -0
  13. package/frontend/src/components/DocumentTable.tsx +47 -0
  14. package/frontend/src/components/Graph3D.tsx +120 -71
  15. package/frontend/src/components/UsageChart.tsx +3 -3
  16. package/frontend/src/components/brand/BrandMark.tsx +95 -0
  17. package/frontend/src/index.css +286 -86
  18. package/frontend/src/pages/ApiKeysPage.tsx +1878 -215
  19. package/frontend/src/pages/ChatPage.tsx +103 -45
  20. package/frontend/src/pages/DesignDocsPage.tsx +5 -5
  21. package/frontend/src/pages/FilesPage.tsx +10 -6
  22. package/frontend/src/pages/GraphPage.tsx +5 -3
  23. package/frontend/src/pages/HelpPage.tsx +2 -2
  24. package/frontend/src/pages/InboxPage.tsx +82 -0
  25. package/frontend/src/pages/InstructionsPage.tsx +1 -1
  26. package/frontend/src/pages/IntegrationsPage.tsx +21 -10
  27. package/frontend/src/pages/LoginPage.tsx +95 -77
  28. package/frontend/src/pages/MemoryPage.tsx +31 -8
  29. package/frontend/src/pages/OnboardingPage.tsx +690 -0
  30. package/frontend/src/pages/OpenWikiPage.tsx +412 -323
  31. package/frontend/src/pages/SettingsPage.tsx +825 -922
  32. package/frontend/src/pages/SkillsPage.tsx +272 -147
  33. package/frontend/src/pages/UsagePage.tsx +23 -31
  34. package/frontend/src/pages/VaultPage.tsx +223 -0
  35. package/frontend/src/types.ts +13 -0
  36. package/package.json +4 -4
  37. package/scaffold/.agent/skills/total-recall/SKILL.md +43 -4
  38. package/scaffold/.agent/skills/total-recall/modules/README.md +10 -0
  39. package/scaffold/.agent/skills/total-recall/modules/agents/README.md +5 -0
  40. package/scaffold/.agent/skills/total-recall/{skills/tr-cli-agents → modules/agents}/agents.yml +8 -0
  41. package/scaffold/.agent/skills/total-recall/modules/skill-deploy/README.md +9 -0
  42. package/scaffold/.agent/skills/total-recall/openwiki/README.md +10 -0
  43. package/scaffold/.agent/skills/total-recall/openwiki/architecture.md +5 -0
  44. package/scaffold/.agent/skills/total-recall/openwiki/ide.md +10 -0
  45. package/scaffold/.agent/skills/total-recall/openwiki/memory.md +11 -0
  46. package/scaffold/.agent/skills/total-recall/openwiki/secrets.md +7 -0
  47. package/scaffold/.agent/skills/total-recall/openwiki/skills.md +7 -0
  48. package/src/cli/brain.mjs +63 -5
  49. package/src/cli/connect.mjs +11 -19
  50. package/src/cli/daemon.mjs +5 -4
  51. package/src/cli/deploy-ui.mjs +8 -3
  52. package/src/cli/deploy.mjs +84 -27
  53. package/src/cli/ingest/index.mjs +1 -1
  54. package/src/cli/init.mjs +63 -34
  55. package/src/cli/remember.mjs +3 -3
  56. package/src/cli/secret.mjs +812 -0
  57. package/src/cli/setup.mjs +34 -7
  58. package/src/cli/share.mjs +2 -2
  59. package/src/cli/skill.mjs +396 -35
  60. package/src/cli/sync.mjs +4 -4
  61. package/src/cli/task.mjs +287 -0
  62. package/src/core/clarity-rewriter.mjs +5 -5
  63. package/src/core/config.mjs +44 -2
  64. package/src/core/conflict-detector.mjs +5 -5
  65. package/src/core/crons.mjs +54 -0
  66. package/src/core/daemon-loop.mjs +87 -235
  67. package/src/core/dream.mjs +170 -10
  68. package/src/core/env-import.mjs +456 -0
  69. package/src/core/fact-seeker.mjs +14 -14
  70. package/src/core/import-rules.mjs +20 -7
  71. package/src/core/inference-engine.mjs +3 -3
  72. package/src/core/migrate.mjs +14 -8
  73. package/src/core/okf-adapter.mjs +2 -2
  74. package/src/core/operation-validator.mjs +39 -331
  75. package/src/core/optimizer.mjs +7 -0
  76. package/src/core/post-mortem.mjs +2 -2
  77. package/src/core/project-brain.mjs +349 -0
  78. package/src/core/provider-catalog.mjs +296 -0
  79. package/src/core/quick-capture.mjs +1 -1
  80. package/src/core/remote-vault-sync.mjs +194 -0
  81. package/src/core/research-queue.mjs +7 -11
  82. package/src/core/runtime.mjs +33 -9
  83. package/src/core/scheduler.mjs +59 -5
  84. package/src/core/schema.mjs +14 -0
  85. package/src/core/search.mjs +1 -1
  86. package/src/core/secrets-env-export.mjs +323 -0
  87. package/src/core/secrets-rotate.mjs +245 -0
  88. package/src/core/secrets-store.mjs +645 -0
  89. package/src/core/semantic-index.mjs +3 -3
  90. package/src/core/skills-registry.mjs +1062 -0
  91. package/src/core/source-adapters.mjs +4 -1
  92. package/src/core/ssss-host-extension.mjs +94 -0
  93. package/src/core/ssss-kernel-bridge.mjs +530 -0
  94. package/src/core/surface.mjs +14 -0
  95. package/src/core/task-envelope.mjs +298 -0
  96. package/src/core/task-executors.mjs +600 -0
  97. package/src/core/validated-write.mjs +124 -56
  98. package/src/core/vault.mjs +21 -34
  99. package/src/core/webauthn-store.mjs +272 -0
  100. package/src/server/api.mjs +26 -16
  101. package/src/server/auth.mjs +51 -0
  102. package/src/server/index.mjs +3 -3
  103. package/src/server/keys.mjs +30 -4
  104. package/src/server/rest.mjs +198 -25
  105. package/src/server/route-manifest.json +156 -0
  106. package/src/server/routes/_shared.mjs +40 -1
  107. package/src/server/routes/docs.mjs +260 -0
  108. package/src/server/routes/memory.mjs +145 -32
  109. package/src/server/routes/research.mjs +30 -0
  110. package/src/server/routes/secrets.mjs +470 -0
  111. package/src/server/routes/share.mjs +2 -2
  112. package/src/server/routes/skills.mjs +452 -54
  113. package/src/server/routes/sync.mjs +48 -0
  114. package/src/server/routes/webauthn.mjs +184 -0
  115. package/src/server/update-route-manifest.mjs +39 -0
  116. package/templates/onboarding-interview.md +32 -22
  117. package/templates/openwiki/README.md +10 -0
  118. package/templates/openwiki/architecture.md +5 -0
  119. package/templates/openwiki/ide.md +10 -0
  120. package/templates/openwiki/memory.md +11 -0
  121. package/templates/openwiki/secrets.md +7 -0
  122. package/templates/openwiki/skills.md +7 -0
  123. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/SKILL.md +0 -121
  124. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/antigravity.txt +0 -20
  125. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/claude.txt +0 -58
  126. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/codex.txt +0 -81
  127. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/gemini.txt +0 -44
  128. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/scripts/.gitkeep +0 -0
  129. package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/subagents/.gitkeep +0 -0
  130. package/scaffold/.agent/skills/total-recall/skills/tr-research/SKILL.md +0 -338
  131. package/scaffold/.agent/skills/total-recall/skills/tr-skill/SKILL.md +0 -282
  132. package/scaffold/.agent/skills/total-recall/skills/tr-skill/assets/skill-template/SKILL.md +0 -20
  133. package/scaffold/.agent/skills/total-recall/skills/tr-skill/evals/evals.json +0 -20
  134. package/scaffold/.agent/skills/total-recall/skills/tr-skill/hooks/on-skill-add.sh +0 -14
  135. package/scaffold/.agent/skills/total-recall/skills/tr-skill/hooks/on-skill-delete.sh +0 -14
  136. package/scaffold/.agent/skills/total-recall/skills/tr-skill/learnings/.gitkeep +0 -0
  137. package/scaffold/.agent/skills/total-recall/skills/tr-skill/references/.gitkeep +0 -0
  138. package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/.gitkeep +0 -0
  139. package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/create-skill.sh +0 -65
  140. package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/enforce-skill-optimization.mjs +0 -135
  141. package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/skill-watcher.mjs +0 -64
  142. package/scaffold/.agent/skills/total-recall/skills/tr-skill/subagents/audit-all-skills.md +0 -62
  143. package/scaffold/.agent/skills/total-recall/skills/tr-ssss/SKILL.md +0 -298
  144. package/scaffold/.agent/skills/total-recall/skills/tr-ssss/evals/evals.json +0 -17
  145. package/scaffold/.agent/skills/total-recall/skills/tr-ssss/references/CHANGELOG.md +0 -124
  146. package/scaffold/.agent/skills/total-recall/skills/tr-ssss/references/ssss-spec.md +0 -1325
  147. package/scaffold/.agent/skills/total-recall/skills/tr-ssss/scripts/validate-schema.mjs +0 -232
  148. package/scaffold/.agent/skills/total-recall/skills/tr-ssss/subagents/audit-ssss-node.md +0 -9
  149. /package/scaffold/.agent/skills/total-recall/{skills/tr-cli-agents/evals → modules/skill-deploy/scripts}/.gitkeep +0 -0
  150. /package/scaffold/.agent/skills/total-recall/{skills/tr-skill → modules/skill-deploy}/scripts/find-skills.mjs +0 -0
  151. /package/scaffold/.agent/skills/total-recall/{skills/tr-skill → modules/skill-deploy}/scripts/install-skill.mjs +0 -0
  152. /package/scaffold/.agent/skills/total-recall/{skills/tr-skill → modules/skill-deploy}/scripts/scan-skill.mjs +0 -0
@@ -0,0 +1,349 @@
1
+ /**
2
+ * Ensure a repo has a complete Total Recall *project brain*
3
+ * (not just skills folders).
4
+ *
5
+ * Layout:
6
+ * <repo>/.agent/skills/total-recall/ ← brain root
7
+ * memory-vault/{categories}/
8
+ * memory-inbox/{pending,conflicts}/
9
+ * memory-derived/
10
+ * sessions/
11
+ * scheduler/queue/
12
+ * openwiki/
13
+ * skills-registry/
14
+ * config/brain.json
15
+ * <repo>/.agent/skills/<user-skills>/ ← sibling agent skills
16
+ */
17
+
18
+ import fs from 'fs';
19
+ import path from 'path';
20
+ import { fileURLToPath } from 'url';
21
+ import YAML from 'yaml';
22
+
23
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
24
+ const ROOT = path.resolve(__dirname, '../..');
25
+
26
+ export const VAULT_CATEGORIES = Object.freeze([
27
+ 'invariants',
28
+ 'patterns',
29
+ 'anti-patterns',
30
+ 'preferences',
31
+ 'decisions',
32
+ 'concepts',
33
+ 'facts',
34
+ 'corrections',
35
+ 'lore',
36
+ ]);
37
+
38
+ /**
39
+ * @param {string} repoRoot
40
+ * @returns {{ agentDir: string, brainDir: string, skillsDir: string }}
41
+ */
42
+ export function resolveProjectBrainPaths(repoRoot) {
43
+ const root = path.resolve(repoRoot);
44
+ const agentDir = path.join(root, '.agent');
45
+ const skillsDir = path.join(agentDir, 'skills');
46
+ const brainDir = path.join(skillsDir, 'total-recall');
47
+ return { agentDir, brainDir, skillsDir, repoRoot: root };
48
+ }
49
+
50
+ function ensureDir(dir) {
51
+ if (!fs.existsSync(dir)) {
52
+ fs.mkdirSync(dir, { recursive: true });
53
+ return true;
54
+ }
55
+ return false;
56
+ }
57
+
58
+ function copyDirMerge(src, dest) {
59
+ if (!fs.existsSync(src)) return 0;
60
+ let n = 0;
61
+ ensureDir(dest);
62
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
63
+ const from = path.join(src, entry.name);
64
+ const to = path.join(dest, entry.name);
65
+ if (entry.isDirectory()) {
66
+ n += copyDirMerge(from, to);
67
+ } else if (!fs.existsSync(to)) {
68
+ fs.copyFileSync(from, to);
69
+ n++;
70
+ }
71
+ }
72
+ return n;
73
+ }
74
+
75
+ /**
76
+ * Seed openwiki pages (templates if packaged).
77
+ */
78
+ export function ensureOpenWiki(brainDir) {
79
+ const wikiDir = path.join(brainDir, 'openwiki');
80
+ const created = ensureDir(wikiDir);
81
+ const templateRoot = path.join(ROOT, 'templates', 'openwiki');
82
+ let files = 0;
83
+ if (fs.existsSync(templateRoot)) {
84
+ files = copyDirMerge(templateRoot, wikiDir);
85
+ } else {
86
+ const pages = {
87
+ 'README.md': `# OpenWiki\n\nProject knowledge docs for this Total Recall brain.\n`,
88
+ 'memory.md': `# Memory\n\nUse \`npx total-recall remember\` / \`recall\` with --project.\n`,
89
+ 'skills.md': `# Skills\n\nUser skills: \`.agent/skills/<name>/SKILL.md\`.\n`,
90
+ 'ide.md': `# IDE\n\n\`npx total-recall connect <ide>\`\n`,
91
+ 'secrets.md': `# Secrets\n\nNever put secrets in openwiki or vault markdown.\n`,
92
+ };
93
+ for (const [name, body] of Object.entries(pages)) {
94
+ const dest = path.join(wikiDir, name);
95
+ if (!fs.existsSync(dest)) {
96
+ fs.writeFileSync(dest, body);
97
+ files++;
98
+ }
99
+ }
100
+ }
101
+ return { wikiDir, created, files_added: files };
102
+ }
103
+
104
+ /**
105
+ * Ensure core total-recall skill package files exist (SKILL.md minimum).
106
+ */
107
+ export function ensureCoreSkillPackage(brainDir) {
108
+ const skillMd = path.join(brainDir, 'SKILL.md');
109
+ if (fs.existsSync(skillMd)) return { seeded: false };
110
+ const scaffold = path.join(ROOT, 'scaffold', '.agent', 'skills', 'total-recall');
111
+ if (fs.existsSync(scaffold)) {
112
+ copyDirMerge(scaffold, brainDir);
113
+ return { seeded: true, from: 'scaffold' };
114
+ }
115
+ // Minimal SKILL.md so agents recognize the brain package
116
+ fs.writeFileSync(
117
+ skillMd,
118
+ `---
119
+ name: total-recall
120
+ description: "Use this skill to operate Total Recall — portable memory, instructions, openwiki, skill deploy, and secrets."
121
+ ---
122
+
123
+ # Total Recall — Project Brain
124
+
125
+ This directory is a **project brain**. Use CLI:
126
+
127
+ \`\`\`bash
128
+ npx total-recall remember fact "..." --project
129
+ npx total-recall recall "..." --project
130
+ npx total-recall compile
131
+ npx total-recall dream
132
+ \`\`\`
133
+ `,
134
+ );
135
+ return { seeded: true, from: 'minimal' };
136
+ }
137
+
138
+ /**
139
+ * Write/update brain.json identity for a project brain.
140
+ */
141
+ export function writeBrainIdentity(brainDir, { name, role = 'project', tags = [] } = {}) {
142
+ const configDir = path.join(brainDir, 'config');
143
+ ensureDir(configDir);
144
+ const brainJsonPath = path.join(configDir, 'brain.json');
145
+ let current = {};
146
+ if (fs.existsSync(brainJsonPath)) {
147
+ try {
148
+ current = JSON.parse(fs.readFileSync(brainJsonPath, 'utf8')) || {};
149
+ } catch {
150
+ current = {};
151
+ }
152
+ }
153
+ const next = {
154
+ ...current,
155
+ name: name || current.name || path.basename(path.resolve(brainDir, '../../..')),
156
+ role: 'project',
157
+ layer: 'project',
158
+ full_brain: true,
159
+ tags: [...new Set([...(current.tags || []), ...tags, 'project-brain'])],
160
+ updated_at: new Date().toISOString(),
161
+ created_at: current.created_at || new Date().toISOString(),
162
+ };
163
+ fs.writeFileSync(brainJsonPath, JSON.stringify(next, null, 2), { encoding: 'utf8', mode: 0o600 });
164
+ return next;
165
+ }
166
+
167
+ /**
168
+ * Ensure a full project brain under repoRoot/.agent/skills/total-recall/.
169
+ *
170
+ * @param {string} repoRoot
171
+ * @param {object} [opts]
172
+ * @param {string} [opts.name]
173
+ * @param {string[]} [opts.tags]
174
+ * @param {string} [opts.globalBrainDir] - for project-registry registration
175
+ * @param {boolean} [opts.register=true]
176
+ */
177
+ export function ensureFullProjectBrain(repoRoot, opts = {}) {
178
+ const { agentDir, brainDir, skillsDir } = resolveProjectBrainPaths(repoRoot);
179
+ const name = opts.name || path.basename(path.resolve(repoRoot));
180
+ const tags = opts.tags || [];
181
+ const created = [];
182
+
183
+ if (ensureDir(agentDir)) created.push('agentDir');
184
+ if (ensureDir(skillsDir)) created.push('skillsDir');
185
+ if (ensureDir(brainDir)) created.push('brainDir');
186
+
187
+ const dataDirs = [
188
+ path.join(brainDir, 'memory-derived'),
189
+ path.join(brainDir, 'memory-inbox', 'pending'),
190
+ path.join(brainDir, 'memory-inbox', 'conflicts'),
191
+ path.join(brainDir, 'memory-inbox', 'capture'),
192
+ path.join(brainDir, 'sessions'),
193
+ path.join(brainDir, 'scheduler', 'queue'),
194
+ path.join(brainDir, 'skills-registry'),
195
+ path.join(brainDir, 'config'),
196
+ path.join(brainDir, 'logs'),
197
+ ...VAULT_CATEGORIES.map((c) => path.join(brainDir, 'memory-vault', c)),
198
+ ];
199
+ let dirsCreated = 0;
200
+ for (const d of dataDirs) {
201
+ if (ensureDir(d)) dirsCreated++;
202
+ }
203
+
204
+ const core = ensureCoreSkillPackage(brainDir);
205
+ const wiki = ensureOpenWiki(brainDir);
206
+ const identity = writeBrainIdentity(brainDir, { name, tags });
207
+
208
+ // Empty skills registry if missing
209
+ const regPath = path.join(brainDir, 'skills-registry', 'index.yaml');
210
+ if (!fs.existsSync(regPath)) {
211
+ fs.writeFileSync(
212
+ regPath,
213
+ YAML.stringify({
214
+ version: 1,
215
+ updated_at: new Date().toISOString(),
216
+ skills: {},
217
+ installs: [],
218
+ }),
219
+ );
220
+ }
221
+
222
+ let registry = null;
223
+ if (opts.register !== false) {
224
+ const home = process.env.HOME || process.env.USERPROFILE || '';
225
+ const gBrain =
226
+ opts.globalBrainDir || path.join(home, '.agent', 'skills', 'total-recall');
227
+ registry = registerProjectBrain(gBrain, {
228
+ name,
229
+ path: path.resolve(repoRoot),
230
+ brainDir,
231
+ tags: [...tags, 'full-brain', 'project-brain'],
232
+ });
233
+ }
234
+
235
+ return {
236
+ repoRoot: path.resolve(repoRoot),
237
+ agentDir,
238
+ brainDir,
239
+ skillsDir,
240
+ full_brain: true,
241
+ identity,
242
+ openwiki: wiki,
243
+ core,
244
+ dirs_created: dirsCreated,
245
+ created_top: created,
246
+ registry,
247
+ };
248
+ }
249
+
250
+ /**
251
+ * Register (or update) a project brain in global config/project-registry.json.
252
+ */
253
+ export function registerProjectBrain(globalBrainDir, entry) {
254
+ const registryPath = path.join(globalBrainDir, 'config', 'project-registry.json');
255
+ ensureDir(path.dirname(registryPath));
256
+ let list = [];
257
+ if (fs.existsSync(registryPath)) {
258
+ try {
259
+ list = JSON.parse(fs.readFileSync(registryPath, 'utf8')) || [];
260
+ } catch {
261
+ list = [];
262
+ }
263
+ }
264
+ if (!Array.isArray(list)) list = [];
265
+
266
+ const abs = path.resolve(entry.path);
267
+ const idx = list.findIndex((p) => path.resolve(p.path || '') === abs);
268
+ const now = new Date().toISOString();
269
+ const row = {
270
+ name: entry.name,
271
+ path: abs,
272
+ brainDir: entry.brainDir,
273
+ full_brain: true,
274
+ layer: 'project',
275
+ tags: entry.tags || ['project-brain'],
276
+ registered_at: idx >= 0 ? list[idx].registered_at || now : now,
277
+ last_compiled: entry.last_compiled || list[idx]?.last_compiled || null,
278
+ updated_at: now,
279
+ };
280
+ if (idx >= 0) list[idx] = { ...list[idx], ...row };
281
+ else list.push(row);
282
+ fs.writeFileSync(registryPath, JSON.stringify(list, null, 2));
283
+ return row;
284
+ }
285
+
286
+ /**
287
+ * Ensure a path is a full project brain and register it.
288
+ * Requires an explicit repoRoot — no hardcoded product repos (open-source safe).
289
+ */
290
+ export function ensureAndRegisterProjectBrain(opts = {}) {
291
+ const repoRoot = opts.repoRoot;
292
+ if (!repoRoot) {
293
+ return {
294
+ ok: false,
295
+ error:
296
+ 'repoRoot required. Example: total-recall brain ensure /path/to/your/project',
297
+ };
298
+ }
299
+ if (!fs.existsSync(repoRoot)) {
300
+ return {
301
+ ok: false,
302
+ error: `Project path not found: ${repoRoot}`,
303
+ };
304
+ }
305
+
306
+ const name = opts.name || path.basename(path.resolve(repoRoot));
307
+ const result = ensureFullProjectBrain(repoRoot, {
308
+ name,
309
+ tags: [...(opts.tags || []), 'full-brain'],
310
+ globalBrainDir: opts.globalBrainDir,
311
+ register: true,
312
+ });
313
+
314
+ return { ok: true, ...result };
315
+ }
316
+
317
+ /**
318
+ * Quick health check: is this path a full project brain?
319
+ */
320
+ export function inspectProjectBrain(repoRoot) {
321
+ const { brainDir, skillsDir } = resolveProjectBrainPaths(repoRoot);
322
+ const checks = {
323
+ brainDir: fs.existsSync(brainDir),
324
+ skillMd: fs.existsSync(path.join(brainDir, 'SKILL.md')),
325
+ memoryVault: fs.existsSync(path.join(brainDir, 'memory-vault')),
326
+ openwiki: fs.existsSync(path.join(brainDir, 'openwiki')),
327
+ sessions: fs.existsSync(path.join(brainDir, 'sessions')),
328
+ scheduler: fs.existsSync(path.join(brainDir, 'scheduler')),
329
+ skillsRegistry: fs.existsSync(path.join(brainDir, 'skills-registry')),
330
+ config: fs.existsSync(path.join(brainDir, 'config')),
331
+ siblingSkills: fs.existsSync(skillsDir),
332
+ };
333
+ const brainJsonPath = path.join(brainDir, 'config', 'brain.json');
334
+ let identity = null;
335
+ if (fs.existsSync(brainJsonPath)) {
336
+ try {
337
+ identity = JSON.parse(fs.readFileSync(brainJsonPath, 'utf8'));
338
+ } catch {}
339
+ }
340
+ const complete = Object.values(checks).every(Boolean);
341
+ return {
342
+ repoRoot: path.resolve(repoRoot),
343
+ brainDir,
344
+ checks,
345
+ complete,
346
+ full_brain: Boolean(identity?.full_brain) || complete,
347
+ identity,
348
+ };
349
+ }
@@ -0,0 +1,296 @@
1
+ /**
2
+ * Static provider catalog — docs, schema hints, default subscription fields.
3
+ * Used by secrets UI/CLI; no secret values stored here.
4
+ */
5
+
6
+ /** @typedef {{
7
+ * id: string,
8
+ * name: string,
9
+ * docs_url: string,
10
+ * pricing_url?: string,
11
+ * console_url?: string,
12
+ * key_patterns: string[],
13
+ * schema: { auth: string, header?: string, env_keys: string[], notes?: string },
14
+ * default_monthly_cap_usd?: number | null,
15
+ * tiers?: { id: string, label: string, monthly_usd?: number | null }[],
16
+ * }} ProviderDef */
17
+
18
+ /** @type {ProviderDef[]} */
19
+ export const PROVIDER_CATALOG = [
20
+ {
21
+ id: 'openai',
22
+ name: 'OpenAI',
23
+ docs_url: 'https://platform.openai.com/docs/api-reference',
24
+ pricing_url: 'https://openai.com/api/pricing/',
25
+ console_url: 'https://platform.openai.com/api-keys',
26
+ key_patterns: ['OPENAI_API_KEY', 'AZURE_OPENAI_API_KEY'],
27
+ schema: {
28
+ auth: 'bearer',
29
+ header: 'Authorization: Bearer <key>',
30
+ env_keys: ['OPENAI_API_KEY'],
31
+ notes: 'Chat Completions + Responses APIs; org optional OPENAI_ORG_ID',
32
+ },
33
+ default_monthly_cap_usd: 50,
34
+ tiers: [
35
+ { id: 'free', label: 'Free / trial', monthly_usd: 0 },
36
+ { id: 'payg', label: 'Pay as you go', monthly_usd: null },
37
+ { id: 'team', label: 'Team', monthly_usd: null },
38
+ ],
39
+ },
40
+ {
41
+ id: 'anthropic',
42
+ name: 'Anthropic',
43
+ docs_url: 'https://docs.anthropic.com/en/api/getting-started',
44
+ pricing_url: 'https://www.anthropic.com/pricing',
45
+ console_url: 'https://console.anthropic.com/settings/keys',
46
+ key_patterns: ['ANTHROPIC_API_KEY'],
47
+ schema: {
48
+ auth: 'header',
49
+ header: 'x-api-key: <key>',
50
+ env_keys: ['ANTHROPIC_API_KEY'],
51
+ notes: 'Messages API; anthropic-version header required',
52
+ },
53
+ default_monthly_cap_usd: 50,
54
+ tiers: [
55
+ { id: 'free', label: 'Free', monthly_usd: 0 },
56
+ { id: 'build', label: 'Build / payg', monthly_usd: null },
57
+ { id: 'scale', label: 'Scale', monthly_usd: null },
58
+ ],
59
+ },
60
+ {
61
+ id: 'google',
62
+ name: 'Google AI / Gemini',
63
+ docs_url: 'https://ai.google.dev/gemini-api/docs',
64
+ pricing_url: 'https://ai.google.dev/pricing',
65
+ console_url: 'https://aistudio.google.com/apikey',
66
+ key_patterns: ['GOOGLE_API_KEY', 'GEMINI_API_KEY', 'GOOGLE_GENERATIVE_AI_API_KEY'],
67
+ schema: {
68
+ auth: 'query_or_header',
69
+ header: 'x-goog-api-key: <key>',
70
+ env_keys: ['GOOGLE_API_KEY', 'GEMINI_API_KEY'],
71
+ },
72
+ default_monthly_cap_usd: 25,
73
+ tiers: [
74
+ { id: 'free', label: 'Free tier', monthly_usd: 0 },
75
+ { id: 'payg', label: 'Paid', monthly_usd: null },
76
+ ],
77
+ },
78
+ {
79
+ id: 'openrouter',
80
+ name: 'OpenRouter',
81
+ docs_url: 'https://openrouter.ai/docs',
82
+ pricing_url: 'https://openrouter.ai/models',
83
+ console_url: 'https://openrouter.ai/keys',
84
+ key_patterns: ['OPENROUTER_API_KEY'],
85
+ schema: {
86
+ auth: 'bearer',
87
+ header: 'Authorization: Bearer <key>',
88
+ env_keys: ['OPENROUTER_API_KEY'],
89
+ notes: 'OpenAI-compatible base URL https://openrouter.ai/api/v1',
90
+ },
91
+ default_monthly_cap_usd: 30,
92
+ tiers: [{ id: 'payg', label: 'Pay as you go', monthly_usd: null }],
93
+ },
94
+ {
95
+ id: 'xai',
96
+ name: 'xAI (Grok)',
97
+ docs_url: 'https://docs.x.ai/docs',
98
+ console_url: 'https://console.x.ai',
99
+ key_patterns: ['XAI_API_KEY', 'GROK_API_KEY'],
100
+ schema: {
101
+ auth: 'bearer',
102
+ header: 'Authorization: Bearer <key>',
103
+ env_keys: ['XAI_API_KEY'],
104
+ },
105
+ default_monthly_cap_usd: 30,
106
+ tiers: [{ id: 'payg', label: 'Pay as you go', monthly_usd: null }],
107
+ },
108
+ {
109
+ id: 'brave',
110
+ name: 'Brave Search',
111
+ docs_url: 'https://api-dashboard.search.brave.com/app/documentation',
112
+ console_url: 'https://api-dashboard.search.brave.com/',
113
+ key_patterns: ['BRAVE_SEARCH_API_KEY', 'BRAVE_API_KEY'],
114
+ schema: {
115
+ auth: 'header',
116
+ header: 'X-Subscription-Token: <key>',
117
+ env_keys: ['BRAVE_SEARCH_API_KEY'],
118
+ },
119
+ default_monthly_cap_usd: 5,
120
+ tiers: [
121
+ { id: 'free', label: 'Free', monthly_usd: 0 },
122
+ { id: 'base', label: 'Base', monthly_usd: 5 },
123
+ { id: 'pro', label: 'Pro', monthly_usd: null },
124
+ ],
125
+ },
126
+ {
127
+ id: 'elevenlabs',
128
+ name: 'ElevenLabs',
129
+ docs_url: 'https://elevenlabs.io/docs/api-reference',
130
+ pricing_url: 'https://elevenlabs.io/pricing',
131
+ console_url: 'https://elevenlabs.io/app/settings/api-keys',
132
+ key_patterns: ['ELEVENLABS_API_KEY', 'ELEVEN_LABS_API_KEY'],
133
+ schema: {
134
+ auth: 'header',
135
+ header: 'xi-api-key: <key>',
136
+ env_keys: ['ELEVENLABS_API_KEY'],
137
+ },
138
+ default_monthly_cap_usd: 22,
139
+ tiers: [
140
+ { id: 'free', label: 'Free', monthly_usd: 0 },
141
+ { id: 'starter', label: 'Starter', monthly_usd: 5 },
142
+ { id: 'creator', label: 'Creator', monthly_usd: 22 },
143
+ { id: 'pro', label: 'Pro', monthly_usd: 99 },
144
+ ],
145
+ },
146
+ {
147
+ id: 'github',
148
+ name: 'GitHub',
149
+ docs_url: 'https://docs.github.com/en/rest',
150
+ console_url: 'https://github.com/settings/tokens',
151
+ key_patterns: ['GITHUB_TOKEN', 'GH_TOKEN'],
152
+ schema: {
153
+ auth: 'bearer',
154
+ header: 'Authorization: Bearer <token>',
155
+ env_keys: ['GITHUB_TOKEN', 'GH_TOKEN'],
156
+ },
157
+ default_monthly_cap_usd: null,
158
+ tiers: [
159
+ { id: 'free', label: 'Free', monthly_usd: 0 },
160
+ { id: 'pro', label: 'Pro', monthly_usd: 4 },
161
+ { id: 'team', label: 'Team', monthly_usd: 4 },
162
+ ],
163
+ },
164
+ {
165
+ id: 'supabase',
166
+ name: 'Supabase',
167
+ docs_url: 'https://supabase.com/docs/reference/javascript/introduction',
168
+ console_url: 'https://supabase.com/dashboard',
169
+ key_patterns: ['SUPABASE_URL', 'SUPABASE_ANON_KEY', 'SUPABASE_SERVICE_ROLE_KEY'],
170
+ schema: {
171
+ auth: 'header',
172
+ header: 'apikey + Authorization Bearer',
173
+ env_keys: ['SUPABASE_URL', 'SUPABASE_ANON_KEY', 'SUPABASE_SERVICE_ROLE_KEY'],
174
+ },
175
+ default_monthly_cap_usd: 25,
176
+ tiers: [
177
+ { id: 'free', label: 'Free', monthly_usd: 0 },
178
+ { id: 'pro', label: 'Pro', monthly_usd: 25 },
179
+ { id: 'team', label: 'Team', monthly_usd: 599 },
180
+ ],
181
+ },
182
+ {
183
+ id: 'digitalocean',
184
+ name: 'DigitalOcean',
185
+ docs_url: 'https://docs.digitalocean.com/reference/api/',
186
+ console_url: 'https://cloud.digitalocean.com/account/api/tokens',
187
+ key_patterns: ['DIGITALOCEAN_API_TOKEN', 'DO_API_TOKEN', 'DIGITALOCEAN_ACCESS_TOKEN'],
188
+ schema: {
189
+ auth: 'bearer',
190
+ header: 'Authorization: Bearer <token>',
191
+ env_keys: ['DIGITALOCEAN_API_TOKEN'],
192
+ },
193
+ default_monthly_cap_usd: null,
194
+ tiers: [{ id: 'payg', label: 'Pay as you go', monthly_usd: null }],
195
+ },
196
+ {
197
+ id: 'huggingface',
198
+ name: 'Hugging Face',
199
+ docs_url: 'https://huggingface.co/docs/api-inference',
200
+ console_url: 'https://huggingface.co/settings/tokens',
201
+ key_patterns: ['HUGGINGFACE_API_KEY', 'HF_TOKEN'],
202
+ schema: {
203
+ auth: 'bearer',
204
+ header: 'Authorization: Bearer <token>',
205
+ env_keys: ['HF_TOKEN'],
206
+ },
207
+ default_monthly_cap_usd: 9,
208
+ tiers: [
209
+ { id: 'free', label: 'Free', monthly_usd: 0 },
210
+ { id: 'pro', label: 'Pro', monthly_usd: 9 },
211
+ ],
212
+ },
213
+ {
214
+ id: 'serper',
215
+ name: 'Serper',
216
+ docs_url: 'https://serper.dev/docs',
217
+ console_url: 'https://serper.dev',
218
+ key_patterns: ['SERPER_API_KEY'],
219
+ schema: {
220
+ auth: 'header',
221
+ header: 'X-API-KEY: <key>',
222
+ env_keys: ['SERPER_API_KEY'],
223
+ },
224
+ default_monthly_cap_usd: 50,
225
+ tiers: [{ id: 'payg', label: 'Credits', monthly_usd: null }],
226
+ },
227
+ {
228
+ id: 'telegram',
229
+ name: 'Telegram Bot',
230
+ docs_url: 'https://core.telegram.org/bots/api',
231
+ console_url: 'https://t.me/BotFather',
232
+ key_patterns: ['TELEGRAM_BOT_TOKEN', 'TELEGRAM_CHAT_ID'],
233
+ schema: {
234
+ auth: 'path',
235
+ env_keys: ['TELEGRAM_BOT_TOKEN', 'TELEGRAM_CHAT_ID'],
236
+ notes: 'Token from BotFather; chat_id for notifications',
237
+ },
238
+ default_monthly_cap_usd: 0,
239
+ tiers: [{ id: 'free', label: 'Free', monthly_usd: 0 }],
240
+ },
241
+ {
242
+ id: 'total-recall',
243
+ name: 'Total Recall',
244
+ docs_url: 'https://github.com/gregiteen/total-recall',
245
+ key_patterns: ['TOTAL_RECALL_TOKEN', 'TR_TOKEN', 'TR_SECRETS_PASSWORD'],
246
+ schema: {
247
+ auth: 'bearer',
248
+ header: 'Authorization: Bearer <pat>',
249
+ env_keys: ['TOTAL_RECALL_TOKEN'],
250
+ notes: 'Dashboard PATs via /api/keys; secrets.enc for provider keys',
251
+ },
252
+ default_monthly_cap_usd: 0,
253
+ tiers: [{ id: 'self-hosted', label: 'Self-hosted', monthly_usd: 0 }],
254
+ },
255
+ ];
256
+
257
+ const byId = new Map(PROVIDER_CATALOG.map((p) => [p.id, p]));
258
+
259
+ /**
260
+ * @param {string} providerId
261
+ */
262
+ export function getProvider(providerId) {
263
+ if (!providerId) return null;
264
+ return byId.get(String(providerId).toLowerCase()) || null;
265
+ }
266
+
267
+ /**
268
+ * @param {string} keyName
269
+ */
270
+ export function providerForKeyName(keyName) {
271
+ const k = String(keyName || '').toUpperCase();
272
+ for (const p of PROVIDER_CATALOG) {
273
+ if (p.key_patterns.some((pat) => pat.toUpperCase() === k || k.includes(pat.replace(/_API_KEY$/, '')))) {
274
+ return p;
275
+ }
276
+ }
277
+ // fuzzy
278
+ for (const p of PROVIDER_CATALOG) {
279
+ if (k.includes(p.id.toUpperCase().replace('-', '_'))) return p;
280
+ }
281
+ return null;
282
+ }
283
+
284
+ export function listProviders() {
285
+ return PROVIDER_CATALOG.map((p) => ({
286
+ id: p.id,
287
+ name: p.name,
288
+ docs_url: p.docs_url,
289
+ pricing_url: p.pricing_url || null,
290
+ console_url: p.console_url || null,
291
+ key_patterns: p.key_patterns,
292
+ schema: p.schema,
293
+ default_monthly_cap_usd: p.default_monthly_cap_usd ?? null,
294
+ tiers: p.tiers || [],
295
+ }));
296
+ }
@@ -39,7 +39,7 @@ function classifyMessage(text) {
39
39
 
40
40
  function buildInboxNode({ text, author, channel, source, ts }) {
41
41
  const now = ts || new Date().toISOString();
42
- const slug = `capture-${source}-${crypto.randomBytes(4).toString('hex')}`;
42
+ const slug = `capture-${source}-${crypto.createHash('md5').update(text).digest('hex').slice(0, 8)}`;
43
43
  const nodeType = classifyMessage(text);
44
44
 
45
45
  return {