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.
- package/LICENSE +21 -0
- package/README.md +173 -0
- package/bin/antigravity.mjs +212 -0
- package/bin/init.mjs +36 -0
- package/bin/oci-sniper.sh +69 -0
- package/bin/total-recall.mjs +229 -0
- package/docs/ARCHITECTURE.md +251 -0
- package/docs/IDE_INTEGRATION.md +112 -0
- package/package.json +71 -0
- package/scaffold/.agent/skills/total-recall/SKILL.md +195 -0
- package/scaffold/.agent/skills/total-recall/evals/evals.json +20 -0
- package/scaffold/.agent/skills/total-recall/memory-vault/concepts/cli-help-reference.md +145 -0
- package/scaffold/.agent/skills/total-recall/memory-vault/invariants/operating-instructions.md +75 -0
- package/scaffold/.agent/skills/total-recall/memory-vault/preferences/always-websearch-gap.md +16 -0
- package/scaffold/.agent/skills/total-recall/memory-vault/preferences/topic-research-sop.md +17 -0
- package/scaffold/.agent/skills/total-recall/references/api-reference.md +187 -0
- package/scaffold/.agent/skills/total-recall/references/architecture-reference.md +81 -0
- package/scaffold/.agent/skills/total-recall/references/cli-reference.md +103 -0
- package/scaffold/.agent/skills/total-recall/references/ssss-reference.md +88 -0
- package/scaffold/.agent/skills/total-recall/references/troubleshooting.md +68 -0
- package/scaffold/.agent/skills/total-recall/scripts/sync-repo.mjs +83 -0
- package/scaffold/.agent/skills/total-recall/skills/cli-agents/SKILL.md +122 -0
- package/scaffold/.agent/skills/total-recall/skills/cli-agents/agents.yml +27 -0
- package/scaffold/.agent/skills/total-recall/skills/cli-agents/references/antigravity.txt +20 -0
- package/scaffold/.agent/skills/total-recall/skills/cli-agents/references/claude.txt +58 -0
- package/scaffold/.agent/skills/total-recall/skills/cli-agents/references/codex.txt +81 -0
- package/scaffold/.agent/skills/total-recall/skills/cli-agents/references/gemini.txt +44 -0
- package/scaffold/.agent/skills/total-recall/skills/research/SKILL.md +337 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/SKILL.md +281 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/assets/skill-template/SKILL.md +20 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/evals/evals.json +20 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/hooks/on-skill-add.sh +14 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/hooks/on-skill-delete.sh +14 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/learnings/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/references/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/scripts/.gitkeep +0 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/scripts/create-skill.sh +65 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/scripts/enforce-skill-optimization.mjs +135 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/scripts/find-skills.mjs +124 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/scripts/scan-skill.mjs +207 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/scripts/skill-watcher.mjs +64 -0
- package/scaffold/.agent/skills/total-recall/skills/skill/subagents/audit-all-skills.md +62 -0
- package/scaffold/.agent/skills/total-recall/skills/ssss/SKILL.md +27 -0
- package/scaffold/.agent/skills/total-recall/skills/ssss/evals/evals.json +17 -0
- package/scaffold/.agent/skills/total-recall/skills/ssss/references/ssss-spec.md +117 -0
- package/scaffold/.agent/skills/total-recall/skills/ssss/scripts/validate-schema.mjs +54 -0
- package/scaffold/.agent/skills/total-recall/skills/ssss/subagents/audit-ssss-node.md +9 -0
- package/scaffold/.agent/skills/total-recall/subagents/total-recall-diagnostician.md +13 -0
- package/src/cli/agent-dir.mjs +105 -0
- package/src/cli/backup.mjs +313 -0
- package/src/cli/brain.mjs +224 -0
- package/src/cli/chat.mjs +74 -0
- package/src/cli/config.mjs +247 -0
- package/src/cli/connect.mjs +872 -0
- package/src/cli/daemon.mjs +228 -0
- package/src/cli/deploy-ui.mjs +1793 -0
- package/src/cli/deploy.mjs +1726 -0
- package/src/cli/doctor.mjs +254 -0
- package/src/cli/dream.mjs +80 -0
- package/src/cli/forget.mjs +92 -0
- package/src/cli/friction.mjs +21 -0
- package/src/cli/generate-pat.mjs +65 -0
- package/src/cli/hash-password.mjs +22 -0
- package/src/cli/help.mjs +153 -0
- package/src/cli/import-rules.mjs +102 -0
- package/src/cli/ingest/index.mjs +318 -0
- package/src/cli/ingest/parsers/chrome-bookmarks.mjs +238 -0
- package/src/cli/ingest/parsers/google-keep.mjs +147 -0
- package/src/cli/ingest/parsers/search-history.mjs +142 -0
- package/src/cli/ingest/parsers/youtube-history.mjs +181 -0
- package/src/cli/ingest/utils/dedup.mjs +69 -0
- package/src/cli/ingest/utils/takeout-walker.mjs +101 -0
- package/src/cli/ingest.mjs +102 -0
- package/src/cli/init.mjs +736 -0
- package/src/cli/integration-dispatcher.mjs +128 -0
- package/src/cli/lint.mjs +166 -0
- package/src/cli/map.mjs +194 -0
- package/src/cli/migrate.mjs +87 -0
- package/src/cli/rebuild.mjs +88 -0
- package/src/cli/recall.mjs +219 -0
- package/src/cli/relay.mjs +518 -0
- package/src/cli/remember.mjs +306 -0
- package/src/cli/research.mjs +381 -0
- package/src/cli/reset-password.mjs +75 -0
- package/src/cli/restore.mjs +135 -0
- package/src/cli/setup.mjs +616 -0
- package/src/cli/share.mjs +180 -0
- package/src/cli/skill.mjs +313 -0
- package/src/cli/snapshot.mjs +108 -0
- package/src/cli/start.mjs +63 -0
- package/src/cli/status.mjs +296 -0
- package/src/cli/sync.mjs +383 -0
- package/src/cli/uninstall.mjs +613 -0
- package/src/cli/upgrade.mjs +141 -0
- package/src/cli/wizard.html +3682 -0
- package/src/core/blackboard.mjs +54 -0
- package/src/core/clarity-rewriter.mjs +770 -0
- package/src/core/conclusion-writer.mjs +259 -0
- package/src/core/config.mjs +277 -0
- package/src/core/conflict-detector.mjs +536 -0
- package/src/core/crypto.mjs +107 -0
- package/src/core/daemon-control.mjs +158 -0
- package/src/core/daemon-loop.mjs +364 -0
- package/src/core/dream.mjs +347 -0
- package/src/core/drift-detector.mjs +143 -0
- package/src/core/embeddings.mjs +632 -0
- package/src/core/emergency-alerts.mjs +137 -0
- package/src/core/evolution.mjs +107 -0
- package/src/core/fact-seeker.mjs +1602 -0
- package/src/core/friction.mjs +95 -0
- package/src/core/import-rules.mjs +308 -0
- package/src/core/inference-engine.mjs +367 -0
- package/src/core/logger.mjs +75 -0
- package/src/core/memory-layers.mjs +96 -0
- package/src/core/migrate.mjs +116 -0
- package/src/core/notifications.mjs +77 -0
- package/src/core/operation-validator.mjs +272 -0
- package/src/core/optimizer.mjs +330 -0
- package/src/core/pattern_detector.mjs +113 -0
- package/src/core/post-mortem.mjs +413 -0
- package/src/core/protect-instructions.mjs +148 -0
- package/src/core/quick-capture.mjs +145 -0
- package/src/core/research-queue.mjs +431 -0
- package/src/core/research.mjs +461 -0
- package/src/core/runtime.mjs +515 -0
- package/src/core/sandbox.mjs +163 -0
- package/src/core/scheduler.mjs +569 -0
- package/src/core/schema.mjs +333 -0
- package/src/core/search.mjs +219 -0
- package/src/core/semantic-index.mjs +276 -0
- package/src/core/session-watcher.mjs +850 -0
- package/src/core/snapshot.mjs +133 -0
- package/src/core/source-adapters.mjs +1038 -0
- package/src/core/steering.mjs +137 -0
- package/src/core/surface.mjs +444 -0
- package/src/core/task_runner.mjs +125 -0
- package/src/core/tts.mjs +142 -0
- package/src/core/usage-tracker.mjs +349 -0
- package/src/core/vault-cache.mjs +76 -0
- package/src/core/vault.mjs +273 -0
- package/src/core/watchdog.mjs +236 -0
- package/src/server/api.mjs +944 -0
- package/src/server/auth.mjs +410 -0
- package/src/server/index.mjs +868 -0
- package/src/server/keys.mjs +181 -0
- package/src/server/rest.mjs +1652 -0
- package/src/server/route-manifest.json +77 -0
- package/src/server/routes/_shared.mjs +93 -0
- package/src/server/routes/keys.mjs +55 -0
- package/src/server/routes/memory.mjs +249 -0
- package/src/server/routes/sessions.mjs +336 -0
- package/src/server/routes/share.mjs +138 -0
- package/src/server/tools.mjs +618 -0
- package/templates/Caddyfile +37 -0
- package/templates/com.totalrecall.backup.plist +42 -0
- package/templates/com.totalrecall.daemon.plist +40 -0
- package/templates/com.totalrecall.duckdns.plist +23 -0
- package/templates/com.totalrecall.server.plist +46 -0
- package/templates/default-config/budget.yml +6 -0
- package/templates/default-config/runtime.yml +11 -0
- package/templates/default-config/security.yml +53 -0
- package/templates/default-config/voice.yml +34 -0
- package/templates/obsidian-queries/conflicts.md +26 -0
- package/templates/obsidian-queries/daily.md +17 -0
- package/templates/obsidian-queries/index.md +38 -0
- package/templates/obsidian-queries/skills.md +14 -0
- package/templates/onboarding-interview.md +72 -0
- package/templates/skill_engineering.md +40 -0
- package/templates/total-recall-daemon.service +21 -0
- package/templates/total-recall-server.service +20 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
[
|
|
2
|
+
{ "method": "GET", "path": "/api/memory" },
|
|
3
|
+
{ "method": "GET", "path": "/api/memory/stats" },
|
|
4
|
+
{ "method": "GET", "path": "/api/memory/:slug" },
|
|
5
|
+
{ "method": "POST", "path": "/api/memory" },
|
|
6
|
+
{ "method": "PUT", "path": "/api/memory/:slug" },
|
|
7
|
+
{ "method": "PATCH", "path": "/api/memory/:slug" },
|
|
8
|
+
{ "method": "DELETE", "path": "/api/memory/:slug" },
|
|
9
|
+
{ "method": "GET", "path": "/api/keys" },
|
|
10
|
+
{ "method": "POST", "path": "/api/keys" },
|
|
11
|
+
{ "method": "DELETE", "path": "/api/keys/:id" },
|
|
12
|
+
{ "method": "GET", "path": "/api/sessions" },
|
|
13
|
+
{ "method": "GET", "path": "/api/sessions/:id" },
|
|
14
|
+
{ "method": "POST", "path": "/api/sessions/ingest" },
|
|
15
|
+
{ "method": "DELETE", "path": "/api/sessions/:id" },
|
|
16
|
+
{ "method": "POST", "path": "/api/memory/search/semantic" },
|
|
17
|
+
{ "method": "POST", "path": "/api/vault/compile" },
|
|
18
|
+
{ "method": "POST", "path": "/api/dream" },
|
|
19
|
+
{ "method": "GET", "path": "/api/vault/status" },
|
|
20
|
+
{ "method": "POST", "path": "/api/sandbox" },
|
|
21
|
+
{ "method": "GET", "path": "/api/config" },
|
|
22
|
+
{ "method": "GET", "path": "/v1/models" },
|
|
23
|
+
{ "method": "GET", "path": "/.well-known/total-recall.json" },
|
|
24
|
+
{ "method": "GET", "path": "/api" },
|
|
25
|
+
{ "method": "GET", "path": "/api/research" },
|
|
26
|
+
{ "method": "POST", "path": "/api/research" },
|
|
27
|
+
{ "method": "PATCH", "path": "/api/research/:id" },
|
|
28
|
+
{ "method": "DELETE", "path": "/api/research/:id" },
|
|
29
|
+
{ "method": "GET", "path": "/api/integrations/active" },
|
|
30
|
+
{ "method": "POST", "path": "/api/integrations/connect" },
|
|
31
|
+
{ "method": "GET", "path": "/api/import/rules" },
|
|
32
|
+
{ "method": "POST", "path": "/api/import/rules" },
|
|
33
|
+
{ "method": "GET", "path": "/api/brain/export" },
|
|
34
|
+
{ "method": "GET", "path": "/api/graph" },
|
|
35
|
+
{ "method": "GET", "path": "/api/conflicts" },
|
|
36
|
+
{ "method": "POST", "path": "/api/conflicts/resolve" },
|
|
37
|
+
{ "method": "GET", "path": "/api/files" },
|
|
38
|
+
{ "method": "GET", "path": "/api/skills" },
|
|
39
|
+
{ "method": "GET", "path": "/api/skills/search" },
|
|
40
|
+
{ "method": "POST", "path": "/api/skills/install" },
|
|
41
|
+
{ "method": "GET", "path": "/api/skills/:name" },
|
|
42
|
+
{ "method": "PUT", "path": "/api/skills/:name" },
|
|
43
|
+
{ "method": "DELETE", "path": "/api/skills/:name" },
|
|
44
|
+
{ "method": "GET", "path": "/api/logs/:type" },
|
|
45
|
+
{ "method": "GET", "path": "/api/tasks" },
|
|
46
|
+
{ "method": "POST", "path": "/api/tasks" },
|
|
47
|
+
{ "method": "GET", "path": "/api/config-json" },
|
|
48
|
+
{ "method": "POST", "path": "/api/config-json" },
|
|
49
|
+
{ "method": "GET", "path": "/api/usage" },
|
|
50
|
+
{ "method": "GET", "path": "/api/config/:name" },
|
|
51
|
+
{ "method": "PUT", "path": "/api/config/:name" },
|
|
52
|
+
{ "method": "POST", "path": "/api/capture/:source" },
|
|
53
|
+
{ "method": "POST", "path": "/auth/login" },
|
|
54
|
+
{ "method": "POST", "path": "/auth/logout" },
|
|
55
|
+
{ "method": "POST", "path": "/auth/change-password" },
|
|
56
|
+
{ "method": "GET", "path": "/auth/me" },
|
|
57
|
+
{ "method": "GET", "path": "/auth/status" },
|
|
58
|
+
{ "method": "POST", "path": "/auth/setup" },
|
|
59
|
+
{ "method": "GET", "path": "/api/tts/status" },
|
|
60
|
+
{ "method": "POST", "path": "/api/tts" },
|
|
61
|
+
{ "method": "GET", "path": "/api/instructions" },
|
|
62
|
+
{ "method": "PUT", "path": "/api/instructions" },
|
|
63
|
+
{ "method": "GET", "path": "/api/ssss" },
|
|
64
|
+
{ "method": "GET", "path": "/api/ssss/instructions" },
|
|
65
|
+
{ "method": "GET", "path": "/api/ssss/skill/ssss" },
|
|
66
|
+
{ "method": "GET", "path": "/api/ssss/spec" },
|
|
67
|
+
{ "method": "GET", "path": "/api/ssss/references" },
|
|
68
|
+
{ "method": "GET", "path": "/api/ssss/references/:name" },
|
|
69
|
+
{ "method": "GET", "path": "/api/scripts" },
|
|
70
|
+
{ "method": "GET", "path": "/api/scripts/:name" },
|
|
71
|
+
{ "method": "PUT", "path": "/api/scripts/:name" },
|
|
72
|
+
{ "method": "POST", "path": "/api/scripts/:name/run" },
|
|
73
|
+
{ "method": "GET", "path": "/api/brains" },
|
|
74
|
+
{ "method": "GET", "path": "/api/brains/:id/nodes" },
|
|
75
|
+
{ "method": "GET", "path": "/api/gemini-models" },
|
|
76
|
+
{ "method": "POST", "path": "/api/share" }
|
|
77
|
+
]
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared constants and response helpers used by every route module.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from rest.mjs as part of the per-resource router refactor.
|
|
5
|
+
* Keep this file free of route definitions — it should be safe to import
|
|
6
|
+
* from any handler without circular concerns.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import os from 'node:os';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
import { agentDir, brainDir } from '../../core/config.mjs';
|
|
14
|
+
import { logger } from '../../core/logger.mjs';
|
|
15
|
+
|
|
16
|
+
export const AGENT_DIR = process.env.AGENT_DIR || agentDir;
|
|
17
|
+
export const BRAIN_DIR = process.env.TR_BRAIN || path.join(AGENT_DIR, 'skills', 'total-recall');
|
|
18
|
+
export const VAULT_DIR = path.join(BRAIN_DIR, 'memory-vault');
|
|
19
|
+
export const SKILLS_DIR = path.join(AGENT_DIR, 'skills');
|
|
20
|
+
export const DERIVED_DIR = path.join(BRAIN_DIR, 'memory-derived');
|
|
21
|
+
export const SESSIONS_DIR = path.join(BRAIN_DIR, 'sessions');
|
|
22
|
+
export const INSTRUCTIONS = path.join(AGENT_DIR, 'INSTRUCTIONS.md');
|
|
23
|
+
export const FILES_DIR = path.join(BRAIN_DIR, 'files');
|
|
24
|
+
export const TASKS_DIR = path.join(BRAIN_DIR, 'scheduler', 'queue');
|
|
25
|
+
export const CONFIG_DIR = path.join(BRAIN_DIR, 'config');
|
|
26
|
+
|
|
27
|
+
const ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
|
|
28
|
+
export const MODEL_CATALOG_DIR = path.join(ROOT, 'models', 'catalog', 'total-recall');
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Resolve a brain-specific vault directory from the request.
|
|
32
|
+
*
|
|
33
|
+
* Reads `req.query.brain` (query param) or `req.body?.brainId` (body field).
|
|
34
|
+
* - If the value is `'global'` or missing, returns the default VAULT_DIR.
|
|
35
|
+
* - If it starts with `'project:'`, looks up the project brain in the
|
|
36
|
+
* project-registry.json (same resolution logic as GET /api/brains/:id/nodes).
|
|
37
|
+
* - Falls back to VAULT_DIR if the resolved path doesn't exist.
|
|
38
|
+
*
|
|
39
|
+
* @param {import('express').Request} req
|
|
40
|
+
* @returns {string} Absolute path to the memory-vault directory
|
|
41
|
+
*/
|
|
42
|
+
export function resolveVaultFromQuery(req) {
|
|
43
|
+
const brainId = req.query?.brain || req.body?.brainId;
|
|
44
|
+
|
|
45
|
+
// No brain specified or explicitly global → default vault
|
|
46
|
+
if (!brainId || brainId === 'global') {
|
|
47
|
+
return VAULT_DIR;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (brainId.startsWith('project:')) {
|
|
51
|
+
const projectName = brainId.slice('project:'.length);
|
|
52
|
+
const globalBrainDir = path.join(os.homedir(), '.agent', 'skills', 'total-recall');
|
|
53
|
+
const registryPath = path.join(globalBrainDir, 'config', 'project-registry.json');
|
|
54
|
+
|
|
55
|
+
if (!fs.existsSync(registryPath)) {
|
|
56
|
+
return VAULT_DIR;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
61
|
+
const project = registry.find(p => p.name === projectName);
|
|
62
|
+
if (project) {
|
|
63
|
+
const projectVaultDir = path.join(project.brainDir, 'memory-vault');
|
|
64
|
+
if (fs.existsSync(projectVaultDir)) {
|
|
65
|
+
return projectVaultDir;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
// Registry parse error — fall through to default
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Unknown brain ID format or resolution failed → default vault
|
|
74
|
+
return VAULT_DIR;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function notFound(res, msg) {
|
|
78
|
+
return res.status(404).json({ error: msg || 'Not found' });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function badRequest(res, msg) {
|
|
82
|
+
return res.status(400).json({ error: msg });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function serverError(res, err) {
|
|
86
|
+
logger.error('rest', 'Internal server error', { error: err.message, stack: err.stack });
|
|
87
|
+
return res.status(500).json({ error: 'Internal server error' });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Strip the internal `body` field from a vault node into `content`. */
|
|
91
|
+
export function sanitizeNode({ body, ...rest }) {
|
|
92
|
+
return { ...rest, content: body };
|
|
93
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /api/keys/* routes — Personal Access Tokens
|
|
3
|
+
*
|
|
4
|
+
* - GET /api/keys list (no raw tokens)
|
|
5
|
+
* - POST /api/keys issue (returns raw token once)
|
|
6
|
+
* - DELETE /api/keys/:id revoke
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import express from 'express';
|
|
10
|
+
import { issueKey, revokeKey, loadKeys } from '../keys.mjs';
|
|
11
|
+
import { requireAuth, requireScope } from '../auth.mjs';
|
|
12
|
+
import { notFound, badRequest, serverError } from './_shared.mjs';
|
|
13
|
+
|
|
14
|
+
const router = express.Router();
|
|
15
|
+
|
|
16
|
+
router.get('/api/keys', requireAuth, requireScope('keys:read'), (req, res) => {
|
|
17
|
+
try {
|
|
18
|
+
const keys = loadKeys().map(({ token_hash, ...k }) => k);
|
|
19
|
+
res.json({ keys });
|
|
20
|
+
} catch (err) {
|
|
21
|
+
serverError(res, err);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
router.post('/api/keys', requireAuth, requireScope('keys:write'), (req, res) => {
|
|
26
|
+
try {
|
|
27
|
+
const { name, scopes, expires_at } = req.body || {};
|
|
28
|
+
if (!name) return badRequest(res, 'name is required');
|
|
29
|
+
const key = issueKey(name, { scopes, expires_at });
|
|
30
|
+
res.status(201).json({
|
|
31
|
+
id: key.id,
|
|
32
|
+
name: key.name,
|
|
33
|
+
token: key.token, // only time it's returned in plaintext
|
|
34
|
+
token_prefix: key.token_prefix,
|
|
35
|
+
scopes: key.scopes,
|
|
36
|
+
expires_at: key.expires_at,
|
|
37
|
+
created_at: key.created_at,
|
|
38
|
+
_warning: 'Save this token — it will not be shown again.',
|
|
39
|
+
});
|
|
40
|
+
} catch (err) {
|
|
41
|
+
serverError(res, err);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
router.delete('/api/keys/:id', requireAuth, requireScope('keys:write'), (req, res) => {
|
|
46
|
+
try {
|
|
47
|
+
const key = revokeKey(req.params.id);
|
|
48
|
+
if (!key) return notFound(res, `Key not found: ${req.params.id}`);
|
|
49
|
+
res.json({ revoked: true, id: key.id, name: key.name });
|
|
50
|
+
} catch (err) {
|
|
51
|
+
serverError(res, err);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
export { router as keysRouter };
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /api/memory/* routes
|
|
3
|
+
*
|
|
4
|
+
* - GET /api/memory list (q, category, tag, limit, offset)
|
|
5
|
+
* - GET /api/memory/stats counts by category
|
|
6
|
+
* - GET /api/memory/:slug read
|
|
7
|
+
* - POST /api/memory create
|
|
8
|
+
* - PUT /api/memory/:slug full replace
|
|
9
|
+
* - PATCH /api/memory/:slug partial update
|
|
10
|
+
* - DELETE /api/memory/:slug delete
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import express from 'express';
|
|
14
|
+
import fs from 'node:fs';
|
|
15
|
+
import path from 'node:path';
|
|
16
|
+
import { writeNode, createMemoryNode, walkMd } from '../../core/vault.mjs';
|
|
17
|
+
import { getNodes, invalidate } from '../../core/vault-cache.mjs';
|
|
18
|
+
import { requireAuth, requireScope } from '../auth.mjs';
|
|
19
|
+
import { compileSurface } from '../../core/surface.mjs';
|
|
20
|
+
import { buildEmbeddingsIndex, buildSessionEmbeddingsIndex } from '../../core/embeddings.mjs';
|
|
21
|
+
import { detectAndResolve } from '../../core/conflict-detector.mjs';
|
|
22
|
+
import {
|
|
23
|
+
BRAIN_DIR,
|
|
24
|
+
VAULT_DIR,
|
|
25
|
+
SKILLS_DIR,
|
|
26
|
+
DERIVED_DIR,
|
|
27
|
+
SESSIONS_DIR,
|
|
28
|
+
INSTRUCTIONS,
|
|
29
|
+
notFound,
|
|
30
|
+
badRequest,
|
|
31
|
+
serverError,
|
|
32
|
+
sanitizeNode,
|
|
33
|
+
resolveVaultFromQuery,
|
|
34
|
+
} from './_shared.mjs';
|
|
35
|
+
|
|
36
|
+
const router = express.Router();
|
|
37
|
+
|
|
38
|
+
function nodes(vaultDir = VAULT_DIR) {
|
|
39
|
+
return getNodes(vaultDir);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Trigger background SSSS semantic conflict resolution, surface compile, and embedding build.
|
|
44
|
+
* Auto-mutates the brain in real time when any fact is written, updated, or deleted!
|
|
45
|
+
*/
|
|
46
|
+
async function triggerMutation(node, vaultDir = VAULT_DIR) {
|
|
47
|
+
try {
|
|
48
|
+
// 1. Semantic conflict detection & auto-resolution (Sovereign OS Intelligence)
|
|
49
|
+
try {
|
|
50
|
+
const existing = nodes(vaultDir);
|
|
51
|
+
if (node && node.type === 'memory') {
|
|
52
|
+
detectAndResolve(node, existing, {
|
|
53
|
+
vaultDir,
|
|
54
|
+
inboxDir: path.join(BRAIN_DIR, 'memory-inbox'),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
} catch (conflictErr) {
|
|
58
|
+
// Non-fatal fallback
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 2. Recompile instructions surface
|
|
62
|
+
await compileSurface({
|
|
63
|
+
vaultDir,
|
|
64
|
+
skillsDir: SKILLS_DIR,
|
|
65
|
+
derivedDir: DERIVED_DIR,
|
|
66
|
+
instructionsFile: INSTRUCTIONS,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// 3. Rebuild dense embeddings index incrementally in background
|
|
70
|
+
try {
|
|
71
|
+
const vaultNodes = nodes(vaultDir);
|
|
72
|
+
await buildEmbeddingsIndex(vaultNodes, DERIVED_DIR);
|
|
73
|
+
await buildSessionEmbeddingsIndex(SESSIONS_DIR, DERIVED_DIR);
|
|
74
|
+
} catch (embedErr) {
|
|
75
|
+
// Ollama/embeddings offline non-fatal
|
|
76
|
+
}
|
|
77
|
+
} catch (err) {
|
|
78
|
+
// Non-fatal background log
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
// SSSS v2 frontmatter fields we pass through verbatim from request bodies.
|
|
84
|
+
const PASSTHROUGH_FIELDS = ['priority', 'modality', 'confidence', 'importance', 'status', 'related', 'sources'];
|
|
85
|
+
|
|
86
|
+
router.get('/api/memory', requireAuth, requireScope('memory:read'), (req, res) => {
|
|
87
|
+
try {
|
|
88
|
+
const vaultDir = resolveVaultFromQuery(req);
|
|
89
|
+
let list = nodes(vaultDir);
|
|
90
|
+
|
|
91
|
+
const { q, category, tag, limit = '200', offset = '0' } = req.query;
|
|
92
|
+
|
|
93
|
+
if (q) {
|
|
94
|
+
const query = String(q).toLowerCase();
|
|
95
|
+
list = list.filter(n =>
|
|
96
|
+
[n.slug, n.title, n.category, (n.tags || []).join(' '), n.body]
|
|
97
|
+
.join(' ').toLowerCase().includes(query)
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
if (category) list = list.filter(n => n.category === category);
|
|
101
|
+
if (tag) list = list.filter(n => (n.tags || []).includes(tag));
|
|
102
|
+
|
|
103
|
+
const total = list.length;
|
|
104
|
+
const off = Math.max(0, parseInt(offset, 10) || 0);
|
|
105
|
+
const lim = Math.min(500, Math.max(1, parseInt(limit, 10) || 200));
|
|
106
|
+
const page = list.slice(off, off + lim).map(sanitizeNode);
|
|
107
|
+
|
|
108
|
+
res.json({ total, offset: off, limit: lim, nodes: page });
|
|
109
|
+
} catch (err) {
|
|
110
|
+
serverError(res, err);
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
router.get('/api/memory/stats', requireAuth, requireScope('memory:read'), (req, res) => {
|
|
115
|
+
try {
|
|
116
|
+
const vaultDir = resolveVaultFromQuery(req);
|
|
117
|
+
const list = nodes(vaultDir);
|
|
118
|
+
const byCategory = {};
|
|
119
|
+
for (const n of list) {
|
|
120
|
+
byCategory[n.category] = (byCategory[n.category] || 0) + 1;
|
|
121
|
+
}
|
|
122
|
+
res.json({ total: list.length, by_category: byCategory });
|
|
123
|
+
} catch (err) {
|
|
124
|
+
serverError(res, err);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
router.get('/api/memory/:slug', requireAuth, requireScope('memory:read'), (req, res) => {
|
|
129
|
+
try {
|
|
130
|
+
const vaultDir = resolveVaultFromQuery(req);
|
|
131
|
+
const node = nodes(vaultDir).find(n => n.slug === req.params.slug);
|
|
132
|
+
if (!node) return notFound(res, `Memory node not found: ${req.params.slug}`);
|
|
133
|
+
res.json(sanitizeNode(node));
|
|
134
|
+
} catch (err) {
|
|
135
|
+
serverError(res, err);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
router.post('/api/memory', requireAuth, requireScope('memory:write'), (req, res) => {
|
|
140
|
+
try {
|
|
141
|
+
const vaultDir = resolveVaultFromQuery(req);
|
|
142
|
+
const { slug, title, category, content, body, tags } = req.body || {};
|
|
143
|
+
const actualContent = content || body;
|
|
144
|
+
if (!slug || !title || !category || !actualContent) {
|
|
145
|
+
return badRequest(res, 'Required fields: slug, title, category, content (or body)');
|
|
146
|
+
}
|
|
147
|
+
if (nodes(vaultDir).find(n => n.slug === slug)) {
|
|
148
|
+
return res.status(409).json({ error: `Node already exists: ${slug}. Use PUT to update.` });
|
|
149
|
+
}
|
|
150
|
+
const node = createMemoryNode({ slug, title, category, content: actualContent });
|
|
151
|
+
if (tags && Array.isArray(tags)) node.tags = tags;
|
|
152
|
+
|
|
153
|
+
for (const key of PASSTHROUGH_FIELDS) {
|
|
154
|
+
if (req.body[key] !== undefined) node[key] = req.body[key];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
writeNode(node, vaultDir);
|
|
158
|
+
invalidate();
|
|
159
|
+
triggerMutation(node, vaultDir);
|
|
160
|
+
res.status(201).json(sanitizeNode(node));
|
|
161
|
+
} catch (err) {
|
|
162
|
+
serverError(res, err);
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
router.put('/api/memory/:slug', requireAuth, requireScope('memory:write'), (req, res) => {
|
|
167
|
+
try {
|
|
168
|
+
const vaultDir = resolveVaultFromQuery(req);
|
|
169
|
+
const { title, category, content, body, tags } = req.body || {};
|
|
170
|
+
const actualContent = content || body;
|
|
171
|
+
if (!title || !category || !actualContent) {
|
|
172
|
+
return badRequest(res, 'Required fields: title, category, content (or body)');
|
|
173
|
+
}
|
|
174
|
+
const node = createMemoryNode({ slug: req.params.slug, title, category, content: actualContent });
|
|
175
|
+
if (tags && Array.isArray(tags)) node.tags = tags;
|
|
176
|
+
|
|
177
|
+
for (const key of PASSTHROUGH_FIELDS) {
|
|
178
|
+
if (req.body[key] !== undefined) node[key] = req.body[key];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
writeNode(node, vaultDir);
|
|
182
|
+
invalidate();
|
|
183
|
+
triggerMutation(node, vaultDir);
|
|
184
|
+
res.json(sanitizeNode(node));
|
|
185
|
+
} catch (err) {
|
|
186
|
+
serverError(res, err);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
router.patch('/api/memory/:slug', requireAuth, requireScope('memory:write'), (req, res) => {
|
|
191
|
+
try {
|
|
192
|
+
const vaultDir = resolveVaultFromQuery(req);
|
|
193
|
+
const existing = nodes(vaultDir).find(n => n.slug === req.params.slug);
|
|
194
|
+
if (!existing) return notFound(res, `Memory node not found: ${req.params.slug}`);
|
|
195
|
+
|
|
196
|
+
const { title, category, content, body, tags } = req.body || {};
|
|
197
|
+
const actualContent = content || body || existing.body;
|
|
198
|
+
const updated = createMemoryNode({
|
|
199
|
+
slug: existing.slug,
|
|
200
|
+
title: title ?? existing.title,
|
|
201
|
+
category: category ?? existing.category,
|
|
202
|
+
content: actualContent,
|
|
203
|
+
});
|
|
204
|
+
updated.tags = tags ?? existing.tags ?? [];
|
|
205
|
+
updated.created_at = existing.created_at;
|
|
206
|
+
|
|
207
|
+
for (const key of PASSTHROUGH_FIELDS) {
|
|
208
|
+
if (existing[key] !== undefined) updated[key] = existing[key];
|
|
209
|
+
}
|
|
210
|
+
for (const key of PASSTHROUGH_FIELDS) {
|
|
211
|
+
if (req.body[key] !== undefined) updated[key] = req.body[key];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
writeNode(updated, vaultDir);
|
|
215
|
+
invalidate();
|
|
216
|
+
triggerMutation(updated, vaultDir);
|
|
217
|
+
res.json(sanitizeNode(updated));
|
|
218
|
+
} catch (err) {
|
|
219
|
+
serverError(res, err);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
router.delete('/api/memory/:slug', requireAuth, requireScope('memory:write'), (req, res) => {
|
|
224
|
+
try {
|
|
225
|
+
const vaultDir = resolveVaultFromQuery(req);
|
|
226
|
+
const list = nodes(vaultDir);
|
|
227
|
+
const node = list.find(n => n.slug === req.params.slug);
|
|
228
|
+
if (!node) return notFound(res, `Memory node not found: ${req.params.slug}`);
|
|
229
|
+
|
|
230
|
+
if (node._filePath && fs.existsSync(node._filePath)) {
|
|
231
|
+
fs.unlinkSync(node._filePath);
|
|
232
|
+
} else {
|
|
233
|
+
for (const file of walkMd(vaultDir)) {
|
|
234
|
+
const raw = fs.readFileSync(file, 'utf8');
|
|
235
|
+
if (raw.includes(`slug: ${req.params.slug}`)) {
|
|
236
|
+
fs.unlinkSync(file);
|
|
237
|
+
break;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
invalidate();
|
|
242
|
+
triggerMutation(null, vaultDir);
|
|
243
|
+
res.json({ deleted: true, slug: req.params.slug });
|
|
244
|
+
} catch (err) {
|
|
245
|
+
serverError(res, err);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
export { router as memoryRouter };
|