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,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* total-recall backup
|
|
3
|
+
*
|
|
4
|
+
* Back up the meta-skill folder (the user's brain) to:
|
|
5
|
+
* - GitHub repo (diff-based, sovereign)
|
|
6
|
+
* - Encrypted/plain tarball
|
|
7
|
+
* - Obsidian vault (rsync)
|
|
8
|
+
*
|
|
9
|
+
* The meta-skill at .agent/skills/total-recall/ IS the brain — it contains
|
|
10
|
+
* rules, knowledge, preferences, corrections, research, integrations,
|
|
11
|
+
* automations, sub-skills, and scripts. Everything else is derived/runtime.
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* npx total-recall backup [--output <path>] [--no-encrypt]
|
|
15
|
+
* npx total-recall backup --push-git <remote-url>
|
|
16
|
+
* npx total-recall backup --obsidian <path>
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { spawnSync } from 'node:child_process';
|
|
20
|
+
import fs from 'node:fs';
|
|
21
|
+
import os from 'node:os';
|
|
22
|
+
import path from 'node:path';
|
|
23
|
+
|
|
24
|
+
import { resolveAgentDir, resolveBrainDir, parseLayerFlag } from './agent-dir.mjs';
|
|
25
|
+
|
|
26
|
+
/** The meta-skill folder — this is what gets backed up */
|
|
27
|
+
function getSkillDir(layer = 'auto') {
|
|
28
|
+
return resolveBrainDir(layer);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function commandExists(cmd) {
|
|
32
|
+
const r = spawnSync('which', [cmd], { stdio: 'pipe' });
|
|
33
|
+
return r.status === 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function parseArgs(args) {
|
|
37
|
+
// Parse layer flag first
|
|
38
|
+
const { layer, remainingArgs } = parseLayerFlag(args);
|
|
39
|
+
const opts = { output: null, encrypt: true, pushGit: null, obsidian: null, help: false, layer };
|
|
40
|
+
for (let i = 0; i < remainingArgs.length; i++) {
|
|
41
|
+
switch (remainingArgs[i]) {
|
|
42
|
+
case '--output':
|
|
43
|
+
case '-o':
|
|
44
|
+
opts.output = remainingArgs[++i];
|
|
45
|
+
break;
|
|
46
|
+
case '--no-encrypt':
|
|
47
|
+
opts.encrypt = false;
|
|
48
|
+
break;
|
|
49
|
+
case '--push-git':
|
|
50
|
+
opts.pushGit = remainingArgs[++i];
|
|
51
|
+
break;
|
|
52
|
+
case '--obsidian':
|
|
53
|
+
opts.obsidian = remainingArgs[++i];
|
|
54
|
+
break;
|
|
55
|
+
case '--help':
|
|
56
|
+
case '-h':
|
|
57
|
+
opts.help = true;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return opts;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function git(cwd, ...gitArgs) {
|
|
65
|
+
const result = spawnSync('git', gitArgs, { cwd, stdio: 'pipe' });
|
|
66
|
+
return {
|
|
67
|
+
ok: result.status === 0,
|
|
68
|
+
stdout: result.stdout?.toString().trim() || '',
|
|
69
|
+
stderr: result.stderr?.toString().trim() || '',
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function defaultOutput(encrypt) {
|
|
74
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
75
|
+
const suffix = encrypt ? '.tar.gz.gpg' : '.tar.gz';
|
|
76
|
+
return path.join(os.homedir(), `total-recall-backup-${stamp}${suffix}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function shellDoubleQuote(value) {
|
|
80
|
+
return `"${String(value).replace(/(["\\$`])/g, '\\$1')}"`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function printHelp() {
|
|
84
|
+
console.log(`
|
|
85
|
+
total-recall backup — Back up the meta-skill (your brain)
|
|
86
|
+
|
|
87
|
+
Usage: total-recall backup [options]
|
|
88
|
+
|
|
89
|
+
Options:
|
|
90
|
+
--output, -o <path> Destination archive path (tarball mode)
|
|
91
|
+
--no-encrypt Write a plain .tar.gz archive (default: gpg-encrypted)
|
|
92
|
+
--push-git <remote> Commit brain to a git remote (sovereign, diff-based)
|
|
93
|
+
e.g. git@github.com:you/total-recall-brain.git
|
|
94
|
+
Initialises the skill folder as a git repo on first run.
|
|
95
|
+
--obsidian <path> Rsync brain snapshot into an Obsidian vault folder.
|
|
96
|
+
e.g. ~/Documents/Obsidian Vault
|
|
97
|
+
Creates/updates "Total Recall/" inside that vault.
|
|
98
|
+
Pair with Obsidian Sync or iCloud for cloud backup.
|
|
99
|
+
--help, -h Show this help
|
|
100
|
+
|
|
101
|
+
What gets backed up:
|
|
102
|
+
.agent/skills/total-recall/ — The meta-skill (rules, knowledge, skills,
|
|
103
|
+
integrations, automations, scripts)
|
|
104
|
+
|
|
105
|
+
What does NOT get backed up (runtime/derived, can be regenerated):
|
|
106
|
+
memory-derived/ — Embeddings, indexes (inside the brain, but regenerable)
|
|
107
|
+
sessions/ — Conversation logs (inside the brain, ingested from IDEs)
|
|
108
|
+
|
|
109
|
+
Scheduling:
|
|
110
|
+
Add --backup-repo <remote> to "total-recall deploy" to install a
|
|
111
|
+
daily backup job (cron on Linux, launchd on macOS).
|
|
112
|
+
Add --backup-obsidian <path> to install a daily Obsidian rsync job.
|
|
113
|
+
`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function pushGitBackup(remote, layer = 'auto') {
|
|
117
|
+
const SKILL_DIR = getSkillDir(layer);
|
|
118
|
+
if (!commandExists('git')) {
|
|
119
|
+
console.error(' ❌ git not found — cannot push backup');
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!fs.existsSync(SKILL_DIR)) {
|
|
124
|
+
console.error(` ❌ Meta-skill not found: ${SKILL_DIR}`);
|
|
125
|
+
console.error(' Run: npx total-recall init');
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Initialise the skill folder as a git repo if needed
|
|
130
|
+
const gitDir = path.join(SKILL_DIR, '.git');
|
|
131
|
+
if (!fs.existsSync(gitDir)) {
|
|
132
|
+
console.error(` 🔧 Initialising git repo in ${SKILL_DIR}`);
|
|
133
|
+
const init = git(SKILL_DIR, 'init', '-b', 'main');
|
|
134
|
+
if (!init.ok) {
|
|
135
|
+
git(SKILL_DIR, 'init');
|
|
136
|
+
git(SKILL_DIR, 'checkout', '-b', 'main');
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Resolve the remote: if the argument is an existing remote name, use its URL.
|
|
141
|
+
// Otherwise treat it as a URL and configure the 'backup' remote to point there.
|
|
142
|
+
const remoteCheck = git(SKILL_DIR, 'remote', 'get-url', remote);
|
|
143
|
+
if (remoteCheck.ok) {
|
|
144
|
+
// Argument is an existing remote name — use it as-is, don't overwrite
|
|
145
|
+
console.error(` 🔗 Using existing remote "${remote}" → ${remoteCheck.stdout}`);
|
|
146
|
+
} else if (remote.includes('/') || remote.includes(':')) {
|
|
147
|
+
// Argument looks like a URL — add or update the 'backup' remote
|
|
148
|
+
const backupCheck = git(SKILL_DIR, 'remote', 'get-url', 'backup');
|
|
149
|
+
if (!backupCheck.ok) {
|
|
150
|
+
git(SKILL_DIR, 'remote', 'add', 'backup', remote);
|
|
151
|
+
console.error(` 🔗 Remote "backup" added → ${remote}`);
|
|
152
|
+
} else if (backupCheck.stdout !== remote) {
|
|
153
|
+
git(SKILL_DIR, 'remote', 'set-url', 'backup', remote);
|
|
154
|
+
console.error(` 🔗 Remote "backup" updated → ${remote}`);
|
|
155
|
+
}
|
|
156
|
+
// Push to the 'backup' remote name, not the raw URL
|
|
157
|
+
remote = 'backup';
|
|
158
|
+
} else {
|
|
159
|
+
console.error(` ❌ "${remote}" is not a configured git remote and doesn't look like a URL.`);
|
|
160
|
+
console.error(' Use a remote name (e.g. "backup") or a full URL (e.g. "https://github.com/user/repo.git")');
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Stage all changes
|
|
165
|
+
git(SKILL_DIR, 'add', '-A');
|
|
166
|
+
const status = git(SKILL_DIR, 'status', '--porcelain');
|
|
167
|
+
const stamp = new Date().toISOString().replace('T', ' ').slice(0, 19);
|
|
168
|
+
|
|
169
|
+
if (!status.stdout) {
|
|
170
|
+
console.error(` ✅ Brain unchanged — nothing to push (${stamp})`);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Commit (fall back to embedded identity if git config is missing)
|
|
175
|
+
let commit = git(SKILL_DIR, 'commit', '-m', `backup: ${stamp}`);
|
|
176
|
+
if (!commit.ok) {
|
|
177
|
+
commit = git(SKILL_DIR, '-c', 'user.email=backup@total-recall', '-c', 'user.name=Total Recall', 'commit', '-m', `backup: ${stamp}`);
|
|
178
|
+
if (!commit.ok) {
|
|
179
|
+
console.error(` ❌ Commit failed: ${commit.stderr}`);
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Push (use --force-with-lease to guard against diverged history)
|
|
185
|
+
console.error(` 📤 Pushing brain to ${remote}...`);
|
|
186
|
+
let push = git(SKILL_DIR, 'push', 'backup', 'HEAD:main', '--force-with-lease');
|
|
187
|
+
if (!push.ok) {
|
|
188
|
+
push = git(SKILL_DIR, 'push', 'backup', 'HEAD:main', '--force');
|
|
189
|
+
if (!push.ok) {
|
|
190
|
+
console.error(` ❌ Push failed: ${push.stderr}`);
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
console.error(` ✅ Brain pushed to ${remote} (${stamp})`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function obsidianBackup(vaultPath, layer = 'auto') {
|
|
198
|
+
const SKILL_DIR = getSkillDir(layer);
|
|
199
|
+
if (!commandExists('rsync')) {
|
|
200
|
+
console.error(' ❌ rsync not found — install it or use --push-git instead');
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (!fs.existsSync(SKILL_DIR)) {
|
|
205
|
+
console.error(` ❌ Meta-skill not found: ${SKILL_DIR}`);
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const expanded = vaultPath.startsWith('~')
|
|
210
|
+
? path.join(os.homedir(), vaultPath.slice(1))
|
|
211
|
+
: vaultPath;
|
|
212
|
+
|
|
213
|
+
if (!fs.existsSync(expanded)) {
|
|
214
|
+
console.error(` ❌ Obsidian vault not found: ${expanded}`);
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const dest = path.join(expanded, 'Total Recall');
|
|
219
|
+
const stamp = new Date().toISOString().replace('T', ' ').slice(0, 19);
|
|
220
|
+
|
|
221
|
+
// Remove stale symlinks (e.g. from a prior install that used the wrong path)
|
|
222
|
+
// so rsync can create the real directory
|
|
223
|
+
try {
|
|
224
|
+
const stat = fs.lstatSync(dest);
|
|
225
|
+
if (stat.isSymbolicLink()) {
|
|
226
|
+
console.error(` ⚠️ Removing stale symlink at ${dest}`);
|
|
227
|
+
fs.unlinkSync(dest);
|
|
228
|
+
}
|
|
229
|
+
} catch {
|
|
230
|
+
// dest doesn't exist yet — that's fine
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
console.error(` 📓 Syncing brain → ${dest} (${stamp})`);
|
|
234
|
+
|
|
235
|
+
// rsync with delete so removed files don't linger in Obsidian
|
|
236
|
+
const result = spawnSync(
|
|
237
|
+
'rsync',
|
|
238
|
+
['-a', '--delete', '--exclude=.git', SKILL_DIR + '/', dest + '/'],
|
|
239
|
+
{ stdio: 'inherit' }
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
if (result.status !== 0) {
|
|
243
|
+
console.error(` ❌ rsync failed (exit ${result.status})`);
|
|
244
|
+
process.exit(result.status || 1);
|
|
245
|
+
}
|
|
246
|
+
console.error(` ✅ Brain synced to Obsidian (${stamp})`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export default async function backup(args) {
|
|
250
|
+
const opts = parseArgs(args);
|
|
251
|
+
const SKILL_DIR = getSkillDir(opts.layer);
|
|
252
|
+
if (opts.help) {
|
|
253
|
+
printHelp();
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (!fs.existsSync(SKILL_DIR)) {
|
|
258
|
+
console.error(` ❌ Meta-skill not found: ${SKILL_DIR}`);
|
|
259
|
+
console.error(' Run: npx total-recall init');
|
|
260
|
+
process.exit(1);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Git-push mode: sovereign diff-based backup
|
|
264
|
+
if (opts.pushGit) {
|
|
265
|
+
await pushGitBackup(opts.pushGit, opts.layer);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Obsidian rsync mode
|
|
270
|
+
if (opts.obsidian) {
|
|
271
|
+
obsidianBackup(opts.obsidian, opts.layer);
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Tarball mode: back up the meta-skill folder
|
|
276
|
+
const output = opts.output || defaultOutput(opts.encrypt);
|
|
277
|
+
fs.mkdirSync(path.dirname(output), { recursive: true });
|
|
278
|
+
|
|
279
|
+
const parentDir = path.dirname(SKILL_DIR);
|
|
280
|
+
const skillBase = path.basename(SKILL_DIR);
|
|
281
|
+
|
|
282
|
+
let command;
|
|
283
|
+
if (opts.encrypt) {
|
|
284
|
+
if (!commandExists('gpg')) {
|
|
285
|
+
console.error(' ❌ gpg not found; use --no-encrypt or install gpg');
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
288
|
+
command = [
|
|
289
|
+
'tar -czf -',
|
|
290
|
+
'-C', shellDoubleQuote(parentDir),
|
|
291
|
+
shellDoubleQuote(skillBase),
|
|
292
|
+
'|',
|
|
293
|
+
'gpg --symmetric --cipher-algo AES256 --output',
|
|
294
|
+
shellDoubleQuote(output)
|
|
295
|
+
].join(' ');
|
|
296
|
+
} else {
|
|
297
|
+
command = [
|
|
298
|
+
'tar -czf', shellDoubleQuote(output),
|
|
299
|
+
'-C', shellDoubleQuote(parentDir),
|
|
300
|
+
shellDoubleQuote(skillBase)
|
|
301
|
+
].join(' ');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
console.error(` 📦 Creating backup: ${output}`);
|
|
305
|
+
const result = spawnSync('sh', ['-c', command], { stdio: 'inherit' });
|
|
306
|
+
if (result.status !== 0) {
|
|
307
|
+
console.error(` ❌ Backup failed (exit ${result.status})`);
|
|
308
|
+
process.exit(result.status || 1);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const stat = fs.statSync(output);
|
|
312
|
+
console.error(` ✅ Backup complete (${stat.size} bytes)`);
|
|
313
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { getBothBrains, resolveBrainDir, getGlobalAgentDir } from './agent-dir.mjs';
|
|
5
|
+
|
|
6
|
+
function printHelp() { console.log(`
|
|
7
|
+
total-recall brain — Manage brain layers, contexts, and registrations
|
|
8
|
+
|
|
9
|
+
Usage: total-recall brain <command> [options]
|
|
10
|
+
|
|
11
|
+
Commands:
|
|
12
|
+
list List global and all registered project brains (default)
|
|
13
|
+
status Show active context resolution paths and loaded layers
|
|
14
|
+
register <project-path> Register a project directory path in the registry
|
|
15
|
+
unregister <project-path> Remove a project directory path from the registry
|
|
16
|
+
|
|
17
|
+
Examples:
|
|
18
|
+
npx total-recall brain
|
|
19
|
+
npx total-recall brain status
|
|
20
|
+
npx total-recall brain register .
|
|
21
|
+
npx total-recall brain unregister /path/to/project
|
|
22
|
+
`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default async function brain(args) {
|
|
26
|
+
const subcommand = args[0] || 'list';
|
|
27
|
+
|
|
28
|
+
if (subcommand === '--help' || subcommand === '-h') {
|
|
29
|
+
printHelp();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const globalAgentDir = getGlobalAgentDir();
|
|
33
|
+
const globalBrainDir = path.join(globalAgentDir, 'skills', 'total-recall');
|
|
34
|
+
const registryPath = path.join(globalBrainDir, 'config', 'project-registry.json');
|
|
35
|
+
switch (subcommand.toLowerCase()) {
|
|
36
|
+
case 'list':
|
|
37
|
+
await handleList(registryPath, globalBrainDir);
|
|
38
|
+
break;
|
|
39
|
+
case 'status':
|
|
40
|
+
await handleStatus();
|
|
41
|
+
break;
|
|
42
|
+
case 'register':
|
|
43
|
+
await handleRegister(registryPath, args.slice(1));
|
|
44
|
+
break;
|
|
45
|
+
case 'unregister':
|
|
46
|
+
await handleUnregister(registryPath, args.slice(1));
|
|
47
|
+
break;
|
|
48
|
+
default:
|
|
49
|
+
console.error(` ⚠️ Unknown brain command: "${subcommand}". Run total-recall brain --help for details.`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function handleList(registryPath, globalBrainDir) {
|
|
55
|
+
const brains = getBothBrains();
|
|
56
|
+
const activeProjectDir = brains.project ? brains.project.brainDir : null;
|
|
57
|
+
|
|
58
|
+
console.log('\n 🧠 Active Total Recall Brain Registry:\n');
|
|
59
|
+
|
|
60
|
+
// Print Global Brain
|
|
61
|
+
let globalCount = 0;
|
|
62
|
+
const globalVault = path.join(globalBrainDir, 'memory-vault');
|
|
63
|
+
if (fs.existsSync(globalVault)) {
|
|
64
|
+
globalCount = countMdFiles(globalVault);
|
|
65
|
+
}
|
|
66
|
+
const isGlobalActive = !activeProjectDir;
|
|
67
|
+
const activeGlobalMarker = isGlobalActive ? ' ⭐️ [ACTIVE]' : '';
|
|
68
|
+
console.log(` 🌐 Global Brain${activeGlobalMarker}`);
|
|
69
|
+
console.log(` ├── Path: ${globalBrainDir}`);
|
|
70
|
+
console.log(` └── Nodes: ${globalCount} compiled SSSS memory nodes`);
|
|
71
|
+
console.log('');
|
|
72
|
+
|
|
73
|
+
// Print Project Brains
|
|
74
|
+
if (fs.existsSync(registryPath)) {
|
|
75
|
+
try {
|
|
76
|
+
const registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
77
|
+
if (registry.length === 0) {
|
|
78
|
+
console.log(' 📁 No registered project brains yet. Run `total-recall brain register .` to track one.');
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
console.log(` 📁 Registered Project Brains (${registry.length}):`);
|
|
83
|
+
for (const project of registry) {
|
|
84
|
+
const isActive = activeProjectDir && path.resolve(activeProjectDir) === path.resolve(project.brainDir);
|
|
85
|
+
const marker = isActive ? ' ⭐️ [ACTIVE]' : '';
|
|
86
|
+
const exists = fs.existsSync(project.brainDir);
|
|
87
|
+
const existsLabel = exists ? '' : ' ⚠️ [NOT FOUND]';
|
|
88
|
+
const projectVault = path.join(project.brainDir, 'memory-vault');
|
|
89
|
+
const nodeCount = exists ? countMdFiles(projectVault) : 0;
|
|
90
|
+
|
|
91
|
+
console.log(` ├── 📄 ${project.name}${marker}${existsLabel}`);
|
|
92
|
+
console.log(` │ ├── Root: ${project.path}`);
|
|
93
|
+
console.log(` │ ├── Brain: ${project.brainDir}`);
|
|
94
|
+
console.log(` │ └── Nodes: ${nodeCount} memory nodes`);
|
|
95
|
+
}
|
|
96
|
+
} catch (err) {
|
|
97
|
+
console.error(` ⚠️ Failed to read project registry: ${err.message}`);
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
console.log(' 📁 No registered project brains yet. Run `total-recall brain register .` to track one.');
|
|
101
|
+
}
|
|
102
|
+
console.log('');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function handleStatus() {
|
|
106
|
+
const brains = getBothBrains();
|
|
107
|
+
|
|
108
|
+
console.log('\n 🧠 Active Brain Context Status:\n');
|
|
109
|
+
|
|
110
|
+
console.log(` Current Working Dir: ${process.cwd()}`);
|
|
111
|
+
|
|
112
|
+
if (brains.global) {
|
|
113
|
+
console.log(` 🌐 Global Layer:`);
|
|
114
|
+
console.log(` ├── Resolved Path: ${brains.global.brainDir}`);
|
|
115
|
+
console.log(` └── Status: Initialized & Available`);
|
|
116
|
+
} else {
|
|
117
|
+
console.log(` 🌐 Global Layer: ⚠️ Not Resolved`);
|
|
118
|
+
}
|
|
119
|
+
if (brains.project) {
|
|
120
|
+
console.log(` 📁 Project Layer:`);
|
|
121
|
+
console.log(` ├── Resolved Path: ${brains.project.brainDir}`);
|
|
122
|
+
console.log(` ├── Project Root: ${path.dirname(brains.project.agentDir)}`);
|
|
123
|
+
console.log(` └── Status: ACTIVE (Cascades over Global)`);
|
|
124
|
+
} else {
|
|
125
|
+
console.log(` 📁 Project Layer: None Detected (Context defaults fully to Global)`);
|
|
126
|
+
}
|
|
127
|
+
console.log('');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async function handleRegister(registryPath, targetArgs) {
|
|
131
|
+
const targetPath = targetArgs[0];
|
|
132
|
+
if (!targetPath) {
|
|
133
|
+
console.error(' ⚠️ Missing project path. Usage: npx total-recall brain register <project-path>');
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const absoluteRoot = path.resolve(targetPath);
|
|
138
|
+
if (!fs.existsSync(absoluteRoot) || !fs.statSync(absoluteRoot).isDirectory()) {
|
|
139
|
+
console.error(` ⚠️ Directory not found: "${absoluteRoot}"`);
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const brainDir = path.join(absoluteRoot, '.agent', 'skills', 'total-recall');
|
|
144
|
+
const name = path.basename(absoluteRoot);
|
|
145
|
+
|
|
146
|
+
// Ensure registry folder exists
|
|
147
|
+
fs.mkdirSync(path.dirname(registryPath), { recursive: true });
|
|
148
|
+
|
|
149
|
+
let registry = [];
|
|
150
|
+
if (fs.existsSync(registryPath)) {
|
|
151
|
+
try {
|
|
152
|
+
registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
153
|
+
} catch {
|
|
154
|
+
registry = [];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Check if already exists
|
|
159
|
+
const existingIndex = registry.findIndex(p => path.resolve(p.path) === absoluteRoot);
|
|
160
|
+
const nowStr = new Date().toISOString();
|
|
161
|
+
|
|
162
|
+
if (existingIndex !== -1) {
|
|
163
|
+
registry[existingIndex].brainDir = brainDir;
|
|
164
|
+
registry[existingIndex].name = name;
|
|
165
|
+
} else {
|
|
166
|
+
registry.push({
|
|
167
|
+
name,
|
|
168
|
+
path: absoluteRoot,
|
|
169
|
+
brainDir,
|
|
170
|
+
registered_at: nowStr
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
fs.writeFileSync(registryPath, JSON.stringify(registry, null, 2), 'utf8');
|
|
175
|
+
console.log(` ✅ Successfully registered project brain: "${name}"`);
|
|
176
|
+
console.log(` ├── Root: ${absoluteRoot}`);
|
|
177
|
+
console.log(` └── Brain: ${brainDir}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function handleUnregister(registryPath, targetArgs) {
|
|
181
|
+
const targetPath = targetArgs[0];
|
|
182
|
+
if (!targetPath) {
|
|
183
|
+
console.error(' ⚠️ Missing project path. Usage: npx total-recall brain unregister <project-path>');
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const absoluteRoot = path.resolve(targetPath);
|
|
188
|
+
if (!fs.existsSync(registryPath)) {
|
|
189
|
+
console.log(' ℹ️ No project registry exists yet.');
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
let registry = [];
|
|
194
|
+
try {
|
|
195
|
+
registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
196
|
+
} catch {
|
|
197
|
+
console.error(' ⚠️ Failed to read project registry.');
|
|
198
|
+
process.exit(1);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const beforeLen = registry.length;
|
|
202
|
+
registry = registry.filter(p => path.resolve(p.path) !== absoluteRoot);
|
|
203
|
+
|
|
204
|
+
if (registry.length === beforeLen) {
|
|
205
|
+
console.log(` ℹ️ Path not registered: "${absoluteRoot}"`);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
fs.writeFileSync(registryPath, JSON.stringify(registry, null, 2), 'utf8');
|
|
210
|
+
console.log(` ✅ Successfully unregistered project: "${absoluteRoot}"`);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function countMdFiles(dir) {
|
|
214
|
+
if (!fs.existsSync(dir)) return 0;
|
|
215
|
+
let count = 0;
|
|
216
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
217
|
+
if (entry.isDirectory()) {
|
|
218
|
+
count += countMdFiles(path.join(dir, entry.name));
|
|
219
|
+
} else if (entry.name.endsWith('.md')) {
|
|
220
|
+
count++;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return count;
|
|
224
|
+
}
|
package/src/cli/chat.mjs
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import readline from 'readline';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { resolveBrainDir } from './agent-dir.mjs';
|
|
4
|
+
import { callLocalRuntime, loadRuntimeConfig, checkRuntimeHealth } from '../core/runtime.mjs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Total Recall CLI Chat Interface
|
|
8
|
+
* Allows direct conversation with the Sovereign OS kernel from the terminal.
|
|
9
|
+
*/
|
|
10
|
+
export default async function runChat(args) {
|
|
11
|
+
console.log('🤖 Total Recall CLI Chat initialized. Type "exit" to quit.\n');
|
|
12
|
+
|
|
13
|
+
const configDir = path.join(resolveBrainDir(), 'config');
|
|
14
|
+
|
|
15
|
+
let config;
|
|
16
|
+
let caller;
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
config = loadRuntimeConfig(path.join(configDir, 'runtime.yml'));
|
|
20
|
+
const health = await checkRuntimeHealth(config);
|
|
21
|
+
if (health.status === 'degraded') {
|
|
22
|
+
console.warn(`[!] Local runtime degraded: ${health.reason}`);
|
|
23
|
+
}
|
|
24
|
+
caller = (prompt, system) => callLocalRuntime(prompt, system, config);
|
|
25
|
+
console.log(`[Config] Connected to Local Runtime (${config.agents?.[0]?.name || 'default'})\n`);
|
|
26
|
+
} catch (e) {
|
|
27
|
+
console.error(`[!] Failed to load configuration: ${e.message}`);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const rl = readline.createInterface({
|
|
32
|
+
input: process.stdin,
|
|
33
|
+
output: process.stdout,
|
|
34
|
+
prompt: 'You: '
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const systemMessage = 'You are the Total Recall Sovereign OS kernel. Keep responses concise and direct.';
|
|
38
|
+
const messages = [];
|
|
39
|
+
|
|
40
|
+
rl.prompt();
|
|
41
|
+
|
|
42
|
+
rl.on('line', async (line) => {
|
|
43
|
+
const input = line.trim();
|
|
44
|
+
if (input.toLowerCase() === 'exit' || input.toLowerCase() === 'quit') {
|
|
45
|
+
console.log('Goodbye.');
|
|
46
|
+
process.exit(0);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!input) {
|
|
50
|
+
rl.prompt();
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Build the prompt with history
|
|
55
|
+
const contextStr = messages.map(m => `${m.role}: ${m.content}`).join('\n');
|
|
56
|
+
const fullPrompt = contextStr ? `${contextStr}\nuser: ${input}\nassistant: ` : input;
|
|
57
|
+
|
|
58
|
+
messages.push({ role: 'user', content: input });
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const response = await caller(fullPrompt, systemMessage);
|
|
62
|
+
console.log(`\nBrain: ${response}\n`);
|
|
63
|
+
messages.push({ role: 'assistant', content: response });
|
|
64
|
+
} catch (err) {
|
|
65
|
+
console.error(`\n[!] Error: ${err.message}\n`);
|
|
66
|
+
messages.pop(); // Remove user message if failed
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
rl.prompt();
|
|
70
|
+
}).on('close', () => {
|
|
71
|
+
console.log('Goodbye.');
|
|
72
|
+
process.exit(0);
|
|
73
|
+
});
|
|
74
|
+
}
|