total-recall-brain 3.41.3 → 3.41.4

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 (80) hide show
  1. package/bin/total-recall.mjs +10 -1
  2. package/frontend/dist/assets/{index-DFenQQEQ.js → index-CRzjQLu7.js} +77 -72
  3. package/frontend/dist/index.html +1 -1
  4. package/package.json +7 -5
  5. package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +2 -2
  6. package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +1 -1
  7. package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +1 -1
  8. package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +48 -0
  9. package/scaffold/.agent/skills/total-recall/plugins/auth/src/login-projections.mjs +42 -0
  10. package/scaffold/.agent/skills/total-recall/plugins/auth/src/mesh-secrets.mjs +18 -0
  11. package/scaffold/.agent/skills/total-recall/plugins/auth/src/routes/secrets.mjs +5 -2
  12. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-carriers.mjs +76 -0
  13. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-prune.mjs +37 -0
  14. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-sync.mjs +53 -17
  15. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +12 -12
  16. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +1 -1
  17. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +12 -12
  18. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +1 -1
  19. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +23 -8
  20. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +6 -2
  21. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +23 -8
  22. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package.json +5 -1
  23. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +23 -10
  24. package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +5 -3
  25. package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +1 -1
  26. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/grants.mjs +7 -3
  27. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/mesh.mjs +61 -38
  28. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/leader-election.mjs +57 -2
  29. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh.mjs +220 -36
  30. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/server/routes/mesh.mjs +7 -2
  31. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +65 -0
  32. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/BRICK.md +59 -0
  33. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +534 -0
  34. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +139 -0
  35. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +2849 -0
  36. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +26 -0
  37. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +376 -0
  38. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +37 -0
  39. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/adapters/dsh.mjs +446 -0
  40. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +23 -0
  41. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +398 -0
  42. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +152 -0
  43. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +82 -0
  44. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +90 -0
  45. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +415 -0
  46. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/runtime.mjs +36 -0
  47. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/setup.mjs +35 -0
  48. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/tracker.mjs +133 -0
  49. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +233 -0
  50. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/DESIGN.md +32 -0
  51. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +845 -0
  52. package/scaffold/.agent/skills/total-recall/plugins/openwiki/capability/BRICK.md +64 -0
  53. package/scaffold/.agent/skills/total-recall/plugins/openwiki/capability/index.mjs +250 -0
  54. package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +344 -14
  55. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +2917 -140
  56. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +9 -3
  57. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +223 -5
  58. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/config.mjs +129 -0
  59. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +355 -0
  60. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/upstream-watcher.mjs +375 -0
  61. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/DESIGN.md +35 -22
  62. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +947 -323
  63. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +23 -8
  64. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +5 -1
  65. package/src/cli/config.mjs +3 -0
  66. package/src/cli/connect.mjs +12 -116
  67. package/src/cli/harness.mjs +0 -1
  68. package/src/cli/plugin/create.mjs +56 -3
  69. package/src/cli/plugin/index.mjs +15 -3
  70. package/src/cli/plugin/monetization.mjs +232 -12
  71. package/src/cli/preflight.mjs +161 -0
  72. package/src/core/cli-commands.mjs +2 -0
  73. package/src/core/context-compiler.mjs +8 -1
  74. package/src/core/default-plugins.mjs +1 -1
  75. package/src/core/meta-harness.mjs +16 -38
  76. package/src/core/plugin-dependencies.mjs +6 -1
  77. package/src/core/preflight-gate.mjs +77 -0
  78. package/src/core/preflight-receipt.mjs +123 -0
  79. package/src/core/runtime.mjs +0 -3
  80. package/src/core/surface.mjs +38 -1
@@ -8,8 +8,23 @@
8
8
  "name": "tr-plugin-embeddings",
9
9
  "version": "0.1.0",
10
10
  "license": "MIT",
11
+ "dependencies": {
12
+ "@gregiteen/ssss-cli": "0.14.3"
13
+ },
11
14
  "devDependencies": {
12
- "total-recall-brain": "3.41.2"
15
+ "total-recall-brain": "3.41.3"
16
+ }
17
+ },
18
+ "node_modules/@gregiteen/ssss-cli": {
19
+ "version": "0.14.3",
20
+ "resolved": "https://registry.npmjs.org/@gregiteen/ssss-cli/-/ssss-cli-0.14.3.tgz",
21
+ "integrity": "sha512-vvGgiNLjus3/xp+iotiGqbSG0R7+Q/bP5cP2Psa7Rhtm/vrMuM5B4LebYu7bPf3TzgCyTbv96PcxGWf6gxbW0Q==",
22
+ "license": "ISC",
23
+ "bin": {
24
+ "ssss": "scripts/ssss.mjs"
25
+ },
26
+ "engines": {
27
+ "node": ">=18"
13
28
  }
14
29
  },
