total-recall-brain 3.41.1 → 3.41.3
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 +9 -3
- package/frontend/dist/assets/{index-BG9NkVUy.css → index-BIQmqDKP.css} +1 -1
- package/frontend/dist/assets/{index-BtuiZqwP.js → index-DFenQQEQ.js} +74 -79
- 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 +2 -2
- package/scaffold/.agent/skills/plugin/scripts/record.mjs +6 -3
- package/scaffold/.agent/skills/total-recall/plugins/auth/README.md +10 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/cli.mjs +197 -31
- package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +1456 -12
- package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +4 -4
- package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +8 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +210 -27
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/provider-catalog.mjs +10 -1
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/rotation-capability.mjs +15 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-env-export.mjs +78 -6
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-merge.mjs +131 -0
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-remote-deploy.mjs +111 -2
- package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-store.mjs +26 -5
- 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 +4 -4
- 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 +4 -4
- 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 +4 -4
- 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 +4 -4
- package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/card.js +31 -19
- package/scaffold/.agent/skills/total-recall/plugins/mesh/README.md +10 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/{src/core/headscale-server.mjs → capability/control-server.mjs} +5 -2
- package/scaffold/.agent/skills/total-recall/plugins/mesh/capability/headscale-api.mjs +192 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/capability/index.mjs +46 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/cli.mjs +9 -1
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +23 -22
- package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +6 -3
- package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +242 -8
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/mesh.mjs +14 -9
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/server.mjs +1 -1
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/credentials.mjs +4 -2
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/headscale-client.mjs +34 -181
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh-auth.mjs +2 -2
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/presence.mjs +5 -5
- package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/screen-view.mjs +78 -0
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/DESIGN.md +211 -12
- package/scaffold/.agent/skills/total-recall/plugins/mesh/ui/card.js +323 -388
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/README.md +12 -1
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +7 -4
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +6 -6
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +3 -2
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +34 -1
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/bootstrap.mjs +99 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/templates/openwiki/README.md +10 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/templates/openwiki/architecture.md +5 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/templates/openwiki/ide.md +10 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/templates/openwiki/memory.md +11 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/templates/openwiki/secrets.md +7 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/templates/openwiki/skills.md +7 -0
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/DESIGN.md +16 -2
- package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +380 -298
- package/scaffold/.agent/skills/total-recall/plugins/security/cli.mjs +7 -1
- package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +6 -6
- package/scaffold/.agent/skills/total-recall/plugins/security/package.json +1 -1
- 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/scaffold/.agent/skills/total-recall/references/auth.md +2 -0
- package/src/cli/command.mjs +23 -18
- package/src/cli/daemon.mjs +23 -1
- package/src/cli/plugin/copies.mjs +8 -5
- package/src/cli/plugin/index.mjs +25 -2
- 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/global-brain-updates.mjs +21 -1
- package/src/core/mesh-brain-updates.mjs +5 -2
- package/src/core/meta-harness.mjs +16 -41
- 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/required-plugins.mjs +7 -0
- package/src/core/skills-registry.mjs +33 -3
- 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
|
@@ -907,7 +907,13 @@ export async function run(argv = []) {
|
|
|
907
907
|
}
|
|
908
908
|
if (IMPLEMENTED[sub]) {
|
|
909
909
|
try { return await IMPLEMENTED[sub](args.slice(1)); }
|
|
910
|
-
catch
|
|
910
|
+
catch (error) {
|
|
911
|
+
// Say what failed. The generic text hid every cause, including a scan that
|
|
912
|
+
// simply hit its coverage limit. Messages are paths and limits, never matched content.
|
|
913
|
+
console.log(JSON.stringify({ status: 'failed', operation: sub, error: `Security operation failed: ${String(error?.message || error).slice(0, 300)}. No success was established.` }));
|
|
914
|
+
process.exitCode = 1;
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
911
917
|
}
|
|
912
918
|
console.error(`Security: "${sub}" is not implemented.`);
|
|
913
919
|
process.exitCode = 2;
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tr-plugin-security",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "tr-plugin-security",
|
|
9
|
-
"version": "0.3.
|
|
9
|
+
"version": "0.3.3",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"devDependencies": {
|
|
12
12
|
"jsdom": "^26.1.0",
|
|
13
|
-
"total-recall-brain": "
|
|
13
|
+
"total-recall-brain": "3.41.2"
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"node_modules/@asamuzakjp/css-color": {
|
|
@@ -2916,9 +2916,9 @@
|
|
|
2916
2916
|
}
|
|
2917
2917
|
},
|
|
2918
2918
|
"node_modules/total-recall-brain": {
|
|
2919
|
-
"version": "3.
|
|
2920
|
-
"resolved": "https://registry.npmjs.org/total-recall-brain/-/total-recall-brain-3.
|
|
2921
|
-
"integrity": "sha512-
|
|
2919
|
+
"version": "3.41.2",
|
|
2920
|
+
"resolved": "https://registry.npmjs.org/total-recall-brain/-/total-recall-brain-3.41.2.tgz",
|
|
2921
|
+
"integrity": "sha512-bwJFAFAyvk9ysf+5zhBzMxrKus1Hj1AygUWdBNt+vQbM2sfSEf6701sa30KwfGyU8+cMuN6YNT5Mn63mFZIi/w==",
|
|
2922
2922
|
"dev": true,
|
|
2923
2923
|
"license": "MIT",
|
|
2924
2924
|
"workspaces": [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tr-plugin-security",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Standalone security suite for Total Recall: static analysis, file and permission hardening schemes, AES-256-GCM encryption tools, and egress audit controls",
|
|
6
6
|
"scripts": {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://github.com/gregiteen/total-recall/blob/main/metadata.plugin.schema.json",
|
|
3
3
|
"id": "security",
|
|
4
4
|
"name": "Security Assessments",
|
|
5
|
-
"version": "0.3.
|
|
5
|
+
"version": "0.3.3",
|
|
6
6
|
"description": "Machine and device security assessments for macOS, Linux, Windows, Android, Chromebook, iPhone and iPad, with explicit measured and manual coverage.",
|
|
7
7
|
"license": "MIT",
|
|
8
8
|
"branding": {
|
|
@@ -5,8 +5,12 @@ import path from 'node:path';
|
|
|
5
5
|
export function scanSource(targetPath) {
|
|
6
6
|
const root = fs.realpathSync(path.resolve(targetPath));
|
|
7
7
|
const findings = [];
|
|
8
|
-
|
|
8
|
+
// Generated brain data (indexes, sessions, logs) is not source; scanning it
|
|
9
|
+
// only hit the size limit and aborted the whole scan.
|
|
10
|
+
const skipped = new Set(['node_modules', '.git', 'dist', 'coverage', 'memory-vault', 'memory-derived', 'sessions', '.cache', '.venv', '__pycache__']);
|
|
11
|
+
const MAX_FILES = 20000, MAX_BYTES = 2097152;
|
|
9
12
|
let count = 0;
|
|
13
|
+
let limited = false;
|
|
10
14
|
function visit(file) {
|
|
11
15
|
const stat = fs.lstatSync(file);
|
|
12
16
|
if (stat.isSymbolicLink()) {
|
|
@@ -14,11 +18,21 @@ export function scanSource(targetPath) {
|
|
|
14
18
|
return;
|
|
15
19
|
}
|
|
16
20
|
if (stat.isDirectory()) {
|
|
17
|
-
for (const name of fs.readdirSync(file)) if (!skipped.has(name)) visit(path.join(file, name));
|
|
21
|
+
for (const name of fs.readdirSync(file)) { if (limited) return; if (!skipped.has(name)) visit(path.join(file, name)); }
|
|
18
22
|
return;
|
|
19
23
|
}
|
|
20
24
|
if (!stat.isFile() || !/\.(?:[cm]?js|tsx?|py|sh|bash|ps1|json|env|ya?ml)$|(?:^|\/)\.env(?:\.|$)/.test(file)) return;
|
|
21
|
-
|
|
25
|
+
// Report incomplete coverage as a finding instead of aborting: an aborted
|
|
26
|
+
// scan returned nothing, which callers could not tell apart from a failure.
|
|
27
|
+
if (stat.size > MAX_BYTES) {
|
|
28
|
+
findings.push({ file: path.relative(root, file), line: 0, rule: 'oversized-file-unscanned', severity: 'HIGH', detail: `Larger than ${MAX_BYTES} bytes; not inspected, coverage incomplete` });
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (++count > MAX_FILES) {
|
|
32
|
+
limited = true;
|
|
33
|
+
findings.push({ file: '.', line: 0, rule: 'coverage-limit-reached', severity: 'HIGH', detail: `Stopped after ${MAX_FILES} files; narrow the target, coverage incomplete` });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
22
36
|
const testFile = /(?:^|\/)(?:test|tests|fixtures)(?:\/|$)|\.(?:test|spec)\./.test(file);
|
|
23
37
|
fs.readFileSync(file, 'utf8').split('\n').forEach((line, index) => {
|
|
24
38
|
const rules = [];
|
|
@@ -1,18 +1,41 @@
|
|
|
1
|
-
# DESIGN.md — Tin Foil
|
|
1
|
+
# DESIGN.md — Security Plugin (Tin Foil)
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
3
|
+
## Brand Identity & Aesthetic
|
|
4
|
+
- **Brand Name**: Tin Foil
|
|
5
|
+
- **Wordmark**: `[TIN//FOIL]`
|
|
6
|
+
- **Theme**: Crimson and Obsidian Host Hardening Console
|
|
7
|
+
- **Tone**: Deterministic, vigilant, fail-closed, precise
|
|
8
|
+
|
|
9
|
+
## Design Tokens
|
|
10
|
+
|
|
11
|
+
The card face defines and consumes these custom properties:
|
|
12
|
+
|
|
13
|
+
```css
|
|
14
|
+
:host {
|
|
15
|
+
--face-bg: #180d11;
|
|
16
|
+
--face-bg-subtle: #251017;
|
|
17
|
+
--face-bg-surface: #11070a;
|
|
18
|
+
--face-bg-table: #0d0508;
|
|
19
|
+
--face-bg-code: #0a0406;
|
|
20
|
+
--face-accent: #f43f5e;
|
|
21
|
+
--face-accent-hover: rgba(244, 63, 94, 0.22);
|
|
22
|
+
--face-accent-glow: rgba(244, 63, 94, 0.35);
|
|
23
|
+
--face-border-subtle: rgba(244, 63, 94, 0.28);
|
|
24
|
+
--face-border-faint: rgba(244, 63, 94, 0.2);
|
|
25
|
+
--face-border-cell: rgba(244, 63, 94, 0.15);
|
|
26
|
+
--face-border-alert: rgba(244, 63, 94, 0.25);
|
|
27
|
+
--face-text: #f1f5f9;
|
|
28
|
+
--face-text-heading: #fecdd3;
|
|
29
|
+
--face-text-muted: #fda4af;
|
|
30
|
+
--face-text-cell: #f8fafc;
|
|
31
|
+
--face-text-desc: #cbd5e1;
|
|
32
|
+
--face-danger: #fb7185;
|
|
33
|
+
--face-warning: #fca5a5;
|
|
34
|
+
}
|
|
13
35
|
```
|
|
14
36
|
|
|
15
|
-
##
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
37
|
+
## System Integration
|
|
38
|
+
- The face binds `--text-primary` with fallback to `var(--face-text)`.
|
|
39
|
+
- The face binds `--border` with fallback to `var(--face-accent-glow)`.
|
|
40
|
+
- All clickable elements enforce a minimum height of 44px for touch ergonomics.
|
|
41
|
+
- Reduced motion styles suppress animation when requested by system settings.
|
|
@@ -5,11 +5,31 @@ export class SecuritySecurityCard extends HTMLElement {
|
|
|
5
5
|
root.innerHTML = `
|
|
6
6
|
<style>
|
|
7
7
|
:host {
|
|
8
|
+
--face-bg: #180d11;
|
|
9
|
+
--face-bg-subtle: #251017;
|
|
10
|
+
--face-bg-surface: #11070a;
|
|
11
|
+
--face-bg-table: #0d0508;
|
|
12
|
+
--face-bg-code: #0a0406;
|
|
13
|
+
--face-accent: #f43f5e;
|
|
14
|
+
--face-accent-hover: rgba(244, 63, 94, 0.22);
|
|
15
|
+
--face-accent-glow: rgba(244, 63, 94, 0.35);
|
|
16
|
+
--face-border-subtle: rgba(244, 63, 94, 0.28);
|
|
17
|
+
--face-border-faint: rgba(244, 63, 94, 0.2);
|
|
18
|
+
--face-border-cell: rgba(244, 63, 94, 0.15);
|
|
19
|
+
--face-border-alert: rgba(244, 63, 94, 0.25);
|
|
20
|
+
--face-text: #f1f5f9;
|
|
21
|
+
--face-text-heading: #fecdd3;
|
|
22
|
+
--face-text-muted: #fda4af;
|
|
23
|
+
--face-text-cell: #f8fafc;
|
|
24
|
+
--face-text-desc: #cbd5e1;
|
|
25
|
+
--face-danger: #fb7185;
|
|
26
|
+
--face-warning: #fca5a5;
|
|
27
|
+
|
|
8
28
|
display: block;
|
|
9
29
|
font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
10
|
-
color:
|
|
11
|
-
background:
|
|
12
|
-
border: 1px solid
|
|
30
|
+
color: var(--text-primary, var(--face-text));
|
|
31
|
+
background: var(--face-bg);
|
|
32
|
+
border: 1px solid var(--border, var(--face-accent-glow));
|
|
13
33
|
border-radius: 10px;
|
|
14
34
|
padding: 18px;
|
|
15
35
|
box-shadow: 0 10px 28px rgba(0, 0, 0, 0.45);
|
|
@@ -27,7 +47,7 @@ export class SecuritySecurityCard extends HTMLElement {
|
|
|
27
47
|
.brand-icon {
|
|
28
48
|
width: 32px;
|
|
29
49
|
height: 38px;
|
|
30
|
-
color:
|
|
50
|
+
color: var(--face-accent);
|
|
31
51
|
flex-shrink: 0;
|
|
32
52
|
}
|
|
33
53
|
|
|
@@ -43,12 +63,12 @@ export class SecuritySecurityCard extends HTMLElement {
|
|
|
43
63
|
font-weight: 700;
|
|
44
64
|
letter-spacing: 0.03em;
|
|
45
65
|
text-transform: uppercase;
|
|
46
|
-
color:
|
|
66
|
+
color: var(--face-text-heading);
|
|
47
67
|
}
|
|
48
68
|
|
|
49
69
|
p.desc {
|
|
50
70
|
margin: 0 0 14px 0;
|
|
51
|
-
color:
|
|
71
|
+
color: var(--face-text-muted);
|
|
52
72
|
font-size: 12px;
|
|
53
73
|
line-height: 1.5;
|
|
54
74
|
}
|
|
@@ -66,11 +86,11 @@ export class SecuritySecurityCard extends HTMLElement {
|
|
|
66
86
|
|
|
67
87
|
button, input, select {
|
|
68
88
|
min-height: 44px;
|
|
69
|
-
border: 1px solid
|
|
89
|
+
border: 1px solid var(--face-border-subtle);
|
|
70
90
|
border-radius: 6px;
|
|
71
91
|
padding: 8px 12px;
|
|
72
|
-
background:
|
|
73
|
-
color:
|
|
92
|
+
background: var(--face-bg-subtle);
|
|
93
|
+
color: var(--face-text);
|
|
74
94
|
font-size: 11px;
|
|
75
95
|
font-weight: 600;
|
|
76
96
|
letter-spacing: 0.02em;
|
|
@@ -84,8 +104,8 @@ export class SecuritySecurityCard extends HTMLElement {
|
|
|
84
104
|
}
|
|
85
105
|
|
|
86
106
|
button:hover {
|
|
87
|
-
background:
|
|
88
|
-
border-color:
|
|
107
|
+
background: var(--face-accent-hover);
|
|
108
|
+
border-color: var(--face-accent);
|
|
89
109
|
}
|
|
90
110
|
|
|
91
111
|
button:active {
|
|
@@ -101,17 +121,17 @@ export class SecuritySecurityCard extends HTMLElement {
|
|
|
101
121
|
min-width: 0;
|
|
102
122
|
flex: 1;
|
|
103
123
|
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
104
|
-
background:
|
|
124
|
+
background: var(--face-bg-surface);
|
|
105
125
|
}
|
|
106
126
|
|
|
107
127
|
input:focus, select:focus {
|
|
108
|
-
border-color:
|
|
128
|
+
border-color: var(--face-accent);
|
|
109
129
|
outline: none;
|
|
110
|
-
box-shadow: 0 0 0 1px
|
|
130
|
+
box-shadow: 0 0 0 1px var(--face-accent-glow);
|
|
111
131
|
}
|
|
112
132
|
|
|
113
133
|
select {
|
|
114
|
-
background:
|
|
134
|
+
background: var(--face-bg-surface);
|
|
115
135
|
cursor: pointer;
|
|
116
136
|
}
|
|
117
137
|
|
|
@@ -120,66 +140,66 @@ export class SecuritySecurityCard extends HTMLElement {
|
|
|
120
140
|
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
121
141
|
font-size: 11px;
|
|
122
142
|
font-weight: 700;
|
|
123
|
-
color:
|
|
124
|
-
background:
|
|
143
|
+
color: var(--face-danger);
|
|
144
|
+
background: var(--face-bg-surface);
|
|
125
145
|
padding: 6px 10px;
|
|
126
146
|
border-radius: 4px;
|
|
127
|
-
border: 1px solid
|
|
147
|
+
border: 1px solid var(--face-border-faint);
|
|
128
148
|
}
|
|
129
149
|
|
|
130
150
|
.scroll {
|
|
131
151
|
overflow-x: auto;
|
|
132
152
|
margin-bottom: 10px;
|
|
133
153
|
border-radius: 6px;
|
|
134
|
-
border: 1px solid
|
|
154
|
+
border: 1px solid var(--face-border-faint);
|
|
135
155
|
}
|
|
136
156
|
|
|
137
157
|
table {
|
|
138
158
|
width: 100%;
|
|
139
159
|
border-collapse: collapse;
|
|
140
160
|
font-size: 11px;
|
|
141
|
-
background:
|
|
161
|
+
background: var(--face-bg-table);
|
|
142
162
|
}
|
|
143
163
|
|
|
144
164
|
th {
|
|
145
|
-
background:
|
|
146
|
-
color:
|
|
165
|
+
background: var(--face-bg-subtle);
|
|
166
|
+
color: var(--face-text-heading);
|
|
147
167
|
font-weight: 700;
|
|
148
168
|
text-transform: uppercase;
|
|
149
169
|
letter-spacing: 0.04em;
|
|
150
170
|
padding: 8px 10px;
|
|
151
|
-
border-bottom: 1px solid
|
|
171
|
+
border-bottom: 1px solid var(--face-border-subtle);
|
|
152
172
|
text-align: left;
|
|
153
173
|
}
|
|
154
174
|
|
|
155
175
|
td {
|
|
156
176
|
padding: 8px 10px;
|
|
157
177
|
text-align: left;
|
|
158
|
-
border-bottom: 1px solid
|
|
178
|
+
border-bottom: 1px solid var(--face-border-cell);
|
|
159
179
|
vertical-align: top;
|
|
160
|
-
color:
|
|
180
|
+
color: var(--face-text-cell);
|
|
161
181
|
}
|
|
162
182
|
|
|
163
183
|
td:first-child {
|
|
164
184
|
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
|
165
185
|
font-weight: 600;
|
|
166
|
-
color:
|
|
186
|
+
color: var(--face-text-muted);
|
|
167
187
|
}
|
|
168
188
|
|
|
169
189
|
td:last-child {
|
|
170
190
|
line-height: 1.45;
|
|
171
|
-
color:
|
|
191
|
+
color: var(--face-text-desc);
|
|
172
192
|
}
|
|
173
193
|
|
|
174
194
|
pre {
|
|
175
|
-
background:
|
|
176
|
-
border: 1px solid
|
|
195
|
+
background: var(--face-bg-code);
|
|
196
|
+
border: 1px solid var(--face-border-faint);
|
|
177
197
|
border-radius: 6px;
|
|
178
198
|
padding: 8px 10px;
|
|
179
199
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
180
200
|
font-size: 10.5px;
|
|
181
201
|
line-height: 1.45;
|
|
182
|
-
color:
|
|
202
|
+
color: var(--face-danger);
|
|
183
203
|
white-space: pre-wrap;
|
|
184
204
|
overflow-wrap: anywhere;
|
|
185
205
|
max-height: 160px;
|
|
@@ -188,8 +208,8 @@ export class SecuritySecurityCard extends HTMLElement {
|
|
|
188
208
|
}
|
|
189
209
|
|
|
190
210
|
#device-review {
|
|
191
|
-
background:
|
|
192
|
-
border: 1px solid
|
|
211
|
+
background: var(--face-bg-surface);
|
|
212
|
+
border: 1px solid var(--face-border-alert);
|
|
193
213
|
border-radius: 6px;
|
|
194
214
|
padding: 10px;
|
|
195
215
|
margin-top: 10px;
|
|
@@ -198,7 +218,7 @@ export class SecuritySecurityCard extends HTMLElement {
|
|
|
198
218
|
#device-review p {
|
|
199
219
|
margin: 0 0 8px 0;
|
|
200
220
|
font-size: 11px;
|
|
201
|
-
color:
|
|
221
|
+
color: var(--face-warning);
|
|
202
222
|
}
|
|
203
223
|
|
|
204
224
|
#device-checks label {
|
|
@@ -209,9 +229,15 @@ export class SecuritySecurityCard extends HTMLElement {
|
|
|
209
229
|
#device-checks label p {
|
|
210
230
|
margin: 0 0 4px 0;
|
|
211
231
|
font-size: 11px;
|
|
212
|
-
color:
|
|
232
|
+
color: var(--face-text-heading);
|
|
213
233
|
font-weight: 600;
|
|
214
234
|
}
|
|
235
|
+
|
|
236
|
+
@media (prefers-reduced-motion: reduce) {
|
|
237
|
+
button, input, select {
|
|
238
|
+
transition: none !important;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
215
241
|
</style>
|
|
216
242
|
|
|
217
243
|
<header>
|
|
@@ -8,6 +8,8 @@ There is one secrets store per host: the global auth store. The CLI never select
|
|
|
8
8
|
|
|
9
9
|
For provider key setup, verify against an authenticated provider operation before saving; format-only checks and timeouts are not verification. Require HTTPS except explicit loopback development, reject redirects containing credentials, and return safe errors. Rotation follows verify → store/project → deploy/verify → revoke, respecting the provider's supported recipe and user authorization. Manual provider steps remain manual. Exported environment files are deployment projections, not an alternate credential authority.
|
|
10
10
|
|
|
11
|
+
Every secret operation goes through `total-recall auth secret`; never read, compare or copy credential values with shell tools. Check a copied credential with `fingerprint --from-clipboard --expect-prefix <p> --not <KEY>` before `rotate <KEY> --from-clipboard`. After `remote deploy <target> --keys … [--path <repo>]`, confirm with `remote verify <target> --keys …` (fingerprint match, no values). A process manager can keep a captured environment that overrides the deployed file (pm2 does): move the keys the app uses into the store with `remote import-pm2 <target> --app <name> --keys … --apply`, then restart the app with a clean environment so it reads only the projection. Stripe webhook signing secrets are issued by Stripe; roll them in the dashboard, never mint them.
|
|
12
|
+
|
|
11
13
|
Current migration remains in progress until package/bootstrap tests, all-plugin bypass checks, owner suites and end-to-end UI/API checks pass. Manifest `auth.plugin` declarations alone do not certify any of these properties.
|
|
12
14
|
|
|
13
15
|
## Sessions, passwords and legacy data
|
package/src/cli/command.mjs
CHANGED
|
@@ -222,6 +222,27 @@ function headerTags(description, risk) {
|
|
|
222
222
|
return (description ? `// @description ${one(description)}\n` : '') + (risk ? `// @risk ${risk}\n` : '');
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
+
/**
|
|
226
|
+
* Build a custom command's file from author-supplied source.
|
|
227
|
+
*
|
|
228
|
+
* The source may be a BODY for run(argv) or a complete module. Blindly wrapping it failed two ways:
|
|
229
|
+
* a module produced "Unexpected identifier 'fs'", and a body that declared its own `args` produced
|
|
230
|
+
* "Identifier 'args' has already been declared". Detect the shape instead of assuming it, and declare
|
|
231
|
+
* `args` only when the body does not. The default export delegates so the body is written once.
|
|
232
|
+
*/
|
|
233
|
+
function buildCommandFile(name, description, risk, code) {
|
|
234
|
+
const source = String(code ?? '');
|
|
235
|
+
const header = `// Auto-generated custom CLI command: ${name}\n${headerTags(description, risk)}`;
|
|
236
|
+
if (/^\s*(?:import|export)\s/m.test(source)) {
|
|
237
|
+
return `${header}// Supplied as a complete module; used as written.\n${source}\n`;
|
|
238
|
+
}
|
|
239
|
+
const declaresArgs = /(?:^|\n)\s*(?:const|let|var)\s+args\b/.test(source);
|
|
240
|
+
const argsLine = declaresArgs ? '' : 'const args = Array.isArray(argv) ? argv.slice(3) : [];\n';
|
|
241
|
+
return `${header}// Your source is the BODY of run(argv). \`argv\` is in scope${declaresArgs ? '' : ', and so is `args` (argv minus the command name)'}.\n`
|
|
242
|
+
+ "// Use `await import('node:module')`; write no import/export statements here.\n"
|
|
243
|
+
+ `export async function run(argv = []) {\n${argsLine}${source}\n}\nexport default function (argv = []) {\n return run(argv);\n}\n`;
|
|
244
|
+
}
|
|
245
|
+
|
|
225
246
|
function existingTag(file, key) {
|
|
226
247
|
try { return fs.readFileSync(file, 'utf8').match(new RegExp(`^// @${key} (.+)$`, 'm'))?.[1] || undefined; } catch { return undefined; }
|
|
227
248
|
}
|
|
@@ -373,15 +394,7 @@ export default async function commandCmd(rawArgs = []) {
|
|
|
373
394
|
}
|
|
374
395
|
|
|
375
396
|
// Wrap code in an exported run/default function for composable CLI execution
|
|
376
|
-
const fileContent =
|
|
377
|
-
${headerTags(description, risk)}export async function run(argv = []) {
|
|
378
|
-
const args = Array.isArray(argv) ? argv.slice(3) : [];
|
|
379
|
-
${code}
|
|
380
|
-
}
|
|
381
|
-
export default async function (args = []) {
|
|
382
|
-
${code}
|
|
383
|
-
}
|
|
384
|
-
`;
|
|
397
|
+
const fileContent = buildCommandFile(name, description, risk, code);
|
|
385
398
|
|
|
386
399
|
fs.writeFileSync(commandPath, fileContent, 'utf8');
|
|
387
400
|
console.log(`\x1b[32m✔ Successfully created custom command: \x1b[1mnpx total-recall ${name}\x1b[0m`);
|
|
@@ -413,15 +426,7 @@ export default async function (args = []) {
|
|
|
413
426
|
// Keep the declared description/risk unless the update replaces them.
|
|
414
427
|
const keptDescription = description ?? existingTag(commandPath, 'description');
|
|
415
428
|
const keptRisk = risk ?? existingTag(commandPath, 'risk');
|
|
416
|
-
const fileContent =
|
|
417
|
-
${headerTags(keptDescription, keptRisk)}export async function run(argv = []) {
|
|
418
|
-
const args = Array.isArray(argv) ? argv.slice(3) : [];
|
|
419
|
-
${code}
|
|
420
|
-
}
|
|
421
|
-
export default async function (args = []) {
|
|
422
|
-
${code}
|
|
423
|
-
}
|
|
424
|
-
`;
|
|
429
|
+
const fileContent = buildCommandFile(name, keptDescription, keptRisk, code);
|
|
425
430
|
|
|
426
431
|
fs.writeFileSync(commandPath, fileContent, 'utf8');
|
|
427
432
|
console.log(`\x1b[32m✔ Successfully updated custom command: \x1b[1m${name}\x1b[0m`);
|
package/src/cli/daemon.mjs
CHANGED
|
@@ -218,7 +218,29 @@ export default async function daemon(args) {
|
|
|
218
218
|
|
|
219
219
|
switch (subcommand) {
|
|
220
220
|
case 'ensure': {
|
|
221
|
-
if(
|
|
221
|
+
if(useLaunchd){
|
|
222
|
+
// macOS: the launchd agent owns the daemon. Load it if needed, restart it
|
|
223
|
+
// on --restart (or start it when nothing runs), then verify the process
|
|
224
|
+
// launchd reports is the one holding the brain lock.
|
|
225
|
+
const label='com.totalrecall.daemon',target=`gui/${process.getuid()}/${label}`;
|
|
226
|
+
const launchdPid=()=>{const r=spawnSync('launchctl',['list',label],{encoding:'utf8'});const m=r.status===0&&/"PID"\s*=\s*(\d+)/.exec(r.stdout||'');return m?Number(m[1]):0;};
|
|
227
|
+
if(spawnSync('launchctl',['list',label],{stdio:'ignore'}).status!==0){
|
|
228
|
+
const boot=spawnSync('launchctl',['bootstrap',`gui/${process.getuid()}`,launchAgentPlist(label)],{encoding:'utf8'});
|
|
229
|
+
if(boot.status!==0)throw new Error(`Cannot load ${label}: ${(boot.stderr||'').trim()}`);
|
|
230
|
+
}
|
|
231
|
+
const owner=readPid(),managedPid=launchdPid();
|
|
232
|
+
if(owner && owner!==managedPid && !await stopDaemon())throw new Error('Cannot stop verified detached daemon');
|
|
233
|
+
if(args.includes('--restart') || !managedPid || (owner && owner!==managedPid)){
|
|
234
|
+
const kick=spawnSync('launchctl',['kickstart','-k',target],{encoding:'utf8'});
|
|
235
|
+
if(kick.status!==0)throw new Error(`launchctl kickstart failed: ${(kick.stderr||'').trim()}`);
|
|
236
|
+
}
|
|
237
|
+
await new Promise(resolve=>setTimeout(resolve,3000));
|
|
238
|
+
const pid=readPid(),current=launchdPid();
|
|
239
|
+
if(!pid || pid!==current)throw new Error('Managed daemon did not retain ownership of the brain lock');
|
|
240
|
+
console.log(JSON.stringify({ok:true,manager:'launchd',pid,enabled:true}));
|
|
241
|
+
break;
|
|
242
|
+
}
|
|
243
|
+
if(!useSystemd)throw new Error('Managed daemon ensure needs systemd or a launchd agent on the daemon host');
|
|
222
244
|
// A detached worker can hold the same brain lock while systemd reports a
|
|
223
245
|
// successful short-lived start. Stop that verified owner before takeover.
|
|
224
246
|
const managed=spawnSync('systemctl',['show','total-recall-daemon','--property=MainPID','--value'],{encoding:'utf8'});
|
|
@@ -17,8 +17,9 @@ export function pluginCopyRows(projectRoot = process.cwd(), { sharedDir = global
|
|
|
17
17
|
root: projectRoot,
|
|
18
18
|
sharedDir,
|
|
19
19
|
count: audit.count,
|
|
20
|
-
removable: audit.copies.filter(copy => copy.edited === false).map(copy => copy.id),
|
|
21
|
-
edited: audit.copies.filter(copy => copy.edited === true).map(copy => copy.id),
|
|
20
|
+
removable: audit.copies.filter(copy => copy.edited === false || copy.stale === true).map(copy => copy.id),
|
|
21
|
+
edited: audit.copies.filter(copy => copy.edited === true && copy.stale !== true).map(copy => copy.id),
|
|
22
|
+
stale: audit.copies.filter(copy => copy.stale === true).map(copy => copy.id),
|
|
22
23
|
unverified: audit.copies.filter(copy => copy.edited === null).map(copy => copy.id),
|
|
23
24
|
copies: audit.copies
|
|
24
25
|
};
|
|
@@ -58,11 +59,13 @@ export async function manageProjectPluginCopies(args = []) {
|
|
|
58
59
|
return;
|
|
59
60
|
}
|
|
60
61
|
for (const copy of report.copies) {
|
|
61
|
-
const verdict = copy.
|
|
62
|
+
const verdict = copy.stale === true ? 'STALE — older than the shared install; it shadows the shared plugin and must be removed'
|
|
63
|
+
: copy.edited === false ? 'identical to the shared install'
|
|
64
|
+
: copy.edited === true ? 'DIFFERS from the shared install' : 'no shared install to compare';
|
|
62
65
|
console.log(` ${copy.id} v${copy.version || '?'} (shared: v${copy.sharedVersion || '—'}) — ${verdict}`);
|
|
63
66
|
}
|
|
64
|
-
console.log(`\n ${report.removable.length} removable, ${report.edited.length} edited, ${report.unverified.length} unverified`);
|
|
67
|
+
console.log(`\n ${report.removable.length} removable (${report.stale.length} stale), ${report.edited.length} edited, ${report.unverified.length} unverified`);
|
|
65
68
|
if (report.removal) console.log(` Removed: ${report.removal.removed.join(', ') || 'none'}${report.removal.failed.length ? `; failed: ${report.removal.failed.map(row => row.id).join(', ')}` : ''}`);
|
|
66
|
-
else if (report.removable.length) console.log(' Re-run with --apply to remove the
|
|
69
|
+
else if (report.removable.length) console.log(' Re-run with --apply to remove the removable copies.');
|
|
67
70
|
console.log();
|
|
68
71
|
}
|
package/src/cli/plugin/index.mjs
CHANGED
|
@@ -6,8 +6,8 @@ import { createPlugin } from './create.mjs';
|
|
|
6
6
|
import { vendorPlugin, listProjectBricks } from './vendor.mjs';
|
|
7
7
|
import { manageProjectPluginCopies } from './copies.mjs';
|
|
8
8
|
import { managePluginLicense } from './monetization.mjs';
|
|
9
|
-
import { getPlugin, getPluginMonetization } from '../../core/plugin-loader.mjs';
|
|
10
|
-
import { describePlugin } from '../../core/plugin-store.mjs';
|
|
9
|
+
import { getPlugin, getPluginMonetization, globalPluginsDir, projectPluginsDir } from '../../core/plugin-loader.mjs';
|
|
10
|
+
import { describePlugin, pruneReplacementArchives, replacementArchiveDir } from '../../core/plugin-store.mjs';
|
|
11
11
|
import { buildPluginCatalog } from '../../core/plugin-catalog.mjs';
|
|
12
12
|
import { setPluginActive } from '../../core/plugin-activation.mjs';
|
|
13
13
|
|
|
@@ -29,6 +29,10 @@ Commands:
|
|
|
29
29
|
refresh Rebuild installed command inventories and instruction surfaces
|
|
30
30
|
copies [dir] [--apply] Audit this repository's leftover copies of the shared default
|
|
31
31
|
plugins; --apply removes only the ones identical to the shared install
|
|
32
|
+
replacements [--prune] [--keep N] [--project] [--json]
|
|
33
|
+
Copies kept by install --replace (default: shared folder). Reports
|
|
34
|
+
the space; --prune keeps the newest N per plugin (default 1) and
|
|
35
|
+
drops node_modules from the kept ones
|
|
32
36
|
on <id> / off <id> Enable/disable host execution for this repository alone
|
|
33
37
|
--all-repos Change the shared setting for every repository
|
|
34
38
|
--clear Drop this repository's override; follow the shared setting
|
|
@@ -136,6 +140,25 @@ export async function run(argv = []) {
|
|
|
136
140
|
return;
|
|
137
141
|
}
|
|
138
142
|
|
|
143
|
+
if (command === 'replacements') {
|
|
144
|
+
const keepAt = rest.indexOf('--keep');
|
|
145
|
+
const keep = keepAt === -1 ? 1 : Number(rest[keepAt + 1]);
|
|
146
|
+
if (!Number.isInteger(keep) || keep < 0) {
|
|
147
|
+
console.error('Usage: total-recall plugin replacements [--prune] [--keep N] [--project] [--json]');
|
|
148
|
+
process.exitCode = 1;
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const dir = replacementArchiveDir(rest.includes('--project') ? projectPluginsDir(process.cwd()) : globalPluginsDir());
|
|
152
|
+
const apply = rest.includes('--prune');
|
|
153
|
+
const r = pruneReplacementArchives(dir, { keep, apply });
|
|
154
|
+
if (rest.includes('--json')) { console.log(JSON.stringify({ ...r, applied: apply }, null, 2)); return; }
|
|
155
|
+
const gb = (r.bytesFreed / 1073741824).toFixed(2);
|
|
156
|
+
console.log(`\n ${r.archiveDir}`);
|
|
157
|
+
console.log(` ${r.kept.length + r.removed.length} retained cop${r.kept.length + r.removed.length === 1 ? 'y' : 'ies'}; keeping ${r.kept.length}, ${apply ? 'removed' : 'would remove'} ${r.removed.length}, ${apply ? 'stripped' : 'would strip'} node_modules from ${r.stripped.length}`);
|
|
158
|
+
console.log(` ${apply ? 'Freed' : 'Would free'} ${gb} GB${apply ? '' : ' — run with --prune to apply'}\n`);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
139
162
|
if (command === 'copies' || command === 'duplicates') {
|
|
140
163
|
await manageProjectPluginCopies(rest);
|
|
141
164
|
return;
|
package/src/cli/rebuild.mjs
CHANGED
|
@@ -163,6 +163,28 @@ export async function compileRegisteredProjects({ brains = listLocalBrains(), co
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
export default async function cli(args) {
|
|
166
|
+
// `--help` must never execute the command. This parsed flags and went straight to a rebuild, so
|
|
167
|
+
// `total-recall compile --help` (and rebuild) threw away the derived indexes and recompiled every
|
|
168
|
+
// brain instead of printing usage.
|
|
169
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
170
|
+
console.log(`
|
|
171
|
+
total-recall compile | rebuild — SSSS projection rebuild
|
|
172
|
+
|
|
173
|
+
Discards the derived indexes and rebuilds them deterministically from the canonical vault,
|
|
174
|
+
then recompiles instruction surfaces.
|
|
175
|
+
|
|
176
|
+
total-recall compile [--check] [--force] [--all] [--global|--project]
|
|
177
|
+
|
|
178
|
+
--check Report drift between the vault and derived indexes; change nothing.
|
|
179
|
+
--force Rebuild even when nothing is stale.
|
|
180
|
+
--all Recompile every registered project brain as well.
|
|
181
|
+
--global Target the global brain (~/.agent).
|
|
182
|
+
--project Target this repository's brain (<cwd>/.agent).
|
|
183
|
+
|
|
184
|
+
A global compile is what \`remember --global\` runs, because global rules land in every project.
|
|
185
|
+
`);
|
|
186
|
+
process.exit(0);
|
|
187
|
+
}
|
|
166
188
|
const { layer, remainingArgs } = parseLayerFlag(args);
|
|
167
189
|
const options = {
|
|
168
190
|
check: remainingArgs.includes('--check'),
|
|
@@ -54,9 +54,10 @@ export default async function resetPassword(args) {
|
|
|
54
54
|
console.log(`
|
|
55
55
|
total-recall reset-password
|
|
56
56
|
|
|
57
|
-
Resets the dashboard admin password in
|
|
57
|
+
Resets the dashboard admin password in the encrypted auth store (the only place the server
|
|
58
|
+
reads it; any password_hash left in config/security.yml is ignored).
|
|
58
59
|
Prompts for the new password; it is never taken as an argument.
|
|
59
|
-
|
|
60
|
+
Existing sessions are invalidated. Setting the well-known default forces a change on first login.
|
|
60
61
|
|
|
61
62
|
For automation, pipe it instead: echo "\$NEW_PW" | total-recall reset-password
|
|
62
63
|
`);
|