total-recall-brain 3.40.0 → 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 (187) 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/backup.mjs +71 -3
  141. package/src/cli/brief.mjs +33 -9
  142. package/src/cli/command.mjs +2 -7
  143. package/src/cli/config.mjs +1 -0
  144. package/src/cli/connect.mjs +5 -0
  145. package/src/cli/context.mjs +2 -1
  146. package/src/cli/daemon.mjs +36 -1
  147. package/src/cli/doctor.mjs +6 -0
  148. package/src/cli/harness.mjs +3 -0
  149. package/src/cli/help.mjs +26 -0
  150. package/src/cli/init.mjs +4 -4
  151. package/src/cli/memory-entry.mjs +1 -0
  152. package/src/cli/memory-init.mjs +2 -0
  153. package/src/cli/plugin/index.mjs +11 -0
  154. package/src/cli/plugin/install.mjs +2 -1
  155. package/src/cli/rebuild.mjs +3 -1
  156. package/src/cli/startup.mjs +2 -1
  157. package/src/cli/update.mjs +19 -0
  158. package/src/core/automatic-brain-updates.mjs +67 -0
  159. package/src/core/brain-updates.mjs +21 -2
  160. package/src/core/cli-commands.mjs +71 -0
  161. package/src/core/cli-maintenance.mjs +45 -0
  162. package/src/core/cli-quick-reference.mjs +20 -0
  163. package/src/core/cli-semantic-index.mjs +722 -0
  164. package/src/core/context-compiler.mjs +27 -1
  165. package/src/core/crons.mjs +2 -13
  166. package/src/core/daemon-control.mjs +6 -4
  167. package/src/core/daemon-loop.mjs +12 -8
  168. package/src/core/default-plugins.mjs +5 -0
  169. package/src/core/global-brain-updates.mjs +39 -0
  170. package/src/core/mesh-brain-updates.mjs +30 -0
  171. package/src/core/meta-harness.mjs +108 -0
  172. package/src/core/plugin-dependencies.mjs +2 -1
  173. package/src/core/plugin-loader.mjs +2 -1
  174. package/src/core/plugin-runner.mjs +12 -4
  175. package/src/core/plugin-setup.mjs +20 -0
  176. package/src/core/plugin-store.mjs +22 -4
  177. package/src/core/plugin-tasks.mjs +1 -1
  178. package/src/core/project-brain.mjs +2 -0
  179. package/src/core/project-status.mjs +64 -0
  180. package/src/core/required-plugins.mjs +46 -0
  181. package/src/core/scaffold-plugins.mjs +17 -3
  182. package/src/core/source-brain-updates.mjs +47 -0
  183. package/src/core/startup-health.mjs +5 -3
  184. package/src/core/surface.mjs +44 -10
  185. package/src/server/routes/extension.mjs +53 -8
  186. package/templates/onboarding-interview.md +1 -1
  187. package/frontend/dist/assets/index-SOVUlZqm.js +0 -5596
package/src/cli/brief.mjs CHANGED
@@ -14,7 +14,10 @@ const argl = Array.isArray(args) ? args : [];
14
14
  const asJson = argl.includes('--json');
15
15
  const withMesh = argl.includes('--mesh');