15
30
  "node_modules/@hexagon/base64": {
@@ -349,9 +364,9 @@
349
364
  },
350
365
  "node_modules/@ssss/cli": {
351
366
  "name": "@gregiteen/ssss-cli",
352
- "version": "0.11.2",
353
- "resolved": "https://registry.npmjs.org/@gregiteen/ssss-cli/-/ssss-cli-0.11.2.tgz",
354
- "integrity": "sha512-DTkBPCOv9uN79YoRtsa/GnXPbbeHOU5PQcQ995axHg+SLWdkL6xmrTRs5Xxshcje/Iv6+lKkYOc9UL+Arn9kHg==",
367
+ "version": "0.12.1",
368
+ "resolved": "https://registry.npmjs.org/@gregiteen/ssss-cli/-/ssss-cli-0.12.1.tgz",
369
+ "integrity": "sha512-ff0L2LV4db1CfD4W3VmIJwT9fvrpuKDGDfhe6gtjrrgtRmo7BP7jYjRtH7yuQipKikyiP9LIvmVIaxvbjLTwkA==",
355
370
  "dev": true,
356
371
  "license": "ISC",
357
372
  "bin": {
@@ -2566,9 +2581,9 @@
2566
2581
  }
2567
2582
  },
2568
2583
  "node_modules/total-recall-brain": {
2569
- "version": "3.41.2",
2570
- "resolved": "https://registry.npmjs.org/total-recall-brain/-/total-recall-brain-3.41.2.tgz",
2571
- "integrity": "sha512-bwJFAFAyvk9ysf+5zhBzMxrKus1Hj1AygUWdBNt+vQbM2sfSEf6701sa30KwfGyU8+cMuN6YNT5Mn63mFZIi/w==",
2584
+ "version": "3.41.3",
2585
+ "resolved": "https://registry.npmjs.org/total-recall-brain/-/total-recall-brain-3.41.3.tgz",
2586
+ "integrity": "sha512-0uIh3WZE3dHxrKFTtRMxvSBLx7aV3j7ry9H9NxRShtCycwQ38T7+70Q/GJAqsdpwt894h8B6AAmWJnHwsFG3XQ==",
2572
2587
  "dev": true,
2573
2588
  "license": "MIT",
2574
2589
  "workspaces": [
@@ -2576,7 +2591,7 @@
2576
2591
  ],
2577
2592
  "dependencies": {
2578
2593
  "@simplewebauthn/server": "^13.3.2",
2579
- "@ssss/cli": "npm:@gregiteen/ssss-cli@0.11.2",
2594
+ "@ssss/cli": "npm:@gregiteen/ssss-cli@0.12.1",
2580
2595
  "bcrypt": "^6.0.0",
2581
2596
  "better-sqlite3": "^12.11.1",
2582
2597
  "cookie-parser": "^1.4.7",
@@ -4,7 +4,8 @@
4
4
  "type": "module",
5
5
  "description": "Local embedding model management: provider/model status, benchmark, index build and coverage",
6
6
  "scripts": {
7
- "test": "node --test test/*.test.mjs"
7
+ "test": "node --test test/*.test.mjs",
8
+ "ssss": "ssss"
8
9
  },
9
10
  "license": "MIT",
10
11
  "files": [
@@ -13,5 +14,8 @@
13
14
  "README.md",
14
15
  "LICENSE",
15
16
  "ui/"
16
- ]
17
+ ],
18
+ "dependencies": {
19
+ "@gregiteen/ssss-cli": "0.14.3"
20
+ }
17
21
  }
@@ -8,11 +8,26 @@
8
8
  "name": "tr-plugin-git-sentinel",
9
9
  "version": "1.1.2",
10
10
  "license": "MIT",
