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
@@ -0,0 +1,71 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
4
+ import { runWorker, runProcess, ADAPTERS } from './worker.mjs';
5
+
6
+ async function hostModule(name) {
7
+ const selected = process.env.TR_PACKAGE_ROOT;
8
+ if (!selected || !path.isAbsolute(selected)) throw new Error('Verified native Total Recall host required');
9
+ const root = fs.realpathSync(selected);
10
+ if (JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')).name !== 'total-recall-brain') throw new Error('Host package identity mismatch');
11
+ return import(pathToFileURL(path.join(root, 'src/core', `${name}.mjs`)).href);
12
+ }
13
+
14
+ export const DEFAULT_PERMISSIONS = Object.freeze({ read: true, write: false, send: false, destructive: false });
15
+ export function commandPermissions(value = DEFAULT_PERMISSIONS) {
16
+ if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Command permissions must be boolean values');
17
+ for (const key of Object.keys(value)) if (!Object.hasOwn(DEFAULT_PERMISSIONS, key) || typeof value[key] !== 'boolean') throw new Error(`Invalid permission ${key}`);
18
+ return { ...DEFAULT_PERMISSIONS, ...value };
19
+ }
20
+
21
+ export async function listTargets() {
22
+ const mesh = await hostModule('mesh');
23
+ return { nodes: mesh.listEnrichedMeshNodes().map(node => ({ id: node.hostname, name: node.title || node.hostname, online: node.online, self: node.self, accessConfigured: Boolean(node.access?.ssh_user) })), adapters: ADAPTERS.map(({ id, name, docs }) => ({ id, name, docs })) };
24
+ }
25
+
26
+ export function workerProgram(request) {
27
+ const source = fs.readFileSync(new URL('./worker.mjs', import.meta.url), 'utf8');
28
+ return source + '\nrunWorker(' + JSON.stringify(request) + ').then(result=>console.log(JSON.stringify(result))).catch(()=>{console.error("Selected agent worker failed");process.exitCode=1;});\n';
29
+ }
30
+
31
+ export async function executeOnNode(node, request, { worker = runWorker, processRunner = runProcess, mesh, access } = {}) {
32
+ if (!node || node === 'local') return worker(request);
33
+ mesh ||= await hostModule('mesh'); access ||= await hostModule('mesh-access');
34
+ const target = mesh.findMeshNode(node);
35
+ if (!target) throw new Error('Selected mesh node is not recorded');
36
+ if (target.self) return worker(request);
37
+ if (target.online === false) throw new Error('Selected mesh node is offline');
38
+ const resolved = access.resolveNodeAccess(target);
39
+ const args = access.buildSshArgs(resolved, { command: 'export PATH="$HOME/.local/bin:/opt/homebrew/bin:/usr/local/bin:$PATH"; exec node --input-type=module', extraOptions: ['BatchMode=yes', 'ConnectTimeout=10'] });
40
+ if (!args) throw new Error('Selected mesh node has no verified SSH access');
41
+ const result = await processRunner('ssh', args, { input: workerProgram(request), timeoutMs: request.operation === 'inspect' ? 120000 : 330000 });
42
+ if (result.reason || result.exitCode !== 0) throw new Error('Selected mesh worker failed; verify node access, workspace and agent installation');
43
+ try { return JSON.parse(result.stdout); } catch { throw new Error('Selected mesh worker returned invalid data'); }
44
+ }
45
+
46
+ export async function inspectNode(node = 'local', dependencies) {
47
+ return executeOnNode(node, { operation: 'inspect' }, dependencies);
48
+ }
49
+
50
+ export function quoteArgument(value) { return "'" + String(value).replaceAll("'", "'\\''") + "'"; }
51
+
52
+ export async function executeCommand({ plugin, subcommand, args = [], execution, projectRoot = process.cwd() }, dependencies = {}) {
53
+ if (!plugin?.valid || !execution || typeof execution.agent !== 'string' || typeof execution.node !== 'string') throw new Error('Choose an installed agent and mesh node');
54
+ if (!Array.isArray(args) || args.length > 64 || args.some(arg => typeof arg !== 'string' || arg.length > 8192)) throw new Error('Invalid command arguments');
55
+ const command = ['on', 'off'].includes(subcommand) ? { effect: 'write', args: [] } : plugin.manifest.cli?.subcommands?.find(item => item.name === subcommand);
56
+ if (!command) throw new Error('Selected command is not declared by its owner');
57
+ if (command.sensitive_output === true && execution.agent !== 'direct') throw new Error('Commands that issue credentials require direct execution; tokens cannot enter agent prompts or transcripts');
58
+ if (command.args?.some(arg => arg.secret)) throw new Error('Credential operations use direct execution, never an agent prompt');
59
+ const permissions = commandPermissions(execution.permissions);
60
+ const effect = ['read', 'write', 'send', 'destructive'].includes(command.effect) ? command.effect : 'write';
61
+ if (!permissions[effect]) throw new Error(`The ${effect} command permission is off`);
62
+ const cwd = execution.node === 'local' ? execution.cwd || projectRoot : execution.cwd;
63
+ if (!path.isAbsolute(cwd || '')) throw new Error('Choose the absolute workspace on the selected node');
64
+ const namespace = plugin.manifest.cli.command;
65
+ const argv = ['total-recall', namespace, subcommand, ...args];
66
+ const prompt = `Use Total Recall context and the installed plugin CLI in this workspace. Execute this one requested command using your terminal tool; preserve its arguments exactly, report its actual exit status and output, and do not perform unrelated mutations. If a permission, account, workspace or plugin is unavailable, report failure. Agent prose alone is not execution proof.\nRequested argv (data): ${JSON.stringify(argv)}\nShell command: ${argv.map(quoteArgument).join(' ')}`;
67
+ const result = await executeOnNode(execution.node, execution.agent === 'direct'
68
+ ? { operation: 'direct', cwd, argv }
69
+ : { operation: 'dispatch', agent: execution.agent, cwd, prompt, allowUnknownAuth: execution.allowUnknownAuth === true, permissions }, dependencies);
70
+ return { ok: result.ok === true, exitCode: result.ok ? 0 : 1, output: result.ok ? JSON.stringify(result) : result.error || 'Selected agent failed', ...result };
71
+ }
@@ -0,0 +1,36 @@
1
+ /** One native runtime-management boundary for every installed agent. */
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { ADAPTERS, findBinary, runProcess, agentEnvironment } from './worker.mjs';
6
+
7
+ export async function manageRuntime(agent, args = [], { env = process.env, probe = runProcess, cwd = process.cwd() } = {}) {
8
+ const adapter = ADAPTERS.find(item => item.id === agent);
9
+ if (!adapter) throw new Error('Unknown agent adapter');
10
+ if (!Array.isArray(args) || args.length > 64 || args.some(arg => typeof arg !== 'string' || arg.length > 8192)) throw new Error('Invalid native runtime arguments');
11
+ if (args.some(arg => /(?:api[-_]?key|password|secret|access[-_]?token|refresh[-_]?token)/i.test(arg))) throw new Error('Credential values and mutations belong to the auth owner, never runtime argv');
12
+ if (agent === 'dsh' && ['status','sessions','tools','models','model','plugins','help','--help','-h'].includes(args[0] || 'status')) {
13
+ const { run } = await import('./adapters/dsh.mjs');
14
+ const lines = [], errors = [], log = console.log, error = console.error;
15
+ const previous = process.exitCode;
16
+ let result, code;
17
+ try {
18
+ console.log = (...parts) => lines.push(parts.join(' ')); console.error = (...parts) => errors.push(parts.join(' '));
19
+ result = await run([process.execPath, 'cli-agents', 'dsh', ...args]);
20
+ code = process.exitCode || 0;
21
+ } finally { console.log = log; console.error = error; process.exitCode = previous; }
22
+ return { ok:code === 0, agent, exitCode:code, data:result?.data, output:lines.join('\n'), error:errors.join('\n') || undefined };
23
+ }
24
+ if (agent === 'hermes' && args[0] === 'auth') {
25
+ const binary = findBinary('total-recall', env);
26
+ if (!binary) throw new Error('Total Recall auth CLI is unavailable');
27
+ const home = env.HERMES_HOME || path.join(env.HOME || os.homedir(), '.hermes');
28
+ fs.mkdirSync(home, { recursive:true, mode:0o700 });
29
+ const result = await probe(binary, ['auth','secret','export-env','--path',home,'--keys','OPENROUTER_API_KEY,TELEGRAM_BOT_TOKEN,TELEGRAM_ALLOWED_USERS'], { cwd, timeoutMs:30000, env:agentEnvironment(env) });
30
+ return { ok:result.exitCode===0 && !result.reason, agent, authority:'auth', exitCode:result.exitCode, output:result.stdout, error:result.reason || (result.exitCode ? 'Auth projection failed' : undefined) };
31
+ }
32
+ const binary = findBinary(adapter.binary, env);
33
+ if (!binary) throw new Error(`${adapter.name} is not installed on the selected node`);
34
+ const result = await probe(binary, args.length ? args : ['--help'], { cwd, timeoutMs:60000, env:agentEnvironment(env) });
35
+ return { ok:result.exitCode===0 && !result.reason, agent, exitCode:result.exitCode, output:result.stdout, error:result.reason || (result.exitCode ? result.stderr || 'Runtime command failed' : undefined) };
36
+ }
@@ -0,0 +1,35 @@
1
+ /** Configure through Total Recall's existing connect, compilation and plugin-record owners. */
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { pathToFileURL } from 'node:url';
5
+ import { findBinary, runProcess, agentEnvironment } from './worker.mjs';
6
+
7
+ export async function setupIntegrations({ node, cwd = process.cwd(), env = process.env, probe = runProcess } = {}) {
8
+ const binary=findBinary('total-recall',env);
9
+ if(!binary)throw new Error('Total Recall CLI is unavailable');
10
+ const connected=[];
11
+ for(const client of ['claude-code','codex','antigravity','cursor','dsh','grok','muse','pi','hermes']) {
12
+ const result=await probe(binary,['connect',client],{cwd,env:agentEnvironment(env),timeoutMs:120000});
13
+ if(result.exitCode!==0 || result.reason) {
14
+ if(/Unknown client/i.test(result.stderr || result.stdout || '')) continue;
15
+ throw new Error(`Total Recall connect ${client} failed`);
16
+ }
17
+ connected.push(client);
18
+ }
19
+ const root=env.TR_PACKAGE_ROOT;
20
+ if(!root || !path.isAbsolute(root) || JSON.parse(fs.readFileSync(path.join(root,'package.json'),'utf8')).name!=='total-recall-brain')throw new Error('Verified native Total Recall host is required');
21
+ let placement=null;
22
+ if(node) {
23
+ const {getPlugin}=await import(pathToFileURL(path.join(root,'src/core/plugin-loader.mjs')));
24
+ const {patchPluginRecord,ensurePluginRecord}=await import(pathToFileURL(path.join(root,'src/core/plugin-store.mjs')));
25
+ const mesh=await import(pathToFileURL(path.join(root,'src/core/mesh.mjs')));
26
+ const target=mesh.findMeshNode(node);
27
+ if(!target)throw new Error('Maintenance node is not recorded in the mesh');
28
+ const plugin=getPlugin('cli-agents',cwd);
29
+ if(!plugin?.valid)throw new Error('CLI-agents is not installed in the selected workspace');
30
+ await ensurePluginRecord(plugin);
31
+ placement=target.hostname;
32
+ await patchPluginRecord(plugin,{task_nodes:{maintenance:placement}});
33
+ }
34
+ return {ok:true,connected,universalInstructions:'AGENTS.md; every dispatch also receives a freshly generated bootstrap',maintenanceNode:placement};
35
+ }
@@ -0,0 +1,175 @@
1
+ /** Portable read-only discovery / bounded dispatch worker; streamed to SSH stdin. */
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { spawn } from 'node:child_process';
6
+
7
+ export const ADAPTERS = [
8
+ { id: 'claude', name: 'Claude Code', binary: 'claude', args: ['--output-format', 'json', '--permission-mode', 'acceptEdits', '-p'], input: 'stdin', auth: ['auth', 'status'], docs: 'https://code.claude.com/docs/en/headless' },
9
+ { id: 'codex', name: 'Codex', binary: 'codex', args: ['exec', '--sandbox', 'workspace-write', '--json', '-'], input: 'stdin', auth: ['login', 'status'], docs: 'https://developers.openai.com/codex/noninteractive' },
10
+ { id: 'agy', name: 'Antigravity', binary: 'agy', args: ['--mode', 'accept-edits', '--sandbox', '--output-format', 'json', '-p'], input: 'argument', docs: 'https://antigravity.google/docs/cli/headless/' },
11
+ { id: 'cursor', name: 'Cursor', binary: 'cursor-agent', args: ['--print', '--output-format', 'json'], input: 'argument', auth: ['status'], docs: 'https://docs.cursor.com/en/cli/reference/parameters' },
12
+ { id: 'dsh', name: 'DSH', binary: 'dsh', args: ['--profile', 'headless', '--json', '-'], input: 'stdin', docs: 'https://github.com/deepseek-ai/deepseek-harness/blob/master/apps/cli/README.md' },
13
+ { id: 'grok', name: 'Grok', binary: 'grok', args: ['--output-format', 'json', '-p'], input: 'argument', docs: 'https://docs.x.ai/build/cli/headless-scripting' },
14
+ { id: 'muse', name: 'Muse', binary: 'muse', args: ['exec', '--json'], input: 'file', docs: 'https://dev.meta.ai/docs/muse-code/' },
15
+ { id: 'pi', name: 'Pi', binary: 'pi', args: ['--print', '--mode', 'json', '--tools', 'read,bash', '--no-mcp', '--no-extensions'], input: 'stdin', docs: 'https://raw.githubusercontent.com/badlogic/pi-mono/main/packages/coding-agent/docs/cli.md' },
16
+ { id: 'hermes', name: 'Hermes', binary: 'hermes', args: ['chat', '--oneshot', '--format', 'stream-json', '--query-file', '-'], input: 'stdin', docs: 'https://github.com/NousResearch/hermes-agent/blob/main/website/docs/reference/cli-commands.md' },
17
+ ];
18
+
19
+ export function agentEnvironment(source = process.env) {
20
+ const allowed = ['PATH', 'HOME', 'USER', 'LOGNAME', 'SHELL', 'TMPDIR', 'TMP', 'TEMP', 'LANG', 'LC_ALL', 'TERM', 'SYSTEMROOT', 'SystemRoot', 'APPDATA', 'LOCALAPPDATA', 'USERPROFILE', 'CODEX_HOME', 'CLAUDE_CONFIG_DIR', 'XDG_CONFIG_HOME', 'XDG_DATA_HOME', 'DSH_HOME', 'HERMES_HOME', 'PI_CODING_AGENT_DIR'];
21
+ return Object.fromEntries(allowed.filter(key => typeof source[key] === 'string').map(key => [key, source[key]]));
22
+ }
23
+
24
+ export function findBinary(name, env = process.env) {
25
+ const home = env.HOME || env.USERPROFILE || os.homedir();
26
+ const installed = [path.dirname(process.execPath), path.join(home, '.local/bin'), path.join(home, '.npm-global/bin'), path.join(home, 'bin')];
27
+ const versions = path.join(home, '.nvm/versions/node');
28
+ try { for (const version of fs.readdirSync(versions).sort().reverse()) installed.push(path.join(versions, version, 'bin')); } catch {}
29
+ for (const dir of [...new Set([...(env.PATH || '').split(path.delimiter), ...installed].filter(Boolean))]) {
30
+ const file = path.join(dir, name);
31
+ try { if (fs.statSync(file).isFile()) { fs.accessSync(file, fs.constants.X_OK); return file; } } catch {}
32
+ }
33
+ return null;
34
+ }
35
+
36
+ export function runProcess(binary, args, { cwd, input = '', timeoutMs = 300000, env = agentEnvironment(), maxBytes = 2 * 1024 * 1024 } = {}) {
37
+ return new Promise(resolve => {
38
+ let stdout = '', stderr = '', bytes = 0, reason = null, child, timer, escalation, settled = false;
39
+ const kill = signal => { try { if (process.platform !== 'win32') process.kill(-child.pid, signal); else child.kill(signal); } catch {} };
40
+ const finish = (exitCode, signal) => {
41
+ if (settled) return;
42
+ settled = true; clearTimeout(timer);
43
+ // A parent can exit on TERM while a descendant still holds the process group.
44
+ // Finish the owned group cleanup before releasing an aborted operation.
45
+ if (reason && escalation) {
46
+ clearTimeout(escalation);
47
+ escalation = setTimeout(() => { kill('SIGKILL'); resolve({ exitCode, signal, stdout, stderr, reason }); }, 500);
48
+ } else resolve({ exitCode, signal, stdout, stderr, reason });
49
+ };
50
+ const stop = cause => { if (reason) return; reason = cause; kill('SIGTERM'); escalation = setTimeout(() => kill('SIGKILL'), 500); };
51
+ try { child = spawn(binary, args, { cwd, env, shell: false, detached: process.platform !== 'win32', stdio: ['pipe', 'pipe', 'pipe'] }); }
52
+ catch { finish(null); return; }
53
+ timer = setTimeout(() => stop('timeout'), timeoutMs);
54
+ child.on('error', () => { reason = 'spawn-error'; finish(null); });
55
+ child.stdin.on('error', () => {});
56
+ for (const [stream, output] of [[child.stdout, true], [child.stderr, false]]) stream.on('data', chunk => {
57
+ const part = chunk.subarray(0, Math.max(0, maxBytes - bytes)).toString(); bytes += chunk.length;
58
+ if (output) stdout += part; else stderr += part;
59
+ if (bytes > maxBytes) stop('output-limit');
60
+ });
61
+ child.on('close', finish); child.stdin.end(input);
62
+ });
63
+ }
64
+
65
+ export function parseAgentResult(id, result) {
66
+ if (result.reason || result.exitCode !== 0) return { ok: false, error: result.reason || 'Agent process failed' };
67
+ let frames;
68
+ try { const value = JSON.parse(result.stdout); frames = Array.isArray(value) ? value : [value]; }
69
+ catch { try { frames = result.stdout.trim().split('\n').filter(Boolean).map(line => JSON.parse(line)); } catch { return { ok: false, error: 'Agent output is not valid structured data' }; } }
70
+ if (!frames.length || frames.some(frame => !frame || typeof frame !== 'object')) return { ok: false, error: 'Agent returned no result' };
71
+ if (frames.some(frame => frame.error || frame.is_error || ['error', 'turn.failed'].includes(frame.type) || ['FAILED', 'ERROR', 'CANCELED', 'WAITING'].includes(frame.status))) return { ok: false, error: 'Agent reported failure' };
72
+ const last = frames.at(-1);
73
+ let text;
74
+ if (id === 'codex' && last.type === 'turn.completed') text = frames.filter(frame => frame.type === 'item.completed' && frame.item?.type === 'agent_message').map(frame => frame.item.text).join('\n');
75
+ if (['claude', 'cursor', 'grok'].includes(id) && last.type === 'result' && last.subtype === 'success') text = last.result;
76
+ if (id === 'agy' && last.status === 'SUCCESS') text = last.response;
77
+ if (id === 'dsh' && last.type === 'final') text = last.text;
78
+ if (id === 'hermes' && last.type === 'result' && last.exit_code === 0) text = last.text;
79
+ if (id === 'pi' && last.type === 'agent_end') {
80
+ const messages = last.messages || [];
81
+ if (messages.some(message => ['error', 'aborted'].includes(message.stopReason))) return { ok: false, error: 'Pi turn failed' };
82
+ text = messages.filter(message => message.role === 'assistant').flatMap(message => message.content || []).filter(part => part.type === 'text').map(part => part.text).join('\n');
83
+ }
84
+ // The official headless contract documents JSONL plus exit status, not MSP
85
+ // notifications. Preserve the actual log rather than invent a headless schema.
86
+ // Exit zero certifies a finished turn only; it never certifies the requested work.
87
+ if (id === 'muse') return { ok: true, response: result.stdout.trim(), events: frames, completionEvidence: 'headless-process-exit', commandExecutionVerified: false };
88
+ return typeof text === 'string' && text.trim() ? { ok: true, response: text, commandExecutionVerified: false } : { ok: false, error: 'Agent did not produce a recognized completed result' };
89
+ }
90
+
91
+ /** Rebuild the instruction payload from the selected node/workspace, never a cached prompt. */
92
+ export async function totalRecallBootstrap(request, { probe = runProcess, env = process.env } = {}) {
93
+ const binary = findBinary('total-recall', env);
94
+ if (!binary) throw new Error('Total Recall is not installed on the selected node');
95
+ const invoke = async args => {
96
+ const result = await probe(binary, args, { cwd: request.cwd, timeoutMs: 120000, maxBytes: 4 * 1024 * 1024, env: agentEnvironment(env) });
97
+ if (result.reason || result.exitCode !== 0) throw new Error(`Total Recall ${args[0]} failed; dispatch refused`);
98
+ return result.stdout;
99
+ };
100
+ const actions = request.permissions?.write ? 'read,edit,test' : 'read';
101
+ await invoke(['cli-agents','setup','--json']);
102
+ const capsule = JSON.parse(await invoke(['context', request.prompt.slice(0,2000), '--action', actions, '--budget', '24000', '--format', 'json']));
103
+ if (capsule.ready !== true || typeof capsule.context !== 'string') throw new Error('Required Total Recall context is not ready');
104
+ const help = await invoke(['--help']);
105
+ const plugins = JSON.parse(await invoke(['plugin', 'library', '--json']));
106
+ const commands = JSON.parse(await invoke(['composable-cli', 'library', '--json']));
107
+ const docs = JSON.parse(await invoke(['cli-agents', 'docs-ensure', '--agent', request.agent, '--json']));
108
+ if (docs.ok !== true) throw new Error('Official agent documentation is unavailable or stale');
109
+ return `Requester: the operator of this Total Recall workspace.\nUse Total Recall and its owning plugin CLI for this task. The following context and command inventories were obtained automatically on this node before dispatch. Official documentation is cached at ${docs.directory}. Read the applicable cached references before changing an integration.\n\nTask context:\n${capsule.context}\n\nComplete CLI help:\n${help}\n\nInstalled plugin command library:\n${JSON.stringify(plugins)}\n\nComposed command library:\n${JSON.stringify(commands)}\n\nRequested task:\n${request.prompt}`;
110
+ }
111
+
112
+ export async function inspectAgents({ probe = runProcess, env = process.env, ids } = {}) {
113
+ const agents = [];
114
+ for (const adapter of ADAPTERS) {
115
+ if (ids && !ids.includes(adapter.id)) continue;
116
+ const binary = findBinary(adapter.binary, env);
117
+ const item = { id: adapter.id, name: adapter.name, installed: Boolean(binary), authenticated: 'unknown', executionVerified: false, docs: adapter.docs };
118
+ if (binary) {
119
+ const version = await probe(binary, ['--version'], { timeoutMs: 8000 });
120
+ item.version = version.exitCode === 0 ? (version.stdout || version.stderr).match(/\b\d+\.\d+(?:\.\d+)?\b/)?.[0] || null : null;
121
+ item.versionVerified = Boolean(item.version);
122
+ if (adapter.auth) {
123
+ const auth = await probe(binary, adapter.auth, { timeoutMs: 15000 });
124
+ if (adapter.id === 'codex') item.authenticated = auth.exitCode === 0 && /Logged in using ChatGPT/i.test(auth.stdout + auth.stderr);
125
+ if (adapter.id === 'claude') {
126
+ try { const value = JSON.parse(auth.stdout); item.authenticated = auth.exitCode === 0 && value.loggedIn === true && value.authMethod === 'claude.ai' && value.apiProvider === 'firstParty' && !value.apiKeySource && Boolean(value.subscriptionType); } catch { item.authenticated = false; }
127
+ }
128
+ if (adapter.id === 'cursor') item.authenticated = auth.exitCode === 0 && /logged in|authenticated/i.test(auth.stdout) && !/not authenticated|not logged in/i.test(auth.stdout);
129
+ }
130
+ }
131
+ agents.push(item);
132
+ }
133
+ return { agents, evidence: 'Installed version/auth metadata; actual command execution requires a separate completed run' };
134
+ }
135
+
136
+ export async function dispatchAgent(request, { probe = runProcess, env = process.env, bootstrap = totalRecallBootstrap } = {}) {
137
+ const adapter = ADAPTERS.find(item => item.id === request.agent);
138
+ if (!adapter || !path.isAbsolute(request.cwd || '') || typeof request.prompt !== 'string' || !request.prompt.trim() || Buffer.byteLength(request.prompt) > 65536) throw new Error('Explicit supported agent, absolute workspace and bounded prompt required');
139
+ if (!fs.statSync(request.cwd).isDirectory()) throw new Error('Selected workspace is not a directory');
140
+ const binary = findBinary(adapter.binary, env);
141
+ if (!binary) throw new Error(`${adapter.name} is not installed on the selected node`);
142
+ const inspected = await inspectAgents({ probe, env, ids: [adapter.id] });
143
+ const state = inspected.agents.find(item => item.id === adapter.id);
144
+ if (!state.versionVerified || state.authenticated === false) throw new Error('Selected agent version or account is not ready');
145
+ if (state.authenticated === 'unknown' && request.allowUnknownAuth !== true) throw new Error('Selected account authentication is unknown; explicitly select this agent to verify it through execution');
146
+ const prompt = await bootstrap(request, { probe, env });
147
+ const args = [...adapter.args];
148
+ const allowWrites = request.permissions?.write === true;
149
+ if (adapter.id === 'codex') args[args.indexOf('--sandbox') + 1] = allowWrites ? 'workspace-write' : 'read-only';
150
+ if (adapter.id === 'claude') args[args.indexOf('--permission-mode') + 1] = allowWrites ? 'acceptEdits' : 'plan';
151
+ if (adapter.id === 'agy') args[args.indexOf('--mode') + 1] = allowWrites ? 'accept-edits' : 'plan';
152
+ if (adapter.id === 'muse' && !allowWrites) args.push('--disable-write');
153
+ let input = adapter.input === 'stdin' ? prompt : '', temporary;
154
+ if (adapter.input === 'argument' || adapter.input === 'file') {
155
+ temporary = fs.mkdtempSync(path.join(os.tmpdir(), 'cli-agent-prompt-'));
156
+ const file = path.join(temporary, 'prompt.txt'); fs.writeFileSync(file, prompt, { mode: 0o600 });
157
+ if (adapter.input === 'file') args.push('--prompt-file', file);
158
+ else args.push(`Read ${file} completely before taking action. It contains the required Total Recall task context, full CLI/plugin/composed-command inventories, official documentation location and the requested task. Carry out that task using the supplied context.`);
159
+ }
160
+ try { return { agent: adapter.id, ...parseAgentResult(adapter.id, await probe(binary, args, { cwd: request.cwd, input, timeoutMs: 300000 })) }; }
161
+ finally { if (temporary) fs.rmSync(temporary, { recursive: true, force: true }); }
162
+ }
163
+
164
+ export async function runWorker(request) {
165
+ if (request.operation === 'inspect') return { ...await inspectAgents(), defaultCwd: os.homedir() };
166
+ if (request.operation === 'dispatch') return dispatchAgent(request);
167
+ if (request.operation === 'direct') {
168
+ if (!path.isAbsolute(request.cwd || '') || !Array.isArray(request.argv) || request.argv.some(arg => typeof arg !== 'string')) throw new Error('Explicit workspace and argv required');
169
+ const binary = findBinary('total-recall');
170
+ if (!binary) throw new Error('Total Recall CLI is not installed on the selected node');
171
+ const result = await runProcess(binary, request.argv.slice(1), { cwd: request.cwd });
172
+ return { ok: result.exitCode === 0 && !result.reason, output: result.stdout, error: result.reason || (result.exitCode ? 'CLI command failed' : undefined), exitCode: result.exitCode, commandExecutionVerified: result.exitCode === 0 && !result.reason };
173
+ }
174
+ throw new Error('Unknown CLI-agent worker operation');
175
+ }
@@ -0,0 +1,18 @@
1
+ export class CommandFleetCard extends HTMLElement {
2
+ connectedCallback() {
3
+ if (!this.shadowRoot) this.attachShadow({ mode: 'open' });
4
+ this.shadowRoot.innerHTML = `<style>:host{display:block;color:#143d38;font-family:var(--font-sans,system-ui)}.fleet{padding:18px;border-radius:18px;background:#f3eee3;border:1px solid #dbb76a}.mark{display:flex;align-items:center;gap:12px}svg{width:36px;height:36px;stroke:#184e47;fill:none;stroke-width:2}h2{font-size:24px;margin:0;letter-spacing:-1px}p{font-size:12px;opacity:.8}.agents{display:flex;overflow-x:auto;gap:9px;scroll-snap-type:x mandatory}.agent{flex:0 0 140px;scroll-snap-align:start;border-left:3px solid #dbb76a;padding:12px;background:#fff9}.agent b{display:block}.agent span{font-size:11px}button{margin-top:12px;padding:10px;border:1px solid #184e47;border-radius:8px;background:#184e47;color:#f3eee3;cursor:pointer}button:disabled{opacity:.6}.working svg{animation:orbit 1.4s ease-in-out infinite}@keyframes orbit{50%{transform:rotate(12deg)}}@media(prefers-reduced-motion:reduce){.working svg{animation:none}}</style><section class="fleet"><div class="mark"><svg viewBox="0 0 40 40" aria-hidden="true"><path d="M6 10h28M6 20h28M6 30h28"/><circle cx="12" cy="10" r="4"/><circle cx="28" cy="20" r="4"/><circle cx="18" cy="30" r="4"/></svg><h2>Command Fleet</h2></div><p>Choose an agent and its workspace for each command.</p><div class="agents" aria-live="polite">No inspection yet.</div><button>Inspect local agents</button><p class="error" role="alert"></p></section>`;
5
+ this.shadowRoot.querySelector('button').onclick = () => this.inspect();
6
+ }
7
+ async inspect() {
8
+ const button = this.shadowRoot.querySelector('button'); button.disabled = true; this.shadowRoot.querySelector('.fleet').classList.add('working');
9
+ try {
10
+ if (!this.host?.run) throw new Error('Connect this card to its command host');
11
+ const result = await this.host.run('status'); const list = this.shadowRoot.querySelector('.agents'); list.replaceChildren();
12
+ for (const agent of result.agents || []) { const entry = document.createElement('div'); entry.className = 'agent'; const title = document.createElement('b'); title.textContent = agent.name; const state = document.createElement('span'); state.textContent = agent.installed ? `Installed · ${agent.version || 'version unknown'} · ${agent.authenticated === true ? 'signed in' : agent.authenticated === false ? 'sign in needed' : 'account unchecked'}` : 'Not installed'; entry.append(title, state); list.append(entry); }
13
+ this.shadowRoot.querySelector('.error').textContent = '';
14
+ } catch (error) { this.shadowRoot.querySelector('.error').textContent = error.message; }
15
+ finally { button.disabled = false; this.shadowRoot.querySelector('.fleet').classList.remove('working'); }
16
+ }
17
+ }
18
+ if (!customElements.get('command-fleet-card')) customElements.define('command-fleet-card', CommandFleetCard);
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Total Recall contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,84 @@
1
+ # Code Quality — a Total Recall plugin
2
+
3
+ One-shot, fail-closed code-quality gates for any repository. The plugin ships a shared **core** (gate runner, report reader, repo detector, config contract). Each repository keeps its own **gate list**, customized through the Total Recall CLI instead of hand-edited files.
4
+
5
+ ## What it does
6
+
7
+ - Runs each configured gate once, as a background job, and writes `reports/latest.json`. There is no daemon.
8
+ - Gate kinds: a command (typecheck, lint, tests, conformance suites) with an output parser (`tsc`, `eslint-stylish`, `eslint-compact`, `biome`, `flake8`, `mypy`, `black`, `generic`), or `grep-forbid`, which turns a written repository invariant into a failing gate.
9
+ - Tiers (`fast`, `full`, `remote`) keep heavy work off small machines. A bounded run lists the gates it skipped.
10
+ - Locking is fail-closed and machine-wide, so only one check runs per host. `--steal-lock` recovers a dead or unverifiable lock and refuses to take one from a verified running check.
11
+ - Reports record coverage, the git head and source freshness. A gate that scans zero files, or a tool that fails without parseable output, is never reported as clean.
12
+
13
+ ## Install into a repository
14
+
15
+ Requires Total Recall and a project brain in the target repository (`total-recall init --project`). There is no skill to deploy: the runner stays in this plugin and the repository keeps only its state.
16
+
17
+ ```bash
18
+ total-recall plugin install https://github.com/gregiteen/tr-plugin-code-quality
19
+ total-recall code-quality detect # review the proposal
20
+ total-recall code-quality detect --apply # or: init (prompts for missing fields)
21
+ total-recall code-quality gate add id=types tier=fast cmd='["npx","tsc","--noEmit"]' parser=tsc
22
+ total-recall code-quality config check # contract, drift, and gate-command checks
23
+ ```
24
+
25
+ ```bash
26
+ total-recall code-quality gate list
27
+ total-recall code-quality-check --tier fast # run the gates once; background job
28
+ total-recall code-quality report # latest report; never starts a check
29
+ total-recall code-quality report file src/ # findings for one path
30
+ ```
31
+
32
+ ## Running on a test node
33
+
34
+ ```bash
35
+ total-recall code-quality config set remoteNode build-box
36
+ total-recall code-quality snapshot --node build-box --name my-repo --install
37
+ ```
38
+
39
+ `snapshot` copies the exact source (git file lists of the repo and its linked plugins, secrets refused) plus the gate config, with a SHA-256 manifest. `--install` runs `npm ci` beside it. On the node, run `node .agent/skills/total-recall/plugins/code-quality/core/check.mjs`.
40
+
41
+ ## Where things live
42
+
43
+ | Path | Owner |
44
+ |:--|:--|
45
+ | `core/` (runner, report reader, detector, config contract) | this plugin |
46
+ | `.agent/skills/code-quality/config.json`, `reports/`, lock | the repository (state only, no skill content) |
47
+
48
+ The canonical gate list is a `skill_config` record in the repository's Total Recall project vault. `config.json` is regenerated from it, and hand edits are reported as drift.
49
+
50
+ ## Data and network access
51
+
52
+ The core runs only the commands in the repository's own config, with the repository as working directory. It reads tracked source files for `grep-forbid` gates and writes only `reports/` and lock files. It makes no network requests. Commands you configure may.
53
+
54
+ ## Development
55
+
56
+ ```bash
57
+ npm test
58
+ ```
59
+
60
+ MIT licensed.
61
+
62
+
63
+ ## Readiness reconciliation — 2026-09-30
64
+
65
+ [Canonical correction plan](docs/projects/completed/CODE_QUALITY_PLUGIN/CODE_QUALITY_PLUGIN_PROJECT_TRACKER.md). No fresh suite was run against this external repository HEAD during the documentation review. Run `npm test` and configured gates on the Mac mini in a bounded background job. The Total Recall central preview is unwired static UI and is not an operational demonstration of this plugin.
66
+
67
+ - **PIC-040:** The Total Recall host CodeQualityPreview.tsx:81 fabricates 42/42 tests and fixed passing gates; it is unwired, and is not proof of this plugin runner.
68
+ - **PIC-041:** Existing architecture names core/config.schema.json, but the tracked file is skills/code-quality/core/config.schema.json; cli.mjs uses host skill-config support, not a self-contained gate configuration store.
69
+
70
+ Durable capability state must use validated SSSS operations. Delivery requires a real clean-install command/UI walkthrough, explicit unavailable/error states and actual result provenance. Tests may use synthetic fixtures; shipped capability behavior may not.
71
+
72
+ ## Use it two ways
73
+
74
+ This plugin is a reusable brick, so a new project does not start from zero.
75
+
76
+ **Add it to a project (for apps you build and ship).** From the project directory:
77
+
78
+ ```sh
79
+ total-recall plugin vendor https://github.com/gregiteen/tr-plugin-code-quality.git
80
+ ```
81
+
82
+ This copies the plugin's source, skills, tests and docs into `bricks/code-quality` and records it in `tr-bricks.json`. Your project then owns that code: edit it, test it in your own gates, and ship it with no runtime dependency on this plugin or on Total Recall. Combine as many bricks as the project needs. List them with `total-recall plugin bricks`.
83
+
84
+ **Use it directly with Total Recall (for your own machines and workflow).** Install it with `total-recall plugin install https://github.com/gregiteen/tr-plugin-code-quality.git` and run `total-recall code-quality`. Manage its credentials with `total-recall auth secret` and its skills with `total-recall skill`.
@@ -0,0 +1,79 @@
1
+ /**
2
+ * `total-recall code-quality <verb>` for the repository you run it from.
3
+ *
4
+ * report [view] [arg] read the latest gate report; never starts a check
5
+ * snapshot --node <name> put the exact source under test on the test node (git file list only)
6
+ * gate list|add|remove edit the repo's gate list
7
+ * config get|set|…, detect [--apply], init
8
+ * the rest of the repo-layer config verbs
9
+ *
10
+ * Config verbs are Total Recall's own `skill config code-quality …`: every
11
+ * write is validated against core/config.schema.json and recorded in the
12
+ * repo's project vault. Gates run once, in the background, through the
13
+ * `code-quality-check` command or core/check.mjs.
14
+ */
15
+ import fs from 'node:fs';
16
+ import path from 'node:path';
17
+ import { pathToFileURL } from 'node:url';
18
+ import { CORE_DIR, findSkillDir, runCore } from './commands/run-core.mjs';
19
+
20
+ const SHORTHAND = new Set(['get', 'set', 'unset', 'validate', 'schema', 'rebuild', 'import', 'check']);
21
+
22
+ // The Total Recall install running this plugin: the bin on the command line,
23
+ // or the package root the plugin runner passes to dashboard runs.
24
+ function skillConfigModule() {
25
+ const roots = [];
26
+ try {
27
+ const bin = fs.realpathSync(process.argv[1]);
28
+ if (path.basename(bin) === 'total-recall.mjs') roots.push(path.dirname(path.dirname(bin)));
29
+ } catch { /* not started from the bin */ }
30
+ if (process.env.TR_PACKAGE_ROOT) roots.push(process.env.TR_PACKAGE_ROOT);
31
+ for (const root of roots) {
32
+ const candidate = path.join(root, 'src', 'cli', 'skill-config.mjs');
33
+ if (fs.existsSync(candidate)) return pathToFileURL(candidate).href;
34
+ }
35
+ return null;
36
+ }
37
+
38
+ function fail(message, exitCode) {
39
+ console.error(message);
40
+ process.exitCode = exitCode;
41
+ }
42
+
43
+ export async function run(argv = []) {
44
+ const args = argv.slice(3);
45
+ const [verb, ...rest] = args;
46
+
47
+ if (verb === 'snapshot') {
48
+ try {
49
+ const { default: snapshot } = await import('./commands/snapshot.mjs');
50
+ const r = snapshot(rest);
51
+ console.log(rest.includes('--json') ? JSON.stringify(r) : `${r.dryRun ? 'Would copy' : 'Copied'} ${r.files} files${r.node ? ` to ${r.node}:${r.dest} (manifest sha256 ${r.manifestSha256})` : ''}${r.warnings.length ? `\n ${r.warnings.join('\n ')}` : ''}`);
52
+ } catch (err) {
53
+ fail(err.message, 1);
54
+ }
55
+ return;
56
+ }
57
+
58
+ if (verb === 'report') {
59
+ try {
60
+ const { exitCode } = runCore('report.mjs', rest);
61
+ if (exitCode) process.exitCode = exitCode;
62
+ } catch (err) {
63
+ fail(err.message, err.exitCode || 1);
64
+ }
65
+ return;
66
+ }
67
+
68
+ const moduleUrl = skillConfigModule();
69
+ if (!moduleUrl) {
70
+ return fail('Run this through the total-recall CLI: config verbs need a Total Recall install with skill config support.', 2);
71
+ }
72
+ const skillDir = findSkillDir();
73
+ const repoRoot = path.resolve(skillDir, '..', '..', '..');
74
+ const { runSkillConfig } = await import(moduleUrl);
75
+ const configArgs = SHORTHAND.has(verb) ? ['config', ...args] : args;
76
+ const result = await runSkillConfig('code-quality', configArgs, { repoRoot, prefix: 'total-recall code-quality', coreDir: CORE_DIR });
77
+ if (!verb || verb === 'help' || args.includes('--help')) console.log(' total-recall code-quality report [worst [skip]|type|file <pattern>|count|raw <checkId>]');
78
+ return result;
79
+ }
@@ -0,0 +1,6 @@
1
+ import { runCore } from './run-core.mjs';
2
+
3
+ /** total-recall code-quality-check [--tier fast|full|remote] [--only <id>] */
4
+ export default function check(args = []) {
5
+ return runCore('check.mjs', args);
6
+ }
@@ -0,0 +1,6 @@
1
+ import { runCore } from './run-core.mjs';
2
+
3
+ /** total-recall code-quality-report [summary|errors|raw <id>|…] */
4
+ export default function report(args = []) {
5
+ return runCore('report.mjs', args);
6
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Run one of this plugin's core scripts against the current repository. The
3
+ * core is never copied into a repo; the repo keeps only its state directory
4
+ * (config.json, reports), found by core/state.mjs.
5
+ */
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+ import { spawnSync } from 'node:child_process';
10
+ import { stateDir } from '../core/state.mjs';
11
+
12
+ export const CORE_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'core');
13
+
14
+ export function findSkillDir(start = process.cwd()) {
15
+ return stateDir(start);
16
+ }
17
+
18
+ export function runCore(script, args) {
19
+ if (!fs.existsSync(stateDir())) {
20
+ const error = new Error('No code-quality state in this repository (.agent/skills/code-quality). Create it: total-recall code-quality init');
21
+ error.exitCode = 2;
22
+ throw error;
23
+ }
24
+ const result = spawnSync(process.execPath, [path.join(CORE_DIR, script), ...args], { stdio: 'inherit' });
25
+ if (result.error) throw result.error;
26
+ return { exitCode: result.status ?? 1, data: { state_dir: stateDir(), script } };
27
+ }