total-recall-brain 3.28.2 → 3.30.1

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 (134) hide show
  1. package/bin/total-recall.mjs +24 -1
  2. package/docs/ARCHITECTURE.md +26 -18
  3. package/extension/background.js +259 -84
  4. package/extension/content-script.css +7 -13
  5. package/extension/content-script.js +252 -477
  6. package/extension/icons/icon-128.png +0 -0
  7. package/extension/icons/icon-16.png +0 -0
  8. package/extension/icons/icon-32.png +0 -0
  9. package/extension/icons/icon-48.png +0 -0
  10. package/extension/icons/mark.svg +6 -0
  11. package/extension/lib/brain-client.js +160 -73
  12. package/extension/lib/tokens.css +187 -0
  13. package/extension/lib/ui-helpers.js +229 -0
  14. package/extension/lib/ui-helpers.spec.mjs +94 -0
  15. package/extension/manifest.json +20 -5
  16. package/extension/options/options.css +64 -218
  17. package/extension/options/options.html +91 -43
  18. package/extension/options/options.js +208 -85
  19. package/extension/sidepanel/sidepanel.css +268 -970
  20. package/extension/sidepanel/sidepanel.html +200 -166
  21. package/extension/sidepanel/sidepanel.js +788 -942
  22. package/frontend/dist/assets/index-DGMNZi_1.js +5742 -0
  23. package/frontend/dist/index.html +1 -1
  24. package/frontend/src/api/plugins.ts +124 -92
  25. package/frontend/src/components/ResearchAgendaTab.spec.tsx +11 -0
  26. package/frontend/src/components/ResearchAgendaTab.tsx +19 -14
  27. package/frontend/src/pages/PluginsPage.spec.tsx +87 -16
  28. package/frontend/src/pages/PluginsPage.tsx +454 -1218
  29. package/package.json +2 -1
  30. package/plugins/code-quality/README.md +25 -0
  31. package/plugins/code-quality/cli.mjs +25 -0
  32. package/plugins/code-quality/plugin.json +17 -0
  33. package/plugins/git-sentinel/README.md +27 -0
  34. package/{scaffold/.agent/plugins → plugins}/git-sentinel/cli.mjs +8 -5
  35. package/plugins/git-sentinel/plugin.json +18 -0
  36. package/plugins/system-monitor/README.md +26 -0
  37. package/{scaffold/.agent/plugins → plugins}/system-monitor/cli.mjs +5 -4
  38. package/{scaffold/.agent/plugins → plugins}/system-monitor/generator.mjs +4 -5
  39. package/plugins/system-monitor/plugin.json +29 -0
  40. package/scaffold/.agent/skills/meta-harness/SKILL.md +4 -4
  41. package/scaffold/.agent/skills/plugins/SKILL.md +53 -0
  42. package/scaffold/.agent/skills/total-recall/SKILL.md +22 -6
  43. package/scaffold/.agent/skills/total-recall/scripts/brain-state.json +1 -0
  44. package/src/cli/agent.mjs +1 -1
  45. package/src/cli/app/index.mjs +493 -0
  46. package/src/cli/brain.mjs +33 -3
  47. package/src/cli/command.mjs +80 -41
  48. package/src/cli/connect.mjs +10 -1
  49. package/src/cli/harness.mjs +1 -1
  50. package/src/cli/init.mjs +3 -1
  51. package/src/cli/mesh.mjs +118 -12
  52. package/src/cli/plugin/create.mjs +144 -25
  53. package/src/cli/plugin/index.mjs +83 -53
  54. package/src/cli/plugin/install.mjs +10 -127
  55. package/src/cli/plugin/list.mjs +44 -44
  56. package/src/cli/plugin/remove.mjs +23 -30
  57. package/src/cli/plugin/scaffold-cmd.mjs +30 -0
  58. package/src/cli/plugin/search.mjs +108 -23
  59. package/src/cli/rebuild.mjs +37 -1
  60. package/src/cli/recall.mjs +17 -2
  61. package/src/cli/research.mjs +4 -6
  62. package/src/cli/share.mjs +2 -0
  63. package/src/core/app-deploy/apply.mjs +386 -0
  64. package/src/core/app-deploy/design-tokens.mjs +217 -0
  65. package/src/core/app-deploy/plan.mjs +249 -0
  66. package/src/core/app-deploy/resolve.mjs +295 -0
  67. package/src/core/app-deploy/source.mjs +337 -0
  68. package/src/core/app-deploy/standalone.mjs +248 -0
  69. package/src/core/app-deploy/upgrade.mjs +256 -0
  70. package/src/core/app-deploy/verify.mjs +213 -0
  71. package/src/core/bound-hosts.mjs +1 -1
  72. package/src/core/brain-registry.mjs +146 -0
  73. package/src/core/brain-state.json +1 -0
  74. package/src/core/daemon-loop.mjs +30 -26
  75. package/src/core/dream.mjs +5 -17
  76. package/src/core/fact-seeker.mjs +53 -368
  77. package/src/core/lan-discovery.mjs +3 -3
  78. package/src/core/mesh-access-sync.mjs +134 -0
  79. package/src/core/mesh-access.mjs +18 -3
  80. package/src/core/mesh-discover.mjs +173 -0
  81. package/src/core/mesh.mjs +131 -32
  82. package/src/core/optimizer.mjs +4 -41
  83. package/src/core/plugin-bundle.mjs +167 -0
  84. package/src/core/plugin-context.mjs +8 -13
  85. package/src/core/plugin-loader.mjs +272 -68
  86. package/src/core/plugin-peers.mjs +163 -0
  87. package/src/core/plugin-public.mjs +85 -0
  88. package/src/core/plugin-runner-child.mjs +24 -0
  89. package/src/core/plugin-runner.mjs +102 -0
  90. package/src/core/plugin-store.mjs +384 -0
  91. package/src/core/plugin-tasks.mjs +212 -0
  92. package/src/core/post-mortem.mjs +40 -9
  93. package/src/core/research-gate.mjs +182 -0
  94. package/src/core/research-queue.mjs +47 -7
  95. package/src/core/research-surface.mjs +211 -0
  96. package/src/core/research.mjs +25 -32
  97. package/src/core/scheduler.mjs +67 -153
  98. package/src/core/schema.mjs +129 -0
  99. package/src/core/search.mjs +12 -1
  100. package/src/core/secret-integration-research.mjs +12 -8
  101. package/src/core/secrets-store.mjs +76 -2
  102. package/src/core/session-watcher.mjs +6 -0
  103. package/src/core/surface.mjs +64 -3
  104. package/src/core/task-executors.mjs +8 -45
  105. package/src/core/vault-cache.mjs +70 -4
  106. package/src/server/api.mjs +21 -0
  107. package/src/server/rest.mjs +2 -0
  108. package/src/server/route-manifest.json +21 -5
  109. package/src/server/routes/collab.mjs +38 -11
  110. package/src/server/routes/extension.mjs +3 -2
  111. package/src/server/routes/memory.mjs +24 -2
  112. package/src/server/routes/mesh.mjs +7 -7
  113. package/src/server/routes/plugins-mesh.mjs +49 -0
  114. package/src/server/routes/plugins.mjs +91 -426
  115. package/src/server/routes/research.mjs +5 -2
  116. package/src/server/routes/share.mjs +2 -0
  117. package/src/server/tools.mjs +29 -2
  118. package/extension/icons/README.md +0 -27
  119. package/extension/lib/preconfigured.js +0 -5
  120. package/extension/popup/popup.css +0 -227
  121. package/extension/popup/popup.html +0 -50
  122. package/extension/popup/popup.js +0 -175
  123. package/frontend/dist/assets/index-U7o-qU99.js +0 -5742
  124. package/scaffold/.agent/plugins/.gitkeep +0 -0
  125. package/scaffold/.agent/plugins/code-quality/README.md +0 -16
  126. package/scaffold/.agent/plugins/code-quality/cli.mjs +0 -15
  127. package/scaffold/.agent/plugins/code-quality/plugin.json +0 -34
  128. package/scaffold/.agent/plugins/git-sentinel/README.md +0 -16
  129. package/scaffold/.agent/plugins/git-sentinel/plugin.json +0 -34
  130. package/scaffold/.agent/plugins/meta-harness/README.md +0 -16
  131. package/scaffold/.agent/plugins/meta-harness/cli.mjs +0 -34
  132. package/scaffold/.agent/plugins/meta-harness/plugin.json +0 -34
  133. package/scaffold/.agent/plugins/system-monitor/README.md +0 -16
  134. package/scaffold/.agent/plugins/system-monitor/plugin.json +0 -38