11
+ "dependencies": {
12
+ "@gregiteen/ssss-cli": "0.14.3"
13
+ },
11
14
  "devDependencies": {
12
- "total-recall-brain": "3.41.2",
15
+ "total-recall-brain": "3.41.3",
13
16
  "vitest": "^4.1.10"
14
17
  }
15
18
  },
19
+ "node_modules/@gregiteen/ssss-cli": {
20
+ "version": "0.14.3",
21
+ "resolved": "https://registry.npmjs.org/@gregiteen/ssss-cli/-/ssss-cli-0.14.3.tgz",
22
+ "integrity": "sha512-vvGgiNLjus3/xp+iotiGqbSG0R7+Q/bP5cP2Psa7Rhtm/vrMuM5B4LebYu7bPf3TzgCyTbv96PcxGWf6gxbW0Q==",
23
+ "license": "ISC",
24
+ "bin": {
25
+ "ssss": "scripts/ssss.mjs"
26
+ },
27
+ "engines": {
28
+ "node": ">=18"
29
+ }
30
+ },
16
31
  "node_modules/@hexagon/base64": {
17
32
  "version": "1.1.28",
18
33
  "resolved": "https://registry.npmjs.org/@hexagon/base64/-/base64-1.1.28.tgz",
@@ -647,9 +662,9 @@
647
662
  },
648
663
  "node_modules/@ssss/cli": {
649
664
  "name": "@gregiteen/ssss-cli",
650
- "version": "0.11.2",
651
- "resolved": "https://registry.npmjs.org/@gregiteen/ssss-cli/-/ssss-cli-0.11.2.tgz",
652
- "integrity": "sha512-DTkBPCOv9uN79YoRtsa/GnXPbbeHOU5PQcQ995axHg+SLWdkL6xmrTRs5Xxshcje/Iv6+lKkYOc9UL+Arn9kHg==",
665
+ "version": "0.12.1",
666
+ "resolved": "https://registry.npmjs.org/@gregiteen/ssss-cli/-/ssss-cli-0.12.1.tgz",
667
+ "integrity": "sha512-ff0L2LV4db1CfD4W3VmIJwT9fvrpuKDGDfhe6gtjrrgtRmo7BP7jYjRtH7yuQipKikyiP9LIvmVIaxvbjLTwkA==",
653
668
  "dev": true,
654
669
  "license": "ISC",
655
670
  "bin": {
@@ -3560,9 +3575,9 @@
3560
3575
  }
3561
3576
  },
3562
3577
  "node_modules/total-recall-brain": {
3563
- "version": "3.41.2",
3564
- "resolved": "https://registry.npmjs.org/total-recall-brain/-/total-recall-brain-3.41.2.tgz",
3565
- "integrity": "sha512-bwJFAFAyvk9ysf+5zhBzMxrKus1Hj1AygUWdBNt+vQbM2sfSEf6701sa30KwfGyU8+cMuN6YNT5Mn63mFZIi/w==",
3578
+ "version": "3.41.3",
3579
+ "resolved": "https://registry.npmjs.org/total-recall-brain/-/total-recall-brain-3.41.3.tgz",
3580
+ "integrity": "sha512-0uIh3WZE3dHxrKFTtRMxvSBLx7aV3j7ry9H9NxRShtCycwQ38T7+70Q/GJAqsdpwt894h8B6AAmWJnHwsFG3XQ==",
3566
3581
  "dev": true,
3567
3582
  "license": "MIT",
3568
3583
  "workspaces": [
@@ -3570,7 +3585,7 @@
3570
3585
  ],
3571
3586
  "dependencies": {
3572
3587
  "@simplewebauthn/server": "^13.3.2",
3573
- "@ssss/cli": "npm:@gregiteen/ssss-cli@0.11.2",
3588
+ "@ssss/cli": "npm:@gregiteen/ssss-cli@0.12.1",
3574
3589
  "bcrypt": "^6.0.0",
3575
3590
  "better-sqlite3": "^12.11.1",
3576
3591
  "cookie-parser": "^1.4.7",
@@ -4,7 +4,8 @@
4
4
  "type": "module",
5
5
  "description": "Reports the state of the current git repository: branch, dirty files, unpushed commits and stashes.",
6
6
  "scripts": {
7
- "test": "vitest run"
7
+ "test": "vitest run",
8
+ "ssss": "ssss"
8
9
  },
9
10
  "license": "MIT",
10
11
  "files": [
@@ -16,5 +17,8 @@
16
17
  ],
17
18
  "publishConfig": {
18
19
  "access": "public"
20
+ },
21
+ "dependencies": {
22
+ "@gregiteen/ssss-cli": "0.14.3"
19
23
  }
20
24
  }
@@ -1,20 +1,21 @@
1
1
  {
2
2
  "name": "tr-plugin-mesh",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "tr-plugin-mesh",
9
- "version": "0.2.4",
9
+ "version": "0.2.6",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
+ "@gregiteen/ssss-cli": "0.14.3",
12
13
  "express": "^5.2.1"
13
14
  },
14
15
  "devDependencies": {
15
16
  "jsdom": "^29.1.1",
16
17
  "supertest": "^7.3.1",
17
- "total-recall-brain": "3.41.2",
18
+ "total-recall-brain": "3.41.3",
18
19
  "vitest": "^4.1.12"
19
20
  }
20
21
  },
