superlocalmemory 2.8.3 → 2.8.6
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/ATTRIBUTION.md +1 -1
- package/CHANGELOG.md +28 -0
- package/bin/slm +36 -1
- package/bin/slm-npm +2 -2
- package/bin/slm.bat +4 -2
- package/install.ps1 +36 -4
- package/install.sh +14 -13
- package/mcp_server.py +88 -12
- package/package.json +1 -1
- package/src/memory_store_v2.py +12 -2
- package/ui_server.py +28 -8
- package/docs/SECURITY-QUICK-REFERENCE.md +0 -214
package/ATTRIBUTION.md
CHANGED
|
@@ -38,7 +38,7 @@ is_valid = QualixarSigner.verify(signed_output)
|
|
|
38
38
|
|
|
39
39
|
### Research Initiative
|
|
40
40
|
|
|
41
|
-
Qualixar is a research
|
|
41
|
+
Qualixar is a research initiative for AI agent development tools by Varun Pratap Bhardwaj. SuperLocalMemory is one of several research initiatives under the Qualixar umbrella.
|
|
42
42
|
|
|
43
43
|
### Third-Party Acknowledgments
|
|
44
44
|
|
package/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,34 @@ SuperLocalMemory V2 - Intelligent local memory system for AI coding assistants.
|
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
19
|
+
## [2.8.6] - 2026-03-06
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- Environment variable support across all CLI tools
|
|
23
|
+
- Multi-tool memory database sharing
|
|
24
|
+
|
|
25
|
+
### Contributors
|
|
26
|
+
- Paweł Przytuła (@pawelel) - Issue #7 and PR #8
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## [2.8.3] - 2026-03-05
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
- Windows installation and cross-platform compatibility
|
|
34
|
+
- Database stability under concurrent usage
|
|
35
|
+
- Forward compatibility with latest Python versions
|
|
36
|
+
|
|
37
|
+
### Added
|
|
38
|
+
- Full Windows support with PowerShell scripts for all operations
|
|
39
|
+
- `slm attribution` command for license and creator information
|
|
40
|
+
|
|
41
|
+
### Improved
|
|
42
|
+
- Overall reliability and code quality
|
|
43
|
+
- Dependency management for reproducible installs
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
19
47
|
## [2.8.2] - 2026-03-04
|
|
20
48
|
|
|
21
49
|
### Fixed
|
package/bin/slm
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
set -e
|
|
12
12
|
|
|
13
|
-
SLM_DIR="${HOME}/.claude-memory"
|
|
13
|
+
SLM_DIR="${SL_MEMORY_PATH:-${HOME}/.claude-memory}"
|
|
14
14
|
|
|
15
15
|
# Check if SuperLocalMemory is installed
|
|
16
16
|
if [ ! -d "$SLM_DIR" ]; then
|
|
@@ -310,6 +310,38 @@ except Exception as e:
|
|
|
310
310
|
"
|
|
311
311
|
;;
|
|
312
312
|
|
|
313
|
+
attribution)
|
|
314
|
+
python3 -c "
|
|
315
|
+
import sys
|
|
316
|
+
sys.path.insert(0, '${SLM_DIR}')
|
|
317
|
+
try:
|
|
318
|
+
from memory_store_v2 import MemoryStoreV2
|
|
319
|
+
store = MemoryStoreV2()
|
|
320
|
+
attr = store.get_attribution()
|
|
321
|
+
print('SuperLocalMemory — Attribution')
|
|
322
|
+
print('=' * 45)
|
|
323
|
+
print(f\"Creator: {attr.get('creator_name', 'Varun Pratap Bhardwaj')}\")
|
|
324
|
+
print(f\"Role: {attr.get('creator_role', 'Solution Architect')}\")
|
|
325
|
+
print(f\"Platform: {attr.get('platform', 'Qualixar')}\")
|
|
326
|
+
print(f\"License: {attr.get('license', 'MIT')}\")
|
|
327
|
+
print(f\"Website: {attr.get('website', 'https://superlocalmemory.com')}\")
|
|
328
|
+
print(f\"Author: {attr.get('author_website', 'https://varunpratap.com')}\")
|
|
329
|
+
print('=' * 45)
|
|
330
|
+
try:
|
|
331
|
+
from qualixar_attribution import QualixarSigner
|
|
332
|
+
from qualixar_watermark import encode_watermark
|
|
333
|
+
print('Layer 1 (Visible): Active')
|
|
334
|
+
print('Layer 2 (Cryptographic): Active')
|
|
335
|
+
print('Layer 3 (Steganographic): Active')
|
|
336
|
+
except ImportError:
|
|
337
|
+
print('Layer 1 (Visible): Active')
|
|
338
|
+
print('Layer 2 (Cryptographic): Not available')
|
|
339
|
+
print('Layer 3 (Steganographic): Not available')
|
|
340
|
+
except Exception as e:
|
|
341
|
+
print(f'Error: {e}')
|
|
342
|
+
"
|
|
343
|
+
;;
|
|
344
|
+
|
|
313
345
|
help|--help|-h)
|
|
314
346
|
cat <<EOF
|
|
315
347
|
SuperLocalMemory V2 - Universal CLI
|
|
@@ -356,6 +388,9 @@ HTTP SERVER (MCP):
|
|
|
356
388
|
slm serve [PORT] Start MCP HTTP server (default port 8417)
|
|
357
389
|
For ChatGPT/remote: ngrok http PORT
|
|
358
390
|
|
|
391
|
+
ATTRIBUTION:
|
|
392
|
+
slm attribution Show creator attribution and provenance status
|
|
393
|
+
|
|
359
394
|
ADVANCED:
|
|
360
395
|
slm reset soft Soft reset (clear memories)
|
|
361
396
|
slm reset hard --confirm Hard reset (nuclear option)
|
package/bin/slm-npm
CHANGED
|
@@ -16,8 +16,8 @@ const path = require('path');
|
|
|
16
16
|
const os = require('os');
|
|
17
17
|
const fs = require('fs');
|
|
18
18
|
|
|
19
|
-
// Installation directory
|
|
20
|
-
const SLM_DIR = path.join(os.homedir(), '.claude-memory');
|
|
19
|
+
// Installation directory (SL_MEMORY_PATH overrides default)
|
|
20
|
+
const SLM_DIR = process.env.SL_MEMORY_PATH || path.join(os.homedir(), '.claude-memory');
|
|
21
21
|
|
|
22
22
|
// Check if SuperLocalMemory is installed
|
|
23
23
|
if (!fs.existsSync(SLM_DIR)) {
|
package/bin/slm.bat
CHANGED
|
@@ -9,7 +9,9 @@ REM ATTRIBUTION REQUIRED: This notice must be preserved in all copies.
|
|
|
9
9
|
setlocal enabledelayedexpansion
|
|
10
10
|
|
|
11
11
|
REM Determine installation location
|
|
12
|
-
if
|
|
12
|
+
if defined SL_MEMORY_PATH (
|
|
13
|
+
set "INSTALL_DIR=%SL_MEMORY_PATH%"
|
|
14
|
+
) else if exist "%USERPROFILE%\.claude-memory\memory_store_v2.py" (
|
|
13
15
|
set INSTALL_DIR=%USERPROFILE%\.claude-memory
|
|
14
16
|
) else if exist "%~dp0..\src\memory_store_v2.py" (
|
|
15
17
|
set INSTALL_DIR=%~dp0..\src
|
|
@@ -181,7 +183,7 @@ echo slm build-graph
|
|
|
181
183
|
echo slm switch-profile work
|
|
182
184
|
echo.
|
|
183
185
|
echo Installation: %INSTALL_DIR%
|
|
184
|
-
echo Database: %
|
|
186
|
+
echo Database: %INSTALL_DIR%\memory.db
|
|
185
187
|
echo.
|
|
186
188
|
echo Documentation: https://github.com/varun369/SuperLocalMemoryV2/wiki
|
|
187
189
|
echo Report issues: https://github.com/varun369/SuperLocalMemoryV2/issues
|
package/install.ps1
CHANGED
|
@@ -15,7 +15,7 @@ param(
|
|
|
15
15
|
|
|
16
16
|
$ErrorActionPreference = "Stop"
|
|
17
17
|
|
|
18
|
-
$INSTALL_DIR = Join-Path $env:USERPROFILE ".claude-memory"
|
|
18
|
+
$INSTALL_DIR = if ($env:SL_MEMORY_PATH) { $env:SL_MEMORY_PATH } else { Join-Path $env:USERPROFILE ".claude-memory" }
|
|
19
19
|
$REPO_DIR = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
20
20
|
|
|
21
21
|
# Auto-detect non-interactive environment
|
|
@@ -122,6 +122,35 @@ if (Test-Path $uiServerPath) {
|
|
|
122
122
|
Write-Host "OK UI server copied" -ForegroundColor Green
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
# Copy security middleware (required by ui_server.py)
|
|
126
|
+
$securityMiddlewarePath = Join-Path $REPO_DIR "security_middleware.py"
|
|
127
|
+
if (Test-Path $securityMiddlewarePath) {
|
|
128
|
+
Copy-Item -Path $securityMiddlewarePath -Destination $INSTALL_DIR -Force
|
|
129
|
+
Write-Host "OK Security middleware copied" -ForegroundColor Green
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
# Copy routes (required by ui_server.py)
|
|
133
|
+
$routesDir = Join-Path $REPO_DIR "routes"
|
|
134
|
+
$installRoutesDir = Join-Path $INSTALL_DIR "routes"
|
|
135
|
+
if (Test-Path $routesDir) {
|
|
136
|
+
if (-not (Test-Path $installRoutesDir)) {
|
|
137
|
+
New-Item -ItemType Directory -Path $installRoutesDir -Force | Out-Null
|
|
138
|
+
}
|
|
139
|
+
Copy-Item -Path (Join-Path $routesDir "*") -Destination $installRoutesDir -Recurse -Force
|
|
140
|
+
Write-Host "OK Routes copied" -ForegroundColor Green
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
# Copy UI assets (required by ui_server.py)
|
|
144
|
+
$uiDir = Join-Path $REPO_DIR "ui"
|
|
145
|
+
$installUiDir = Join-Path $INSTALL_DIR "ui"
|
|
146
|
+
if (Test-Path $uiDir) {
|
|
147
|
+
if (-not (Test-Path $installUiDir)) {
|
|
148
|
+
New-Item -ItemType Directory -Path $installUiDir -Force | Out-Null
|
|
149
|
+
}
|
|
150
|
+
Copy-Item -Path (Join-Path $uiDir "*") -Destination $installUiDir -Recurse -Force
|
|
151
|
+
Write-Host "OK UI assets copied" -ForegroundColor Green
|
|
152
|
+
}
|
|
153
|
+
|
|
125
154
|
# Copy MCP server
|
|
126
155
|
$mcpServerPath = Join-Path $REPO_DIR "mcp_server.py"
|
|
127
156
|
if (Test-Path $mcpServerPath) {
|
|
@@ -169,8 +198,9 @@ if (Test-Path $setupValidatorPath) {
|
|
|
169
198
|
# Fallback: create basic database
|
|
170
199
|
& python -c @"
|
|
171
200
|
import sqlite3
|
|
201
|
+
import os
|
|
172
202
|
from pathlib import Path
|
|
173
|
-
db_path = Path.home() / '.claude-memory' / 'memory.db'
|
|
203
|
+
db_path = Path(os.environ.get('SL_MEMORY_PATH') or str(Path.home() / '.claude-memory')) / 'memory.db'
|
|
174
204
|
conn = sqlite3.connect(db_path)
|
|
175
205
|
cursor = conn.cursor()
|
|
176
206
|
cursor.execute('''CREATE TABLE IF NOT EXISTS memories (
|
|
@@ -228,8 +258,9 @@ Write-Host "Initializing advanced features..."
|
|
|
228
258
|
# Add sample memories if database is empty (for first-time users)
|
|
229
259
|
$memoryCount = & python -c @"
|
|
230
260
|
import sqlite3
|
|
261
|
+
import os
|
|
231
262
|
from pathlib import Path
|
|
232
|
-
db_path = Path.home() / '.claude-memory' / 'memory.db'
|
|
263
|
+
db_path = Path(os.environ.get('SL_MEMORY_PATH') or str(Path.home() / '.claude-memory')) / 'memory.db'
|
|
233
264
|
conn = sqlite3.connect(db_path)
|
|
234
265
|
cursor = conn.cursor()
|
|
235
266
|
cursor.execute('SELECT COUNT(*) FROM memories')
|
|
@@ -261,8 +292,9 @@ try {
|
|
|
261
292
|
& python "$INSTALL_DIR\pattern_learner.py" update 2>$null | Out-Null
|
|
262
293
|
$patternCount = & python -c @"
|
|
263
294
|
import sqlite3
|
|
295
|
+
import os
|
|
264
296
|
from pathlib import Path
|
|
265
|
-
db_path = Path.home() / '.claude-memory' / 'memory.db'
|
|
297
|
+
db_path = Path(os.environ.get('SL_MEMORY_PATH') or str(Path.home() / '.claude-memory')) / 'memory.db'
|
|
266
298
|
conn = sqlite3.connect(db_path)
|
|
267
299
|
cursor = conn.cursor()
|
|
268
300
|
cursor.execute('SELECT COUNT(*) FROM identity_patterns')
|
package/install.sh
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
set -e
|
|
10
10
|
|
|
11
|
-
INSTALL_DIR="${HOME}/.claude-memory"
|
|
11
|
+
INSTALL_DIR="${SL_MEMORY_PATH:-${HOME}/.claude-memory}"
|
|
12
12
|
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd)"
|
|
13
13
|
|
|
14
14
|
# Parse command line arguments
|
|
@@ -214,6 +214,11 @@ if [ -f "${REPO_DIR}/ui_server.py" ]; then
|
|
|
214
214
|
echo "✓ UI server copied"
|
|
215
215
|
fi
|
|
216
216
|
|
|
217
|
+
if [ -f "${REPO_DIR}/security_middleware.py" ]; then
|
|
218
|
+
cp "${REPO_DIR}/security_middleware.py" "${INSTALL_DIR}/"
|
|
219
|
+
echo "✓ Security middleware copied"
|
|
220
|
+
fi
|
|
221
|
+
|
|
217
222
|
if [ -d "${REPO_DIR}/ui" ]; then
|
|
218
223
|
mkdir -p "${INSTALL_DIR}/ui/js"
|
|
219
224
|
cp "${REPO_DIR}/ui/index.html" "${INSTALL_DIR}/ui/" 2>/dev/null || true
|
|
@@ -258,8 +263,7 @@ echo "✓ Directories created"
|
|
|
258
263
|
if [ ! -f "${INSTALL_DIR}/audit.db" ]; then
|
|
259
264
|
python3 -c "
|
|
260
265
|
import sqlite3
|
|
261
|
-
|
|
262
|
-
audit_path = Path.home() / '.claude-memory' / 'audit.db'
|
|
266
|
+
audit_path = '${INSTALL_DIR}/audit.db'
|
|
263
267
|
conn = sqlite3.connect(audit_path)
|
|
264
268
|
cursor = conn.cursor()
|
|
265
269
|
cursor.execute('''CREATE TABLE IF NOT EXISTS audit_events (
|
|
@@ -298,8 +302,7 @@ else
|
|
|
298
302
|
# Fallback: create basic tables
|
|
299
303
|
python3 -c "
|
|
300
304
|
import sqlite3
|
|
301
|
-
|
|
302
|
-
db_path = Path.home() / '.claude-memory' / 'memory.db'
|
|
305
|
+
db_path = '${INSTALL_DIR}/memory.db'
|
|
303
306
|
conn = sqlite3.connect(db_path)
|
|
304
307
|
cursor = conn.cursor()
|
|
305
308
|
cursor.execute('''CREATE TABLE IF NOT EXISTS memories (
|
|
@@ -399,8 +402,7 @@ echo "Initializing advanced features..."
|
|
|
399
402
|
# Add sample memories if database is empty (for first-time users)
|
|
400
403
|
MEMORY_COUNT=$(python3 -c "
|
|
401
404
|
import sqlite3
|
|
402
|
-
|
|
403
|
-
db_path = Path.home() / '.claude-memory' / 'memory.db'
|
|
405
|
+
db_path = '${INSTALL_DIR}/memory.db'
|
|
404
406
|
conn = sqlite3.connect(db_path)
|
|
405
407
|
cursor = conn.cursor()
|
|
406
408
|
cursor.execute('SELECT COUNT(*) FROM memories')
|
|
@@ -428,8 +430,7 @@ echo "○ Learning patterns..."
|
|
|
428
430
|
if python3 "${INSTALL_DIR}/pattern_learner.py" update > /dev/null 2>&1; then
|
|
429
431
|
PATTERN_COUNT=$(python3 -c "
|
|
430
432
|
import sqlite3
|
|
431
|
-
|
|
432
|
-
db_path = Path.home() / '.claude-memory' / 'memory.db'
|
|
433
|
+
db_path = '${INSTALL_DIR}/memory.db'
|
|
433
434
|
conn = sqlite3.connect(db_path)
|
|
434
435
|
cursor = conn.cursor()
|
|
435
436
|
cursor.execute('SELECT COUNT(*) FROM identity_patterns')
|
|
@@ -486,8 +487,8 @@ else
|
|
|
486
487
|
fi
|
|
487
488
|
|
|
488
489
|
# Check if PATH already configured
|
|
489
|
-
PATH_EXPORT="export PATH=\"
|
|
490
|
-
if grep -
|
|
490
|
+
PATH_EXPORT="export PATH=\"${INSTALL_DIR}/bin:\${PATH}\""
|
|
491
|
+
if grep -Fq -- "${INSTALL_DIR}/bin" "${SHELL_CONFIG}" 2>/dev/null; then
|
|
491
492
|
echo "○ PATH already configured in ${SHELL_CONFIG}"
|
|
492
493
|
else
|
|
493
494
|
# Add PATH export to shell config
|
|
@@ -498,7 +499,7 @@ else
|
|
|
498
499
|
fi
|
|
499
500
|
|
|
500
501
|
# Add to current session PATH
|
|
501
|
-
export PATH="${
|
|
502
|
+
export PATH="${INSTALL_DIR}/bin:${PATH}"
|
|
502
503
|
echo "✓ Commands available in current session"
|
|
503
504
|
|
|
504
505
|
# ============================================================================
|
|
@@ -932,7 +933,7 @@ case "$INSTALL_CHOICE" in
|
|
|
932
933
|
echo " pip3 install -r ${REPO_DIR}/requirements-search.txt"
|
|
933
934
|
echo ""
|
|
934
935
|
echo "Start Web Dashboard:"
|
|
935
|
-
echo " python3
|
|
936
|
+
echo " python3 ${INSTALL_DIR}/ui_server.py"
|
|
936
937
|
echo " Then open: http://localhost:8000"
|
|
937
938
|
;;
|
|
938
939
|
esac
|
package/mcp_server.py
CHANGED
|
@@ -33,7 +33,15 @@ from pathlib import Path
|
|
|
33
33
|
from typing import Optional, Dict, List, Any
|
|
34
34
|
|
|
35
35
|
# Add src directory to path (use existing code!)
|
|
36
|
-
|
|
36
|
+
import logging
|
|
37
|
+
_raw_memory_dir = os.environ.get("SL_MEMORY_PATH")
|
|
38
|
+
MEMORY_DIR = Path(_raw_memory_dir).expanduser() if _raw_memory_dir else (Path.home() / ".claude-memory")
|
|
39
|
+
MEMORY_DIR = MEMORY_DIR.resolve()
|
|
40
|
+
|
|
41
|
+
_required_modules = ("memory_store_v2.py", "graph_engine.py", "pattern_learner.py")
|
|
42
|
+
if not MEMORY_DIR.is_dir() or not all((MEMORY_DIR / m).exists() for m in _required_modules):
|
|
43
|
+
raise RuntimeError(f"Invalid SuperLocalMemory install directory: {MEMORY_DIR}")
|
|
44
|
+
|
|
37
45
|
sys.path.insert(0, str(MEMORY_DIR))
|
|
38
46
|
|
|
39
47
|
# Import existing core modules (zero duplicate logic)
|
|
@@ -61,6 +69,26 @@ try:
|
|
|
61
69
|
except ImportError:
|
|
62
70
|
TRUST_AVAILABLE = False
|
|
63
71
|
|
|
72
|
+
# Qualixar Attribution (v2.8.3 — 3-layer provenance)
|
|
73
|
+
try:
|
|
74
|
+
from qualixar_attribution import QualixarSigner
|
|
75
|
+
from qualixar_watermark import encode_watermark
|
|
76
|
+
_signer = QualixarSigner("superlocalmemory", "2.8.3")
|
|
77
|
+
ATTRIBUTION_AVAILABLE = True
|
|
78
|
+
except ImportError:
|
|
79
|
+
_signer = None
|
|
80
|
+
ATTRIBUTION_AVAILABLE = False
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _sign_response(response: dict) -> dict:
|
|
84
|
+
"""Apply Layer 2 cryptographic signing to MCP tool responses."""
|
|
85
|
+
if _signer and isinstance(response, dict):
|
|
86
|
+
try:
|
|
87
|
+
return _signer.sign(response)
|
|
88
|
+
except Exception:
|
|
89
|
+
pass
|
|
90
|
+
return response
|
|
91
|
+
|
|
64
92
|
# Learning System (v2.7+)
|
|
65
93
|
try:
|
|
66
94
|
sys.path.insert(0, str(Path(__file__).parent / "src"))
|
|
@@ -577,14 +605,14 @@ def _eager_init():
|
|
|
577
605
|
pass
|
|
578
606
|
try:
|
|
579
607
|
from behavioral.outcome_tracker import OutcomeTracker
|
|
580
|
-
OutcomeTracker(str(
|
|
608
|
+
OutcomeTracker(str(MEMORY_DIR / "learning.db"))
|
|
581
609
|
except Exception:
|
|
582
|
-
|
|
610
|
+
logging.getLogger("superlocalmemory.mcp").exception("OutcomeTracker eager initialization failed")
|
|
583
611
|
try:
|
|
584
612
|
from compliance.audit_db import AuditDB
|
|
585
|
-
AuditDB(str(
|
|
613
|
+
AuditDB(str(MEMORY_DIR / "audit.db"))
|
|
586
614
|
except Exception:
|
|
587
|
-
|
|
615
|
+
logging.getLogger("superlocalmemory.mcp").exception("AuditDB eager initialization failed")
|
|
588
616
|
|
|
589
617
|
# Run once at module load
|
|
590
618
|
_eager_init()
|
|
@@ -676,12 +704,12 @@ async def remember(
|
|
|
676
704
|
# Format response
|
|
677
705
|
preview = content[:100] + "..." if len(content) > 100 else content
|
|
678
706
|
|
|
679
|
-
return {
|
|
707
|
+
return _sign_response({
|
|
680
708
|
"success": True,
|
|
681
709
|
"memory_id": memory_id,
|
|
682
710
|
"message": f"Memory saved with ID {memory_id}",
|
|
683
711
|
"content_preview": preview
|
|
684
|
-
}
|
|
712
|
+
})
|
|
685
713
|
|
|
686
714
|
except Exception as e:
|
|
687
715
|
return {
|
|
@@ -805,13 +833,13 @@ async def recall(
|
|
|
805
833
|
if r.get('score', 0) >= min_score
|
|
806
834
|
]
|
|
807
835
|
|
|
808
|
-
return {
|
|
836
|
+
return _sign_response({
|
|
809
837
|
"success": True,
|
|
810
838
|
"query": query,
|
|
811
839
|
"results": filtered_results,
|
|
812
840
|
"count": len(filtered_results),
|
|
813
841
|
"total_searched": len(results)
|
|
814
|
-
}
|
|
842
|
+
})
|
|
815
843
|
|
|
816
844
|
except Exception as e:
|
|
817
845
|
return {
|
|
@@ -888,10 +916,10 @@ async def get_status() -> dict:
|
|
|
888
916
|
# Call existing get_stats method
|
|
889
917
|
stats = store.get_stats()
|
|
890
918
|
|
|
891
|
-
return {
|
|
919
|
+
return _sign_response({
|
|
892
920
|
"success": True,
|
|
893
921
|
**stats
|
|
894
|
-
}
|
|
922
|
+
})
|
|
895
923
|
|
|
896
924
|
except Exception as e:
|
|
897
925
|
return {
|
|
@@ -1637,6 +1665,54 @@ async def project_context_prompt(project_name: str) -> str:
|
|
|
1637
1665
|
# SERVER STARTUP
|
|
1638
1666
|
# ============================================================================
|
|
1639
1667
|
|
|
1668
|
+
@mcp.tool(annotations=ToolAnnotations(
|
|
1669
|
+
readOnlyHint=True,
|
|
1670
|
+
destructiveHint=False,
|
|
1671
|
+
openWorldHint=False,
|
|
1672
|
+
))
|
|
1673
|
+
async def get_attribution() -> dict:
|
|
1674
|
+
"""
|
|
1675
|
+
Get creator attribution and provenance verification for SuperLocalMemory.
|
|
1676
|
+
|
|
1677
|
+
Returns creator information, license details, and verification status
|
|
1678
|
+
for the 3-layer Qualixar attribution system.
|
|
1679
|
+
|
|
1680
|
+
Returns:
|
|
1681
|
+
{
|
|
1682
|
+
"creator": str,
|
|
1683
|
+
"license": str,
|
|
1684
|
+
"platform": str,
|
|
1685
|
+
"layers": {
|
|
1686
|
+
"visible": bool,
|
|
1687
|
+
"cryptographic": bool,
|
|
1688
|
+
"steganographic": bool
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
"""
|
|
1692
|
+
try:
|
|
1693
|
+
store = get_store()
|
|
1694
|
+
attribution = store.get_attribution()
|
|
1695
|
+
|
|
1696
|
+
return _sign_response({
|
|
1697
|
+
"success": True,
|
|
1698
|
+
**attribution,
|
|
1699
|
+
"website": "https://superlocalmemory.com",
|
|
1700
|
+
"author_website": "https://varunpratap.com",
|
|
1701
|
+
"attribution_layers": {
|
|
1702
|
+
"layer1_visible": True,
|
|
1703
|
+
"layer2_cryptographic": ATTRIBUTION_AVAILABLE,
|
|
1704
|
+
"layer3_steganographic": ATTRIBUTION_AVAILABLE,
|
|
1705
|
+
},
|
|
1706
|
+
})
|
|
1707
|
+
|
|
1708
|
+
except Exception as e:
|
|
1709
|
+
return {
|
|
1710
|
+
"success": False,
|
|
1711
|
+
"error": _sanitize_error(e),
|
|
1712
|
+
"message": "Failed to get attribution"
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
|
|
1640
1716
|
if __name__ == "__main__":
|
|
1641
1717
|
import argparse
|
|
1642
1718
|
|
|
@@ -1662,7 +1738,7 @@ if __name__ == "__main__":
|
|
|
1662
1738
|
# Print startup message to stderr (stdout is used for MCP protocol)
|
|
1663
1739
|
print("=" * 60, file=sys.stderr)
|
|
1664
1740
|
print("SuperLocalMemory V2 - MCP Server", file=sys.stderr)
|
|
1665
|
-
print("Version: 2.
|
|
1741
|
+
print("Version: 2.8.3", file=sys.stderr)
|
|
1666
1742
|
print("=" * 60, file=sys.stderr)
|
|
1667
1743
|
print("Created by: Varun Pratap Bhardwaj (Solution Architect)", file=sys.stderr)
|
|
1668
1744
|
print("Repository: https://github.com/varun369/SuperLocalMemoryV2", file=sys.stderr)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "superlocalmemory",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.6",
|
|
4
4
|
"description": "Your AI Finally Remembers You - Local-first intelligent memory system for AI assistants. Works with Claude, Cursor, Windsurf, VS Code/Copilot, Codex, and 17+ AI tools. 100% local, zero cloud dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-memory",
|
package/src/memory_store_v2.py
CHANGED
|
@@ -1105,7 +1105,8 @@ class MemoryStoreV2:
|
|
|
1105
1105
|
|
|
1106
1106
|
# Qualixar platform provenance (non-breaking additions)
|
|
1107
1107
|
attribution['platform'] = 'Qualixar'
|
|
1108
|
-
attribution['
|
|
1108
|
+
attribution['website'] = 'https://superlocalmemory.com'
|
|
1109
|
+
attribution['author_website'] = 'https://varunpratap.com'
|
|
1109
1110
|
|
|
1110
1111
|
return attribution
|
|
1111
1112
|
|
|
@@ -1136,7 +1137,16 @@ class MemoryStoreV2:
|
|
|
1136
1137
|
output.append(entry)
|
|
1137
1138
|
char_count += len(entry)
|
|
1138
1139
|
|
|
1139
|
-
|
|
1140
|
+
text = ''.join(output)
|
|
1141
|
+
|
|
1142
|
+
# Layer 3: Steganographic watermark on text exports
|
|
1143
|
+
try:
|
|
1144
|
+
from qualixar_watermark import encode_watermark
|
|
1145
|
+
text = encode_watermark(text, "slm")
|
|
1146
|
+
except ImportError:
|
|
1147
|
+
pass
|
|
1148
|
+
|
|
1149
|
+
return text
|
|
1140
1150
|
|
|
1141
1151
|
|
|
1142
1152
|
# CLI interface (V1 compatible + V2 extensions)
|
package/ui_server.py
CHANGED
|
@@ -20,6 +20,9 @@ import sys
|
|
|
20
20
|
from pathlib import Path
|
|
21
21
|
from datetime import datetime
|
|
22
22
|
|
|
23
|
+
_script_dir = str(Path(__file__).parent.resolve())
|
|
24
|
+
sys.path = [p for p in sys.path if p not in ("", _script_dir)]
|
|
25
|
+
|
|
23
26
|
try:
|
|
24
27
|
from fastapi import FastAPI
|
|
25
28
|
from fastapi.staticfiles import StaticFiles
|
|
@@ -33,6 +36,8 @@ except ImportError:
|
|
|
33
36
|
"Install with: pip install 'fastapi[all]' uvicorn websockets"
|
|
34
37
|
)
|
|
35
38
|
|
|
39
|
+
sys.path.insert(0, _script_dir)
|
|
40
|
+
|
|
36
41
|
from security_middleware import SecurityHeadersMiddleware
|
|
37
42
|
|
|
38
43
|
# Add src/ and routes/ to path
|
|
@@ -50,7 +55,7 @@ app = FastAPI(
|
|
|
50
55
|
description="Real-Time Memory Dashboard with Event Bus, Agent Registry, and Trust Scoring",
|
|
51
56
|
version="2.5.0",
|
|
52
57
|
docs_url="/api/docs",
|
|
53
|
-
redoc_url="/api/redoc"
|
|
58
|
+
redoc_url="/api/redoc",
|
|
54
59
|
)
|
|
55
60
|
|
|
56
61
|
# Middleware (order matters: security headers should be outermost)
|
|
@@ -59,9 +64,9 @@ app.add_middleware(GZipMiddleware, minimum_size=1000)
|
|
|
59
64
|
app.add_middleware(
|
|
60
65
|
CORSMiddleware,
|
|
61
66
|
allow_origins=[
|
|
62
|
-
"http://localhost:8765",
|
|
67
|
+
"http://localhost:8765", # Dashboard
|
|
63
68
|
"http://127.0.0.1:8765",
|
|
64
|
-
"http://localhost:8417",
|
|
69
|
+
"http://localhost:8417", # MCP
|
|
65
70
|
"http://127.0.0.1:8417",
|
|
66
71
|
],
|
|
67
72
|
allow_credentials=True,
|
|
@@ -84,10 +89,11 @@ try:
|
|
|
84
89
|
allowed, remaining = limiter.is_allowed(client_ip)
|
|
85
90
|
if not allowed:
|
|
86
91
|
from fastapi.responses import JSONResponse
|
|
92
|
+
|
|
87
93
|
return JSONResponse(
|
|
88
94
|
status_code=429,
|
|
89
95
|
content={"error": "Too many requests. Please slow down."},
|
|
90
|
-
headers={"Retry-After": str(limiter.window)}
|
|
96
|
+
headers={"Retry-After": str(limiter.window)},
|
|
91
97
|
)
|
|
92
98
|
|
|
93
99
|
response = await call_next(request)
|
|
@@ -107,9 +113,12 @@ try:
|
|
|
107
113
|
headers = dict(request.headers)
|
|
108
114
|
if not check_api_key(headers, is_write=is_write):
|
|
109
115
|
from fastapi.responses import JSONResponse
|
|
116
|
+
|
|
110
117
|
return JSONResponse(
|
|
111
118
|
status_code=401,
|
|
112
|
-
content={
|
|
119
|
+
content={
|
|
120
|
+
"error": "Invalid or missing API key. Set X-SLM-API-Key header."
|
|
121
|
+
},
|
|
113
122
|
)
|
|
114
123
|
response = await call_next(request)
|
|
115
124
|
return response
|
|
@@ -136,6 +145,7 @@ from routes.ws import router as ws_router, manager as ws_manager
|
|
|
136
145
|
# v2.7 Learning routes (graceful)
|
|
137
146
|
try:
|
|
138
147
|
from routes.learning import router as learning_router
|
|
148
|
+
|
|
139
149
|
LEARNING_ROUTES = True
|
|
140
150
|
except ImportError:
|
|
141
151
|
LEARNING_ROUTES = False
|
|
@@ -154,18 +164,21 @@ if LEARNING_ROUTES:
|
|
|
154
164
|
# v2.8 routes (graceful — don't fail if engines unavailable)
|
|
155
165
|
try:
|
|
156
166
|
from routes.lifecycle import router as lifecycle_router
|
|
167
|
+
|
|
157
168
|
app.include_router(lifecycle_router)
|
|
158
169
|
except ImportError:
|
|
159
170
|
pass
|
|
160
171
|
|
|
161
172
|
try:
|
|
162
173
|
from routes.behavioral import router as behavioral_router
|
|
174
|
+
|
|
163
175
|
app.include_router(behavioral_router)
|
|
164
176
|
except ImportError:
|
|
165
177
|
pass
|
|
166
178
|
|
|
167
179
|
try:
|
|
168
180
|
from routes.compliance import router as compliance_router
|
|
181
|
+
|
|
169
182
|
app.include_router(compliance_router)
|
|
170
183
|
except ImportError:
|
|
171
184
|
pass
|
|
@@ -173,6 +186,7 @@ except ImportError:
|
|
|
173
186
|
# Wire WebSocket manager into routes that need broadcast capability
|
|
174
187
|
import routes.profiles as _profiles_mod
|
|
175
188
|
import routes.data_io as _data_io_mod
|
|
189
|
+
|
|
176
190
|
_profiles_mod.ws_manager = ws_manager
|
|
177
191
|
_data_io_mod.ws_manager = ws_manager
|
|
178
192
|
|
|
@@ -180,6 +194,7 @@ _data_io_mod.ws_manager = ws_manager
|
|
|
180
194
|
# Basic Routes (root page + health check)
|
|
181
195
|
# ============================================================================
|
|
182
196
|
|
|
197
|
+
|
|
183
198
|
@app.get("/", response_class=HTMLResponse)
|
|
184
199
|
async def root():
|
|
185
200
|
"""Serve main UI page."""
|
|
@@ -206,7 +221,7 @@ async def health_check():
|
|
|
206
221
|
"status": "healthy",
|
|
207
222
|
"version": "2.5.0",
|
|
208
223
|
"database": "connected" if DB_PATH.exists() else "missing",
|
|
209
|
-
"timestamp": datetime.now().isoformat()
|
|
224
|
+
"timestamp": datetime.now().isoformat(),
|
|
210
225
|
}
|
|
211
226
|
|
|
212
227
|
|
|
@@ -214,6 +229,7 @@ async def health_check():
|
|
|
214
229
|
# Startup Events
|
|
215
230
|
# ============================================================================
|
|
216
231
|
|
|
232
|
+
|
|
217
233
|
@app.on_event("startup")
|
|
218
234
|
async def startup_event():
|
|
219
235
|
"""Register Event Bus listener for SSE bridge on startup."""
|
|
@@ -229,8 +245,12 @@ if __name__ == "__main__":
|
|
|
229
245
|
import socket
|
|
230
246
|
|
|
231
247
|
parser = argparse.ArgumentParser(description="SuperLocalMemory V2 - Web Dashboard")
|
|
232
|
-
parser.add_argument(
|
|
233
|
-
|
|
248
|
+
parser.add_argument(
|
|
249
|
+
"--port", type=int, default=8765, help="Port to run on (default 8765)"
|
|
250
|
+
)
|
|
251
|
+
parser.add_argument(
|
|
252
|
+
"--profile", type=str, default=None, help="Memory profile to use"
|
|
253
|
+
)
|
|
234
254
|
args = parser.parse_args()
|
|
235
255
|
|
|
236
256
|
def find_available_port(preferred):
|
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
# Security Quick Reference — For Developers
|
|
2
|
-
|
|
3
|
-
**Last Updated:** March 4, 2026
|
|
4
|
-
|
|
5
|
-
## TL;DR — Safe Coding Patterns
|
|
6
|
-
|
|
7
|
-
### ✅ DO THIS (Safe)
|
|
8
|
-
|
|
9
|
-
```javascript
|
|
10
|
-
// Safe: Use textContent for plain text
|
|
11
|
-
element.textContent = userContent;
|
|
12
|
-
|
|
13
|
-
// Safe: Use escapeHtml() before DOM insertion
|
|
14
|
-
element.innerHTML = escapeHtml(userContent);
|
|
15
|
-
|
|
16
|
-
// Safe: Create elements programmatically
|
|
17
|
-
const div = document.createElement('div');
|
|
18
|
-
div.textContent = userContent;
|
|
19
|
-
parent.appendChild(div);
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
### ❌ DON'T DO THIS (Dangerous)
|
|
23
|
-
|
|
24
|
-
```javascript
|
|
25
|
-
// DANGEROUS: Direct assignment with user content
|
|
26
|
-
element.innerHTML = userContent; // XSS VULNERABLE
|
|
27
|
-
|
|
28
|
-
// DANGEROUS: Using code evaluation with user input
|
|
29
|
-
// Never use eval, Function constructor, or similar
|
|
30
|
-
|
|
31
|
-
// DANGEROUS: Inline event handlers with user content
|
|
32
|
-
element.setAttribute('onclick', userCode); // XSS VULNERABLE
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
## Security Checklist — Before Committing
|
|
36
|
-
|
|
37
|
-
- [ ] No direct innerHTML assignments without escapeHtml()
|
|
38
|
-
- [ ] All user content either uses textContent or is escaped
|
|
39
|
-
- [ ] No code evaluation with user input
|
|
40
|
-
- [ ] No inline event handlers with dynamic content
|
|
41
|
-
- [ ] All API endpoints return explicit Content-Type
|
|
42
|
-
- [ ] SQL queries use parameterized statements (never string concatenation)
|
|
43
|
-
- [ ] No secrets in code or config files
|
|
44
|
-
- [ ] Run security tests: pytest tests/test_security_headers.py -v
|
|
45
|
-
|
|
46
|
-
## Common Patterns
|
|
47
|
-
|
|
48
|
-
### Rendering User-Generated Content
|
|
49
|
-
|
|
50
|
-
**Pattern 1: Plain Text Only**
|
|
51
|
-
```javascript
|
|
52
|
-
const contentDiv = document.getElementById('memory-content');
|
|
53
|
-
contentDiv.textContent = memory.content; // Automatically safe
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
**Pattern 2: Mixed Content (HTML Structure + User Text)**
|
|
57
|
-
```javascript
|
|
58
|
-
const html = `
|
|
59
|
-
<div class="memory-card">
|
|
60
|
-
<h3>${escapeHtml(memory.title)}</h3>
|
|
61
|
-
<p>${escapeHtml(memory.content)}</p>
|
|
62
|
-
<span class="badge">${escapeHtml(memory.category)}</span>
|
|
63
|
-
</div>
|
|
64
|
-
`;
|
|
65
|
-
container.innerHTML = html;
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
**Pattern 3: Building DOM Programmatically (Most Secure)**
|
|
69
|
-
```javascript
|
|
70
|
-
const card = document.createElement('div');
|
|
71
|
-
card.className = 'memory-card';
|
|
72
|
-
|
|
73
|
-
const title = document.createElement('h3');
|
|
74
|
-
title.textContent = memory.title;
|
|
75
|
-
|
|
76
|
-
const content = document.createElement('p');
|
|
77
|
-
content.textContent = memory.content;
|
|
78
|
-
|
|
79
|
-
card.appendChild(title);
|
|
80
|
-
card.appendChild(content);
|
|
81
|
-
container.appendChild(card);
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
### FastAPI Routes
|
|
85
|
-
|
|
86
|
-
**Safe Pattern:**
|
|
87
|
-
```python
|
|
88
|
-
@router.get("/api/memories")
|
|
89
|
-
async def get_memories():
|
|
90
|
-
# FastAPI automatically sets Content-Type: application/json
|
|
91
|
-
return {
|
|
92
|
-
"memories": memories, # User content here is safe in JSON
|
|
93
|
-
"total": len(memories)
|
|
94
|
-
}
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
**SQL Queries:**
|
|
98
|
-
```python
|
|
99
|
-
# SAFE: Parameterized query
|
|
100
|
-
cursor.execute("SELECT * FROM memories WHERE id = ?", (memory_id,))
|
|
101
|
-
|
|
102
|
-
# DANGEROUS: String concatenation (SQL injection)
|
|
103
|
-
# Never use f-strings or concatenation for SQL queries
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
## Testing XSS Protection
|
|
107
|
-
|
|
108
|
-
```python
|
|
109
|
-
# Test that XSS payloads are handled safely
|
|
110
|
-
def test_xss_payload():
|
|
111
|
-
payload = "<script>alert('xss')</script>"
|
|
112
|
-
|
|
113
|
-
# This should be safe when:
|
|
114
|
-
# 1. Returned as JSON with Content-Type: application/json
|
|
115
|
-
# 2. Rendered with escapeHtml() on client
|
|
116
|
-
# 3. Protected by CSP headers
|
|
117
|
-
|
|
118
|
-
response = client.get(f"/api/memory?content={payload}")
|
|
119
|
-
assert response.headers["Content-Type"] == "application/json"
|
|
120
|
-
assert response.headers["X-Content-Type-Options"] == "nosniff"
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
## Security Headers Reference
|
|
124
|
-
|
|
125
|
-
All responses include these headers automatically via SecurityHeadersMiddleware:
|
|
126
|
-
|
|
127
|
-
- X-Content-Type-Options: nosniff
|
|
128
|
-
- X-Frame-Options: DENY
|
|
129
|
-
- X-XSS-Protection: 1; mode=block
|
|
130
|
-
- Content-Security-Policy: (comprehensive policy)
|
|
131
|
-
- Referrer-Policy: strict-origin-when-cross-origin
|
|
132
|
-
- Cache-Control: no-store (API endpoints only)
|
|
133
|
-
|
|
134
|
-
**Don't override these headers** unless you have a specific security reason and understand the implications.
|
|
135
|
-
|
|
136
|
-
## CORS Configuration
|
|
137
|
-
|
|
138
|
-
Current allowed origins:
|
|
139
|
-
- http://localhost:8765
|
|
140
|
-
- http://127.0.0.1:8765
|
|
141
|
-
- http://localhost:8417
|
|
142
|
-
- http://127.0.0.1:8417
|
|
143
|
-
|
|
144
|
-
**To add a new origin:**
|
|
145
|
-
1. Add to the allow_origins list in ui_server.py
|
|
146
|
-
2. Document why it's needed
|
|
147
|
-
3. Never use wildcard * in production
|
|
148
|
-
|
|
149
|
-
## When to Update Security
|
|
150
|
-
|
|
151
|
-
### Add New Route
|
|
152
|
-
- [ ] Verify return type has explicit Content-Type
|
|
153
|
-
- [ ] Test with XSS payloads
|
|
154
|
-
- [ ] Add test case to test_security_headers.py
|
|
155
|
-
|
|
156
|
-
### Add New JavaScript
|
|
157
|
-
- [ ] Use textContent for plain text
|
|
158
|
-
- [ ] Use escapeHtml() for mixed content
|
|
159
|
-
- [ ] Test rendering with XSS payloads
|
|
160
|
-
|
|
161
|
-
### Modify Security Headers
|
|
162
|
-
- [ ] Document reason in commit message
|
|
163
|
-
- [ ] Update SECURITY.md
|
|
164
|
-
- [ ] Run full security test suite
|
|
165
|
-
- [ ] Consider security implications
|
|
166
|
-
|
|
167
|
-
## Resources
|
|
168
|
-
|
|
169
|
-
- **Full Security Policy:** See SECURITY.md
|
|
170
|
-
- **Security Enhancement Details:** .backup/security-enhancement-2026-03-04.md
|
|
171
|
-
- **Security Tests:** tests/test_security_headers.py
|
|
172
|
-
- **Middleware Source:** security_middleware.py
|
|
173
|
-
- **Client-Side Escaping:** ui/app.js (escapeHtml function)
|
|
174
|
-
|
|
175
|
-
## Quick Test Commands
|
|
176
|
-
|
|
177
|
-
```bash
|
|
178
|
-
# Run security tests only
|
|
179
|
-
pytest tests/test_security_headers.py -v
|
|
180
|
-
|
|
181
|
-
# Run all tests
|
|
182
|
-
pytest tests/ -x -q
|
|
183
|
-
|
|
184
|
-
# Manual verification (browser)
|
|
185
|
-
python3 ui_server.py
|
|
186
|
-
# Open http://localhost:8765
|
|
187
|
-
# DevTools → Network → Check response headers
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
## Red Flags — Stop and Review
|
|
191
|
-
|
|
192
|
-
If you see any of these patterns, STOP and review:
|
|
193
|
-
|
|
194
|
-
- innerHTML with user content (without escaping)
|
|
195
|
-
- Code evaluation functions anywhere
|
|
196
|
-
- String concatenation in SQL queries
|
|
197
|
-
- CORS with wildcard *
|
|
198
|
-
- User input in event handler attributes
|
|
199
|
-
- Disabling CSP or security headers
|
|
200
|
-
- Secrets or API keys in code
|
|
201
|
-
|
|
202
|
-
## When in Doubt
|
|
203
|
-
|
|
204
|
-
**Ask these questions:**
|
|
205
|
-
1. Could a user inject a script tag here?
|
|
206
|
-
2. Is this content escaped before rendering?
|
|
207
|
-
3. Does this SQL query use parameters?
|
|
208
|
-
4. Could this header configuration weaken security?
|
|
209
|
-
|
|
210
|
-
**If unsure:** Use the safest pattern (textContent or programmatic DOM creation).
|
|
211
|
-
|
|
212
|
-
---
|
|
213
|
-
|
|
214
|
-
**Remember:** Defense in depth. We have multiple layers (headers, CSP, escaping). Use all of them.
|