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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "total-recall-brain",
3
- "version": "3.1.0",
3
+ "version": "3.1.2",
4
4
  "description": "Sovereign AI system — database-free, SSSS-powered personal intelligence engine deployable to any POSIX host.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -117,7 +117,7 @@ export default async function remember(args) {
117
117
  let tags = [];
118
118
  let importance = 3;
119
119
  let priority = 'normal';
120
- let modality = 'descriptive';
120
+ let modality = 'should';
121
121
  let confidence = 1.0;
122
122
  let slug = null;
123
123
  let title = null;
@@ -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 = () => process.env._TR_TEST_AGENT_DIR || brainDir;
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();
@@ -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: { ...process.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
 
@@ -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,