@@ -240,6 +241,18 @@
240
241
  }
241
242
  }
242
243
  },
244
+ "node_modules/@gregiteen/ssss-cli": {
245
+ "version": "0.14.3",
246
+ "resolved": "https://registry.npmjs.org/@gregiteen/ssss-cli/-/ssss-cli-0.14.3.tgz",
247
+ "integrity": "sha512-vvGgiNLjus3/xp+iotiGqbSG0R7+Q/bP5cP2Psa7Rhtm/vrMuM5B4LebYu7bPf3TzgCyTbv96PcxGWf6gxbW0Q==",
248
+ "license": "ISC",
249
+ "bin": {
250
+ "ssss": "scripts/ssss.mjs"
251
+ },
252
+ "engines": {
253
+ "node": ">=18"
254
+ }
255
+ },
243
256
  "node_modules/@hexagon/base64": {
244
257
  "version": "1.1.28",
245
258
  "resolved": "https://registry.npmjs.org/@hexagon/base64/-/base64-1.1.28.tgz",
@@ -897,9 +910,9 @@
897
910
  },
898
911
  "node_modules/@ssss/cli": {
899
912
  "name": "@gregiteen/ssss-cli",
900
- "version": "0.11.2",
901
- "resolved": "https://registry.npmjs.org/@gregiteen/ssss-cli/-/ssss-cli-0.11.2.tgz",
902
- "integrity": "sha512-DTkBPCOv9uN79YoRtsa/GnXPbbeHOU5PQcQ995axHg+SLWdkL6xmrTRs5Xxshcje/Iv6+lKkYOc9UL+Arn9kHg==",
913
+ "version": "0.12.1",
914
+ "resolved": "https://registry.npmjs.org/@gregiteen/ssss-cli/-/ssss-cli-0.12.1.tgz",
915
+ "integrity": "sha512-ff0L2LV4db1CfD4W3VmIJwT9fvrpuKDGDfhe6gtjrrgtRmo7BP7jYjRtH7yuQipKikyiP9LIvmVIaxvbjLTwkA==",
903
916
  "dev": true,
904
917
  "license": "ISC",
905
918
  "bin": {
@@ -4216,9 +4229,9 @@
4216
4229
  }
4217
4230
  },
