total-recall-brain 3.40.1 → 3.40.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (186) hide show
  1. package/README.md +152 -41
  2. package/bin/total-recall.mjs +16 -76
  3. package/docs/reference/cli-reference.md +10 -10
  4. package/extension/background.js +6 -2
  5. package/extension/config.js +8 -0
  6. package/extension/lib/brain-client.js +29 -4
  7. package/extension/options/options.html +1 -0
  8. package/extension/sidepanel/sidepanel.html +1 -0
  9. package/frontend/dist/assets/{index-CvQRdT2j.css → index-AGUes-VG.css} +1 -1
  10. package/frontend/dist/assets/index-Bt5iVPZj.js +5618 -0
  11. package/frontend/dist/index.html +2 -2
  12. package/frontend/src/api/keys.ts +9 -2
  13. package/frontend/src/api/plugins.ts +4 -0
  14. package/frontend/src/components/plugins/card/CardEngine.tsx +2 -1
  15. package/frontend/src/components/plugins/card/card.css +1 -1
  16. package/frontend/src/pages/IntegrationsPage.tsx +315 -8
  17. package/frontend/src/pages/PluginsPage.tsx +532 -87
  18. package/package.json +6 -5
  19. package/scaffold/.agent/skills/meta-harness/references/optimized/original.md +1 -1
  20. package/scaffold/.agent/skills/meta-harness/references/optimized/section-09.md +1 -1
  21. package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +10 -10
  22. package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +1 -6
  23. package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +0 -1
  24. package/scaffold/.agent/skills/total-recall/plugins/auth/src/browser-session.mjs +309 -4
  25. package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +44 -34
  26. package/scaffold/.agent/skills/total-recall/plugins/auth/src/google-oauth.mjs +6 -4
  27. package/scaffold/.agent/skills/total-recall/plugins/auth/src/provider-catalog.mjs +1 -1
  28. package/scaffold/.agent/skills/total-recall/plugins/auth/src/provider-rotation-recipes.mjs +118 -4
  29. package/scaffold/.agent/skills/total-recall/plugins/auth/src/routes/auth.mjs +2 -2
  30. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-env-export.mjs +3 -3
  31. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-remote-deploy.mjs +1 -1
  32. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-rotate.mjs +24 -6
  33. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/card.js +164 -123
  34. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/README.md +65 -0
  35. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/cli.mjs +76 -0
  36. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/package.json +19 -0
  37. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/plugin.json +290 -0
  38. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/adapters/dsh.mjs +446 -0
  39. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/documentation.mjs +152 -0
  40. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/executor.mjs +71 -0
  41. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/runtime.mjs +36 -0
  42. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/setup.mjs +35 -0
  43. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/src/worker.mjs +175 -0
  44. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/card.js +18 -0
  45. package/scaffold/.agent/skills/total-recall/plugins/code-quality/LICENSE +21 -0
  46. package/scaffold/.agent/skills/total-recall/plugins/code-quality/README.md +84 -0
  47. package/scaffold/.agent/skills/total-recall/plugins/code-quality/cli.mjs +79 -0
  48. package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/check.mjs +6 -0
  49. package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/report.mjs +6 -0
  50. package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/run-core.mjs +27 -0
  51. package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/snapshot.mjs +163 -0
  52. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/check.mjs +757 -0
  53. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/config.schema.json +86 -0
  54. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/detect.mjs +89 -0
  55. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report-data.mjs +145 -0
  56. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report.mjs +210 -0
  57. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/state.mjs +20 -0
  58. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +2869 -0
  59. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +28 -0
  60. package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +186 -0
  61. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/DESIGN.md +18 -0
  62. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/card.js +272 -0
  63. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/code-quality-report.mjs +129 -0
  64. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/LICENSE +21 -0
  65. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/README.md +73 -0
  66. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/cli.mjs +375 -0
  67. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/examples/commands/echo-tool.mjs +23 -0
  68. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/examples/commands/group.mjs +262 -0
  69. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/examples/commands/project-id.mjs +74 -0
  70. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +2869 -0
  71. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +31 -0
  72. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +221 -0
  73. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/schemas/cli_command.schema.json +107 -0
  74. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/skills/composable-cli/SKILL.md +63 -0
  75. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/command.mjs +313 -0
  76. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/compile.mjs +19 -0
  77. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/compose.mjs +144 -0
  78. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/contract.mjs +110 -0
  79. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/library.mjs +23 -0
  80. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/registry.mjs +192 -0
  81. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/resolve.mjs +221 -0
  82. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/risk.mjs +126 -0
  83. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/src/ssss-documents.mjs +29 -0
  84. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/DESIGN.md +18 -0
  85. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/card.js +273 -0
  86. package/scaffold/.agent/skills/total-recall/plugins/embeddings/LICENSE +21 -0
  87. package/scaffold/.agent/skills/total-recall/plugins/embeddings/README.md +20 -0
  88. package/scaffold/.agent/skills/total-recall/plugins/embeddings/cli.mjs +310 -0
  89. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +2826 -0
  90. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +11 -0
  91. package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +62 -0
  92. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/card.js +297 -0
  93. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/LICENSE +21 -0
  94. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/README.md +43 -0
  95. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/cli.mjs +258 -0
  96. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +4005 -0
  97. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package.json +20 -0
  98. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/plugin.json +125 -0
  99. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/DESIGN.md +18 -0
  100. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/card.js +278 -0
  101. package/scaffold/.agent/skills/total-recall/plugins/mesh/README.md +9 -0
  102. package/scaffold/.agent/skills/total-recall/plugins/mesh/cli.mjs +6 -0
  103. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +4721 -0
  104. package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +3 -21
  105. package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +14 -1
  106. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/mesh.mjs +10 -0
  107. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/headscale-client.mjs +5 -1
  108. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/leader-election.mjs +4 -13
  109. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh-access-verify.mjs +22 -0
  110. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh-files.mjs +18 -0
  111. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh.mjs +2 -0
  112. package/scaffold/.agent/skills/total-recall/plugins/mesh/test-support/memory-root.mjs +14 -0
  113. package/scaffold/.agent/skills/total-recall/plugins/openwiki/LICENSE +21 -0
  114. package/scaffold/.agent/skills/total-recall/plugins/openwiki/README.md +35 -0
  115. package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +124 -0
  116. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +4097 -0
  117. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +12 -0
  118. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +165 -0
  119. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/ingest.mjs +127 -0
  120. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/DESIGN.md +18 -0
  121. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +279 -0
  122. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/panel.js +539 -0
  123. package/scaffold/.agent/skills/total-recall/plugins/security/LICENSE +21 -0
  124. package/scaffold/.agent/skills/total-recall/plugins/security/README.md +67 -0
  125. package/scaffold/.agent/skills/total-recall/plugins/security/cli.mjs +932 -0
  126. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +3256 -0
  127. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +25 -0
  128. package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +297 -0
  129. package/scaffold/.agent/skills/total-recall/plugins/security/scripts/build-ui.mjs +4 -0
  130. package/scaffold/.agent/skills/total-recall/plugins/security/skills/security/SKILL.md +37 -0
  131. package/scaffold/.agent/skills/total-recall/plugins/security/src/android.mjs +21 -0
  132. package/scaffold/.agent/skills/total-recall/plugins/security/src/devices.mjs +94 -0
  133. package/scaffold/.agent/skills/total-recall/plugins/security/src/machines.mjs +164 -0
  134. package/scaffold/.agent/skills/total-recall/plugins/security/src/scanner.mjs +41 -0
  135. package/scaffold/.agent/skills/total-recall/plugins/security/src/windows.mjs +42 -0
  136. package/scaffold/.agent/skills/total-recall/plugins/security/ui/DESIGN.md +18 -0
  137. package/scaffold/.agent/skills/total-recall/plugins/security/ui/card.js +95 -0
  138. package/scaffold/.agent/skills/total-recall/plugins/security/ui/panel.js +98 -0
  139. package/scaffold/.agent/skills/total-recall/references/auth.md +1 -1
  140. package/src/cli/brief.mjs +33 -9
  141. package/src/cli/command.mjs +2 -7
  142. package/src/cli/config.mjs +1 -0
  143. package/src/cli/connect.mjs +5 -0
  144. package/src/cli/context.mjs +2 -1
  145. package/src/cli/daemon.mjs +36 -1
  146. package/src/cli/doctor.mjs +6 -0
  147. package/src/cli/harness.mjs +3 -0
  148. package/src/cli/help.mjs +26 -0
  149. package/src/cli/init.mjs +4 -4
  150. package/src/cli/memory-entry.mjs +1 -0
  151. package/src/cli/memory-init.mjs +2 -0
  152. package/src/cli/plugin/index.mjs +11 -0
  153. package/src/cli/plugin/install.mjs +2 -1
  154. package/src/cli/rebuild.mjs +3 -1
  155. package/src/cli/startup.mjs +2 -1
  156. package/src/cli/update.mjs +19 -0
  157. package/src/core/automatic-brain-updates.mjs +67 -0
  158. package/src/core/brain-updates.mjs +21 -2
  159. package/src/core/cli-commands.mjs +71 -0
  160. package/src/core/cli-maintenance.mjs +45 -0
  161. package/src/core/cli-quick-reference.mjs +20 -0
  162. package/src/core/cli-semantic-index.mjs +722 -0
  163. package/src/core/context-compiler.mjs +27 -1
  164. package/src/core/crons.mjs +2 -13
  165. package/src/core/daemon-control.mjs +6 -4
  166. package/src/core/daemon-loop.mjs +12 -8
  167. package/src/core/default-plugins.mjs +5 -0
  168. package/src/core/global-brain-updates.mjs +39 -0
  169. package/src/core/mesh-brain-updates.mjs +30 -0
  170. package/src/core/meta-harness.mjs +108 -0
  171. package/src/core/plugin-dependencies.mjs +2 -1
  172. package/src/core/plugin-loader.mjs +2 -1
  173. package/src/core/plugin-runner.mjs +12 -4
  174. package/src/core/plugin-setup.mjs +20 -0
  175. package/src/core/plugin-store.mjs +22 -4
  176. package/src/core/plugin-tasks.mjs +1 -1
  177. package/src/core/project-brain.mjs +2 -0
  178. package/src/core/project-status.mjs +64 -0
  179. package/src/core/required-plugins.mjs +46 -0
  180. package/src/core/scaffold-plugins.mjs +17 -3
  181. package/src/core/source-brain-updates.mjs +47 -0
  182. package/src/core/startup-health.mjs +5 -3
  183. package/src/core/surface.mjs +44 -10
  184. package/src/server/routes/extension.mjs +53 -8
  185. package/templates/onboarding-interview.md +1 -1
  186. package/frontend/dist/assets/index-SOVUlZqm.js +0 -5596
