total-recall-brain 3.41.2 → 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.
- package/bin/total-recall.mjs +19 -4
- package/frontend/dist/assets/{index-BG9NkVUy.css → index-BIQmqDKP.css} +1 -1
- package/frontend/dist/assets/{index-BtuiZqwP.js → index-CRzjQLu7.js} +7 -7
- package/frontend/dist/index.html +2 -2
- package/frontend/dist/sw.js +8 -5
- package/frontend/src/api/auth.ts +0 -3
- package/frontend/src/components/plugins/PluginPanel.tsx +7 -3
- package/frontend/src/components/plugins/card/CardEngine.tsx +1 -1
- package/frontend/src/pages/LoginPage.tsx +1 -41
- package/frontend/src/pages/PluginsPage.tsx +14 -7
- package/package.json +7 -5
- package/scaffold/.agent/skills/plugin/scripts/record.mjs +6 -3
- package/scaffold/.agent/skills/total-recall/plugins/auth/cli.mjs +197 -31
- package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +6 -6
- package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +8 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +48 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/login-projections.mjs +42 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/mesh-secrets.mjs +18 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/routes/secrets.mjs +5 -2
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-carriers.mjs +76 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-prune.mjs +37 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-sync.mjs +53 -17
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/DESIGN.md +56 -8
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/card.js +1068 -204
- package/scaffold/.agent/skills/total-recall/plugins/auth/ui/panel.js +4 -2
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/DESIGN.md +32 -0
- package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/card.js +436 -274
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +12 -12
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/DESIGN.md +16 -2
- package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/card.js +461 -242
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +12 -12
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +1 -7
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/DESIGN.md +17 -2
- package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/card.js +426 -300
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +23 -8
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +6 -2
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/DESIGN.md +32 -0
- package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/card.js +350 -283
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +23 -8
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package.json +5 -1
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/card.js +31 -19
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +23 -10
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +5 -3
- package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/grants.mjs +7 -3
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/mesh.mjs +61 -38
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/leader-election.mjs +57 -2
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh.mjs +220 -36
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/server/routes/mesh.mjs +7 -2
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +65 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/BRICK.md +59 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +534 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +139 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +2849 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +26 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +376 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +37 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/adapters/dsh.mjs +446 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +23 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +398 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +152 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +82 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +90 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +415 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/runtime.mjs +36 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/setup.mjs +35 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/tracker.mjs +133 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +233 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/DESIGN.md +32 -0
- package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +845 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/capability/BRICK.md +64 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/capability/index.mjs +250 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +344 -14
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +2917 -140
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +9 -3
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +223 -5
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/config.mjs +129 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +355 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/upstream-watcher.mjs +375 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/DESIGN.md +37 -10
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +986 -280
- package/scaffold/.agent/skills/total-recall/plugins/security/cli.mjs +7 -1
- package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +25 -10
- package/scaffold/.agent/skills/total-recall/plugins/security/package.json +6 -2
- package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/security/src/scanner.mjs +17 -3
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/DESIGN.md +38 -15
- package/scaffold/.agent/skills/total-recall/plugins/security/ui/card.js +60 -34
- package/src/cli/command.mjs +23 -18
- package/src/cli/config.mjs +3 -0
- package/src/cli/connect.mjs +12 -116
- package/src/cli/daemon.mjs +23 -1
- package/src/cli/harness.mjs +0 -1
- package/src/cli/plugin/copies.mjs +8 -5
- package/src/cli/plugin/create.mjs +56 -3
- package/src/cli/plugin/index.mjs +40 -5
- package/src/cli/plugin/monetization.mjs +232 -12
- package/src/cli/preflight.mjs +161 -0
- package/src/cli/rebuild.mjs +22 -0
- package/src/cli/reset-password.mjs +3 -2
- package/src/cli/skill.mjs +8 -2
- package/src/cli/status.mjs +25 -7
- package/src/core/automatic-brain-updates.mjs +9 -3
- package/src/core/brain-updates.mjs +63 -5
- package/src/core/cli-commands.mjs +2 -0
- package/src/core/context-compiler.mjs +8 -1
- package/src/core/default-plugins.mjs +1 -1
- package/src/core/global-brain-updates.mjs +21 -1
- package/src/core/mesh-brain-updates.mjs +5 -2
- package/src/core/meta-harness.mjs +32 -79
- package/src/core/plugin-dependencies.mjs +6 -1
- package/src/core/plugin-runner.mjs +6 -1
- package/src/core/plugin-store.mjs +67 -1
- package/src/core/plugin-updates.mjs +1 -1
- package/src/core/portable-skills-maintenance.mjs +19 -0
- package/src/core/preflight-gate.mjs +77 -0
- package/src/core/preflight-receipt.mjs +123 -0
- package/src/core/required-plugins.mjs +7 -0
- package/src/core/runtime.mjs +0 -3
- package/src/core/skills-registry.mjs +33 -3
- package/src/core/surface.mjs +38 -1
- package/src/server/auth.mjs +41 -1
- package/src/server/memory-app.mjs +2 -1
- package/src/server/routes/auth.mjs +25 -1
- package/src/server/routes/plugins.mjs +17 -4
|
@@ -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.
|
|
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.
|
|
651
|
-
"resolved": "https://registry.npmjs.org/@gregiteen/ssss-cli/-/ssss-cli-0.
|
|
652
|
-
"integrity": "sha512-
|
|
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.
|
|
3564
|
-
"resolved": "https://registry.npmjs.org/total-recall-brain/-/total-recall-brain-3.41.
|
|
3565
|
-
"integrity": "sha512-
|
|
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.
|
|
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
|
}
|
|
@@ -410,14 +410,26 @@ export class GitSentinelCard extends HTMLElement {
|
|
|
410
410
|
--face-ok: #047857;
|
|
411
411
|
--face-warn: #b45309;
|
|
412
412
|
--face-danger: #b91c1c;
|
|
413
|
+
--face-text-default: #111827;
|
|
414
|
+
--face-muted-default: #6b7280;
|
|
415
|
+
--face-line-default: #e5e7eb;
|
|
416
|
+
--face-surface-default: #f9fafb;
|
|
417
|
+
--face-accent-default: #b45309;
|
|
418
|
+
--face-dark-ok: #34d399;
|
|
419
|
+
--face-dark-warn: #fbbf24;
|
|
420
|
+
--face-dark-danger: #f87171;
|
|
421
|
+
--face-dark-text: #e8eaf0;
|
|
422
|
+
--face-dark-muted: #a2a8b8;
|
|
423
|
+
--face-dark-line: #2a3040;
|
|
424
|
+
--face-dark-surface: #171a23;
|
|
413
425
|
--face-radius: 6px;
|
|
414
426
|
--face-motion: 140ms ease-out;
|
|
415
427
|
--face-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
416
|
-
--face-text: var(--text-primary,
|
|
417
|
-
--face-muted: var(--text-secondary,
|
|
418
|
-
--face-line: var(--border,
|
|
419
|
-
--face-surface: var(--bg-secondary,
|
|
420
|
-
--face-accent: var(--card-color,
|
|
428
|
+
--face-text: var(--text-primary, var(--face-text-default));
|
|
429
|
+
--face-muted: var(--text-secondary, var(--face-muted-default));
|
|
430
|
+
--face-line: var(--border, var(--face-line-default));
|
|
431
|
+
--face-surface: var(--bg-secondary, var(--face-surface-default));
|
|
432
|
+
--face-accent: var(--card-color, var(--face-accent-default));
|
|
421
433
|
font-family: 'Space Grotesk', var(--font-sans, system-ui, sans-serif);
|
|
422
434
|
color: var(--face-text);
|
|
423
435
|
}
|
|
@@ -425,25 +437,25 @@ export class GitSentinelCard extends HTMLElement {
|
|
|
425
437
|
verdict line, so they are retuned whenever the host is dark. */
|
|
426
438
|
@media (prefers-color-scheme: dark) {
|
|
427
439
|
:host {
|
|
428
|
-
--face-ok:
|
|
429
|
-
--face-warn:
|
|
430
|
-
--face-danger:
|
|
431
|
-
--face-text: var(--text-primary,
|
|
432
|
-
--face-muted: var(--text-secondary,
|
|
433
|
-
--face-line: var(--border,
|
|
434
|
-
--face-surface: var(--bg-secondary,
|
|
440
|
+
--face-ok: var(--face-dark-ok);
|
|
441
|
+
--face-warn: var(--face-dark-warn);
|
|
442
|
+
--face-danger: var(--face-dark-danger);
|
|
443
|
+
--face-text: var(--text-primary, var(--face-dark-text));
|
|
444
|
+
--face-muted: var(--text-secondary, var(--face-dark-muted));
|
|
445
|
+
--face-line: var(--border, var(--face-dark-line));
|
|
446
|
+
--face-surface: var(--bg-secondary, var(--face-dark-surface));
|
|
435
447
|
}
|
|
436
448
|
}
|
|
437
449
|
:host-context([data-theme="dark"]),
|
|
438
450
|
:host-context(.theme-dark),
|
|
439
451
|
:host-context(.dark) {
|
|
440
|
-
--face-ok:
|
|
441
|
-
--face-warn:
|
|
442
|
-
--face-danger:
|
|
443
|
-
--face-text: var(--text-primary,
|
|
444
|
-
--face-muted: var(--text-secondary,
|
|
445
|
-
--face-line: var(--border,
|
|
446
|
-
--face-surface: var(--bg-secondary,
|
|
452
|
+
--face-ok: var(--face-dark-ok);
|
|
453
|
+
--face-warn: var(--face-dark-warn);
|
|
454
|
+
--face-danger: var(--face-dark-danger);
|
|
455
|
+
--face-text: var(--text-primary, var(--face-dark-text));
|
|
456
|
+
--face-muted: var(--text-secondary, var(--face-dark-muted));
|
|
457
|
+
--face-line: var(--border, var(--face-dark-line));
|
|
458
|
+
--face-surface: var(--bg-secondary, var(--face-dark-surface));
|
|
447
459
|
}
|
|
448
460
|
* { box-sizing: border-box; }
|
|
449
461
|
.top { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 12px; }
|
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tr-plugin-mesh",
|
|
3
|
-
"version": "0.2.
|
|
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.
|
|
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.
|
|
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.
|
|
901
|
-
"resolved": "https://registry.npmjs.org/@gregiteen/ssss-cli/-/ssss-cli-0.
|
|
902
|
-
"integrity": "sha512-
|
|
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.
|
|
4220
|
-
"resolved": "https://registry.npmjs.org/total-recall-brain/-/total-recall-brain-3.41.
|
|
4221
|
-
"integrity": "sha512-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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 (
|
|
386
|
-
fail(
|
|
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
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
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(
|
|
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
|
-
|
|
549
|
-
|
|
550
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
621
|
-
|
|
622
|
-
|
|
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
|
|
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
|
|
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
|
/**
|