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.
- package/README.md +91 -108
- package/bin/antigravity.mjs +81 -52
- package/bin/total-recall.mjs +32 -57
- package/extension/background.js +1 -1
- package/extension/options/options.js +9 -3
- package/extension/sidepanel/sidepanel.js +0 -2
- package/frontend/index.html +4 -3
- package/frontend/package.json +1 -0
- package/frontend/src/App.tsx +109 -152
- package/frontend/src/api.ts +605 -23
- package/frontend/src/components/BrainSelector.tsx +211 -199
- package/frontend/src/components/DocumentEditorModal.tsx +57 -0
- package/frontend/src/components/DocumentTable.tsx +47 -0
- package/frontend/src/components/Graph3D.tsx +120 -71
- package/frontend/src/components/UsageChart.tsx +3 -3
- package/frontend/src/components/brand/BrandMark.tsx +95 -0
- package/frontend/src/index.css +286 -86
- package/frontend/src/pages/ApiKeysPage.tsx +1878 -215
- package/frontend/src/pages/ChatPage.tsx +103 -45
- package/frontend/src/pages/DesignDocsPage.tsx +5 -5
- package/frontend/src/pages/FilesPage.tsx +10 -6
- package/frontend/src/pages/GraphPage.tsx +5 -3
- package/frontend/src/pages/HelpPage.tsx +2 -2
- package/frontend/src/pages/InboxPage.tsx +82 -0
- package/frontend/src/pages/InstructionsPage.tsx +1 -1
- package/frontend/src/pages/IntegrationsPage.tsx +21 -10
- package/frontend/src/pages/LoginPage.tsx +95 -77
- package/frontend/src/pages/MemoryPage.tsx +31 -8
- package/frontend/src/pages/OnboardingPage.tsx +690 -0
- package/frontend/src/pages/OpenWikiPage.tsx +412 -323
- package/frontend/src/pages/SettingsPage.tsx +825 -922
- package/frontend/src/pages/SkillsPage.tsx +272 -147
- package/frontend/src/pages/UsagePage.tsx +23 -31
- package/frontend/src/pages/VaultPage.tsx +223 -0
- package/frontend/src/types.ts +13 -0
- package/package.json +4 -4
- package/scaffold/.agent/skills/total-recall/SKILL.md +43 -4
- package/scaffold/.agent/skills/total-recall/modules/README.md +10 -0
- package/scaffold/.agent/skills/total-recall/modules/agents/README.md +5 -0
- package/scaffold/.agent/skills/total-recall/{skills/tr-cli-agents → modules/agents}/agents.yml +8 -0
- package/scaffold/.agent/skills/total-recall/modules/skill-deploy/README.md +9 -0
- package/scaffold/.agent/skills/total-recall/openwiki/README.md +10 -0
- package/scaffold/.agent/skills/total-recall/openwiki/architecture.md +5 -0
- package/scaffold/.agent/skills/total-recall/openwiki/ide.md +10 -0
- package/scaffold/.agent/skills/total-recall/openwiki/memory.md +11 -0
- package/scaffold/.agent/skills/total-recall/openwiki/secrets.md +7 -0
- package/scaffold/.agent/skills/total-recall/openwiki/skills.md +7 -0
- package/src/cli/brain.mjs +63 -5
- package/src/cli/connect.mjs +11 -19
- package/src/cli/daemon.mjs +5 -4
- package/src/cli/deploy-ui.mjs +8 -3
- package/src/cli/deploy.mjs +84 -27
- package/src/cli/ingest/index.mjs +1 -1
- package/src/cli/init.mjs +63 -34
- package/src/cli/remember.mjs +3 -3
- package/src/cli/secret.mjs +812 -0
- package/src/cli/setup.mjs +34 -7
- package/src/cli/share.mjs +2 -2
- package/src/cli/skill.mjs +396 -35
- package/src/cli/sync.mjs +4 -4
- package/src/cli/task.mjs +287 -0
- package/src/core/clarity-rewriter.mjs +5 -5
- package/src/core/config.mjs +44 -2
- package/src/core/conflict-detector.mjs +5 -5
- package/src/core/crons.mjs +54 -0
- package/src/core/daemon-loop.mjs +87 -235
- package/src/core/dream.mjs +170 -10
- package/src/core/env-import.mjs +456 -0
- package/src/core/fact-seeker.mjs +14 -14
- package/src/core/import-rules.mjs +20 -7
- package/src/core/inference-engine.mjs +3 -3
- package/src/core/migrate.mjs +14 -8
- package/src/core/okf-adapter.mjs +2 -2
- package/src/core/operation-validator.mjs +39 -331
- package/src/core/optimizer.mjs +7 -0
- package/src/core/post-mortem.mjs +2 -2
- package/src/core/project-brain.mjs +349 -0
- package/src/core/provider-catalog.mjs +296 -0
- package/src/core/quick-capture.mjs +1 -1
- package/src/core/remote-vault-sync.mjs +194 -0
- package/src/core/research-queue.mjs +7 -11
- package/src/core/runtime.mjs +33 -9
- package/src/core/scheduler.mjs +59 -5
- package/src/core/schema.mjs +14 -0
- package/src/core/search.mjs +1 -1
- package/src/core/secrets-env-export.mjs +323 -0
- package/src/core/secrets-rotate.mjs +245 -0
- package/src/core/secrets-store.mjs +645 -0
- package/src/core/semantic-index.mjs +3 -3
- package/src/core/skills-registry.mjs +1062 -0
- package/src/core/source-adapters.mjs +4 -1
- package/src/core/ssss-host-extension.mjs +94 -0
- package/src/core/ssss-kernel-bridge.mjs +530 -0
- package/src/core/surface.mjs +14 -0
- package/src/core/task-envelope.mjs +298 -0
- package/src/core/task-executors.mjs +600 -0
- package/src/core/validated-write.mjs +124 -56
- package/src/core/vault.mjs +21 -34
- package/src/core/webauthn-store.mjs +272 -0
- package/src/server/api.mjs +26 -16
- package/src/server/auth.mjs +51 -0
- package/src/server/index.mjs +3 -3
- package/src/server/keys.mjs +30 -4
- package/src/server/rest.mjs +198 -25
- package/src/server/route-manifest.json +156 -0
- package/src/server/routes/_shared.mjs +40 -1
- package/src/server/routes/docs.mjs +260 -0
- package/src/server/routes/memory.mjs +145 -32
- package/src/server/routes/research.mjs +30 -0
- package/src/server/routes/secrets.mjs +470 -0
- package/src/server/routes/share.mjs +2 -2
- package/src/server/routes/skills.mjs +452 -54
- package/src/server/routes/sync.mjs +48 -0
- package/src/server/routes/webauthn.mjs +184 -0
- package/src/server/update-route-manifest.mjs +39 -0
- package/templates/onboarding-interview.md +32 -22
- package/templates/openwiki/README.md +10 -0
- package/templates/openwiki/architecture.md +5 -0
- package/templates/openwiki/ide.md +10 -0
- package/templates/openwiki/memory.md +11 -0
- package/templates/openwiki/secrets.md +7 -0
- package/templates/openwiki/skills.md +7 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/SKILL.md +0 -121
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/antigravity.txt +0 -20
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/claude.txt +0 -58
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/codex.txt +0 -81
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/references/gemini.txt +0 -44
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/scripts/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-cli-agents/subagents/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-research/SKILL.md +0 -338
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/SKILL.md +0 -282
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/assets/skill-template/SKILL.md +0 -20
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/evals/evals.json +0 -20
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/hooks/on-skill-add.sh +0 -14
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/hooks/on-skill-delete.sh +0 -14
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/learnings/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/references/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/create-skill.sh +0 -65
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/enforce-skill-optimization.mjs +0 -135
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/scripts/skill-watcher.mjs +0 -64
- package/scaffold/.agent/skills/total-recall/skills/tr-skill/subagents/audit-all-skills.md +0 -62
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/SKILL.md +0 -298
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/evals/evals.json +0 -17
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/references/CHANGELOG.md +0 -124
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/references/ssss-spec.md +0 -1325
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/scripts/validate-schema.mjs +0 -232
- package/scaffold/.agent/skills/total-recall/skills/tr-ssss/subagents/audit-ssss-node.md +0 -9
- /package/scaffold/.agent/skills/total-recall/{skills/tr-cli-agents/evals → modules/skill-deploy/scripts}/.gitkeep +0 -0
- /package/scaffold/.agent/skills/total-recall/{skills/tr-skill → modules/skill-deploy}/scripts/find-skills.mjs +0 -0
- /package/scaffold/.agent/skills/total-recall/{skills/tr-skill → modules/skill-deploy}/scripts/install-skill.mjs +0 -0
- /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.
|
|
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 {
|