@@ -115,6 +115,11 @@ export const CLIENTS = {
115
115
  // Grok discovers repository skills in <project>/.agents/skills.
116
116
  skillsProjection: { scope: 'project', dir: path.join('.agents', 'skills') }
117
117
  },
118
+ muse: {
119
+ label: 'Muse Code',
120
+ mode: 'symlink',
121
+ target: 'AGENTS.md'
122
+ },
118
123
  replit: {
119
124
  label: 'Replit Agent',
120
125
  mode: 'file',
@@ -41,8 +41,9 @@ export default async function context(args = []) {
41
41
  const nodes = own ? mergeGlobalRuleNodes(getNodes(path.join(own, 'memory-vault')), global ? getNodes(path.join(global, 'memory-vault')) : []) :
42
42
  getNodes(path.join(global, 'memory-vault')).map(n => ({ ...n, _layer: 'global' }));
43
43
  const skillsDir = path.dirname(own || global);
44
+ const derivedDir = own ? path.join(own, 'memory-derived') : (global ? path.join(global, 'memory-derived') : null);
44
45
  const result = await compileContext({ query, actions, nodes, includeKnowledge, budget: { total: budget }, projectRoot: process.cwd(),
45
- contributions: legacyRuleContributions(skillsDir), versionInputs: surfaceInputsHash({ skillsDir }) });
46
+ derivedDir, contributions: legacyRuleContributions(skillsDir), versionInputs: surfaceInputsHash({ skillsDir }) });
46
47
  if (debug && format === 'text') throw new Error('--debug requires --format json');
47
48
  const response = capsuleResponse(result, { total: budget, debug, format });