16
16
  if (argl.includes('--help') || argl.includes('-h')) {
17
- console.log(`Usage: total-recall brief [--json] [--mesh]
17
+ console.log(`Usage: total-recall brief [--json] [--mesh] [--all-projects]
18
+
19
+ --all-projects inventories outstanding trackers and daemon tasks across every
20
+ registered root, with source paths and dates; it does not claim live readiness.
18
21
 
19
22
  Read-only brief for the start of a session in the current repo. Composes:
20
23
  project-id, group, command list, secret catalog/rotation-due/shared/tracking-health
@@ -23,6 +26,27 @@ the compiled rules line, docs/projects trackers, openwiki, and the repo's own
23
26
  .agent/skills/start/SKILL.md, which you follow next when it exists.`);
24
27
  return;
25
28
  }
29
+ if(argl.includes('--all-projects')){
30
+ const {outstandingProjectStatus,formatOutstandingProjectStatus}=await import('../core/project-status.mjs');
31
+ const report=outstandingProjectStatus();
32
+ if(argl.includes('--output')){
33
+ const at=argl.indexOf('--output');if(!argl[at+1] || argl[at+1].startsWith('--'))throw new Error('--output requires a file path');
34
+ const output=path.resolve(argl[at+1]);fs.mkdirSync(path.dirname(output),{recursive:true,mode:0o700});
35
+ fs.writeFileSync(output,output.endsWith('.json')?JSON.stringify(report,null,2):formatOutstandingProjectStatus(report),{mode:0o600});
36
+ console.log(JSON.stringify({output,rootCount:report.rootCount,projectCount:report.projectCount,taskCount:report.tasks.length}));return report;
37
+ }
38
+ if(argl.includes('--summary')){
39
+ console.log(JSON.stringify({generatedAt:report.generatedAt,rootCount:report.rootCount,projectCount:report.projectCount,openCheckboxCount:report.openCheckboxCount,evidence:report.evidence,projects:report.projects.filter(row=>row.projects.length).map(row=>({name:row.name,projects:row.projects.map(p=>({id:p.id,state:p.state,status:p.status,open:p.openCount,modifiedAt:p.modifiedAt}))})),tasks:report.tasks.reduce((counts,task)=>{const key=task.slug==='onboarding-interview'?'onboarding-template':`${task.status}:${task.executor}`;counts[key]=(counts[key] || 0)+1;return counts;},{}),missing:report.missing},null,2));return report;
40
+ }
41
+ if(asJson)console.log(JSON.stringify(report,null,2));
42
+ else {
43
+ console.log(`${report.rootCount} roots; ${report.projectCount} outstanding project trackers; ${report.openCheckboxCount} unchecked items; ${report.tasks.length} outstanding daemon tasks.\n${report.evidence}`);
44
+ for(const repo of report.projects.filter(row=>row.projects.length))for(const project of repo.projects)console.log(`${repo.name} / ${project.id}: ${project.state}, ${project.status}, ${project.openCount ?? '?'} open — ${project.tracker || project.folder}`);
45
+ for(const task of report.tasks)console.log(`Task ${task.slug}: ${task.status} — ${task.intent}`);
46
+ for(const row of report.missing)console.log(`Missing root: ${row.root}`);
47
+ }
48
+ return report;
49
+ }
26
50
 
27
51
  // Run a Total Recall verb (async, bounded). Output is parsed for counts only.
28
52
  const tr = (verbArgs, ms = 45000) => new Promise((resolve) => {
@@ -51,12 +75,12 @@ const [pid, groupsRes, cmds, catalog, due, shared, tracking, tasks, secretList,
51
75
  tr(['project-id', '--json']),
52
76
  tr(['group', 'list', '--json']),
53
77
  tr(['command', 'list', '--json']),
54
- tr(['secret', 'catalog']),
55
- tr(['secret', 'rotation-due']),
56
- tr(['secret', 'shared']),
57
- tr(['secret', 'tracking-health']),
78
+ tr(['auth', 'secret', 'catalog']),
79
+ tr(['auth', 'secret', 'rotation-due']),
80
+ tr(['auth', 'secret', 'shared']),
81
+ tr(['auth', 'secret', 'tracking-health']),
58
82
  tr(['task', 'list']),
59
- tr(['secret', 'list']),
83
+ tr(['auth', 'secret', 'list']),
60
84
  tr(['mesh', 'status', '--json']),
61
85
  withMesh ? tr(['mesh', 'ping', '--json'], 60000) : Promise.resolve(null),
62
86
  ]);
@@ -154,9 +178,9 @@ const harnesses = await queryHarnessUsage().catch(() => []);
154
178
  const warnings = [];
155
179
  if (!runtime.ready) warnings.push('Shared runtime readiness failed or unknown: total-recall startup check --json.');
156
180
  if (!project?.project_id) warnings.push('No project brain here (total-recall init) — memory, secrets and rules are global-only.');
157
- if (secrets.rotation_due && secrets.rotation_due !== 0) warnings.push(`Secrets overdue for rotation: ${secrets.rotation_due} (total-recall secret rotation-due).`);
158
- if (secrets.shared_values && secrets.shared_values !== 0) warnings.push(`Credential values reused across keys/apps: ${secrets.shared_values} group(s) (total-recall secret shared).`);
159
- if (secrets.untracked && secrets.untracked !== 0) warnings.push(`Secrets without usage/account tracking: ${secrets.untracked} (total-recall secret tracking-health).`);
181
+ if (secrets.rotation_due && secrets.rotation_due !== 0) warnings.push(`Secrets overdue for rotation: ${secrets.rotation_due} (total-recall auth secret rotation-due).`);
182
+ if (secrets.shared_values && secrets.shared_values !== 0) warnings.push(`Credential values reused across keys/apps: ${secrets.shared_values} group(s) (total-recall auth secret shared).`);
183
+ if (secrets.untracked && secrets.untracked !== 0) warnings.push(`Secrets without usage/account tracking: ${secrets.untracked} (total-recall auth secret tracking-health).`);
160
184
  if (rules && (now - new Date(rules.compiled)) > 7 * 86400000) warnings.push(`Instruction surfaces last compiled ${rules.compiled.slice(0, 10)} — run total-recall compile.`);
161
185
  for (const w of openwiki) if (w.updated && (now - new Date(w.updated)) > 30 * 86400000) warnings.push(`OpenWiki ${w.dir} last updated ${w.updated.slice(0, 10)} — may be stale.`);
162
186
  if (meshStatus && meshStatus.configured === false) warnings.push(`Mesh not configured: ${meshStatus.reason || 'unknown'}.`);
@@ -4,6 +4,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
4
4
  import { spawn } from 'node:child_process';
5
5
  import { resolveBrainLayer } from '../core/config.mjs';
6
6
  import { isSafeRelativePath, validatePluginManifest } from '../core/plugin-loader.mjs';
7
+ import { CLI_COMMANDS } from '../core/cli-commands.mjs';
7
8
 
8
9
  const COMMAND_NAME = /^[a-z][a-z0-9-]{0,63}$/;
9
10
 
@@ -204,13 +205,7 @@ export async function run(argv = []) {
204
205
  * dispatcher itself so this list cannot drift.
205
206
  */
206
207
  export function builtinCommandNames() {
207
- try {
208
- const bin = fs.readFileSync(new URL('../../bin/total-recall.mjs', import.meta.url), 'utf8');
209
- const block = bin.match(/^const COMMANDS = \{([\s\S]*?)^\};/m)?.[1] || '';
210
- return new Set([...block.matchAll(/^\s+'?([a-z][a-z0-9-]*)'?\s*:/gm)].map((m) => m[1]));
211
- } catch {
212
- return new Set();
213
- }
208
+ return new Set(Object.keys(CLI_COMMANDS));
214
209
  }
215
210
 
216
211
  const RISKS = new Set(['read', 'write', 'money', 'dns-cert', 'production-deploy', 'secret-revoke']);
@@ -30,6 +30,7 @@ const KEY_MAP = {
30
30
  // default when every rule should load for every action instead of being
31
31
  // narrowed by applicability.
32
32
  'context_budget': { file: 'surface', path: 'context.budget', type: 'number' },
33
+ 'surface_bootstrap_budget': { file: 'surface', path: 'budget.bootstrap_total_tokens', type: 'number' },
33
34
  };
34
35
 
35
36
  function resolveKeyInfo(key) {
@@ -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
+ };