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.
Files changed (128) hide show
  1. package/bin/total-recall.mjs +19 -4
  2. package/frontend/dist/assets/{index-BG9NkVUy.css → index-BIQmqDKP.css} +1 -1
  3. package/frontend/dist/assets/{index-BtuiZqwP.js → index-CRzjQLu7.js} +7 -7
  4. package/frontend/dist/index.html +2 -2
  5. package/frontend/dist/sw.js +8 -5
  6. package/frontend/src/api/auth.ts +0 -3
  7. package/frontend/src/components/plugins/PluginPanel.tsx +7 -3
  8. package/frontend/src/components/plugins/card/CardEngine.tsx +1 -1
  9. package/frontend/src/pages/LoginPage.tsx +1 -41
  10. package/frontend/src/pages/PluginsPage.tsx +14 -7
  11. package/package.json +7 -5
  12. package/scaffold/.agent/skills/plugin/scripts/record.mjs +6 -3
  13. package/scaffold/.agent/skills/total-recall/plugins/auth/cli.mjs +197 -31
  14. package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +6 -6
  15. package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +1 -1
  16. package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +8 -1
  17. package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +48 -0
  18. package/scaffold/.agent/skills/total-recall/plugins/auth/src/login-projections.mjs +42 -0
  19. package/scaffold/.agent/skills/total-recall/plugins/auth/src/mesh-secrets.mjs +18 -0
  20. package/scaffold/.agent/skills/total-recall/plugins/auth/src/routes/secrets.mjs +5 -2
  21. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-carriers.mjs +76 -0
  22. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-prune.mjs +37 -0
  23. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-sync.mjs +53 -17
  24. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/DESIGN.md +56 -8
  25. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/card.js +1068 -204
  26. package/scaffold/.agent/skills/total-recall/plugins/auth/ui/panel.js +4 -2
  27. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/DESIGN.md +32 -0
  28. package/scaffold/.agent/skills/total-recall/plugins/cli-agents/ui/card.js +436 -274
  29. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +12 -12
  30. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +1 -1
  31. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/DESIGN.md +16 -2
  32. package/scaffold/.agent/skills/total-recall/plugins/code-quality/ui/card.js +461 -242
  33. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +12 -12
  34. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +1 -1
  35. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +1 -7
  36. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/DESIGN.md +17 -2
  37. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/ui/card.js +426 -300
  38. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +23 -8
  39. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +6 -2
  40. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/DESIGN.md +32 -0
  41. package/scaffold/.agent/skills/total-recall/plugins/embeddings/ui/card.js +350 -283
  42. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package-lock.json +23 -8
  43. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/package.json +5 -1
  44. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/ui/card.js +31 -19
  45. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +23 -10
  46. package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +5 -3
  47. package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +1 -1
  48. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/grants.mjs +7 -3
  49. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/cli/mesh.mjs +61 -38
  50. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/leader-election.mjs +57 -2
  51. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/core/mesh.mjs +220 -36
  52. package/scaffold/.agent/skills/total-recall/plugins/mesh/src/server/routes/mesh.mjs +7 -2
  53. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +65 -0
  54. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/BRICK.md +59 -0
  55. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +534 -0
  56. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +139 -0
  57. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +2849 -0
  58. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +26 -0
  59. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +376 -0
  60. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +37 -0
  61. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/adapters/dsh.mjs +446 -0
  62. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +23 -0
  63. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +398 -0
  64. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +152 -0
  65. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +82 -0
  66. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +90 -0
  67. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +415 -0
  68. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/runtime.mjs +36 -0
  69. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/setup.mjs +35 -0
  70. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/tracker.mjs +133 -0
  71. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +233 -0
  72. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/DESIGN.md +32 -0
  73. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +845 -0
  74. package/scaffold/.agent/skills/total-recall/plugins/openwiki/capability/BRICK.md +64 -0
  75. package/scaffold/.agent/skills/total-recall/plugins/openwiki/capability/index.mjs +250 -0
  76. package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +344 -14
  77. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +2917 -140
  78. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +9 -3
  79. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +223 -5
  80. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/config.mjs +129 -0
  81. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +355 -0
  82. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/upstream-watcher.mjs +375 -0
  83. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/DESIGN.md +37 -10
  84. package/scaffold/.agent/skills/total-recall/plugins/openwiki/ui/card.js +986 -280
  85. package/scaffold/.agent/skills/total-recall/plugins/security/cli.mjs +7 -1
  86. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +25 -10
  87. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +6 -2
  88. package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +1 -1
  89. package/scaffold/.agent/skills/total-recall/plugins/security/src/scanner.mjs +17 -3
  90. package/scaffold/.agent/skills/total-recall/plugins/security/ui/DESIGN.md +38 -15
  91. package/scaffold/.agent/skills/total-recall/plugins/security/ui/card.js +60 -34
  92. package/src/cli/command.mjs +23 -18
  93. package/src/cli/config.mjs +3 -0
  94. package/src/cli/connect.mjs +12 -116
  95. package/src/cli/daemon.mjs +23 -1
  96. package/src/cli/harness.mjs +0 -1
  97. package/src/cli/plugin/copies.mjs +8 -5
  98. package/src/cli/plugin/create.mjs +56 -3
  99. package/src/cli/plugin/index.mjs +40 -5
  100. package/src/cli/plugin/monetization.mjs +232 -12
  101. package/src/cli/preflight.mjs +161 -0
  102. package/src/cli/rebuild.mjs +22 -0
  103. package/src/cli/reset-password.mjs +3 -2
  104. package/src/cli/skill.mjs +8 -2
  105. package/src/cli/status.mjs +25 -7
  106. package/src/core/automatic-brain-updates.mjs +9 -3
  107. package/src/core/brain-updates.mjs +63 -5
  108. package/src/core/cli-commands.mjs +2 -0
  109. package/src/core/context-compiler.mjs +8 -1
  110. package/src/core/default-plugins.mjs +1 -1
  111. package/src/core/global-brain-updates.mjs +21 -1
  112. package/src/core/mesh-brain-updates.mjs +5 -2
  113. package/src/core/meta-harness.mjs +32 -79
  114. package/src/core/plugin-dependencies.mjs +6 -1
  115. package/src/core/plugin-runner.mjs +6 -1
  116. package/src/core/plugin-store.mjs +67 -1
  117. package/src/core/plugin-updates.mjs +1 -1
  118. package/src/core/portable-skills-maintenance.mjs +19 -0
  119. package/src/core/preflight-gate.mjs +77 -0
  120. package/src/core/preflight-receipt.mjs +123 -0
  121. package/src/core/required-plugins.mjs +7 -0
  122. package/src/core/runtime.mjs +0 -3
  123. package/src/core/skills-registry.mjs +33 -3
  124. package/src/core/surface.mjs +38 -1
  125. package/src/server/auth.mjs +41 -1
  126. package/src/server/memory-app.mjs +2 -1
  127. package/src/server/routes/auth.mjs +25 -1
  128. 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 { console.log(JSON.stringify({ status: 'failed', operation: sub, error: 'Security operation failed; verify arguments, owning plugin availability and authorization. No success was established.' })); process.exitCode = 1; return; }
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,19 @@
1
1
  {
2
2
  "name": "tr-plugin-security",
3
- "version": "0.3.2",
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.2",
9
+ "version": "0.3.3",
10
10
  "license": "MIT",
11
+ "dependencies": {
12
+ "@gregiteen/ssss-cli": "0.14.3"
13
+ },
11
14
  "devDependencies": {
12
15
  "jsdom": "^26.1.0",
13
- "total-recall-brain": "3.41.1"
16
+ "total-recall-brain": "3.41.3"
14
17
  }
15
18
  },