48
49
  if (!response.ready && !debug) {
@@ -56,8 +56,10 @@ function printHelp() {
56
56
 
57
57
  Commands:
58
58
  start Start the background Active Intelligence Daemon
59
+ ensure Enable the managed daemon; --restart reloads current source
59
60
  stop Stop the background daemon
60
61
  status Show daemon status + recent log entries
62
+ logs Read daemon logs (--lines N, default 50)
61
63
 
62
64
  The daemon runs a durable task loop that:
63
65
  • Ingests IDE conversation logs (Claude Code, Codex, Gemini, etc.)
@@ -207,6 +209,39 @@ export default async function daemon(args) {
207
209
  const useLaunchd = !useSystemd && hasLaunchd();
208
210
 
209
211
  switch (subcommand) {
212
+ case 'ensure': {
213
+ if(!useSystemd)throw new Error('Managed daemon ensure currently requires systemd on the daemon host');
214
+ // A detached worker can hold the same brain lock while systemd reports a
215
+ // successful short-lived start. Stop that verified owner before takeover.
216
+ const managed=spawnSync('systemctl',['show','total-recall-daemon','--property=MainPID','--value'],{encoding:'utf8'});
217
+ if(managed.status!==0)throw new Error('Cannot inspect managed daemon');
218
+ const owner=readPid(),managedPid=Number(managed.stdout.trim());
219
+ if(owner && (owner!==managedPid || args.includes('--restart'))){
220
+ if(owner===managedPid)execFileSync('systemctl',['stop','total-recall-daemon'],{stdio:'inherit'});
221
+ else if(!await stopDaemon())throw new Error('Cannot stop verified detached daemon');
222
+ }
223
+ execFileSync('systemctl',['enable','--now','total-recall-daemon'],{stdio:'inherit'});
224
+ await new Promise(resolve=>setTimeout(resolve,3000));
225
+ const active=spawnSync('systemctl',['is-active','--quiet','total-recall-daemon']);
226
+ const current=spawnSync('systemctl',['show','total-recall-daemon','--property=MainPID','--value'],{encoding:'utf8'});
227
+ const pid=readPid();
228
+ if(active.status!==0 || !pid || pid!==Number(current.stdout?.trim()))throw new Error('Managed daemon did not retain ownership of the brain lock');
229
+ console.log(JSON.stringify({ok:true,manager:'systemd',pid,enabled:true}));
230
+ break;
231
+ }
232
+ case 'logs': {
233
+ const index = args.indexOf('--lines');
234
+ const lines = index < 0 ? 50 : Number(args[index + 1]);
235
+ if (!Number.isInteger(lines) || lines < 1 || lines > 10000) throw new Error('--lines must be an integer from 1 to 10000');
236
+ if (useSystemd) {
237
+ const result = spawnSync('journalctl', ['-u', 'total-recall-daemon.service', '-n', String(lines), '--no-pager'], { stdio: 'inherit' });
238
+ if (result.error || result.status !== 0) throw new Error('Could not read the daemon journal');
239
+ } else {
240
+ if (!fs.existsSync(LOG_FILE)) throw new Error('No daemon log exists');
241
+ console.log(fs.readFileSync(LOG_FILE, 'utf8').trimEnd().split('\n').slice(-lines).join('\n'));
242
+ }
243
+ break;
244
+ }
210
245
  case 'start':
211
246
  if (useSystemd) systemdStart();
212
247
  else if (useLaunchd) launchctlStart();
@@ -224,7 +259,7 @@ export default async function daemon(args) {
224
259
  break;
225
260
  default:
226
261
  console.error(` Unknown daemon command: ${subcommand}`);
227
- console.error(` Valid commands: start, stop, status`);
262
+ console.error(` Valid commands: start, ensure, stop, status, logs`);
228
263
  process.exit(1);
229
264
  }
230
265
  }
@@ -18,6 +18,7 @@ import { fileURLToPath, URL } from 'node:url';
18
18
  import { getGlobalBrainDir } from './agent-dir.mjs';
19
19
  import { processIdentity, verifyListener } from '../core/startup-health.mjs';
20
20
  import { loadKnownRepoRoots } from '../core/skills-registry.mjs';
21
+ import { inspectRequiredPlugins } from '../core/required-plugins.mjs';
21
22
 
22
23
  // ─── Formatting & UI Helpers ────────────────────────────────────────────────
23
24
  function log(msg) { console.log(` ${msg}`); }
@@ -91,6 +92,11 @@ export default async function doctor() {
91
92
 
92
93
  let warnings = 0;
93
94
  let failures = 0;
95
+ const { resolveBrainDir } = await import('./agent-dir.mjs');
96
+ for (const brain of new Set([getGlobalBrainDir(), resolveBrainDir()])) {
97
+ const required = inspectRequiredPlugins(brain);
98
+ if (required.status !== 'ready') { logFail(`${brain}: ${required.errors.join('; ')}`); failures++; }
99
+ }
94
100
 
95
101
  // ── Step 1: System Hardware Check ──
96
102
  hr();
@@ -23,6 +23,9 @@ Available Harness IDs:
23
23
  grok xAI Grok Build CLI (Autonomous coding, inspection, shell execution)
24
24
  gemini Google Gemini CLI (Fast utilities & completion chaining)
25
25
  ollama Ollama Local LLM (Local neural reasoning, zero API cost)
26
+ hermes Hermes Agent Runtime (Autonomous supervisor & messaging gateway)
27
+ dsh DeepSeek Harness DSH (High-throughput reasoning engine)
28
+ openclaw OpenClaw Agent Bridge (Multi-channel messaging & tool execution)
26
29
 
27
30
  Examples:
28
31
  npx total-recall harness list
package/src/cli/help.mjs CHANGED
@@ -122,6 +122,32 @@ export default async function run(argv) {
122
122
  console.log('\n' + '─'.repeat(60) + '\n');
123
123
  }
124
124
  } else {
125
+ // Semantic CLI search fallback across all core, plugin, and composed verbs
126
+ try {
127
+ const { queryCliSemanticIndex } = await import('../core/cli-semantic-index.mjs');
128
+ const matches = await queryCliSemanticIndex(requestedTopic || topic, { limit: 6, minScore: 0.25 });
129
+ if (matches.length > 0) {
130
+ if (isJson) {
131
+ console.log(JSON.stringify({ topic: requestedTopic || topic, matches }, null, 2));
132
+ return;
133
+ }
134
+ console.log(`\n\x1b[34m=== Total Recall Semantic Help: "${requestedTopic || topic}" ===\x1b[0m\n`);
135
+ console.log(' Top matching CLI verbs:\n');
136
+ for (const m of matches) {
137
+ const badge = m.category === 'plugin'
138
+ ? `\x1b[35m[plugin:${m.namespace}]\x1b[0m`
139
+ : (m.category === 'composed' ? `\x1b[36m[composed]\x1b[0m` : `\x1b[32m[core]\x1b[0m`);
140
+ console.log(` • \x1b[1m${m.verb}\x1b[0m ${badge}`);
141
+ console.log(` ${m.description}`);
142
+ console.log(` \x1b[2mUsage: ${m.usage}\x1b[0m\n`);
143
+ }
144
+ console.log('─'.repeat(60) + '\n');
145
+ return;
146
+ }
147
+ } catch {
148
+ /* Fallback to default not found message */
149
+ }
150
+
125
151
  console.error(` ❌ Error: No help section found for topic or command: "${topic}"`);
126
152
  console.error(` Run 'npx total-recall help' to see the complete list of commands.`);
127
153
  process.exit(1);
package/src/cli/init.mjs CHANGED
@@ -244,10 +244,10 @@ export default async function init(args) {
244
244
  logOk(`Base directory structure ready (${created} created, ${dirs.length - created} already existed)`);
245
245
 
246
246
  // Auth is a required scaffold plugin. Existing installations are preserved.
247
- const authSource = path.join(ROOT, 'scaffold', '.agent', 'skills', 'total-recall', 'plugins', 'auth');
248
- const authTarget = path.join(brainDir, 'plugins', 'auth');
249
- if (!fs.existsSync(path.join(authSource, 'plugin.json'))) throw new Error('Required auth scaffold is missing');
250
- if (!fs.existsSync(authTarget) && !opts.dryRun) fs.cpSync(authSource, authTarget, { recursive: true, errorOnExist: true, force: false });
247
+ if (!opts.dryRun) {
248
+ const { ensureRequiredPlugins } = await import('../core/required-plugins.mjs');
249
+ ensureRequiredPlugins(brainDir);
250
+ }
251
251
 
252
252
  // Every scaffold plugin's node_modules are wired in Step 2.5, after the skill
253
253
  // tree has been copied into the brain (see the note there).
@@ -26,6 +26,7 @@ const handlers = {
26
26
 
27
27
  export async function main(args = []) {
28
28
  const [command, ...rest] = args;
29
+ if (command === 'secret' || command === 'secrets') throw new Error('Credential commands belong to total-recall auth secret');
29
30
  if (!command || ['help', '--help', '-h'].includes(command)) {
30
31
  console.log(`Total Recall — portable memory and instructions
31
32
  Usage: total-recall <command> [options]
@@ -4,6 +4,7 @@ import path from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { getGlobalAgentDir } from './agent-dir.mjs';
6
6
  import { registerProjectBrain, writeBrainIdentity, VAULT_CATEGORIES } from '../core/memory-brain.mjs';
7
+ import { ensureRequiredPlugins } from '../core/required-plugins.mjs';
7
8
 
8
9
  export default async function init(args = []) {
9
10
  if (args.includes('--help') || args.includes('-h')) {
@@ -24,6 +25,7 @@ export default async function init(args = []) {
24
25
  for (const relative of ['config', 'plugins', 'memory-derived', ...VAULT_CATEGORIES.map(c => `memory-vault/${c}`)]) {
25
26
  fs.mkdirSync(path.join(brainDir, relative), { recursive: true });
26
27
  }
28
+ ensureRequiredPlugins(brainDir);
27
29
  const skillFile = path.join(brainDir, 'SKILL.md');
28
30
  if (!fs.existsSync(skillFile)) {
29
31
  // Resolve the template by PATH, not `new URL(..., import.meta.url)`. Under a
@@ -25,6 +25,7 @@ Usage:
25
25
  Commands:
26
26
  list Installed plugins (project & global) with source and sharing
27
27
  library [--plugin ID] Installed CLI operation library (--json for agents)
28
+ refresh Rebuild installed command inventories and instruction surfaces
28
29
  on <id> / off <id> Enable/disable host execution; compile catalogs and surfaces
29
30
  available [query] Plugins bundled with this Total Recall package
30
31
  peers [query] Plugins your mesh peers share (queried live)
@@ -38,6 +39,7 @@ Commands:
38
39
  <git-url> a git repository with plugin.json
39
40
  <path> a local directory
40
41
  --link, -l Symlink a local directory instead of copying
42
+ --replace Replace an install, retain previous source and restore on failure
41
43
  --global, -g Install for every project on this machine
42
44
  share <id> Offer an installed plugin by direct link and mesh
43
45
  unshare <id> Stop offering it
@@ -83,6 +85,15 @@ export async function run(argv = []) {
83
85
 
84
86
  const command = args[0];
85
87
  const rest = args.slice(1);
88
+ if (command === 'refresh') {
89
+ const { recompileAfterPluginMutation } = await import('../../core/plugin-store.mjs');
90
+ const result = await recompileAfterPluginMutation(process.cwd(), rest.includes('--global'));
91
+ const { setupInstalledPlugins } = await import('../../core/plugin-setup.mjs');
92
+ const setup = result.ok ? await setupInstalledPlugins() : {ok:false,results:[]};
93
+ console.log(JSON.stringify({...result,setup,ok:result.ok && setup.ok}));
94
+ if (!result.ok || !setup.ok) process.exitCode = 1;
95
+ return;
96
+ }
86
97
  if (command === 'on' || command === 'off') {
87
98
  const result = await setPluginActive(rest[0], command === 'on');
88
99
  console.log(JSON.stringify(result));
@@ -13,11 +13,12 @@ export async function installPlugin(args = []) {
13
13
  }
14
14
 
15
15
  try {
16
- const result = await install(source, { projectRoot: process.cwd(), global: isGlobal, link: isLink });
16
+ const result = await install(source, { projectRoot: process.cwd(), global: isGlobal, link: isLink, replace:args.includes('--replace') });
17
17
  const from = result.source.kind === 'peer' ? `peer ${result.source.peer_hostname}` : result.source.kind;
18
18
  console.log(`\n✅ Installed '${result.plugin.name}' (id: ${result.plugin.id}, v${result.plugin.version}) from ${from}`);
19
19
  console.log(` Location: ${result.plugin.dir}`);
20
20
  console.log(` sha256: ${result.sha256}`);
21
+ if(result.previousDirectory)console.log(` Previous source retained: ${result.previousDirectory}`);
21
22
  if (result.dependencies?.installed) {
22
23
  console.log(` Dependencies: installed ${result.dependencies.packages.length} package(s)`);
23
24
  } else if (result.dependencies?.reason && !['no package.json', 'no dependencies declared'].includes(result.dependencies.reason)) {
@@ -72,13 +72,14 @@ export async function runRebuild(options = {}) {
72
72
  if (fs.existsSync(derivedDir)) {
73
73
  for (const entry of fs.readdirSync(derivedDir)) {
74
74
  if (entry.startsWith('embeddings.db')) continue; // also skips -wal / -shm
75
+ if (entry === 'cli-semantic-index.json') continue;
75
76
  fs.rmSync(path.join(derivedDir, entry), { recursive: true, force: true });
76
77
  }
77
78
  }
78
79
 
79
80
  console.log('🏗️ Recompiling surface from canonical vault...');
80
81
  try {
81
- const stats = await compileSurface({ vaultDir, skillsDir, derivedDir, instructionsFile, force: true });
82
+ const stats = await compileSurface({ vaultDir, skillsDir, derivedDir, instructionsFile, force: Boolean(options.force) });
82
83
  console.log(`✅ Processed ${stats.nodesProcessed} canonical memory nodes.`);
83
84
  console.log(`✅ Injected memory into ${stats.skillsInjected} skill files.`);
84
85
  console.log(`✅ Rebuilt graph-index.jsonl, memory-layers.jsonl, and skill-routes.jsonl.`);
@@ -163,6 +164,7 @@ export default async function cli(args) {
163
164
  const { layer, remainingArgs } = parseLayerFlag(args);
164
165
  const options = {
165
166
  check: remainingArgs.includes('--check'),
167
+ force: remainingArgs.includes('--force'),
166
168
  layer,
167
169
  };
168
170
  let code = await runRebuild(options);
@@ -35,7 +35,8 @@ export default async function startup(args = []) {
35
35
  const report = await inspectStartup(options);
36
36
  if (args.includes('--json')) console.log(JSON.stringify(report, null, 2));
37
37
  else {
38
- console.log(`Startup: server ${report.server?.status || 'unknown'}; brain ${report.brain?.status || 'unknown'}; daemon ${report.daemon?.status || 'unknown'}; SSSS tooling ${report.ssss?.status || 'unknown'}; app ${report.app?.status || 'unknown'}`);
38
+ console.log(`Startup: required plugins ${report.required_plugins?.status || 'unknown'}; server ${report.server?.status || 'unknown'}; brain ${report.brain?.status || 'unknown'}; daemon ${report.daemon?.status || 'unknown'}; SSSS tooling ${report.ssss?.status || 'unknown'}; app ${report.app?.status || 'unknown'}`);
39
+ for (const error of report.required_plugins?.errors || []) console.log(`Required plugin: ${error}`);
39
40
  const harnesses = await queryHarnessUsage().catch(() => []);
40
41
  const availH = harnesses.filter(h => h.available);
41
42
  const readyH = availH.filter(h => h.authed);
@@ -18,6 +18,7 @@ import {
18
18
  import { brainDir } from '../core/config.mjs';
19
19
  import { installAutoPull } from '../core/autopull-install.mjs';
20
20
  import { updateAllBrains } from '../core/brain-updates.mjs';
21
+ import { automaticUpdateStatus, runAutomaticBrainUpdates } from '../core/automatic-brain-updates.mjs';
21
22
 
22
23
  /** @param {string[]} args */
23
24
  export function parseArgs(args) {
@@ -32,6 +33,9 @@ export function parseArgs(args) {
32
33
  noBuild: false,
33
34
  allBrains: false,
34
35
  json: false,
36
+ automatic: false,
37
+ status: false,
38
+ localOnly: false,
35
39
  };
36
40
  for (let i = 0; i < args.length; i++) {
37
41
  const a = args[i];
@@ -45,6 +49,9 @@ export function parseArgs(args) {
45
49
  else if (a === '--no-build') opts.noBuild = true;
46
50
  else if (a === '--all-brains') opts.allBrains = true;
47
51
  else if (a === '--json') opts.json = true;
52
+ else if (a === '--automatic') opts.automatic = true;
53
+ else if (a === '--status') opts.status = true;
54
+ else if (a === '--local-only') opts.localOnly = true;
48
55
  }
49
56
  return opts;
50
57
  }
@@ -64,6 +71,10 @@ function printHelp() {
64
71
  total-recall update --all-brains --apply [--json]
65
72
  Pin the same published versions, use npm/pnpm, replace shared dependency
66
73
  links with independent local installs, and verify every resolved version.
74
+ total-recall update --automatic [--json]
75
+ Daemon maintenance: update both packages, ensure defaults and refresh surfaces.
76
+ total-recall update --status [--json]
77
+ Read the actual last automatic maintenance report without starting work.
67
78
 
68
79
  This brain's own checkout (git):
69
80
  total-recall update --install-autopull [--port N] [--no-build] [--dry-run]
@@ -89,6 +100,14 @@ export default async function update(args = []) {
89
100
  return;
90
101
  }
91
102
 
103
+ if(opts.status){const status=automaticUpdateStatus(brainDir);console.log(JSON.stringify(status,null,2));return status;}
104
+ if(opts.automatic){
105
+ const report=await runAutomaticBrainUpdates({brainDir,force:opts.force,fleet:!opts.localOnly});
106
+ console.log(JSON.stringify(report,null,2));
107
+ if(report.failed)process.exitCode=1;
108
+ return report;
109
+ }
110
+
92
111
  if (opts.allBrains) {
93
112
  const summary = await updateAllBrains({ brainDir, roots: opts.repos,
94
113
  dryRun: !opts.apply || opts.dryRun });
@@ -0,0 +1,67 @@
1
+ /** The existing TR daemon maintains both published brain packages, not only TR. */
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { updateAllBrains } from './brain-updates.mjs';
5
+ import { isPackageAutoUpdateEnabled } from './package-auto-update.mjs';
6
+ import { maintainCliSurfaces } from './cli-maintenance.mjs';
7
+ import { runBounded, cliEntry } from './startup-health.mjs';
8
+ import { logger } from './logger.mjs';
9
+ import { readProcessCommand, shouldHonorPidLock } from './pid-lock.mjs';
10
+ import { updateGlobalBrainPackages } from './global-brain-updates.mjs';
11
+ import { updateMeshBrainPackages } from './mesh-brain-updates.mjs';
12
+ import { updateSourceBrainPackages } from './source-brain-updates.mjs';
13
+
14
+ export function automaticUpdateStatus(brainDir) {
15
+ let last;
16
+ try { last=JSON.parse(fs.readFileSync(path.join(brainDir,'memory-derived/automatic-brain-updates.json'),'utf8')); } catch {}
17
+ return {enabled:isPackageAutoUpdateEnabled(), intervalMs:60*60*1000, last:last || null};
18
+ }
19
+
20
+ // Keep synchronous package-manager work outside the daemon's event loop. This
21
+ // timer runs even when the task queue is empty; it does not create a scheduler.
22
+ export function startAutomaticBrainUpdates({brainDir,intervalMs=60*60*1000,run=runBounded,onError=message=>logger.error('automatic-brain-updates',message)}={}) {
23
+ let busy=false,stopped=false;
24
+ const tick=async()=>{
25
+ if(busy || stopped)return;
26
+ busy=true;
27
+ try {
28
+ const result=await run(process.execPath,[cliEntry,'update','--automatic','--json'],{cwd:path.resolve(brainDir,'../../..'),timeoutMs:55*60*1000,maxBytes:8*1024*1024});
29
+ if(result.code!==0)onError(result.reason || result.output || 'Automatic update failed');
30
+ } catch(error){onError(error.message);} finally{busy=false;}
31
+ };
32
+ // Wake frequently enough to retry failures; successful checks remain hourly.
33
+ const timer=setInterval(tick,Math.min(intervalMs,5*60*1000));timer.unref?.();void tick();
34
+ return {tick,stop(){stopped=true;clearInterval(timer);}};
35
+ }
36
+
37
+ export async function runAutomaticBrainUpdates({brainDir,fleet=false,force=false,skipThrottle=false,intervalMs=60*60*1000,update=updateAllBrains,compile=maintainCliSurfaces,globalUpdate=updateGlobalBrainPackages,meshUpdate=updateMeshBrainPackages,sourceUpdate=updateSourceBrainPackages,now=Date.now}={}) {
38
+ if(!force && !isPackageAutoUpdateEnabled())return {skipped:true,reason:'disabled',results:[]};
39
+ const file=path.join(brainDir,'memory-derived/automatic-brain-updates.json');
40
+ let saved;try{saved=JSON.parse(fs.readFileSync(file,'utf8'));}catch{}
41
+ const delay=saved?.failed?Math.min(intervalMs,5*60*1000):intervalMs;
42
+ if(!force && !skipThrottle && saved?.checkedAt && now()>=saved.checkedAt && now()-saved.checkedAt<delay)return {skipped:true,reason:'throttled',results:[]};
43
+ fs.mkdirSync(path.dirname(file),{recursive:true});
44
+ const lock=`${file}.lock`;
45
+ let fd;
46
+ if(fs.existsSync(lock)){
47
+ const text=fs.readFileSync(lock,'utf8');
48
+ const verdict=Number(text)===process.pid ? {honor:true} : shouldHonorPidLock(Number(text),{isAlive:pid=>{try{process.kill(pid,0);return true;}catch{return false;}},readCommand:readProcessCommand,entryHint:'total-recall'});
49
+ if(!verdict.honor && fs.readFileSync(lock,'utf8')===text)fs.unlinkSync(lock);
50
+ }
51
+ try {fd=fs.openSync(lock,'wx',0o600);fs.writeFileSync(fd,String(process.pid));}
52
+ catch(error){if(error.code==='EEXIST')return {skipped:true,reason:'maintenance-already-running',failed:1,results:[]};throw error;}
53
+ try {
54
+ let report;
55
+ try {
56
+ const summary=await update({brainDir,dryRun:false,ensureDefaults:true});
57
+ const global=summary.versions?await globalUpdate({versions:summary.versions}):{failed:1,results:[],error:'Cannot maintain global packages without verified published versions'};
58
+ const sources=summary.versions?await sourceUpdate({brainDir,versions:summary.versions}):{failed:1,results:[]};
59
+ const mesh=fleet && summary.versions?await meshUpdate({versions:summary.versions,version:summary.versions['total-recall-brain']}):{failed:0,results:[],scope:'local-only'};
60
+ const changed=summary.updated>0 || global.results.some(row=>row.status==='updated') || sources.results.some(row=>row.status==='updated') || JSON.stringify(saved?.versions)!==JSON.stringify(summary.versions);
61
+ const surfaces=await compile({brainDir,force:changed});
62
+ report={...summary,global,sources,mesh,surfaces,failed:(summary.failed || 0)+global.failed+sources.failed+mesh.failed+(surfaces.ok?0:surfaces.results.filter(row=>row.status==='failed').length),checkedAt:now()};
63
+ }catch(error){report={failed:1,error:error.message,checkedAt:now(),results:[]};}
64
+ const temporary=`${file}.${process.pid}.tmp`;fs.writeFileSync(temporary,JSON.stringify(report,null,2));fs.renameSync(temporary,file);
65
+ return report;
66
+ } finally {fs.closeSync(fd);fs.unlinkSync(lock);}
67
+ }
@@ -8,11 +8,14 @@ import { createHash } from 'node:crypto';
8
8
  import { listUpdateRoots, resolveRegistryFiles, readRegistryEntries, rootsFromEnv } from './package-auto-update.mjs';
9
9
  import { discoverPlugins } from './plugin-loader.mjs';
10
10
  import { comparePackageVersions } from './package-version.mjs';
11
+ import { inspectRequiredPlugins } from './required-plugins.mjs';
12
+ import { ensureRequiredPlugins } from './required-plugins.mjs';
13
+ import YAML from 'yaml';
11
14
 
12
15
  export const BRAIN_PACKAGES = ['total-recall-brain', '@gregiteen/ssss-cli'];
13
16
  const sections = ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies'];
14
17
  const ignored = new Set(['node_modules', 'vendor', 'dist', 'build', 'coverage', 'venv',
15
- 'test', 'tests', '__tests__', 'fixtures', 'tmp', 'scratch']);
18
+ 'test', 'tests', '__tests__', 'fixtures', 'tmp', 'scratch', 'scaffold']);
16
19
  const canonical = key => key === '@ssss/cli' ? '@gregiteen/ssss-cli' : key;
17
20
  const read = file => JSON.parse(fs.readFileSync(file, 'utf8'));
18
21
  const isLink = file => { try { return fs.lstatSync(file).isSymbolicLink(); } catch { return false; } };
@@ -68,7 +71,12 @@ export function brainInstallOwner(root) {
68
71
  const file = path.join(dir, 'package.json');
69
72
  if (fs.existsSync(file)) {
70
73
  const pkg = read(file);
71
- if (dir === root || pkg.workspaces || fs.existsSync(path.join(dir, 'pnpm-workspace.yaml'))) owner = dir;
74
+ let patterns=Array.isArray(pkg.workspaces)?pkg.workspaces:pkg.workspaces?.packages || [];
75
+ const pnpm=path.join(dir,'pnpm-workspace.yaml');
76
+ if(fs.existsSync(pnpm))patterns=[...patterns,...(YAML.parse(fs.readFileSync(pnpm,'utf8'))?.packages || [])];
77
+ const relative=path.relative(dir,root).split(path.sep).join('/');
78
+ const belongs=patterns.some(pattern=>typeof pattern==='string' && !pattern.startsWith('!') && new RegExp('^'+pattern.split('/').map(part=>part==='**'?'.*':part.split('*').map(chunk=>chunk.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')).join('[^/]*')).join('/')+'$').test(relative));
79
+ if (dir === root || belongs) owner = dir;
72
80
  }
73
81
  if (dir === path.dirname(dir)) break;
74
82
  }
@@ -90,6 +98,17 @@ export async function updateAllBrains(opts = {}) {
90
98
  const exec = opts.exec || spawnSync;
91
99
  const roots = listUpdateRoots(opts).map(row => row.root);
92
100
  const errors = [];
101
+ const brains = roots.map(root => ({ root, brain: path.join(root, '.agent', 'skills', 'total-recall') }))
102
+ .filter(({ brain }) => fs.existsSync(brain));
103
+ if (opts.brainDir) brains.push({ root: opts.brainDir, brain: opts.brainDir });
104
+ for (const { root, brain } of brains) {
105
+ if (!dryRun && opts.ensureDefaults === true) {
106
+ try { ensureRequiredPlugins(brain); }
107
+ catch(error){errors.push({root,status:'failed',error:error.message});continue;}
108
+ }
109
+ const required = inspectRequiredPlugins(brain);
110
+ if (required.status !== 'ready') errors.push({ root, status: 'failed', error: required.errors.join('; ') });
111
+ }
93
112
  const requested = [...(opts.roots || []), ...rootsFromEnv(),
94
113
  ...resolveRegistryFiles(opts).flatMap(file => readRegistryEntries(file).map(row => row.path)).filter(Boolean)];
95
114
  for (const root of new Set(requested.map(root => path.resolve(root)))) {
@@ -0,0 +1,71 @@
1
+ /** Canonical built-in CLI routing inventory. */
2
+ export const CLI_COMMANDS = {
3
+ app: 'app/index.mjs',
4
+ init: 'init.mjs',
5
+ setup: 'setup.mjs',
6
+ start: 'start.mjs',
7
+ startup: 'startup.mjs',
8
+ deploy: 'deploy.mjs',
9
+ doctor: 'doctor.mjs',
10
+ backup: 'backup.mjs',
11
+ dream: 'dream.mjs',
12
+ lint: 'lint.mjs',
13
+ daemon: 'daemon.mjs',
14
+ restore: 'restore.mjs',
15
+ upgrade: 'upgrade.mjs',
16
+ update: 'update.mjs',
17
+ friction: 'friction.mjs',
18
+ chat: 'chat.mjs',
19
+ status: 'status.mjs',
20
+ remember: 'remember.mjs',
21
+ forget: 'forget.mjs',
22
+ edit: 'edit.mjs',
23
+ recall: 'recall.mjs',
24
+ context: 'context.mjs',
25
+ rules: 'rules.mjs',
26
+ search: 'recall.mjs',
27
+ research: 'research.mjs',
28
+ proposals: 'proposals.mjs',
29
+ proposal: 'proposals.mjs',
30
+ task: 'task.mjs',
31
+ share: 'share.mjs',
32
+ 'generate-pat': 'generate-pat.mjs',
33
+ key: 'key.mjs',
34
+ keys: 'key.mjs',
35
+ pat: 'key.mjs',
36
+ 'hash-password': 'hash-password.mjs',
37
+ 'reset-password': 'reset-password.mjs',
38
+ compile: 'rebuild.mjs',
39
+ rebuild: 'rebuild.mjs',
40
+ snapshot: 'snapshot.mjs',
41
+ migrate: 'migrate.mjs',
42
+ backfill: 'backfill.mjs',
43
+ ingest: 'ingest.mjs',
44
+ import: 'import-rules.mjs',
45
+ connect: 'connect.mjs',
46
+ sync: 'sync.mjs',
47
+ relay: 'relay.mjs',
48
+ uninstall: 'uninstall.mjs',
49
+ map: 'map.mjs',
50
+ brain: 'brain.mjs',
51
+ config: 'config.mjs',
52
+ skill: 'skill.mjs',
53
+ 'project-id': 'project-id.mjs',
54
+ api: 'api.mjs',
55
+ brief: 'brief.mjs',
56
+ group: 'group.mjs',
57
+ groups: 'group.mjs',
58
+ help: 'help.mjs',
59
+ collab: 'collab.mjs',
60
+ command: 'command.mjs',
61
+ export: 'export.mjs',
62
+ harness: 'harness.mjs',
63
+ watch: 'rebuild.mjs',
64
+ agent: 'agent.mjs',
65
+ agents: 'agent.mjs',
66
+ new: 'new.mjs',
67
+ plugin: 'plugin/index.mjs',
68
+ plugins: 'plugin/index.mjs',
69
+ scaffold: 'plugin/scaffold-cmd.mjs',
70
+ 'scaffold-plugin': 'plugin/scaffold-cmd.mjs',
71
+ };
@@ -0,0 +1,45 @@
1
+ /** Daemon-owned change detection for CLI inventories and every registered surface. */
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import crypto from 'node:crypto';
5
+ import { listLocalBrains } from './brain-registry.mjs';
6
+ import { buildPluginCatalog } from './plugin-catalog.mjs';
7
+ import { discoverPlugins, PACKAGE_ROOT } from './plugin-loader.mjs';
8
+ import { runBounded } from './startup-health.mjs';
9
+ import { logger } from './logger.mjs';
10
+
11
+ export function cliInputFingerprint(root) {
12
+ const hash=crypto.createHash('sha256');
13
+ const add=file=>{try{const stat=fs.statSync(file);hash.update(`${file}:${stat.mtimeMs}:${stat.size}\n`);}catch{hash.update(`missing:${file}\n`);}};
14
+ for(const file of ['bin/total-recall.mjs','src/core/cli-commands.mjs','src/core/cli-quick-reference.mjs'])add(path.join(PACKAGE_ROOT,file));
15
+ const walk=directory=>{for(const item of fs.readdirSync(directory,{withFileTypes:true})){const file=path.join(directory,item.name);if(item.isDirectory())walk(file);else if(item.name.endsWith('.mjs'))add(file);}};
16
+ walk(path.join(PACKAGE_ROOT,'src/cli'));
17
+ hash.update(buildPluginCatalog(root).fingerprint);
18
+ for(const plugin of discoverPlugins(root)){add(plugin.manifestPath);if(plugin.manifest.cli?.handler)add(path.join(plugin.dir,plugin.manifest.cli.handler));}
19
+ return hash.digest('hex');
20
+ }
21
+
22
+ export async function maintainCliSurfaces({brainDir,brains,force=false,run=runBounded,fingerprint=cliInputFingerprint}={}) {
23
+ const results=[];
24
+ for(const brain of brains || listLocalBrains({globalBrainDir:brainDir})) {
25
+ if(!brain.exists || brain.enabled===false)continue;
26
+ const root=brain.root || path.dirname(path.dirname(brain.brainDir));
27
+ const file=path.join(brain.brainDir,'memory-derived/cli-maintenance.json');
28
+ let previous;try{previous=JSON.parse(fs.readFileSync(file,'utf8'));}catch{}
29
+ const current=fingerprint(root);
30
+ if(!force && previous?.fingerprint===current){results.push({root,status:'current'});continue;}
31
+ const result=await run(process.execPath,[path.join(PACKAGE_ROOT,'bin/total-recall.mjs'),'plugin','refresh',...(brain.kind==='global'?['--global']:[])],{cwd:root,timeoutMs:600000,maxBytes:262144});
32
+ if(result.code!==0){results.push({root,status:'failed',error:result.reason || result.output?.slice(-1000) || 'CLI compilation failed'});continue;}
33
+ fs.mkdirSync(path.dirname(file),{recursive:true});
34
+ const temp=`${file}.${process.pid}.tmp`;fs.writeFileSync(temp,JSON.stringify({fingerprint:current,compiledAt:new Date().toISOString()}));fs.renameSync(temp,file);
35
+ results.push({root,status:'refreshed'});
36
+ }
37
+ return {ok:results.every(row=>row.status!=='failed'),results};
38
+ }
39
+
40
+ export function startCliSurfaceMaintenance({brainDir,intervalMs=30000}={}) {
41
+ let busy=false;
42
+ const tick=async()=>{if(busy)return;busy=true;try{const result=await maintainCliSurfaces({brainDir});if(!result.ok)logger.error('cli-maintenance',JSON.stringify(result));}catch(error){logger.error('cli-maintenance',error.message);}finally{busy=false;}};
43
+ const timer=setInterval(tick,intervalMs);timer.unref?.();void tick();
44
+ return {stop(){clearInterval(timer);}};
45
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * The command map every compiled instruction surface leads with, so an agent
3
+ * sees which CLI owns a job before it reaches for curl, sed or ssh. The plugin
4
+ * namespaces and their operations are appended live by
5
+ * `buildInstalledPluginsSummary`, so they cannot go stale.
6
+ * `cli-quick-reference.spec.mjs` fails when a listed command stops existing in
7
+ * `total-recall --help`.
8
+ */
9
+ import { CLI_COMMANDS } from './cli-commands.mjs';
10
+ export const CLI_QUICK_REFERENCE_COMMANDS = Object.keys(CLI_COMMANDS);
11
+
12
+ export const CLI_QUICK_REFERENCE = `## TOTAL RECALL CLI — USE IT FOR EVERYTHING (run \`total-recall <command> --help\` before guessing)
13
+ - Built-in commands: ${CLI_QUICK_REFERENCE_COMMANDS.map(command => `\`${command}\``).join(' · ')}
14
+ - Memory: \`context "<task>" --action <a>\` · \`recall "<q>" --local\` · \`remember <category> "<text>" --global|--project\` · \`edit <slug>\` · \`compile\` · \`brief\` · \`status\` / \`doctor\`
15
+ - CREDENTIALS GO THROUGH THE AUTH PLUGIN ONLY (Total Recall has no native secrets store; never hand-roll curl/sed/ssh for keys): \`auth secret list|get <K>|set <K> --stdin|rotate <K> --stdin|--from-clipboard [--remote <t>]|rotate-auto <K>|rotation-due|shared|meta|catalog|audit|diff-env|export-env\`; remote targets: \`auth secret remote add|list|deploy <t> [--keys a,b] [--dry-run]\`. The store follows \`TR_SECRETS_BRAIN\`.
16
+ - Auth: \`auth status|audit|key list|issue|revoke|password set --stdin|passkey|google status\`
17
+ - Mesh nodes: \`mesh nodes|status|ping|exec <node> <cmd>|ssh <node> [cmd]|access <node>|leader\` — never ssh a machine into itself.
18
+ - Agents and work: \`agent spawn|list|logs|kill\` · \`harness dispatch|council\` · \`task <cmd>\` · \`plugin list|install|info|library\` · \`skill <cmd>\` · \`brain <cmd>\` · \`daemon <cmd>\` · \`update --all-brains --apply\`
19
+ - Every installed plugin namespace and its operations is listed next; run \`total-recall <namespace> --help\` for flags.
20
+ `;