total-recall-brain 3.1.0 → 3.1.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/package.json
CHANGED
package/src/cli/remember.mjs
CHANGED
|
@@ -16,7 +16,11 @@ import matter from 'gray-matter';
|
|
|
16
16
|
import { loadNodes, writeNode, deleteNode } from './vault.mjs';
|
|
17
17
|
import { brainDir } from './config.mjs';
|
|
18
18
|
|
|
19
|
-
const getBrainDir = () =>
|
|
19
|
+
const getBrainDir = () => {
|
|
20
|
+
if (process.env._TR_TEST_AGENT_DIR) return process.env._TR_TEST_AGENT_DIR;
|
|
21
|
+
if (process.env.AGENT_DIR) return path.join(process.env.AGENT_DIR, 'skills', 'total-recall');
|
|
22
|
+
return brainDir;
|
|
23
|
+
};
|
|
20
24
|
|
|
21
25
|
function getQueueFile(overrideBrainDir) {
|
|
22
26
|
const dir = overrideBrainDir || getBrainDir();
|
package/src/core/runtime.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import yaml from 'yaml';
|
|
|
4
4
|
import os from 'os';
|
|
5
5
|
import { spawnSync } from 'node:child_process';
|
|
6
6
|
import { logger } from './logger.mjs';
|
|
7
|
-
import { agentDir, brainDir } from './config.mjs';
|
|
7
|
+
import { agentDir, brainDir, googleApiKey, tavilyApiKey, braveApiKey, exaApiKey, serperApiKey, githubToken } from './config.mjs';
|
|
8
8
|
import { checkBudgetSafety } from './usage-tracker.mjs';
|
|
9
9
|
import { validateCommand } from './sandbox.mjs';
|
|
10
10
|
|
|
@@ -288,7 +288,15 @@ export async function callLocalRuntime(prompt, system, config) {
|
|
|
288
288
|
encoding: 'utf8',
|
|
289
289
|
timeout,
|
|
290
290
|
maxBuffer: 10 * 1024 * 1024,
|
|
291
|
-
env: {
|
|
291
|
+
env: {
|
|
292
|
+
...process.env,
|
|
293
|
+
GOOGLE_API_KEY: process.env.GOOGLE_API_KEY || googleApiKey,
|
|
294
|
+
TAVILY_API_KEY: process.env.TAVILY_API_KEY || tavilyApiKey,
|
|
295
|
+
BRAVE_API_KEY: process.env.BRAVE_API_KEY || braveApiKey,
|
|
296
|
+
EXA_API_KEY: process.env.EXA_API_KEY || exaApiKey,
|
|
297
|
+
SERPER_API_KEY: process.env.SERPER_API_KEY || serperApiKey,
|
|
298
|
+
GITHUB_TOKEN: process.env.GITHUB_TOKEN || githubToken,
|
|
299
|
+
},
|
|
292
300
|
cwd: process.cwd(),
|
|
293
301
|
});
|
|
294
302
|
|
package/src/server/rest.mjs
CHANGED
|
@@ -726,6 +726,59 @@ router.get('/api/brain/export', requireAuth, requireScope('brain:export'), async
|
|
|
726
726
|
} catch (err) { serverError(res, err); }
|
|
727
727
|
});
|
|
728
728
|
|
|
729
|
+
// ─── Chrome Extension Download ───────────────────────────────────────────────
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* GET /api/extension/download
|
|
733
|
+
* Streams the Chrome extension directory as a .zip file for easy installation.
|
|
734
|
+
* No auth required — the extension itself is not sensitive.
|
|
735
|
+
*/
|
|
736
|
+
router.get('/api/extension/download', async (_req, res) => {
|
|
737
|
+
try {
|
|
738
|
+
// Extension lives at <package-root>/extension/
|
|
739
|
+
const extDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../extension');
|
|
740
|
+
if (!fs.existsSync(extDir) || !fs.existsSync(path.join(extDir, 'manifest.json'))) {
|
|
741
|
+
return res.status(404).json({ error: 'Chrome extension not found in this installation.' });
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
res.setHeader('Content-Type', 'application/zip');
|
|
745
|
+
res.setHeader('Content-Disposition', 'attachment; filename="total-recall-extension.zip"');
|
|
746
|
+
|
|
747
|
+
// Use zip if available, fall back to tar
|
|
748
|
+
const zip = spawn('zip', ['-r', '-', '.'], { cwd: extDir, stdio: ['ignore', 'pipe', 'ignore'] });
|
|
749
|
+
zip.stdout.pipe(res);
|
|
750
|
+
zip.on('error', () => {
|
|
751
|
+
// zip not available — try tar
|
|
752
|
+
if (!res.headersSent) {
|
|
753
|
+
res.setHeader('Content-Type', 'application/gzip');
|
|
754
|
+
res.setHeader('Content-Disposition', 'attachment; filename="total-recall-extension.tar.gz"');
|
|
755
|
+
const tar = spawn('tar', ['czf', '-', '-C', path.dirname(extDir), 'extension'], { stdio: ['ignore', 'pipe', 'ignore'] });
|
|
756
|
+
tar.stdout.pipe(res);
|
|
757
|
+
tar.on('error', err => { if (!res.headersSent) serverError(res, err); });
|
|
758
|
+
tar.on('close', () => { if (!res.writableEnded) res.end(); });
|
|
759
|
+
}
|
|
760
|
+
});
|
|
761
|
+
zip.on('close', code => { if (code !== 0 && !res.writableEnded) res.end(); });
|
|
762
|
+
} catch (err) { serverError(res, err); }
|
|
763
|
+
});
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* GET /api/extension/status
|
|
767
|
+
* Returns whether the extension is available (packaged) and connected (has sent captures).
|
|
768
|
+
*/
|
|
769
|
+
router.get('/api/extension/status', async (_req, res) => {
|
|
770
|
+
try {
|
|
771
|
+
const extDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../extension');
|
|
772
|
+
const available = fs.existsSync(path.join(extDir, 'manifest.json'));
|
|
773
|
+
|
|
774
|
+
// Check if extension has ever connected by looking for the marker file
|
|
775
|
+
const markerPath = path.join(BRAIN_DIR, 'config', '.extension-connected');
|
|
776
|
+
const connected = fs.existsSync(markerPath);
|
|
777
|
+
|
|
778
|
+
res.json({ available, connected });
|
|
779
|
+
} catch (err) { serverError(res, err); }
|
|
780
|
+
});
|
|
781
|
+
|
|
729
782
|
// ─── Dashboard Intelligence Endpoints (feature-flagged) ──────────────────────────
|
|
730
783
|
// Feature flag: presence of ~/.agent/memory-vault/preferences/dashboard-enhanced.md
|
|
731
784
|
|
|
@@ -73,5 +73,7 @@
|
|
|
73
73
|
{ "method": "GET", "path": "/api/brains" },
|
|
74
74
|
{ "method": "GET", "path": "/api/brains/:id/nodes" },
|
|
75
75
|
{ "method": "GET", "path": "/api/gemini-models" },
|
|
76
|
-
{ "method": "POST", "path": "/api/share" }
|
|
76
|
+
{ "method": "POST", "path": "/api/share" },
|
|
77
|
+
{ "method": "GET", "path": "/api/extension/download" },
|
|
78
|
+
{ "method": "GET", "path": "/api/extension/status" }
|
|
77
79
|
]
|
|
@@ -60,7 +60,7 @@ function resolveAction({ url, excerpt, action }) {
|
|
|
60
60
|
* POST /api/share
|
|
61
61
|
* Body: { url, title, excerpt, source, action, brainId, tags }
|
|
62
62
|
*/
|
|
63
|
-
router.post('/api/share', requireAuth, requireScope('memory:write'), (req, res) => {
|
|
63
|
+
router.post('/api/share', requireAuth, requireScope('memory:write'), async (req, res) => {
|
|
64
64
|
try {
|
|
65
65
|
const { url, title, excerpt, source, action: rawAction, tags } = req.body || {};
|
|
66
66
|
|
|
@@ -124,6 +124,18 @@ router.post('/api/share', requireAuth, requireScope('memory:write'), (req, res)
|
|
|
124
124
|
writeNode(node, vaultDir);
|
|
125
125
|
invalidate();
|
|
126
126
|
|
|
127
|
+
// Mark extension as connected when share comes from the extension
|
|
128
|
+
if (source && source.includes('extension')) {
|
|
129
|
+
const fs = await import('node:fs');
|
|
130
|
+
const path = await import('node:path');
|
|
131
|
+
const configDir = path.join(vaultDir, '..', 'config');
|
|
132
|
+
const markerPath = path.join(configDir, '.extension-connected');
|
|
133
|
+
if (!fs.existsSync(markerPath)) {
|
|
134
|
+
try { fs.mkdirSync(configDir, { recursive: true }); } catch { /* ok */ }
|
|
135
|
+
fs.writeFileSync(markerPath, new Date().toISOString());
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
127
139
|
return res.status(201).json({
|
|
128
140
|
action_taken: 'remember',
|
|
129
141
|
id: node.slug,
|