16
19
  "node_modules/@asamuzakjp/css-color": {
@@ -142,6 +145,18 @@
142
145
  "node": ">=18"
143
146
  }
144
147
  },
148
+ "node_modules/@gregiteen/ssss-cli": {
149
+ "version": "0.14.3",
150
+ "resolved": "https://registry.npmjs.org/@gregiteen/ssss-cli/-/ssss-cli-0.14.3.tgz",
151
+ "integrity": "sha512-vvGgiNLjus3/xp+iotiGqbSG0R7+Q/bP5cP2Psa7Rhtm/vrMuM5B4LebYu7bPf3TzgCyTbv96PcxGWf6gxbW0Q==",
152
+ "license": "ISC",
153
+ "bin": {
154
+ "ssss": "scripts/ssss.mjs"
155
+ },
156
+ "engines": {
157
+ "node": ">=18"
158
+ }
159
+ },
145
160
  "node_modules/@hexagon/base64": {
146
161
  "version": "1.1.28",
147
162
  "resolved": "https://registry.npmjs.org/@hexagon/base64/-/base64-1.1.28.tgz",
@@ -479,9 +494,9 @@
479
494
  },
480
495
  "node_modules/@ssss/cli": {
481
496
  "name": "@gregiteen/ssss-cli",
482
- "version": "0.11.2",
483
- "resolved": "https://registry.npmjs.org/@gregiteen/ssss-cli/-/ssss-cli-0.11.2.tgz",
484
- "integrity": "sha512-DTkBPCOv9uN79YoRtsa/GnXPbbeHOU5PQcQ995axHg+SLWdkL6xmrTRs5Xxshcje/Iv6+lKkYOc9UL+Arn9kHg==",
497
+ "version": "0.12.1",
498
+ "resolved": "https://registry.npmjs.org/@gregiteen/ssss-cli/-/ssss-cli-0.12.1.tgz",
499
+ "integrity": "sha512-ff0L2LV4db1CfD4W3VmIJwT9fvrpuKDGDfhe6gtjrrgtRmo7BP7jYjRtH7yuQipKikyiP9LIvmVIaxvbjLTwkA==",
485
500
  "dev": true,
486
501
  "license": "ISC",
487
502
  "bin": {
@@ -2916,9 +2931,9 @@
2916
2931
  }
2917
2932
  },