4218
4231
  "node_modules/total-recall-brain": {
4219
- "version": "3.41.2",
4220
- "resolved": "https://registry.npmjs.org/total-recall-brain/-/total-recall-brain-3.41.2.tgz",
4221
- "integrity": "sha512-bwJFAFAyvk9ysf+5zhBzMxrKus1Hj1AygUWdBNt+vQbM2sfSEf6701sa30KwfGyU8+cMuN6YNT5Mn63mFZIi/w==",
4232
+ "version": "3.41.3",
4233
+ "resolved": "https://registry.npmjs.org/total-recall-brain/-/total-recall-brain-3.41.3.tgz",
4234
+ "integrity": "sha512-0uIh3WZE3dHxrKFTtRMxvSBLx7aV3j7ry9H9NxRShtCycwQ38T7+70Q/GJAqsdpwt894h8B6AAmWJnHwsFG3XQ==",
4222
4235
  "dev": true,
4223
4236
  "license": "MIT",
4224
4237
  "workspaces": [
@@ -4226,7 +4239,7 @@
4226
4239
  ],
4227
4240
  "dependencies": {
4228
4241
  "@simplewebauthn/server": "^13.3.2",
4229
- "@ssss/cli": "npm:@gregiteen/ssss-cli@0.11.2",
4242
+ "@ssss/cli": "npm:@gregiteen/ssss-cli@0.12.1",
4230
4243
  "bcrypt": "^6.0.0",
4231
4244
  "better-sqlite3": "^12.11.1",
4232
4245
  "cookie-parser": "^1.4.7",
@@ -1,14 +1,16 @@
1
1
  {
2
2
  "name": "tr-plugin-mesh",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "type": "module",
5
5
  "description": "Headscale and Tailscale SSH control-server mesh administration and node discovery",
6
6
  "scripts": {
7
- "test": "node --test test/*.test.mjs && vitest run"
7
+ "test": "node --test test/*.test.mjs && vitest run",
8
+ "ssss": "ssss"
8
9
  },
9
10
  "license": "MIT",
10
11
  "dependencies": {
11
- "express": "^5.2.1"
12
+ "express": "^5.2.1",
13
+ "@gregiteen/ssss-cli": "0.14.3"
12
14
  },
13
15
  "files": [
14
16
  "cli.mjs",
@@ -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.4",
5
+ "version": "0.2.6",
6
6
  "description": "Control-server mesh administration, Tailscale SSH, and peer node discovery.",
7
7
  "license": "MIT",
8
8
  "requires": [
@@ -2,10 +2,12 @@
2
2
  * `mesh grants` — which enrolled machines may read which secret keys.
3
3
  *
4
4
  * mesh grants list
5
- * mesh grants add (--node <name> | --tag <tag:x> | --user <login>) KEY [KEY...]
5
+ * mesh grants add (--node <name> | --tag <tag:x> | --user <login>) (KEY | --repo <name>)...
6
6
  * mesh grants remove (--node <name> | --tag <tag:x> | --user <login>) [KEY...]
7
7
  *
8
- * Keys may end in `*` to match a prefix. Nothing is granted by default.
8
+ * Keys may end in `*` to match a prefix. `--repo <name>` grants every secret whose `repos`
9
+ * metadata names that repository (stored as `repo:<name>`, resolved on the leader). Prefer it to
10
+ * `*`: a node that leaks then exposes only what its repositories use. Nothing is granted by default.
9
11
  */
10
12
  import { grantKeys, loadPolicy, revokeKeys, savePolicy, policyPath } from '../core/mesh-identity.mjs';
11
13
 
@@ -15,13 +17,14 @@ function parse(args) {
15
17
  for (let i = 0; i < args.length; i += 1) {
16
18
  const arg = args[i];
17
19
  if (arg === '--node' || arg === '--tag' || arg === '--user') selector[arg.slice(2)] = args[(i += 1)];
20
+ else if (arg === '--repo') keys.push(`repo:${args[(i += 1)]}`);
18
21
  else if (arg.startsWith('--')) throw new Error(`Unknown option ${arg}`);
19
22
  else keys.push(arg);
20
23
  }
21
24
  return { selector, keys };
22
25
  }
23
26
 
24
- export async function grantsCli(args, brainDir, out = console.log) {
27
+ export async function grantsCli(args, brainDir, out = console.log, warn = console.error) {
25
28
  const action = args[0] || 'list';
26
29
  if (action === 'list') {
27
30
  out(JSON.stringify({ path: policyPath(brainDir), ...loadPolicy(brainDir) }));
@@ -29,6 +32,7 @@ export async function grantsCli(args, brainDir, out = console.log) {
29
32
  }
30
33
  if (action !== 'add' && action !== 'remove') throw new Error('Usage: mesh grants list|add|remove');
31
34
  const { selector, keys } = parse(args.slice(1));
35
+ if (action === 'add' && keys.includes('*')) warn('Warning: "*" gives this machine every secret; if it leaks, every secret must be rotated. Prefer --repo <name> or named keys.');
32
36
  const current = loadPolicy(brainDir);
33
37
  const next = action === 'add' ? grantKeys(current, selector, keys) : revokeKeys(current, selector, keys);
34
38
  savePolicy(brainDir, next);
@@ -26,9 +26,10 @@ import {
26
26
  buildMeshSshPolicy,
27
27
  } from '../core/headscale-client.mjs';
28
28
  import {
29
- findMeshNode,
29
+ isSelfNode,
30
30
  listEnrichedMeshNodes,
31
31
  meshVaultRoot,
32
+ resolveMeshNode,
32
33
  setMeshNodeAccess,
33
34
  } from '../core/mesh.mjs';
34
35
  import {
@@ -51,6 +52,9 @@ function printHelp() {
51
52
  nodes List mesh nodes and how to reach each one
52
53
  ssh <node> [cmd…] Open a session using the node's recorded access
53
54
  exec <node> <cmd…> Execute a command on a node non-interactively (--json)
55
+ leader [--json] Show the elected leader and its server port
56
+ leader pin <ip> [--port <n>]
57
+ Pin the leader node and the port its server answers on
54
58
  access <node> Show resolved access for a node
55
59
  access <node> --user <u> [--port <n>] [--host <h>] [--identity <path>]
56
60
  Record how to reach a node
@@ -368,22 +372,19 @@ export default async function meshCli(argv = []) {
368
372
  return;
369
373
  }
370
374
 
371
- // Try enriched nodes first, then fall back to the Headscale registry
372
- // for givenName matches (the friendly name shown in `mesh nodes`).
373
- let node = findMeshNode(target, vaultRoot);
374
- if (!node) {
375
- let registry = null;
376
- try {
377
- const data = await headscaleFetch(
378
- '/api/v1/node', {}, brainDir,
379
- );
380
- registry = data?.nodes || [];
381
- } catch {}
382
- node = findMeshNode(target, vaultRoot, registry);
375
+ let node;
376
+ try {
377
+ node = await resolveMeshNode(target, { vaultRoot, brainDir });
378
+ } catch (err) {
379
+ fail(err.message, 'Run `total-recall mesh nodes` to list them.');
380
+ return;
383
381
  }
384
382
 
385
- if (!node) {
386
- fail(`No mesh node matches "${target}".`, 'Run `total-recall mesh nodes` to list them.');
383
+ if (isSelfNode(node)) {
384
+ fail(
385
+ `"${node.hostname}" is this machine — the mesh never tunnels a node into itself.`,
386
+ 'Run the command locally; `mesh ssh`/`mesh exec` address peers.',
387
+ );
387
388
  return;
388
389
  }
389
390
 
@@ -416,7 +417,7 @@ export default async function meshCli(argv = []) {
416
417
 
417
418
  try {
418
419
  const { execMeshCommand } = await import('../core/mesh.mjs');
419
- const res = await execMeshCommand(target, cmd, { vaultRoot });
420
+ const res = await execMeshCommand(target, cmd, { vaultRoot, brainDir });
420
421
  if (jsonMode) {
421
422
  console.log(JSON.stringify(res, null, 2));
422
423
  } else {
@@ -446,14 +447,21 @@ export default async function meshCli(argv = []) {
446
447
  if (target === 'discover') {
447
448
  const { discoverNodeAccess } = await import('../core/mesh-discover.mjs');
448
449
  const wanted = args.find((a) => !a.startsWith('-'));
449
- const targets = wanted
450
- ? [findMeshNode(wanted, vaultRoot)].filter(Boolean)
451
- : listEnrichedMeshNodes(vaultRoot).filter(
452
- (n) => !n.self && n.ip && n.online !== false && !n.access?.verified_at,
453
- );
450
+ let targets;
451
+ if (wanted) {
452
+ try {
453
+ targets = [await resolveMeshNode(wanted, { vaultRoot, brainDir })];
454
+ } catch (err) {
455
+ fail(err.message, 'Run `total-recall mesh nodes` to list them.');
456
+ return;
457
+ }
458
+ } else {
459
+ targets = listEnrichedMeshNodes(vaultRoot).filter(
460
+ (n) => !n.self && n.ip && n.online !== false && !n.access?.verified_at,
461
+ );
462
+ }
454
463
  if (!targets.length) {
455
- console.log(wanted ? `No mesh node matches "${wanted}".` : 'Every online node already has a verified login.');
456
- if (wanted) process.exitCode = 1;
464
+ console.log('Every online node already has a verified login.');
457
465
  return;
458
466
  }
459
467
  let missing = 0;
@@ -545,9 +553,11 @@ export default async function meshCli(argv = []) {
545
553
  const identity = readOption('--identity');
546
554
 
547
555
  if (!user && !port && !host && !identity) {
548
- const node = findMeshNode(target, vaultRoot);
549
- if (!node) {
550
- fail(`No mesh node matches "${target}".`);
556
+ let node;
557
+ try {
558
+ node = await resolveMeshNode(target, { vaultRoot, brainDir });
559
+ } catch (err) {
560
+ fail(err.message, 'Run `total-recall mesh nodes` to list them.');
551
561
  return;
552
562
  }
553
563
  console.log(JSON.stringify(resolveNodeAccess(node), null, 2));
@@ -560,13 +570,12 @@ export default async function meshCli(argv = []) {
560
570
  if (host) patch.ssh_host = host;
561
571
  if (identity) patch.identity_file = identity;
562
572
 
563
- const result = await setMeshNodeAccess(target, patch, { vaultRoot });
573
+ const result = await setMeshNodeAccess(target, patch, { vaultRoot, brainDir });
564
574
  if (!result.written) {
575
+ const unresolved = result.reason === 'node-not-found' || result.reason === 'node-ambiguous';
565
576
  fail(
566
- `Could not record access for "${target}".`,
567
- result.reason === 'node-not-found'
568
- ? 'Run `total-recall mesh nodes` to see known nodes.'
569
- : 'The vault write was rejected.',
577
+ unresolved ? result.error : `Could not record access for "${target}".`,
578
+ unresolved ? 'Run `total-recall mesh nodes` to see known nodes.' : 'The vault write was rejected.',
570
579
  );
571
580
  return;
572
581
  }
@@ -591,7 +600,19 @@ export default async function meshCli(argv = []) {
591
600
  }
592
601
 
593
602
  if (command === 'leader') {
594
- const { getLeaderInfo, isLeader } = await import('../core/leader-election.mjs');
603
+ const { getLeaderInfo, isLeader, setLeaderPin } = await import('../core/leader-election.mjs');
604
+ if (args[0] === 'pin') {
605
+ const ip = args[1];
606
+ const portIndex = args.indexOf('--port');
607
+ const port = portIndex >= 0 ? args[portIndex + 1] : undefined;
608
+ if (!ip || ip.startsWith('-')) {
609
+ fail('Usage: total-recall mesh leader pin <mesh-ip> [--port <leader server port>]');
610
+ return;
611
+ }
612
+ const pinned = setLeaderPin({ ip, port });
613
+ console.log(`Leader pinned to ${pinned.leaderIp}${pinned.leaderPort ? `:${pinned.leaderPort}` : ''} in ${pinned.file}`);
614
+ return;
615
+ }
595
616
  const leaderInfo = await getLeaderInfo();
596
617
  const thisIsLeader = await isLeader();
597
618
  if (args.includes('--json')) {
@@ -602,7 +623,7 @@ export default async function meshCli(argv = []) {
602
623
  console.log('No leader elected (no mesh nodes online).');
603
624
  return;
604
625
  }
605
- console.log(`👑 Current Cluster Leader: ${leaderInfo.hostname} (${leaderInfo.ip})`);
626
+ console.log(`👑 Current Cluster Leader: ${leaderInfo.hostname} (${leaderInfo.ip}${leaderInfo.port ? `:${leaderInfo.port}` : ''})`);
606
627
  console.log(` Strategy: ${leaderInfo.strategy}`);
607
628
  console.log(` Local node: ${thisIsLeader ? 'LEADER (this node runs cluster workloads)' : 'FOLLOWER'}`);
608
629
  return;
@@ -611,18 +632,20 @@ export default async function meshCli(argv = []) {
611
632
  if (command === 'ping' || command === 'latency') {
612
633
  const target = args.find((a) => !a.startsWith('-'));
613
634
  const isJson = args.includes('--json');
614
- const { getMeshPeers, findMeshNode } = await import('../core/mesh.mjs');
635
+ const { getMeshPeers } = await import('../core/mesh.mjs');
615
636
  const { throttledFetch } = await import('../core/throttled-fetch.mjs');
616
637
 
617
638
  const port = Number(process.env.TR_SERVER_PORT || process.env.PORT || 3000);
618
639
 
619
640
  if (target) {
620
- const node = findMeshNode(target, vaultRoot);
621
- if (!node) {
622
- fail(`No mesh node matches "${target}".`);
641
+ let node;
642
+ try {
643
+ node = await resolveMeshNode(target, { vaultRoot, brainDir });
644
+ } catch (err) {
645
+ fail(err.message, 'Run `total-recall mesh nodes` to list them.');
623
646
  return;
624
647
  }
625
- if (node.self) {
648
+ if (isSelfNode(node)) {
626
649
  if (isJson) {
627
650
  console.log(JSON.stringify({ node: node.hostname, ip: node.ip, latency_ms: 0, self: true, reachable: true }, null, 2));
628
651
  } else {
@@ -60,6 +60,58 @@ export function leaderPin(brain = brainDir) {
60
60
  }
61
61
  }
62
62
 
63
+ function validPort(value) {
64
+ const port = Number(value);
65
+ return Number.isInteger(port) && port > 0 && port <= 65535 ? port : null;
66
+ }
67
+
68
+ /**
69
+ * Port the pinned leader's Total Recall server answers on.
70
+ *
71
+ * Followers used to reach the leader on their OWN configured port, which only
72
+ * works when every node happens to use the same one. The droplet serves on 3900
73
+ * while devices default to 3000, so every follower's secrets sync failed with a
74
+ * bare "fetch failed" once a minute, forever. The pin names the port alongside
75
+ * the IP (`TR_LEADER_PORT`, or `leaderPort` in config/leader.json).
76
+ */
77
+ export function leaderPort(brain = brainDir) {
78
+ const raw = String(process.env.TR_LEADER_PORT || '').trim();
79
+ if (raw) return validPort(raw);
80
+ try {
81
+ const file = path.join(brain, 'config', 'leader.json');
82
+ if (!fs.existsSync(file)) return null;
83
+ return validPort(JSON.parse(fs.readFileSync(file, 'utf8'))?.leaderPort);
84
+ } catch {
85
+ return null;
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Record the operator pin in <brain>/config/leader.json, keeping any note.
91
+ * The env vars still win when the supervisor sets them.
92
+ */
93
+ export function setLeaderPin({ ip, port } = {}, brain = brainDir) {
94
+ const octets = String(ip || '').split('.').map(Number);
95
+ if (octets.length !== 4 || octets.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) {
96
+ throw new Error(`Leader pin needs a mesh IPv4 address, got "${ip}"`);
97
+ }
98
+ if (port !== undefined && port !== null && !validPort(port)) throw new Error(`Invalid leader port "${port}"`);
99
+ const file = path.join(brain, 'config', 'leader.json');
100
+ let current = {};
101
+ try { current = JSON.parse(fs.readFileSync(file, 'utf8')) || {}; } catch {}
102
+ const next = { ...current, leaderIp: String(ip).trim(), updated_at: new Date().toISOString() };
103
+ if (port !== undefined && port !== null) next.leaderPort = validPort(port);
104
+ fs.mkdirSync(path.dirname(file), { recursive: true });
105
+ const temporary = `${file}.${process.pid}.tmp`;
106
+ fs.writeFileSync(temporary, `${JSON.stringify(next, null, 2)}\n`);
107
+ fs.renameSync(temporary, file);
108
+ return { file, ...next };
109
+ }
110
+
111
+ function withPort(info, port) {
112
+ return port ? { ...info, port } : info;
113
+ }
114
+
63
115
  function compareNodes(a, b) {
64
116
  const ipOrder = String(a.ip || '').localeCompare(String(b.ip || ''), undefined, { numeric: true });
65
117
  return ipOrder || String(a.hostname || '').localeCompare(String(b.hostname || ''));
@@ -77,11 +129,14 @@ export async function getLeaderInfo() {
77
129
  if (pin) {
78
130
  const pinned = nodes.find((node) => node.ip === pin);
79
131
  if (!pinned) return null;
80
- return { hostname: pinned.hostname, ip: pinned.ip, strategy: 'pinned-mesh-ip' };
132
+ return withPort(
133
+ { hostname: pinned.hostname, ip: pinned.ip, strategy: 'pinned-mesh-ip' },
134
+ leaderPort() || validPort(pinned.brain_port),
135
+ );
81
136
  }
82
137
 
83
138
  const leader = [...nodes].sort(compareNodes)[0];
84
- return { hostname: leader.hostname, ip: leader.ip, strategy: 'lowest-mesh-ip' };
139
+ return withPort({ hostname: leader.hostname, ip: leader.ip, strategy: 'lowest-mesh-ip' }, validPort(leader.brain_port));
85
140
  }
86
141
 
87
142
  /**