@@ -0,0 +1,493 @@
1
+ /**
2
+ * Total Recall — App Capability Deployment CLI
3
+ *
4
+ * Implements `npx total-recall app <subcommand>`
5
+ * Supported subcommands:
6
+ * app plan <source> [--target <dir>] [--adapter <id>] [--json] [--pin <sha256>]
7
+ * app add <source> [--target <dir>] [--adapter <id>] [--dry-run] [--force] [--json]
8
+ * app create <dir> --plugin <source> [--adapter <id>] [--name <name>] [--force] [--json]
9
+ * app upgrade <id> --to <source> [--target <dir>] [--adapter <id>] [--dry-run] [--json]
10
+ * app verify [--target <dir>] [--json]
11
+ */
12
+
13
+ import path from 'node:path';
14
+ import { createDeploymentPlan } from '../../core/app-deploy/plan.mjs';
15
+ import {
16
+ applyDeploymentPlan,
17
+ AppApplyPreflightError,
18
+ AppApplyError
19
+ } from '../../core/app-deploy/apply.mjs';
20
+ import {
21
+ upgradeCapability,
22
+ AppUpgradeError
23
+ } from '../../core/app-deploy/upgrade.mjs';
24
+ import {
25
+ createStandaloneApp,
26
+ AppCreateError
27
+ } from '../../core/app-deploy/standalone.mjs';
28
+ import { verifyApplication } from '../../core/app-deploy/verify.mjs';
29
+ import {
30
+ IncompatibleAdapterError,
31
+ IncompatibleSsssVersionError,
32
+ OwnershipCollisionError
33
+ } from '../../core/app-deploy/resolve.mjs';
34
+
35
+ function printHelp() {
36
+ console.log(`
37
+ 🚀 Total Recall — App Capability Deployment
38
+
39
+ Plan, compose, deploy, and verify modular capabilities into standalone and host applications.
40
+
41
+ Usage:
42
+ npx total-recall app <command> [options]
43
+
44
+ Commands:
45
+ plan <source> Compute a read-only deterministic deployment plan
46
+ --target <dir> Target application directory (default: cwd)
47
+ --adapter <id> Target adapter (ssss-app, nextjs, react, flask)
48
+ --pin <sha256> Enforce exact source hash pin
49
+ --json Output plan as JSON
50
+
51
+ add <source> Apply capability plan to target application
52
+ --target <dir> Target application directory (default: cwd)
53
+ --adapter <id> Target adapter
54
+ --dry-run Preflight and stage only, no app mutations
55
+ --force Bypass identical checks
56
+ --json Output result as JSON
57
+
58
+ create <dir> Scaffold a new standalone application with capability
59
+ --plugin <source> Initial capability plugin source (required)
60
+ --adapter <id> Adapter (default: ssss-app)
61
+ --name <name> App name (default: dir basename)
62
+ --force Overwrite existing non-empty directory
63
+ --json Output result as JSON
64
+
65
+ upgrade <id> Upgrade an installed capability to a new version/pin
66
+ --to <source> New capability source or pinned version (required)
67
+ --target <dir> Target application directory (default: cwd)
68
+ --adapter <id> Adapter
69
+ --dry-run Preflight only
70
+ --json Output result as JSON
71
+
72
+ verify Verify deployed capabilities, file digests, and SSSS conformance
73
+ --target <dir> Target application directory (default: cwd)
74
+ --json Output verification report as JSON
75
+
76
+ Exit Codes:
77
+ 0 Success
78
+ 1 User/argument error or internal failure
79
+ 2 Plan conflict detected or verification drift
80
+ 3 Incompatible adapter or SSSS version
81
+ `);
82
+ }
83
+
84
+ export async function run(argv = []) {
85
+ let args = Array.isArray(argv) ? argv : [];
86
+ if (args[0]?.endsWith('node')) {
87
+ args = args.slice(2);
88
+ }
89
+ if (args[0] === 'app') {
90
+ args = args.slice(1);
91
+ }
92
+
93
+ const sub = args[0];
94
+ const rest = args.slice(1);
95
+
96
+ if (!sub || sub === '--help' || sub === '-h') {
97
+ printHelp();
98
+ return;
99
+ }
100
+
101
+ // 1. APP PLAN
102
+ if (sub === 'plan') {
103
+ let source = null;
104
+ let target = process.cwd();
105
+ let adapter = 'ssss-app';
106
+ let isJson = false;
107
+ let pin = null;
108
+
109
+ for (let i = 0; i < rest.length; i++) {
110
+ const arg = rest[i];
111
+ if (arg === '--json') {
112
+ isJson = true;
113
+ } else if (arg === '--target' && rest[i + 1]) {
114
+ target = rest[i + 1];
115
+ i++;
116
+ } else if (arg === '--adapter' && rest[i + 1]) {
117
+ adapter = rest[i + 1];
118
+ i++;
119
+ } else if (arg === '--pin' && rest[i + 1]) {
120
+ pin = rest[i + 1];
121
+ i++;
122
+ } else if (!arg.startsWith('--') && !source) {
123
+ source = arg;
124
+ }
125
+ }
126
+
127
+ if (!source) {
128
+ if (isJson) {
129
+ console.error(JSON.stringify({ error: 'Missing capability source argument' }));
130
+ } else {
131
+ console.error('❌ Error: Missing capability source.');
132
+ console.error(' Usage: total-recall app plan <source> [--target <dir>] [--adapter <id>] [--json]\n');
133
+ }
134
+ process.exit(1);
135
+ }
136
+
137
+ let plan;
138
+ try {
139
+ plan = await createDeploymentPlan(source, {
140
+ target,
141
+ adapter,
142
+ expectedHash: pin
143
+ });
144
+ } catch (err) {
145
+ if (err?.message?.startsWith('process.exit(')) {
146
+ throw err;
147
+ }
148
+ if (isJson) {
149
+ console.error(JSON.stringify({ error: err.message, type: err.name }));
150
+ } else {
151
+ console.error(`\n❌ Plan Failed: ${err.message}\n`);
152
+ }
153
+
154
+ if (err instanceof IncompatibleAdapterError || err instanceof IncompatibleSsssVersionError) {
155
+ process.exit(3);
156
+ } else if (err instanceof OwnershipCollisionError) {
157
+ process.exit(2);
158
+ }
159
+ process.exit(1);
160
+ }
161
+
162
+ if (isJson) {
163
+ console.log(JSON.stringify(plan, null, 2));
164
+ } else {
165
+ console.log(`\n📋 Capability Deployment Plan`);
166
+ console.log(` Plan Hash: \x1b[36m${plan.plan_hash}\x1b[0m`);
167
+ console.log(` Target App: ${plan.target_app.dir} (${plan.target_app.framework})`);
168
+ console.log(` Adapter: ${plan.adapter}`);
169
+ console.log(` Valid: ${plan.valid ? '\x1b[32m✔ YES\x1b[0m' : '\x1b[31m✖ NO (Conflicts detected)\x1b[0m'}\n`);
170
+
171
+ console.log(`📦 Capabilities (${plan.capabilities.length}):`);
172
+ for (const cap of plan.capabilities) {
173
+ console.log(` - \x1b[1m${cap.id}\x1b[0m v${cap.version} (hash: ${cap.source_sha256.slice(0, 12)}...)`);
174
+ }
175
+
176
+ console.log(`\n📄 File Operations (${plan.file_operations.length}):`);
177
+ for (const op of plan.file_operations) {
178
+ const color = op.action === 'create' ? '\x1b[32m+' : op.action === 'conflict' ? '\x1b[31m!' : op.action === 'modify' ? '\x1b[33m~' : '\x1b[90m=';
179
+ console.log(` ${color} [${op.action.toUpperCase()}]\x1b[0m ${op.path} (${op.size} bytes)`);
180
+ }
181
+
182
+ if (plan.access_grants.length > 0) {
183
+ console.log(`\n🔑 Required Access Grants:`);
184
+ for (const g of plan.access_grants) {
185
+ console.log(` - ${g}`);
186
+ }
187
+ }
188
+
189
+ if (plan.conflicts.length > 0) {
190
+ console.log(`\n⚠️ Conflicts Detected:`);
191
+ for (const c of plan.conflicts) {
192
+ console.log(` - \x1b[31m${c.path}\x1b[0m: file in target app conflicts with capability payload`);
193
+ }
194
+ }
195
+ console.log('');
196
+ }
197
+
198
+ plan.cleanup?.();
199
+
200
+ if (!plan.valid) {
201
+ process.exit(2);
202
+ }
203
+ process.exit(0);
204
+ }
205
+
206
+ // 2. APP ADD
207
+ else if (sub === 'add') {
208
+ let source = null;
209
+ let target = process.cwd();
210
+ let adapter = 'ssss-app';
211
+ let isJson = false;
212
+ let dryRun = false;
213
+ let force = false;
214
+
215
+ for (let i = 0; i < rest.length; i++) {
216
+ const arg = rest[i];
217
+ if (arg === '--json') {
218
+ isJson = true;
219
+ } else if (arg === '--dry-run') {
220
+ dryRun = true;
221
+ } else if (arg === '--force') {
222
+ force = true;
223
+ } else if (arg === '--target' && rest[i + 1]) {
224
+ target = rest[i + 1];
225
+ i++;
226
+ } else if (arg === '--adapter' && rest[i + 1]) {
227
+ adapter = rest[i + 1];
228
+ i++;
229
+ } else if (!arg.startsWith('--') && !source) {
230
+ source = arg;
231
+ }
232
+ }
233
+
234
+ if (!source) {
235
+ if (isJson) {
236
+ console.error(JSON.stringify({ error: 'Missing capability source argument' }));
237
+ } else {
238
+ console.error('❌ Error: Missing capability source.');
239
+ console.error(' Usage: total-recall app add <source> [--target <dir>] [--adapter <id>] [--dry-run] [--json]\n');
240
+ }
241
+ process.exit(1);
242
+ }
243
+
244
+ let result;
245
+ try {
246
+ result = await applyDeploymentPlan(source, {
247
+ target,
248
+ adapter,
249
+ dryRun,
250
+ force
251
+ });
252
+ } catch (err) {
253
+ if (err?.message?.startsWith('process.exit(')) throw err;
254
+ if (isJson) {
255
+ console.error(JSON.stringify({ error: err.message, type: err.name }));
256
+ } else {
257
+ console.error(`\n❌ Add Failed: ${err.message}\n`);
258
+ }
259
+ if (err instanceof AppApplyPreflightError) {
260
+ process.exit(2);
261
+ }
262
+ process.exit(1);
263
+ }
264
+
265
+ if (isJson) {
266
+ console.log(JSON.stringify(result, null, 2));
267
+ } else {
268
+ if (dryRun) {
269
+ console.log(`\n✔ Dry-run successful! Preflight passed for plan \x1b[36m${result.plan_hash}\x1b[0m (${result.operations} operations).`);
270
+ } else if (result.already_applied) {
271
+ console.log(`\n✔ Capability already applied with matching plan \x1b[36m${result.plan_hash}\x1b[0m. No changes needed.`);
272
+ } else {
273
+ console.log(`\n✔ Successfully deployed capabilities [${result.capabilities.join(', ')}]!`);
274
+ console.log(` Applied files: ${result.applied_files}`);
275
+ console.log(` Plan hash: \x1b[36m${result.plan_hash}\x1b[0m\n`);
276
+ }
277
+ }
278
+ process.exit(0);
279
+ }
280
+
281
+ // 3. APP CREATE
282
+ else if (sub === 'create') {
283
+ let targetDir = null;
284
+ let plugin = null;
285
+ let adapter = 'ssss-app';
286
+ let name = null;
287
+ let force = false;
288
+ let isJson = false;
289
+
290
+ for (let i = 0; i < rest.length; i++) {
291
+ const arg = rest[i];
292
+ if (arg === '--json') {
293
+ isJson = true;
294
+ } else if (arg === '--force') {
295
+ force = true;
296
+ } else if (arg === '--plugin' && rest[i + 1]) {
297
+ plugin = rest[i + 1];
298
+ i++;
299
+ } else if (arg === '--adapter' && rest[i + 1]) {
300
+ adapter = rest[i + 1];
301
+ i++;
302
+ } else if (arg === '--name' && rest[i + 1]) {
303
+ name = rest[i + 1];
304
+ i++;
305
+ } else if (!arg.startsWith('--') && !targetDir) {
306
+ targetDir = arg;
307
+ }
308
+ }
309
+
310
+ if (!targetDir || !plugin) {
311
+ if (isJson) {
312
+ console.error(JSON.stringify({ error: 'Missing target directory or --plugin source' }));
313
+ } else {
314
+ console.error('❌ Error: Missing target directory or --plugin source.');
315
+ console.error(' Usage: total-recall app create <dir> --plugin <source> [--adapter <id>] [--json]\n');
316
+ }
317
+ process.exit(1);
318
+ }
319
+
320
+ let result;
321
+ try {
322
+ result = await createStandaloneApp(targetDir, {
323
+ plugin,
324
+ adapter,
325
+ name,
326
+ force
327
+ });
328
+ } catch (err) {
329
+ if (err?.message?.startsWith('process.exit(')) throw err;
330
+ if (isJson) {
331
+ console.error(JSON.stringify({ error: err.message, type: err.name }));
332
+ } else {
333
+ console.error(`\n❌ Create Failed: ${err.message}\n`);
334
+ }
335
+ process.exit(1);
336
+ }
337
+
338
+ if (isJson) {
339
+ console.log(JSON.stringify(result, null, 2));
340
+ } else {
341
+ console.log(`\n🎉 Standalone application '\x1b[1m${result.name}\x1b[0m' created successfully!`);
342
+ console.log(` Location: ${result.targetDir}`);
343
+ console.log(` Adapter: ${result.adapter}`);
344
+ console.log(` Capabilities: ${result.capabilities.join(', ')}\n`);
345
+ }
346
+ process.exit(0);
347
+ }
348
+
349
+ // 4. APP UPGRADE
350
+ else if (sub === 'upgrade') {
351
+ let capabilityId = null;
352
+ let toSource = null;
353
+ let target = process.cwd();
354
+ let adapter = 'ssss-app';
355
+ let dryRun = false;
356
+ let isJson = false;
357
+
358
+ for (let i = 0; i < rest.length; i++) {
359
+ const arg = rest[i];
360
+ if (arg === '--json') {
361
+ isJson = true;
362
+ } else if (arg === '--dry-run') {
363
+ dryRun = true;
364
+ } else if (arg === '--to' && rest[i + 1]) {
365
+ toSource = rest[i + 1];
366
+ i++;
367
+ } else if (arg === '--target' && rest[i + 1]) {
368
+ target = rest[i + 1];
369
+ i++;
370
+ } else if (arg === '--adapter' && rest[i + 1]) {
371
+ adapter = rest[i + 1];
372
+ i++;
373
+ } else if (!arg.startsWith('--') && !capabilityId) {
374
+ capabilityId = arg;
375
+ }
376
+ }
377
+
378
+ if (!capabilityId || !toSource) {
379
+ if (isJson) {
380
+ console.error(JSON.stringify({ error: 'Missing capability ID or --to source' }));
381
+ } else {
382
+ console.error('❌ Error: Missing capability ID or --to source.');
383
+ console.error(' Usage: total-recall app upgrade <id> --to <source> [--target <dir>] [--json]\n');
384
+ }
385
+ process.exit(1);
386
+ }
387
+
388
+ let result;
389
+ try {
390
+ result = await upgradeCapability(capabilityId, {
391
+ to: toSource,
392
+ target,
393
+ adapter,
394
+ dryRun
395
+ });
396
+ } catch (err) {
397
+ if (err?.message?.startsWith('process.exit(')) throw err;
398
+ if (isJson) {
399
+ console.error(JSON.stringify({ error: err.message, type: err.name }));
400
+ } else {
401
+ console.error(`\n❌ Upgrade Failed: ${err.message}\n`);
402
+ }
403
+ process.exit(1);
404
+ }
405
+
406
+ if (isJson) {
407
+ console.log(JSON.stringify(result, null, 2));
408
+ } else {
409
+ if (dryRun) {
410
+ console.log(`\n✔ Dry-run upgrade plan computed: ${result.from_version} → \x1b[32m${result.to_version}\x1b[0m`);
411
+ console.log(` Removed obsolete files: ${result.removed_files.join(', ') || 'none'}`);
412
+ } else {
413
+ console.log(`\n🚀 Capability '\x1b[1m${result.capability_id}\x1b[0m' upgraded: v${result.from_version} → \x1b[32mv${result.to_version}\x1b[0m`);
414
+ console.log(` Plan Hash: ${result.plan_hash}`);
415
+ console.log(` Applied files: ${result.applied_files}\n`);
416
+ }
417
+ }
418
+ process.exit(0);
419
+ }
420
+
421
+ // 5. APP VERIFY
422
+ else if (sub === 'verify') {
423
+ let target = process.cwd();
424
+ let isJson = false;
425
+
426
+ for (let i = 0; i < rest.length; i++) {
427
+ const arg = rest[i];
428
+ if (arg === '--json') {
429
+ isJson = true;
430
+ } else if (arg === '--target' && rest[i + 1]) {
431
+ target = rest[i + 1];
432
+ i++;
433
+ }
434
+ }
435
+
436
+ let report;
437
+ try {
438
+ report = await verifyApplication(target);
439
+ } catch (err) {
440
+ if (err?.message?.startsWith('process.exit(')) throw err;
441
+ if (isJson) {
442
+ console.error(JSON.stringify({ error: err.message }));
443
+ } else {
444
+ console.error(`\n❌ Verification Failed: ${err.message}\n`);
445
+ }
446
+ process.exit(1);
447
+ }
448
+
449
+ if (isJson) {
450
+ console.log(JSON.stringify(report, null, 2));
451
+ } else {
452
+ console.log(`\n🔍 Application Verification Report`);
453
+ console.log(` Target: ${report.target}`);
454
+ console.log(` Status: ${report.valid ? '\x1b[32m✔ CONFORMANT (No drift)\x1b[0m' : '\x1b[31m✖ DRIFT DETECTED\x1b[0m'}`);
455
+ console.log(` Events: ${report.events_count} recorded\n`);
456
+
457
+ if (report.capabilities.length > 0) {
458
+ console.log(`📦 Capabilities (${report.capabilities.length}):`);
459
+ for (const cap of report.capabilities) {
460
+ const statusIcon = cap.status === 'installed' && cap.drift.length === 0 ? '✔' : '✖';
461
+ console.log(` ${statusIcon} \x1b[1m${cap.id}\x1b[0m v${cap.version} [${cap.status}] (${cap.files_intact}/${cap.files_checked} files verified)`);
462
+ for (const d of cap.drift) {
463
+ console.log(` \x1b[31m! Drift in ${d.path} (missing: ${Boolean(d.missing)})\x1b[0m`);
464
+ }
465
+ }
466
+ }
467
+
468
+ if (report.errors.length > 0) {
469
+ console.log(`\n⚠️ Errors:`);
470
+ for (const e of report.errors) {
471
+ console.log(` - \x1b[31m${e}\x1b[0m`);
472
+ }
473
+ }
474
+ console.log('');
475
+ }
476
+
477
+ if (!report.valid) {
478
+ process.exit(2);
479
+ }
480
+ process.exit(0);
481
+ }
482
+
483
+ // UNKNOWN SUBCOMMAND
484
+ else {
485
+ console.error(`Unknown app subcommand: ${sub}`);
486
+ printHelp();
487
+ process.exit(1);
488
+ }
489
+ }
490
+
491
+ export default async function (args = []) {
492
+ return run(args);
493
+ }
package/src/cli/brain.mjs CHANGED
@@ -9,7 +9,9 @@ function printHelp() { console.log(`
9
9
  Usage: total-recall brain <command> [options]
10
10
 
11
11
  Commands:
12
- list List global and all registered project brains (default)
12
+ list List global and all registered project brains, on/off (default)
13
+ on <brain> Switch a brain on (name, repo path, brain path, or "global")
14
+ off <brain> Switch a brain off — recall stops reading it
13
15
  status Show active context resolution paths and loaded layers
14
16
  register <project-path> Track any project path (full brain + registry)
15
17
  unregister <project-path> Remove a project directory path from the registry
@@ -22,6 +24,8 @@ function printHelp() { console.log(`
22
24
  npx total-recall brain register /path/to/any-app
23
25
  npx total-recall brain ensure ~/code/my-lib
24
26
  npx total-recall brain list
27
+ npx total-recall brain off hermes-agent
28
+ npx total-recall brain on global
25
29
  npx total-recall brain unregister /path/to/project
26
30
 
27
31
  Skill multi-repo sync: registered projects + TR_SYNC_REPOS + skill track/--repo
@@ -54,12 +58,35 @@ export default async function brain(args) {
54
58
  case 'ensure':
55
59
  await handleEnsure(args.slice(1), globalBrainDir);
56
60
  break;
61
+ case 'on':
62
+ case 'off':
63
+ await handleToggle(subcommand.toLowerCase() === 'on', args.slice(1), globalBrainDir);
64
+ break;
57
65
  default:
58
66
  console.error(` ⚠️ Unknown brain command: "${subcommand}". Run total-recall brain --help for details.`);
59
67
  process.exit(1);
60
68
  }
61
69
  }
62
70
 
71
+ async function handleToggle(enabled, targetArgs, globalBrainDir) {
72
+ const query = targetArgs.find((a) => !a.startsWith('-'));
73
+ const verb = enabled ? 'on' : 'off';
74
+ if (!query) {
75
+ console.error(` ⚠️ Usage: npx total-recall brain ${verb} <name | repo path | brain path | global>`);
76
+ process.exit(1);
77
+ }
78
+ const { listLocalBrains, findLocalBrain, setBrainEnabled } = await import('../core/brain-registry.mjs');
79
+ const brains = listLocalBrains({ globalBrainDir, activeProject: getBothBrains().project });
80
+ const target = findLocalBrain(brains, query);
81
+ if (!target) {
82
+ console.error(` ⚠️ No single brain matches "${query}". Known: ${brains.map((b) => b.name).join(', ')}`);
83
+ process.exit(1);
84
+ }
85
+ setBrainEnabled(target.brainDir, enabled, { globalBrainDir });
86
+ console.log(` ✅ ${target.name} is now ${verb.toUpperCase()} (${target.brainDir})`);
87
+ if (!enabled) console.log(' recall skips it until: npx total-recall brain on ' + target.name);
88
+ }
89
+
63
90
  async function handleEnsure(targetArgs, globalBrainDir) {
64
91
  const targetPath = targetArgs[0];
65
92
  if (!targetPath) {
@@ -90,6 +117,9 @@ async function handleEnsure(targetArgs, globalBrainDir) {
90
117
  async function handleList(registryPath, globalBrainDir) {
91
118
  const brains = getBothBrains();
92
119
  const activeProjectDir = brains.project ? brains.project.brainDir : null;
120
+ const { isBrainEnabled, readBrainToggles } = await import('../core/brain-registry.mjs');
121
+ const toggles = readBrainToggles(globalBrainDir);
122
+ const state = (dir) => (isBrainEnabled(dir, { toggles }) ? ' [ON]' : ' ⏸ [OFF]');
93
123
 
94
124
  console.log('\n 🧠 Active Total Recall Brain Registry:\n');
95
125
 
@@ -101,7 +131,7 @@ async function handleList(registryPath, globalBrainDir) {
101
131
  }
102
132
  const isGlobalActive = !activeProjectDir;
103
133
  const activeGlobalMarker = isGlobalActive ? ' ⭐️ [ACTIVE]' : '';
104
- console.log(` 🌐 Global Brain${activeGlobalMarker}`);
134
+ console.log(` 🌐 Global Brain${state(globalBrainDir)}${activeGlobalMarker}`);
105
135
  console.log(` ├── Path: ${globalBrainDir}`);
106
136
  console.log(` └── Nodes: ${globalCount} compiled SSSS memory nodes`);
107
137
  console.log('');
@@ -124,7 +154,7 @@ async function handleList(registryPath, globalBrainDir) {
124
154
  const projectVault = path.join(project.brainDir, 'memory-vault');
125
155
  const nodeCount = exists ? countMdFiles(projectVault) : 0;
126
156
 
127
- console.log(` ├── 📄 ${project.name}${marker}${existsLabel}`);
157
+ console.log(` ├── 📄 ${project.name}${state(project.brainDir)}${marker}${existsLabel}`);
128
158
  console.log(` │ ├── Root: ${project.path}`);
129
159
  console.log(` │ ├── Brain: ${project.brainDir}`);
130
160
  console.log(` │ └── Nodes: ${nodeCount} memory nodes`);