2918
2933
  "node_modules/total-recall-brain": {
2919
- "version": "3.41.1",
2920
- "resolved": "https://registry.npmjs.org/total-recall-brain/-/total-recall-brain-3.41.1.tgz",
2921
- "integrity": "sha512-QraXzcZpCTMssx1gaz8xjvSKf5KqirAaxzzER/ICBrWvawdNTLUDDMsVEYQUA/7thgwtL1QVIxU4dpfCGZwp+g==",
2934
+ "version": "3.41.3",
2935
+ "resolved": "https://registry.npmjs.org/total-recall-brain/-/total-recall-brain-3.41.3.tgz",
2936
+ "integrity": "sha512-0uIh3WZE3dHxrKFTtRMxvSBLx7aV3j7ry9H9NxRShtCycwQ38T7+70Q/GJAqsdpwt894h8B6AAmWJnHwsFG3XQ==",
2922
2937
  "dev": true,
2923
2938
  "license": "MIT",
2924
2939
  "workspaces": [
@@ -2926,7 +2941,7 @@
2926
2941
  ],
2927
2942
  "dependencies": {
2928
2943
  "@simplewebauthn/server": "^13.3.2",
2929
- "@ssss/cli": "npm:@gregiteen/ssss-cli@0.11.2",
2944
+ "@ssss/cli": "npm:@gregiteen/ssss-cli@0.12.1",
2930
2945
  "bcrypt": "^6.0.0",
2931
2946
  "better-sqlite3": "^12.11.1",
2932
2947
  "cookie-parser": "^1.4.7",
@@ -1,11 +1,12 @@
1
1
  {
2
2
  "name": "tr-plugin-security",
3
- "version": "0.3.2",
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": {
7
7
  "test": "node --test test/*.test.mjs",
8
- "build:ui": "node scripts/build-ui.mjs"
8
+ "build:ui": "node scripts/build-ui.mjs",
9
+ "ssss": "ssss"
9
10
  },
10
11
  "license": "MIT",
11
12
  "files": [
@@ -21,5 +22,8 @@
21
22
  ],
22
23
  "publishConfig": {
23
24
  "access": "public"
25
+ },
26
+ "dependencies": {
27
+ "@gregiteen/ssss-cli": "0.14.3"
24
28
  }
25
29
  }
@@ -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.2",
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
- const skipped = new Set(['node_modules', '.git', 'dist', 'coverage', 'memory-vault', '.cache', '.venv', '__pycache__']);
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
- if (++count > 20000 || stat.size > 2097152) throw new Error('Scan coverage limit exceeded; narrow the target');
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
- ```yaml
4
- tokens:
5
- brand-name: "Tin Foil"
6
- wordmark: "[TIN//FOIL]"
7
- color-primary: "#f59e0b"
8
- color-background: "#0a0a0c"
9
- color-accent: "#e2e8f0"
10
- badge: "Hardening"
11
- font-mono: "'JetBrains Mono', 'Fira Code', monospace"
12
- border-radius: "8px"
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
- ## Brand & Typographic Wordmark
16
- - **Name**: Tin Foil
17
- - **Wordmark Logo**: `[TIN//FOIL]` (custom typographic badge, no generic icons)
18
- - **Palette**: Primary `#f59e0b`, Surface `#0a0a0c`, Accent `#e2e8f0`.
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: #f1f5f9;
11
- background: #180d11;
12
- border: 1px solid rgba(244, 63, 94, 0.35);
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: #f43f5e;
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: #fecdd3;
66
+ color: var(--face-text-heading);
47
67
  }
48
68
 
49
69
  p.desc {
50
70
  margin: 0 0 14px 0;
51
- color: #fda4af;
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 rgba(244, 63, 94, 0.28);
89
+ border: 1px solid var(--face-border-subtle);
70
90
  border-radius: 6px;
71
91
  padding: 8px 12px;
72
- background: #251017;
73
- color: #f1f5f9;
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: rgba(244, 63, 94, 0.22);
88
- border-color: #f43f5e;
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: #11070a;
124
+ background: var(--face-bg-surface);
105
125
  }
106
126
 
107
127
  input:focus, select:focus {
108
- border-color: #f43f5e;
128
+ border-color: var(--face-accent);
109
129
  outline: none;
110
- box-shadow: 0 0 0 1px rgba(244, 63, 94, 0.3);
130
+ box-shadow: 0 0 0 1px var(--face-accent-glow);
111
131
  }
112
132
 
113
133
  select {
114
- background: #11070a;
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: #fb7185;
124
- background: #11070a;
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 rgba(244, 63, 94, 0.2);
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 rgba(244, 63, 94, 0.2);
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: #0d0508;
161
+ background: var(--face-bg-table);
142
162
  }
143
163
 
144
164
  th {
145
- background: #251017;
146
- color: #fecdd3;
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 rgba(244, 63, 94, 0.3);
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 rgba(244, 63, 94, 0.15);
178
+ border-bottom: 1px solid var(--face-border-cell);
159
179
  vertical-align: top;
160
- color: #f8fafc;
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: #fda4af;
186
+ color: var(--face-text-muted);
167
187
  }
168
188
 
169
189
  td:last-child {
170
190
  line-height: 1.45;
171
- color: #cbd5e1;
191
+ color: var(--face-text-desc);
172
192
  }
173
193
 
174
194
  pre {
175
- background: #0a0406;
176
- border: 1px solid rgba(244, 63, 94, 0.2);
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: #fb7185;
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: #11070a;
192
- border: 1px solid rgba(244, 63, 94, 0.25);
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: #fca5a5;
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: #fecdd3;
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>
@@ -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 = `// Auto-generated custom CLI command: ${name}
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 = `// Auto-generated custom CLI command: ${name}
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`);
@@ -17,6 +17,9 @@ const KEY_MAP = {
17
17
  'public_health': { file: 'security', path: 'network.public_health', type: 'boolean' },
18
18
  'allowed_origins': { file: 'security', path: 'network.allowed_origins', type: 'array' },
19
19
 
20
+ // How long a `total-recall preflight ack` receipt stays fresh. Recorded in the receipt when it is written.
21
+ 'preflight_max_age_minutes': { file: 'preflight', path: 'preflight.max_age_minutes', type: 'number' },
22
+
20
23
  'daily_cap_usd': { file: 'budget', path: 'budget.daily_cap_usd', type: 'number' },
21
24
  'weekly_cap_usd': { file: 'budget', path: 'budget.weekly_cap_usd', type: 'number' },
22
25
  'budget_enabled': { file: 'budget', path: 'budget.enabled', type: 'boolean' },