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
@@ -1,32 +1,14 @@
1
1
  {
2
2
  "name": "tr-plugin-mesh",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
+ "private": true,
4
5
  "type": "module",
5
6
  "description": "Headscale and Tailscale SSH control-server mesh administration and node discovery",
6
7
  "scripts": {
7
- "test": "node --test test/*.test.mjs",
8
- "test:runtime": "vitest run"
9
- },
10
- "devDependencies": {
11
- "total-recall-brain": "^3.39.1",
12
- "vitest": "^4.1.11",
13
- "supertest": "^7.2.2",
14
- "jsdom": "^29.1.1"
8
+ "test": "node --test test/*.test.mjs"
15
9
  },
16
10
  "license": "MIT",
17
11
  "dependencies": {
18
12
  "express": "^5.2.1"
19
- },
20
- "files": [
21
- "cli.mjs",
22
- "plugin.json",
23
- "README.md",
24
- "LICENSE",
25
- "src/",
26
- "!src/**/*.spec.mjs",
27
- "ui/"
28
- ],
29
- "publishConfig": {
30
- "access": "public"
31
13
  }
32
14
  }
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://github.com/gregiteen/total-recall/blob/main/metadata.plugin.schema.json",
3
3
  "id": "mesh",
4
4
  "name": "Headscale",
5
- "version": "0.2.1",
5
+ "version": "0.2.2",
6
6
  "description": "Control-server mesh administration, Tailscale SSH, and peer node discovery.",
7
7
  "license": "MIT",
