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.
Files changed (170) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +173 -0
  3. package/bin/antigravity.mjs +212 -0
  4. package/bin/init.mjs +36 -0
  5. package/bin/oci-sniper.sh +69 -0
  6. package/bin/total-recall.mjs +229 -0
  7. package/docs/ARCHITECTURE.md +251 -0
  8. package/docs/IDE_INTEGRATION.md +112 -0
  9. package/package.json +71 -0
  10. package/scaffold/.agent/skills/total-recall/SKILL.md +195 -0
  11. package/scaffold/.agent/skills/total-recall/evals/evals.json +20 -0
  12. package/scaffold/.agent/skills/total-recall/memory-vault/concepts/cli-help-reference.md +145 -0
  13. package/scaffold/.agent/skills/total-recall/memory-vault/invariants/operating-instructions.md +75 -0
  14. package/scaffold/.agent/skills/total-recall/memory-vault/preferences/always-websearch-gap.md +16 -0
  15. package/scaffold/.agent/skills/total-recall/memory-vault/preferences/topic-research-sop.md +17 -0
  16. package/scaffold/.agent/skills/total-recall/references/api-reference.md +187 -0
  17. package/scaffold/.agent/skills/total-recall/references/architecture-reference.md +81 -0
  18. package/scaffold/.agent/skills/total-recall/references/cli-reference.md +103 -0
  19. package/scaffold/.agent/skills/total-recall/references/ssss-reference.md +88 -0
  20. package/scaffold/.agent/skills/total-recall/references/troubleshooting.md +68 -0
  21. package/scaffold/.agent/skills/total-recall/scripts/sync-repo.mjs +83 -0
  22. package/scaffold/.agent/skills/total-recall/skills/cli-agents/SKILL.md +122 -0
  23. package/scaffold/.agent/skills/total-recall/skills/cli-agents/agents.yml +27 -0
  24. package/scaffold/.agent/skills/total-recall/skills/cli-agents/references/antigravity.txt +20 -0
  25. package/scaffold/.agent/skills/total-recall/skills/cli-agents/references/claude.txt +58 -0
  26. package/scaffold/.agent/skills/total-recall/skills/cli-agents/references/codex.txt +81 -0
  27. package/scaffold/.agent/skills/total-recall/skills/cli-agents/references/gemini.txt +44 -0
  28. package/scaffold/.agent/skills/total-recall/skills/research/SKILL.md +337 -0
  29. package/scaffold/.agent/skills/total-recall/skills/skill/SKILL.md +281 -0
  30. package/scaffold/.agent/skills/total-recall/skills/skill/assets/skill-template/SKILL.md +20 -0
  31. package/scaffold/.agent/skills/total-recall/skills/skill/evals/evals.json +20 -0
  32. package/scaffold/.agent/skills/total-recall/skills/skill/hooks/on-skill-add.sh +14 -0
  33. package/scaffold/.agent/skills/total-recall/skills/skill/hooks/on-skill-delete.sh +14 -0
  34. package/scaffold/.agent/skills/total-recall/skills/skill/learnings/.gitkeep +0 -0
  35. package/scaffold/.agent/skills/total-recall/skills/skill/references/.gitkeep +0 -0
  36. package/scaffold/.agent/skills/total-recall/skills/skill/scripts/.gitkeep +0 -0
  37. package/scaffold/.agent/skills/total-recall/skills/skill/scripts/create-skill.sh +65 -0
  38. package/scaffold/.agent/skills/total-recall/skills/skill/scripts/enforce-skill-optimization.mjs +135 -0
  39. package/scaffold/.agent/skills/total-recall/skills/skill/scripts/find-skills.mjs +124 -0
  40. package/scaffold/.agent/skills/total-recall/skills/skill/scripts/scan-skill.mjs +207 -0
  41. package/scaffold/.agent/skills/total-recall/skills/skill/scripts/skill-watcher.mjs +64 -0
  42. package/scaffold/.agent/skills/total-recall/skills/skill/subagents/audit-all-skills.md +62 -0
  43. package/scaffold/.agent/skills/total-recall/skills/ssss/SKILL.md +27 -0
  44. package/scaffold/.agent/skills/total-recall/skills/ssss/evals/evals.json +17 -0
  45. package/scaffold/.agent/skills/total-recall/skills/ssss/references/ssss-spec.md +117 -0
  46. package/scaffold/.agent/skills/total-recall/skills/ssss/scripts/validate-schema.mjs +54 -0
  47. package/scaffold/.agent/skills/total-recall/skills/ssss/subagents/audit-ssss-node.md +9 -0
  48. package/scaffold/.agent/skills/total-recall/subagents/total-recall-diagnostician.md +13 -0
  49. package/src/cli/agent-dir.mjs +105 -0
  50. package/src/cli/backup.mjs +313 -0
  51. package/src/cli/brain.mjs +224 -0
  52. package/src/cli/chat.mjs +74 -0
  53. package/src/cli/config.mjs +247 -0
  54. package/src/cli/connect.mjs +872 -0
  55. package/src/cli/daemon.mjs +228 -0
  56. package/src/cli/deploy-ui.mjs +1793 -0
  57. package/src/cli/deploy.mjs +1726 -0
  58. package/src/cli/doctor.mjs +254 -0
  59. package/src/cli/dream.mjs +80 -0
  60. package/src/cli/forget.mjs +92 -0
  61. package/src/cli/friction.mjs +21 -0
  62. package/src/cli/generate-pat.mjs +65 -0
  63. package/src/cli/hash-password.mjs +22 -0
  64. package/src/cli/help.mjs +153 -0
  65. package/src/cli/import-rules.mjs +102 -0
  66. package/src/cli/ingest/index.mjs +318 -0
  67. package/src/cli/ingest/parsers/chrome-bookmarks.mjs +238 -0
  68. package/src/cli/ingest/parsers/google-keep.mjs +147 -0
  69. package/src/cli/ingest/parsers/search-history.mjs +142 -0
  70. package/src/cli/ingest/parsers/youtube-history.mjs +181 -0
  71. package/src/cli/ingest/utils/dedup.mjs +69 -0
  72. package/src/cli/ingest/utils/takeout-walker.mjs +101 -0
  73. package/src/cli/ingest.mjs +102 -0
  74. package/src/cli/init.mjs +736 -0
  75. package/src/cli/integration-dispatcher.mjs +128 -0
  76. package/src/cli/lint.mjs +166 -0
  77. package/src/cli/map.mjs +194 -0
  78. package/src/cli/migrate.mjs +87 -0
  79. package/src/cli/rebuild.mjs +88 -0
  80. package/src/cli/recall.mjs +219 -0
  81. package/src/cli/relay.mjs +518 -0
  82. package/src/cli/remember.mjs +306 -0
  83. package/src/cli/research.mjs +381 -0
  84. package/src/cli/reset-password.mjs +75 -0
  85. package/src/cli/restore.mjs +135 -0
  86. package/src/cli/setup.mjs +616 -0
  87. package/src/cli/share.mjs +180 -0
  88. package/src/cli/skill.mjs +313 -0
  89. package/src/cli/snapshot.mjs +108 -0
  90. package/src/cli/start.mjs +63 -0
  91. package/src/cli/status.mjs +296 -0
  92. package/src/cli/sync.mjs +383 -0
  93. package/src/cli/uninstall.mjs +613 -0
  94. package/src/cli/upgrade.mjs +141 -0
  95. package/src/cli/wizard.html +3682 -0
  96. package/src/core/blackboard.mjs +54 -0
  97. package/src/core/clarity-rewriter.mjs +770 -0
  98. package/src/core/conclusion-writer.mjs +259 -0
  99. package/src/core/config.mjs +277 -0
  100. package/src/core/conflict-detector.mjs +536 -0
  101. package/src/core/crypto.mjs +107 -0
  102. package/src/core/daemon-control.mjs +158 -0
  103. package/src/core/daemon-loop.mjs +364 -0
  104. package/src/core/dream.mjs +347 -0
  105. package/src/core/drift-detector.mjs +143 -0
  106. package/src/core/embeddings.mjs +632 -0
  107. package/src/core/emergency-alerts.mjs +137 -0
  108. package/src/core/evolution.mjs +107 -0
  109. package/src/core/fact-seeker.mjs +1602 -0
  110. package/src/core/friction.mjs +95 -0
  111. package/src/core/import-rules.mjs +308 -0
  112. package/src/core/inference-engine.mjs +367 -0
  113. package/src/core/logger.mjs +75 -0
  114. package/src/core/memory-layers.mjs +96 -0
  115. package/src/core/migrate.mjs +116 -0
  116. package/src/core/notifications.mjs +77 -0
  117. package/src/core/operation-validator.mjs +272 -0
  118. package/src/core/optimizer.mjs +330 -0
  119. package/src/core/pattern_detector.mjs +113 -0
  120. package/src/core/post-mortem.mjs +413 -0
  121. package/src/core/protect-instructions.mjs +148 -0
  122. package/src/core/quick-capture.mjs +145 -0
  123. package/src/core/research-queue.mjs +431 -0
  124. package/src/core/research.mjs +461 -0
  125. package/src/core/runtime.mjs +515 -0
  126. package/src/core/sandbox.mjs +163 -0
  127. package/src/core/scheduler.mjs +569 -0
  128. package/src/core/schema.mjs +333 -0
  129. package/src/core/search.mjs +219 -0
  130. package/src/core/semantic-index.mjs +276 -0
  131. package/src/core/session-watcher.mjs +850 -0
  132. package/src/core/snapshot.mjs +133 -0
  133. package/src/core/source-adapters.mjs +1038 -0
  134. package/src/core/steering.mjs +137 -0
  135. package/src/core/surface.mjs +444 -0
  136. package/src/core/task_runner.mjs +125 -0
  137. package/src/core/tts.mjs +142 -0
  138. package/src/core/usage-tracker.mjs +349 -0
  139. package/src/core/vault-cache.mjs +76 -0
  140. package/src/core/vault.mjs +273 -0
  141. package/src/core/watchdog.mjs +236 -0
  142. package/src/server/api.mjs +944 -0
  143. package/src/server/auth.mjs +410 -0
  144. package/src/server/index.mjs +868 -0
  145. package/src/server/keys.mjs +181 -0
  146. package/src/server/rest.mjs +1652 -0
  147. package/src/server/route-manifest.json +77 -0
  148. package/src/server/routes/_shared.mjs +93 -0
  149. package/src/server/routes/keys.mjs +55 -0
  150. package/src/server/routes/memory.mjs +249 -0
  151. package/src/server/routes/sessions.mjs +336 -0
  152. package/src/server/routes/share.mjs +138 -0
  153. package/src/server/tools.mjs +618 -0
  154. package/templates/Caddyfile +37 -0
  155. package/templates/com.totalrecall.backup.plist +42 -0
  156. package/templates/com.totalrecall.daemon.plist +40 -0
  157. package/templates/com.totalrecall.duckdns.plist +23 -0
  158. package/templates/com.totalrecall.server.plist +46 -0
  159. package/templates/default-config/budget.yml +6 -0
  160. package/templates/default-config/runtime.yml +11 -0
  161. package/templates/default-config/security.yml +53 -0
  162. package/templates/default-config/voice.yml +34 -0
  163. package/templates/obsidian-queries/conflicts.md +26 -0
  164. package/templates/obsidian-queries/daily.md +17 -0
  165. package/templates/obsidian-queries/index.md +38 -0
  166. package/templates/obsidian-queries/skills.md +14 -0
  167. package/templates/onboarding-interview.md +72 -0
  168. package/templates/skill_engineering.md +40 -0
  169. package/templates/total-recall-daemon.service +21 -0
  170. 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
+ }
@@ -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
+ }