8
8
  "branding": {
@@ -81,6 +81,18 @@
81
81
  "command": "mesh",
82
82
  "handler": "./cli.mjs",
83
83
  "subcommands": [
84
+ {
85
+ "name": "file",
86
+ "description": "Transfer a file through authenticated mesh access and verify its destination hash",
87
+ "effect": "write",
88
+ "output": "json",
89
+ "args": [
90
+ {"name":"operation","label":"Operation","type":"string","positional":true,"required":true},
91
+ {"name":"node","label":"Node","type":"string","positional":true,"required":true},
92
+ {"name":"source","label":"Source","type":"string","positional":true,"required":true},
93
+ {"name":"destination","label":"Destination","type":"string","positional":true,"required":true}
94
+ ]
95
+ },
84
96
  {
85
97
  "name": "status",
86
98
  "description": "Inspect live mesh state",
@@ -113,6 +125,7 @@
113
125
  },
114
126
  {
115
127
  "name": "access",
128
+ "timeout_ms": 180000,
116
129
  "description": "Mesh access operation",
117
130
  "effect": "write"
118
131
  },
@@ -19,6 +19,7 @@ import { spawnSync } from 'node:child_process';
19
19
  import { resolveBrainDir, parseLayerFlag } from './agent-dir.mjs';
20
20
  import {
21
21
  describeHeadscaleAvailability,
22
+ headscaleFetch,
22
23
  createHeadscalePreAuthKey,
23
24
  getHeadscalePolicy,
24
25
  setHeadscalePolicy,
@@ -54,6 +55,10 @@ function printHelp() {
54
55
  Record how to reach a node
55
56
  access import Propose access from ~/.ssh/config (--apply to save)
56
57
  access sync Learn login accounts from every peer already reachable
58
+ access verify [--all-pairs] [--json]
59
+ Prove execution from each configured machine to every other
60
+ file put <node> <source> <destination> [--mode 600]
61
+ Atomically transfer a file and verify its SHA-256
57
62
  access discover [node] Find a working login with this machine's keys and record it
58
63
  ping [node] Measure round-trip latency to a node (or all nodes) (--json)
59
64
  leader Show cluster leader election status and current leader (--json)
@@ -427,6 +432,11 @@ export default async function meshCli(argv = []) {
427
432
 
428
433
  if (command === 'access') {
429
434
  const target = args.shift();
435
+ if(target==='verify'){
436
+ const {verifyMeshAccess}=await import('../core/mesh-access-verify.mjs');
437
+ const report=await verifyMeshAccess({allPairs:args.includes('--all-pairs')});
438
+ console.log(JSON.stringify(report,null,2));if(!report.ok)process.exitCode=1;return;
439
+ }
430
440
  if (!target) {
431
441
  fail('`access` requires a node name, or `import`.');
432
442
  return;
@@ -272,8 +272,12 @@ export function buildMeshSshPolicy({ allowRoot = false } = {}) {
272
272
  ssh: [
273
273
  {
274
274
  action: 'accept',
275
+ // autogroup:member, not autogroup:self. 'self' limits a member to SSH on their own
276
+ // devices, so no node on the mesh could reach any other one and every cross-machine
277
+ // operation fell back to key auth that had never been set up — which reads as the peer
278
+ // being unreachable. The point of this policy is that members reach each other.
275
279
  src: ['autogroup:member'],
276
- dst: ['autogroup:self'],
280
+ dst: ['autogroup:member'],
277
281
  users: allowRoot ? ['autogroup:nonroot', 'root'] : ['autogroup:nonroot'],
278
282
  },
279
283
  ],
@@ -1,6 +1,6 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
- import { getMeshPeers, getMeshSelf, listEnrichedMeshNodes } from './mesh.mjs';
3
+ import { getMeshPeers, getMeshSelf } from './mesh.mjs';
4
4
  import { brainDir } from './config.mjs';
5
5
 
6
6
  /**
@@ -70,27 +70,18 @@ function compareNodes(a, b) {
70
70
  * each node wrote a private, node-local lease document and could elect itself.
71
71
  */
72
72
  export async function getLeaderInfo() {
73
- // Use the ENTITY-ENRICHED node list, not getMeshPeers(). `brain_port` is an
74
- // entity variable: it lives in the vault's mesh-node document and is absent
75
- // from raw Tailscale status. getMeshPeers() reads only that status, so its
76
- // `brain_port` was always null and every caller fell back to port 3000 --
77
- // while the leader was reachable the whole time on its real port.
78
- const nodes = listEnrichedMeshNodes().filter((node) => node.online);
73
+ const nodes = getMeshPeers({ includeSelf: true }).filter((node) => node.online);
79
74
  if (nodes.length === 0) return null;
80
75
 
81
76
  const pin = leaderPin();
82
77
  if (pin) {
83
78
  const pinned = nodes.find((node) => node.ip === pin);
84
79
  if (!pinned) return null;
85
- return { hostname: pinned.hostname, ip: pinned.ip, brain_port: pinned.brain_port ?? null, strategy: 'pinned-mesh-ip' };
80
+ return { hostname: pinned.hostname, ip: pinned.ip, strategy: 'pinned-mesh-ip' };
86
81
  }
87
82
 
88
83
  const leader = [...nodes].sort(compareNodes)[0];
89
- // `brain_port` is carried through because the leader does not necessarily
90
- // serve on the default port. It was dropped here, so callers such as
91
- // secrets-sync fell back to 3000 and reported "fetch failed" against a leader
92
- // that was up and reachable the whole time on a different port.
93
- return { hostname: leader.hostname, ip: leader.ip, brain_port: leader.brain_port ?? null, strategy: 'lowest-mesh-ip' };
84
+ return { hostname: leader.hostname, ip: leader.ip, strategy: 'lowest-mesh-ip' };
94
85
  }
95
86
 
96
87
  /**
@@ -0,0 +1,22 @@
1
+ /** Prove execution from every configured online machine; metadata is not access proof. */
2
+ const quote=value=>"'"+String(value).replaceAll("'","'\\''")+"'";
3
+ export async function verifyMeshAccess({allPairs=false,listNodes,exec}={}){
4
+ if(!listNodes || !exec){const mesh=await import('./mesh.mjs');listNodes ||= ()=>mesh.listEnrichedMeshNodes();exec ||= mesh.execMeshCommand;}
5
+ const nodes=listNodes();const machines=nodes.filter(node=>node.ip && node.access?.ssh_user);
6
+ const self=nodes.find(node=>node.self);
7
+ if(!self)throw new Error('Local mesh identity is unavailable');
8
+ const sources=allPairs?machines:[self];const results=[];
9
+ for(const source of sources)for(const destination of machines){
10
+ if(source.ip===destination.ip)continue;
11
+ const row={source:source.ip,destination:destination.ip,status:'unverified'};results.push(row);
12
+ if(source.online===false || destination.online===false){row.status='offline';continue;}
13
+ try{
14
+ const command=source.self?'id -un':`cd "$HOME" && total-recall mesh exec ${quote(destination.ip)} ${quote('id -un')}`;
15
+ const result=await exec(source.self?destination.ip:source.ip,command,{timeoutMs:20000});
16
+ const actual=result.stdout?.trim();
17
+ row.status=result.success && actual===destination.access.ssh_user?'verified':'failed';
18
+ if(row.status==='failed')row.error=result.stderr || `Expected recorded login ${destination.access.ssh_user}, received ${actual || 'no identity'}`;
19
+ }catch(error){row.status='failed';row.error=error.message;}
20
+ }
21
+ return {ok:results.length>0 && results.every(row=>row.status==='verified'),observedAt:new Date().toISOString(),results,unconfigured:nodes.filter(node=>node.ip && !node.access?.ssh_user).map(node=>({node:node.ip,online:node.online,reason:'No SSH account is configured; execution not verified'}))};
22
+ }
@@ -0,0 +1,18 @@
1
+ /** Bounded atomic file transfer over the authenticated mesh execution owner. */
2
+ import fs from 'node:fs';
3
+ import crypto from 'node:crypto';
4
+ export async function putMeshFile({node,source,destination,mode=0o600,exec}={}){
5
+ if(!node || !source || typeof destination!=='string' || !destination || destination.includes('\0'))throw new Error('Node, source and destination are required');
6
+ if(!Number.isInteger(mode) || mode<0 || mode>0o777)throw new Error('Invalid file mode');
7
+ const bytes=fs.readFileSync(source);if(bytes.length>256*1024)throw new Error('Mesh file exceeds 256 KiB transfer budget');
8
+ const sha256=crypto.createHash('sha256').update(bytes).digest('hex');
9
+ exec ||= (await import('./mesh.mjs')).execMeshCommand;
10
+ const payload=JSON.stringify({destination,mode,data:bytes.toString('base64'),sha256});
11
+ const program=`const fs=require('node:fs'),path=require('node:path'),os=require('node:os'),crypto=require('node:crypto');let input='';for await(const chunk of process.stdin)input+=chunk;const p=JSON.parse(input);const f=p.destination.startsWith('~/')?path.join(os.homedir(),p.destination.slice(2)):path.resolve(p.destination);const b=Buffer.from(p.data,'base64');if(crypto.createHash('sha256').update(b).digest('hex')!==p.sha256)throw Error('Transfer hash mismatch');fs.mkdirSync(path.dirname(f),{recursive:true,mode:448});const t=f+'.tr-transfer-'+process.pid;fs.writeFileSync(t,b,{mode:p.mode,flag:'wx'});fs.renameSync(t,f);const actual=crypto.createHash('sha256').update(fs.readFileSync(f)).digest('hex');if(actual!==p.sha256)throw Error('Destination verification failed');console.log(JSON.stringify({path:f,bytes:b.length,sha256:actual}));`;
12
+ const command=`node --input-type=commonjs -e '${('(async()=>{'+program+'})().catch(()=>{console.error("Mesh transfer failed");process.exitCode=1;});').replaceAll("'","'\\''")}'`;
13
+ const result=await exec(node,command,{timeoutMs:20000,input:payload});
14
+ if(!result.success)throw new Error(result.stderr || 'Mesh file transfer failed');
15
+ let observed;try{observed=JSON.parse(result.stdout);}catch{throw new Error('Invalid mesh file verification response');}
16
+ if(observed.sha256!==sha256 || observed.bytes!==bytes.length)throw new Error('Remote file verification mismatch');
17
+ return {ok:true,node,...observed};
18
+ }
@@ -701,6 +701,8 @@ export async function execMeshCommand(target, command, options = {}) {
701
701
 
702
702
  return new Promise((resolve, reject) => {
703
703
  const proc = spawn('ssh', sshArgs);
704
+ proc.stdin.on('error', error => { if(error.code!=='EPIPE') logger.error('mesh', 'Mesh stdin transport failed'); });
705
+ proc.stdin.end(options.input || '');
704
706
  let stdout = '';
705
707
  let stderr = '';
706
708
  let timedOut = false;
@@ -0,0 +1,14 @@
1
+ /** Per-worker isolation must exist before modules initialize config or logging. */
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { afterAll } from 'vitest';
6
+
7
+ const previous = process.env._TR_TEST_AGENT_DIR;
8
+ const fixture = fs.mkdtempSync(path.join(os.tmpdir(), 'tr-suite-brain-'));
9
+ process.env._TR_TEST_AGENT_DIR = fixture;
10
+ afterAll(() => {
11
+ if (previous === undefined) delete process.env._TR_TEST_AGENT_DIR;
12
+ else process.env._TR_TEST_AGENT_DIR = previous;
13
+ fs.rmSync(fixture, { recursive: true, force: true });
14
+ });
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Greg Iteen
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,35 @@
1
+ # OpenWiki
2
+
3
+ > Multi-repo knowledge graph and documentation wiki engine for Total Recall
4
+
5
+ ## Sharing
6
+
7
+ Offer it to your other Total Recall nodes over the mesh:
8
+
9
+ ```bash
10
+ npx total-recall plugin share openwiki
11
+ ```
12
+
13
+ Peers install it with `npx total-recall plugin install peer:<this-node>/openwiki`.
14
+
15
+ ## Features
16
+
17
+ - **Identifier**: `openwiki`
18
+ - **Version**: 0.1.0
19
+ - **SSSS Category**: `openwiki-hubs`
20
+ - **CLI Command**: `npx total-recall openwiki`
21
+ - **UI**: `ui/panel.js` custom element with DESIGN.md tokens
22
+
23
+ ## Use it two ways
24
+
25
+ This plugin is a reusable brick, so a new project does not start from zero.
26
+
27
+ **Add it to a project (for apps you build and ship).** From the project directory:
28
+
29
+ ```sh
30
+ total-recall plugin vendor https://github.com/gregiteen/tr-plugin-openwiki.git
31
+ ```
32
+
33
+ This copies the plugin's source, skills, tests and docs into `bricks/openwiki` 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`.
34
+
35
+ **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-openwiki.git` and run `total-recall openwiki`. Manage its credentials with `total-recall auth secret` and its skills with `total-recall skill`.
@@ -0,0 +1,124 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * OpenWiki CLI Handler
4
+ * Executed via: npx total-recall openwiki <subcommand>
5
+ */
6
+ // Scaffold: report honestly what exists. Add each real subcommand to
7
+ // IMPLEMENTED and to plugin.json "cli.subcommands" as it is built.
8
+
9
+ import { spawnSync } from "node:child_process";
10
+ import fs from "node:fs";
11
+ import path from "node:path";
12
+
13
+ const TR = process.env.TOTAL_RECALL_BIN || "total-recall";
14
+
15
+ async function probeCommand(args) {
16
+ const json = args.includes("--json");
17
+ const host = process.env.TOTAL_RECALL_URL || "http://127.0.0.1:3000";
18
+ const start = Date.now();
19
+ try {
20
+ const res = await fetch(`${host}/health`, { signal: AbortSignal.timeout(3000) });
21
+ const latency_ms = Date.now() - start;
22
+ const ok = res.ok;
23
+ const status = res.status;
24
+ const result = { status, ok, latency_ms, endpoint: "/health" };
25
+ if (json) console.log(JSON.stringify(result));
26
+ else console.log(`Health probe: HTTP ${status} (${latency_ms}ms) from ${host}`);
27
+ } catch (err) {
28
+ const latency_ms = Date.now() - start;
29
+ const result = { status: 503, ok: false, latency_ms, error: err.message };
30
+ if (json) console.log(JSON.stringify(result));
31
+ else console.error(`Health probe failure: ${err.message} (${latency_ms}ms)`);
32
+ process.exitCode = 1;
33
+ }
34
+ }
35
+
36
+ async function hubsCommand(args) {
37
+ const json = args.includes("--json");
38
+ const targetDir = path.resolve(process.cwd(), "openwiki");
39
+ const hubs = [];
40
+
41
+ if (fs.existsSync(targetDir)) {
42
+ const entries = fs.readdirSync(targetDir, { withFileTypes: true });
43
+ for (const ent of entries) {
44
+ if (ent.isFile() && ent.name.endsWith(".md")) {
45
+ const fullPath = path.join(targetDir, ent.name);
46
+ const content = fs.readFileSync(fullPath, "utf8");
47
+ const titleMatch = content.match(/^#\s+(.+)$/m);
48
+ const title = titleMatch ? titleMatch[1].trim() : ent.name.replace(/\.md$/, "");
49
+ const lines = content.split("\n").length;
50
+ hubs.push({
51
+ title,
52
+ path: path.relative(process.cwd(), fullPath),
53
+ lines,
54
+ size_bytes: content.length
55
+ });
56
+ }
57
+ }
58
+ }
59
+
60
+ if (json) {
61
+ console.log(JSON.stringify({ hubs, count: hubs.length, directory: targetDir }));
62
+ } else {
63
+ if (hubs.length === 0) {
64
+ console.log(`No markdown hubs found in ${targetDir}`);
65
+ } else {
66
+ console.log(`Discovered ${hubs.length} OpenWiki Hub(s) in ${targetDir}:`);
67
+ for (const h of hubs) {
68
+ console.log(` - ${h.title}: ${h.path} (${h.lines} lines, ${h.size_bytes} bytes)`);
69
+ }
70
+ }
71
+ }
72
+ }
73
+
74
+ import { runOpenWikiIngest } from "./src/ingest.mjs";
75
+
76
+ async function ingestCommand(args) {
77
+ const result = await runOpenWikiIngest(args);
78
+ if (!result.ok) {
79
+ process.exitCode = 1;
80
+ }
81
+ }
82
+
83
+ const IMPLEMENTED = {};
84
+ IMPLEMENTED.probe = probeCommand;
85
+ IMPLEMENTED.hubs = hubsCommand;
86
+ IMPLEMENTED.ingest = ingestCommand;
87
+
88
+
89
+ export async function run(argv = []) {
90
+ const args = Array.isArray(argv) ? argv.slice(3) : [];
91
+ const sub = args[0] || "status";
92
+ const json = args.includes("--json");
93
+
94
+ if (sub === "status") {
95
+ const status = { plugin: "openwiki", implemented: Object.keys(IMPLEMENTED) };
96
+ if (json) console.log(JSON.stringify(status));
97
+ else console.log(status.implemented.length
98
+ ? `OpenWiki: implemented subcommands: ${status.implemented.join(", ")}`
99
+ : `OpenWiki: scaffold only — no subcommands implemented yet.`);
100
+ return;
101
+ }
102
+
103
+ if (sub === "help" || sub === "--help" || sub === "-h") {
104
+ console.log(`
105
+ OpenWiki — Multi-repo knowledge graph and documentation wiki engine
106
+
107
+ Usage:
108
+ total-recall openwiki <subcommand> [options]
109
+
110
+ Subcommands:
111
+ status Show OpenWiki plugin status
112
+ probe [--json] Check subsystem health
113
+ hubs [--json] List indexed documentation hubs
114
+ ingest <path> [options] Ingest Markdown files into knowledge graph
115
+ `);
116
+ return;
117
+ }
118
+
119
+ if (IMPLEMENTED[sub]) return IMPLEMENTED[sub](args.slice(1));
120
+
121
+ console.error(`OpenWiki: "${sub}" is not implemented.`);
122
+ process.exitCode = 2;
123
+ }
124
+ export default run;